2017-09-27 57 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