The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
dzdh

怎么在 http sever 的链接握手时设置一些变量能在 handle 里接收到呢?

  •  
  •   dzdh · Apr 23, 2023 · 2463 views
  •   You need to sign in to view this topic
    This topic created in 1236 days ago, the information mentioned may be changed or developed.

    比如客户端是 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 值呢?

    19 replies    2023-04-23 13:42:45 +08:00
    Nazz
        1
    Nazz  
       Apr 23, 2023   ❤️ 1
    需要库 /框架的支持, 试试 gnet, netpoll
    0o0O0o0O0o
        2
    0o0O0o0O0o  
       Apr 23, 2023 via iPhone
    把 ja3 ( tls )的逻辑放进 http handle ,我觉得已经是高度定制了,建议自己实现这样的 http 库。
    Nazz
        3
    Nazz  
       Apr 23, 2023   ❤️ 1
    nbio 也是支持的 OnOpen, SetSession 的, 比其他异步框架更易用一些
    sofukwird
        4
    sofukwird  
       Apr 23, 2023 via Android
    放 header 里,自定义 header 字段 X-Custom-Field ,每次进来都初始化默认值,避免客户端注入 header
    dzdh
        5
    dzdh  
    OP
       Apr 23, 2023
    @sofukwird verifypeer 操作不了 request
    aladdinding
        6
    aladdinding  
       Apr 23, 2023   ❤️ 1
    重写 conn 的 read 方法就行了,可以看下 cmux 这个库
    lesismal
        7
    lesismal  
       Apr 23, 2023   ❤️ 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 可以自行探索
    lesismal
        8
    lesismal  
       Apr 23, 2023   ❤️ 1
    哦对了,基于标准库的代码部分,我忘记处理连接关闭时 Map.Delete 了,得看下 ConnState 之类的 Hook 能不能处理,如果不能处理,那基于标准库还不能这样简单搞、连接断开不清理就相当于泄露了导致 map 无限增长
    lesismal
        9
    lesismal  
       Apr 23, 2023   ❤️ 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()
    }
    ```
    dzdh
        10
    dzdh  
    OP
       Apr 23, 2023
    总结一下各位佬推荐的是 wrapper 思路。也未尝不可。我尝试一下。
    0o0O0o0O0o
        11
    0o0O0o0O0o  
       Apr 23, 2023   ❤️ 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)
    }
    0o0O0o0O0o
        12
    0o0O0o0O0o  
       Apr 23, 2023
    像这样拿 conn 乃至 tls conn 是可以的,问题就是怎么从 tls conn 读 handshake 信息,无非是再给 conn 包一层,可是浪费性能,不如修改 tls 代码。

    高度定制的逻辑没必要追求用原版。
    sofukwird
        13
    sofukwird  
       Apr 23, 2023 via Android
    @dzdh 你可以参考 caddy 的做法,它能把证书信息写到 header 里
    lesismal
        14
    lesismal  
       Apr 23, 2023
    @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 不同版本编译的兼容
    lesismal
        15
    lesismal  
       Apr 23, 2023
    @lesismal #14

    > 或者 OP 也可以另一种方案,还是先自己封装下 Listener ,tls Accept 得到的 tls Conn 用 RemoteAddr string 存 map 里

    ConnContext 就可以了,更简单
    0o0O0o0O0o
        16
    0o0O0o0O0o  
       Apr 23, 2023 via iPhone
    @lesismal

    虽说肯定能实现,但其实我觉得他这种需求还是丢给 nginx 去做比较好,也有现成的。

    而且我觉得 tls 很复杂,我一直是不喜欢用 go 直接 serve tls ,我平时对付这种需求都是 docker compose 里套一个 nginx 。。。
    lesismal
        17
    lesismal  
       Apr 23, 2023
    @0o0O0o0O0o
    其实用 go 最大的好处是:可以省去 nginx 了。:joy:
    lesismal
        18
    lesismal  
       Apr 23, 2023   ❤️ 1
    #17 go 性能、占用、开发效率能达到非常好的平衡。自定制起来也比 nginx lua/resty 要更有潜力。
    lesismal
        19
    lesismal  
       Apr 23, 2023
    @0o0O0o0O0o 我们的一些新项目,没有历史包袱,就没有部署 nginx 了。但很多团队里,nginx 这些似乎成了一种技术惯性,不管有没有必要、都部署,整个社区需要很长时间去摆脱这个惯性
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   5236 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 48ms · UTC 03:46 · PVG 11:46 · LAX 20:46 · JFK 23:46
    ♥ Do have faith in what you're doing.