1+ """
2+ Project: GurgleApps Web Server
3+ File: gurgleapps_webserver.py
4+ Author: GurgleApps.com
5+ Date: Your Date 2023-04-01
6+ Description: GurgleApps Web Server
7+ """
18import network
29import re
310import time
613import ujson as json
714from response import Response
815from request import Request
16+ import gc
17+ import os
18+
919
1020class GurgleAppsWebserver :
1121
12- def __init__ (self , wifi_ssid , wifi_password , port = 80 , timeout = 20 , doc_root = "/www" ):
22+ def __init__ (self , wifi_ssid , wifi_password , port = 80 , timeout = 20 , doc_root = "/www" , log_level = 0 ):
1323 print ("GurgleApps.com Webserver" )
1424 self .port = port
1525 self .timeout = timeout
1626 self .wifi_ssid = wifi_ssid
1727 self .wifi_password = wifi_password
1828 self .doc_root = doc_root
19- self .function_routes = []
29+ self .function_routes = []
30+ self .log_level = log_level
2031 # wifi client in station mode so we can connect to an access point
2132 self .wlan = network .WLAN (network .STA_IF )
2233 # activate the interface
2334 self .wlan .active (True )
2435 # connect to the access point with the ssid and password
2536 self .wlan .connect (self .wifi_ssid , self .wifi_password )
26-
2737 self .html = """<!DOCTYPE html>
2838 <html>
2939 <head> <title>GurgleApps.com Webserver</title> </head>
@@ -40,33 +50,43 @@ def __init__(self, wifi_ssid, wifi_password, port=80, timeout=20, doc_root="/www
4050 print ('waiting for connection...' )
4151 time .sleep (1 )
4252
43- if self .wlan .status () != 3 :
53+ #if self.wlan.status() != 3:
54+ if self .wlan .isconnected () == False :
4455 raise RuntimeError ('network connection failed' )
4556 else :
4657 print ('connected' )
4758 status = self .wlan .ifconfig ()
4859 print ('ip = ' + status [0 ])
4960 self .serving = True
5061 print ('point your browser to http://' , status [0 ])
51- try :
52- pass
53- #asyncio.run(self.start_server())
54- except OSError as e :
55- print (e )
56- finally :
57- asyncio .new_event_loop ()
62+ #asyncio.new_event_loop()
5863 print ("exit constructor" )
5964
65+ # async def start_server(self):
66+ # print("start_server")
67+ # asyncio.create_task(asyncio.start_server(
68+ # self.serve_request, "0.0.0.0", 80))
69+ # while self.serving:
70+ # await asyncio.sleep(0.1)
71+
6072 async def start_server (self ):
61- asyncio .create_task (asyncio .start_server (self .serve_request , "0.0.0.0" , 80 ))
62- while self .serving :
63- await asyncio .sleep (1 )
64-
73+ print ("start_server" )
74+ server_task = asyncio .create_task (asyncio .start_server (
75+ self .serve_request , "0.0.0.0" , 80 ))
76+ await server_task
77+
78+ # async def start_server(self):
79+ # print("start_server")
80+ # server = await asyncio.start_server(
81+ # self.serve_request, "0.0.0.0", 80)
82+ # async with server:
83+ # await server.serve_forever()
84+
6585 def add_function_route (self , route , function ):
66- self .function_routes .append ({"route" :route , "function" :function })
67-
86+ self .function_routes .append ({"route" : route , "function" : function })
6887
6988 async def serve_request (self , reader , writer ):
89+ gc .collect ()
7090 try :
7191 url = ""
7292 method = ""
@@ -76,18 +96,27 @@ async def serve_request(self, reader, writer):
7696 post_data = None
7797 while True :
7898 line = await reader .readline ()
99+ #print("line: "+str(line))
79100 line = line .decode ('utf-8' ).strip ()
80101 if line == "" :
81102 break
82103 headers .append (line )
83- request_raw = str ("\r " .join (headers ))
104+ request_raw = str ("\r \n " .join (headers ))
84105 print (request_raw )
85106 request_pattern = re .compile (r"(GET|POST)\s+([^\s]+)\s+HTTP" )
86107 match = request_pattern .search (request_raw )
87108 if match :
88109 method = match .group (1 )
89110 url = match .group (2 )
90111 print (method , url )
112+ else : # regex didn't match, try splitting the request line
113+ request_parts = request_raw .split (" " )
114+ if len (request_parts ) > 1 :
115+ method = request_parts [0 ]
116+ url = request_parts [1 ]
117+ print (method , url )
118+ else :
119+ print ("no match" )
91120 # extract content length for POST requests
92121 if method == "POST" :
93122 content_length_pattern = re .compile (r"Content-Length:\s+(\d+)" )
@@ -107,16 +136,20 @@ async def serve_request(self, reader, writer):
107136 print ("path_components: " + str (path_components ))
108137 route_function , params = self .match_route (path_components )
109138 if route_function :
110- print ("calling function: " + str (route_function )+ " with params: " + str (params ))
139+ print ("calling function: " + str (route_function ) +
140+ " with params: " + str (params ))
111141 await route_function (request , response , * params )
112142 return
113143 # perhaps it is a file
114- file = self .get_file (self .doc_root + url )
115- print ("file: " + str (file ))
116- if file :
117- print ("file found so serving it" )
118- print (file )
119- await response .send (file )
144+ file_path = self .doc_root + url
145+ if self .log_level > 0 :
146+ print ("file_path: " + str (file_path ))
147+ #if uos.stat(file_path)[6] > 0:
148+ if self .file_exists (file_path ):
149+ content_type = self .get_content_type (url )
150+ if self .log_level > 1 :
151+ print ("content_type: " + str (content_type ))
152+ await response .send_file (file_path , content_type = content_type )
120153 return
121154 print ("file not found" )
122155 await response .send (self .html % "page not found " + url , status_code = 404 )
@@ -125,9 +158,21 @@ async def serve_request(self, reader, writer):
125158 except OSError as e :
126159 print (e )
127160
161+ def dir_exists (self , filename ):
162+ try :
163+ return (os .stat (filename )[0 ] & 0x4000 ) != 0
164+ except OSError :
165+ return False
166+
167+ def file_exists (self , filename ):
168+ try :
169+ return (os .stat (filename )[0 ] & 0x4000 ) == 0
170+ except OSError :
171+ return False
172+
128173 def get_file (self , filename ):
129174 print ("getFile: " + filename )
130- try :
175+ try :
131176 # Check if the file exists
132177 if uos .stat (filename )[6 ] > 0 :
133178 # Open the file in read mode
@@ -141,20 +186,23 @@ def get_file(self, filename):
141186 # print the error
142187 print (e )
143188 return False
144-
189+
145190 def get_path_components (self , path ):
191+ print ("get_path_components: " + path )
146192 return tuple (filter (None , path .split ('/' )))
147-
193+
148194 def match_route (self , path_components ):
149195 for route in self .function_routes :
150196 route_pattern = list (filter (None , route ["route" ].split ("/" )))
151- #print("route_pattern: "+str(route_pattern))
197+ if self .log_level > 1 :
198+ print ("route_pattern: " + str (route_pattern ))
152199 if len (route_pattern ) != len (path_components ):
153200 continue
154201 match = True
155202 params = []
156203 for idx , pattern_component in enumerate (route_pattern ):
157- print ("pattern_component: " + pattern_component + " path_component: " + path_components [idx ])
204+ if self .log_level > 2 :
205+ print ("pattern_component: " + str (pattern_component ))
158206 if pattern_component .startswith ('<' ) and pattern_component .endswith ('>' ):
159207 param_value = path_components [idx ]
160208 params .append (param_value )
@@ -166,5 +214,23 @@ def match_route(self, path_components):
166214 return route ["function" ], params
167215 return None , []
168216
217+ def get_file_extension (self , file_path ):
218+ file_parts = file_path .split ('.' )
219+ if len (file_parts ) > 1 :
220+ return file_parts [- 1 ]
221+ return ''
169222
170223
224+ def get_content_type (self ,file_path ):
225+ extension = self .get_file_extension (file_path )
226+ content_type_map = {
227+ 'html' : 'text/html' ,
228+ 'css' : 'text/css' ,
229+ 'js' : 'application/javascript' ,
230+ 'jpg' : 'image/jpeg' ,
231+ 'jpeg' : 'image/jpeg' ,
232+ 'png' : 'image/png' ,
233+ 'gif' : 'image/gif' ,
234+ 'ico' : 'image/x-icon'
235+ }
236+ return content_type_map .get (extension , 'text/plain' )
0 commit comments