11import json
2- import mimetypes
3- import webbrowser
42from functools import cached_property
53from http .cookies import SimpleCookie
64from http .server import BaseHTTPRequestHandler , HTTPServer
7- from pathlib import Path
85from urllib .parse import parse_qsl , urlparse
96
107
@@ -14,12 +11,12 @@ def url(self):
1411 return urlparse (self .path )
1512
1613 @cached_property
17- def query_data (self ):
14+ def query_data (self ) -> dict [ str , str ] :
1815 return dict (parse_qsl (self .url .query ))
1916
2017 @cached_property
21- def post_data (self ):
22- content_length = int (self .headers [ "Content-Length" ] )
18+ def post_data (self ) -> bytes :
19+ content_length = int (self .headers . get ( "Content-Length" , 0 ) )
2320 return self .rfile .read (content_length )
2421
2522 @cached_property
@@ -31,134 +28,28 @@ def cookies(self):
3128 return SimpleCookie (self .headers .get ("Cookie" ))
3229
3330 def do_GET (self ):
34- self .send_error (404 )
35-
36- def do_POST (self ):
37- self .send_error (404 )
38-
39-
40- class StaticView (WebRequestHandler ):
41- STATIC_DIR = Path ("static" )
42- STATIC_PATH = "/static"
43-
44- def do_GET (self ):
45- if self .url .path .startswith (self .STATIC_PATH ):
46- self .send_file (self .STATIC_DIR / Path (self .url .path ).name )
47- else :
48- super ().do_GET ()
49-
50- def send_file (self , path , encoding = "utf-8" ):
51- if path .exists () and path .is_file ():
52- mime_type , _ = mimetypes .guess_type (path )
53- if mime_type .startswith ("text/" ):
54- content_type = f"{ mime_type } ; charset={ encoding } "
55- else :
56- content_type = mime_type
57- self .send_response (200 )
58- self .send_header ("Content-Type" , content_type )
59- self .end_headers ()
60- self .wfile .write (path .read_bytes ())
61- else :
62- self .send_error (404 )
63-
64-
65- class DynamicView (WebRequestHandler ):
66- TEMPLATES_DIR = Path ("templates" )
67-
68- def send_redirect (self , location , extra_headers = None ):
69- self .send_response (302 )
70- self .send_header ("Location" , location )
71- if extra_headers :
72- for name , value in extra_headers .items ():
73- self .send_header (name , value )
74- self .end_headers ()
75-
76- def render_template (self , name , encoding = "utf-8" , ** context ):
7731 self .send_response (200 )
78- self .send_header ("Content-Type" , f"text/html; charset={ encoding } " )
79- self .send_header ("Cache-Control" , "no-store, must-revalidate" )
80- self .send_header ("Pragma" , "no-cache" )
81- self .send_header ("Expires" , "0" )
32+ self .send_header ("Content-Type" , "application/json" )
8233 self .end_headers ()
83- template = (self .TEMPLATES_DIR / name ).read_text (encoding )
84- self .wfile .write (template .format (** context ).encode (encoding ))
85-
86-
87- class MyAppView (StaticView , DynamicView ):
88- SESSION_COOKIE = "session"
89- USERS_DATABASE = Path ("users.json" )
90-
91- def do_GET (self ):
92- match self .url .path :
93- case "/" :
94- self .home_view ()
95- case "/login" :
96- self .login_view ()
97- case "/logout" :
98- self .logout_view ()
99- case _:
100- super ().do_GET ()
34+ self .wfile .write (self .get_response ().encode ("utf-8" ))
10135
10236 def do_POST (self ):
103- if self .url .path == "/login" :
104- self .handle_login_form ()
105- else :
106- super ().do_POST ()
107-
108- @property
109- def users (self ):
110- with self .USERS_DATABASE .open (encoding = "utf-8" ) as file :
111- return json .load (file )
112-
113- @property
114- def logged_in (self ):
115- return self .SESSION_COOKIE in self .cookies
116-
117- @property
118- def logged_user (self ):
119- return self .cookies [self .SESSION_COOKIE ].value
120-
121- def home_view (self ):
122- if self .logged_in :
123- self .render_template ("home_view.html" , username = self .logged_user )
124- else :
125- self .send_redirect ("/login" )
126-
127- def login_view (self ):
128- if self .logged_in :
129- self .send_redirect ("/" )
130- else :
131- hidden = "" if "failed" in self .query_data else "hidden"
132- self .render_template ("login_form.html" , hidden = hidden )
133-
134- def logout_view (self ):
135- self .send_redirect (
136- "/login" ,
137- extra_headers = {"Set-Cookie" : f"{ self .SESSION_COOKIE } =; Max-Age=0" },
37+ self .do_GET ()
38+
39+ def get_response (self ):
40+ return json .dumps (
41+ {
42+ "path" : self .url .path ,
43+ "query_data" : self .query_data ,
44+ "post_data" : self .post_data .decode ("utf-8" ),
45+ "form_data" : self .form_data ,
46+ "cookies" : {
47+ name : cookie .value for name , cookie in self .cookies .items ()
48+ },
49+ }
13850 )
13951
140- def handle_login_form (self ):
141- try :
142- username = self .form_data ["username" ]
143- password = self .form_data ["password" ]
144- except KeyError :
145- self .send_error (400 )
146- else :
147- if self .authenticate (username , password ):
148- self .send_redirect (
149- "/" ,
150- extra_headers = {
151- "Set-Cookie" : f"{ self .SESSION_COOKIE } ={ username } "
152- },
153- )
154- else :
155- self .send_redirect ("/login?failed=true" )
156-
157- def authenticate (self , username , password ):
158- return password == self .users .get (username )
159-
16052
16153if __name__ == "__main__" :
162- server = HTTPServer (("0.0.0.0" , 8000 ), MyAppView )
163- webbrowser .open ("http://{0}:{1}" .format (* server .server_address ))
54+ server = HTTPServer (("0.0.0.0" , 8000 ), WebRequestHandler )
16455 server .serve_forever ()
0 commit comments