web服务器如何知道何时完全接收到HTTP请求
How can a web server know when an HTTP request is fully received?
我目前正在编写一个非常简单的web服务器,以了解有关低级套接字编程的更多信息。更具体地说,我使用C++作为我的主要语言,并试图用更高级的API将低级C系统调用封装在C++类中。
我已经编写了一个Socket
类,它管理套接字文件描述符并使用RAII处理打开和关闭。这个类还公开了面向连接的套接字(TCP)的标准套接字操作,如绑定、侦听、接受、连接等。
在阅读了send和recv系统调用的手册页后,我意识到我需要在某种形式的循环中调用这些函数,以确保所有字节都能成功发送/接收。
我的API发送和接收看起来类似于这个
void SendBytes(const std::vector<std::uint8_t>& bytes) const;
void SendStr(const std::string& str) const;
std::vector<std::uint8_t> ReceiveBytes() const;
std::string ReceiveStr() const;
对于发送功能,我决定在这样的循环中使用阻塞send
调用(这是一个内部帮助函数,适用于std::string和std::vector)。
template<typename T>
void Send(const int fd, const T& bytes)
{
using ValueType = typename T::value_type;
using SizeType = typename T::size_type;
const ValueType *const data{bytes.data()};
SizeType bytesToSend{bytes.size()};
SizeType bytesSent{0};
while (bytesToSend > 0)
{
const ValueType *const buf{data + bytesSent};
const ssize_t retVal{send(fd, buf, bytesToSend, 0)};
if (retVal < 0)
{
throw ch::NetworkError{"Failed to send."};
}
const SizeType sent{static_cast<SizeType>(retVal)};
bytesSent += sent;
bytesToSend -= sent;
}
}
这似乎可以很好地工作,并保证在成员函数返回时发送所有字节,而不会引发异常。
然而,当我开始实现接收功能时,我开始遇到问题。在我的第一次尝试中,我在循环中使用了一个阻塞recv
调用,如果recv
返回0表示底层TCP连接已关闭,则退出循环。
template<typename T>
T Receive(const int fd)
{
using SizeType = typename T::size_type;
using ValueType = typename T::value_type;
T result;
const SizeType bufSize{1024};
ValueType buf[bufSize];
while (true)
{
const ssize_t retVal{recv(fd, buf, bufSize, 0)};
if (retVal < 0)
{
throw ch::NetworkError{"Failed to receive."};
}
if (retVal == 0)
{
break; /* Connection is closed. */
}
const SizeType offset{static_cast<SizeType>(retVal)};
result.insert(std::end(result), buf, buf + offset);
}
return result;
}
只要发送方在发送完所有字节后关闭连接,这种方法就可以正常工作。然而,当使用例如Chrome来请求网页时,情况并非如此。连接保持打开状态,在接收到请求中的所有字节后,我的接收成员函数在recv
系统调用上被阻塞。我通过使用setsockopt在recv
调用上设置超时来解决这个问题。基本上,一旦超时,我会返回到目前为止接收到的所有字节。这感觉是一个非常不雅的解决方案,我不认为这是网络服务器在现实中处理这个问题的方式。
那么,关于我的问题。
web服务器如何知道何时已完全接收到HTTP请求
HTTP1.1中的GET
请求似乎不包括Content-Length标头。请参见例如此链接。
HTTP/1.1是一个基于文本的协议,以一种有点古怪的方式添加了二进制POST数据。在为HTTP编写"接收循环"时,不能将数据接收部分与HTTP解析部分完全分离。这是因为在HTTP中,某些字符具有特殊的含义。特别地,CRLF
(0x0D 0x0A
)令牌用于分离报头,但也用于使用两个相继的CRLF
令牌来结束请求。
因此,要停止接收,您需要继续接收数据,直到发生以下情况之一:
- 超时–然后发送超时响应
- 请求中有两个
CRLF
——然后解析请求,然后根据需要进行响应(解析正确?请求有意义?发送数据?) - 数据过多–某些HTTP漏洞旨在耗尽服务器资源,如内存或进程(例如,请参阅慢速loris)
也许还有其他边缘情况。还要注意,这只适用于没有正文的请求。对于POST请求,首先等待两个CRLF
令牌,然后再读取Content-Length
字节。当客户端使用多部分编码时,这就更加复杂了。
请求标头由一行空行终止(两个CRLF之间没有任何内容)。
因此,当服务器收到一个请求头,然后收到一个空行,并且如果请求是GET
(没有有效负载),它就知道请求已经完成,可以继续处理形成响应的问题。在其他情况下,它可以继续读取内容长度的有效载荷,并采取相应的行动。
这是一个可靠的、定义良好的语法属性。
不需要内容长度或对GET
有用:内容始终为零长度。假设的Header Length更像您所问的,但您必须首先解析标头才能找到它,因此它不存在,我们使用语法的此属性。因此,您可以考虑在正常解析的基础上添加一个人工超时和最大缓冲区大小,以保护自己免受偶尔恶意的慢速或长时间请求的影响。
解决方案在您的链接中
HTTP1.1中的GET请求似乎不包括Content-Length标头。请参见例如此链接。
上面写着:
它必须使用CRLF行结尾,并且必须以\r\n\r\n 结尾
答案在HTTP协议规范中正式定义1:
-
在W3C的HTTP 0.9规范中。
-
在RFC 1945的HTTP 1.0中,特别是在第4节:HTTP消息、第5节:请求和第7节:实体中。
-
在用于HTTP 1.1的RFC 2616中,特别是在第4节:HTTP消息中,尤其是在4.3节:消息体和4.4节:消息长度中。
-
在HTTP 1.1的RFC 7230(和7231…7235)中,特别是在第3节:消息格式,特别是3.3节:消息正文中。
总之,服务器首先读取消息的初始start-line
,以确定请求类型。如果HTTP版本是0.9,那么请求就完成了,因为唯一支持的请求是没有任何头的GET
。否则,服务器读取消息的message-header
s,直到到达终止CRLF
。然后,只有当请求类型具有定义的消息正文时,服务器才会根据请求标头概述的传输格式读取正文(请求和响应不限于使用HTTP1.1中的Content-Length
标头)
在GET
请求的情况下,没有定义消息主体,因此在HTTP 0.9中,消息在start-line
之后结束,在HTTP 1.0和1.1中,在message-header
的终止CRLF
之后结束。
1:我不打算讨论HTTP2.0,这是一个完全不同的游戏
- 如何在boost beast http请求中设置http头
- 在多个核心中处理一个HTTP请求
- 使用 Winsock2.h C++向不和谐 API 发送 HTTP 请求时出现问题
- 使用 winsock 接收 http 请求
- 对于 http 请求,python 比 c++ 快吗?
- 在 C++/C 中使用 CURL 发出带有数据文件的 GET HTTP 请求
- 带有C++的 HTTP 请求
- 如何在QT中同步发送http请求
- 我可以使用 Boost.Asio 和 Boost.Beast 库发出 HTTPS 请求或 HTTP/2 请求吗?
- HTTP 请求中的标头名称无效
- 套接字或 HTTP 请求
- Winsock2.h 无法发送 http 请求
- 用libcurl生成http请求作为字符串
- http请求之前和之后的垃圾
- 从主机向在VirtualBox linux机器上运行的服务器发送http请求
- 如何在不阻塞 UI 线程的情况下对C++发出 http 请求
- C++ Boost 1.66 使用 Beast http 请求解析器解析字符串
- 在新线程C++中发送 http 请求
- 有什么方法可以通过按下按钮将HTTP请求从JS发送到C
- 套接字"Resource temporarily unavailable"的原始 HTTP 请求