diff --git a/http.py b/http.py index 2e0cd12..20f5e00 100644 --- a/http.py +++ b/http.py @@ -33,10 +33,10 @@ Fork = False # i suspect this one might be ai generated. # It's a subclass of dict with key access via the dot # operator. Similar to how ruby does things -# I don't know why this isn't the default -# i've coded this dozens of times but i couldn't use -# client's code in a public project. -# Thus cut 'n' paste. + +# Obviously some things still need standard dictionary access +# i found this code looked at it, tested it, before using it +# CRLF = "\r\n" LF = "\n" @@ -78,13 +78,12 @@ class AccessDict(dict): # In a future part. I will go over how to make a more full featured # implementation. This is just making us ssl ready and laying the ground work class HttpRequest(AccessDict): - def __init__( - self, method="GET", path="/", headers={}, body="goodbye\r\n", *args, **kwargs - ): + def __init__(self, method="GET", path="/", headers={}, *args, **kwargs): super().__init__(*args, **kwargs) self["method"] = method self["headers"] = headers - self["body"] = StringIO(body) + if "body" in kwargs: + self["body"] = StringIO(kwargs["body"]) self["path"] = path if "Host" not in self["headers"]: self["headers"]["Host"] = "localhost" @@ -96,14 +95,14 @@ class HttpRequest(AccessDict): buf = StringIO() buf.write(f"{self.method} {self.path} HTTP/1.1") for k, v in self["headers"].items(): - buf.write(f"{k}: {v}\r\n") - buf.write(CRLF + CRLF) - buf.write(self["body"].getvalue() + "\r\n") - return buf.getvalue() + "\r\n" + buf.write(f"{k}: {v}" + CRLF) + buf.write(CRLF) + buf.write(self["body"].getvalue() + CRLF) + return buf.getvalue() + CRLF class HttpResponse(AccessDict): - def __init__(self, status="404", headers={}, body="goodbye\r\n", *args, **kwargs): + def __init__(self, status="400", headers={}, body="goodbye\r\n", *args, **kwargs): super().__init__(*args, **kwargs) self["status"] = status self["headers"] = headers @@ -125,15 +124,16 @@ class HttpResponse(AccessDict): def __str__(self): buf = StringIO() print(self.headers) - buf.write(f"HTTP/1.1 {self.status}\r\n") + buf.write(f"HTTP/1.1 {self.status}" + CRLF) length = len(self["body"].getvalue()) for k, v in self["headers"].items(): buf.write(f"{k}: {v}\r\n") - buf.write(f"Content-Length: {length}\r\n") - buf.write(CRLF + CRLF) # Per RFC 9112 + if "Content-Length" not in self["headers"]: + buf.write(f"Content-Length: {length}\r\n") + buf.write(CRLF) # Per RFC 9112 - buf.write(self["body"].getvalue() + "\r\n") - return buf.getvalue() + "\r\n" + buf.write(self["body"].getvalue() + CRLF) + return buf.getvalue() + CRLF RICKROLL_LYRICS = """ @@ -170,6 +170,10 @@ Never gonna make you cry, never gonna say goodbye Never gonna tell a lie and hurt you """ +head_response = HttpResponse() +head_response.status = 200 +head_response.headers["Content-Length"] = 980 +head_response.write("") good_response = HttpResponse() good_response.status = 200 good_response.headers["Last-Modified"] = "Mon, 27 July 1987 00:00 GMT" @@ -209,7 +213,7 @@ def server_handler(): return -# I made two simple changes to make it use our new http objects +# I made three simple changes to make it use our new http objects # This will still accept anything as long as the method verb # is correct. Clients need not conform to RFC 9112 (yet). # We however, do our best effort to conform to rfc 9112, @@ -219,8 +223,19 @@ def server_handler(): # the client, but strict in what you send back, was first # was first forumlated by John Postel in that later half # of the 1970s. +# +# Lastly we add support for the HEAD method, as some http clients +# Will get confused if we don't have it +# RFC 9112 appears to say GET and HEAD are the only methods we +# are ABSOLUTELY REQUIRED to support. So we add it.abs +# At this point it will get a static response as well. +# See Above. -# Doing this, is also motivation for me to write Parts 4, 5, and 6 +# Doing this, is also motivation for me to write Parts 1, 2, and 3 +# By the way you're in a Star Wars, Sort of thing +# This is Part 5. I thought the 4 would be more entertaining.abs +# Parts 7, 8, and 9 will be made for Capitalism reasons +# AKA Donate on my kofi link at the end. def client_handler(sock): @@ -234,11 +249,16 @@ def client_handler(sock): dstring = data.decode("UTF-8") if dstring.startswith("GET"): break + elif dstring.startswith("HEAD"): + hr = str(head_response) + sock.send(hr.encode("utf-8")) + sock.close() + return else: error = str(error_response) sock.send(error.encode("utf-8")) junk_counter += 1 - gevent.sleep(0.25) + gevent.sleep(0.25) # this is a somewhat magical value default = str(good_response) sock.send(default.encode("utf-8")) sock.close()