如何将 HTTP 标头/正文与 PHP 套接字请求隔离开来

Posted

技术标签:

【中文标题】如何将 HTTP 标头/正文与 PHP 套接字请求隔离开来【英文标题】:How to isolate the HTTP headers/body from a PHP Sockets request 【发布时间】:2012-02-04 02:02:31 【问题描述】:

我在 php 中使用套接字连接将数据发布到 Apache 网络服务器。我对这种技术有点陌生,我不确定如何将标头与响应正文隔离开来。

发送代码:

<?php
// collect data to post
$postdata = array(
    'hello' => 'world'
);
$postdata = http_build_query($postdata);
// open socket, send request
$fp = fsockopen('127.0.0.1', 80);
fwrite($fp, "POST /server.php HTTP/1.1\r\n");
fwrite($fp, "Host: fm1\r\n");
fwrite($fp, "Content-Type: application/x-www-form-urlencoded\r\n");
fwrite($fp, "Content-Length: ".strlen($postdata)."\r\n");
fwrite($fp, "Connection: close\r\n");
fwrite($fp, "\r\n");
fwrite($fp, $postdata);
// go through result
$result = "";
while(!feof($fp))
    $result .= fgets($fp);

// close
fclose($fp);
// display result
echo $result;
?>

服务器代码:

Hello this is server. You posted:
<pre>
<?php print_r($_POST); ?>
</pre>

当发布到一台服务器时,我得到:

HTTP/1.1 200 OK
Date: Fri, 06 Jan 2012 09:55:27 GMT
Server: Apache/2.2.15 (Win32) mod_ssl/2.2.15 OpenSSL/0.9.8m PHP/5.3.2
X-Powered-By: PHP/5.3.2
Content-Length: 79
Connection: close
Content-Type: text/html

Hello this is server. You posted:
<pre>
Array
(
    [hello] => world
)
</pre>

正如预期的那样。不过,我想去掉标题,然后从“你好,这是服务器.....”开始阅读正文。 我怎样才能可靠地检测到标题的结尾并将正文读入变量?

另外,我测试过的另一台服务器的回复是这样的:

HTTP/1.1 200 OK
Date: Fri, 06 Jan 2012 10:02:04 GMT
Server: Apache/2
X-Powered-By: PHP/5.2.17
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html

4d
Hello this is server. You posted:
<pre>
Array
(
    [hello] => world
)
</pre>
0

正文周围的“4d”和“0”是什么??

谢谢!

PS 在有人说使用 CURL 之前,不幸的是我不能 :-(

【问题讨论】:

不确定4d0,但您可以非常可靠地使用explode('\r\n\r\n', $result) 【参考方案1】:

标题应以"\r\n\r\n" 结尾(两次)。这些 4d0 可能是您的 php 响应的一部分(它们不是标头的一部分)。

【讨论】:

【参考方案2】:

您可以通过双换行符将标题与正文分开。它应该是&lt;CRLF&gt;&lt;CRLF&gt;,所以这通常会起作用:

list($header, $body) = explode("\r\n\r\n", $response, 2);

更可靠的是,您应该使用正则表达式来捕获换行符变化(极不可能发生):

list($header, $body) = preg_split("/\R\R/", $response, 2);

带有4d0 的东西称为chunked encoding。 (它是用另一个换行符分隔的十六进制数字,并指示以下原始内容块的长度。

要清除它,您必须先查看标题,然后查看是否有相应的 Transfer-Encoding: 条目。如果它变得复杂,建议使用无数现有的 HTTP 用户空间处理类之一。梨has one.

【讨论】:

太好了,您拆分标题的方法似乎效果很好。此外,看起来您对数字的看法是正确的。它们确实会根据内容的长度而变化。我希望我可以使用 CURL,我会研究 PEAR 来处理它。非常感谢! 您应该能够在请求时使用 HTTP/1.0 以避免解析块。【参考方案3】:

在大多数情况下,马里奥的答案应该有效,但我刚刚尝试将此方法应用于 Couch DB 的响应,并且在某些情况下它不起作用。

如果响应不包含任何文档,则 Couch DB 将 "\r\n\r\n" 放在响应正文中,试图保持结果格式正确,在这种情况下,仅将响应拆分为 "\r 是不够的\n\r\n" 因为你可能会不小心切割身体的末端。

HTTP/1.0 200 OK Server: CouchDB/1.6.1 (Erlang OTP/R16B02) ETag: "DJNMQO5WQIBZHFMDU40F1O94T" Date: Mon, 06 Jul 2015 09:37:33 GMT Content-Type: text/plain; charset=utf-8 Cache-Control: must-revalidate 
"total_rows":0,"offset":0,"rows":[
// Couch DB adds some extra line breakers on this line
]

以下解析似乎对 Couch DB 更可靠:

$parts = explode("\r\n\r\n", $response);

if ($parts)

    $headers = array_shift($parts);
    $body = json_decode(implode("\r\n\r\n", $parts));

【讨论】:

FWIW:他的回答仍然对你有用。关键是explode 的第三个参数中的“2”。意味着在使用explode 创建的数组中最多只能有 2 个项目。因此,在list($header, $body) 中,数组的第一个“块”将分配给$headers,而其他所有内容,无论存在多少\r\n,都将分配给$body

以上是关于如何将 HTTP 标头/正文与 PHP 套接字请求隔离开来的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Flutter/Dart 中使用 url 编码的标头和正文发出 HTTP POST 请求

使用 http 端点访问 lambda 中的 HTTP 请求(标头、查询字符串、cookie、正文)对象

从 PHP 中的 JSON POST 读取 HTTP 请求正文的问题

当服务器仅从请求中读取标头时,Http客户端不会收到响应

有没有办法将 Authorize 属性与没有身份验证标头的请求一起使用?

如何使用 WcfCoreMtomEncoder .NET 添加标头或请求正文