Header中host与url不一致导致的400错误码

猜想原文:https://www.jianshu.com/p/4b1899316f49

问题描述

对crm进行二次开发的时候,接收腾讯广告平台推送线索的时候推送失败,后台未接收到请求,出现400错误码。

导致原因

腾讯方的推送服务在发请求的时候没有默认加host,请求我们的服务器的时候就会出现400错误,腾讯方告知手动添加host请求成功。

猜想

查阅相关资料后,有其他开发者也出现过类似问题,通常返回400错误码一般有两种情况:
1、语义有误,当前请求无法被服务器理解。
2、请求参数有误
这两种情况都非常广泛。

确定问题原因

尝试比较一下成功的请求和失败的请求有什么不同。比较400和200的请求,我们发现一个规律。我们观察到,从返回200状态码的请求的URL路径中并没有携带Host地址,类似于这样

>GET  /svc/request   HTTP/1.1

Host: sample1.com

HTTP/1.1 200

但当URL路径携带了完整的Host地址时,请求进入新应用就会返回400状态码

GET  http://sample1.com:80/svc/request   HTTP/1.1

Host: sample1.com

HTTP/1.1 400

可以看到以上两个请求中,请求头中都有Host字段,并传入的都是正确的值。然而这个时候,我们发现了一个细节,在返回状态码为400的请求中,请求头Host中的值是不带“:80”端口信息的,但在URL路径中,Host则携带了端口号。
那么有没有可能是URL路径和Request Header中Host不匹配造成的400状态码呢?我们将两者统一了之后再进行尝试,发现无论是否携带端口,只要两者是一致的,就能请求成功。

GET  http://sample1.com:80/svc/request   HTTP/1.1

Host: sample1bc.com:80

HTTP/1.1 200
GET  http://sample1.com/svc/request   HTTP/1.1

Host: sample1.com

HTTP/1.1 200

那么我们基本上可以确定,返回400状态码的请求都是URL路径与请求头的Host不一致的。那接下来的问题是,这些URL和Header中Host不一致的请求是新出现的还是原本就有的呢?如果原本就有,那么为什么应用服务器升级前没出现同样的问题呢?

通过查询规范,HTTP1.1 RFC2616规定 Header Host的值和URI的Host值可以不一样,请参考3.2.3章 《URI Comparison》。HTTP1.1 RFC7230则严格不允许,请参考5.5章 《Effective Request URI》。

于是我们查看了旧版本的应用服务器,发现它使用的是较为早期的RFC2616协议,而新应用服务器则使用的是RFC7230协议。

对于RFC 2616和RFC 7230的实现,我们可以参考开源项目Tomcat的源码中的Http11Processor类中的部分代码。

// The requirements of RFC 2616 are being
    // applied. If the host header and the request
    // line do not agree, the request line takes
    // precedence
    hostValueMB headers.setValueC'host"); 
    hostValueMB.setBytes(uriB, uriBCStart + pos, slashPos pos);
} else {
    // The requirements of RFC 7230 are being
    // applied. If the host header and the request
    // line do not agree, trigger a 400 response.
    response.setStatus(400);
    setErrorState(Errorstate.CLOSE_CLEAN, null); 
    if (log.isDebugEnabled()) {
        log,debug(sm.getString("httpllprocessor. request. inconsistentHosts'*)); }
    }
} 

从代码中很容易就可以看出两种协议的不同实现,RFC2616会忽略请求头中原有的字段,将URL路径中的Host放到请求头的字段中。而RFC7230严格不允许Header与URL中的Host不匹配,则会返回400的状态码。源码中protocol.getAllowHostHeaderMismatch()默认值即为false,说明Tomcat实现的是RFC7230协议。

原理了解清楚了之后,解决方法自然就明确了。RFC7230协议强校验Host不一致一定有它的历史原因,那么理论上请求都应该遵循RFC7230的规范。那么要使URL中的Host和Header中的Host一致,我们可以在客户端修改,也可以在路由中进行修改。修改后,这个问题就被完美解决了。

posted @ 2023-02-03 09:23  JacketLi  阅读(1035)  评论(0编辑  收藏  举报