Skip to content

Commit 654f58f

Browse files
authored
Add files via upload
0 parents  commit 654f58f

6 files changed

Lines changed: 341 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
No Documentation yet.
2+
Still under development.

config.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import os
2+
3+
# Configuration
4+
5+
DATABASE = {
6+
}
7+
8+
favicon = False
9+
10+
#Should be relative to this path
11+
template_dir = "templates"

http_response.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Response module
2+
from config import template_dir
3+
from parser import set_cookie_header
4+
5+
6+
7+
def response(context, status, data):
8+
""" Set Header and start response """
9+
content_type = context.request["content-type"]
10+
set_cookie = set_cookie_header(context.session)
11+
header = [("Content-Type", content_type),] + set_cookie
12+
start_response = context.response["start_response"]
13+
start_response(status, header)
14+
return data
15+
16+
17+
def static_response(context, status, data):
18+
""" Set Header and start static response """
19+
content_type = context.request["content-type"]
20+
header = [("Content-Type", content_type),]
21+
start_response = context.response["start_response"]
22+
start_response(status, header)
23+
return data
24+
25+
26+
def encode_html(path):
27+
path = template_dir + "/" + path
28+
try:
29+
with open(path, "rb") as f:
30+
data = f.read()
31+
except FileNotFoundError:
32+
raise Exception("Template cannot be found")
33+
return data
34+
35+
36+
def redirect(context, status, uri):
37+
header = context.response["header"]
38+
header["Location"] = uri
39+
start_response = context.response["start_response"]
40+
start_response(status, list(header.items()))
41+
return b""
42+
43+

parser.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Parser module to parse environ variables
2+
from http.cookies import SimpleCookie
3+
import io
4+
import cgi
5+
import re
6+
7+
8+
def clean(item):
9+
if item != "":
10+
return True
11+
12+
def set_cookie_header(cookie):
13+
"""Parse cookie object to HTTP Set-Cookie header"""
14+
15+
header = list()
16+
cookie_options = cookie.output().split("Set-Cookie:")
17+
cookie_options = list(filter(clean, cookie_options))
18+
for index, option in enumerate(cookie_options):
19+
cookie_options[index] = option.strip("\r\n")
20+
cookie_options = ",".join(cookie_options) + '; ' + ';'.join(cookie.options)
21+
22+
23+
header = [("Set-Cookie", cookie_options),]
24+
return header
25+
26+
27+
28+
29+
class Cookie(SimpleCookie):
30+
"""Cookie class inheriting http.SimpleCookie class"""
31+
32+
def __init__(self):
33+
super().__init__()
34+
self.options = ["HttpOnly",]
35+
36+
def __setitem__(self, key, value):
37+
super().__setitem__(key, value)
38+
item = self.__getitem__(key)
39+
40+
def httponly(self, choice):
41+
"""Set httponly option in Set-Cookie"""
42+
if choice == True:
43+
if not "HttpOnly" in self.options:
44+
self.options.append("HttpOnly")
45+
elif choice == False:
46+
if "HttpOnly" in self.options:
47+
self.options.remove("HttpOnly")
48+
49+
50+
def secure(self, choice):
51+
if choice == True:
52+
if not "Secure" in self.options:
53+
self.options.append("Secure")
54+
elif choice == False:
55+
if "Secure" in self.options:
56+
self.options.remove("Secure")
57+
58+
59+
60+
61+
def flush(self):
62+
"""Expires a given cookie value"""
63+
64+
self.options.append("expires=Mon 23 Jun 1967 04:34:23 GMT")
65+
66+
def will_expire(self, date):
67+
"""Sets future expiry date for cookie value"""
68+
69+
self.options.append("expires={}".format(date))
70+
71+
72+
73+
74+
75+
76+
77+
class Context():
78+
"""Make needed environ variables accessible in a single class"""
79+
80+
def __init__(self, environ):
81+
82+
#Get incoming cookies
83+
cookie = environ.get("HTTP_COOKIE", "")
84+
self.session = Cookie()
85+
self.session.load(cookie)
86+
87+
#HTTP Request
88+
self.request = dict()
89+
self.request["content-length"] = environ.get("CONTENT_LENGTH", 0)
90+
self.request["content-type"] = "text/html"
91+
self.request["user-agent"] = environ.get("HTTP_USER-AGENT", "")
92+
self.request["ip-address"] = environ.get("REMOTE_ADDR", "")
93+
self.request["path"] = environ.get("PATH_INFO", "/")
94+
self.query_str = environ.get("QUERY_STRING")
95+
self.request["method"] = environ.get("REQUEST_METHOD")
96+
self.request["protocol"] = environ.get("SERVER_PROTOCOL", "")
97+
self.request["scheme"] = environ.get("wsgi.url_scheme", "http")
98+
self.request["data"] = environ.get("wsgi.input", "")
99+
100+
101+
#HTTP Response dict
102+
self.response = dict()
103+
104+
#HTTP Parameters dict()
105+
self.params = dict()
106+
107+
if self.request["content-length"] == '':
108+
self.request["content-length"]=0
109+
110+
#HTTP Form
111+
self.form = dict()
112+
self.formFile = dict()
113+
fp = self.request["data"].read(int(self.request["content-length"]))
114+
formValues = cgi.FieldStorage(fp=io.BytesIO(fp), environ=environ, keep_blank_values=True)
115+
keys = formValues.keys()
116+
for key in keys:
117+
value = formValues[key]
118+
if value.filename:
119+
self.formFile[key] = value
120+
else:
121+
self.form[key] = formValues.getvalue(key)
122+
123+
124+
#Query matching and parsing
125+
self.query = dict()
126+
127+
query_regex = "[a-zA-Z0-9%+]+=[a-zA-Z0-9%+]+"
128+
queries = re.findall(query_regex, self.query_str)
129+
for index, query in enumerate(queries):
130+
queries[index] = query.replace("%20", " ").replace("+", " ")
131+
132+
#Populate Query dict
133+
for query in queries:
134+
q = query.split("=")
135+
self.query[q[0]] = q[1]
136+
137+
138+

