请求库强制使用HTTP / 1.1在HTTPS代理CONNECT上

问题描述 投票:3回答:2

我遇到了行为不端的HTTP代理服务器的问题。遗憾的是,我无法控制代理服务器 - 它是IBM的“企业”产品。代理服务器是用于软件测试的服务虚拟化解决方案的一部分。

基本问题(我认为*)是代理服务器发送回HTTP / 1.0响应。我可以从SOAP UI(Java应用程序)和命令行卷曲中使用它,但Python拒绝连接。据我所知,Python表现正常,而另外两个则没有,因为服务器需要HTTP / 1.1响应(它至少需要主机头,将服务请求路由到给定的存根)。

有没有办法获取请求,或底层urllib3,或甚至更远的http lib始终使用http1.1,即使另一端似乎使用1.0?

这是一个示例程序(不幸的是,它要求您安装带有RTCP的IBM Ration Integration Tester进行真正复制)以重现问题:

import http.client as http_client
http_client.HTTPConnection.debuglevel = 1
import logging
import requests
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True

requests.post("https://host:8443/axl", 
            headers={"soapAction": '"CUCM:DB ver=9.1 updateSipTrunk"'}, 
            data='<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://www.cisco.com/AXL/API/9.1"><soapenv:Header/><soapenv:Body><tns:updateSipTrunk><name>PLACEHOLDER</name><newName>PLACEHOLDER</newName><destinations><destination><addressIpv4>10.10.1.5</addressIpv4><sortOrder>1</sortOrder></destination></destinations></tns:updateSipTrunk></soapenv:Body></soapenv:Envelope>', 
            verify=False)

(通过HTTPS_PROXY环境变量配置代理)

在错误之前调试输出,注意HTTP / 1.0:

INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): host.com
send: b'CONNECT host.com:8443 HTTP/1.0\r\n'
send: b'\r\n'
header: Host: host.com:8443

header: Proxy-agent: Green Hat HTTPS Proxy/1.0

RHEL 6中出现的确切错误文本是:

requests.exceptions.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:646)

尽管此处显示的是主机标头,但它并未显示在线路上。我用tcpdump证实了这一点:

14:03:14.315049 IP sourcehost.53214 > desthost.com: Flags [P.], seq 0:32, ack 1, win 115, options [nop,nop,TS val 2743933964 ecr 4116114841], length 32
        0x0000:  0000 0c07 ac00 0050 56b5 4044 0800 4500  [email protected].
        0x0010:  0054 3404 4000 4006 2ca0 0af8 3f15 0afb  .T4.@.@.,...?...
        0x0020:  84f8 cfde 0c7f a4f8 280a 4ebd b425 8018  ........(.N..%..
        0x0030:  0073 da46 0000 0101 080a a38d 1c0c f556  .s.F...........V
        0x0040:  XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX  ..CONNECT.host
        0x0050:  XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX  xx:8443.HTTP/1.0
        0x0060:  0d0a                          

当我用详细卷曲它时,这就是输出的样子:

* About to connect() to proxy proxy-host.com port 3199 (#0)
*   Trying 10.**.**.** ... connected
* Connected to proxy-host.com (10.**.**.**) port 3199 (#0)
* Establish HTTP proxy tunnel to host.com:8443
> CONNECT host.com:8443 HTTP/1.1
> Host: host.com:8443
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Proxy-Connection: Keep-Alive
> soapAction: "CUCM:DB ver=9.1 updateSipTrunk"
>
< HTTP/1.0 200 OK
< Host: host.com:8443
< Proxy-agent: Green Hat HTTPS Proxy/1.0
<
* Proxy replied OK to CONNECT request
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /path/to/store/ca-bundle.crt
  CApath: none
* SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

在此之后截断。连接后,您可以看到代理的HTTP / 1.0响应。 curl的tcpdump还清楚地显示了主机头以及HTTP 1.1。

*我不能完全确定这是根本问题,因为我无法测试它。我确实看到HTTP / 1.0响应,可以告诉我的非工作Python代码发送CONNECT HTTP / 1.0消息,而工作Java发送HTTP / 1.1消息,Curl也是如此。问题可能是无关的(虽然我觉得不太可能)或Python是行为不端,而不是Java / curl。我只是不知道肯定知道。

那么,有没有办法强制urllib3 /请求始终使用HTTP v1.1?

python curl python-requests python-3.5 http-proxy
2个回答
0
投票

httplibwhich requests relies upon for HTTP(S) heavy lifting)总是使用HTTP/1.0CONNECT

Lib/httplib.py:788

def _tunnel(self):
    self.send("CONNECT %s:%d HTTP/1.0\r\n" % (self._tunnel_host,
        self._tunnel_port))
    for header, value in self._tunnel_headers.iteritems():
        self.send("%s: %s\r\n" % (header, value))
    self.send("\r\n")
    <...>

所以除了编辑子程序之外,你不能“强迫”它使用“HTTP / 1.1”。


如果代理不支持HTTP / 1.0,这可能是问题 - 特别是1.0不需要Host:头,事实上,正如你可以通过比较你的日志输出和上面的代码看到的那样,httplib不会发送它。 While, in verity, a proxy may expect it regardless。但是如果是这种情况,你应该从代理中得到一个错误或响应CONNECT的东西 - 除非代理是如此borken它替换Host:的一些默认(或垃圾),无论如何返回200并尝试连接上帝知道 - 在哪里,你在哪里获得超时。

你可以让httplibHost:标题添加到CONNECT,方法是将其添加到_tunnel_headers(间接):

s=requests.Session()
proxy_url=os.environ['HTTPS_PROXY']
s.proxies["https"]=proxy_url
# have to specify proxy here because env variable is only detected by httplib code
#while we need to trigger requests' proxy logic that acts earlier
# "https" means any https host. Since a Session persists cookies,
#it's meaningless to make requests to multiple hosts through it anyway.

pm=s.get_adapter("https://").proxy_manager_for(proxy_url)
pm.proxy_headers['Host']="host.com"
del pm,proxy_url
<...>
s.get('https://host.com')

1
投票

如果您不依赖于请求库,则可能会发现以下代码段非常有用:

import http.client

conn = http.client.HTTPSConnection("proxy.domain.lu", 8080)
conn.set_tunnel("www.domain.org", 443, headers={'User-Agent': 'curl/7.56.0'})
conn.request("GET", "/api")
response = conn.getresponse()

print( response.read() )
© www.soinside.com 2019 - 2024. All rights reserved.