背景

之前在一次不规范HTTP请求引发的nginx响应400问题分析与解决 中写过客户端query参数未urlencode导致的400问题,当时的结论是:

  1. 对于query参数带空格的请求,由于其不符合HTTP规范,golangnet/http库无法识别会直接报错400,而nginx和使用uwsginginx交互的api主服务却可以兼容,可以正常处理。
  2. 最终的临时解决方案是:在nginx层根据query 参数是否包含空格决定是转发到golanglog serverapi主服务。

本来以为这事就这么结束了,结果最近查询nginx的错误log,居然又发现少部分400错误,最终定位也是因为query 参数包含空格,而且这次报错是直接在nginx层返回400,后面的转发判定逻辑都不会被触发,于是很神奇的发现了两类空格导致的400问题:

  1. 第一类是之前解决了的nginx可以兼容识别,但golang 网络库无法识别会报400的含空格请求,举例入下:
  1. curl 'http://test.myexample123.com/test?appname=demoapp&phonetype=android&device_type=android&osn=Android OS 10 / API-29 (HONORHLK-AL00/102.0.0.270C00)&osv=Android OS 10 / API-29 (HONORHLK-AL00/102.0.0.270C00)&channel=Google Play&model=HUAWEIHLK-AL00&build=Android OS 10 / API-29 (HONORHLK-AL00/102.0.0.270C00)'
  2. {"status": 1, "data": {"test": "ok"}}
  1. 第二类是这次新发现的nginx层直接返回400的含空格请求,并且还发发现该类报错很多都是来源于华为手机,如下可看出其400响应为nginx直接返回:
  1. curl 'http://test.myexample123.com/test?appname=demoapp&phonetype=android&device_type=android&osn=Android OS 10 / API-29 (HONORHLK-AL00/102.0.0.270C00)&osv=Android OS 10 / API-29 (HONORHLK-AL00/102.0.0.270C00)&channel=Google Play&model=HUAWEI HLK-AL00&build=Android OS 10 / API-29 (HONORHLK-AL00/102.0.0.270C00)'
  2. <html>
  3. <head><title>400 Bad Request</title></head>
  4. <body>
  5. <center><h1>400 Bad Request</h1></center>
  6. <hr><center>nginx/1.16.1</center>
  7. </body>
  8. </html>

乍看之下其请求参数完全没看出区别,无论哪类问题只要去掉了空格就不会有问题了,难不成nginx对华为手机还有歧视不成(>_<)。

问题定位

两类问题都是由于query参数带空格引起的,最终通过二分法试错确认了其关键区别:如果query参数中包含" H"--即空格+H的组合,nginx层即会直接报错返回400,而如果不包含" H"这一组合,nginx层将能兼容处理--这解释了为何大部分400请求来自华为手机,因为华为手机model参数很多都是"HUAWEI HRY-AL00"这类取值,即包含了" H"这一子串,看起来" H"这个组合在nginx内部有特殊含义,华为手机给撞枪口上了。

