访问net/http响应的底层套接字

KGJ*_*GJV 6 sockets go

我是Go的新手并为项目进行评估.

我正在尝试编写一个自定义处理程序来提供文件net/http.我不能使用默认http.FileServer()处理程序,因为我需要访问底层套接字(内部net.Conn),所以我可以执行一些特定于信息平台的"系统调用"调用(主要是TCP_INFO).

更精确:我需要访问http.ResponseWriter处理函数中的底层套接字:

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

用于

http.HandleFunc("/", myHandler)
Run Code Online (Sandbox Code Playgroud)

有没有办法解决这个问题.我看看这是怎么websocket.Upgrade做的,但是它使用的Hijack()是"太多",因为那时我必须在我得到的原始tcp套接字上编码'说http'.我只想要一个对socket的引用而不是完全接管.

Pau*_*hue 9

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)
  ...
}
Run Code Online (Sandbox Code Playgroud)

在那之前...对于侦听 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)
  ...
}
Run Code Online (Sandbox Code Playgroud)

对于侦听 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
}
Run Code Online (Sandbox Code Playgroud)


icz*_*cza 8

请注意,虽然在当前实现中http.ResponseWriter是一个*http.response(注意小写!)来保存连接,但是该字段是未导出的,您无法访问它.

而是看一下Server.ConnState钩子:你可以"注册"一个在连接状态改变时调用的函数,详见http.ConnState详见.例如net.Conn,在请求进入处理程序(http.StateNewhttp.StateActive状态)之前,您将获得偶数.

您可以通过创建这样的自定义来安装连接状态侦听器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)
}
Run Code Online (Sandbox Code Playgroud)

这样,net.Conn即使在调用处理程序之前(以及期间和之后),您也可以获得完全所需的内容.缺点是它不与"配对",ResponseWriter如果需要,你必须手动完成.

  • 谢谢 !这似乎做了这个工作.将conn与相应的ResponseWriter"配对"确实很棘手,任何关于如何做到这一点的想法? (3认同)