如何处理来自代理的额外 HTTP 标头?

Posted

技术标签:

【中文标题】如何处理来自代理的额外 HTTP 标头?【英文标题】:What to do with extra HTTP header from proxy? 【发布时间】:2013-06-02 15:56:51 【问题描述】:

我们的环境需要为非现场服务使用出站代理。通常这不是问题。在这种使用 Twilio 的情况下,返回的额外标头会破坏客户端。

传出标题:

POST /2010-04-01/Accounts/FOO/SMS/Messages.json HTTP/1.1
Authorization: Basic FOO==
User-Agent: twilio-php/3.10.0
Host: api.twilio.com
Accept: */*
Accept-Charset: utf-8
Content-Type: application/x-www-form-urlencoded
Content-Length: 108

响应标头:

HTTP/1.0 200 Connection established

HTTP/1.1 201 Created
Server: nginx
Date: Thu, 06 Jun 2013 14:39:24 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 551
Connection: close
X-Powered-By: PHP/5.3.11

我只能假设代理正在添加额外的 HTTP 标头。

Twilio 客户端会检查:

list($head, $body) = ($parts[0] == 'HTTP/1.1 100 Continue') 

据我了解,curl 有时或版本会在请求中自动添加 Expect 标头,响应中会返回 HTTP 100,但在这种情况下不是,响应为 200连接已建立。值得添加一个空的 Expect: 或一个 Expect:bacon 并没有改变结果。

我真的不想在这里过多地破解 Twilio 客户端,我特别想避免只添加 || $parts[0] == 'HTTP/1.0 200 Connection established' 因为看起来很乱。

是否可以发送一个请求标头来抑制/隐藏额外的标头?或者,我没有看到忽略它的 curl 选项?

出站代理是 Linux/Squid

【问题讨论】:

这真的让我大吃一惊。 @TheSurrican 那是什么? 根据规范,http 响应中只有一个“状态行”,然后是标头定义。也就是说,如果我没看错……w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6 201状态的措辞也很有趣w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2 【参考方案1】:

代理问题是很多脚本都面临的问题。我可以在互联网上找到的首选解决方案是简单地添加以下代码行。

<?php
// cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string
if (false !== stripos($response, "HTTP/1.0 200 Connection established\r\n\r\n")) 
  $response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $response);

?>

现在将它添加到 twilio 客户端确实有点混乱。幸运的是,您可以使用命名空间来重新创建本机函数。请参阅以下示例。

<?php
namespace FakeCurl;

//create curl_exec function with same name, but its created in the FakeCurl namespace now.
function curl_exec($ch) 
  //execute the actual curl_exec function in the main namespace
  $response =  \curl_exec($ch);

  // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string
  if (false !== stripos($response, "HTTP/1.0 200 Connection established\r\n\r\n")) 
    $response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $response);
   

  return "ADDED TO RESPONSE\r\n\r\n".$response;


//make a regular curl request, no alterations.

$curl = curl_init();
curl_setopt_array( $curl, array(
    CURLOPT_HEADER => true,
    CURLOPT_NOBODY => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_URL => 'http://***.com' ) );
$response = curl_exec( $curl );
curl_close( $curl );

echo '<pre>'.$response.'</pre>';

?>

输出

ADDED TO RESPONSE

HTTP/1.1 200 OK
Cache-Control: public, max-age=11
Content-Length: 191951
Content-Type: text/html; charset=utf-8
Expires: Wed, 12 Jun 2013 07:09:02 GMT
Last-Modified: Wed, 12 Jun 2013 07:08:02 GMT
Vary: *
X-Frame-Options: SAMEORIGIN
Date: Wed, 12 Jun 2013 07:08:49 GMT

所以要与 twilio 客户端一起使用,您需要在脚本的最顶部放置以下内容:

<?php
namespace FakeCurl;
function curl_exec($ch) 
  $response =  \curl_exec($ch);

  // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string
  if (false !== stripos($response, "HTTP/1.0 200 Connection established\r\n\r\n")) 
    $response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $response);
   

  return $response;


include("twilio.php");
?>

如果命名空间选项由于某种原因失败,我会在 twilio 客户端之外添加一个简单的函数。

<?php
function fixProxyResponse($response) 
  // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string
  if (false !== stripos($response, "HTTP/1.0 200 Connection established\r\n\r\n")) 
    $response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $response);
   

  return $response;

然后更改 twilio 脚本 TinyHttp.php 并仅更改以下行 (~linenr 63)

if ($response = curl_exec($curl)) 
  $parts = explode("\r\n\r\n", $response, 3);
  list($head, $body) = ($parts[0] == 'HTTP/1.1 100 Continue')

if ($response = curl_exec($curl)) 
  $parts = explode("\r\n\r\n", fixProxyResponse($response), 3);
  list($head, $body) = ($parts[0] == 'HTTP/1.1 100 Continue')

【讨论】:

必须等待一个小时才能获奖,但谢谢。您已经证实了我的想法,即我无法摆脱从 Twilio 修改 TinyHttp 代码,我将继续这样做。 等到赏金结束总是好的。有时,当它排在首位时,人们会想出很好的解决方案。 顺便问一下,命名空间选项不起作用吗?那么你根本不需要改变它。 不幸的是,这是对仍然在 centos 5.x 中的站点的更新。我们很快就会迁移它,但现在,对于这个补丁,我必须不使用命名空间。糟透了。【参考方案2】:

一些非常晚的澄清。当您通过代理连接到 SSL/TLS 服务器时,代理会使用 HTTP CONNECT 建立隧道。这在RFC2817 和过期的tunneling spec 中有所涉及,而不是在 RFC2616 中。

原始隧道规范要求代理在成功连接到服务器后向客户端返回“200 连接已建立”,这就是您所看到的。在连接变得透明并且您从服务器获得实际响应之前,这可能会跟随更多标头,然后是一个空行。所以,你得到两组标题。 RFC 2817 放宽了这一点,并允许任何 2xx 响应 CONNECT 请求。

这意味着,简而言之,您不能依赖于使用上面的 php 代码检测和删除单个标题行。可能不止一行,第一行可能没有 200 代码,并且可能不包含“已建立连接”字符串。您必须准备好检测两套完整的标头。

cURL 在 2004 年 7.11.1 之前删除了初始连接响应,但现在将所有内容发送回客户端。详情请见here。

【讨论】:

以上是关于如何处理来自代理的额外 HTTP 标头?的主要内容,如果未能解决你的问题,请参考以下文章

使用 HttpClient.GetFromJsonAsync(),如何处理基于 HttpStatusCode 的 HttpRequestException 而无需额外的 SendAsync 调用?

如何处理2个几乎相同的构造函数[重复]

流分析 - 如何处理参考输入中的 json

在播放结果上设置 HTTP 标头(如过期) - 以及如何处理 ETag?

net/http:请求已取消(等待标头时超出 Client.Timeout)为啥/如何处理?

1 周后如何处理来自分布式日志代理(例如 Kafka)的日志?