router.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Router package for matching routes
2+
import re
3+
4+
5+
def path_to_regex(path):
6+
path_segments = path.split("/")
7+
params = dict()
8+
for index, segment in enumerate(path_segments):
9+
if segment == ":id":
10+
path_segments[index] = "[0-9]+"
11+
elif segment == ":slug":
12+
path_segments[index] = "[a-zA-Z]+(?:-[a-zA-Z]+)*"
13+
path = "/".join(path_segments) + "$"
14+
return path
15+
16+
17+
18+
def match_route(routes, path):
19+
for route in routes:
20+
match = re.match(route, path)
21+
if match is not None:
22+
return route
23+
24+
25+
def get_params(match, path):
26+
path_segments = path.split("/")
27+
match_segments = match.split("/")
28+
id_ = None
29+
slug = None
30+
id_index = None
31+
slug_index = None
32+
if "[0-9]+" in match_segments:
33+
id_index = match_segments.index("[0-9]+")
34+
if "[a-zA-Z]+(?:-[a-zA-Z]+)*" in match_segments:
35+
slug_index = match_segments.index("[a-zA-Z]+(?:-[a-zA-Z]+)*")
36+
if id_index != None:
37+
id_ = path_segments[id_index]
38+
if slug_index != None:
39+
slug = path_segments[slug_index]
40+
41+
return (id_, slug)
42+
43+
44+
45+
46+
47+
48+
49+
50+
51+
52+
53+
54+

wsgi.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from parser import Context
2+
import router
3+
4+
5+
class App(object):
6+
def __init__(self):
7+
self.routes_func = []
8+
self.routes_no_func = []
9+
self.static_path_prefix = '--'
10+
self.static_dir = ''
11+
12+
13+
def match_route(self, context):
14+
"""Function to match response function for a given request"""
15+
16+
global resp
17+
18+
path = context.request["path"]
19+
if path[-1] != "/":
20+
path += "/"
21+
22+
#Check if static
23+
if path.startswith(self.static_path_prefix):
24+
resp = self.serve_static(context, path)
25+
else:
26+
resp = self.serve_response(context, path)
27+
return resp
28+
29+
30+
31+
def serve_response(self, context, path):
32+
"""Method to serve HTTP response"""
33+
global response_
34+
35+
matched_route = router.match_route(self.routes_no_func, path)
36+
if matched_route == None:
37+
resp = not_found(context)
38+
else:
39+
id_, slug = router.get_params(matched_route, path)
40+
context.params["slug"] = slug
41+
context.params["id"] = id_
42+
for route in self.routes_func:
43+
if route[0] == matched_route:
44+
response_ = route[1](context)
45+
return response_
46+
47+
48+
49+
50+
def run(self, environ, start_response):
51+
""" WSGI entry point"""
52+
53+
context_ = Context(environ)
54+
context_.response["start_response"] = start_response
55+
response = self.match_route(context_)
56+
yield response
57+
58+
59+
def set_static(self, static_path_prefix, static_dir):
60+
"""Set static directory to serve from and path prefix"""
61+
self.static_path_prefix = static_path_prefix
62+
if static_dir[-1] != '/':
63+
self.static_dir = static_dir + '/'
64+
65+
66+
def serve_static(self, context, path):
67+
""" Method to serve static files """
68+
file_path = self.static_dir + path.lstrip(self.static_path_prefix).rstrip("/")
69+
try:
70+
with open(file_path, "rb") as file_:
71+
static_file = file_.read()
72+
except:
73+
static_file = b''
74+
75+
return static_response(context, "200 OK", static_file)
76+
77+
def add_path(self, uri, method):
78+
if uri[-1] != "/":
79+
uri += "/"
80+
uri = router.path_to_regex(uri)
81+
path = (uri, method)
82+
self.routes_func.append(path)
83+
self.routes_no_func.append(uri)
84+
85+
86+
87+
88+
89+
def not_found(context):
90+
data = encode_html("404.html")
91+
return response(context, "404 NOT FOUND", data)
92+
93+

0 commit comments

Comments
 (0)