打开我的网络服务器时,浏览器显示 ERR_INVALID_HTTP_RESPONSE

When opening my web server the browser says ERR_INVALID_HTTP_RESPONSE

我试图在端口 4460 上创建一个网络服务器,但是当我在浏览器中输入“http://127.0.0.1:4460/”时 地址栏浏览器显示 ERR_INVALID_HTTP_RESONSE.(Google Chrome)。浏览器是最新版本。
代码没有引发任何错误,也没有发送任何错误 bad_gateway requests.it 没有访问 .ico 文件。
Python ver:3.8.10
我的代码:

import socket
from socket import AF_INET,SOCK_STREAM
from threading import Lock
from pprint import pprint
from threadtools import threaded
from email.utils import format_datetime as fmd
import datetime
from deflate import gzip_compress
ms = (lambda x:x/1000)
socket.setdefaulttimeout(ms(700))
ol = Lock()
plok = Lock()
ENCODINGS = "utf-8 utf-16 cp936 latin-1".split()
response_header = b"""\
200 OK
Content-Type: text/html
Content-Length: $$CTXLN$$
Content-Encoding: gzip
Connection: close
Date: $$DATE$$
Keep-Alive: timeout=2, max=2
$$CTX$$"""
bad_gateway = b"""\
502 Bad Gateway
Content-type:text/html
Content-legth:0"""
def decode(x,verbose=False):
    for enc in ENCODINGS:
        flag = False
        try:
            return x.decode(enc)
        except:
            flag = True
        finally:
            print("Decoded in:"+enc) if(not flag)and verbose else None
    return ""
def startswithany(a,lis):
    for x in lis:
        if a.startswith(x):
            return True
    return False
def is_newline(x):
    return x in ("\r\n","\n")
def load_rsrc(acpt):
    if "text/html" in acpt or "text/*" in acpt or "*/*" in acpt:
        return open("response.html","rb").read()
    elif "image/ico" in acpt or "image/*" in acpt:
        return open("response.ico","rb").read()
    else:
        return b""
def handle_connection(cnct,addr):
    global pending
    with plok:
        pending += 1
    try:
        if pending > 20:#Too many connections!!!
            cnct.send(bad_gateway)
        with ol:
            print(f"----------------\nConnection from:{addr}\n")
        has_ln = True
        data = b""
        ctx = ""
        headers = {"Unknown-Lines":[]}
        while True:
            data = b""
            while not decode(data).endswith("\n"):#\r\n ends with \n
                try:
                    data += cnct.recv(1)
                except:#timeout
                    has_ln = False
                    break
            if not has_ln:
                break
            assert len(data)
            data = decode(data).strip(" ")
            assert not data.startswith(" ")
            if is_newline(data):#empty line
                continue
            if startswithany(data,("GET","POST","PUT")):
                headers["Request-Type"] = data.strip()
            else:
                dsp = data.split(":",1)
                if len(dsp)!=2:
                    print(f"Unknown header:{data}")
                    headers["Unknown-Lines"].append(data)
                else:
                    a,b = data.split(":",1)
                    b = b.strip()
                    headers[a] = b
        with ol:
            print(f"Headers:")
            for k,v in headers.items():
                print(f"{k}:{v}")
        accept = headers.get("Accept","text/html")
        accept = accept.split(",")
        q = []
        for i,x in enumerate(accept):
            if ";q=" in x:
                a,b = x.split(";q=")
                b = float(b)
                accept[i] = a
                q.append(b)
        rt = tuple(map(str.strip,headers.get("Request-Type","GET/HTTP/1.0").split("/")))
        req = rt[0]#GET/POST/PUT
        protocol = rt[1]#HTTP;NO SECURE SERVER FOR NOW
        ver = rt[2]#version
        assert ver in ("1.0","1.1")
        now = datetime.datetime.now(datetime.timezone.utc)
        datestr = fmd(now,True).encode()
        ctx = load_rsrc(accept)
        ln = str(len(ctx)+1).encode()
        response = response_header.replace(b"$$CTXLN$$",ln)\
                                  .replace(b"$$CTX$$",ctx)\
                                  .replace(b"$$DATE$$",datestr)
        response_cmpr = gzip_compress(response)
        cnct.send(response_cmpr)
        print("Sent:")
        print(response.decode())
        if headers.get("Connection","Keep-alive") == "Keep-alive":
            import time
            time.sleep(2)
    finally:
        cnct.close()
        with plok:
            pending -= 1
