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

Python 3.8.10搭建4460端口Web服务器时Chrome提示ERR_INVALID_HTTP_RESPONSE错误求助

排查Chrome显示ERR_INVALID_HTTP_RESPONSE的问题

我帮你梳理了代码里的几个关键问题,这些问题导致浏览器无法识别你的HTTP响应,从而抛出ERR_INVALID_HTTP_RESPONSE错误:

1. HTTP响应格式不符合协议规范

HTTP响应的第一行必须包含HTTP版本,比如 HTTP/1.1 200 OK,你的response_header第一行只写了200 OK,浏览器根本无法识别这是合法的HTTP响应开头。

另外,HTTP协议要求所有头字段之间、头与正文之间必须用\r\n(CRLF)分隔,而你用的是\n,这也会导致解析失败。

2. 响应头与正文的压缩逻辑错误

你把整个响应(包括头和正文)都用gzip压缩了,但HTTP头是不允许被压缩的——Content-Encoding: gzip只表示响应正文是gzip压缩的。同时你计算的Content-Length是原始正文长度+1,这完全不符合实际,应该填写压缩后正文的长度。

3. 请求解析逻辑有缺陷

  • 你通过循环逐字节读取直到\n的方式效率极低,而且没有正确判断请求头结束的标志(连续两个\r\n,也就是空行),导致代码可能超时后才开始处理响应,此时浏览器已经失去耐心。
  • bad_gateway响应里有拼写错误:Content-legth应该是Content-Length,同样也缺少HTTP版本开头。

修正后的代码

下面是修复了上述问题的完整代码,我保留了你的核心逻辑,只修正了协议相关的错误:

import socket
from socket import AF_INET, SOCK_STREAM
from threading import Lock
from datetime import datetime, timezone
from email.utils import format_datetime as fmd
import gzip  # 改用标准库的gzip,避免第三方依赖

ms = lambda x: x / 1000
socket.setdefaulttimeout(ms(700))
ol = Lock()
plok = Lock()
ENCODINGS = "utf-8 utf-16 cp936 latin-1".split()

# 修正响应头格式:添加HTTP版本,用CRLF分隔,最后留空行分隔头和正文
response_header = b"""\
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: $$CTXLN$$
Content-Encoding: gzip
Connection: close
Date: $$DATE$$
Keep-Alive: timeout=2, max=2

"""
# 修正bad_gateway的格式和拼写
bad_gateway = b"""\
HTTP/1.1 502 Bad Gateway
Content-Type: text/html
Content-Length: 0

"""

def decode(x, verbose=False):
    for enc in ENCODINGS:
        flag = False
        try:
            return x.decode(enc)
        except:
            flag = True
        finally:
            if not flag and verbose:
                print(f"Decoded in: {enc}")
    return ""

def startswithany(a, lis):
    return any(a.startswith(x) for x in lis)

def is_empty_line(x):
    return x.strip() == ""

def load_rsrc(acpt):
    if any(ctype in acpt for ctype in ("text/html", "text/*", "*/*")):
        try:
            return open("response.html", "rb").read()
        except FileNotFoundError:
            return b"<h1>Hello World!</h1>"  #  fallback内容
    elif any(ctype in acpt for ctype in ("image/ico", "image/*")):
        try:
            return open("response.ico", "rb").read()
        except FileNotFoundError:
            return b""
    else:
        return b""

def handle_connection(cnct, addr):
    global pending
    with plok:
        pending += 1
    try:
        if pending > 20:
            cnct.send(bad_gateway)
            return
        
        with ol:
            print(f"----------------\nConnection from: {addr}\n")
        
        # 改进请求读取逻辑:一次性读取更多数据,然后按CRLF分割
        data = b""
        while b"\r\n\r\n" not in data:  # 等待请求头结束的空行
            chunk = cnct.recv(1024)
            if not chunk:
                break
            data += chunk
        
        if not data:
            return
        
        # 解析请求头
        headers = {"Unknown-Lines": []}
        lines = decode(data).split("\r\n")
        for line in lines:
            if is_empty_line(line):
                continue
            if startswithany(line, ("GET", "POST", "PUT")):
                headers["Request-Type"] = line.strip()
            else:
                dsp = line.split(":", 1)
                if len(dsp) != 2:
                    print(f"Unknown header: {line}")
                    headers["Unknown-Lines"].append(line)
                else:
                    a, b = dsp
                    headers[a.strip()] = b.strip()
        
        with ol:
            print(f"Headers:")
            for k, v in headers.items():
                print(f"{k}: {v}")
        
        # 处理Accept头
        accept = headers.get("Accept", "text/html").split(",")
        accept = [item.split(";q=")[0].strip() for item in accept]
        
        # 构造响应
        now = datetime.now(timezone.utc)
        datestr = fmd(now, True).encode()
        ctx = load_rsrc(accept)
        
        # 压缩正文
        compressed_ctx = gzip.compress(ctx)
        ln = str(len(compressed_ctx)).encode()
        
        # 填充响应头变量
        response_hdr = response_header.replace(b"$$CTXLN$$", ln)\
                                      .replace(b"$$DATE$$", datestr)
        
        # 先发送头,再发送压缩后的正文
        cnct.send(response_hdr)
        cnct.send(compressed_ctx)
        
        with ol:
            print("Sent header:")
            print(response_hdr.decode())
            print(f"Compressed body length: {len(compressed_ctx)}")
        
        if headers.get("Connection", "").lower() == "keep-alive":
            import time
            time.sleep(2)
    except Exception as e:
        with ol:
            print(f"Error handling connection: {e}")
    finally:
        cnct.close()
        with plok:
            pending -= 1

if __name__ == "__main__":
    skt = socket.socket(AF_INET, SOCK_STREAM)
    skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 允许端口复用
    skt.bind(("", 4460))
    skt.listen(3)
    skt.settimeout(None)
    pending = 0
    print("Server running on http://127.0.0.1:4460/")
    while True:
        cn, ad = skt.accept()
        handle_connection(cn, ad)

额外说明

  • 我改用了Python标准库的gzip模块,避免依赖第三方的deflate库,如果你坚持要用deflate,可以把gzip.compress替换回去。
  • 添加了文件不存在的 fallback 内容,避免因为缺少response.html或response.ico导致报错。
  • 优化了请求头的读取逻辑,一次性读取1024字节,直到找到请求结束的空行,效率更高。
  • 添加了SO_REUSEADDR套接字选项,避免重启服务器时出现端口占用错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 13:17:35