2009-08-18 17 views
6

Sto sviluppando un client FTP in ftplib Python. Come aggiungo il supporto per i proxy (la maggior parte delle app FTP che ho visto sembrano averlo)? Sto pensando soprattutto ai proxy SOCKS, ma anche ad altri tipi ... FTP, HTTP (è persino possibile usare i proxy HTTP con il programma FTP?)Proxy nell'applicazione FTP Python

Qualche idea come si fa?

risposta

1

Il modulo standard ftplib non supporta i proxy. Sembra che l'unica soluzione sia scrivere la propria versione personalizzata di ftplib.

6

Come da sorgente this.

Dipende dal proxy, ma un metodo comune è quello di ftp al proxy, quindi utilizzare il nome utente e la password per il server di destinazione.

E.g. per ftp.example.com:

Server address: proxyserver (or open proxyserver from with ftp) 
User:   [email protected] 
Password:  password 

Nel codice Python:

from ftplib import FTP 
site = FTP('my_proxy') 
site.set_debuglevel(1) 
msg = site.login('[email protected]', 'password') 
site.cwd('/pub') 
+0

il link nella risposta di cui sopra è 404. avrebbe potuto significare questo: http : //mail.python.org/pipermail/python-list/2004-October/863602.html – AndrewR

+3

La parte "anonymous at ftp.download.com" è pura finzione. Nulla di simile è mai stato menzionato in nessun RFC o implementato/supportato da alcun server, per quanto ne so. In modo nativo, il protocollo FTP non supporta il proxy. AFAIK, l'unico modo per proxy FTP è l'utilizzo di un SOCKS nel qual caso il client deve connettersi al SOCKS e quest'ultimo deve essere istruito su quale sia il vero server FTP. –

+1

Questa risposta mi risolve un vero grande mal di testa. Grazie!!! – dgg32

4

È possibile utilizzare la ProxyHandler in urllib2.

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

L'errore di "urlli2" nell'esempio non può essere modificato perché "le modifiche devono essere di almeno 6 caratteri". – HongboZhu

0

Patching biblioteca presa incorporato sicuramente non sarà un'opzione per tutti, ma la mia soluzione era quella di rattoppare socket.create_connection() di utilizzare un proxy HTTP quando il nome host corrisponde a una whitelist:

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 

ho anche dovuto creare una sottoclasse di ftplib.FTP che ignora la host restituito dalla PASV e EPSV comandi FTP. Esempio di utilizzo:

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() 
0

Ecco soluzione utilizzando requests, testato con un proxy squid che non supporta CONNECT tunneling:

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:[email protected]$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