我是Go的新手并为项目进行评估。
我正在尝试编写自定义处理程序来提供net/http
的文件。
我无法使用默认的http.FileServer()
处理程序,因为我需要访问底层套接字(内部net.Conn
),因此我可以在其上执行一些特定于信息平台的“系统调用”调用(主要是{{ 1}})。
更精确:我需要访问处理函数中TCP_INFO
的底层套接字:
http.ResponseWriter
用于
func myHandler(w http.ResponseWriter, r *http.Request) {
...
// I need the net.Conn of w
...
}
有没有办法解决这个问题。我看了http.HandleFunc("/", myHandler)
如何做到这一点,但是它使用websocket.Upgrade
这是'太多',因为那时我必须在我得到的原始tcp套接字上编码'说http'。我只想要一个对socket的引用而不是完全接管。
答案 0 :(得分:8)
请注意,虽然在当前实现中http.ResponseWriter
是一个*http.response
(注意小写!),它保存了连接,但该字段未导出,您无法访问它。
相反,请查看Server.ConnState
挂钩:您可以“注册”一个在连接状态发生变化时将被调用的函数,有关详细信息,请参阅http.ConnState
。例如,在请求进入处理程序(http.StateNew
和http.StateActive
状态)之前,您将获得net.Conn
。
您可以通过创建这样的自定义Server
来安装连接状态侦听器:
func main() {
http.HandleFunc("/", myHandler)
s := &http.Server{
Addr: ":8081",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
ConnState: ConnStateListener,
}
panic(s.ListenAndServe())
}
func ConnStateListener(c net.Conn, cs http.ConnState) {
fmt.Printf("CONN STATE: %v, %v\n", cs, c)
}
这样,即使在调用处理程序之前(以及期间和之后),您也会拥有所需的net.Conn
。缺点是它没有与ResponseWriter
“配对”,如果需要,你必须手动完成。
答案 1 :(得分:3)
您可以使用HttpHijacker从ResponseWriter接管TCP连接。一旦你完成了,你可以自由地使用套接字做你想做的任何事情。
请参阅http://golang.org/pkg/net/http/#Hijacker,其中也包含一个很好的示例。
答案 2 :(得分:1)
扩展KGJV's answer,这是一个使用反射来维护由net.Conn实例内存地址索引的连接映射的工作解决方案。
net.Conn的实例可以通过指针查找,并使用针对http.Response的反射派生指针。
这有点令人讨厌,但鉴于你无法通过反射访问未发布的字段,这是我能看到的唯一方法。
// Connection array indexed by connection address
var conns = make(map[uintptr]net.Conn)
var connMutex = sync.Mutex{}
// writerToConnPrt converts an http.ResponseWriter to a pointer for indexing
func writerToConnPtr(w http.ResponseWriter) uintptr {
ptrVal := reflect.ValueOf(w)
val := reflect.Indirect(ptrVal)
// http.conn
valconn := val.FieldByName("conn")
val1 := reflect.Indirect(valconn)
// net.TCPConn
ptrRwc := val1.FieldByName("rwc").Elem()
rwc := reflect.Indirect(ptrRwc)
// net.Conn
val1conn := rwc.FieldByName("conn")
val2 := reflect.Indirect(val1conn)
return val2.Addr().Pointer()
}
// connToPtr converts a net.Conn into a pointer for indexing
func connToPtr(c net.Conn) uintptr {
ptrVal := reflect.ValueOf(c)
return ptrVal.Pointer()
}
// ConnStateListener bound to server and maintains a list of connections by pointer
func ConnStateListener(c net.Conn, cs http.ConnState) {
connPtr := connToPtr(c)
connMutex.Lock()
defer connMutex.Unlock()
switch cs {
case http.StateNew:
log.Printf("CONN Opened: 0x%x\n", connPtr)
conns[connPtr] = c
case http.StateClosed:
log.Printf("CONN Closed: 0x%x\n", connPtr)
delete(conns, connPtr)
}
}
func HandleRequest(w http.ResponseWriter, r *http.Request) {
connPtr := writerToConnPtr(w)
connMutex.Lock()
defer connMutex.Unlock()
// Requests can access connections by pointer from the responseWriter object
conn, ok := conns[connPtr]
if !ok {
log.Printf("error: no matching connection found")
return
}
// Do something with connection here...
}
// Bind with http.Server.ConnState = ConnStateListener
答案 3 :(得分:1)
完成Issue #30694之后,Go 1.13可能会支持在请求上下文中存储net.Conn,这使得它相当干净和简单:
package main
import (
"net/http"
"context"
"net"
"log"
)
type contextKey struct {
key string
}
var ConnContextKey = &contextKey{"http-conn"}
func SaveConnInContext(ctx context.Context, c net.Conn) (context.Context) {
return context.WithValue(ctx, ConnContextKey, c)
}
func GetConn(r *http.Request) (net.Conn) {
return r.Context().Value(ConnContextKey).(net.Conn)
}
func main() {
http.HandleFunc("/", myHandler)
server := http.Server{
Addr: ":8080",
ConnContext: SaveConnInContext,
}
server.ListenAndServe()
}
func myHandler(w http.ResponseWriter, r *http.Request) {
conn := GetConn(r)
...
}
直到... ...对于在TCP端口上侦听的服务器,net.Conn.RemoteAddr()。String()对于每个连接都是唯一的,并且可以作为r.RemoteAddr供http.Handler使用,因此可以用作Conns全球地图的关键:
package main
import (
"net/http"
"net"
"fmt"
"log"
)
var conns = make(map[string]net.Conn)
func ConnStateEvent(conn net.Conn, event http.ConnState) {
if event == http.StateActive {
conns[conn.RemoteAddr().String()] = conn
} else if event == http.StateHijacked || event == http.StateClosed {
delete(conns, conn.RemoteAddr().String())
}
}
func GetConn(r *http.Request) (net.Conn) {
return conns[r.RemoteAddr]
}
func main() {
http.HandleFunc("/", myHandler)
server := http.Server{
Addr: ":8080",
ConnState: ConnStateEvent,
}
server.ListenAndServe()
}
func myHandler(w http.ResponseWriter, r *http.Request) {
conn := GetConn(r)
...
}
对于在UNIX套接字上侦听的服务器,net.Conn.RemoteAddr()。String()始终为“ @”,因此上述操作无效。为此,我们可以覆盖net.Listener.Accept(),并使用它覆盖net.Conn.RemoteAddr()。String(),以便为每个连接返回唯一的字符串:
package main
import (
"net/http"
"net"
"os"
"golang.org/x/sys/unix"
"fmt"
"log"
)
func main() {
http.HandleFunc("/", myHandler)
listenPath := "/var/run/go_server.sock"
l, err := NewUnixListener(listenPath)
if err != nil {
log.Fatal(err)
}
defer os.Remove(listenPath)
server := http.Server{
ConnState: ConnStateEvent,
}
server.Serve(NewConnSaveListener(l))
}
func myHandler(w http.ResponseWriter, r *http.Request) {
conn := GetConn(r)
if unixConn, isUnix := conn.(*net.UnixConn); isUnix {
f, _ := unixConn.File()
pcred, _ := unix.GetsockoptUcred(int(f.Fd()), unix.SOL_SOCKET, unix.SO_PEERCRED)
f.Close()
log.Printf("Remote UID: %d", pcred.Uid)
}
}
var conns = make(map[string]net.Conn)
type connSaveListener struct {
net.Listener
}
func NewConnSaveListener(wrap net.Listener) (net.Listener) {
return connSaveListener{wrap}
}
func (self connSaveListener) Accept() (net.Conn, error) {
conn, err := self.Listener.Accept()
ptrStr := fmt.Sprintf("%d", &conn)
conns[ptrStr] = conn
return remoteAddrPtrConn{conn, ptrStr}, err
}
func GetConn(r *http.Request) (net.Conn) {
return conns[r.RemoteAddr]
}
func ConnStateEvent(conn net.Conn, event http.ConnState) {
if event == http.StateHijacked || event == http.StateClosed {
delete(conns, conn.RemoteAddr().String())
}
}
type remoteAddrPtrConn struct {
net.Conn
ptrStr string
}
func (self remoteAddrPtrConn) RemoteAddr() (net.Addr) {
return remoteAddrPtr{self.ptrStr}
}
type remoteAddrPtr struct {
ptrStr string
}
func (remoteAddrPtr) Network() (string) {
return ""
}
func (self remoteAddrPtr) String() (string) {
return self.ptrStr
}
func NewUnixListener(path string) (net.Listener, error) {
if err := unix.Unlink(path); err != nil && !os.IsNotExist(err) {
return nil, err
}
mask := unix.Umask(0777)
defer unix.Umask(mask)
l, err := net.Listen("unix", path)
if err != nil {
return nil, err
}
if err := os.Chmod(path, 0660); err != nil {
l.Close()
return nil, err
}
return l, nil
}
答案 4 :(得分:0)
看起来你不能&#34;配对&#34;一个socket(或net.Conn)到http.Request或http.ResponseWriter。
但是你可以实现自己的监听器:
package main
import (
"fmt"
"net"
"net/http"
"time"
"log"
)
func main() {
// init http server
m := &MyHandler{}
s := &http.Server{
Handler: m,
}
// create custom listener
nl, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
l := &MyListener{nl}
// serve through custom listener
err = s.Serve(l)
if err != nil {
log.Fatal(err)
}
}
// net.Conn
type MyConn struct {
nc net.Conn
}
func (c MyConn) Read(b []byte) (n int, err error) {
return c.nc.Read(b)
}
func (c MyConn) Write(b []byte) (n int, err error) {
return c.nc.Write(b)
}
func (c MyConn) Close() error {
return c.nc.Close()
}
func (c MyConn) LocalAddr() net.Addr {
return c.nc.LocalAddr()
}
func (c MyConn) RemoteAddr() net.Addr {
return c.nc.RemoteAddr()
}
func (c MyConn) SetDeadline(t time.Time) error {
return c.nc.SetDeadline(t)
}
func (c MyConn) SetReadDeadline(t time.Time) error {
return c.nc.SetReadDeadline(t)
}
func (c MyConn) SetWriteDeadline(t time.Time) error {
return c.nc.SetWriteDeadline(t)
}
// net.Listener
type MyListener struct {
nl net.Listener
}
func (l MyListener) Accept() (c net.Conn, err error) {
nc, err := l.nl.Accept()
if err != nil {
return nil, err
}
return MyConn{nc}, nil
}
func (l MyListener) Close() error {
return l.nl.Close()
}
func (l MyListener) Addr() net.Addr {
return l.nl.Addr()
}
// http.Handler
type MyHandler struct {
// ...
}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
答案 5 :(得分:0)
这可以通过反射来完成。它有点“脏”,但它有效:
package main
import "net/http"
import "fmt"
import "runtime"
import "reflect"
func myHandler(w http.ResponseWriter, r *http.Request) {
ptrVal := reflect.ValueOf(w)
val := reflect.Indirect(ptrVal)
// w is a "http.response" struct from which we get the 'conn' field
valconn := val.FieldByName("conn")
val1 := reflect.Indirect(valconn)
// which is a http.conn from which we get the 'rwc' field
ptrRwc := val1.FieldByName("rwc").Elem()
rwc := reflect.Indirect(ptrRwc)
// which is net.TCPConn from which we get the embedded conn
val1conn := rwc.FieldByName("conn")
val2 := reflect.Indirect(val1conn)
// which is a net.conn from which we get the 'fd' field
fdmember := val2.FieldByName("fd")
val3 := reflect.Indirect(fdmember)
// which is a netFD from which we get the 'sysfd' field
netFdPtr := val3.FieldByName("sysfd")
fmt.Printf("netFDPtr= %v\n", netFdPtr)
// which is the system socket (type is plateform specific - Int for linux)
if runtime.GOOS == "linux" {
fd := int(netFdPtr.Int())
fmt.Printf("fd = %v\n", fd)
// fd is the socket - we can call unix.Syscall6(unix.SYS_GETSOCKOPT, uintptr(fd),....) on it for instance
}
fmt.Fprintf(w, "Hello World")
}
func main() {
http.HandleFunc("/", myHandler)
err := http.ListenAndServe(":8081", nil)
fmt.Println(err.Error())
}
理想情况下,应该使用获取底层net.Conn
的方法来扩充库