spring-cloud网关报400 Bad Request问题排查

依然范特西╮ 提交于 2020-11-17 22:23:38

问题现象

api访问时,如果访问过快,第二次访问大概率报Bad Request。

我们对网关做的操作

  1. 记录请求报文,打印日志。
  2. 对请求进行验签。
  3. 验签不通过,打回。验签通过,提取真正的请求参数。
  4. 根据请求报文和数据库配置,路由到后端的微服务,并附带真正的请求参数。

问题排查

  1. 排查网关日志,未发现异常现象。网关只打印了HTTP的报文体,未打印请求头。
  2. 排查微服务日志,发现tomcat接收的请求日志,HTTP请求头丢失了一部,所以Bad Request是微服务报出来的。
  3. 由此定位到网关出问题了。查看Netty的HttpServerCodec。发现请求头是一定会写的,为什么会丢失,怀凝是ByteBuf出问题了,导致一部分内容未写进去。
  4. 检查 ByteBuf并添加日志,确认ByteBuf没有问题。
  5. 由此判定是 tomcat解析出问题了,一部分请求头未解析到。
  6. 继续查看tomcat日志,无意中发现content-length与实际内容不一致。由此猜想,可能是对网关进行转发的时候,改变了网关的请求内容,而content-length是已经解析好的。所以需要重设content-length。
String content = toData.getString("content");
        byte[] content4Byte =  content.getBytes(StandardCharsets.UTF_8);
        ServerHttpRequest newRequest = new ServerHttpRequestDecorator(serverHttpRequest) {

            @Override
            public HttpHeaders getHeaders() {

                HttpHeaders headers = HttpHeaders.writableHttpHeaders(super.getHeaders());

                //如果有chunk代表是块,根据HTTP协议不需要contentLength字段
                if(headers.getValuesAsList(HttpHeaders.TRANSFER_ENCODING).contains("chunked")){
                    return super.getHeaders();
                }

                //这里需要重新设置contentLength,因为转发的时候会改变原报文内容,导致内容长度计算不准确
                headers.setContentLength(content4Byte.length);

                return HttpHeaders.readOnlyHttpHeaders(HttpHeaders.readOnlyHttpHeaders(headers));
            }

            @Override
            public URI getURI() {
                Route route = new CachePostBodyGlobalFilter.Build().uri(URI.create(appMethod4Final.getHostUri())).id("a1").build();
                exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, route);
                log.info("reqId={},toUrl={}", reqId, appMethod4Final.getHostUri() + appMethod4Final.getReqUri());
                return URI.create("http://127.0.0.1:8081/" + appMethod4Final.getReqUri());

            }

            @Override
            public Flux<DataBuffer> getBody() {

                //JSONObject toData = JSONObject.parseObject(body);
                //String content = toData.getString("content");
                NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
                DataBuffer bodyDataBuffer = nettyDataBufferFactory.wrap(content4Byte);
                return Flux.just(bodyDataBuffer);

            }
        };

复盘

这次能解决问题,算是有点运气成分。不过在无法定位问题,而且源码比较复杂的情况下,是可以从日志中找到答案的。前提是要仔细看日志,并且日志足够。通过这次的问题,我必须要弄懂tomcat源码,要不然,下次再出问题,可能就没有这么幸运了。以下是要加深学习的地方:

  1. Http协议
  2. Https协议
  3. Http2
  4. netty
  5. tomcat
  6. reacotor
  7. spring-cloud-gateway
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!