Прокси в FTP-приложении Python

Я разрабатываю FTP-клиент на Python ftplib. Как мне добавить к нему поддержку прокси (похоже, что большинство FTP-приложений, которые я видел, имеют это)? Я особенно думаю о SOCKS-прокси, но также и о других типах... FTP, HTTP (возможно ли вообще использовать HTTP-прокси с FTP-программой?)

Любые идеи, как это сделать?


person Community    schedule 18.08.2009    source источник


Ответы (6)


Согласно этому источнику.

Зависит от прокси-сервера, но наиболее распространенным методом является подключение к прокси-серверу по ftp, а затем использование имени пользователя и пароля для целевого сервера.

Например. для ftp.example.com:

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

В коде Python:

from ftplib import FTP
site = FTP('my_proxy')
site.set_debuglevel(1)
msg = site.login('[email protected]', 'password')
site.cwd('/pub')
person Kevin Boyd    schedule 18.08.2009
comment
ссылка в приведенном выше ответе — 404. Возможно, это имелось в виду: mail.python.org/pipermail/python-list/2004-October/863602.html - person AndrewR; 21.01.2010
comment
Анонимная часть на ftp.download.com — чистая выдумка. Насколько мне известно, ничего подобного никогда не упоминалось ни в одном RFC и не реализовывалось/поддерживалось каким-либо сервером. Изначально протокол FTP не поддерживает проксирование. Насколько я знаю, единственный способ проксировать FTP — это использовать SOCKS, и в этом случае клиент должен подключиться к SOCKS, а последний должен быть проинструктирован, что такое настоящий FTP-сервер. - person Giampaolo Rodolà; 21.01.2011

Вы можете использовать ProxyHandler в urllib2.

ph = urllib2.ProxyHandler( { 'ftp' : proxy_server_url } )
server= urllib2.build_opener( ph )
person S.Lott    schedule 18.08.2009
comment
Опечатку urlli2 в примере нельзя отредактировать, т.к. Edits должен содержать не менее 6 символов. - person HongboZhu; 19.09.2013

У меня была та же проблема, и мне нужно было использовать модуль ftplib (чтобы не переписывать все мои скрипты с помощью URLlib2).

Мне удалось написать скрипт, который устанавливает прозрачное HTTP-туннелирование на уровне сокетов (используется ftplib).

Теперь я могу использовать FTP через HTTP прозрачно!

Вы можете получить его здесь: http://code.activestate.com/recipes/577643-transparent-http-tunnel-for-python-sockets-to-be-u/

person Raphael Jolivet    schedule 07.04.2011

Стандартный модуль ftplib не поддерживает прокси. Похоже, единственное решение — написать свою собственную версию файла ftplib.

person Eugene Morozov    schedule 18.08.2009

Исправление встроенной библиотеки сокетов определенно подойдет не всем, но мое решение состояло в том, чтобы исправить 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, который игнорирует host, возвращаемый командами PASV и EPSV FTP. Пример использования:

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()
person Talia    schedule 10.05.2016
comment
Спасибо, это единственное решение, которое я нашел до сих пор, но оно все еще не работает для меня. После добавления некоторых .encode и .decode, чтобы сделать этот Python 3 совместимым, теперь я могу установить соединение с ftp-сервером через прокси-сервер, но в тот момент, когда я запускаю, например, команду LIST, я получаю ftplib.error_temp: 425 Unable to open the data connection и не получаю никаких данных назад. Любые идеи? - person Jordan Dimov; 11.12.2019
comment
@JordanDimov, не могли бы вы опубликовать, что вы сделали? Я пытаюсь найти решение этой проблемы, но ни один из фрагментов, упомянутых здесь, не работает для меня. - person Karan Matalia; 30.04.2021

Вот обходной путь с использованием requests, протестированный с прокси-сервером squid, который НЕ поддерживает туннелирование CONNECT:

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
person Lucas Cimon    schedule 02.09.2016