那" H"在nginx中到底有什么特殊含义呢?又到了探究源码的时候了,通过拜读源码最终在ngx_http_parse.c 中负责解析http 请求行的ngx_http_parse_request_line 函数中找到了原因,如下

  1. 103 ngx_int_t
  2. 104 ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
  3. 105 {
  4. 106 u_char c, ch, *p, *m;
  5. 107 enum {
  6. 108 sw_start = 0,
  7. 109 sw_method,
  8. 110 sw_spaces_before_uri,
  9. 111 sw_schema,
  10. 112 sw_schema_slash,
  11. 113 sw_schema_slash_slash,
  12. 114 sw_host_start,
  13. 115 sw_host,
  14. 116 sw_host_end,
  15. 117 sw_host_ip_literal,
  16. 118 sw_port,
  17. 119 sw_host_http_09,
  18. 120 sw_after_slash_in_uri,
  19. 121 sw_check_uri,
  20. 122 sw_check_uri_http_09,
  21. 123 sw_uri,
  22. 124 sw_http_09,
  23. 125 sw_http_H,
  24. 126 sw_http_HT,
  25. 127 sw_http_HTT,
  26. 128 sw_http_HTTP,
  27. 129 sw_first_major_digit,
  28. 130 sw_major_digit,
  29. 131 sw_first_minor_digit,
  30. 132 sw_minor_digit,
  31. 133 sw_spaces_after_digit,
  32. 134 sw_almost_done
  33. 135 } state;
  34. 136
  35. 137 state = r->state;
  36. 138
  37. 139 for (p = b->pos; p < b->last; p++) {
  38. 140 ch = *p;
  39. 141
  40. 142 switch (state) {
  41. 143
  42. 144 /* HTTP methods: GET, HEAD, POST */
  43. 145 case sw_start:
  44. 146 r->request_start = p;
  45. 147
  46. 148 if (ch == CR || ch == LF) {
  47. 149 break;
  48. 150 }
  49. ...
  50. 486 /* check "/.", "//", "%", and "\" (Win32) in URI */
  51. 487 case sw_after_slash_in_uri:
  52. 488
  53. 489 if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
  54. 490 state = sw_check_uri;
  55. 491 break;
  56. 492 }
  57. 493
  58. 494 switch (ch) {
  59. 495 case ' ':
  60. 496 r->uri_end = p;
  61. 497 state = sw_check_uri_http_09;
  62. 498 break;
  63. 499 case CR:
  64. 500 r->uri_end = p;
  65. 501 r->http_minor = 9;
  66. 502 state = sw_almost_done;
  67. 503 break;
  68. ...
  69. 606 /* space+ after URI */
  70. 607 case sw_check_uri_http_09:
  71. 608 switch (ch) {
  72. ...
  73. 618 case 'H':
  74. 619 r->http_protocol.data = p;
  75. 620 state = sw_http_H;
  76. 621 break;
  77. 622 default:
  78. 623 r->space_in_uri = 1;
  79. 624 state = sw_check_uri;
  80. 625 p--;
  81. 626 break;
  82. 627 }
  83. 628 break;
  84. ...
  85. 684 case sw_http_H:
  86. 685 switch (ch) {
  87. 686 case 'T':
  88. 687 state = sw_http_HT;
  89. 688 break;
  90. 689 default:
  91. 690 return NGX_HTTP_PARSE_INVALID_REQUEST;
  92. 691 }
  93. 692 break;
  94. 693
  95. 694 case sw_http_HT:
  96. 695 switch (ch) {
  97. 696 case 'T':
  98. 697 state = sw_http_HTT;
  99. 698 break;
  100. 699 default:
  101. 700 return NGX_HTTP_PARSE_INVALID_REQUEST;
  102. 701 }
  103. 702 break;
  104. 703
  105. 704 case sw_http_HTT:
  106. 705 switch (ch) {
  107. 706 case 'P':
  108. 707 state = sw_http_HTTP;
  109. 708 break;
  110. 709 default:
  111. 710 return NGX_HTTP_PARSE_INVALID_REQUEST;
  112. 711 }
  113. 712 break;
  114. ...

如上ngx_http_parse_request_line函数解析请求行原理为通过for循环逐个遍历字符,内部使用大量switch语句实现了一个状态机进行解析。

当解析到sw_after_slash_in_uri分支的case ' '(495行)时,会设置状态state=sw_check_uri_http_09,而后在sw_check_uri_http_09分支的case 'H'(618行)设置state=sw_http_H,而sw_http_H其实是HTTP protocol的解析分支,其负责解析出类似HTTP/1.1 这样的内容,所以在分支sw_http_H(684行)其期待的正确字符应该是HTTP/1.1的 第二个字符T,而后进入case sw_http_HT期待解析HTTP/1.1的第三个字符T,以此类推最终逐个解析完成整个protocol字符串,但是在sw_http_H分支中若没有解析到期望的字符T,其默认行为就是直接返回NGX_HTTP_PARSE_INVALID_REQUEST,也就是400常量了。

简单来说,nginx在解析请求行时,若在query参数中遇到了" H"的组合会导致状态机认为已经进入protocol字段的解析分支,当碰到不识别的字符串则认为格式错误,会直接返回400,而如果query参数中虽然包含未转义空格但却没有" H"组合,nginx的这个请求行解析状态机倒还能够一定程度兼容此类错误,将请求正常转发给upstream server处理。

当然,无论nginx能不能兼容query参数未转义空格,最正确的做法还是客户端应该一开始就保证所有query参数都经过必要urlencode再进行使用,这样压根就不会有这么一堆幺蛾子。

转载请注明出处:https://www.cnblogs.com/AcAc-t/p/nginx_http_400_for_space_H.html

一个有趣的nginx HTTP 400响应问题分析的更多相关文章

  1. 一个有趣的nginx问题引发的小问题

    最近处理一个nginx问题,故障现象是:所有的work进程,都在等锁.调用的是sem_wait 根据对应的堆栈,查看一下大家等的锁都一样,看看这把锁被谁拿了: 锁的结构是: typedef struc ...

  2. Nginx配置各种响应头防止XSS,点击劫持,frame恶意攻击

    为什么要配置HTTP响应头? 不知道各位有没有被各类XSS攻击.点击劫持 (ClickJacking. frame 恶意引用等等方式骚扰过,百度联盟被封就有这些攻击的功劳在里面.为此一直都在搜寻相关防 ...

  3. 【小贴士】关于transitionEnd/animate的一个有趣故事

    前言 在很久之前,我们项目有一个动画功能,功能本身很简单,便是典型的右进左出,并且带动画功能 以当时来说,虽然很简单,但是受限于框架本身的难度,就直接使用了CSS3的方式完成了功能 当时主要使用tra ...

  4. nginx 解决400 bad request 的方法(转载)

    nginx的400错误比较难查找原因,因为此错误并不是每次都会出现的,另外,出现错误的时候,通常在浏览器和日志里看不到任何有关提示. 经长时间观察和大量试验查明,此乃request header过大所 ...

  5. nginx 解决400 bad request 的方法

    nginx的400错误比较难查找原因,因为此错误并不是每次都会出现的,另外,出现错误的时候,通常在浏览器和日志里看不到任何有关提示. 经长时间观察和大量试验查明,此乃request header过大所 ...

  6. nginx与apache 对比 apache是同步多进程模型,一个连接对应一个进程;nginx是异步的,多个连接(万级别)可以对应一个进程

    nginx与apache详细性能对比 http://m.blog.csdn.net/lengzijian/article/details/7699444 http://www.cnblogs.com/ ...

  7. 一个有趣的SQL Server 层级汇总数据问题

        看SQL Server大V宋大侠的博客文章,发现了一个有趣的sql server层级汇总数据问题.          具体的问题如下:     parent_id emp_id emp_nam ...

  8. 一个有趣的模拟光照的shader

    一个有趣的模拟光照的shader(类似法线贴图) http://www.cnblogs.com/flytrace/p/3395911.html -----  可否用于需UI中需要加灯的模型.

  9. 一个有趣的 SQL 查询(查询7天连续登陆)

    一个有趣的 SQL 查询 一个朋友有这样一个SQL查询需求: 有一个登录表(tmp_test),包含用户ID(uid)和登录时间(login_time).表结构如下: . row ********** ...

  10. 另一个有趣的Captcha 网站

    今天在一个网站注册时又发现了一个有趣的Captcha形式.给你一个翻转的图片,然后让你拽下面的slide bar让它回到正常的位置,很有趣.下面是提供这个Captcha的网站. minteye – s ...

随机推荐

  1. 第二章:视图层 - 5:HttpRequest对象

    每当一个用户请求发送过来,Django将HTTP数据包中的相关内容,打包成为一个HttpRequest对象,并传递给每个视图函数作为第一位置参数,也就是request,供我们调用. HttpReque ...

  2. nginx配置文件内容详解

    events { # 服务器最大链接数 worker_connections 1024; # 设置一个进程是否同时接受多个网络连接,默认为off multi_accept on; #事件驱动模型,se ...

  3. Spring Boot 项目转容器化 K8S 部署实用经验分享

    转载自:https://cloud.tencent.com/developer/article/1477003 我们知道 Kubernetes 是 Google 开源的容器集群管理系统,它构建在目前流 ...

  4. 跟我学Python图像处理丨带你掌握傅里叶变换原理及实现

    摘要:傅里叶变换主要是将时间域上的信号转变为频率域上的信号,用来进行图像除噪.图像增强等处理. 本文分享自华为云社区<[Python图像处理] 二十二.Python图像傅里叶变换原理及实现> ...

  5. a += 20 和 a = a+20前者不报错,后者报错的原因

    我们在使用a += 20 和 a = a+20两种不同方式的赋值运算是发现尽然前者不报错,后者报错 代码示例: shot s = 5; s += 5; s = s+5; 很明显我们可以看出s = s+ ...

  6. 手把手教你使用LabVIEW人工智能视觉工具包快速实现图像读取与采集(含源码)

    目录 前言 一.工具包位置 二.图像采集与色彩空间转换 1.文件读写 2.实现图片读取 3.使用算子cvtColor实现颜色空间转换 三.从摄像头采集图像 1.Camera类 2.属性节点 3.实现摄 ...

  7. a除于b

    a=eval(input()) b=eval(input()) if b!=0: print("{}".format(round(a/b,2))) else: print(&quo ...

  8. PAT (Basic Level) Practice 1002 写出这个数 分数 20

    读入一个正整数 n,计算其各位数字之和,用汉语拼音写出和的每一位数字. 输入格式: 每个测试输入包含 1 个测试用例,即给出自然数 n 的值.这里保证 n 小于 10100. 输出格式: 在一行内输出 ...

  9. NSIS 自定义安装界面准确获取安装进度完美解决方案

    友情提醒:随着7zip版本快速更新,nsis7z插件已经不能全面兼容新版,使用本例子请将7zip版本降至9.2x以下并下载最新版nsis7z.dll! ------------------------ ...

  10. hmtl5 web SQL 和indexDB

    前端缓存有cookie,localStorage,sessionStorage,webSQL,indexDB: cookie:有缺点 localStorage:功能单一 sessionStorage: ...