Skip to content

Commit bf4df5c

Browse files
committed
Updates.
1 parent 637425e commit bf4df5c

File tree

5 files changed

+231
-0
lines changed

5 files changed

+231
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.swp
2+
__pycache__

mini_django.py

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# https://docs.python.org/3/howto/sockets.html
2+
# https://stackoverflow.com/questions/8627986/how-to-keep-a-socket-open-until-client-closes-it
3+
# https://stackoverflow.com/questions/10091271/how-can-i-implement-a-simple-web-server-using-python-without-using-any-libraries
4+
5+
from socket import *
6+
import traceback, json
7+
from dataclasses import dataclass
8+
from dataclasses import field
9+
import sys
10+
11+
@dataclass
12+
class HttpRequest:
13+
method: str = ""
14+
path: str = ""
15+
headers: dict = field(default_factory=dict)
16+
body: str = ""
17+
18+
@dataclass
19+
class HttpResponse:
20+
code: str = "200"
21+
headers: dict = field(default_factory=dict)
22+
_body: list = field(default_factory=list)
23+
24+
def println(self, line: str) :
25+
self._body.append(line)
26+
27+
def parseRequest(rd:str) -> HttpRequest:
28+
retval = HttpRequest()
29+
retval.body = rd
30+
ipos = rd.find("\r\n\r\n")
31+
if ipos < 1 :
32+
print('Incorrectly formatted request input')
33+
print(repr(rd))
34+
return None
35+
36+
# Find the blank line between HEAD and BODY
37+
head = rd[0:ipos-1]
38+
lines = head.split("\n")
39+
40+
# GET / HTTP/1.1
41+
if len(lines) > 0 :
42+
firstline = lines[0]
43+
pieces = firstline.split(' ')
44+
if len(pieces) >= 2 :
45+
retval.method = pieces[0] or 'Missing';
46+
retval.path = pieces[1] or 'Missing';
47+
48+
# Accept-Language: en-US,en;q=0.5
49+
for line in lines:
50+
line = line.strip()
51+
pieces = line.split(": ", 1)
52+
if len(pieces) != 2 : continue
53+
retval.headers[pieces[0].strip()] = pieces[1].strip()
54+
return retval
55+
56+
def responseSend(clientsocket, response: HttpResponse) :
57+
58+
try:
59+
print('==== Sending Response Headers')
60+
firstline = "HTTP/1.1 "+response.code+" OK\r\n"
61+
clientsocket.sendall(firstline.encode())
62+
for key, value in response.headers.items():
63+
print(key+': '+value)
64+
clientsocket.sendall(key.encode())
65+
clientsocket.sendall(": ".encode())
66+
clientsocket.sendall(value.encode())
67+
clientsocket.sendall("\r\n".encode())
68+
69+
70+
clientsocket.sendall("\r\n".encode())
71+
chars = 0
72+
for line in response._body:
73+
line = patchAutograder(line)
74+
75+
chars += len(line)
76+
clientsocket.sendall(line.replace("\n", "\r\n").encode())
77+
clientsocket.sendall("\r\n".encode())
78+
print("==== Sent",chars,"characters body output")
79+
80+
except Exception as exc :
81+
print(exc)
82+
print(response)
83+
print(traceback.format_exc())
84+
85+
# If we are sending HTML, include the endpoint for the DJ4E JavaScript autograder
86+
# For local dev testing, this can be run as
87+
# python runserver.py 9000 http://localhost:8888/dj4e/tools/jsauto/autograder.js
88+
89+
def patchAutograder(line: str) -> str:
90+
if line.find('</body>') == -1 : return line
91+
dj4e_autograder = "https://www.dj4e.com/tools/jsauto/autograder.js"
92+
if len(sys.argv) > 2 :
93+
dj4e_autograder = sys.argv[2]
94+
return line.replace('</body>', '\n<script src="'+dj4e_autograder+'"></script>\n</body>');
95+
96+
def httpServer(router):
97+
port = 9000
98+
if len(sys.argv) > 1 :
99+
port = int(sys.argv[1])
100+
101+
print('\n================ Starting mini_django server on '+str(port))
102+
serversocket = socket(AF_INET, SOCK_STREAM)
103+
try :
104+
serversocket.bind(('localhost',port))
105+
serversocket.listen(5)
106+
while(1):
107+
print('\n================ Waiting for the Next Request')
108+
(clientsocket, address) = serversocket.accept()
109+
110+
rd = clientsocket.recv(5000).decode()
111+
print('====== Received Headers')
112+
print(rd)
113+
request = parseRequest(rd)
114+
115+
# If we did not get a valid request, send a 500
116+
if not isinstance(request, HttpRequest) :
117+
response = view_fail(request, "500", "Request could not be parsed")
118+
119+
# Send valid request to the router (urls.py)
120+
else:
121+
response = router(request)
122+
123+
# If we did not get a valid response, log it and send back a 500
124+
if not isinstance(response, HttpResponse) :
125+
response = view_fail(request, "500", "Response returned from router / view is not of type HttpResponse")
126+
127+
try:
128+
responseSend(clientsocket, response)
129+
clientsocket.shutdown(SHUT_WR)
130+
except Exception as exc :
131+
print(exc)
132+
print(traceback.format_exc())
133+
134+
except KeyboardInterrupt :
135+
print("\nShutting down...\n")
136+
except Exception as exc :
137+
print(exc)
138+
print(traceback.format_exc())
139+
140+
print("Closing socket")
141+
serversocket.close()
142+
143+
def view_fail(req: HttpRequest, code: str, failure: str) -> HttpResponse:
144+
res = HttpResponse()
145+
146+
print(" ")
147+
print("Sending view_fail, code="+code+" failure="+failure)
148+
149+
res.code = code
150+
151+
res.headers['Content-Type'] = 'text/html; charset=utf-8'
152+
153+
res.println('<html><body>')
154+
if res.code == "404" :
155+
res.println('<div style="background-color: rgb(255, 255, 204);">')
156+
else :
157+
res.println('<div style="background-color: pink;">')
158+
159+
res.println('<b>Page has errors</b>')
160+
res.println('<div><b>Request Method:</b> '+req.method+"</div>")
161+
res.println('<div><b>Request URL:</b> '+req.path+'</div>')
162+
res.println('<div><b>Response Failure:</b> '+failure+'</div>')
163+
res.println('<div><b>Response Code:</b> '+res.code+'</div>')
164+
res.println("</div><pre>")
165+
res.println("Valid paths: /dj4e /js4e or /404")
166+
res.println("\nRequest header data:")
167+
res.println(json.dumps(req.headers, indent=4))
168+
res.println("</pre></body></html>")
169+
return res
170+
171+

