Web服务器如何知道何时完全接收到HTTP请求?

Jon*_*anE 5 c++ sockets http

我目前正在编写一个非常简单的Web服务器,以了解有关底层套接字编程的更多信息。更具体地说,我使用C ++作为主要语言,并且尝试使用更高级的API将低级C系统调用封装在C ++类中。

我编写了一个Socket类,该类管理套接字文件描述符并使用RAII处理打开和关闭。此类还公开了面向连接的套接字(TCP)的标准套接字操作,例如绑定,监听,接受,连接等。

在阅读了sendrecv系统调用的手册页之后,我意识到我需要在某种形式的循环内调用这些函数,以确保所有字节都可以成功发送/接收。

我发送和接收的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;
Run Code Online (Sandbox Code Playgroud)

对于发送功能,我决定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;
   }
}
Run Code Online (Sandbox Code Playgroud)

这似乎工作正常,并保证在成员函数返回后发送所有字节而不会引发异常。

但是,当我开始实现接收功能时,我开始遇到问题。对于我的第一次尝试,我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;
}
Run Code Online (Sandbox Code Playgroud)

只要在发送完所有字节之后发送方关闭连接,此方法就可以正常工作。但是,使用Chrome浏览器请求网页时,情况并非如此。recv接收到请求中的所有字节后,连接保持打开状态,并且我的接收成员函数在系统调用中被阻塞。我设法通过recv使用setsockopt设置呼叫超时来解决此问题。基本上,一旦超时到期,我将返回到目前为止收到的所有字节。感觉这是一个非常微不足道的解决方案,我不认为这是Web服务器在现实中处理此问题的方式。

所以,关于我的问题。

Web服务器如何知道何时已完全接收到HTTP请求?

GET在HTTP 1.1请求似乎不包括Content-Length头。参见例如此链接

Aur*_*ílý 5

HTTP/1.1 是一种基于文本的协议,以一种有点hacky 的方式添加了二进制POST 数据。在为 HTTP 编写“接收循环”时,您无法将数据接收部分与 HTTP 解析部分完全分开。这是因为在 HTTP 中,某些字符具有特殊含义。特别是,CRLF( 0x0D 0x0A) 令牌用于分隔标头,但也用于使用两个CRLF令牌一个接一个地结束请求。

因此,要停止接收,您需要继续接收数据,直到发生以下情况之一:

  • 超时 - 跟随发送超时响应
  • CRLF请求中的两个- 接着解析请求,然后根据需要进行响应(正确解析?请求有意义?发送数据?)
  • 太多的数据——某些 HTTP 攻击旨在耗尽服务器资源,如内存或进程(参见例如慢速loris)

也许还有其他边缘情况。另请注意,这仅适用于没有正文的请求。对于 POST 请求,您首先等待两个CRLF令牌,然后Content-Length另外读取字节。当客户端使用多部分编码时,这更加复杂。