ejemplo - python ftp server
Proxies en la aplicaciĆ³n de FTP Python (6)
El módulo estándar ftplib
no admite proxies. Parece que la única solución es escribir su propia versión personalizada de ftplib
.
Estoy desarrollando un cliente FTP en Python ftplib. ¿Cómo añado el soporte de proxies (la mayoría de las aplicaciones de FTP que he visto parecen tenerlo)? Estoy especialmente pensando en proxies SOCKS, pero también en otros tipos ... FTP, HTTP (¿es posible utilizar proxies HTTP con el programa FTP?)
¿Alguna idea de como hacerlo?
Tenía el mismo problema y necesitaba usar el módulo ftplib (no para volver a escribir todos mis scripts con URLlib2).
Me las he arreglado para escribir un script que instala un tunneling HTTP transparente en la capa de socket (usado por ftplib).
¡Ahora puedo hacer FTP a través de HTTP de forma transparente!
Puede obtenerlo allí: http://code.activestate.com/recipes/577643-transparent-http-tunnel-for-python-sockets-to-be-u/
Puede usar ProxyHandler en urllib2
.
ph = urllib2.ProxyHandler( { ''ftp'' : proxy_server_url } )
server= urllib2.build_opener( ph )
Aplicar parches a la biblioteca de socket incorporada definitivamente no será una opción para todos, pero mi solución fue parchar socket.create_connection()
para usar un proxy HTTP cuando el nombre de host coincida con una lista blanca:
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
También tuve que crear una subclase de ftplib.FTP que ignora el host
devuelto por los comandos FTP de PASV
y EPSV
. Ejemplo de uso:
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()
Aquí hay una solución usando requests
, probada con un proxy squid que NO es compatible con 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:$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
Según esta fuente.
Depende del proxy, pero un método común es convertir al proxy, luego usar el nombre de usuario y la contraseña para el servidor de destino.
Por ejemplo, para ftp.example.com:
Server address: proxyserver (or open proxyserver from with ftp)
User: [email protected]
Password: password
En código Python:
from ftplib import FTP
site = FTP(''my_proxy'')
site.set_debuglevel(1)
msg = site.login(''[email protected]'', ''password'')
site.cwd(''/pub'')