Go服务端如何记录MaxHeaderBytes触发的431请求头过大错误?
问题描述
我创建了如下Go服务器:
s := &http.Server{ Addr: ":8080", Handler: http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.URL.Path) }), MaxHeaderBytes: 1, } s.ListenAndServe()
随后我发送带有大请求头的请求以测试MaxHeaderBytes限制:
$ curl -H "X-Request-Id: $(head -c 4005 < /dev/zero | tr '\0' '\141')" http://localhost:8080/ 431 Request Header Fields Too Large
(注:4005触发限制而4004未触发的问题不在本次问询范围内,请勿对此展开讨论)
我的问题是:如何在服务端感知到431错误?
当该错误触发时,服务端未生成任何日志(甚至log包的标准日志也没有),仅客户端收到错误提示,我无法在服务端知晓该错误的发生。设置Server.ErrorLog也无法记录该情况,请问有什么方法可以捕获并记录这个错误?
解决方案
方式一:正确配置Server.ErrorLog
Go的http.Server在返回431错误时,底层会尝试通过ErrorLog记录该错误,若之前设置后未生效,大概率是配置方式有误。正确配置示例如下:
import ( "log" "os" "net/http" ) func main() { // 创建自定义日志记录器,指定输出目标和前缀 errorLogger := log.New(os.Stderr, "HTTP ERROR: ", log.LstdFlags|log.Lshortfile) s := &http.Server{ Addr: ":8080", Handler: http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.URL.Path) }), MaxHeaderBytes: 1, ErrorLog: errorLogger, // 绑定错误日志器 } s.ListenAndServe() }
当请求头超过限制时,会输出类似日志:
HTTP ERROR: 2024/05/20 15:30:00 server.go:337: http: request header fields too large
方式二:用ConnState回调监控连接状态
若上述方式无法满足需求,可通过ConnState回调函数监控连接状态变化,结合连接错误信息捕获431异常:
import ( "log" "net" "net/http" "strings" ) func main() { s := &http.Server{ Addr: ":8080", Handler: http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.URL.Path) }), MaxHeaderBytes: 1, } // 设置ConnState回调 s.ConnState = func(conn net.Conn, state http.ConnState) { // 当连接关闭时检查错误 if state == http.StateClosed { if tcpConn, ok := conn.(*net.TCPConn); ok { if err := tcpConn.CloseRead(); err != nil { if strings.Contains(err.Error(), "request header fields too large") { log.Printf("431错误来自客户端: %s", conn.RemoteAddr()) } } } } } s.ListenAndServe() }
方式三:自定义Listener拦截底层错误
还可通过包装net.Listener拦截所有连接的读写错误,从而捕获请求头过大的异常:
import ( "log" "net" "net/http" "strings" ) type loggingListener struct { net.Listener } func (l *loggingListener) Accept() (net.Conn, error) { conn, err := l.Listener.Accept() if err != nil { log.Printf("连接接受失败: %v", err) return nil, err } return &loggingConn{conn}, nil } type loggingConn struct { net.Conn } func (c *loggingConn) Read(b []byte) (n int, err error) { n, err = c.Conn.Read(b) if err != nil { if strings.Contains(err.Error(), "request header fields too large") { log.Printf("客户端 %s 触发431错误: 请求头过大", c.RemoteAddr()) } } return } func main() { ln, err := net.Listen("tcp", ":8080") if err != nil { log.Fatalf("监听失败: %v", err) } ln = &loggingListener{ln} s := &http.Server{ Handler: http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.URL.Path) }), MaxHeaderBytes: 1, } s.Serve(ln) }
内容的提问来源于stack exchange,提问作者425nesp
相关产品推荐
相关产品推荐

