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
相关产品推荐
相关产品推荐

