Search code examples
pythonpython-3.xserverpython-3.8

When opening my web server the browser says ERR_INVALID_HTTP_RESPONSE


I tried to make a web server on port 4460 but when i type "http://127.0.0.1:4460/" in the browser address bar the browser says ERR_INVALID_HTTP_RESONSE.(Google Chrome).Browser is latest version.
The code did not raise any errors and did not send any bad_gateway requests.it did not access the .ico file.
Python ver:3.8.10
my code:

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)

Solution

  • You are close to your goal. Making slight adjustments I got your snippet working. The main issue is in the HTTP response formatting, it should be defined as follow:

    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
    

    I have adapted the MCVE your provided, please find below a working version for latest Edge and Firefox browsers.

    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)