http服务器响应(套接字)的标头和内容之间的差异
Posted
技术标签:
【中文标题】http服务器响应(套接字)的标头和内容之间的差异【英文标题】:Differ between header and content of http server response (sockets) 【发布时间】:2013-04-21 00:31:50 【问题描述】:我想知道,是否有可能找出响应流中标头结束的位置?
问题的背景如下,我在c中使用套接字从网站获取内容,内容以gzip编码。我想直接从流中读取内容并使用 zlib 对 gzip 内容进行编码。但是我怎么知道gzip内容开始了,http头已经完成了。
我粗略地尝试了两种方法,在我看来,它们给了我一些奇怪的结果。首先,我读入整个流,并在终端中打印出来,我的 http 标头以“\r\n\r\n”结尾,就像我预期的那样,但是第二次,我只检索一次响应以获取标头然后使用 while 循环读取内容,此处标题以没有“\r\n\r\n”结尾。
为什么?哪种方式才是阅读内容的正确方式?
我只是给你代码,这样你就可以看到我是如何从服务器获得响应的。
//first way (gives rnrn)
char *output, *output_header, *output_content, **output_result;
size_t size;
FILE *stream;
stream = open_memstream (&output, &size);
char BUF[BUFSIZ];
while(recv(socket_desc, BUF, (BUFSIZ - 1), 0) > 0)
fprintf (stream, "%s", BUF);
fflush(stream);
fclose(stream);
output_result = str_split(output, "\r\n\r\n");
output_header = output_result[0];
output_content = output_result[1];
printf("Header:\n%s\n", output_header);
printf("Content:\n%s\n", output_content);
.
//second way (doesnt give rnrn)
char *content, *output_header;
size_t size;
FILE *stream;
stream = open_memstream (&content, &size);
char BUF[BUFSIZ];
if((recv(socket_desc, BUF, (BUFSIZ - 1), 0) > 0)
output_header = BUF;
while(recv(socket_desc, BUF, (BUFSIZ - 1), 0) > 0)
fprintf (stream, "%s", BUF); //i would just use this as input stream to zlib
fflush(stream);
fclose(stream);
printf("Header:\n%s\n", output_header);
printf("Content:\n%s\n", content);
两者都给出相同的结果,将它们打印到终端,但第二个应该打印出更多的中断,至少我期望,因为它们在拆分字符串时会丢失。
我是 c 新手,所以我可能会监督一些简单的事情。
【问题讨论】:
标题总是以空行结束。它由 RFC 定义。内容可以用内容长度编码,也可以用分块编码,这在处理上有很大的不同。 你能解释一下为什么第二个有效吗?我的意思是我可以更改缓冲区大小,但仍将仅获取标题。服务器有什么特殊行为吗? 看来您的第二个示例在某些时候可能完全是偶然和运气。您只是连续两次从套接字读取。偶然的时机可能是第一个recv()
获得标题,第二个获得正文。但你不能指望这一点。您必须解析收到的数据并查找空白行。
以分块形式,内容被分段传输。首先,您需要检查什么是“传输编码”标头值(是的,不区分大小写)。如果它被“分块”,你就有麻烦了。您将得到一个字符串,而不是简单的内容长度,它包含下一段的长度,然后是数据,然后是下一个长度,依此类推。如果长度为 0,这是最后一部分,在它之后可能会有额外的头...如果您需要详细信息,您需要检查 RFC2616。
【参考方案1】:
您在循环中调用recv()
,直到套接字断开连接或失败(并将接收到的数据以错误的方式写入您的流),将所有原始数据存储到您的char*
缓冲区中。这不是读取 HTTP 响应的正确方法,尤其是在使用 HTTP keep-alives 的情况下(在这种情况下,响应结束时不会发生断开连接)。您必须遵守RFC 2616 中列出的规则。即:
读取直到遇到"\r\n\r\n"
序列。这将终止响应标头。在此之前不要再读取任何字节。
根据RFC 2616 Section 4.4 中的规则分析接收到的标头。它们会告诉您剩余响应数据的实际格式。
根据 #2 中发现的格式读取剩余数据(如果有)。
如果响应使用 HTTP 1.1,请检查接收到的标头是否存在 Connection: close
标头,或者如果响应使用 HTTP 0.9 或 1.0,则检查是否缺少 Connection: keep-alive
标头。如果检测到,请关闭您的套接字连接端,因为服务器正在关闭它的端部。否则,保持连接打开并将其重新用于后续请求(除非您使用完连接,在这种情况下关闭它)。
根据需要处理接收到的数据。
简而言之,你需要做更多类似这样的事情(伪代码):
string headers[];
byte data[];
string statusLine = read a CRLF-delimited line;
int statusCode = extract from status line;
string responseVersion = extract from status line;
do
string header = read a CRLF-delimited line;
if (header == "") break;
add header to headers list;
while (true);
if ( !((statusCode in [1xx, 204, 304]) || (request was "HEAD")) )
if (headers["Transfer-Encoding"] ends with "chunked")
do
string chunk = read a CRLF delimited line;
int chunkSize = extract from chunk line;
if (chunkSize == 0) break;
read exactly chunkSize number of bytes into data storage;
read and discard until a CRLF has been read;
while (true);
do
string header = read a CRLF-delimited line;
if (header == "") break;
add header to headers list;
while (true);
else if (headers["Content-Length"] is present)
read exactly Content-Length number of bytes into data storage;
else if (headers["Content-Type"] begins with "multipart/")
string boundary = extract from Content-Type header;
read into data storage until terminating boundary has been read;
else
read bytes into data storage until disconnected;
if (!disconnected)
if (responseVersion == "HTTP/1.1")
if (headers["Connection"] == "close")
close connection;
else
if (headers["Connection"] != "keep-alive")
close connection;
check statusCode for errors;
process data contents, per info in headers list;
【讨论】:
以上是关于http服务器响应(套接字)的标头和内容之间的差异的主要内容,如果未能解决你的问题,请参考以下文章
HTTP响应标头中“允许”和“访问控制允许方法”之间的区别?