Go/HTTP | 标准库对请求头的处理简析

某次测试一个用 Go 编写的 HTTP 服务(使用 nginx 进行了反向代理)的时候,发现请求直接返回了 400 Bad Request: invalid header value。而在 handler 入口处的调试日志并没有打印。同时,nginx 日志显示,请求已经被转发到了后面的 HTTP 服务。也就是说,这个 400 错误是 Go 在接受请求后,进入 handler 之前返回的。故而在此记录下,Go 的 HTTP 标准库对请求 header 的处理方式。

httplex

golang 的标准库 net/http 使用 golang.org/x/net/lex/httplex 对请求头进行校验。其中,有三个校验函数:

  • func ValidHeaderFieldName(v string) bool

    用以检验 v 是否为一个有效的 HTTP/1.x 的 header 字段名。(对于 HTTP/2 有额外的限制:不允许大写的 ASCII 字母)

    检验标准:RFC 7230。不能为空,并且字符范围为:”!” / “#” / “$” / “%” / “&” / “‘“ / “*” / “+” / “-“ / “.” / “^” / “_” / “`” / “|” / “~” / DIGIT(数字) / ALPHA(字母,不区分大小写)

  • func ValidHeaderFieldValue(v string) bool

    用以检验 v 是哦否为一个有效的 header 字段值。

    检验标准:http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2

  • func ValidHostHeader(h string) bool

    用以检验 h 是否为一个有效的 Host 头。

    当前的校验标准:不完全受 rfc7230 限制,只要字符范围为 “!” / “$” / “%” / “&” / “(“ / “)” / “*” / “+” / “,” / “-“ / “.” / “:” / “;” / “=” / “[“ / “\’” / “]” / “_” / “~” / DIGIT(数字) / ALPHA(字母,不区分大小写)即可。

net/http 对请求头的校验

使用 go 1.9.2,HTTP/1.x。代码位于 net/http/server.go

  • Host 头的数目不能超过 1,否则:

    1
    2
    $ curl http://localhost:18080/ -H "Host: 123" -H "Host: 456"
    400 Bad Request: too many Host headers
  • Host 头的值可以通过 httplex.ValidHostHeader 方法的校验,否则:

    1
    2
    $ curl http://localhost:18080/ -H "Host: 123@"
    400 Bad Request: malformed Host header
  • 每一个 header 的字段名都可以通过 httplex.ValidHeaderFieldName 的校验,否则:

    1
    2
    $ curl http://localhost:18080/ -H "Host: 123" -H "X-Custome@tes123: 123abc"
    400 Bad Request: invalid header name
  • 每一个 header 的字段值都可以通过 httplex.ValidHeaderFieldValue 的校验,否则返回:

    1
    400 Bad Request: invalid header value

碎碎念

当发送 HTTP 请求后,收到 Go 自身标准库的错误返回,而不是自己编写的错误返回时,如果错误信息模糊无法确定真正的异常原因。那么只好拿着错误信息到 net/http 包代码所在到位置直接暴力搜索了。有时候可以加上日志打印,把错误抛出的上下文打印出来(记得备份源代码,记得删除 http.a 文件,记得重新编译!!)。这样,就可以让服务“直接”告诉你根因所在了。