背景

环境信息

  • nginx:1.4.2 https
  • tomcat:8.5.34 http

以上环境信息为背景,通过nginx访问前台正常,但是工程中某个和外部系统联调的请求中,始终报400 Bad Request;但是直接通过tomcat访问,该请求正常;

问题现象

  • 前台报错

  • nginx后台access.log报错
10.189.147.210 - - [09/Oct/2018:22:25:14 +0800] "GET /daxp/xxcheck/data_query/SQL_CUSTOM_ALL_REG/1%2320697%2324%7C29%7C-18%7C-74%7C-96%7C30%7C46%7C-97%7C-70%7C-69%7C-90%7C23%7C126%7C-28%7C-61%7C19%7C27%7C-45%7C-111%7C-18%7C16%7C108%7C-105%7C-33%7C-1 HTTP/1.1" 400 5 "https://192.168.37.11:8080/daxp/xxcheck/getjk?dimCode=SQL_CUSTOM_ALL_REG" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36"
  • tomcat后台报错

问题排查及处理过程

  • nginx配置如下
upstream  daxpnew  {
  server   192.168.37.100:8080 ;
  ip_hash;
 }
 
 server {
        listen       192.168.37.11:8080 ssl;
        server_name _;
        access_log   off;
        client_max_body_size 200M;
        error_page 404 502  /404.html;

        ssl_certificate      /home/ssl/nginx/nginx/ca.ssl/server.crt;
        ssl_certificate_key  /home/ssl/nginx/nginx/ca.ssl/server.key;

        ssl_session_timeout  10m;

        ssl_protocols  SSLv2 SSLv3 TLSv1;
        ssl_ciphers ALL:!kEDH!ADH:RC4+RSA:+HIGH:+EXP;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;

        port_in_redirect on;

        location / {
            root   html;
            index  index.html index.htm;
        }

        location /daxp {
            proxy_pass http://daxpnew/daxp;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_read_timeout 1800;
            proxy_connect_timeout 1800;
            root   html;
            index  index.html index.htm;
        }

百度及google搜索,有人说400错误原因和解决办法有几种:

  1. request header过大所引起,request过大,通常是由于cookie中写入了较大的值所引起。 在nginx.conf中,将client_header_buffer_sizelarge_client_header_buffers都调大,可缓解此问题。
  2. 客户端的调用方式没有使用host 参数,传递了空的Host头给服务端,一旦Nginx设置了proxy_set_header Host $host,空Host头就传给了后端。然而,在http 1.1的规范中,Host只要出现空,就会返回400,所以出现了这个故障。而对于需要在Host字段里带上端口信息的,则仍需要配置proxy_set_header Host $host:$server_port
  3. 在server下加入server_name _;
  4. 在tomcat的server.xml中加入一下配置
            <Valve className="org.apache.catalina.valves.RemoteIpValve"
                  portHeader="x-forwarded-port"
                  remoteIpHeader="x-forwarded-for"
                  proxiesHeader="x-forwarded-by"
                  protocolHeader="x-forwarded-proto" />

以上方法都试过一次,改配置的都配置了,但是还是出现400错误;折腾了大半天,无果,哎,心累;

问题解决

在tomcat日志中,一直有个报错

11-Oct-2018 11:44:26.346 INFO [http-nio-8080-exec-2] org.apache.coyote.http11.Http11Processor.service Error parsing HTTP request header
 Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level.
 java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
        at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:484)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:684)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

因为这个报错一直都存在,而且在服务请求报400错误的时候并没有报出来这个错误,就忽略掉了,今天我突然仔细查询这个报错和之前有点不一样,之前的报错如下,但是下面的报错并不影响前台功能

10-Oct-2018 14:43:12.531 INFO [http-nio-8080-exec-7] org.apache.coyote.http11.Http11Processor.service Error parsing HTTP request header
 Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level.
 java.lang.IllegalArgumentException: Invalid character found in method name. HTTP method names must be tokens
        at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:428)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:684)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

百度了下现在的报错,Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986,也就是说我们的请求中用了无效的字符。查看RFC规范知,url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~四个特殊字符以及保留字符( ! * ’ ( ) ; : @ & = + $ , / ? # [ ] ) (26*2+10+4+18=84)这84个字符.而我们的请求中出现了|、{}大括号,所以tomcat报错.。

Tomcat从 7.0.73, 8.0.39, 8.5.7 版本后添加了对Url的限制。

  • 配置tomcat支持|{}等字符的方法是:在 catalina.properties中添加 tomcat.util.http.parser.HttpParser.requestTargetAllow=|{} 但是只支持7.0.76, 8.0.42, 8.5.12 之后的版本(这些版本之后支持设置上述属性)
  • Tomcat从 7.0.73, 8.0.39, 8.5.7 版本后添加了对Url的限制。

于是修改了 catalina.properties的配置文件,重启tomcat,再次测试服务请求正常,不再报400错误;

遗留问题

  1. 在没有配置catalina.properties前,通过tomcat直接请求服务正常,但是通过nginx请求错误,不知道差异在哪儿;
  2. org.apache.coyote.http11.Http11Processor.service Error parsing HTTP request heade这个报错暂时没有发现影响前台功能,所以目前未处理(偷个懒);

总结

造成这次错误的原因如下

  1. 由于头一天我们的tomcat7.0.72升级到了tomcat8.5.34,之前的tomcat没有对Url进行限制,但是新版本的tomcat配置未改,而且我们并不知道tomcat7和8的差异;
  2. 在查看tomcat日志的时候没有仔细,并没有发现用tomcat7和tomcat8的报错的差异;