Golang Go语言中怎么在http server的链接握手时设置一些变量能在handle里接收到呢?
比如客户端是 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 值呢?
Golang Go语言中怎么在http server的链接握手时设置一些变量能在handle里接收到呢?
更多关于Golang Go语言中怎么在http server的链接握手时设置一些变量能在handle里接收到呢?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
需要库 /框架的支持, 试试 gnet, netpoll
更多关于Golang Go语言中怎么在http server的链接握手时设置一些变量能在handle里接收到呢?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
把 ja3 ( tls )的逻辑放进 http handle ,我觉得已经是高度定制了,建议自己实现这样的 http 库。
nbio 也是支持的 OnOpen, SetSession 的, 比其他异步框架更易用一些
放 header 里,自定义 header 字段 X-Custom-Field ,每次进来都初始化默认值,避免客户端注入 header
verifypeer 操作不了 request
重写 conn 的 read 方法就行了,可以看下 cmux 这个库
如果只是要知道 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 可以自行探索
哦对了,基于标准库的代码部分,我忘记处理连接关闭时 Map.Delete 了,得看下 ConnState 之类的 Hook 能不能处理,如果不能处理,那基于标准库还不能这样简单搞、连接断开不清理就相当于泄露了导致 map 无限增长
#8
修改了下,用标准库的没必要自己封装 Listener ,直接 ConnState 处理就可以了,代码更新了golang<br>package main<br><br>import (<br> "context"<br> "fmt"<br> "net"<br> "net/http"<br> "os"<br> "os/signal"<br> "sync"<br> "time"<br><br> "<a target="_blank" href="http://github.com/lesismal/nbio/nbhttp" rel="nofollow noopener">github.com/lesismal/nbio/nbhttp</a>"<br>)<br><br>var connections = sync.Map{}<br><br>func nbioOnEcho(w http.ResponseWriter, r *http.Request) {<br> res, ok := w.(*nbhttp.Response)<br> if ok {<br> conn := res.Parser.Processor.Conn()<br> yourData, ok := connections.Load(conn)<br> if ok {<br> fmt.Println("nbioServer onEcho:", yourData)<br> } else {<br> fmt.Println("nbioServer onEcho: not found connection")<br> }<br> }<br> w.Write([]byte(time.Now().Format("20060102 15:04:05")))<br>}<br><br>func nbioServer() {<br> mux := &http.ServeMux{}<br> mux.HandleFunc("/", nbioOnEcho)<br><br> engine := nbhttp.NewServer(nbhttp.Config{<br> Network: "tcp",<br> Addrs: []string{"localhost:8080"},<br> Handler: mux,<br> IOMod: nbhttp.IOModBlocking,<br> })<br> engine.OnOpen(func(conn net.Conn) {<br> fmt.Println("nbioServer onOpen:", conn.RemoteAddr())<br> yourData := "data: " + conn.RemoteAddr().String() // or other things<br> connections.Store(conn, yourData)<br> })<br> engine.OnClose(func(conn net.Conn, err error) {<br> connections.Delete(conn)<br> fmt.Println("nbioServer onClose:", conn.RemoteAddr(), err)<br> })<br><br> err := engine.Start()<br> if err != nil {<br> fmt.Printf("nbio.Start failed: %v\n", err)<br> return<br> }<br><br> interrupt := make(chan os.Signal, 1)<br> signal.Notify(interrupt, os.Interrupt)<br> <-interrupt<br><br> ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)<br> defer cancel()<br> engine.Shutdown(ctx)<br>}<br><br>func netOnEcho(w http.ResponseWriter, r *http.Request) {<br> yourData, ok := connections.Load(r.RemoteAddr)<br> if ok {<br> fmt.Println("netServer onEcho:", yourData)<br> } else {<br> fmt.Println("netServer onEcho: not found connection " + r.RemoteAddr)<br> }<br> w.Write([]byte(time.Now().Format("20060102 15:04:05")))<br>}<br><br>func netServer() {<br> mux := &http.ServeMux{}<br> mux.HandleFunc("/", netOnEcho)<br><br> server := &http.Server{<br> Addr: "localhost:8080",<br> Handler: mux,<br> ConnState: func(conn net.Conn, state http.ConnState) {<br> switch state {<br><br> case http.StateNew:<br> fmt.Println("netServer onOpen:", conn.RemoteAddr())<br> remoteAddr := conn.RemoteAddr().String()<br> yourData := "data: " + remoteAddr // or other things<br> connections.Store(remoteAddr, yourData)<br> case http.StateActive:<br> case http.StateIdle:<br> case http.StateHijacked:<br> case http.StateClosed:<br> connections.Delete(conn)<br> fmt.Println("netServer onClose:", conn.RemoteAddr())<br> }<br> },<br> }<br> err := server.ListenAndServe()<br> fmt.Println("server exit:", err)<br>}<br><br>func main() {<br> netServer()<br> // nbioServer()<br>}<br>
总结一下各位佬推荐的是 wrapper 思路。也未尝不可。我尝试一下。
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®
switch c := conn.(type) {
case *tls.Conn:
fmt.Println(reflect.ValueOf©.Elem().FieldByName(“config”))
}
fmt.Fprintf(w, “%s\n”, conn.RemoteAddr())
w.WriteHeader(200)
}
像这样拿 conn 乃至 tls conn 是可以的,问题就是怎么从 tls conn 读 handshake 信息,无非是再给 conn 包一层,可是浪费性能,不如修改 tls 代码。
高度定制的逻辑没必要追求用原版。
你可以参考 caddy 的做法,它能把证书信息写到 header 里
SaveConnInContext 这个挺好,学到了
要不我把 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 不同版本编译的兼容
#14
> 或者 OP 也可以另一种方案,还是先自己封装下 Listener ,tls Accept 得到的 tls Conn 用 RemoteAddr string 存 map 里
ConnContext 就可以了,更简单
虽说肯定能实现,但其实我觉得他这种需求还是丢给 nginx 去做比较好,也有现成的。
而且我觉得 tls 很复杂,我一直是不喜欢用 go 直接 serve tls ,我平时对付这种需求都是 docker compose 里套一个 nginx 。。。
其实用 go 最大的好处是:可以省去 nginx 了。:joy:
#17 go 性能、占用、开发效率能达到非常好的平衡。自定制起来也比 nginx lua/resty 要更有潜力。
我们的一些新项目,没有历史包袱,就没有部署 nginx 了。但很多团队里,nginx 这些似乎成了一种技术惯性,不管有没有必要、都部署,整个社区需要很长时间去摆脱这个惯性
在Go语言中,要在HTTP服务器的连接握手时设置一些变量并在处理请求(handler)中接收到,通常可以使用上下文(context)来实现。Context提供了一种在API边界之间传递截止日期、取消信号和其他请求范围的值的方法。
以下是一个简单的示例,演示如何在HTTP服务器启动时设置变量,并在handler中通过context获取这些变量:
-
定义Context Key: 首先,定义一个唯一的context key,用于在context中存储和检索变量。
type contextKey string const myVarKey contextKey = "myVar"
-
在Server启动时设置Context: 在HTTP服务器处理请求之前,将变量设置到请求的context中。这通常通过中间件实现。
func myMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), myVarKey, "someValue") next.ServeHTTP(w, r.WithContext(ctx)) }) }
-
在Handler中获取变量: 在handler中,通过context获取变量。
func myHandler(w http.ResponseWriter, r *http.Request) { myVar := r.Context().Value(myVarKey).(string) fmt.Fprintf(w, "My Variable: %s\n", myVar) }
-
使用中间件: 将中间件应用到HTTP服务器。
http.ListenAndServe(":8080", myMiddleware(http.HandlerFunc(myHandler)))
这样,你就可以在HTTP服务器的连接握手时设置变量,并在处理请求时通过context接收到这些变量。