skt = socket.socket(AF_INET,SOCK_STREAM)
skt.bind(("",4460))
skt.listen(3)
skt.settimeout(None)
pending = 0
while True:
    cn,ad = skt.accept()
    handle_connection(cn,ad)

您离目标很近了。稍作调整后,您的代码段就可以正常工作了。主要问题出在 HTTP 响应格式上,应该定义如下:

HTTP/1.1 200 OK                 <--- Missing HTTP/1.1 prefix
Content-Type: text/html
...
Keep-Alive: timeout=2, max=2
                                <--- Mind the extra newline here which is mandatory
$$CTX$$                         <--- Browser will expect HTML here

我已经调整了您提供的 MCVE,请在下面找到适用于最新 Edge 和 Firefox 浏览器的工作版本。

import socket
from socket import AF_INET,SOCK_STREAM
from threading import Lock
from pprint import pprint
#from threadtools import threaded
from email.utils import format_datetime as fmd
import datetime
#from deflate import gzip_compress

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

response_header = b"""\
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: $$CTXLN$$
Connection: close
Date: $$DATE$$
Keep-Alive: timeout=2, max=2

$$CTX$$"""
# Missing HTTP/1.1 Prefix
# The extra new line is required

bad_gateway = b"""\
HTTP/1.1 502 Bad Gateway
Content-type:text/html
Content-legth:0
"""

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

def startswithany(a,lis):
    for x in lis:
        if a.startswith(x):
            return True
    return False

def is_newline(x):
    return x in ("\r\n","\n")

def load_rsrc(acpt):
    if "text/html" in acpt or "text/*" in acpt or "*/*" in acpt:
        #return open("response.html","rb").read()
        return b"hello"
    elif "image/ico" in acpt or "image/*" in acpt:
        return b"icon"
    else:
        return b""

def handle_connection(cnct,addr):
    global pending
    with plok:
        pending += 1
    try:
        if pending > 20:#Too many connections!!!
            cnct.send(bad_gateway)
        with ol:
            print(f"----------------\nConnection from:{addr}\n")
        has_ln = True
        data = b""
        ctx = ""
        headers = {"Unknown-Lines":[]}
        while True:
            data = b""
            while not decode(data).endswith("\n"):#\r\n ends with \n
                try:
                    data += cnct.recv(1)
                except:#timeout
                    has_ln = False
                    break
            if not has_ln:
                break
            assert len(data)
            data = decode(data).strip(" ")
            assert not data.startswith(" ")
            if is_newline(data):#empty line
                continue
            if startswithany(data,("GET","POST","PUT")):
                headers["Request-Type"] = data.strip()
            else:
                dsp = data.split(":",1)
                if len(dsp)!=2:
                    print(f"Unknown header:{data}")
                    headers["Unknown-Lines"].append(data)
                else:
                    a,b = data.split(":",1)
                    b = b.strip()
                    headers[a] = b
        with ol:
            print(f"Headers:")
            for k,v in headers.items():
                print(f"{k}:{v}")
        accept = headers.get("Accept","text/html")
        accept = accept.split(",")
        q = []
        for i,x in enumerate(accept):
            if ";q=" in x:
                a,b = x.split(";q=")
                b = float(b)
                accept[i] = a
                q.append(b)
        rt = tuple(map(str.strip,headers.get("Request-Type","GET/HTTP/1.0").split("/")))
        req = rt[0]#GET/POST/PUT
        protocol = rt[1]#HTTP;NO SECURE SERVER FOR NOW
        ver = rt[2]#version
        assert ver in ("1.0","1.1")
        now = datetime.datetime.now(datetime.timezone.utc)
        datestr = fmd(now,True).encode()
        ctx = load_rsrc(accept)
        ln = str(len(ctx)+1).encode()
        response = response_header.replace(b"$$CTXLN$$",ln)\
                                  .replace(b"$$CTX$$",ctx)\
                                  .replace(b"$$DATE$$",datestr)
        #response_cmpr = gzip_compress(response)
        cnct.send(response)
        print("Sent:")
        print(response.decode())
        if headers.get("Connection","Keep-alive") == "Keep-alive":
            import time
            time.sleep(2)
    finally:
        cnct.close()
        with plok:
            pending -= 1

skt = socket.socket(AF_INET,SOCK_STREAM)
skt.bind(("",8080))
skt.listen(3)
skt.settimeout(None)
pending = 0

while True:
    cn,ad = skt.accept()
    handle_connection(cn,ad)