├── 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 | New microWebSrv2                   10 | New microWebSrv2 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 | ![HC²](hc2.png "HC²") 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 |
20 | First name:
21 | Last name:
22 | 23 |
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 |
21 | 22 |

23 | Afficher « un fichier PDF » 24 |

25 |
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 | --------------------------------------------------------------------------------