用python写一个代理转发工具,在nginx不起作用的情况下使用,如响应406代码

用python写一个代理转发工具,在nginx不起作用的情况下使用,如响应406代码

python代码:proxy.py

from flask import Flask, request, Response
import requests
from urllib.parse import urljoin
import logging

# 配置日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger('proxy')

app = Flask(__name__)

# 配置信息
SOURCE_PREFIX = "/xxx"  # 源URL前缀
TARGET_BASE = "https://192.168.10.100:443/xxx"  # 目标基础URL
VERIFY_SSL = False  # 是否验证SSL证书
TARGET_HOST = "192.168.10.100"  # 目标服务器主机名

@app.route(f"{SOURCE_PREFIX}/<path:subpath>", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
@app.route(SOURCE_PREFIX, methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
def proxy(subpath=None):
    # 构建完整目标URL
    if subpath:
        target_url = urljoin(f"{TARGET_BASE}/", subpath)
    else:
        target_url = TARGET_BASE

    # 保留原始查询参数
    if request.query_string:
        target_url = f"{target_url}?{request.query_string.decode('utf-8')}"

    # 创建新的请求头 - 完全重建头部
    headers = {
        'Host': TARGET_HOST,
        'User-Agent': 'Mozilla/5.0',
        'Accept': '*/*',
        'Accept-Encoding': 'identity',  # 禁用压缩
        'Connection': 'close'  # 避免keep-alive问题
    }

    # 复制特定原始头
    for key, value in request.headers:
        key_lower = key.lower()
        if key_lower in ['content-type', 'authorization', 'cookie']:
            headers[key] = value

    #logger.info(f"Proxying to: {target_url}")
    #logger.info(f"Request Headers: {headers}")

    # 转发请求
    try:
        resp = requests.request(
            method=request.method,
            url=target_url,
            headers=headers,
            data=request.get_data(),
            cookies=request.cookies,
            allow_redirects=False,
            verify=VERIFY_SSL,
            stream=True
        )
    except requests.exceptions.RequestException as e:
        logger.error(f"Request failed: {str(e)}")
        return str(e), 500

    #logger.info(f"Response Status: {resp.status_code}")
    #logger.info(f"Response Headers: {dict(resp.headers)}")

    # 构建响应头 - 只保留安全头
    safe_headers = [
        'Content-Type', 'Cache-Control', 'ETag', 'Last-Modified',
        'Accept-Ranges', 'Content-Disposition', 'Location'
    ]

    response_headers = {}
    for name, value in resp.headers.items():
        name_lower = name.lower()
        if name_lower in ['content-encoding', 'transfer-encoding', 'connection']:
            continue
        if any(h.lower() == name_lower for h in safe_headers):
            response_headers[name] = value

    # 特殊处理内容类型
    content_type = resp.headers.get('Content-Type', 'application/octet-stream')
    if 'charset' not in content_type:
        content_type += '; charset=utf-8'

    # 流式响应处理
    def generate():
        for chunk in resp.iter_content(chunk_size=8192):
            yield chunk

    return Response(
        generate(),
        status=resp.status_code,
        headers=response_headers,
        content_type=content_type
    )

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8443)

创建 systemd 服务文件

vim /etc/systemd/system/xxx-proxy.service
[Unit]
Description=XXX Parking System Reverse Proxy Service
After=network.target

[Service]
User=root
Group=root
WorkingDirectory=/opt/server/xxx
ExecStart=/usr/bin/python3 /opt/server/xxx/proxy.py
Restart=always
RestartSec=5
StandardOutput=file:/opt/server/xxx/logs/proxy.log
StandardError=file:/opt/server/xxx/logs/proxy-error.log
Environment=PYTHONUNBUFFERED=1

# 安全加固
PrivateTmp=true
ProtectSystem=full
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

启动服务

# 重新加载 systemd 配置
systemctl daemon-reload
# 启用开机自启
systemctl enable xxx-proxy.service
# 启动服务
systemctl start xxx-proxy.service
# 检查状态
systemctl status xxx-proxy.service

对应的日志轮转配置

vim /etc/logrotate.d/xxx-proxy
/opt/server/xxx/logs/proxy.log
/opt/server/xxx/logs/proxy-error.log {
    daily
    missingok
    rotate 7
    compress
    delaycompress
    notifempty
    create 644 root root
    sharedscripts
    postrotate
        systemctl restart xxx-proxy.service > /dev/null
    endscript
}

其他方案

socat (SSL 透传)

socat TCP4-LISTEN:8443,fork,reuseaddr OPENSSL:182.168.10.100:443,verify=0
# /etc/systemd/system/xxx-proxy.service
[Unit]
Description=HTTPS SSL Proxy via Socat
After=network.target

[Service]
ExecStart=/usr/bin/socat TCP4-LISTEN:8443,fork,reuseaddr OPENSSL:182.168.10.100:443,verify=0
Restart=always
User=root

[Install]
WantedBy=multi-user.target

iptables (内核级转发)

# 1. 启用 IP 转发
echo 1 > /proc/sys/net/ipv4/ip_forward
# 2. DNAT 规则(需 root)
iptables -t nat -A PREROUTING -p tcp --dport 8443 -j DNAT --to-destination 182.168.10.100:443
# 3. 允许返回流量
iptables -t nat -A POSTROUTING -j MASQUERADE
# 4. 开放端口
iptables -A INPUT -p tcp --dport 8443 -j ACCEPT

Nginx Stream(平衡方案)

stream {
    server {
        listen 8443;
        proxy_pass 182.168.10.100:443;
        proxy_ssl on;
        proxy_ssl_verify off;
    }
}

Nginx Stream 模块 (推荐生产环境)

# 在 /etc/nginx/nginx.conf 的 main 上下文添加
stream {
    upstream backend_ssl {
        server 182.168.10.100:443;
    }

    server {
        listen 8443 ssl;
        proxy_pass backend_ssl;
        
        # 若需要客户端也走 HTTPS
        ssl_certificate /opt/server/xxx/nginx/cert/cert.pem;
        ssl_certificate_key /opt/server/xxx/nginx/cert/key.pem;
        
        proxy_ssl on;
        proxy_ssl_verify off;
        proxy_ssl_server_name on;
    }
}

方案对比

虽然提供了这些方案,测试了好几遍,只有proxy.py是成功的

方案 工作原理 备注
socat 纯 TCP/SSL 层转发 完全透传原始请求头和响应,不修改任何内容,虽然结合下面的nginx转发时处理了Accept头,但是还是会406
iptables 内核级 IP 包转发(NAT) 不解析 HTTP 协议,直接转发原始数据包,虽然结合下面的nginx转发时处理了Accept头,但是还是会406
Nginx Stream 四层代理(L4) 不解析 HTTP 协议,转发原始字节流,虽然结合下面的nginx转发时处理了Accept头,但是还是会406
proxy.py 应用层代理(解析并重建请求) 能手动处理 Accept 头,处理不当会触发 406

nginx配置:nginx.conf 可选-主要是配合使用

worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

stream {
    upstream mysql {
        server 192.168.10.200:3306 max_fails=30 fail_timeout=30s;
    }

    server {
        listen 3306;
        proxy_connect_timeout 10s;
        proxy_timeout 30s;
        proxy_pass mysql;
    }
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent '
                '"$http_x_forwarded_for" '
                '"$scheme://$host:$server_port$request_uri"';

    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log;
    gzip on;
    gzip_disable "msie6";

    # 文件上传可能需要更大的body大小限制
    client_max_body_size 50M;

    # 增加缓冲区大小
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;

    sub_filter_once off;
    sub_filter_types *;

    server {
        listen 443 ssl;
        server_name  www.xxx.cn;

        access_log /var/log/nginx/www.xxx.cn.access.log main;
        error_log /var/log/nginx/www.xxx.cn.error.log;
        charset utf-8;

        ssl_certificate /opt/nginx/certificate/www.xxx.cn.pem;
        ssl_certificate_key /opt/nginx/certificate/www.xxx.cn.key;

        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
        #表示优先使用服务端加密套件。默认开启
        ssl_prefer_server_ciphers on;

        root /var/www/www.xxx.cn;
        index index.html;

        # 文件上传可能需要更大的body大小限制
        client_max_body_size 50M;

        location / {
            root   /opt/server/dist;
            try_files $uri $uri/ /index.html;
            index  index.html index.htm;
        }

        location /xxx {
            # 重置所有关键头
            proxy_set_header Accept '*/*';
            proxy_set_header Host $host;
            proxy_set_header Accept-Encoding "";
            proxy_set_header Connection "";

            # 传递真实IP
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            # 禁用SSL验证
            proxy_ssl_verify off;
            proxy_ssl_session_reuse off;

            # 确保传递原始请求URI
            proxy_pass http://127.0.0.1:8443$request_uri;  // 转发到 proxy.py 代理

            # 重定向处理
            proxy_redirect https://192.168.10.100:443 https://www.xxx.cn;

            # 内容替换
            sub_filter 'https://192.168.10.100:443' 'https://www.xxx.cn';
            sub_filter 'http://192.168.10.100:443' 'https://www.xxx.cn';
            sub_filter_types *;
            sub_filter_once off;

            # 代理超时设置
            proxy_connect_timeout 60s;
            proxy_read_timeout 600s;
            proxy_send_timeout 600s;
        }

    }

}
posted @ 2025-06-29 05:00  明月心~  阅读(56)  评论(0)    收藏  举报