IT技术博客大学习 共学习 共进步

代理的本地部分

BT的花 blogs 2010-04-01 08:56:08 累计浏览 3,785 次

我的这个 proxy 代码参考自 SUZUKI Hisao 的 Tiny HTTP Proxy。主要修改的有两点:

  1. 原版的 do_CONNECT 是两个套接字直接互相转发数据,我改成了 SSL 中间人代理.. 而且依赖到 python 2.6 才支持的 server-side ssl wrap
  2. 另外就是自己封装了 descrypto 类,完成和远程 PHP 的加密

配合其运行的代码见
代理的远程部分
代理的加密部分

# -*- coding: utf-8 -*-
# 建议 Python 2.6 环境,以支持 https proxy
# Win32 下需安装
# http://www.python.org/ftp/python/2.6.5/python-2.6.5.msi
# http://www.voidspace.org.uk/downloads/pycrypto-2.0.1.win32-py2.6.exe
# 至于 IronPython 目前还没有 server-side ssl 支持,据说 IP 2.6.1 将会有...
try:
    from ipcrypto import descrypto
    #IronPython 里的 socket 不支持 bind 到 '0.0.0.0'
    import platform
    bind_address = (platform.node(), 8000)
except:
    from pycrypto import descrypto
    bind_address = ('0.0.0.0', 8000)

import urllib2
import BaseHTTPServer, SocketServer

#REMOTEURL/PASSWORD 和国外主机配合
REMOTEURL = 'http://www.dup2.org/blarblarblar.php'
PASSWORD = 'yourpasswordhere'

# KEY/CERT 的生成参考 http://docs.python.org/library/ssl.html
KEYFILE = 'cert.pem'
CERTFILE = 'cert.pem'

#自定义允许的IP列表, 给每个IP起个名字帮助记忆
allow_clients = {'127.0.0.1': 'myself'}

desobj = descrypto(PASSWORD)

skip_headers = ["keep-alive", "proxy-connection", "connection", "accept-encoding"]

class pseudofile():
    ''' SSL Pseudo File Object'''
    def __init__(self, sslobj):
        self.sslobj = sslobj
        self.closed = 0

    def read(self, size):
        chunks = []
        read = 0
        while read < size:
            data = self.sslobj.read(size-read)
            read += len(data)
            chunks.append(data)
        return ''.join(chunks)

    def readline(self):
        line = []
        while 1:
            char = self.sslobj.read(1)
            line.append(char)
            if char == "\n": return ''.join(line)

    def write(self, data):
        bytes = len(data)
        while bytes > 0:
            sent = self.sslobj.write(data)
            if sent == bytes:
                break    # avoid copy
            data = data[sent:]
            bytes = bytes - sent

    # 下面两个方法是 BaseHTTPServer 里会调用到的
    def flush(self):
        pass
    close = flush

def checkip(f):
    def new_f(_self):
        (ip, port) = _self.client_address
        if ip in allow_clients:
            f(_self)
        else:
            _self.send_error(403)
    return new_f

class ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):

    @checkip
    def do_GET(self):
        content_length = 0
        if hasattr(self, "sslhost"): self.raw_requestline = "%s https://%s%s %s\r\n" % (self.command, self.sslhost, self.path
, self.request_version)
        h = [self.raw_requestline]
        for kv in self.headers.items():
            if kv[0] == 'content-length':
                content_length = int(kv[1])
            if kv[0] in skip_headers: continue
            h.append("%s: %s" % kv)
        h.append("connection: close")
        req = "\r\n".join(h) + "\r\n\r\n"
       
        if content_length:
            req += self.rfile.read(content_length)

        encreq = desobj.enc(req)
        req = urllib2.Request(REMOTEURL, encreq)
        f = urllib2.urlopen(req)
       
        text_mode = f.read(1)
        response = f.read()
        if text_mode == "1":
            response = desobj.dec(response)
        self.wfile.write(response)
        print 'REQUEST:', self.raw_requestline.strip()
        #有时候一些看起来是 text/* 的请求也是 binary mode,通常是 304 Not Modified
        print 'RESPONSE: %s, %d Bytes' % ('crypted mode' if text_mode == "1" else 'raw mode', len(response))
       
        self.close_connection = 1

    @checkip
    def do_CONNECT(self):
        # print self.raw_requestline
        # "CONNECT twitter.com:443 HTTP/1.1"
        self.sslhost = self.raw_requestline.split()[1]
        self.wfile.write(self.protocol_version + " 200 Connection established\r\n")
        self.wfile.write("Proxy-agent: QYB\r\n\r\n")
        # TODO 浏览器端会看到一个警告,但是没有办法;避免警告是不对的,必须让使用者认识到现在是中间人模式
        try:
            import ssl
            self.rfile = pseudofile(ssl.wrap_socket(self.connection, KEYFILE, CERTFILE, True))
            self.wfile = self.rfile
            self.handle_one_request()
        except:
            print 'ssl error:', self.raw_requestline
            self.close_connection = 1
       
    do_PUT = do_GET
    do_POST = do_GET
    do_HEAD = do_GET
    do_DELETE = do_GET

class ThreadingHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): pass
httpd = ThreadingHTTPServer(bind_address, ProxyHandler)
httpd.serve_forever()

建议继续学习

  1. 让安卓手机通过代理翻墙的方法 (累计阅读 8,804)
  2. 代理的加密部分 (累计阅读 8,247)
  3. 关于 SOCKS 代理的远端 DNS 解析 (累计阅读 7,745)
  4. HTTP 正向代理与反向代理 (累计阅读 4,983)
  5. 关于不得不在python中使用代理访问网络的方法 (累计阅读 4,945)
  6. nginx.conf控制指定的代理ip和ip访问的设置手记 (累计阅读 4,628)
  7. 关于http代理 (累计阅读 4,525)
  8. 使用 Perl 实现 HTTP 代理 (累计阅读 4,005)
  9. socks5代理服务器的配置 (累计阅读 3,863)
  10. 代理的远程部分 (累计阅读 3,165)