duanrebo3559 2015-04-09 07:08
浏览 27
已采纳

访问net / http响应的基础套接字

I'm new to Go and evaluating it for a project.

I'm trying to write a custom handler to serve files with net/http. I can't use the default http.FileServer() handler because I need to have access to the underlying socket (the internal net.Conn) so I can perform some informational platform specific "syscall" calls on it (mainly TCP_INFO).

More precisly: I need to access the underlying socket of the http.ResponseWriter in the handler function:

func myHandler(w http.ResponseWriter, r *http.Request) {
...
// I need the net.Conn of w
...
}

used in

http.HandleFunc("/", myHandler)

Is there a way to this. I looked at how websocket.Upgrade does this but it uses Hijack() which is 'too much' because then I have to code 'speaking http' over the raw tcp socket I get. I just want a reference to the socket and not taking over completely.

  • 写回答

6条回答 默认 最新

  • doudie2693 2019-03-24 22:47
    关注

    After Issue #30694 is completed, it looks like Go 1.13 will probably support storing the net.Conn in the Request Context, which makes this fairly clean and simple:

    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)
      ...
    }
    

    Until then ... For a server listening on a TCP port, net.Conn.RemoteAddr().String() is unique for each connection and is available to the http.Handler as r.RemoteAddr, so it can be used as a key to a global map of 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)
      ...
    }
    

    For a server listening on a UNIX socket, net.Conn.RemoteAddr().String() is always "@", so the above doesn't work. To make this work, we can override net.Listener.Accept(), and use that to override net.Conn.RemoteAddr().String() so that it returns a unique string for each connection:

    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
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(5条)

报告相同问题?

悬赏问题

  • ¥30 seata使用出现报错,其他服务找不到seata
  • ¥35 引用csv数据文件(4列1800行),通过高斯-赛德尔法拟合曲线,在选取(每五十点取1点)数据,求该数据点的曲率中心。
  • ¥20 程序只发送0X01,串口助手显示不正确,配置看了没有问题115200-8-1-no,如何解决?
  • ¥15 Google speech command 数据集获取
  • ¥15 vue3+element-plus页面崩溃
  • ¥15 像这种代码要怎么跑起来?
  • ¥15 安卓C读取/dev/fastpipe屏幕像素数据
  • ¥15 pyqt5tools安装失败
  • ¥15 mmdetection
  • ¥15 nginx代理报502的错误