在Python中编码HTTP请求

3

简短版:是否有一种简单的API可以对HTTP请求进行编码(并解码响应),而不实际在过程中传输和接收编码字节?

详细版:我正在编写一些嵌入式软件,其中使用paramiko打开与服务器的SSH会话。然后,我需要通过使用transport.open_channel('direct-tcpip', <remote address>, <source address>)打开的SSH通道进行HTTP请求。

requests具有传输适配器,可以让您替换自己的传输。但是,BaseAdapter提供的send接口只接受一个PreparedRequest对象,该对象(a)没有以任何有用的方式提供远程地址;您需要解析URL以查找主机和端口,并且(b)仅提供请求的编码版本,仅提供头文件的字典和编码的正文(如果有)。它还没有提供解码响应的帮助。 HTTPAdapter将整个过程(包括编码请求、建立网络连接、发送字节、接收响应字节和解码响应)都推迟到了urllib3

urllib3同样推迟到http.clienthttp.clientHTTPConnection类中,其中编码和网络操作都混在一起。

是否有一种简单的方法来说,“给我一堆字节发送到HTTP服务器”,“这是HTTP服务器的一堆字节;将它们转换为有用的Python对象”?

2个回答

3
这是我能想到的最简单的实现方式:

这是我能想到的最简单的实现方式:

from http.client import HTTPConnection
import requests
from requests.structures import CaseInsensitiveDict
from urllib.parse import urlparse
from argparse import ArgumentParser

class TunneledHTTPConnection(HTTPConnection):
    def __init__(self, transport, *args, **kwargs):
        self.ssh_transport = transport
        HTTPConnection.__init__(self, *args, **kwargs)

    def connect(self):
        self.sock = self.ssh_transport.open_channel(
            'direct-tcpip', (self.host, self.port), ('localhost', 0)
        )

class TunneledHTTPAdapter(requests.adapters.BaseAdapter):
    def __init__(self, transport):
        self.transport = transport

    def close(self):
        pass

    def send(self, request, **kwargs):
        scheme, location, path, params, query, anchor = urlparse(request.url)
        if ':' in location:
            host, port = location.split(':')
            port = int(port)
        else:
            host = location
            port = 80

        connection = TunneledHTTPConnection(self.transport, host, port)
        connection.request(method=request.method,
                           url=request.url,
                           body=request.body,
                           headers=request.headers)
        r = connection.getresponse()
        resp = requests.Response()
        resp.status_code = r.status
        resp.headers = CaseInsensitiveDict(r.headers)
        resp.raw = r
        resp.reason = r.reason
        resp.url = request.url
        resp.request = request
        resp.connection = connection
        resp.encoding = requests.utils.get_encoding_from_headers(response.headers)
        requests.cookies.extract_cookies_to_jar(resp.cookies, request, r)
        return resp

if __name__ == '__main__':
    import paramiko

    parser = ArgumentParser()
    parser.add_argument('-p', help='Port the SSH server listens on', default=22)
    parser.add_argument('host', help='SSH server to tunnel through')
    parser.add_argument('username', help='Username on SSH server')
    parser.add_argument('url', help='URL to perform HTTP GET on')
    args = parser.parse_args()

    client = paramiko.SSHClient()
    client.load_system_host_keys()
    client.connect(args.host, args.p, username=args.username)

    transport = client.get_transport()

    s = requests.Session()
    s.mount(url, TunneledHTTPAdapter(transport))
    response = s.get(url)
    print(response.text)

有各种选项可供BaseAdapter.send使用,但它无法处理这些选项,并且完全忽略连接池等问题,不过它能够完成工作。


TunneledHTTPAdapter 在哪里使用?我在代码中没有看到它被引用。 - Tom
1
@Tom 说实话,我不知道。我好像丢失了实现这个功能的代码。我需要找出一台旧笔记本电脑,如果我找到了,我会发布更新的。 - Tom

1
你可以编写自己的SOCKS4代理,将其运行在本地主机上,然后将HTTP请求指向它。例如,https://urllib3.readthedocs.io/en/latest/advanced-usage.html描述了如何使用SOCKS代理与urllib3一起使用。 SOCKS4基本上是一个简单的握手,然后是原始的HTTP/TCP流量。握手传递目标IP地址和端口。因此,您的代理可以进行握手以满足客户端它是一个SOCKS服务器,然后代理可以直接将“真实”的流量发送到SSH会话(并在相反方向中代理响应)。
这种方法的好处是它可以与大量客户端一起使用 - SOCKS已经广泛存在了很长时间。

这是一个有趣的想法,但比我想要的复杂多了。我已经找到了一个可行的方法(我很快会添加作为答案),但它仍然似乎过于复杂。 - Tom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接