Sep
3
小记:前端URL/Header过长的几种常见服务错误细节(httpcode 414 / 431 / 400)
偶尔会遇到前端访问遇到httpcode431和414的错误。具体情况划分起来,会遇到以下三种:
- 414 Request-URI Too Large:即URL过长
- 400 Request Header Or Cookie Too Large:即Header过长
- 431 Bad Message reason: Request Header Fields Too Large:即Header Fields过长(HTTP整个前半部分,包括url+header)
虽然明白意思,但是实际遇到的时候晕乎乎的,特别是431的情况。因此梳理这几个异常的来源。
服务器背景:
- 我们的服务器是非常标准的基本默认配置Nginx的反向代理打到实际的java服务上,java服务Jetty+SpringBoot配置。这个配置也是目前国内市面上最常见的服务端基础架构。
- 每一个请求打到Nginx层进行拦截处理,完成后转发到实际java服务,Jetty与SpringBoot也会有很厚一层http解析过程,完成后再走到实际业务代码。
我们已知遇到以上问题的情况下服务端业务代码无感知,那么几个问题显然,要么是Nginx层拦截了,要么是Jetty+SpringBoot层拦截了。
在阅读相关文档和自测验证后,发现:
Nginx层
寻找ng的相关配置(http://nginx.org/en/docs/http/ngx_http_core_module.html),我们发现了两项目:
设置请求头的buffer(string)大小,默认为1K。
如果请求头的实际大小大于这个值,则会使用large_client_header_buffers,即下一个值做配置。
设置大请求的请求头的buffer数组大小,默认8K。(4个buffer是多个请求使用)
如果请求行(request line,携带url那一行)超过这个大小,会出现414 (Request-URI Too Large)错误。
如果一个请求header(a request header field)超过这个大小,会出现400 (Bad Request)错误。注意这里是一个请求header不能超过8K,但是如果我的请求头很多,其实屁事没有。
这两个可以清晰的解释:Ng层的限制逻辑是,单行(http raw line)8K限制,并且明确会生产414和400两个错误。
Jetty+SpringBoot层
搜索spring相关教程,431果然是这一层出现的。来源于配置(application.properties or application.yaml或者类似衍生配置):
根据文档,这个参数的默认值还和选择的内嵌服务器有关,Tomcat和Jetty下是8kB,Undertow下则是1MB。当然Undertow国内几乎没人用,那正常情况下就是8Kb了。
结论
因此我们的到结论:假设我们的整个请求头部的组成可以简单理解为url + header_array
那么:
也可以看到,如果我们采用默认值的话,最容易遇到的其实是限制最小的431,随后才可能遇到414或者400。
另外合理性讨论上,虽然公说公有理,从前端视角上看问题不大:
1. 理论上一个应用的请求头信息是相对固定的(包括cookie中的参数),所有请求头接近8K对应的数据量已经相当大了,应该是够用的。
2. cookie可能出现风险,问题可能出现在前端或者承载前端的容器xjb注入cookie。现代方案应使用注入storage或者桥协议方案代替,因此限制是合理的。
3. 同时我们与遇到过一些收集用户上下文信息的字段过长的。讨论的结果是:这种详尽的数据收集并不是每个接口都需要,而是部分关键接口需要。如果是特定大数据传输接口,直接使用post传输表明含义更合理(虽然语义上使用post可能有歧义)。
- 414 Request-URI Too Large:即URL过长
- 400 Request Header Or Cookie Too Large:即Header过长
- 431 Bad Message reason: Request Header Fields Too Large:即Header Fields过长(HTTP整个前半部分,包括url+header)
虽然明白意思,但是实际遇到的时候晕乎乎的,特别是431的情况。因此梳理这几个异常的来源。
服务器背景:
- 我们的服务器是非常标准的基本默认配置Nginx的反向代理打到实际的java服务上,java服务Jetty+SpringBoot配置。这个配置也是目前国内市面上最常见的服务端基础架构。
- 每一个请求打到Nginx层进行拦截处理,完成后转发到实际java服务,Jetty与SpringBoot也会有很厚一层http解析过程,完成后再走到实际业务代码。
我们已知遇到以上问题的情况下服务端业务代码无感知,那么几个问题显然,要么是Nginx层拦截了,要么是Jetty+SpringBoot层拦截了。
在阅读相关文档和自测验证后,发现:
Nginx层
寻找ng的相关配置(http://nginx.org/en/docs/http/ngx_http_core_module.html),我们发现了两项目:
Syntax: client_header_buffer_size size;
Default: client_header_buffer_size 1k;
Context: http, server
设置请求头的buffer(string)大小,默认为1K。
如果请求头的实际大小大于这个值,则会使用large_client_header_buffers,即下一个值做配置。
Syntax: large_client_header_buffers number size;
Default: large_client_header_buffers 4 8k;
Context: http, server
设置大请求的请求头的buffer数组大小,默认8K。(4个buffer是多个请求使用)
如果请求行(request line,携带url那一行)超过这个大小,会出现414 (Request-URI Too Large)错误。
如果一个请求header(a request header field)超过这个大小,会出现400 (Bad Request)错误。注意这里是一个请求header不能超过8K,但是如果我的请求头很多,其实屁事没有。
这两个可以清晰的解释:Ng层的限制逻辑是,单行(http raw line)8K限制,并且明确会生产414和400两个错误。
Jetty+SpringBoot层
搜索spring相关教程,431果然是这一层出现的。来源于配置(application.properties or application.yaml或者类似衍生配置):
server.max-http-header-size=8KB //大概率并没有配置
根据文档,这个参数的默认值还和选择的内嵌服务器有关,Tomcat和Jetty下是8kB,Undertow下则是1MB。当然Undertow国内几乎没人用,那正常情况下就是8Kb了。
结论
因此我们的到结论:假设我们的整个请求头部的组成可以简单理解为url + header_array
那么:
url.length > 8KB => 414 by Nginx
header_array.any.length > 8KB => 400 by Nginx
url.length + header_array.all.length > 8KB => 431 by Jetty
也可以看到,如果我们采用默认值的话,最容易遇到的其实是限制最小的431,随后才可能遇到414或者400。
另外合理性讨论上,虽然公说公有理,从前端视角上看问题不大:
1. 理论上一个应用的请求头信息是相对固定的(包括cookie中的参数),所有请求头接近8K对应的数据量已经相当大了,应该是够用的。
2. cookie可能出现风险,问题可能出现在前端或者承载前端的容器xjb注入cookie。现代方案应使用注入storage或者桥协议方案代替,因此限制是合理的。
3. 同时我们与遇到过一些收集用户上下文信息的字段过长的。讨论的结果是:这种详尽的数据收集并不是每个接口都需要,而是部分关键接口需要。如果是特定大数据传输接口,直接使用post传输表明含义更合理(虽然语义上使用post可能有歧义)。