2017-09-27 82 views
1

简版:是否有任何简单的API用于编码HTTP请求(并解码响应),而不实际发送和接收编码字节作为过程的一部分?在Python中对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

这是我能拿出的这个最简单的实现:

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') 
    args = parser.parse_args() 

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

有多种方案,以BaseAdapter.send,它不处理,并且它完全忽略的问题,如连接池等,但它完成了工作。

0

您可以编写自己的SOCKS4代理,在localhost上运行它,然后将HTTP请求指向它。例如,https://urllib3.readthedocs.io/en/latest/advanced-usage.html描述了如何在urllib3中使用SOCKS代理。

SOCKS4 is basically简单的握手,然后是原始HTTP/TCP通信。握手传达目标IP地址和端口。因此,您的代理可以进行握手以满足客户端它是SOCKS服务器,然后代理可以将“真实”流量直接发送到SSH会话(并以相反方向代理响应)。

这种方法很酷的一点是,它可以与大量的客户端一起工作 - SOCKS已经在很长一段时间普及了。

+0

这是一个有趣的想法,但waaaaay比我以后更复杂。我已经想出了一种可行的方法(我将尽快添加一个答案),但它看起来似乎太复杂了。 – Tom