用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;
}
}
}
浙公网安备 33010602011771号