Django在8000端口上运行,Apache在80端口上运行。我在apache中配置了以下重写规则以重定向到django:
RewriteRule ^/?checkout/ http://%{HTTP_HOST}:8000/checkout/ [L,QSA]
如果在浏览器中打开一个url,它可以很好地工作并完美地重定向。
然而,外部客户端(在没有apache的情况下直接连接到django时运行良好)总是会在django服务器上导致Bad Request Syntax错误。以下是Django日志中的片段。看起来Apache会自动将那些"内容长度"的东西附加到查询中,为什么?
[05/Mar/2014 18:01:35] code 400, message Bad request syntax ('GET /checkout/wx_signature?signature=b226bb8f6e9ce2fdecb752c6808a979c62e235f7&echostr=5987526888415258224×tamp=1394042480&nonce=1394079741Content-Length: 445Connection: closeContent-Type: text/html; charset=iso-8859-1 HTTP/1.0')
tl;dr:这是由你的"外部客户端"中的一个错误引起的。它是一个设计糟糕的HTTP客户端,应该避免,因为它不仅会导致这个错误,而且可能会为安全漏洞开辟途径。
为了了解正在发生的事情,你需要逆向工作。
首先,让我们从Django内置服务器的日志行开始:
[05/Mar/2014 18:01:35] code 400, message Bad request syntax ('GET /checkout/wx_signature?signature=b226bb8f6e9ce2fdecb752c6808a979c62e235f7&echostr=5987526888415258224×tamp=1394042480&nonce=1394079741Content-Length: 445Connection: closeContent-Type: text/html; charset=iso-8859-1 HTTP/1.0')
"代码400"是指HTTP状态代码400。这意味着实际的HTTP请求构造不正确,无法理解。幸运的是,Django记录了错误的输入,因此我们可以对其进行分析
现在我们了解了问题的本质,我们将删除不相关的日志错误和长签名,以更深入地了解实际请求:
GET /checkout/wx_signature?[SIGNATURE REMOVED]Content-Length: 445Connection: closeContent-Type: text/html; charset=iso-8859-1 HTTP/1.0
在这里,我们看到HTTP请求的第一行无效。
来自RFC2616第5.1节:
请求行以方法令牌开始,然后是请求URI和协议版本,最后以CRLF结束。元素由SP字符分隔。除最终CRLF序列外,不允许使用CR或LF。
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
在无效请求中,我们可以识别出HTTP谓词GET
在那里,HTTP/1.0
的版本结尾在那里,所以这些都不是问题。中间部分,应该是URL,如下所示:
/checkout/wx_signature?[SIGNATURE REMOVED]Content-Length: 445Connection: closeContent-Type: text/html; charset=iso-8859-1
在发送到服务器之前,URL中的空格通常被+
或%20
替换。正如您所看到的,这里的情况并非如此,它是无效请求的原因。一个好的HTTP客户端永远不会这样做,因为它会自动转义URL这是一个危险信号,表明您使用的"外部客户端"质量较差
请注意,该空间出现在许多看起来奇怪的字段旁边。
如果您查看RFC2616第14.13节,就会发现Content-Length
实际上是HTTP1.1标头的名称。Connection
和Content-Type
也是如此。
它显然不属于那里,那么为什么它与URL连接在一起呢?
从这里我只能猜测,因为我无法访问您的代码。然而,我认为我对正在发生的事情有一个很好的了解。
让我们先了解一下HTTP标头的性质。我们将发送一个原始请求,以模拟我们访问时发生的情况。"http://google.com"。这将触发谷歌将我们重定向到"http://www.google.com".
原始请求:
GET / HTTP/1.1
Host: google.com
原始响应:
HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Date: Thu, 15 May 2014 21:28:46 GMT
Expires: Sat, 14 Jun 2014 21:28:46 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Alternate-Protocol: 80:quic
[HTML content removed]
哇,谷歌返回了一大堆标题!不过,我们只对前几行感兴趣:
HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
...
Content-Length: 219
在这里,您可以看到Content-Type
、Content-Length
和其他标头跟在Location
标头之后。实际订单通常并不重要,因为HTTP客户端或服务器足够聪明,能够理解每一个订单的含义。但是,如果去掉Location
标头之后的行结尾,该怎么办?
你最终会得到这样的东西:
HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/Content-Type: text/html; charset=UTF-8Content-Length: 219
哦。。。如果您是HTTP客户端,您会认为我想将您重定向到http://www.google.com/Content-Type: text/html; charset=UTF-8Content-Length: 219
。
这看起来就像你的症状。。。但为什么会发生这种情况呢?
Apache不太可能以这种损坏的形式返回标头(除非您自定义了插件或类似的东西)。
这也是不太可能你的"外部客户端">故意在收到信头后剥离信头中的行尾。
可能的情况是,"外部客户端"被编码为将内容之前和Location:
之后的所有内容解释为URL,然后在某个地方去掉CRLF字符(通常是出于安全原因在处理HTTP标头时这样做的,具有讽刺意味的是,在这种情况下做得不正确)。客户端尝试使用HTTP/1.0而不是HTTP/1.1发送请求,这一事实支持这一点,因为HTTP/1.0客户端在功能方面通常非常有限,并且往往会根据其过时的知识做出沉重的假设。
您的"外部客户端"很可能在请求行之后将整个标头读取为字符串,字符串处理程序会自动剥离CRLF。
我认为很明显,问题在于"外部客户端",尽管没有足够的信息来深入研究。
我建议您使用不同的客户端或库来完成请求。
当您在Django中使用HTTPS url时,似乎会出现此消息。您可能还需要在Apache2中配置HTTPS配置,灵感来自这个问题,例如:带有django和mod_wsgi 的基于SSL的虚拟主机