我正在使用Python ftplib开发一个FTP客户端。如何添加代理支持(我见过的大多数FTP应用程序似乎都有)?我特别想到SOCKS代理,还有其他类型...... FTP,HTTP(甚至可以在FTP程序中使用HTTP代理吗?)
任何想法怎么做?
答案 0 :(得分:8)
根据this来源。
取决于代理,但常见的方法是ftp到代理,然后使用 目标服务器的用户名和密码。
E.g。对于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')
答案 1 :(得分:4)
您可以使用urllib2
中的ProxyHandler。
ph = urllib2.ProxyHandler( { 'ftp' : proxy_server_url } )
server= urllib2.build_opener( ph )
答案 2 :(得分:3)
我遇到了同样的问题,需要使用 ftplib 模块(不要用URLlib2重写我的所有脚本)。
我设法编写了一个在套接字层上安装透明 HTTP隧道的脚本(由ftplib使用)。
现在,我可以透明地执行 FTP over HTTP !
你可以在那里得到它: http://code.activestate.com/recipes/577643-transparent-http-tunnel-for-python-sockets-to-be-u/
答案 3 :(得分:2)
标准模块ftplib
不支持代理。似乎唯一的解决方案是编写自己的ftplib
自定义版本。
答案 4 :(得分:1)
修补内置套接字库绝对不是每个人的选择,但我的解决方案是在主机名与白名单匹配时修补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
FTP命令返回的EPSV
。用法示例:
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()
答案 5 :(得分:0)
以下是使用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