HTTP和TCP代理的python实现

一、TCP正向代理[在这里插入图片描述]

1、启动python脚本

2、打开主机代理设置,选择使用代理服务器,提入'地址、端口'信息,然后浏览器的流量即可通过代理服务器

3、linux系统配置http代理

shell 复制代码
export http_proxy=http://IP地址:8080
export https_proxy=https://IP地址:8080
#永久设置需要添加到以下文件内
source ~/.bashrc

4、代码如下

python 复制代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    simple-http-proxy ( https://github.com/WengChaoxi/simple-http-proxy )
    ~ ~ ~ ~ ~ ~
    一个简单的http代理
    :license: MIT, see LICENSE for more details.
"""
from __future__ import print_function

import socket
import select
import time

def debug(tag, msg):
    print('[%s] %s' % (tag, msg))

class HttpRequestPacket(object):
    '''
    HTTP请求包
    '''
    def __init__(self, data):
        self.__parse(data)

    def __parse(self, data):
        '''
        解析一个HTTP请求数据包
        GET http://test.wengcx.top/index.html HTTP/1.1\r\nHost: test.wengcx.top\r\nProxy-Connection: keep-alive\r\nCache-Control: max-age=0\r\n\r\n
        
        参数:data 原始数据
        '''
        i0 = data.find(b'\r\n') # 请求行与请求头的分隔位置
        i1 = data.find(b'\r\n\r\n') # 请求头与请求数据的分隔位置
    
        # 请求行 Request-Line
        self.req_line = data[:i0]
        self.method, self.req_uri, self.version = self.req_line.split() # 请求行由method、request uri、version组成
        
        # 请求头域 Request Header Fields
        self.req_header = data[i0+2:i1]
        self.headers = {}
        for header in self.req_header.split(b'\r\n'):
            k, v = header.split(b': ')
            self.headers[k] = v
        self.host = self.headers.get(b'Host')
        
        # 请求数据
        self.req_data = data[i1+4:]

class SimpleHttpProxy(object):
    '''
    简单的HTTP代理

    客户端(client) <=> 代理端(proxy) <=> 服务端(server)
    '''
    def __init__(self, host='0.0.0.0', port=8080, listen=10, bufsize=8, delay=1):
        '''
        初始化代理套接字,用于与客户端、服务端通信

        参数:host 监听地址,默认0.0.0.0,代表本机任意ipv4地址
        参数:port 监听端口,默认8080
        参数:listen 监听客户端数量,默认10
        参数:bufsize 数据传输缓冲区大小,单位kb,默认8kb
        参数:delay 数据转发延迟,单位ms,默认1ms
        '''
        self.socket_proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket_proxy.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 将SO_REUSEADDR标记为True, 当socket关闭后,立刻回收该socket的端口
        self.socket_proxy.bind((host, port))
        self.socket_proxy.listen(listen)

        self.socket_recv_bufsize = bufsize*1024
        self.delay = delay/1000.0

        debug('info', 'bind=%s:%s' % (host, port))
        debug('info', 'listen=%s' % listen)
        debug('info', 'bufsize=%skb, delay=%sms' % (bufsize, delay))

    def __del__(self):
        self.socket_proxy.close()
    
    def __connect(self, host, port):
        '''
        解析DNS得到套接字地址并与之建立连接

        参数:host 主机
        参数:port 端口
        返回:与目标主机建立连接的套接字
        '''
        # 解析DNS获取对应协议簇、socket类型、目标地址
        # getaddrinfo -> [(family, sockettype, proto, canonname, target_addr),]
        (family, sockettype, _, _, target_addr) = socket.getaddrinfo(host, port)[0]
        
        tmp_socket = socket.socket(family, sockettype)
        tmp_socket.setblocking(0)
        tmp_socket.settimeout(5)
        tmp_socket.connect(target_addr)
        return tmp_socket
         
    def __proxy(self, socket_client):
        '''
        代理核心程序

        参数:socket_client 代理端与客户端之间建立的套接字
        '''
        # 接收客户端请求数据
        req_data = socket_client.recv(self.socket_recv_bufsize)
        if req_data == b'':
            return

        # 解析http请求数据
        http_packet = HttpRequestPacket(req_data)

        # 获取服务端host、port
        if b':' in http_packet.host:
            server_host, server_port = http_packet.host.split(b':')
        else:
            server_host, server_port = http_packet.host, 80

        # 修正http请求数据
        tmp = b'%s//%s' % (http_packet.req_uri.split(b'//')[0], http_packet.host)
        req_data = req_data.replace(tmp, b'')

        # HTTP
        if http_packet.method in [b'GET', b'POST', b'PUT', b'DELETE', b'HEAD']:
            socket_server = self.__connect(server_host, server_port) # 建立连接
            socket_server.send(req_data) # 将客户端请求数据发给服务端

        # HTTPS,会先通过CONNECT方法建立TCP连接
        elif http_packet.method == b'CONNECT':
            socket_server = self.__connect(server_host, server_port) # 建立连接

            success_msg = b'%s %d Connection Established\r\nConnection: close\r\n\r\n'\
                %(http_packet.version, 200)
            socket_client.send(success_msg) # 完成连接,通知客户端
            
            # 客户端得知连接建立,会将真实请求数据发送给代理服务端
            req_data = socket_client.recv(self.socket_recv_bufsize) # 接收客户端真实数据
            socket_server.send(req_data) # 将客户端真实请求数据发给服务端

        # 使用select异步处理,不阻塞
        self.__nonblocking(socket_client, socket_server)

    def __nonblocking(self, socket_client, socket_server):
        '''
        使用select实现异步处理数据

        参数:socket_client 代理端与客户端之间建立的套接字
        参数:socket_server 代理端与服务端之间建立的套接字
        '''
        _rlist = [socket_client, socket_server]
        is_recv = True
        while is_recv:
            try:
                # rlist, wlist, elist = select.select(_rlist, _wlist, _elist, [timeout])
                # 参数1:当列表_rlist中的文件描述符fd状态为readable时,fd将被添加到rlist中
                # 参数2:当列表_wlist中存在文件描述符fd时,fd将被添加到wlist
                # 参数3:当列表_xlist中的文件描述符fd发生错误时,fd将被添加到elist
                # 参数4:超时时间timeout
                #  1) 当timeout==None时,select将一直阻塞,直到监听的文件描述符fd发生变化时返回
                #  2) 当timeout==0时,select不会阻塞,无论文件描述符fd是否有变化,都立刻返回
                #  3) 当timeout>0时,若文件描述符fd无变化,select将被阻塞timeout秒再返回
                rlist, _, elist = select.select(_rlist, [], [], 2)
                if elist:
                    break
                for tmp_socket in rlist:
                    is_recv = True
                    # 接收数据
                    data = tmp_socket.recv(self.socket_recv_bufsize)
                    if data == b'':
                        is_recv = False
                        continue
                    
                    # socket_client状态为readable, 当前接收的数据来自客户端
                    if tmp_socket is socket_client: 
                        socket_server.send(data) # 将客户端请求数据发往服务端
                        # debug('proxy', 'client -> server')

                    # socket_server状态为readable, 当前接收的数据来自服务端
                    elif tmp_socket is socket_server:
                        socket_client.send(data) # 将服务端响应数据发往客户端
                        # debug('proxy', 'client <- server')
                        
                time.sleep(self.delay) # 适当延迟以降低CPU占用
            except Exception as e:
                break

        socket_client.close()
        socket_server.close()

    def client_socket_accept(self):
        '''
        获取已经与代理端建立连接的客户端套接字,如无则阻塞,直到可以获取一个建立连接套接字

        返回:socket_client 代理端与客户端之间建立的套接字
        '''
        socket_client, _ = self.socket_proxy.accept()
        return socket_client

    def handle_client_request(self, socket_client):
        try:
            self.__proxy(socket_client)
        except:
            pass

    def start(self):
        try:
            import _thread as thread # py3
        except ImportError:
            import thread # py2
        while True:
            try:
                # self.handle_client_request(self.client_socket_accept())
                thread.start_new_thread(self.handle_client_request, (self.client_socket_accept(), ))
            except KeyboardInterrupt:
                break

if __name__ == '__main__':
    # 默认参数
    host, port, listen, bufsize, delay = '0.0.0.0', 8080, 10, 8, 1
    
    import sys, getopt
    try:
        opts, _ = getopt.getopt(sys.argv[1:], 'h:p:l:b:d:', ['host=', 'port=', 'listen=', 'bufsize=', 'delay='])        
        for opt, arg in opts:
            if opt in ('-h', '--host'):
                host = arg
            elif opt in ('-p', '--port'):
                port = int(arg)
            elif opt in ('-l', '--listen'):
                listen = int(arg)
            elif opt in ('-b', '--bufsize'):
                bufsize = int(arg)
            elif opt in ('-d', '--delay'):
                delay = float(arg)
    except:
        debug('error', 'read the readme.md first!')
        sys.exit()
    
    # 启动代理
    SimpleHttpProxy(host, port, listen, bufsize, delay).start()

二、TCP反向代理实现

1、启动python脚本,主机8899端口启动一个ssh服务

2、通过使用ssh客户端访问19999端口实现ssh连接的建立(也可代理web服务流量)

3、代码如下

python 复制代码
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
    :copyright: (c) 2024 by Wsq.
"""

import os
import sys
import ssl
import time
import json
import socket
import select
#import argparse
import threading

def debug(tag, msg):
    print('[%s] %s' % (tag, msg))


class TcpProxyThread(threading.Thread):
    def __init__(self, host='0.0.0.0', port=443, listen=10, bufsize=8, delay=1,server_host="127.0.0.1",server_port=None,tls=True,server_key=None,server_cert=None,server_cacert=None,client_key=None,client_cert=None):
        threading.Thread.__init__(self)
        self.server_host = server_host
        self.server_port = server_port
        self.socket_proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket_proxy.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 将SO_REUSEADDR标记为True, 当socket关闭后,立刻回收该socket的端口
        try:
            self.socket_proxy.bind((host, port))
        except OSError as e:
            debug('error','(%s,%s) Address or port refuse to be used!' % (host, port))
            sys.exit(-1)
        self.socket_proxy.listen(listen)

        self.socket_recv_bufsize = bufsize*1024
        self.delay = delay/1000.0
        self.tls = tls
        self.server_keyfile = server_key
        self.server_certfile = server_cert
        self.server_cacertfile = server_cacert
        self.client_keyfile = client_key
        self.client_certfile = client_cert

    def __del__(self):
        self.socket_proxy.close()
    
    def __connect(self, host, port,):
        '''
        解析DNS(如果是域名方式请求)得到套接字地址并与之建立连接
        参数:host 主机
        参数:port 端口
        返回:与目标主机建立连接的套接字
        '''
        # 解析DNS获取对应协议簇、socket类型、目标地址
        # getaddrinfo -> [(family, sockettype, proto, canonname, target_addr),]
        (family, sockettype, _, _, target_addr) = socket.getaddrinfo(host, port)[0]
        
        tmp_socket = socket.socket(family, sockettype)
        tmp_socket.setblocking(0)
        tmp_socket.settimeout(8)
        tmp_socket.connect(target_addr)
        if self.tls == True:
            context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
            try:
                # 此处不校验SSL服务器server_hostname信息
                context.check_hostname = False
                context.verify_mode = ssl.CERT_NONE
                #context.load_verify_locations(c)
                context.load_cert_chain(certfile=self.client_certfile, keyfile=self.client_keyfile)
                socket_client_tls = context.wrap_socket(tmp_socket, server_side=False)
            except Exception as e:
                debug('error',e)
            return socket_client_tls
        return tmp_socket
         
    def __proxy(self, socket_client):
        '''
        代理核心程序
        参数:socket_client 代理端与客户端之间建立的套接字
        '''
        # 定义服务器套接字列表和select套接字列表
        # socket_client_list = []
        
        socket_list = []

        socket_client_list = []
        socket_client_list.append(socket_client)
        
        # 接收客户端请求数据
        req_datas = []
        for socket_client in socket_client_list:
            req_data = socket_client.recv(self.socket_recv_bufsize)      
            #print("############raw:#############\n","get a request\n%s" % req_data)
            #if req_data == b'':
            req_datas.append(req_data)
            socket_list.append(socket_client)




        #  接收命令行传入的server_host和server_port
        #  此处可以设置全局变量接收其他线程发来的server_host替换self.server_host
        '''
        for server_host in self.server_host:
            socket_server = self.__connect(server_host, self.server_port) # 与server建立连接
            socket_server_list.append(socket_server)
            socket_list.append(socket_server)
            socket_server.send(req_data)
        '''
        # 添加裁决逻辑,输出裁决后的请求adju_req_data
        adju_req_data = req_datas[0]

        #向服务器发送请求
        socket_server = self.__connect(self.server_host, self.server_port)
        socket_server.send(adju_req_data)

        # 使用select异步处理,不阻塞
        socket_list.append(socket_server)
        #print(socket_list)
        if len(socket_list) < 2:
            debug("debug","not client or server")
            return
        self.__nonblocking(socket_client_list,socket_server,socket_list)

    def __nonblocking(self,socket_client_list,socket_server,socket_list):
        '''
        使用select实现异步处理数据
        参数:socket_client 代理端与客户端之间建立的套接字
        参数:socket_server_list 代理端与服务端(可以多台)之间建立的套接字
        '''
        _rlist = socket_list
        is_recv = True
        timeout_select = 8
        adju_data_list = []
        while is_recv:
            try:
                # rlist, wlist, elist = select.select(_rlist, _wlist, _elist, [timeout])
                # 参数1:当列表_rlist中的文件描述符fd状态为readable时,fd将被添加到rlist中
                # 参数2:当列表_wlist中存在文件描述符fd时,fd将被添加到wlist
                # 参数3:当列表_xlist中的文件描述符fd发生错误时,fd将被添加到elist
                # 参数4:超时时间timeout
                #  1) 当timeout==None时,select将一直阻塞,直到监听的文件描述符fd发生变化时返回
                #  2) 当timeout==0时,select不会阻塞,无论文件描述符fd是否有变化,都立刻返回
                #  3) 当timeout>0时,若文件描述符fd无变化,select将被阻塞timeout秒再返回
                rlist, wlist, elist = select.select(_rlist, [], [], timeout_select)
                if elist:
                    debug("error","exception break.")
                    break
                
                if not rlist and not wlist and not elist:
                    continue
                for tmp_socket in rlist:
                    is_recv = True
                    # 接收数据
                    data = tmp_socket.recv(self.socket_recv_bufsize)
                    if data == b'':
                        is_recv = False
                        continue
                    
                    # # socket_client状态为readable, 当前接收的数据来自客户端
                    # if tmp_socket is socket_client: 
                    #     for conn in socket_server_list:
                    #         conn.send(data) # 将客户端请求数据发往服务端
                    #         #print("消息来自客户端:%s\n:" %(socket_client))
                    #         # debug('proxy', 'client -> server')

                    # # socket_server状态为readable, 当前接收的数据来自服务端
                    # else:
                    #     for conn in socket_server_list:
                    #         pass
                    #         # 可以记录日志
                    #         #print(f"得到一个服务器响应:{conn}")
                    #     if tmp_socket is socket_server_list[0]:
                    #         socket_client.send(data) # 将第一个服务端响应数据发往客户端
                    #         print("############raw:#############\n","get a response\n%s" %data)
                    if tmp_socket is socket_server: 
                        for conn in socket_client_list:
                            conn.send(data) #将响应给客户端

                    else:
                        adju_data_list.append(data)
                    if adju_data_list:
                        if len(adju_data_list) == len(socket_client_list):
                            #caijue
                            socket_server.send(adju_data_list[0])
                            adju_data_list = []


                #time.sleep(self.delay) # 适当延迟以降低CPU占用
            except Exception as e:
                debug("error",e)
                break

        # socket_client.close()
        # for socket_server in socket_server_list:
        #     socket_server.close()
        socket_server.close()
    def client_socket_accept(self):
        '''
        获取已经与代理端建立连接的客户端套接字,如无则阻塞,直到可以获取一个建立连接套接字
        返回:socket_client 代理端与客户端之间建立的套接字
        '''
        socket_client, client = self.socket_proxy.accept()
        #socket_clients.append(socket_client)
        if self.tls == True:
            try:
                context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
                #context.load_cert_chain(certfile=server_certfile,keyfile=server_keyfile)
                #socket_client = context.wrap_socket(socket_client, server_side=True)
                context.load_verify_locations(self.server_cacertfile)
                context.load_cert_chain(certfile=self.server_certfile,keyfile=self.server_keyfile)
                socket_client = context.wrap_socket(socket_client, server_side=True)
            except FileNotFoundError:
                debug("error","check certfile or keyfile!")
                sys.exit(-1)
            except Exception as e:
                debug("debug_server",e)
        #  
        return socket_client

    def handle_client_request(self, socket_client):
        try:
            self.__proxy(socket_client)
        except:
            pass

    def run(self):
        try:
            import _thread as thread # py3
        except ImportError:
            import thread # py2
        while True:
            try:
                # 多线程处理客户端请求
                thread.start_new_thread(self.handle_client_request, (self.client_socket_accept(),))
            except KeyboardInterrupt:
                break

def make_ca_cert(option, opt_str, value, parser):

    #ovs-pki init #first
    os.system("ovs-pki req+sign ctl controller")
    debug('debug','get "ctl-privkey.pem" and "ctl-cert.pem" success!')
    os.system("ovs-pki req+sign sc switch")
    debug('debug','get "sc-privkey.pem" and "sc-cert.pem" success!')
    sys.exit(0)

def option_parser():
    from optparse import OptionParser
    parser = OptionParser(description='tls agent')
    #parser = argparse.ArgumentParser(description='tls agent')
    parser.add_option('--host', default="0.0.0.0", help='self hostname or IP address , default 0.0.0.0')
    parser.add_option('-p', '--port', type=int, default=19999,help='self TCP port number ,default 443')
    parser.add_option('--server_host', default="127.0.0.1", help='proxy server hostname or IP address,defau1t 127.0.0.1')
    parser.add_option('--server_port', type=int, default=9999,help='proxy server TCP port number')
    parser.add_option('-l', '--listen', type=int, default=5, help='tcp max listen number, default 10')
    parser.add_option('-b', '--bufsize', type=int, default=2,help='recv bufsize, default 2k  bytes size')
    parser.add_option('-d', '--delay', type=int, default=1, help='recv delay ,default 1ms')
    parser.add_option('-T', '--tls', action='store_false',default=False,help='tls enable ,defalut False')
    parser.add_option('-m','--make_ca_cert', action="callback", callback=make_ca_cert, help='test: use gen keyfile and certfile,defalut None')
    # tls client use
    parser.add_option('--client_key', dest='client_keyfile',metavar='KEYFILE', default='/etc/openvswitch/sc-privkey.pem',
                        help='run as server: path to server KEY file ,default in /etc/openvswitch/sc-privkey.pem')
    parser.add_option('--client_cert', dest='client_certfile',metavar='CERTFILE', default='/etc/openvswitch/sc-cert.pem',
                        help='run as server: path to server CERT file ,default  in /etc/openvswitch/sc-cert.pem')
    # tls server use                        
    parser.add_option('--server_key', dest='server_keyfile',metavar='KEYFILE', default='/home/path/to/cert/ctl-privkey.pem',
                        help='run as server: path to server KEY file ,default in /home/path/to/cert/ctl-privkey.pem')
    parser.add_option('--server_cert', dest='server_certfile',metavar='CERTFILE', default='/home/path/to/cert/ctl-cert.pem',
                        help='run as server: path to server CERT file ,default  in /home/path/to/cert/ctl-cert.pem')
    parser.add_option('--server_cacert', dest='server_cacertfile',metavar='CACERTFILE', default='/var/lib/openvswitch/pki/switchca/cacert.pem',
                        help='run as server: path to server CACERT file ,default  in /var/lib/openvswitch/pki/switchca/cacert.pem')


    options, args = parser.parse_args()

    return options, args


def handle_options_server_host(data):
    if "," in data:
        r_list = data.split(',')
        return r_list
    r_list = [data]
    return r_list

def main():
    options, args = option_parser()
    host, port, listen, bufsize, delay ,tls,server_host,server_port,server_key,server_cert, server_cacert, client_cert, client_key = \
        options.host, options.port, options.listen, options.bufsize, options.delay, options.tls,options.server_host,options.server_port,options.server_keyfile,\
        options.server_certfile, options.server_cacertfile,options.client_certfile,options.client_keyfile
    #server_host = handle_options_server_host(server_host)
    debug('info', 'bind=%s:%s' % (host, port))
    debug('info', 'listen=%s' % listen)
    debug('info', 'bufsize=%skb, delay=%sms' % (bufsize, delay))
    debug('info', 'tls=%s' % tls)
    debug('info', 'server_host=%s' % server_host)
    debug('info', 'server_port=%s' % server_port)
    if tls:
        debug('info', 'server_key=%s' % server_key)
        debug('info', 'server_cert=%s' % server_cert)
        debug('info', 'server_cacert=%s' % server_cacert)
        debug('info', 'client_key=%s' % client_key)
        debug('info', 'client_cert=%s' % client_cert)
    # 启动tls代理
    tls_proxy = TcpProxyThread(host, port, listen, bufsize, delay,server_host,server_port,tls,server_key,server_cert,server_cacert,client_key,client_cert)
    tls_proxy.start()
    #print("test second thread...")
    
if __name__ == '__main__':
    main()
相关推荐
CSXB998 分钟前
三十四、Python基础语法(文件操作-上)
开发语言·python·功能测试·测试工具
yfs10241 小时前
压缩Minio桶中的文件为ZIP,并通过 HTTP 响应输出
网络·网络协议·http
hgdlip1 小时前
有什么办法换网络ip动态
网络·tcp/ip·智能路由器
亚图跨际1 小时前
MATLAB和Python及R潜变量模型和降维
python·matlab·r语言·生物学·潜变量模型
IT古董1 小时前
【机器学习】决定系数(R²:Coefficient of Determination)
人工智能·python·机器学习
დ旧言~1 小时前
【网络】应用层——HTTP协议
开发语言·网络·网络协议·http·php
德育处主任Pro1 小时前
『Django』APIView基于类的用法
后端·python·django
Star Patrick1 小时前
算法训练(leetcode)二刷第十九天 | *39. 组合总和、*40. 组合总和 II、*131. 分割回文串
python·算法·leetcode
武子康2 小时前
大数据-213 数据挖掘 机器学习理论 - KMeans Python 实现 距离计算函数 质心函数 聚类函数
大数据·人工智能·python·机器学习·数据挖掘·scikit-learn·kmeans
写点什么啦2 小时前
使用R语言survminer获取生存分析高风险和低风险的最佳截断值cut-off
开发语言·python·r语言·生存分析·x-tile