我是Go的新手,正在为一个项目进行评估。
我正在尝试编写一个自定义处理程序来为提供文件net/http。我无法使用默认http.FileServer()处理程序,因为我需要访问基础套接字(内部net.Conn),因此可以在其上执行一些信息平台特定的“ syscall”调用(主要是TCP_INFO)。
net/http
http.FileServer()
net.Conn
TCP_INFO
更确切地说:我需要http.ResponseWriter在处理程序函数中访问的基础套接字:
http.ResponseWriter
func myHandler(w http.ResponseWriter, r *http.Request) { ... // I need the net.Conn of w ... }
用在
http.HandleFunc("/", myHandler)
有没有办法做到这一点。我看了看这是怎么websocket.Upgrade做的,但是它使用了Hijack()“太多”的内容,因为然后我必须在获得的原始tcp套接字上编码“说HTTP”。我只想引用套接字,而不是完全接管。
websocket.Upgrade
Hijack()
在问题#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中使用,因此可以用作康恩斯全球地图的关键:
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 }