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

时间:2015-04-09 07:08:25

标签: sockets go

我是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的引用而不是完全接管。

6 个答案:

答案 0 :(得分:8)

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

相反,请查看Server.ConnState挂钩:您可以“注册”一个在连接状态发生变化时将被调用的函数,有关详细信息,请参阅http.ConnState。例如,在请求进入处理程序(http.StateNewhttp.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

的方法来扩充库