如何知道http请求是不是是部分的以及如何在生成响应之前完全解析它c ++
Posted
技术标签:
【中文标题】如何知道http请求是不是是部分的以及如何在生成响应之前完全解析它c ++【英文标题】:how to know if a http request is partial and how to fully parse it before generating a response c++如何知道http请求是否是部分的以及如何在生成响应之前完全解析它c ++ 【发布时间】:2022-01-18 21:23:49 【问题描述】:我正在开发一个 C++ 项目,我在其中侦听套接字并根据我在 fds 上从客户端收到的请求生成 HTTP 响应,简而言之,我使用浏览器发送请求,最终得到原始请求,我解析它并生成相应的http响应。
但是在大型 POST 请求的情况下,通常会发生我收到部分请求,所以在第一部分我通常只会找到第一行(版本/方法/uri),一些标头但没有正文,并且我想应该以某种方式得到身体的其余部分,但是我无法弄清楚两件事,
首先,我如何知道我收到的请求是部分请求还是仅从第一部分完成?我没有得到任何与范围有关的信息,这是我的客户向我发送 POST 请求时得到的第一部分。
POST / HTTP/1.1
Host: localhost:8081
Connection: keep-alive
Content-Length: 8535833
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1:8081
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryOs6fsdbaegBIumqh
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/96.0.4664.93 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8081/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr,en-US;q=0.9,en;q=0.8
我如何才能从中确定是收到部分请求还是仅收到错误请求(如果请求说它具有 X 内容长度但正文大小,我需要生成 400 错误不同)
第二个问题是,假设我已经知道它是否是部分请求,我该如何继续将整个请求存储在缓冲区中,然后再将其发送到我的解析器并生成响应?这是我的接收功能(我已经知道客户的 fd,所以我只是接收它
void Client::receive_request(void)
char buffer[2024];
int ret;
ret = recv(_fd, buffer, 2024, 0);
buffer[ret] = 0;
_received_request += buffer;
_bytes_request += ret;
std::cout << "Raw Request:\n" << _received_request << std::endl;
if (buffer[ret-1] == '\n')
_ready_request = true;
_request.parse(_received_request, _server->get_config());
这是检查客户端是否尝试发送请求、解析并生成响应的代码
int Connections::check_clients()
int fd;
for (std::vector<Client*>::iterator client = clients.begin();
client != clients.end() && ready_fd != 0 ; client++)
fd = (*client)->get_fd();
if (FD_ISSET(fd, &ready_rset))
ready_fd--;
(*client)->receive_request();
if ((*client)->request_is_ready())
(*client)->wait_response();
close(fd);
FD_CLR(fd, &active_set);
fd_list.remove(fd);
max_fd = *std::max_element(fd_list.begin(), fd_list.end());
free(*client);
client = clients.erase(client);
return 0;
正如您所看到的,我用 C++ (98) 编写所有内容,并且不想得到只是驳回我的问题并让我参考不同的技术或库的答案,除非它可以帮助我理解做错了什么以及如何处理部分问题请求。
对于信息,我只处理 HTTP 1.1(仅限 GET/POST/DELETE),并且我通常只在获取大块文件或具有非常大主体的文件上传时才会遇到此问题。谢谢
PS : 如果需要,如果您想进一步查看代码,我可以链接当前项目的 github 存储库
【问题讨论】:
阅读 RFC 2616 Section 4.4 和 RFC 7230 Section 3.3.3 了解如何确定 HTTP 消息的长度。在您的情况下,客户端以multipart/form-data
格式发送数据,这是一种自终止格式,因此在 theory 中,您可以继续从套接字读取,直到检测到最终终止符边界。但是,另一方面,客户端也在发送Content-Length
,因此如果您没有收到完全指定的字节数,则请求失败。
【参考方案1】:
我如何才能从中确定是收到部分请求还是仅收到错误请求(如果请求说它具有 X 内容长度但正文大小,我需要生成 400 错误不同)
根据定义,主体大小是Content-Length
字段的大小。之后收到的任何字节都属于下一个 HTTP 请求(请参阅HTTP pipelining)。如果您在合理的时间段内没有收到Content-Length
字节,那么您可以让服务器发出408 Request Timeout 错误。
第二个问题是,假设我已经知道它是否是部分请求,我该如何继续将整个请求存储在缓冲区中,然后再将其发送到我的解析器并生成响应?这是我的接收功能(我已经知道客户的 fd,所以我只是接收它
您发布的代码至少存在以下问题:
-
你应该检查
recv
的返回值来判断函数是成功还是失败,如果失败,你应该适当地处理错误。在您当前的代码中,如果recv
失败并返回值-1
,那么您将写入数组buffer
越界,导致未定义的行为。
似乎不适合使用if (buffer[ret-1] == '\n')
行。当你遇到"\r\n\r\n"
时,HTTP 请求标头将结束,当您读取正文的Content-Length
字节时,HTTP 请求正文将结束。 header 和 body 的结尾不一定会出现在recv
读取的数据的末尾,但也可以出现在中间。如果你想支持 HTTP 管道,那么额外的数据应该由下一个 HTTP 请求的处理程序处理。如果您不想支持 HTTP 管道,那么您可以简单地丢弃额外的数据并在 HTTP 响应标头中使用 Connection: close
。
您似乎使用空终止字符来标记recv
读取的数据的结束。但是,如果具有值 0
的字节是 HTTP 请求的一部分,这将不起作用。假设这样的字节不应该是 HTTP 请求标头的一部分可能是安全的,但假设这样的字节不会是 HTTP 请求正文的一部分可能是不安全的(例如,当使用带有二进制的 POST 时)数据)。
【讨论】:
以上是关于如何知道http请求是不是是部分的以及如何在生成响应之前完全解析它c ++的主要内容,如果未能解决你的问题,请参考以下文章
如何以编程方式进行一响呼叫以及如何判断我呼叫的线路是不是已关闭?
每秒处理3百万请求的Web集群搭建-如何生成每秒百万级别的 HTTP 请求?