runserver.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import mini_django
2+
import urls
3+
4+
print('Access http://localhost:9000')
5+
mini_django.httpServer(urls.router)
6+

urls.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
from mini_django import HttpRequest, HttpResponse, view_fail
3+
import views
4+
5+
# This is similar to Django's urls.py
6+
7+
def router(request: HttpRequest) -> HttpResponse:
8+
print('==== Routing to path:', request.path);
9+
if request.path == '/' :
10+
return views.root(request)
11+
elif request.path.startswith('/dj4e') :
12+
return views.dj4e(request)
13+
elif request.path == '/js4e' :
14+
return views.js4e(request)
15+
elif request.path == '/broken' :
16+
return views.broken(request)
17+
18+
# When all else fails send the 404 screen
19+
else :
20+
return view_fail(request, "404", "urls.py could not find a view for the path")
21+

views.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
from mini_django import HttpRequest, HttpResponse
3+
4+
# This is similar to Django's views.py
5+
6+
def root(req: HttpRequest) -> HttpResponse:
7+
res = HttpResponse()
8+
res.headers['Content-Type'] = 'text/html; charset=utf-8'
9+
res.println("<html><head></head><body>")
10+
res.println("This is the page at the root path, try another path")
11+
res.println("Try /dj4e /js4e /ca4e or /broken")
12+
res.println("</body></html>")
13+
return res
14+
15+
def dj4e(req: HttpRequest) -> HttpResponse:
16+
res = HttpResponse()
17+
res.headers['Content-Type'] = 'text/plain; charset=utf-8'
18+
res.println("Django is fun")
19+
return res
20+
21+
def js4e(req: HttpRequest) -> HttpResponse:
22+
res = HttpResponse()
23+
res.code = "302" # Lets do a temporary redirect...
24+
res.headers['Location'] = '/dj4e'
25+
res.headers['Content-Type'] = 'text/plain; charset=utf-8'
26+
res.println("You will only see this in the debugger!")
27+
return res
28+
29+
def broken(req: HttpRequest):
30+
return "I am a broken view, returning a string by mistake"
31+

0 commit comments

Comments
 (0)