You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flask搭建代理服务HTTP正常HTTPS请求报404错误

问题根因

报404的核心原因非常明确,共三个点:

  • HTTPS请求走正向代理时,客户端不会直接发GET/POST这类普通请求,第一步会先发CONNECT方法的请求,要求代理和目标站点建立TCP隧道,隧道连通后才会在加密通道里传输实际HTTPS请求。你写的Flask路由只绑定了GET/POST/DELETE/PATCH/PUT这几个方法,根本没处理CONNECT方法,请求进来Flask找不到对应路由直接返回404,正好对应报错里的Tunnel connection failed: 404 NOT FOUND。
  • Flask本身是WSGI框架,设计初衷是做普通Web应用的请求-响应处理,根本不支持正向代理需要的裸TCP隧道透传。就算你给路由加上CONNECT方法,Flask的请求上下文模型也没法直接实现双向字节流转发的隧道逻辑,硬改成本极高。
  • 你代码本身存在逻辑错误:给Flask加了ssl_context参数把代理本身做成HTTPS服务,又在转发时直接拿request.url当目标地址,HTTPS代理场景下这个值根本不是完整的目标站点地址,逻辑从根上就不对。
修复方案

先明确一个前提:纯Flask实现不了完整的正向代理HTTPS转发能力,WSGI协议的模型天生不适合做这个,别在路由注册上绕弯路,选下面两种可行路径:

方案1:基于底层TCP能力自行实现

放弃Flask,直接用原生socket或者支持底层TCP处理的库(gevent、asyncio都可以)写代理逻辑,核心流程:

  1. 监听端口收到客户端连接后,先读取请求第一行判断类型
    • 如果是普通HTTP请求(方法为GET/POST等,路径是完整http://开头的URL),按原有逻辑转发即可
    • 如果是CONNECT请求,解析出目标站点的主机、端口,和目标建立TCP连接后,给客户端返回200 Connection Established响应,之后直接双向透传两个连接之间的字节流,不需要解析中间的加密内容
  2. 如果需要代理本身走HTTPS(也就是客户端配置的代理地址是https://开头),先给客户端连接完成TLS握手,再走上面的请求判断、转发流程。

最简的socket实现CONNECT隧道的示例代码:

import socket
import threading

def transfer(src, dst):
    try:
        while True:
            buff = src.recv(8192)
            if not buff:
                break
            dst.sendall(buff)
    except Exception:
        pass
    finally:
        src.close()
        dst.close()

def handle_conn(client):
    try:
        head_data = client.recv(8192)
        if not head_data:
            client.close()
            return
        method, target, _ = head_data.split(b' ')[:3]
        if method == b'CONNECT':
            host, port = target.decode().split(':')
            port = int(port)
            remote = socket.create_connection((host, port))
            client.send(b'HTTP/1.1 200 Connection Established\r\n\r\n')
            threading.Thread(target=transfer, args=(client, remote)).start()
            threading.Thread(target=transfer, args=(remote, client)).start()
        else:
            # 普通HTTP请求转发逻辑自行补充即可
            client.close()
    except Exception:
        client.close()

if __name__ == '__main__':
    listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    listen_sock.bind(('0.0.0.0', 39100))
    listen_sock.listen(128)
    while True:
        conn, _ = listen_sock.accept()
        threading.Thread(target=handle_conn, args=(conn,)).start()

如果一定要复用Flask相关的依赖,可以基于werkzeug的底层服务器自定义请求处理逻辑,拦截CONNECT方法做透传,但本质还是绕开Flask的路由体系,不如直接写socket省事。

方案2:直接使用成熟代理组件

如果不需要在代理里加自定义业务逻辑,不用自己造轮子,直接用现成的正向代理方案即可,比如Nginx配置正向代理规则、用mitmproxy这类工具,稳定性比自己写的高很多。

现有代码的其他问题
  • 转发请求时直接透传全部请求头,其中Host字段是代理服务的地址,不是目标站点地址,就算HTTP请求能通,遇到做Host校验的站点也会报错
  • 异常捕获只覆盖了ProxyError,实际转发时会遇到超时、连接拒绝、证书错误等各类异常,都会直接抛出500错误
  • 代理本身配置了自签SSL证书,测试时就算隧道逻辑通了,客户端如果没信任这个自签证书,和代理建立TLS连接时就会报错,你测试代码里的verify=False只会跳过目标站点的证书校验,不会跳过代理本身的证书校验。

内容的提问来源于stack exchange,提问作者Anters Bear

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 04:09:16