检测HTTP请求体的结尾

76

我正在尝试编写自己的HTTP客户端和服务器,并希望客户端在请求中包含可选的主体。在服务器端,我想在发送HTTP响应之前读取整个主体。我的问题是,在服务器上,我如何知道我已经读取了整个主体?

虽然在这种情况下我控制了客户端和服务器,但我正在寻找一种“标准”方法。但是,由于Content-Length是可选的,我想要一种不需要它的方法。如果客户端关闭连接,则可以轻松读取所有可用数据,但是客户端需要保持连接以等待响应,因此此方法不起作用。

我所能想到的唯一方法就是了解正文的格式并检测终止符(例如</HTML>)。理想情况下,我不想要求那些知识。

是否有我忽略的方法?

6个回答

72

假设你希望你的客户端与其他服务器一起工作,并且服务器要与其他客户端一起工作,那么你的服务器不能指望被友好地对待。

有两种方法可以判断消息体何时结束。它们都不需要像你所建议的那样知道消息体的内容类型(例如不用找</html> -- 那远超出HTTP协议范围)。

  1. 如果客户端使用了Transfer-Encoding: Chunked来发送消息,那么你需要解析有些复杂的分块传输编码语法。你没有太多的选择,如果客户端以这种格式发送,则必须接收它。当客户端使用此方法时,你可以通过长度为0的块来检测消息体的结尾。
  2. 如果客户端使用Content-Length代替,则必须使用该标头。

正如你所建议的那样,第三种检测结束的方法--当连接关闭时--仅适用于响应,而不适用于请求(因为此时无法发送响应)。


您介意重新表述最后一段吗?当连接关闭时,仅适用于响应是什么意思? - Pacerier
10
如果服务器在传送响应后关闭TCP连接,那么这是一种有效的表示响应正文已完成的方式(前提是没有 Content-Length或 Transfer-Encoding 标头)。但是对于请求来说,这种做法是没有意义的,因为如果客户端在请求正文结束时关闭连接,服务器将无法发送响应。因此,请求必须始终使用 Content-Length 或 Transfer-Encoding,或默认情况下,请求正文被视为空(对于 GET 请求来说是正确的)。 - mgiuca
一般来说,您可以仅在一个方向上关闭TCP连接。这意味着,客户端可以在完成发送HTTP请求后仅在发送方向上关闭连接,然后仍然接收响应。 - Niklas Peter
@NiklasPeter 好的,但我不认为HTTP有处理半关闭TCP连接的规定。(在HTTP规范中,我所知道的没有任何东西可以让服务器在请求方关闭连接后就视请求体为完整。)虽然我可能错了。 - mgiuca

31
If a request contains a message-body and a Content-Length is not given, 
the server SHOULD respond with 400 (bad request) if it cannot determine
the length of the message, or with 411 (length required) if it wishes 
to insist on receiving a valid Content-Length.

也就是说,你有权坚持使用Transfer-Encoding: chunkedContent-Length中的任意一个,因此在其他情况下不必担心确定长度。


2
非常清晰。\r\n标记标志着头部的结束,如果在处理完它之后你还不知道,那就放弃吧。 - user645280

18

我之所以添加另一个答案,主要是因为我的声望不够,在mgiuca的评论下无法发表评论。我知道这个问题有点老了,但还没有明确的答案。

正如提到的那样,需要考虑的主要问题是,您的服务器与不可控制的其他人进行交互,这意味着您无法知道他们将会发送什么,必须准备好处理通过该门传送的任何内容。在考虑这一点时,坚持标准和常规做法可能是最好的选择。

如果客户端发送了“Content-Length”头,则服务器必须解析它并使用它来确定请求的结束。如果没有这样的头文件,但存在“Transfer-Encoding: chunked”头文件,则服务器必须能够解析分块请求(link 参见mgiuca的答案)。最后,如果两者都不存在,则“连接的结束”表示请求的结束。

我认为你忽略了一个事实,即客户端可以结束连接并仍然从服务器获得响应。我的意思是,“结束连接”是什么意思?请记住,HTTP是一种应用层协议,通常通过TCP传输。探索TCP的功能(特别是其connection termination协议)会揭示一些有趣的信息:
  • 要主动结束连接,客户端发送带有FIN标志的数据包,这是四次握手的一部分。连接仍被视为打开状态,因为终止协议尚未完成。
  • 服务器接收此数据包并通知客户端(ACK数据包)。服务器现在知道客户端不会再传输数据了。
  • 客户端进入FIN_WAIT2状态,等待来自服务器的带有FIN标志的数据包以正确关闭连接。
但事实上,客户端已经通知要结束连接了,服务器也知道这一点,但是在客户端仍然保持连接打开(因为他还没有收到FIN数据包),服务器现在回复请求并正常关闭连接。值得注意的是,客户端将使用额外的RST标志确认每个服务器数据包,并告诉服务器他仍然期望FIN来关闭连接。

当服务器完成操作后(在我们的小例子中,发送HTTP响应后),它会在自己的一侧关闭连接,发送FIN数据包。当客户端接收到它时,他会关闭自己的一侧,并用ACK通知服务器。

在此补充一点,我不知道您编程时所处的上下文,但大多数情况下,您最终会在套接字上调用shutdown()POSIX的shutdown(以及Windows至少)将要关闭的连接接口作为函数参数。这些规范明确指出,您可以仅关闭发送方部分(这正是客户端将要执行的操作),从而禁用数据发送,同时允许客户端接收更多数据。
TCP连接的进一步细节超出了本问题的范围,但我建议您阅读相关资料,以便更好地理解使用它的更高层协议。

9

RFC标准文档

简单的方法:使用HTTP 1.0并要求内容长度

为了与HTTP/1.0应用程序兼容,HTTP/1.1请求包含消息主体时必须包括一个有效的Content-Length头字段,除非已知服务器符合HTTP/1.1规范。如果请求包含消息主体而未给出Content-Length,则服务器应在无法确定消息长度时响应400(坏请求),或者在希望坚持接收有效的Content-Length时响应411(需要长度)。

3

2
我认为当你说“Content-Length是可选的”时,你正在阻止自己使用最明显的选择。
根据HTTP规范(http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13),应用程序“应该”使用这个字段来指示消息体的传输长度,除非在第4.4节中的规则禁止这样做。
如果你知道长度(而且似乎确实知道),请在Content-Length头部指定它,并完成此操作,因为规范基本上要求你这样做(假设你没有违反http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4中提到的规则)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接