【问题标题】:Proxies in Python FTP applicationPython FTP 应用程序中的代理
【发布时间】:2010-11-20 13:55:50
【问题描述】:

我正在用 Python ftplib 开发一个 FTP 客户端。如何为其添加代理支持(我见过的大多数 FTP 应用程序似乎都有它)?我特别考虑 SOCKS 代理,但也考虑其他类型... FTP、HTTP(甚至可以将 HTTP 代理与 FTP 程序一起使用吗?)

有什么办法吗?

【问题讨论】:

    标签: python proxy ftp ftplib


    【解决方案1】:

    根据this 来源。

    取决于代理,但常用的方法是ftp到代理,然后使用 目标服务器的用户名和密码。

    例如对于 ftp.example.com:

    Server address: proxyserver (or open proxyserver from with ftp)
    User:           anonymous@ftp.example.com
    Password:       password
    

    在 Python 代码中:

    from ftplib import FTP
    site = FTP('my_proxy')
    site.set_debuglevel(1)
    msg = site.login('anonymous@ftp.example.com', 'password')
    site.cwd('/pub')
    

    【讨论】:

    • 上述答案中的链接是 404。可能意味着这个:mail.python.org/pipermail/python-list/2004-October/863602.html
    • “ftp.download.com 上的匿名”部分纯属虚构。据我所知,任何 RFC 中都没有提到过类似的东西,也没有任何服务器实现/支持过类似的东西。 FTP 协议本身不支持代理。 AFAIK,代理 FTP 的唯一方法是使用 SOCKS,在这种情况下,客户端应该连接到 SOCKS,而后者应该被告知真正的 FTP 服务器是什么。
    【解决方案2】:

    这是使用requests 的解决方法,使用不支持 CONNECT 隧道的 squid 代理进行测试:

    def ftp_fetch_file_through_http_proxy(host, user, password, remote_filepath, http_proxy, output_filepath):
        """
        This function let us to make a FTP RETR query through a HTTP proxy that does NOT support CONNECT tunneling.
        It is equivalent to: curl -x $HTTP_PROXY --user $USER:$PASSWORD ftp://$FTP_HOST/path/to/file
        It returns the 'Last-Modified' HTTP header value from the response.
    
        More precisely, this function sends the following HTTP request to $HTTP_PROXY:
            GET ftp://$USER:$PASSWORD@$FTP_HOST/path/to/file HTTP/1.1
        Note that in doing so, the host in the request line does NOT match the host we send this packet to.
    
        Python `requests` lib does not let us easily "cheat" like this.
        In order to achieve what we want, we need:
        - to mock urllib3.poolmanager.parse_url so that it returns a (host,port) pair indicating to send the request to the proxy
        - to register a connection adapter to the 'ftp://' prefix. This is basically a HTTP adapter but it uses the FULL url of
        the resource to build the request line, instead of only its relative path.
        """
        url = 'ftp://{}:{}@{}/{}'.format(user, password, host, remote_filepath)
        proxy_host, proxy_port = http_proxy.split(':')
    
        def parse_url_mock(url):
            return requests.packages.urllib3.util.url.parse_url(url)._replace(host=proxy_host, port=proxy_port, scheme='http')
    
        with open(output_filepath, 'w+b') as output_file, patch('requests.packages.urllib3.poolmanager.parse_url', new=parse_url_mock):
            session = requests.session()
            session.mount('ftp://', FTPWrappedInFTPAdapter())
            response = session.get(url)
            response.raise_for_status()
            output_file.write(response.content)
            return response.headers['last-modified']
    
    
    class FTPWrappedInFTPAdapter(requests.adapters.HTTPAdapter):
        def request_url(self, request, _):
            return request.url
    

    【讨论】:

      【解决方案3】:

      修补内置套接字库肯定不是每个人的选择,但我的解决方案是修补 socket.create_connection() 以在主机名匹配白名单时使用 HTTP 代理:

      from base64 import b64encode
      from functools import wraps
      import socket
      
      _real_create_connection = socket.create_connection
      _proxied_hostnames = {}  # hostname: (proxy_host, proxy_port, proxy_auth)
      
      
      def register_proxy (host, proxy_host, proxy_port, proxy_username=None, proxy_password=None):
          proxy_auth = None
          if proxy_username is not None or proxy_password is not None:
              proxy_auth = b64encode('{}:{}'.format(proxy_username or '', proxy_password or ''))
          _proxied_hostnames[host] = (proxy_host, proxy_port, proxy_auth)
      
      
      @wraps(_real_create_connection)
      def create_connection (address, *args, **kwds):
          host, port = address
          if host not in _proxied_hostnames:
              return _real_create_connection(address, *args, **kwds)
      
          proxy_host, proxy_port, proxy_auth = _proxied_hostnames[host]
          conn = _real_create_connection((proxy_host, proxy_port), *args, **kwds)
          try:
              conn.send('CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n{auth_header}\r\n'.format(
                  host=host, port=port,
                  auth_header=('Proxy-Authorization: basic {}\r\n'.format(proxy_auth) if proxy_auth else '')
              ))
              response = ''
              while not response.endswith('\r\n\r\n'):
                  response += conn.recv(4096)
              if response.split()[1] != '200':
                  raise socket.error('CONNECT failed: {}'.format(response.strip()))
          except socket.error:
              conn.close()
              raise
      
          return conn
      
      
      socket.create_connection = create_connection
      

      我还必须创建一个 ftplib.FTP 的子类,它忽略 PASVEPSV FTP 命令返回的 host。示例用法:

      from ftplib import FTP
      import paramiko  # For SFTP
      from proxied_socket import register_proxy
      
      class FTPIgnoreHost (FTP):
          def makepasv (self):
              # Ignore the host returned by PASV or EPSV commands (only use the port).
              return self.host, FTP.makepasv(self)[1]
      
      register_proxy('ftp.example.com', 'proxy.example.com', 3128, 'proxy_username', 'proxy_password')
      
      ftp_connection = FTP('ftp.example.com', 'ftp_username', 'ftp_password')
      
      ssh = paramiko.SSHClient()
      ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())  # If you don't care about security.
      ssh.connect('ftp.example.com', username='sftp_username', password='sftp_password')
      sftp_connection = ssh.open_sftp()
      

      【讨论】:

      • 谢谢,这是迄今为止我发现的唯一可行的解​​决方案,但它仍然对我不起作用。在添加了一些 .encode 和 .decode 以使这个 Python 3 兼容之后,我现在可以通过代理建立到 ftp 服务器的连接,但是当我运行 LIST 命令时,我得到了ftplib.error_temp: 425 Unable to open the data connection 而没有得到任何数据回来。有什么想法吗?
      • @JordanDimov 你能发布你所做的吗?我正在尝试为这个问题找到解决方案,但这里提到的 sn-ps 都不适合我。
      【解决方案4】:

      您可以在urllib2 中使用ProxyHandler

      ph = urllib2.ProxyHandler( { 'ftp' : proxy_server_url } )
      server= urllib2.build_opener( ph )
      

      【讨论】:

      • 示例中“urlli2”的错字无法编辑,因为“Edits must be at least 6 characters”。
      【解决方案5】:

      我遇到了同样的问题,需要使用 ftplib 模块(不要用 URLlib2 重写我的所有脚本)。

      我设法编写了一个脚本,在套接字层(由 ftplib 使用)上安装透明 HTTP 隧道

      现在,我可以透明地进行 FTP over HTTP 了!

      你可以在那里得到它: http://code.activestate.com/recipes/577643-transparent-http-tunnel-for-python-sockets-to-be-u/

      【讨论】:

        【解决方案6】:

        标准模块ftplib 不支持代理。似乎唯一的解决方案是编写您自己的自定义版本的ftplib

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多