read only http finished

This commit is contained in:
Matt Arnold 2025-09-07 18:27:10 -04:00
parent a4846211dc
commit 3b3f15a6ac

60
http.py
View File

@ -33,10 +33,10 @@ Fork = False
# i suspect this one might be ai generated. # i suspect this one might be ai generated.
# It's a subclass of dict with key access via the dot # It's a subclass of dict with key access via the dot
# operator. Similar to how ruby does things # 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 # Obviously some things still need standard dictionary access
# client's code in a public project. # i found this code looked at it, tested it, before using it
# Thus cut 'n' paste. #
CRLF = "\r\n" CRLF = "\r\n"
LF = "\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 # 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 # implementation. This is just making us ssl ready and laying the ground work
class HttpRequest(AccessDict): class HttpRequest(AccessDict):
def __init__( def __init__(self, method="GET", path="/", headers={}, *args, **kwargs):
self, method="GET", path="/", headers={}, body="goodbye\r\n", *args, **kwargs
):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self["method"] = method self["method"] = method
self["headers"] = headers self["headers"] = headers
self["body"] = StringIO(body) if "body" in kwargs:
self["body"] = StringIO(kwargs["body"])
self["path"] = path self["path"] = path
if "Host" not in self["headers"]: if "Host" not in self["headers"]:
self["headers"]["Host"] = "localhost" self["headers"]["Host"] = "localhost"
@ -96,14 +95,14 @@ class HttpRequest(AccessDict):
buf = StringIO() buf = StringIO()
buf.write(f"{self.method} {self.path} HTTP/1.1") buf.write(f"{self.method} {self.path} HTTP/1.1")
for k, v in self["headers"].items(): for k, v in self["headers"].items():
buf.write(f"{k}: {v}\r\n") buf.write(f"{k}: {v}" + CRLF)
buf.write(CRLF + CRLF) buf.write(CRLF)
buf.write(self["body"].getvalue() + "\r\n") buf.write(self["body"].getvalue() + CRLF)
return buf.getvalue() + "\r\n" return buf.getvalue() + CRLF
class HttpResponse(AccessDict): 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) super().__init__(*args, **kwargs)
self["status"] = status self["status"] = status
self["headers"] = headers self["headers"] = headers
@ -125,15 +124,16 @@ class HttpResponse(AccessDict):
def __str__(self): def __str__(self):
buf = StringIO() buf = StringIO()
print(self.headers) 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()) length = len(self["body"].getvalue())
for k, v in self["headers"].items(): for k, v in self["headers"].items():
buf.write(f"{k}: {v}\r\n") buf.write(f"{k}: {v}\r\n")
if "Content-Length" not in self["headers"]:
buf.write(f"Content-Length: {length}\r\n") buf.write(f"Content-Length: {length}\r\n")
buf.write(CRLF + CRLF) # Per RFC 9112 buf.write(CRLF) # Per RFC 9112
buf.write(self["body"].getvalue() + "\r\n") buf.write(self["body"].getvalue() + CRLF)
return buf.getvalue() + "\r\n" return buf.getvalue() + CRLF
RICKROLL_LYRICS = """ RICKROLL_LYRICS = """
@ -170,6 +170,10 @@ Never gonna make you cry, never gonna say goodbye
Never gonna tell a lie and hurt you 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 = HttpResponse()
good_response.status = 200 good_response.status = 200
good_response.headers["Last-Modified"] = "Mon, 27 July 1987 00:00 GMT" good_response.headers["Last-Modified"] = "Mon, 27 July 1987 00:00 GMT"
@ -209,7 +213,7 @@ def server_handler():
return 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 # This will still accept anything as long as the method verb
# is correct. Clients need not conform to RFC 9112 (yet). # is correct. Clients need not conform to RFC 9112 (yet).
# We however, do our best effort to conform to rfc 9112, # 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 # the client, but strict in what you send back, was first
# was first forumlated by John Postel in that later half # was first forumlated by John Postel in that later half
# of the 1970s. # 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): def client_handler(sock):
@ -234,11 +249,16 @@ def client_handler(sock):
dstring = data.decode("UTF-8") dstring = data.decode("UTF-8")
if dstring.startswith("GET"): if dstring.startswith("GET"):
break break
elif dstring.startswith("HEAD"):
hr = str(head_response)
sock.send(hr.encode("utf-8"))
sock.close()
return
else: else:
error = str(error_response) error = str(error_response)
sock.send(error.encode("utf-8")) sock.send(error.encode("utf-8"))
junk_counter += 1 junk_counter += 1
gevent.sleep(0.25) gevent.sleep(0.25) # this is a somewhat magical value
default = str(good_response) default = str(good_response)
sock.send(default.encode("utf-8")) sock.send(default.encode("utf-8"))
sock.close() sock.close()