├── LICENSE.md
├── README.md
├── _config.yml
├── hc2.png
├── main.py
├── microWebSocket.py
├── microWebSrv.py
├── microWebSrv2.png
├── microWebTemplate.py
└── www
├── favicon.ico
├── index.html
├── pdf.png
├── style.css
├── test.pdf
├── test.pyhtml
└── wstest.html
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright © 2018 Jean-Christophe Bos & HC² (www.hc2.fr)
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Also check out the new ESP32 MPY-Jama free IDE and the latest MicroWebSrv2 below:
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ---
14 |
15 |
16 |
17 | ### MicroWebSrv is a micro HTTP Web server that supports WebSockets, html/python language templating and routing handlers, for MicroPython (principally used on ESP32 and [Pycom](http://www.pycom.io) modules. Now supports all variants of [Pyboard D-series](https://store.micropython.org/category/pyboard%20D-series) from the makers of Micropython)
18 |
19 | 
20 |
21 | Very easy to integrate and very light with 3 files only :
22 | - `"microWebSrv.py"` - The Web server
23 | - `"microWebSocket.py"` - The optional support of WebSockets
24 | - `"microWebTemplate.py"` - The optional templating language for **.pyhtml** rendered pages
25 |
26 | Mini tuto in video by rdagger68 :
27 | - [https://www.youtube.com/watch?v=xscBwC1SrF4](https://www.youtube.com/watch?v=xscBwC1SrF4)
28 |
29 | Simple but effective :
30 | - Use it to embed a cool Web site in yours modules
31 | - Handle GET, POST, ... requests to interract with user and configure options
32 | - Exchange in JSON format on HTTP methods to make an embedded fullREST API
33 | - Serve files on the fly to export any data to the user
34 | - Use routes and variable route handlers
35 | - Play with AjAX to interact speedly with a Web application
36 | - Make it a captive portal simply
37 | - Use WebSockets for fast and powerful data exchange
38 | - Make html/python files for rendering more efficient web pages
39 |
40 | ### Using *microWebSrv* main class :
41 |
42 | | Name | Function |
43 | | - | - |
44 | | Constructor | `mws = MicroWebSrv(routeHandlers=None, port=80, bindIP='0.0.0.0', webPath="/flash/www")` |
45 | | Start Web server | `mws.Start(threaded=True)` |
46 | | Stop Web server | `mws.Stop()` |
47 | | Check if Web server is running | `mws.IsStarted()` |
48 | | Set URL location for not found page | `mws.SetNotFoundPageUrl(url=None)` |
49 | | Get mime type from file extention | `mws.GetMimeTypeFromFilename(filename)` |
50 | | Get handler function from route | `(routeHandler, routeArgs) = mws.GetRouteHandler(resUrl, method)` |
51 | | Callback function to enable and accept WebSockets | `mws.AcceptWebSocketCallback = _acptWS` `_acptWS(webSocket, httpClient) { }` |
52 | | Maximum length of memory allocated to receive WebSockets data (1024 by default) | `mws.MaxWebSocketRecvLen` |
53 | | New thread used for each WebSocket connection (True by default) | `mws.WebSocketThreaded` |
54 | | Escape string to HTML usage | `MicroWebSrv.HTMLEscape(s)` |
55 |
56 | ### Basic example :
57 | ```python
58 | from microWebSrv import MicroWebSrv
59 | mws = MicroWebSrv() # TCP port 80 and files in /flash/www
60 | mws.Start(threaded=True) # Starts server in a new thread
61 | ```
62 |
63 | ### Using as captive portal :
64 | ```python
65 | # To intercept all not found queries and redirect it,
66 | mws.SetNotFoundPageUrl("http://my-device.wifi")
67 | ```
68 | - Can be used with [MicroDNSSrv](http://microdnssrv.hc2.fr) easily.
69 |
70 | ### Using route handlers example :
71 | ```python
72 |
73 | routeHandlers = [
74 | ( "relative_url_route_1", "METHOD", handlerFunc_1 ),
75 | ( "relative_url_route_2", "METHOD", handlerFunc_2 ),
76 | ( ... )
77 | ]
78 | ```
79 | ```python
80 | def handlerFunc_1(httpClient, httpResponse, routeArgs=None) :
81 | print("In HTTP handler 1")
82 |
83 | def handlerFunc_2(httpClient, httpResponse, routeArgs=None) :
84 | print("In HTTP handler 2")
85 | ```
86 |
87 | ### Using directly route handlers decorators example :
88 | ```python
89 | @MicroWebSrv.route('/get-test')
90 | def handlerFuncGet(httpClient, httpResponse) :
91 | print("In GET-TEST HTTP")
92 |
93 | @MicroWebSrv.route('/post-test', 'POST')
94 | def handlerFuncPost(httpClient, httpResponse) :
95 | print("In POST-TEST HTTP")
96 | ```
97 |
98 | ### Using variable routes example :
99 | ```python
100 | routeHandlers = [
101 | ( "/edit//", "GET", handlerFuncEdit ),
102 | ( ... )
103 | ]
104 | def handlerFuncEdit(httpClient, httpResponse, routeArgs) :
105 | print("In EDIT HTTP variable route :")
106 | print(" - testid = %s" % routeArgs['testid'])
107 | print(" - testpath = %s" % routeArgs['testpath'])
108 | ```
109 |
110 | Or direclty with route handler decorator :
111 |
112 | ```python
113 | @MicroWebSrv.route('/edit//')
114 | def handlerFuncEdit(httpClient, httpResponse, routeArgs) :
115 | print("In EDIT HTTP variable route :")
116 | print(" - testid = %s" % routeArgs['testid'])
117 | print(" - testpath = %s" % routeArgs['testpath'])
118 | ```
119 |
120 | ### Using *httpClient* class in a route handler function :
121 |
122 | | Name | Function |
123 | | - | - |
124 | | Get MicroWebSrv class | `httpClient.GetServer()` |
125 | | Get client address as tuple | `httpClient.GetAddr()` |
126 | | Get client IP address | `httpClient.GetIPAddr()` |
127 | | Get client TCP port | `httpClient.GetPort()` |
128 | | Get client request method | `httpClient.GetRequestMethod()` |
129 | | Get client request total path | `httpClient.GetRequestTotalPath()` |
130 | | Get client request ressource path | `httpClient.GetRequestPath()` |
131 | | Get client request query string | `httpClient.GetRequestQueryString()` |
132 | | Get client request query parameters as list | `httpClient.GetRequestQueryParams()` |
133 | | Get client request headers as list | `httpClient.GetRequestHeaders()` |
134 | | Get client request content type | `httpClient.GetRequestContentType()` |
135 | | Get client request content length | `httpClient.GetRequestContentLength()` |
136 | | Get client request content | `httpClient.ReadRequestContent(size=None)` |
137 | | Get client request form data as list | `httpClient.ReadRequestPostedFormData()` |
138 | | Get client request as JSON object | `httpClient.ReadRequestContentAsJSON()` |
139 |
140 | ### Using *httpResponse* class in a route handler function :
141 |
142 | | Name | Function |
143 | | - | - |
144 | | Write switching protocols response | `httpResponse.WriteSwitchProto(upgrade, headers=None)` |
145 | | Write generic response | `httpResponse.WriteResponse(code, headers, contentType, contentCharset, content)` |
146 | | Write PyHTML rendered response page | `httpResponse.WriteResponsePyHTMLFile(filepath, headers=None, vars=None)` |
147 | | Write file directly as response | `httpResponse.WriteResponseFile(filepath, contentType=None, headers=None)` |
148 | | Write attached file as response | `httpResponse.WriteResponseFileAttachment(filepath, attachmentName, headers=None)` |
149 | | Write OK response | `httpResponse.WriteResponseOk(headers=None, contentType=None, contentCharset=None, content=None)` |
150 | | Write JSON object as OK response | `httpResponse.WriteResponseJSONOk(obj=None, headers=None)` |
151 | | Write redirect response | `httpResponse.WriteResponseRedirect(location)` |
152 | | Write error response | `httpResponse.WriteResponseError(code)` |
153 | | Write JSON object as error response | `httpResponse.WriteResponseJSONError(code, obj=None)` |
154 | | Write bad request response | `httpResponse.WriteResponseBadRequest()` |
155 | | Write forbidden response | `httpResponse.WriteResponseForbidden()` |
156 | | Write not found response | `httpResponse.WriteResponseNotFound()` |
157 | | Write method not allowed response | `httpResponse.WriteResponseMethodNotAllowed()` |
158 | | Write internal server error response | `httpResponse.WriteResponseInternalServerError()` |
159 | | Write not implemented response | `httpResponse.WriteResponseNotImplemented()` |
160 |
161 | ### Using route handler function example :
162 |
163 | ```python
164 | def _httpHandlerTestPost(httpClient, httpResponse) :
165 | formData = httpClient.ReadRequestPostedFormData()
166 | firstname = formData["firstname"]
167 | lastname = formData["lastname"]
168 | content = """\
169 |
170 |
171 |
172 |
173 | TEST POST
174 |
175 |
176 | TEST POST
177 | Firstname = %s
178 | Lastname = %s
179 |
180 |
181 | """ % ( MicroWebSrv.HTMLEscape(firstname),
182 | MicroWebSrv.HTMLEscape(lastname) )
183 | httpResponse.WriteResponseOk( headers = None,
184 | contentType = "text/html",
185 | contentCharset = "UTF-8",
186 | content = content )
187 | ```
188 |
189 | ### Known mime types (content types) :
190 |
191 | | File extension | Mime type |
192 | | - | - |
193 | | .txt | text/plain |
194 | | .htm | text/html |
195 | | .html | text/html |
196 | | .css | text/css |
197 | | .csv | text/csv |
198 | | .js | application/javascript |
199 | | .xml | application/xml |
200 | | .xhtml | application/xhtml+xml |
201 | | .json | application/json |
202 | | .zip | application/zip |
203 | | .pdf | application/pdf |
204 | | .ts | application/typescript |
205 | | .woff | font/woff |
206 | | .woff2 | font/woff2 |
207 | | .ttf | font/ttf |
208 | | .otf | font/otf |
209 | | .jpg | image/jpeg |
210 | | .jpeg | image/jpeg |
211 | | .png | image/png |
212 | | .gif | image/gif |
213 | | .svg | image/svg+xml |
214 | | .ico | image/x-icon |
215 |
216 | ### Default index pages order (for http://hostname/) :
217 |
218 | | Filename |
219 | | - |
220 | | index.pyhtml |
221 | | index.html |
222 | | index.htm |
223 | | default.pyhtml |
224 | | default.html |
225 | | default.htm |
226 |
227 | ### Using optional module *microWebSocket* to connect WebSockets :
228 |
229 | - File `"microWebSocket.py"` must be present to activate WebSockets support
230 |
231 | ### Enable and accept WebSockets :
232 | ```python
233 | from microWebSrv import MicroWebSrv
234 | mws = MicroWebSrv() # TCP port 80 and files in /flash/www
235 | mws.MaxWebSocketRecvLen = 256 # Default is set to 1024
236 | mws.WebSocketThreaded = False # WebSockets without new threads
237 | mws.AcceptWebSocketCallback = _acceptWebSocketCallback # Function to receive WebSockets
238 | mws.Start(threaded=True) # Starts server in a new thread
239 | ```
240 |
241 | | Name | Function |
242 | | - | - |
243 | | Callback function to receive text message | `ws.RecvTextCallback = func(webSocket, msg)` |
244 | | Callback function to receive binary data | `ws.RecvBinaryCallback = func(webSocket, data)` |
245 | | Callback function when connection was closed | `ws.ClosedCallback = func(webSocket)` |
246 | | Send a text message | `ws.SendText(msg)` |
247 | | Send a binary message | `ws.SendBinary(data)` |
248 | | Check connection state | `ws.IsClosed()` |
249 | | Close the connection | `ws.Close()` |
250 |
251 | ### Basic example of callback functions :
252 | ```python
253 | def _acceptWebSocketCallback(webSocket, httpClient) :
254 | print("WS ACCEPT")
255 | webSocket.RecvTextCallback = _recvTextCallback
256 | webSocket.RecvBinaryCallback = _recvBinaryCallback
257 | webSocket.ClosedCallback = _closedCallback
258 |
259 | def _recvTextCallback(webSocket, msg) :
260 | print("WS RECV TEXT : %s" % msg)
261 | webSocket.SendText("Reply for %s" % msg)
262 |
263 | def _recvBinaryCallback(webSocket, data) :
264 | print("WS RECV DATA : %s" % data)
265 |
266 | def _closedCallback(webSocket) :
267 | print("WS CLOSED")
268 | ```
269 |
270 | ### Using optional module *microWebTemplate* for *.pyhtml* rendered pages :
271 |
272 | - File `"microWebTemplate.py"` must be present to activate **.pyhtml** pages
273 | - Pages will be rendered in HTML with integrated MicroPython code
274 |
275 | | Instruction | Schema |
276 | | - | - |
277 | | PY | `{{ py }}` *MicroPython code* `{{ end }}` |
278 | | IF | `{{ if` *MicroPython condition* `}}` *html bloc* `{{ end }}` |
279 | | ELIF | `{{ elif` *MicroPython condition* `}}` *html bloc* `{{ end }}` |
280 | | ELSE | `{{ else }}` *html bloc* `{{ end }}` |
281 | | FOR | `{{ for` *identifier* `in` *MicroPython iterator* `}}` *html bloc* `{{ end }}` |
282 | | INCLUDE | `{{ include` *pyhtml_filename* `}}` |
283 | | ? | `{{` *MicroPython expression* `}}` |
284 |
285 |
286 | ### Using {{ py }} :
287 |
288 | ```python
289 | {{ py }}
290 | import machine
291 | from utime import sleep
292 | test = 123
293 | def testFunc(x) :
294 | return 2 * x
295 | {{ end }}
296 | ```
297 |
298 | ### Using {{ if ... }} :
299 |
300 | ```python
301 | {{ if testFunc(5) <= 3 }}
302 | titi
303 | {{ elif testFunc(10) >= 15 }}
304 | tata
305 | {{ else }}
306 | I like the number {{ test }} !
307 | {{ end }}
308 | ```
309 |
310 | ### Using {{ for ... }} :
311 |
312 | ```python
313 | {{ for toto in range(testFunc(3)) }}
314 | toto x 10 equal {{ toto * 10 }}
315 |
316 | {{ end }}
317 | ```
318 |
319 | ### Using {{ include ... }} :
320 |
321 | ```python
322 | {{ include myTemplate.pyhtml }}
323 | ```
324 |
325 | ### Example of a .pyhtml file :
326 |
327 | ```html
328 |
329 |
330 | TEST PYHTML
331 |
332 |
333 | BEGIN
334 | {{ py }}
335 | def _testFunction(x) :
336 | return "IN TEST FUNCTION %s" % x
337 | {{ end }}
338 |
339 | {{ for toto in range(3) }}
340 | This is an HTML test...
341 | TOTO = {{ toto + 1 }} !
342 | {{ for toto2 in range(3) }}
343 | TOTO2 = {{ _testFunction(toto2) }}
344 | {{ end }}
345 | Ok good.
346 | {{ end }}
347 |
348 | {{ _testFunction(100) }}
349 |
350 | {{ if 2+5 < 3 }}
351 | IN IF (1)
352 | {{ elif 10+15 != 25 }}
353 | IN ELIF (2)
354 | {{ elif 10+15 == 25 }}
355 | IN ELIF (3)
356 | {{ else }}
357 | IN ELSE (4)
358 | {{ end }}
359 |
360 |
361 | ```
362 |
363 |
364 | ## :wink: Author
365 |
366 | **Jean-Christophe Bos** (:fr:)
367 | - GitHub: *[@jczic](https://github.com/jczic)*
368 | - Email: **
369 | - Profil: *[LinkedIn](https://www.linkedin.com/in/jczic)*
370 | - Music: *[SoundCloud](https://soundcloud.com/jczic/sets/electro-pulse)*
371 | *[Spotify](https://open.spotify.com/album/5fUd57GcAIcdUn9NX3fviG)*
372 | *[YouTube](https://www.youtube.com/playlist?list=PL9CsGuMbcLaU02VKS7jtR6LaDNpq7MZEq)*
373 |
374 |
375 | ### By JC`zic for [HC²](https://www.hc2.fr) ;')
376 |
377 | *Keep it simple, stupid* :+1:
378 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | show_downloads: true
2 | theme: jekyll-theme-midnight
--------------------------------------------------------------------------------
/hc2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jczic/MicroWebSrv/9909b830792d5d06cd1c34dce50985b3291eb0da/hc2.png
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 |
2 | from microWebSrv import MicroWebSrv
3 |
4 | # ----------------------------------------------------------------------------
5 |
6 | @MicroWebSrv.route('/test')
7 | def _httpHandlerTestGet(httpClient, httpResponse) :
8 | content = """\
9 |
10 |
11 |
12 |
13 | TEST GET
14 |
15 |
16 | TEST GET
17 | Client IP address = %s
18 |
19 |
24 |
25 |
26 | """ % httpClient.GetIPAddr()
27 | httpResponse.WriteResponseOk( headers = None,
28 | contentType = "text/html",
29 | contentCharset = "UTF-8",
30 | content = content )
31 |
32 |
33 | @MicroWebSrv.route('/test', 'POST')
34 | def _httpHandlerTestPost(httpClient, httpResponse) :
35 | formData = httpClient.ReadRequestPostedFormData()
36 | firstname = formData["firstname"]
37 | lastname = formData["lastname"]
38 | content = """\
39 |
40 |
41 |
42 |
43 | TEST POST
44 |
45 |
46 | TEST POST
47 | Firstname = %s
48 | Lastname = %s
49 |
50 |
51 | """ % ( MicroWebSrv.HTMLEscape(firstname),
52 | MicroWebSrv.HTMLEscape(lastname) )
53 | httpResponse.WriteResponseOk( headers = None,
54 | contentType = "text/html",
55 | contentCharset = "UTF-8",
56 | content = content )
57 |
58 |
59 | @MicroWebSrv.route('/edit/') # /edit/123 -> args['index']=123
60 | @MicroWebSrv.route('/edit//abc/') # /edit/123/abc/bar -> args['index']=123 args['foo']='bar'
61 | @MicroWebSrv.route('/edit') # /edit -> args={}
62 | def _httpHandlerEditWithArgs(httpClient, httpResponse, args={}) :
63 | content = """\
64 |
65 |
66 |
67 |
68 | TEST EDIT
69 |
70 |
71 | """
72 | content += "EDIT item with {} variable arguments "\
73 | .format(len(args))
74 |
75 | if 'index' in args :
76 | content += "index = {}
".format(args['index'])
77 |
78 | if 'foo' in args :
79 | content += "foo = {}
".format(args['foo'])
80 |
81 | content += """
82 |
83 |
84 | """
85 | httpResponse.WriteResponseOk( headers = None,
86 | contentType = "text/html",
87 | contentCharset = "UTF-8",
88 | content = content )
89 |
90 | # ----------------------------------------------------------------------------
91 |
92 | def _acceptWebSocketCallback(webSocket, httpClient) :
93 | print("WS ACCEPT")
94 | webSocket.RecvTextCallback = _recvTextCallback
95 | webSocket.RecvBinaryCallback = _recvBinaryCallback
96 | webSocket.ClosedCallback = _closedCallback
97 |
98 | def _recvTextCallback(webSocket, msg) :
99 | print("WS RECV TEXT : %s" % msg)
100 | webSocket.SendText("Reply for %s" % msg)
101 |
102 | def _recvBinaryCallback(webSocket, data) :
103 | print("WS RECV DATA : %s" % data)
104 |
105 | def _closedCallback(webSocket) :
106 | print("WS CLOSED")
107 |
108 | # ----------------------------------------------------------------------------
109 |
110 | #routeHandlers = [
111 | # ( "/test", "GET", _httpHandlerTestGet ),
112 | # ( "/test", "POST", _httpHandlerTestPost )
113 | #]
114 |
115 | srv = MicroWebSrv(webPath='www/')
116 | srv.MaxWebSocketRecvLen = 256
117 | srv.WebSocketThreaded = False
118 | srv.AcceptWebSocketCallback = _acceptWebSocketCallback
119 | srv.Start()
120 |
121 | # ----------------------------------------------------------------------------
122 |
--------------------------------------------------------------------------------
/microWebSocket.py:
--------------------------------------------------------------------------------
1 | """
2 | The MIT License (MIT)
3 | Copyright © 2018 Jean-Christophe Bos & HC² (www.hc2.fr)
4 | """
5 |
6 | from hashlib import sha1
7 | from binascii import b2a_base64
8 | from struct import pack
9 | from _thread import start_new_thread, allocate_lock
10 | import gc
11 |
12 | class MicroWebSocket :
13 |
14 | # ============================================================================
15 | # ===( Constants )============================================================
16 | # ============================================================================
17 |
18 | _handshakeSign = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
19 |
20 | _opContFrame = 0x0
21 | _opTextFrame = 0x1
22 | _opBinFrame = 0x2
23 | _opCloseFrame = 0x8
24 | _opPingFrame = 0x9
25 | _opPongFrame = 0xA
26 |
27 | _msgTypeText = 1
28 | _msgTypeBin = 2
29 |
30 | # ============================================================================
31 | # ===( Utils )===============================================================
32 | # ============================================================================
33 |
34 | @staticmethod
35 | def _tryAllocByteArray(size) :
36 | for x in range(10) :
37 | try :
38 | gc.collect()
39 | return bytearray(size)
40 | except :
41 | pass
42 | return None
43 |
44 | # ----------------------------------------------------------------------------
45 |
46 | @staticmethod
47 | def _tryStartThread(func, args=()) :
48 | for x in range(10) :
49 | try :
50 | gc.collect()
51 | start_new_thread(func, args)
52 | return True
53 | except :
54 | global _mws_thread_id
55 | try :
56 | _mws_thread_id += 1
57 | except :
58 | _mws_thread_id = 0
59 | try :
60 | start_new_thread('MWS_THREAD_%s' % _mws_thread_id, func, args)
61 | return True
62 | except :
63 | pass
64 | return False
65 |
66 | # ============================================================================
67 | # ===( Constructor )==========================================================
68 | # ============================================================================
69 |
70 | def __init__(self, socket, httpClient, httpResponse, maxRecvLen, threaded, acceptCallback) :
71 | self._socket = socket
72 | self._httpCli = httpClient
73 | self._closed = True
74 | self._lock = allocate_lock()
75 | self.RecvTextCallback = None
76 | self.RecvBinaryCallback = None
77 | self.ClosedCallback = None
78 |
79 | if hasattr(socket, 'read'): # MicroPython
80 | self._socketfile = self._socket
81 | else: # CPython
82 | self._socketfile = self._socket.makefile('rwb')
83 |
84 | if self._handshake(httpResponse) :
85 | self._ctrlBuf = MicroWebSocket._tryAllocByteArray(0x7D)
86 | self._msgBuf = MicroWebSocket._tryAllocByteArray(maxRecvLen)
87 | if self._ctrlBuf and self._msgBuf :
88 | self._msgType = None
89 | self._msgLen = 0
90 | if threaded :
91 | if MicroWebSocket._tryStartThread(self._wsProcess, (acceptCallback, )) :
92 | return
93 | else :
94 | self._wsProcess(acceptCallback)
95 | return
96 | print("MicroWebSocket : Out of memory on new WebSocket connection.")
97 | try :
98 | if self._socketfile is not self._socket:
99 | self._socketfile.close()
100 | self._socket.close()
101 | except :
102 | pass
103 |
104 | # ============================================================================
105 | # ===( Functions )============================================================
106 | # ============================================================================
107 |
108 | def _handshake(self, httpResponse) :
109 | try :
110 | key = self._httpCli.GetRequestHeaders().get('sec-websocket-key', None)
111 | if key :
112 | key += self._handshakeSign
113 | r = sha1(key.encode()).digest()
114 | r = b2a_base64(r).decode().strip()
115 | httpResponse.WriteSwitchProto("websocket", { "Sec-WebSocket-Accept" : r })
116 | return True
117 | except :
118 | pass
119 | return False
120 |
121 | # ----------------------------------------------------------------------------
122 |
123 | def _wsProcess(self, acceptCallback) :
124 | self._socket.settimeout(3600)
125 | self._closed = False
126 | try :
127 | acceptCallback(self, self._httpCli)
128 | except Exception as ex :
129 | print("MicroWebSocket : Error on accept callback (%s)." % str(ex))
130 | while not self._closed :
131 | if not self._receiveFrame() :
132 | self.Close()
133 | if self.ClosedCallback :
134 | try :
135 | self.ClosedCallback(self)
136 | except Exception as ex :
137 | print("MicroWebSocket : Error on closed callback (%s)." % str(ex))
138 |
139 | # ----------------------------------------------------------------------------
140 |
141 | def _receiveFrame(self) :
142 | try :
143 | b = self._socketfile.read(2)
144 | if not b or len(b) != 2 :
145 | return False
146 |
147 | fin = b[0] & 0x80 > 0
148 | opcode = b[0] & 0x0F
149 | masked = b[1] & 0x80 > 0
150 | length = b[1] & 0x7F
151 |
152 | if opcode == self._opContFrame and not self._msgType :
153 | return False
154 | elif opcode == self._opTextFrame :
155 | self._msgType = self._msgTypeText
156 | elif opcode == self._opBinFrame :
157 | self._msgType = self._msgTypeBin
158 |
159 | if length == 0x7E :
160 | b = self._socketfile.read(2)
161 | if not b or len(b) != 2 :
162 | return False
163 | length = (b[0] << 8) + b[1]
164 | elif length == 0x7F :
165 | return False
166 |
167 | mask = self._socketfile.read(4) if masked else None
168 | if masked and (not mask or len(mask) != 4) :
169 | return False
170 |
171 | if opcode == self._opContFrame or \
172 | opcode == self._opTextFrame or \
173 | opcode == self._opBinFrame :
174 |
175 | if length > 0 :
176 | buf = memoryview(self._msgBuf)[self._msgLen:]
177 | if length > len(buf) :
178 | return False
179 | x = self._socketfile.readinto(buf[0:length])
180 | if x != length :
181 | return False
182 | if masked :
183 | for i in range(length) :
184 | idx = self._msgLen + i
185 | self._msgBuf[idx] ^= mask[i%4]
186 | self._msgLen += length
187 | if fin :
188 | b = bytes(memoryview(self._msgBuf)[:self._msgLen])
189 | if self._msgType == self._msgTypeText :
190 | if self.RecvTextCallback :
191 | try :
192 | self.RecvTextCallback(self, b.decode())
193 | except Exception as ex :
194 | print("MicroWebSocket : Error on recv text callback (%s)." % str(ex))
195 | else :
196 | if self.RecvBinaryCallback :
197 | try :
198 | self.RecvBinaryCallback(self, b)
199 | except Exception as ex :
200 | print("MicroWebSocket : Error on recv binary callback (%s)." % str(ex))
201 | self._msgType = None
202 | self._msgLen = 0
203 | else :
204 | return False
205 |
206 | elif opcode == self._opPingFrame :
207 |
208 | if length > len(self._ctrlBuf) :
209 | return False
210 | if length > 0 :
211 | x = self._socketfile.readinto(self._ctrlBuf[0:length])
212 | if x != length :
213 | return False
214 | pingData = memoryview(self._ctrlBuf)[:length]
215 | else :
216 | pingData = None
217 | self._sendFrame(self._opPongFrame, pingData)
218 |
219 | elif opcode == self._opCloseFrame :
220 | self.Close()
221 |
222 | except :
223 | return False
224 |
225 | return True
226 |
227 | # ----------------------------------------------------------------------------
228 |
229 | def _sendFrame(self, opcode, data=None, fin=True) :
230 | if not self._closed and opcode >= 0x00 and opcode <= 0x0F :
231 | dataLen = 0 if not data else len(data)
232 | if dataLen <= 0xFFFF :
233 | b1 = (0x80 | opcode) if fin else opcode
234 | b2 = 0x7E if dataLen >= 0x7E else dataLen
235 | self._lock.acquire()
236 | try :
237 | if self._socketfile.write(pack('>BB', b1, b2)) == 2 :
238 | if dataLen > 0 :
239 | if dataLen >= 0x7E :
240 | self._socketfile.write(pack('>H', dataLen))
241 | ret = self._socketfile.write(data) == dataLen
242 | else :
243 | ret = True
244 | if self._socketfile is not self._socket :
245 | self._socketfile.flush() # CPython needs flush to continue protocol
246 | self._lock.release()
247 | return ret
248 | except :
249 | pass
250 | self._lock.release()
251 | return False
252 |
253 | # ----------------------------------------------------------------------------
254 |
255 | def SendText(self, msg) :
256 | return self._sendFrame(self._opTextFrame, msg.encode())
257 |
258 | # ----------------------------------------------------------------------------
259 |
260 | def SendBinary(self, data) :
261 | return self._sendFrame(self._opBinFrame, data)
262 |
263 | # ----------------------------------------------------------------------------
264 |
265 | def IsClosed(self) :
266 | return self._closed
267 |
268 | # ----------------------------------------------------------------------------
269 |
270 | def Close(self) :
271 | if not self._closed :
272 | try :
273 | self._sendFrame(self._opCloseFrame)
274 | if self._socketfile is not self._socket:
275 | self._socketfile.close()
276 | self._socket.close()
277 | self._closed = True
278 | except :
279 | pass
280 |
281 | # ============================================================================
282 | # ============================================================================
283 | # ============================================================================
284 |
285 |
--------------------------------------------------------------------------------
/microWebSrv.py:
--------------------------------------------------------------------------------
1 | """
2 | The MIT License (MIT)
3 | Copyright © 2018 Jean-Christophe Bos & HC² (www.hc2.fr)
4 | """
5 |
6 |
7 | from json import loads, dumps
8 | from os import stat
9 | from _thread import start_new_thread
10 | import socket
11 | import gc
12 | import re
13 |
14 | try :
15 | from microWebTemplate import MicroWebTemplate
16 | except :
17 | pass
18 |
19 | try :
20 | from microWebSocket import MicroWebSocket
21 | except :
22 | pass
23 |
24 | class MicroWebSrvRoute :
25 | def __init__(self, route, method, func, routeArgNames, routeRegex) :
26 | self.route = route
27 | self.method = method
28 | self.func = func
29 | self.routeArgNames = routeArgNames
30 | self.routeRegex = routeRegex
31 |
32 |
33 | class MicroWebSrv :
34 |
35 | # ============================================================================
36 | # ===( Constants )============================================================
37 | # ============================================================================
38 |
39 | _indexPages = [
40 | "index.pyhtml",
41 | "index.html",
42 | "index.htm",
43 | "default.pyhtml",
44 | "default.html",
45 | "default.htm"
46 | ]
47 |
48 | _mimeTypes = {
49 | ".txt" : "text/plain",
50 | ".htm" : "text/html",
51 | ".html" : "text/html",
52 | ".css" : "text/css",
53 | ".csv" : "text/csv",
54 | ".js" : "application/javascript",
55 | ".xml" : "application/xml",
56 | ".xhtml" : "application/xhtml+xml",
57 | ".json" : "application/json",
58 | ".zip" : "application/zip",
59 | ".pdf" : "application/pdf",
60 | ".ts" : "application/typescript",
61 | ".woff" : "font/woff",
62 | ".woff2" : "font/woff2",
63 | ".ttf" : "font/ttf",
64 | ".otf" : "font/otf",
65 | ".jpg" : "image/jpeg",
66 | ".jpeg" : "image/jpeg",
67 | ".png" : "image/png",
68 | ".gif" : "image/gif",
69 | ".svg" : "image/svg+xml",
70 | ".ico" : "image/x-icon"
71 | }
72 |
73 | _html_escape_chars = {
74 | "&" : "&",
75 | '"' : """,
76 | "'" : "'",
77 | ">" : ">",
78 | "<" : "<"
79 | }
80 |
81 | _pyhtmlPagesExt = '.pyhtml'
82 |
83 | # ============================================================================
84 | # ===( Class globals )=======================================================
85 | # ============================================================================
86 |
87 | _docoratedRouteHandlers = []
88 |
89 | # ============================================================================
90 | # ===( Utils )===============================================================
91 | # ============================================================================
92 |
93 | @classmethod
94 | def route(cls, url, method='GET'):
95 | """ Adds a route handler function to the routing list """
96 | def route_decorator(func):
97 | item = (url, method, func)
98 | cls._docoratedRouteHandlers.append(item)
99 | return func
100 | return route_decorator
101 |
102 | # ----------------------------------------------------------------------------
103 |
104 | @staticmethod
105 | def HTMLEscape(s) :
106 | return ''.join(MicroWebSrv._html_escape_chars.get(c, c) for c in s)
107 |
108 | # ----------------------------------------------------------------------------
109 |
110 | @staticmethod
111 | def _startThread(func, args=()) :
112 | try :
113 | start_new_thread(func, args)
114 | except :
115 | global _mwsrv_thread_id
116 | try :
117 | _mwsrv_thread_id += 1
118 | except :
119 | _mwsrv_thread_id = 0
120 | try :
121 | start_new_thread('MWSRV_THREAD_%s' % _mwsrv_thread_id, func, args)
122 | except :
123 | return False
124 | return True
125 |
126 | # ----------------------------------------------------------------------------
127 |
128 | @staticmethod
129 | def _unquote(s) :
130 | r = str(s).split('%')
131 | try :
132 | b = r[0].encode()
133 | for i in range(1, len(r)) :
134 | try :
135 | b += bytes([int(r[i][:2], 16)]) + r[i][2:].encode()
136 | except :
137 | b += b'%' + r[i].encode()
138 | return b.decode('UTF-8')
139 | except :
140 | return str(s)
141 |
142 | # ------------------------------------------------------------------------------
143 |
144 | @staticmethod
145 | def _unquote_plus(s) :
146 | return MicroWebSrv._unquote(s.replace('+', ' '))
147 |
148 | # ------------------------------------------------------------------------------
149 |
150 | @staticmethod
151 | def _fileExists(path) :
152 | try :
153 | stat(path)
154 | return True
155 | except :
156 | return False
157 |
158 | # ----------------------------------------------------------------------------
159 |
160 | @staticmethod
161 | def _isPyHTMLFile(filename) :
162 | return filename.lower().endswith(MicroWebSrv._pyhtmlPagesExt)
163 |
164 | # ============================================================================
165 | # ===( Constructor )==========================================================
166 | # ============================================================================
167 |
168 | def __init__( self,
169 | routeHandlers = [],
170 | port = 80,
171 | bindIP = '0.0.0.0',
172 | webPath = "/flash/www" ) :
173 |
174 | self._srvAddr = (bindIP, port)
175 | self._webPath = webPath
176 | self._notFoundUrl = None
177 | self._started = False
178 |
179 | self.MaxWebSocketRecvLen = 1024
180 | self.WebSocketThreaded = True
181 | self.AcceptWebSocketCallback = None
182 | self.LetCacheStaticContentLevel = 2
183 |
184 | self._routeHandlers = []
185 | routeHandlers += self._docoratedRouteHandlers
186 | for route, method, func in routeHandlers :
187 | routeParts = route.split('/')
188 | # -> ['', 'users', '', 'addresses', '', 'test', '']
189 | routeArgNames = []
190 | routeRegex = ''
191 | for s in routeParts :
192 | if s.startswith('<') and s.endswith('>') :
193 | routeArgNames.append(s[1:-1])
194 | routeRegex += '/(\\w*)'
195 | elif s :
196 | routeRegex += '/' + s
197 | routeRegex += '$'
198 | # -> '/users/(\w*)/addresses/(\w*)/test/(\w*)$'
199 | routeRegex = re.compile(routeRegex)
200 |
201 | self._routeHandlers.append(MicroWebSrvRoute(route, method, func, routeArgNames, routeRegex))
202 |
203 | # ============================================================================
204 | # ===( Server Process )=======================================================
205 | # ============================================================================
206 |
207 | def _serverProcess(self) :
208 | self._started = True
209 | while True :
210 | try :
211 | client, cliAddr = self._server.accept()
212 | except Exception as ex :
213 | if ex.args and ex.args[0] == 113 :
214 | break
215 | continue
216 | self._client(self, client, cliAddr)
217 | self._started = False
218 |
219 | # ============================================================================
220 | # ===( Functions )============================================================
221 | # ============================================================================
222 |
223 | def Start(self, threaded=False) :
224 | if not self._started :
225 | self._server = socket.socket()
226 | self._server.setsockopt( socket.SOL_SOCKET,
227 | socket.SO_REUSEADDR,
228 | 1 )
229 | self._server.bind(self._srvAddr)
230 | self._server.listen(16)
231 | if threaded :
232 | MicroWebSrv._startThread(self._serverProcess)
233 | else :
234 | self._serverProcess()
235 |
236 | # ----------------------------------------------------------------------------
237 |
238 | def Stop(self) :
239 | if self._started :
240 | self._server.close()
241 |
242 | # ----------------------------------------------------------------------------
243 |
244 | def IsStarted(self) :
245 | return self._started
246 |
247 | # ----------------------------------------------------------------------------
248 |
249 | def SetNotFoundPageUrl(self, url=None) :
250 | self._notFoundUrl = url
251 |
252 | # ----------------------------------------------------------------------------
253 |
254 | def GetMimeTypeFromFilename(self, filename) :
255 | filename = filename.lower()
256 | for ext in self._mimeTypes :
257 | if filename.endswith(ext) :
258 | return self._mimeTypes[ext]
259 | return None
260 |
261 | # ----------------------------------------------------------------------------
262 |
263 | def GetRouteHandler(self, resUrl, method) :
264 | if self._routeHandlers :
265 | #resUrl = resUrl.upper()
266 | if resUrl.endswith('/') :
267 | resUrl = resUrl[:-1]
268 | method = method.upper()
269 | for rh in self._routeHandlers :
270 | if rh.method == method :
271 | m = rh.routeRegex.match(resUrl)
272 | if m : # found matching route?
273 | if rh.routeArgNames :
274 | routeArgs = {}
275 | for i, name in enumerate(rh.routeArgNames) :
276 | value = m.group(i+1)
277 | try :
278 | value = int(value)
279 | except :
280 | pass
281 | routeArgs[name] = value
282 | return (rh.func, routeArgs)
283 | else :
284 | return (rh.func, None)
285 | return (None, None)
286 |
287 | # ----------------------------------------------------------------------------
288 |
289 | def _physPathFromURLPath(self, urlPath) :
290 | if urlPath == '/' :
291 | for idxPage in self._indexPages :
292 | physPath = self._webPath + '/' + idxPage
293 | if MicroWebSrv._fileExists(physPath) :
294 | return physPath
295 | else :
296 | physPath = self._webPath + urlPath.replace('../', '/')
297 | if MicroWebSrv._fileExists(physPath) :
298 | return physPath
299 | return None
300 |
301 | # ============================================================================
302 | # ===( Class Client )========================================================
303 | # ============================================================================
304 |
305 | class _client :
306 |
307 | # ------------------------------------------------------------------------
308 |
309 | def __init__(self, microWebSrv, socket, addr) :
310 | socket.settimeout(2)
311 | self._microWebSrv = microWebSrv
312 | self._socket = socket
313 | self._addr = addr
314 | self._method = None
315 | self._path = None
316 | self._httpVer = None
317 | self._resPath = "/"
318 | self._queryString = ""
319 | self._queryParams = { }
320 | self._headers = { }
321 | self._contentType = None
322 | self._contentLength = 0
323 |
324 | if hasattr(socket, 'readline'): # MicroPython
325 | self._socketfile = self._socket
326 | else: # CPython
327 | self._socketfile = self._socket.makefile('rwb')
328 |
329 | self._processRequest()
330 |
331 | # ------------------------------------------------------------------------
332 |
333 | def _processRequest(self) :
334 | try :
335 | response = MicroWebSrv._response(self)
336 | if self._parseFirstLine(response) :
337 | if self._parseHeader(response) :
338 | upg = self._getConnUpgrade()
339 | if not upg :
340 | routeHandler, routeArgs = self._microWebSrv.GetRouteHandler(self._resPath, self._method)
341 | if routeHandler :
342 | try :
343 | if routeArgs is not None:
344 | routeHandler(self, response, routeArgs)
345 | else :
346 | routeHandler(self, response)
347 | except Exception as ex :
348 | print('MicroWebSrv handler exception:\r\n - In route %s %s\r\n - %s' % (self._method, self._resPath, ex))
349 | raise ex
350 | elif self._method.upper() == "GET" :
351 | filepath = self._microWebSrv._physPathFromURLPath(self._resPath)
352 | if filepath :
353 | if MicroWebSrv._isPyHTMLFile(filepath) :
354 | response.WriteResponsePyHTMLFile(filepath)
355 | else :
356 | contentType = self._microWebSrv.GetMimeTypeFromFilename(filepath)
357 | if contentType :
358 | if self._microWebSrv.LetCacheStaticContentLevel > 0 :
359 | if self._microWebSrv.LetCacheStaticContentLevel > 1 and \
360 | 'if-modified-since' in self._headers :
361 | response.WriteResponseNotModified()
362 | else:
363 | headers = { 'Last-Modified' : 'Fri, 1 Jan 2018 23:42:00 GMT', \
364 | 'Cache-Control' : 'max-age=315360000' }
365 | response.WriteResponseFile(filepath, contentType, headers)
366 | else :
367 | response.WriteResponseFile(filepath, contentType)
368 | else :
369 | response.WriteResponseForbidden()
370 | else :
371 | response.WriteResponseNotFound()
372 | else :
373 | response.WriteResponseMethodNotAllowed()
374 | elif upg == 'websocket' and 'MicroWebSocket' in globals() \
375 | and self._microWebSrv.AcceptWebSocketCallback :
376 | MicroWebSocket( socket = self._socket,
377 | httpClient = self,
378 | httpResponse = response,
379 | maxRecvLen = self._microWebSrv.MaxWebSocketRecvLen,
380 | threaded = self._microWebSrv.WebSocketThreaded,
381 | acceptCallback = self._microWebSrv.AcceptWebSocketCallback )
382 | return
383 | else :
384 | response.WriteResponseNotImplemented()
385 | else :
386 | response.WriteResponseBadRequest()
387 | except :
388 | response.WriteResponseInternalServerError()
389 | try :
390 | if self._socketfile is not self._socket:
391 | self._socketfile.close()
392 | self._socket.close()
393 | except :
394 | pass
395 |
396 | # ------------------------------------------------------------------------
397 |
398 | def _parseFirstLine(self, response) :
399 | try :
400 | elements = self._socketfile.readline().decode().strip().split()
401 | if len(elements) == 3 :
402 | self._method = elements[0].upper()
403 | self._path = elements[1]
404 | self._httpVer = elements[2].upper()
405 | elements = self._path.split('?', 1)
406 | if len(elements) > 0 :
407 | self._resPath = MicroWebSrv._unquote_plus(elements[0])
408 | if len(elements) > 1 :
409 | self._queryString = elements[1]
410 | elements = self._queryString.split('&')
411 | for s in elements :
412 | param = s.split('=', 1)
413 | if len(param) > 0 :
414 | value = MicroWebSrv._unquote(param[1]) if len(param) > 1 else ''
415 | self._queryParams[MicroWebSrv._unquote(param[0])] = value
416 | return True
417 | except :
418 | pass
419 | return False
420 |
421 | # ------------------------------------------------------------------------
422 |
423 | def _parseHeader(self, response) :
424 | while True :
425 | elements = self._socketfile.readline().decode().strip().split(':', 1)
426 | if len(elements) == 2 :
427 | self._headers[elements[0].strip().lower()] = elements[1].strip()
428 | elif len(elements) == 1 and len(elements[0]) == 0 :
429 | if self._method == 'POST' or self._method == 'PUT' :
430 | self._contentType = self._headers.get("content-type", None)
431 | self._contentLength = int(self._headers.get("content-length", 0))
432 | return True
433 | else :
434 | return False
435 |
436 | # ------------------------------------------------------------------------
437 |
438 | def _getConnUpgrade(self) :
439 | if 'upgrade' in self._headers.get('connection', '').lower() :
440 | return self._headers.get('upgrade', '').lower()
441 | return None
442 |
443 | # ------------------------------------------------------------------------
444 |
445 | def GetServer(self) :
446 | return self._microWebSrv
447 |
448 | # ------------------------------------------------------------------------
449 |
450 | def GetAddr(self) :
451 | return self._addr
452 |
453 | # ------------------------------------------------------------------------
454 |
455 | def GetIPAddr(self) :
456 | return self._addr[0]
457 |
458 | # ------------------------------------------------------------------------
459 |
460 | def GetPort(self) :
461 | return self._addr[1]
462 |
463 | # ------------------------------------------------------------------------
464 |
465 | def GetRequestMethod(self) :
466 | return self._method
467 |
468 | # ------------------------------------------------------------------------
469 |
470 | def GetRequestTotalPath(self) :
471 | return self._path
472 |
473 | # ------------------------------------------------------------------------
474 |
475 | def GetRequestPath(self) :
476 | return self._resPath
477 |
478 | # ------------------------------------------------------------------------
479 |
480 | def GetRequestQueryString(self) :
481 | return self._queryString
482 |
483 | # ------------------------------------------------------------------------
484 |
485 | def GetRequestQueryParams(self) :
486 | return self._queryParams
487 |
488 | # ------------------------------------------------------------------------
489 |
490 | def GetRequestHeaders(self) :
491 | return self._headers
492 |
493 | # ------------------------------------------------------------------------
494 |
495 | def GetRequestContentType(self) :
496 | return self._contentType
497 |
498 | # ------------------------------------------------------------------------
499 |
500 | def GetRequestContentLength(self) :
501 | return self._contentLength
502 |
503 | # ------------------------------------------------------------------------
504 |
505 | def ReadRequestContent(self, size=None) :
506 | if size is None :
507 | size = self._contentLength
508 | if size > 0 :
509 | try :
510 | return self._socketfile.read(size)
511 | except :
512 | pass
513 | return b''
514 |
515 | # ------------------------------------------------------------------------
516 |
517 | def ReadRequestPostedFormData(self) :
518 | res = { }
519 | data = self.ReadRequestContent()
520 | if data :
521 | elements = data.decode().split('&')
522 | for s in elements :
523 | param = s.split('=', 1)
524 | if len(param) > 0 :
525 | value = MicroWebSrv._unquote_plus(param[1]) if len(param) > 1 else ''
526 | res[MicroWebSrv._unquote_plus(param[0])] = value
527 | return res
528 |
529 | # ------------------------------------------------------------------------
530 |
531 | def ReadRequestContentAsJSON(self) :
532 | data = self.ReadRequestContent()
533 | if data :
534 | try :
535 | return loads(data.decode())
536 | except :
537 | pass
538 | return None
539 |
540 | # ============================================================================
541 | # ===( Class Response )======================================================
542 | # ============================================================================
543 |
544 | class _response :
545 |
546 | # ------------------------------------------------------------------------
547 |
548 | def __init__(self, client) :
549 | self._client = client
550 |
551 | # ------------------------------------------------------------------------
552 |
553 | def _write(self, data, strEncoding='ISO-8859-1') :
554 | if data :
555 | if type(data) == str :
556 | data = data.encode(strEncoding)
557 | data = memoryview(data)
558 | while data :
559 | n = self._client._socketfile.write(data)
560 | if n is None :
561 | return False
562 | data = data[n:]
563 | return True
564 | return False
565 |
566 | # ------------------------------------------------------------------------
567 |
568 | def _writeFirstLine(self, code) :
569 | reason = self._responseCodes.get(code, ('Unknown reason', ))[0]
570 | return self._write("HTTP/1.1 %s %s\r\n" % (code, reason))
571 |
572 | # ------------------------------------------------------------------------
573 |
574 | def _writeHeader(self, name, value) :
575 | return self._write("%s: %s\r\n" % (name, value))
576 |
577 | # ------------------------------------------------------------------------
578 |
579 | def _writeContentTypeHeader(self, contentType, charset=None) :
580 | if contentType :
581 | ct = contentType \
582 | + (("; charset=%s" % charset) if charset else "")
583 | else :
584 | ct = "application/octet-stream"
585 | self._writeHeader("Content-Type", ct)
586 |
587 | # ------------------------------------------------------------------------
588 |
589 | def _writeServerHeader(self) :
590 | self._writeHeader("Server", "MicroWebSrv by JC`zic")
591 |
592 | # ------------------------------------------------------------------------
593 |
594 | def _writeEndHeader(self) :
595 | return self._write("\r\n")
596 |
597 | # ------------------------------------------------------------------------
598 |
599 | def _writeBeforeContent(self, code, headers, contentType, contentCharset, contentLength) :
600 | self._writeFirstLine(code)
601 | if isinstance(headers, dict) :
602 | for header in headers :
603 | self._writeHeader(header, headers[header])
604 | if contentLength > 0 :
605 | self._writeContentTypeHeader(contentType, contentCharset)
606 | self._writeHeader("Content-Length", contentLength)
607 | self._writeServerHeader()
608 | self._writeHeader("Connection", "close")
609 | self._writeEndHeader()
610 |
611 | # ------------------------------------------------------------------------
612 |
613 | def WriteSwitchProto(self, upgrade, headers=None) :
614 | self._writeFirstLine(101)
615 | self._writeHeader("Connection", "Upgrade")
616 | self._writeHeader("Upgrade", upgrade)
617 | if isinstance(headers, dict) :
618 | for header in headers :
619 | self._writeHeader(header, headers[header])
620 | self._writeServerHeader()
621 | self._writeEndHeader()
622 | if self._client._socketfile is not self._client._socket :
623 | self._client._socketfile.flush() # CPython needs flush to continue protocol
624 |
625 | # ------------------------------------------------------------------------
626 |
627 | def WriteResponse(self, code, headers, contentType, contentCharset, content) :
628 | try :
629 | if content :
630 | if type(content) == str :
631 | content = content.encode(contentCharset)
632 | contentLength = len(content)
633 | else :
634 | contentLength = 0
635 | self._writeBeforeContent(code, headers, contentType, contentCharset, contentLength)
636 | if content :
637 | return self._write(content)
638 | return True
639 | except :
640 | return False
641 |
642 | # ------------------------------------------------------------------------
643 |
644 | def WriteResponsePyHTMLFile(self, filepath, headers=None, vars=None) :
645 | if 'MicroWebTemplate' in globals() :
646 | with open(filepath, 'r') as file :
647 | code = file.read()
648 | mWebTmpl = MicroWebTemplate(code, escapeStrFunc=MicroWebSrv.HTMLEscape, filepath=filepath)
649 | try :
650 | tmplResult = mWebTmpl.Execute(None, vars)
651 | return self.WriteResponse(200, headers, "text/html", "UTF-8", tmplResult)
652 | except Exception as ex :
653 | return self.WriteResponse( 500,
654 | None,
655 | "text/html",
656 | "UTF-8",
657 | self._execErrCtnTmpl % {
658 | 'module' : 'PyHTML',
659 | 'message' : str(ex)
660 | } )
661 | return self.WriteResponseNotImplemented()
662 |
663 | # ------------------------------------------------------------------------
664 |
665 | def WriteResponseFile(self, filepath, contentType=None, headers=None) :
666 | try :
667 | size = stat(filepath)[6]
668 | if size > 0 :
669 | with open(filepath, 'rb') as file :
670 | self._writeBeforeContent(200, headers, contentType, None, size)
671 | try :
672 | buf = bytearray(1024)
673 | while size > 0 :
674 | x = file.readinto(buf)
675 | if x < len(buf) :
676 | buf = memoryview(buf)[:x]
677 | if not self._write(buf) :
678 | return False
679 | size -= x
680 | return True
681 | except :
682 | self.WriteResponseInternalServerError()
683 | return False
684 | except :
685 | pass
686 | self.WriteResponseNotFound()
687 | return False
688 |
689 | # ------------------------------------------------------------------------
690 |
691 | def WriteResponseFileAttachment(self, filepath, attachmentName, headers=None) :
692 | if not isinstance(headers, dict) :
693 | headers = { }
694 | headers["Content-Disposition"] = "attachment; filename=\"%s\"" % attachmentName
695 | return self.WriteResponseFile(filepath, None, headers)
696 |
697 | # ------------------------------------------------------------------------
698 |
699 | def WriteResponseOk(self, headers=None, contentType=None, contentCharset=None, content=None) :
700 | return self.WriteResponse(200, headers, contentType, contentCharset, content)
701 |
702 | # ------------------------------------------------------------------------
703 |
704 | def WriteResponseJSONOk(self, obj=None, headers=None) :
705 | return self.WriteResponse(200, headers, "application/json", "UTF-8", dumps(obj))
706 |
707 | # ------------------------------------------------------------------------
708 |
709 | def WriteResponseRedirect(self, location) :
710 | headers = { "Location" : location }
711 | return self.WriteResponse(302, headers, None, None, None)
712 |
713 | # ------------------------------------------------------------------------
714 |
715 | def WriteResponseError(self, code) :
716 | responseCode = self._responseCodes.get(code, ('Unknown reason', ''))
717 | return self.WriteResponse( code,
718 | None,
719 | "text/html",
720 | "UTF-8",
721 | self._errCtnTmpl % {
722 | 'code' : code,
723 | 'reason' : responseCode[0],
724 | 'message' : responseCode[1]
725 | } )
726 |
727 | # ------------------------------------------------------------------------
728 |
729 | def WriteResponseJSONError(self, code, obj=None) :
730 | return self.WriteResponse( code,
731 | None,
732 | "application/json",
733 | "UTF-8",
734 | dumps(obj if obj else { }) )
735 |
736 | # ------------------------------------------------------------------------
737 |
738 | def WriteResponseNotModified(self) :
739 | return self.WriteResponseError(304)
740 |
741 | # ------------------------------------------------------------------------
742 |
743 | def WriteResponseBadRequest(self) :
744 | return self.WriteResponseError(400)
745 |
746 | # ------------------------------------------------------------------------
747 |
748 | def WriteResponseForbidden(self) :
749 | return self.WriteResponseError(403)
750 |
751 | # ------------------------------------------------------------------------
752 |
753 | def WriteResponseNotFound(self) :
754 | if self._client._microWebSrv._notFoundUrl :
755 | self.WriteResponseRedirect(self._client._microWebSrv._notFoundUrl)
756 | else :
757 | return self.WriteResponseError(404)
758 |
759 | # ------------------------------------------------------------------------
760 |
761 | def WriteResponseMethodNotAllowed(self) :
762 | return self.WriteResponseError(405)
763 |
764 | # ------------------------------------------------------------------------
765 |
766 | def WriteResponseInternalServerError(self) :
767 | return self.WriteResponseError(500)
768 |
769 | # ------------------------------------------------------------------------
770 |
771 | def WriteResponseNotImplemented(self) :
772 | return self.WriteResponseError(501)
773 |
774 | # ------------------------------------------------------------------------
775 |
776 | def FlashMessage(self, messageText, messageStyle='') :
777 | if 'MicroWebTemplate' in globals() :
778 | MicroWebTemplate.MESSAGE_TEXT = messageText
779 | MicroWebTemplate.MESSAGE_STYLE = messageStyle
780 |
781 | # ------------------------------------------------------------------------
782 |
783 | _errCtnTmpl = """\
784 |
785 |
786 | Error
787 |
788 |
789 | %(code)d %(reason)s
790 | %(message)s
791 |
792 |
793 | """
794 |
795 | # ------------------------------------------------------------------------
796 |
797 | _execErrCtnTmpl = """\
798 |
799 |
800 | Page execution error
801 |
802 |
803 | %(module)s page execution error
804 | %(message)s
805 |
806 |
807 | """
808 |
809 | # ------------------------------------------------------------------------
810 |
811 | _responseCodes = {
812 | 100: ('Continue', 'Request received, please continue'),
813 | 101: ('Switching Protocols',
814 | 'Switching to new protocol; obey Upgrade header'),
815 |
816 | 200: ('OK', 'Request fulfilled, document follows'),
817 | 201: ('Created', 'Document created, URL follows'),
818 | 202: ('Accepted',
819 | 'Request accepted, processing continues off-line'),
820 | 203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
821 | 204: ('No Content', 'Request fulfilled, nothing follows'),
822 | 205: ('Reset Content', 'Clear input form for further input.'),
823 | 206: ('Partial Content', 'Partial content follows.'),
824 |
825 | 300: ('Multiple Choices',
826 | 'Object has several resources -- see URI list'),
827 | 301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
828 | 302: ('Found', 'Object moved temporarily -- see URI list'),
829 | 303: ('See Other', 'Object moved -- see Method and URL list'),
830 | 304: ('Not Modified',
831 | 'Document has not changed since given time'),
832 | 305: ('Use Proxy',
833 | 'You must use proxy specified in Location to access this '
834 | 'resource.'),
835 | 307: ('Temporary Redirect',
836 | 'Object moved temporarily -- see URI list'),
837 |
838 | 400: ('Bad Request',
839 | 'Bad request syntax or unsupported method'),
840 | 401: ('Unauthorized',
841 | 'No permission -- see authorization schemes'),
842 | 402: ('Payment Required',
843 | 'No payment -- see charging schemes'),
844 | 403: ('Forbidden',
845 | 'Request forbidden -- authorization will not help'),
846 | 404: ('Not Found', 'Nothing matches the given URI'),
847 | 405: ('Method Not Allowed',
848 | 'Specified method is invalid for this resource.'),
849 | 406: ('Not Acceptable', 'URI not available in preferred format.'),
850 | 407: ('Proxy Authentication Required', 'You must authenticate with '
851 | 'this proxy before proceeding.'),
852 | 408: ('Request Timeout', 'Request timed out; try again later.'),
853 | 409: ('Conflict', 'Request conflict.'),
854 | 410: ('Gone',
855 | 'URI no longer exists and has been permanently removed.'),
856 | 411: ('Length Required', 'Client must specify Content-Length.'),
857 | 412: ('Precondition Failed', 'Precondition in headers is false.'),
858 | 413: ('Request Entity Too Large', 'Entity is too large.'),
859 | 414: ('Request-URI Too Long', 'URI is too long.'),
860 | 415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
861 | 416: ('Requested Range Not Satisfiable',
862 | 'Cannot satisfy request range.'),
863 | 417: ('Expectation Failed',
864 | 'Expect condition could not be satisfied.'),
865 |
866 | 500: ('Internal Server Error', 'Server got itself in trouble'),
867 | 501: ('Not Implemented',
868 | 'Server does not support this operation'),
869 | 502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
870 | 503: ('Service Unavailable',
871 | 'The server cannot process the request due to a high load'),
872 | 504: ('Gateway Timeout',
873 | 'The gateway server did not receive a timely response'),
874 | 505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
875 | }
876 |
877 | # ============================================================================
878 | # ============================================================================
879 | # ============================================================================
880 |
881 |
--------------------------------------------------------------------------------
/microWebSrv2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jczic/MicroWebSrv/9909b830792d5d06cd1c34dce50985b3291eb0da/microWebSrv2.png
--------------------------------------------------------------------------------
/microWebTemplate.py:
--------------------------------------------------------------------------------
1 | """
2 | The MIT License (MIT)
3 | Copyright © 2018 Jean-Christophe Bos & HC² (www.hc2.fr)
4 | """
5 |
6 | import re
7 |
8 | class MicroWebTemplate :
9 |
10 | # ============================================================================
11 | # ===( Constants )============================================================
12 | # ============================================================================
13 |
14 | TOKEN_OPEN = '{{'
15 | TOKEN_CLOSE = '}}'
16 | TOKEN_OPEN_LEN = len(TOKEN_OPEN)
17 | TOKEN_CLOSE_LEN = len(TOKEN_CLOSE)
18 |
19 | INSTRUCTION_PYTHON = 'py'
20 | INSTRUCTION_IF = 'if'
21 | INSTRUCTION_ELIF = 'elif'
22 | INSTRUCTION_ELSE = 'else'
23 | INSTRUCTION_FOR = 'for'
24 | INSTRUCTION_END = 'end'
25 | INSTRUCTION_INCLUDE = 'include'
26 |
27 | MESSAGE_TEXT = ''
28 | MESSAGE_STYLE = ''
29 |
30 | # ============================================================================
31 | # ===( Constructor )==========================================================
32 | # ============================================================================
33 |
34 | def __init__(self, code, escapeStrFunc=None, filepath='') :
35 | self._code = code
36 | self._escapeStrFunc = escapeStrFunc
37 | self._filepath = filepath
38 | self._pos = 0
39 | self._endPos = len(code)-1
40 | self._line = 1
41 | self._reIdentifier = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*$')
42 | self._pyGlobalVars = { }
43 | self._pyLocalVars = { }
44 | self._rendered = ''
45 | self._instructions = {
46 | MicroWebTemplate.INSTRUCTION_PYTHON : self._processInstructionPYTHON,
47 | MicroWebTemplate.INSTRUCTION_IF : self._processInstructionIF,
48 | MicroWebTemplate.INSTRUCTION_ELIF : self._processInstructionELIF,
49 | MicroWebTemplate.INSTRUCTION_ELSE : self._processInstructionELSE,
50 | MicroWebTemplate.INSTRUCTION_FOR : self._processInstructionFOR,
51 | MicroWebTemplate.INSTRUCTION_END : self._processInstructionEND,
52 | MicroWebTemplate.INSTRUCTION_INCLUDE: self._processInstructionINCLUDE,
53 | }
54 |
55 | # ============================================================================
56 | # ===( Functions )============================================================
57 | # ============================================================================
58 |
59 | def Validate(self, pyGlobalVars=None, pyLocalVars=None) :
60 | try :
61 | self._parseCode(pyGlobalVars, pyLocalVars, execute=False)
62 | return None
63 | except Exception as ex :
64 | return str(ex)
65 |
66 | # ----------------------------------------------------------------------------
67 |
68 | def Execute(self, pyGlobalVars=None, pyLocalVars=None) :
69 | try :
70 | self._parseCode(pyGlobalVars, pyLocalVars, execute=True)
71 | return self._rendered
72 | except Exception as ex :
73 | raise Exception(str(ex))
74 |
75 | # ============================================================================
76 | # ===( Utils )===============================================================
77 | # ============================================================================
78 |
79 | def _parseCode(self, pyGlobalVars, pyLocalVars, execute) :
80 | if pyGlobalVars:
81 | self._pyGlobalVars.update(pyGlobalVars)
82 | if pyLocalVars:
83 | self._pyLocalVars.update(pyLocalVars)
84 | self._pyLocalVars['MESSAGE_TEXT'] = MicroWebTemplate.MESSAGE_TEXT
85 | self._pyLocalVars['MESSAGE_STYLE'] = MicroWebTemplate.MESSAGE_STYLE
86 | self._rendered = ''
87 | newTokenToProcess = self._parseBloc(execute)
88 | if newTokenToProcess is not None :
89 | raise Exception( '"%s" instruction is not valid here (line %s)'
90 | % (newTokenToProcess, self._line) )
91 | MicroWebTemplate.MESSAGE_TEXT = ''
92 | MicroWebTemplate.MESSAGE_STYLE = ''
93 |
94 | # ----------------------------------------------------------------------------
95 |
96 | def _parseBloc(self, execute) :
97 | while self._pos <= self._endPos :
98 | c = self._code[self._pos]
99 | if c == MicroWebTemplate.TOKEN_OPEN[0] and \
100 | self._code[ self._pos : self._pos + MicroWebTemplate.TOKEN_OPEN_LEN ] == MicroWebTemplate.TOKEN_OPEN :
101 | self._pos += MicroWebTemplate.TOKEN_OPEN_LEN
102 | tokenContent = ''
103 | x = self._pos
104 | while True :
105 | if x > self._endPos :
106 | raise Exception("%s is missing (line %s)" % (MicroWebTemplate.TOKEN_CLOSE, self._line))
107 | c = self._code[x]
108 | if c == MicroWebTemplate.TOKEN_CLOSE[0] and \
109 | self._code[ x : x + MicroWebTemplate.TOKEN_CLOSE_LEN ] == MicroWebTemplate.TOKEN_CLOSE :
110 | self._pos = x + MicroWebTemplate.TOKEN_CLOSE_LEN
111 | break
112 | elif c == '\n' :
113 | self._line += 1
114 | tokenContent += c
115 | x += 1
116 | newTokenToProcess = self._processToken(tokenContent, execute)
117 | if newTokenToProcess is not None :
118 | return newTokenToProcess
119 | continue
120 | elif c == '\n' :
121 | self._line += 1
122 | if execute :
123 | self._rendered += c
124 | self._pos += 1
125 | return None
126 |
127 | # ----------------------------------------------------------------------------
128 |
129 | def _processToken(self, tokenContent, execute) :
130 | tokenContent = tokenContent.strip()
131 | parts = tokenContent.split(' ', 1)
132 | instructName = parts[0].strip()
133 | instructBody = parts[1].strip() if len(parts) > 1 else None
134 | if len(instructName) == 0 :
135 | raise Exception( '"%s %s" : instruction is missing (line %s)'
136 | % (MicroWebTemplate.TOKEN_OPEN, MicroWebTemplate.TOKEN_CLOSE, self._line) )
137 | newTokenToProcess = None
138 | if instructName in self._instructions :
139 | newTokenToProcess = self._instructions[instructName](instructBody, execute)
140 | elif execute :
141 | try :
142 | s = str( eval( tokenContent,
143 | self._pyGlobalVars,
144 | self._pyLocalVars ) )
145 | if (self._escapeStrFunc is not None) :
146 | self._rendered += self._escapeStrFunc(s)
147 | else :
148 | self._rendered += s
149 | except Exception as ex :
150 | raise Exception('%s (line %s)' % (str(ex), self._line))
151 | return newTokenToProcess
152 |
153 | # ----------------------------------------------------------------------------
154 |
155 | def _processInstructionPYTHON(self, instructionBody, execute) :
156 | if instructionBody is not None :
157 | raise Exception( 'Instruction "%s" is invalid (line %s)'
158 | % (MicroWebTemplate.INSTRUCTION_PYTHON, self._line) )
159 | pyCode = ''
160 | while True :
161 | if self._pos > self._endPos :
162 | raise Exception( '"%s" instruction is missing (line %s)'
163 | % (MicroWebTemplate.INSTRUCTION_END, self._line) )
164 | c = self._code[self._pos]
165 | if c == MicroWebTemplate.TOKEN_OPEN[0] and \
166 | self._code[ self._pos : self._pos + MicroWebTemplate.TOKEN_OPEN_LEN ] == MicroWebTemplate.TOKEN_OPEN :
167 | self._pos += MicroWebTemplate.TOKEN_OPEN_LEN
168 | tokenContent = ''
169 | x = self._pos
170 | while True :
171 | if x > self._endPos :
172 | raise Exception("%s is missing (line %s)" % (MicroWebTemplate.TOKEN_CLOSE, self._line))
173 | c = self._code[x]
174 | if c == MicroWebTemplate.TOKEN_CLOSE[0] and \
175 | self._code[ x : x + MicroWebTemplate.TOKEN_CLOSE_LEN ] == MicroWebTemplate.TOKEN_CLOSE :
176 | self._pos = x + MicroWebTemplate.TOKEN_CLOSE_LEN
177 | break
178 | elif c == '\n' :
179 | self._line += 1
180 | tokenContent += c
181 | x += 1
182 | tokenContent = tokenContent.strip()
183 | if tokenContent == MicroWebTemplate.INSTRUCTION_END :
184 | break
185 | raise Exception( '"%s" is a bad instruction in a python bloc (line %s)'
186 | % (tokenContent, self._line) )
187 | elif c == '\n' :
188 | self._line += 1
189 | if execute :
190 | pyCode += c
191 | self._pos += 1
192 | if execute :
193 | lines = pyCode.split('\n')
194 | indent = ''
195 | for line in lines :
196 | if len(line.strip()) > 0 :
197 | for c in line :
198 | if c == ' ' or c == '\t' :
199 | indent += c
200 | else :
201 | break
202 | break
203 | pyCode = ''
204 | for line in lines :
205 | if line.find(indent) == 0 :
206 | line = line[len(indent):]
207 | pyCode += line + '\n'
208 | try :
209 | exec(pyCode, self._pyGlobalVars, self._pyLocalVars)
210 | except Exception as ex :
211 | raise Exception('%s (line %s)' % (str(ex), self._line))
212 | return None
213 |
214 | # ----------------------------------------------------------------------------
215 |
216 | def _processInstructionIF(self, instructionBody, execute) :
217 | if instructionBody is not None :
218 | if execute :
219 | try :
220 | if (' ' not in instructionBody) and \
221 | ('=' not in instructionBody) and \
222 | ('<' not in instructionBody) and \
223 | ('>' not in instructionBody) and \
224 | (instructionBody not in self._pyGlobalVars) and \
225 | (instructionBody not in self._pyLocalVars):
226 | result = False
227 | else:
228 | result = bool(eval(instructionBody, self._pyGlobalVars, self._pyLocalVars))
229 | except Exception as ex :
230 | raise Exception('%s (line %s)' % (str(ex), self._line))
231 | else :
232 | result = False
233 | newTokenToProcess = self._parseBloc(execute and result)
234 | if newTokenToProcess is not None :
235 | if newTokenToProcess == MicroWebTemplate.INSTRUCTION_END :
236 | return None
237 | elif newTokenToProcess == MicroWebTemplate.INSTRUCTION_ELSE :
238 | newTokenToProcess = self._parseBloc(execute and not result)
239 | if newTokenToProcess is not None :
240 | if newTokenToProcess == MicroWebTemplate.INSTRUCTION_END :
241 | return None
242 | raise Exception( '"%s" instruction waited (line %s)'
243 | % (MicroWebTemplate.INSTRUCTION_END, self._line) )
244 | raise Exception( '"%s" instruction is missing (line %s)'
245 | % (MicroWebTemplate.INSTRUCTION_END, self._line) )
246 | elif newTokenToProcess == MicroWebTemplate.INSTRUCTION_ELIF :
247 | self._processInstructionIF(self._elifInstructionBody, execute and not result)
248 | return None
249 | raise Exception( '"%s" instruction waited (line %s)'
250 | % (MicroWebTemplate.INSTRUCTION_END, self._line) )
251 | raise Exception( '"%s" instruction is missing (line %s)'
252 | % (MicroWebTemplate.INSTRUCTION_END, self._line) )
253 | raise Exception( '"%s" alone is an incomplete syntax (line %s)'
254 | % (MicroWebTemplate.INSTRUCTION_IF, self._line) )
255 |
256 | # ----------------------------------------------------------------------------
257 |
258 | def _processInstructionELIF(self, instructionBody, execute) :
259 | if instructionBody is None :
260 | raise Exception( '"%s" alone is an incomplete syntax (line %s)'
261 | % (MicroWebTemplate.INSTRUCTION_ELIF, self._line) )
262 | self._elifInstructionBody = instructionBody
263 | return MicroWebTemplate.INSTRUCTION_ELIF
264 |
265 | # ----------------------------------------------------------------------------
266 |
267 | def _processInstructionELSE(self, instructionBody, execute) :
268 | if instructionBody is not None :
269 | raise Exception( 'Instruction "%s" is invalid (line %s)'
270 | % (MicroWebTemplate.INSTRUCTION_ELSE, self._line) )
271 | return MicroWebTemplate.INSTRUCTION_ELSE
272 |
273 | # ----------------------------------------------------------------------------
274 |
275 | def _processInstructionFOR(self, instructionBody, execute) :
276 | if instructionBody is not None :
277 | parts = instructionBody.split(' ', 1)
278 | identifier = parts[0].strip()
279 | if self._reIdentifier.match(identifier) is not None and len(parts) > 1 :
280 | parts = parts[1].strip().split(' ', 1)
281 | if parts[0] == 'in' and len(parts) > 1 :
282 | expression = parts[1].strip()
283 | newTokenToProcess = None
284 | beforePos = self._pos
285 | if execute :
286 | try :
287 | result = eval(expression, self._pyGlobalVars, self._pyLocalVars)
288 | except :
289 | raise Exception('%s (line %s)' % (str(expression), self._line))
290 | if execute and len(result) > 0 :
291 | for x in result :
292 | self._pyLocalVars[identifier] = x
293 | self._pos = beforePos
294 | newTokenToProcess = self._parseBloc(True)
295 | if newTokenToProcess != MicroWebTemplate.INSTRUCTION_END :
296 | break
297 | else :
298 | newTokenToProcess = self._parseBloc(False)
299 | if newTokenToProcess is not None :
300 | if newTokenToProcess == MicroWebTemplate.INSTRUCTION_END :
301 | return None
302 | raise Exception( '"%s" instruction waited (line %s)'
303 | % (MicroWebTemplate.INSTRUCTION_END, self._line) )
304 | raise Exception( '"%s" instruction is missing (line %s)'
305 | % (MicroWebTemplate.INSTRUCTION_END, self._line) )
306 | raise Exception( '"%s %s" is an invalid syntax'
307 | % (MicroWebTemplate.INSTRUCTION_FOR, instructionBody) )
308 | raise Exception( '"%s" alone is an incomplete syntax (line %s)'
309 | % (MicroWebTemplate.INSTRUCTION_FOR, self._line) )
310 |
311 | # ----------------------------------------------------------------------------
312 |
313 | def _processInstructionEND(self, instructionBody, execute) :
314 | if instructionBody is not None :
315 | raise Exception( 'Instruction "%s" is invalid (line %s)'
316 | % (MicroWebTemplate.INSTRUCTION_END, self._line) )
317 | return MicroWebTemplate.INSTRUCTION_END
318 |
319 | # ----------------------------------------------------------------------------
320 |
321 | def _processInstructionINCLUDE(self, instructionBody, execute) :
322 | if not instructionBody :
323 | raise Exception( '"%s" alone is an incomplete syntax (line %s)' % (MicroWebTemplate.INSTRUCTION_INCLUDE, self._line) )
324 | filename = instructionBody.replace('"','').replace("'",'').strip()
325 | idx = self._filepath.rindex('/')
326 | if idx >= 0 :
327 | filename = self._filepath[:idx+1] + filename
328 | with open(filename, 'r') as file :
329 | includeCode = file.read()
330 |
331 | self._code = self._code[:self._pos] + includeCode + self._code[self._pos:]
332 | self._endPos += len(includeCode)
333 |
334 | # ============================================================================
335 | # ============================================================================
336 | # ============================================================================
337 |
--------------------------------------------------------------------------------
/www/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jczic/MicroWebSrv/9909b830792d5d06cd1c34dce50985b3291eb0da/www/favicon.ico
--------------------------------------------------------------------------------
/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Page de test MicroWebSrv
8 |
9 |
10 |
11 |
12 | Page de test MicroWebSrv
13 | Hello !
14 |
15 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis eget ligula quis libero mollis dapibus. Suspendisse potenti. Nullam facilisis neque et sem. Proin placerat adipiscing urna. Aenean sollicitudin. Mauris lorem erat, fringilla quis, sagittis a, varius sed, nunc. Pellentesque ligula. Nullam egestas eleifend turpis. Vivamus ac sapien. Sed venenatis, ligula ut scelerisque vehicula, erat tellus euismod ipsum, eget faucibus tortor arcu et lectus. Vivamus vel purus. Fusce dignissim tortor quis diam elementum fermentum. Mauris eleifend lorem vel arcu. Vivamus tempus faucibus lectus. Curabitur volutpat ornare mi. Curabitur ac libero. Sed eu elit ac metus egestas iaculis.
16 |
17 |
18 |
19 |
20 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/www/pdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jczic/MicroWebSrv/9909b830792d5d06cd1c34dce50985b3291eb0da/www/pdf.png
--------------------------------------------------------------------------------
/www/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 | body {
6 | background-color: white;
7 | font-family: Verdana, sans-serif;
8 | font-size: 100%;
9 | }
10 | h1 {
11 | font-size: 200%;
12 | color: navy;
13 | text-align: center;
14 | }
15 | h2 {
16 | font-size: 150%;
17 | color: red;
18 | padding-left: 15px;
19 | }
20 | p,ul,li,td {
21 | color: black;
22 | }
23 | a:link {
24 | color: green;
25 | text-decoration: underline;
26 | }
27 | a:visited {
28 | color: gray;
29 | }
30 | a:hover {
31 | color: red;
32 | text-decoration: none;
33 | }
34 | a:active, a:focus {
35 | color: red;
36 | }
--------------------------------------------------------------------------------
/www/test.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jczic/MicroWebSrv/9909b830792d5d06cd1c34dce50985b3291eb0da/www/test.pdf
--------------------------------------------------------------------------------
/www/test.pyhtml:
--------------------------------------------------------------------------------
1 |
2 |
3 | TEST
4 |
5 |
6 | BEGIN
7 | {{ py }}
8 | def _testFunction(x) :
9 | return "IN TEST FUNCTION %s" % x
10 | {{ end }}
11 |
12 | {{ for toto in range(3) }}
13 | This is an HTML test...
14 | TOTO = {{ toto + 1 }} !
15 | {{ for toto2 in range(3) }}
16 | TOTO2 = {{ _testFunction(toto2) }}
17 | {{ end }}
18 | Ok good.
19 | {{ end }}
20 |
21 | {{ _testFunction(100) }}
22 |
23 | {{ if 2+5 < 3 }}
24 | IN IF (1)
25 | {{ elif 10+15 != 25 }}
26 | IN ELIF (2)
27 | {{ elif 10+15 == 25 }}
28 | IN ELIF (3)
29 | {{ else }}
30 | IN ELSE (4)
31 | {{ end }}
32 | JC :')
33 |
34 |
35 |
--------------------------------------------------------------------------------
/www/wstest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | MicroWebSocket Test
8 |
9 |
10 |
72 |
73 |
74 | MicroWebSocket Test :
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------