比如客户端是 ipv6 ,我可能想在 server.OnConnect
(假如有这么一个 callback) 时候设置一个 context.WithValue(conn.context, "is-ipv6", "true")
然后在 handle 里判断 context.Value("is-ipv6") == "true"
来进行判断并处理一些逻辑
当然只是举个栗子,并不一定真是判断是不是 ipv6
再比如,https 我想在 HandshakeComplete
时获取一下客户端证书的内容( DN/CN 啥的),我知道可以在 handle 获取 peercertificate ,但要是想计算 JA3 指纹呢 看了看只有在 clienthello 阶段能获取到链接的一些信息,在 handle 里就没有了,关键在tls.Config.GetConfigForClient/tls.Config.VerifyPeerConnection
等 callback 中,也没法设置请求上下文的值。
然后重新总结一下问题,就是在 http/https 的连接建立时候有什么方法或者怎么实现 设置仅限当前请求有效的 context 值呢?
1
Nazz 2023-04-23 09:38:43 +08:00 1
需要库 /框架的支持, 试试 gnet, netpoll
|
2
0o0O0o0O0o 2023-04-23 09:41:32 +08:00 via iPhone
把 ja3 ( tls )的逻辑放进 http handle ,我觉得已经是高度定制了,建议自己实现这样的 http 库。
|
3
Nazz 2023-04-23 09:46:12 +08:00 1
nbio 也是支持的 OnOpen, SetSession 的, 比其他异步框架更易用一些
|
4
sofukwird 2023-04-23 10:38:13 +08:00 via Android
放 header 里,自定义 header 字段 X-Custom-Field ,每次进来都初始化默认值,避免客户端注入 header
|
6
aladdinding 2023-04-23 10:51:20 +08:00 1
重写 conn 的 read 方法就行了,可以看下 cmux 这个库
|
7
lesismal 2023-04-23 10:52:00 +08:00 1
如果只是要知道 ip 相关,使用 RemoteAddr 就可以了。
如果是需要其他信息(但楼主不讲明是需要什么的前提下我还真想不出 conn 除了 remote addr 还需要啥信息这么有必要去特殊处理),基于标准库包装一下 Listener 自己 map 映射存上也可以,或者基于 nbio OnOpen 。 基于标准库和 nbio 的区别是: 基于标准库的方式在 handler 里没法拿到 Conn ,因为只有 Hijack 一种方法拿到,但拿到后标准库 http server 就不继续处理该 Conn 了、Hijack 得到的 Conn 处理权转给了用户; nbio 的 http handler 里除了 Hijack 的方式、也可以通过类型断言拿到 Conn 并且不涉及处理权的转移。 完整代码: https://gist.github.com/lesismal/316b711a7f39cc539cebaad6c8e5b0fd 代码只示例了 4 层的 Conn ,如果需要 tls ,另外再加点处理、OP 可以自行探索 |
8
lesismal 2023-04-23 11:01:06 +08:00 1
哦对了,基于标准库的代码部分,我忘记处理连接关闭时 Map.Delete 了,得看下 ConnState 之类的 Hook 能不能处理,如果不能处理,那基于标准库还不能这样简单搞、连接断开不清理就相当于泄露了导致 map 无限增长
|
9
lesismal 2023-04-23 11:08:47 +08:00 1
#8
修改了下,用标准库的没必要自己封装 Listener ,直接 ConnState 处理就可以了,代码更新了 ```golang package main import ( "context" "fmt" "net" "net/http" "os" "os/signal" "sync" "time" "github.com/lesismal/nbio/nbhttp" ) var connections = sync.Map{} func nbioOnEcho(w http.ResponseWriter, r *http.Request) { res, ok := w.(*nbhttp.Response) if ok { conn := res.Parser.Processor.Conn() yourData, ok := connections.Load(conn) if ok { fmt.Println("nbioServer onEcho:", yourData) } else { fmt.Println("nbioServer onEcho: not found connection") } } w.Write([]byte(time.Now().Format("20060102 15:04:05"))) } func nbioServer() { mux := &http.ServeMux{} mux.HandleFunc("/", nbioOnEcho) engine := nbhttp.NewServer(nbhttp.Config{ Network: "tcp", Addrs: []string{"localhost:8080"}, Handler: mux, IOMod: nbhttp.IOModBlocking, }) engine.OnOpen(func(conn net.Conn) { fmt.Println("nbioServer onOpen:", conn.RemoteAddr()) yourData := "data: " + conn.RemoteAddr().String() // or other things connections.Store(conn, yourData) }) engine.OnClose(func(conn net.Conn, err error) { connections.Delete(conn) fmt.Println("nbioServer onClose:", conn.RemoteAddr(), err) }) err := engine.Start() if err != nil { fmt.Printf("nbio.Start failed: %v\n", err) return } interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) <-interrupt ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) defer cancel() engine.Shutdown(ctx) } func netOnEcho(w http.ResponseWriter, r *http.Request) { yourData, ok := connections.Load(r.RemoteAddr) if ok { fmt.Println("netServer onEcho:", yourData) } else { fmt.Println("netServer onEcho: not found connection " + r.RemoteAddr) } w.Write([]byte(time.Now().Format("20060102 15:04:05"))) } func netServer() { mux := &http.ServeMux{} mux.HandleFunc("/", netOnEcho) server := &http.Server{ Addr: "localhost:8080", Handler: mux, ConnState: func(conn net.Conn, state http.ConnState) { switch state { case http.StateNew: fmt.Println("netServer onOpen:", conn.RemoteAddr()) remoteAddr := conn.RemoteAddr().String() yourData := "data: " + remoteAddr // or other things connections.Store(remoteAddr, yourData) case http.StateActive: case http.StateIdle: case http.StateHijacked: case http.StateClosed: connections.Delete(conn) fmt.Println("netServer onClose:", conn.RemoteAddr()) } }, } err := server.ListenAndServe() fmt.Println("server exit:", err) } func main() { netServer() // nbioServer() } ``` |
10
dzdh OP 总结一下各位佬推荐的是 wrapper 思路。也未尝不可。我尝试一下。
|
11
0o0O0o0O0o 2023-04-23 11:25:50 +08:00 1
package main
import ( "context" "crypto/tls" "fmt" "net" "net/http" "reflect" ) 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: ":8443", ConnContext: SaveConnInContext, } server.ListenAndServeTLS("server.crt", "server.key") // server.ListenAndServe() } func myHandler(w http.ResponseWriter, r *http.Request) { conn := GetConn(r) switch c := conn.(type) { case *tls.Conn: fmt.Println(reflect.ValueOf(c).Elem().FieldByName("config")) } fmt.Fprintf(w, "%s\n", conn.RemoteAddr()) w.WriteHeader(200) } |
12
0o0O0o0O0o 2023-04-23 11:27:35 +08:00
像这样拿 conn 乃至 tls conn 是可以的,问题就是怎么从 tls conn 读 handshake 信息,无非是再给 conn 包一层,可是浪费性能,不如修改 tls 代码。
高度定制的逻辑没必要追求用原版。 |
14
lesismal 2023-04-23 13:04:14 +08:00
@0o0O0o0O0o SaveConnInContext 这个挺好,学到了
@dzdh 要不我把 nbio 的 tls 的 clientHello 以及 clientHello 的 marshal 暴露出来,然后就方便拿到了,我本地改了试了下是可以的了,暂时没有更新到 repo: https://gist.github.com/lesismal/316b711a7f39cc539cebaad6c8e5b0fd?permalink_comment_id=4545513#gistcomment-4545513 但是 nbio 只支持 http1.x ,http2.0/3.0QUIC 功能太多、我短期内没精力去做了。 或者 OP 也可以另一种方案,还是先自己封装下 Listener ,tls Accept 得到的 tls Conn 用 RemoteAddr string 存 map 里,handler 里取出 tls Conn ,并且反射或者自己 type 一个相同 fields 的结构体但是 ClientHello 大写导出的方式+unsafe 指针强转类型后把 clientHello 这个字段取出来用,但是这样怕 go 不同版本该结构体字段发生变化,要自己额外做一些对应 go 不同版本编译的兼容 |
15
lesismal 2023-04-23 13:06:40 +08:00
@lesismal #14
> 或者 OP 也可以另一种方案,还是先自己封装下 Listener ,tls Accept 得到的 tls Conn 用 RemoteAddr string 存 map 里 ConnContext 就可以了,更简单 |
16
0o0O0o0O0o 2023-04-23 13:18:18 +08:00 via iPhone
@lesismal
虽说肯定能实现,但其实我觉得他这种需求还是丢给 nginx 去做比较好,也有现成的。 而且我觉得 tls 很复杂,我一直是不喜欢用 go 直接 serve tls ,我平时对付这种需求都是 docker compose 里套一个 nginx 。。。 |
17
lesismal 2023-04-23 13:38:42 +08:00
@0o0O0o0O0o
其实用 go 最大的好处是:可以省去 nginx 了。:joy: |
18
lesismal 2023-04-23 13:40:30 +08:00 1
#17 go 性能、占用、开发效率能达到非常好的平衡。自定制起来也比 nginx lua/resty 要更有潜力。
|
19
lesismal 2023-04-23 13:42:45 +08:00
@0o0O0o0O0o 我们的一些新项目,没有历史包袱,就没有部署 nginx 了。但很多团队里,nginx 这些似乎成了一种技术惯性,不管有没有必要、都部署,整个社区需要很长时间去摆脱这个惯性
|