我是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的引用而不是完全接管.
在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)
请注意,虽然在当前实现中http.ResponseWriter
是一个*http.response
(注意小写!)来保存连接,但是该字段是未导出的,您无法访问它.
而是看一下Server.ConnState
钩子:你可以"注册"一个在连接状态改变时调用的函数,详见http.ConnState
详见.例如net.Conn
,在请求进入处理程序(http.StateNew
和http.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
如果需要,你必须手动完成.
归档时间: |
|
查看次数: |
3936 次 |
最近记录: |