├── ESPWebServer.py
├── README.md
├── Simple_led.py
├── TestWebServer.py
├── index.p.html
├── main.py
├── www
└── index.html
└── www2
├── index.html
├── index.p.html
└── test.css
/ESPWebServer.py:
--------------------------------------------------------------------------------
1 | """A simple HTTP server that only accept GET request
2 | It adopt the programming style of ESP8266WebServer
3 | library in ESP8266 Arduino Core
4 | """
5 |
6 | import network
7 | import machine
8 | import socket
9 | import uselect
10 | import os
11 |
12 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
13 | server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
14 |
15 | # Use for checking a new client connection
16 | poller = uselect.poll()
17 | # Dict for registed GET handlers of all paths
18 | getHandlers = {}
19 | # Dict for registed POST handlers of all paths
20 | postHandlers = {}
21 | # Dict for registed PUT handlers of all paths
22 | putHandlers = {}
23 | # Function of handler for request not found
24 | notFoundHandler = None
25 | # The path to the web documents on MicroPython filesystem
26 | docPath = "/"
27 | # Data for template
28 | tplData = {}
29 | # Max. POST/PUT-Data size
30 | maxContentLength = 1024
31 |
32 | # MIME types
33 | mimeTypes = {
34 | ".css":"text/css",
35 | ".jpg":"image/jpg",
36 | ".png":"image/png",
37 | }
38 |
39 | def begin(port=80):
40 | """Function to start http server
41 | """
42 | global server, poller
43 | server.bind(('0.0.0.0', port))
44 | server.listen(1)
45 | # Register for checking new client connection
46 | poller.register(server, uselect.POLLIN)
47 |
48 | def close():
49 | """Function to stop http server
50 | """
51 | poller.unregister(server)
52 | server.close()
53 |
54 | def handleClient():
55 | """Check for new client connection and process the request
56 | """
57 | global server, poller
58 | # Note:don't call poll() with 0, that would randomly cause
59 | # reset with "Fatal exception 28(LoadProhibitedCause)" message
60 | res = poller.poll(1)
61 | if res: # There's a new client connection
62 | (socket, sockaddr) = server.accept()
63 | socket.settimeout(0.02) # set timeout for readline to avoid blocking
64 | handle(socket)
65 | socket.close()
66 |
67 | def __sendPage(socket, filePath):
68 | """Send the file as webpage to client
69 | """
70 | try:
71 | f = open(filePath, "rb")
72 | while True:
73 | data = f.read(64)
74 | if (data == b""):
75 | break
76 | socket.write(data)
77 | f.close()
78 | except Exception as e:
79 | print(e)
80 |
81 |
82 | def err(socket, code, message):
83 | """Respong error meesage to client
84 | """
85 | socket.write("HTTP/1.1 " + code + " " + message + "\r\n")
86 | socket.write("Content-Type: text/html\r\n\r\n")
87 | socket.write("
" + message + "
")
88 |
89 | def ok(socket, code, *args):
90 | """Response successful message or webpage to client
91 | """
92 | if len(args)==1:
93 | content_type = "text/plain"
94 | msg = args[0]
95 | elif len(args)==2:
96 | content_type = args[0]
97 | msg = args[1]
98 | else:
99 | raise TypeError("ok() takes 3 or 4 positional arguments but "+ str(len(args)+2) +" were given")
100 | socket.write("HTTP/1.1 " + code + " OK\r\n")
101 | socket.write("Content-Type: " + content_type + "\r\n\r\n")
102 | # if __fileExist(msg):
103 | # filePath = msg
104 | # __sendPage(socket, filePath)
105 | # else:
106 | socket.write(msg)
107 |
108 | def __fileExist(path):
109 | """Check for file existence
110 | """
111 | print(path)
112 | try:
113 | stat = os.stat(path)
114 | # stat[0] bit 15 / 14 -> file/dir
115 | if stat[0] & 0x8000 == 0x8000: # file
116 | print("Found.")
117 | return True
118 | else: # Dir
119 | return False
120 | except:
121 | print("Not Found.")
122 | return False
123 |
124 |
125 | def __serveFile(socket, path):
126 | """Serves a file from the filesystem
127 | """
128 | filePath = docPath + path
129 | fileFound = True
130 | # find the file
131 | if not __fileExist(filePath):
132 | if not path.endswith("/"):
133 | fileFound = False
134 | else:
135 | filePath = path + docPath + "/index.html"
136 | # find index.html in the path
137 | if not __fileExist(filePath):
138 | filePath = path + docPath + "/index.p.html"
139 | # find index.p.html in the path
140 | if not __fileExist(filePath): # no default html file found
141 | fileFound = False
142 | if not fileFound: # file or default html file specified in path not found
143 | if notFoundHandler:
144 | notFoundHandler(socket)
145 | else:
146 | err(socket, "404", "Not Found")
147 | return
148 | # Responds the header first
149 | socket.write("HTTP/1.1 200 OK\r\n")
150 | contentType = "text/html"
151 | for ext in mimeTypes:
152 | if filePath.endswith(ext):
153 | contentType = mimeTypes[ext]
154 | socket.write("Content-Type: " + contentType + "\r\n\r\n")
155 | # Responds the file content
156 | if filePath.endswith(".p.html"):
157 | print("template file.")
158 | f = open(filePath, "r")
159 | for l in f:
160 | socket.write(l.format(**tplData))
161 | f.close()
162 | else:
163 | __sendPage(socket, filePath)
164 |
165 | def handle(socket):
166 | """Processing new GET request
167 | """
168 | global docPath, handlers
169 | try: # capture timeout for wainting a line
170 | currLine = str(socket.readline(), 'utf-8')
171 | except:
172 | currLine = "" # readline timeout (not a complete line)
173 | request = currLine.split(" ")
174 | if len(request) != 3: # Discarded if it's a bad header
175 | return
176 | (method, url, version) = request
177 | if "?" in url: # Check if there's query string?
178 | (path, query) = url.split("?", 2)
179 | else:
180 | (path, query) = (url, "")
181 | args = {}
182 | contentType = ""
183 | content = b""
184 | contentLength = 0
185 | if query: # Parsing the querying string
186 | argPairs = query.split("&")
187 | for argPair in argPairs:
188 | arg = argPair.split("=")
189 | args[arg[0]] = arg[1]
190 | while True: # Read until blank line after header
191 | header = socket.readline()
192 | if header.startswith(b"Content-Length"):
193 | (key, contentLengthStr) = str(header).split(" ")
194 | contentLength = int(contentLengthStr[0:-5])
195 | if (contentLength > maxContentLength):
196 | err(socket, "400", "Bad Request")
197 | return
198 | if (header.startswith(b"Content-Type")):
199 | (key, contentType) = str(header).split(" ")
200 | contentType = contentType[0:-5]
201 | if (header == b""):
202 | return
203 | if (header == b"\r\n" and contentLength > 0):
204 | while(len(content) < contentLength):
205 | content = content + socket.recv(contentLength)
206 | if (len(content) > maxContentLength):
207 | err(socket, "400", "Bad Request")
208 | return
209 | break
210 | elif header == b"\r\n":
211 | break
212 |
213 | # Check for supported HTTP version
214 | if version != "HTTP/1.0\r\n" and version != "HTTP/1.1\r\n":
215 | err(socket, "505", "Version Not Supported")
216 | elif (method != "GET" and method != "PUT" and method != "POST"): # Only accept GET,PUT and POST request
217 | err(socket, "501", "Not Implemented")
218 | elif (path in postHandlers and method == "POST"): # Check for registered POST path
219 | postHandlers[path](socket, args, contentType, content)
220 | elif (path in putHandlers and method == "PUT"): # Check for registered PUT path
221 | putHandlers[path](socket, args, contentType, content)
222 | elif (path in getHandlers and method == "GET"): # Check for registered GET path
223 | getHandlers[path](socket, args)
224 | elif path in getHandlers: # here to ensure compatibility
225 | getHandlers[path](socket, args)
226 | else: # find file in the document path
227 | __serveFile(socket, path)
228 |
229 | def onPath(path, handler):
230 | """Register handler for processing GET request of specified path,
231 | Here to ensure compatibility.
232 | """
233 | onGetPath(path, handler)
234 |
235 | def onGetPath(path, handler):
236 | """Register handler for processing GET request of specified path
237 | """
238 | global handlers
239 | getHandlers[path] = handler
240 |
241 | def onPostPath(path, handler):
242 | """Register handler for processing POST of specified path
243 | """
244 | global handlers
245 | postHandlers[path] = handler
246 |
247 | def onPutPath(path, handler):
248 | """Register handler for processing PUT of specified path
249 | """
250 | global handlers
251 | putHandlers[path] = handler
252 |
253 | def onNotFound(handler):
254 | """Register handler for processing request of not found path
255 | """
256 | global notFoundHandler
257 | notFoundHandler = handler
258 |
259 | def setDocPath(path):
260 | """Set the path to documents' directory
261 | """
262 | global docPath
263 | if path.endswith("/"):
264 | docPath = path[:-1]
265 | else:
266 | docPath = path
267 |
268 | def setTplData(data):
269 | """Set data for template
270 | """
271 | global tplData
272 | tplData = data
273 |
274 | def setMaxContentLength(max):
275 | """Set the maximum content lenpth for incomming data bodies
276 | """
277 | global maxContentLength
278 | maxContentLength = max
279 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ESPWebServer
2 |
3 | This is a very lightweight web server for MicroPython on ESP8266/32.It only accept GET, POST and PUT requests.It adopts the programming style of ESP8266WebServer library in ESP8266 Arduino Core.This make it suitable for serving REST API.The original code was inspired from the project [Controlling a GPIO through an ESP8266-based web server](https://lab.whitequark.org/notes/2016-10-20/controlling-a-gpio-through-an-esp8266-based-web-server/).
4 |
5 | ## Installation
6 |
7 | Just upload ESPWebServer.py to your ESP8266/32 board and you're done.
8 |
9 | ## Usage
10 |
11 | To use ESPWebServer.py library, you should:
12 |
13 | 1. Write functions as handlers for each path you'd like to serve contents.
14 |
15 | 1. Start the server by calling begin().
16 |
17 | 1. Regsiter the handlers you just prepared by calling onPath().
18 |
19 | 1. You can also uploading HTML files onto somewhere in the filesystem and settnig the document path by calling setDocPath().
20 |
21 | 1. Call handleClient() repeatly to process requests.
22 |
23 | ### Documents and Templates
24 |
25 | With setDocPath(), you can spcicified the path for all html files. For examples, if you call setDocPath('www'), and put index.html into /www, you can browse the file with 'http://server_ip/www/index.html.
26 |
27 | If you put a file with suffix '.p.html', the server would do formatting processing before output the content. You should first call setTplData() with a dictionary before accessing any template file, the server uses the elements in the dictionary to replacing all the formatting string in the template file.
28 |
29 | If you access the document path without filename, the server would try to find out if there's a index.html or index.p.html file existed, and output the file.
30 |
31 | ## Function reference
32 |
33 | ### begin(port)
34 |
35 | Start the server at specified *port*.
36 |
37 | ### onPath(path, handler)
38 |
39 | Legacy method to ensure compatibility. Calls `onGetPath(path, handler)`.
40 |
41 | ### onGetPath(path, handler)
42 |
43 | Registers a handler for handling GET Requests.
44 |
45 | The Handlers expected Method Signature: `methodName(socket, args)`
46 |
47 | ### onPostPath(path, handler)
48 |
49 | Registers a handler for handling POST Requests.
50 |
51 | The Handlers expected Method Signature: `methodName(socket, args, contenttype, content)`
52 |
53 | ### onPutPath(path, handler)
54 |
55 | Registers a handler for handling PUT Requests.
56 |
57 | The Handlers expected Method signature: `methodName(socket, args, contenttype, content)`
58 |
59 | ### setDocPath(path)
60 |
61 | Specified the directory in the filesystem containing all the HTML files.
62 |
63 | ### setTplData(dic)
64 |
65 | Specified the dictionary for template file. `dic` sould be a dictionary with all keys are string and contains all the names in replacing fields in all the template files.
66 |
67 | ### setMaxContentLength(size)
68 |
69 | Defines the maximum Content Length of incoming request bodies (POST, PUT) in bytes. Default: 1024
70 |
71 | ### handleClient()
72 |
73 | Check for new request and call corresponding handler to process it.
74 |
75 | ## Examples
76 |
77 | You can upload www directory and index.p.html to "/" on ESP8266 board and run TestWebServer.py to see how it works.
78 |
79 | `main.py` contains an example for handling POST Requests. PUT Requests are acting the same way.
80 |
81 | TestWebServer.py will show its own IP address through serial monitor.Just open your browser and connect it to http://serverIP:8899 or http://serverIP:8899/index.p.html, you'll get the main page that can turn on/off the buildin led on ESP8266 board. The main page also demonstrate the template file usage.
82 |
83 | You can also open http://serverip:8899/www/index.html or http://serverip:8899/www/ to view alternative version of controlling page that use AJAX to asynchronously turn on/off led.
84 |
85 | You can use http://serverip:8899/switch to switch led on/off led directly. Or you can use http://serverip:8899/cmd?led=on to turn the led on and http://serverip:8899/cmd?led=off to turn the led off.
86 |
--------------------------------------------------------------------------------
/Simple_led.py:
--------------------------------------------------------------------------------
1 | import ESPWebServer
2 | import network
3 | import machine
4 |
5 | GPIO_NUM = 2 # Builtin led (D4)
6 |
7 | # Wi-Fi configuration
8 | STA_SSID = "MEE_MI"
9 | STA_PSK = "PinkFloyd1969"
10 |
11 | # Disable AP interface
12 | ap_if = network.WLAN(network.AP_IF)
13 | if ap_if.active():
14 | ap_if.active(False)
15 |
16 | # Connect to Wi-Fi if not connected
17 | sta_if = network.WLAN(network.STA_IF)
18 | if not ap_if.active():
19 | sta_if.active(True)
20 | if not sta_if.isconnected():
21 | sta_if.connect(STA_SSID, STA_PSK)
22 | # Wait for connecting to Wi-Fi
23 | while not sta_if.isconnected():
24 | pass
25 |
26 | # Show IP address
27 | print("Server started @", sta_if.ifconfig()[0])
28 |
29 | # Get pin object for controlling builtin LED
30 | pin = machine.Pin(GPIO_NUM, machine.Pin.OUT)
31 | pin.on() # Turn LED off (it use sinking input)
32 |
33 | # Handler for path "/cmd?led=[on|off]"
34 | def handleCmd(socket, args):
35 | global ledData
36 | if 'led' in args:
37 | if args['led'] == 'on':
38 | ledData["status"]="ON"
39 | pin.off()
40 | elif args['led'] == 'off':
41 | ledData["status"]="OFF"
42 | pin.on()
43 | ESPWebServer.ok(socket, "200", args["led"])
44 | else:
45 | ESPWebServer.err(socket, "400", "Bad Request")
46 |
47 | # Start the server @ port 8899
48 | ESPWebServer.begin(8899)
49 |
50 | # Register handler for each path
51 | ESPWebServer.onPath("/cmd", handleCmd)
52 | ESPWebServer.setDocPath("/www2")
53 |
54 | ledData = {
55 | "status":"Off",
56 | }
57 | ESPWebServer.setTplData(ledData)
58 |
59 | try:
60 | while True:
61 | # Let server process requests
62 | ESPWebServer.handleClient()
63 | except:
64 | ESPWebServer.close()
65 |
--------------------------------------------------------------------------------
/TestWebServer.py:
--------------------------------------------------------------------------------
1 | import ESPWebServer
2 | import network
3 | import machine
4 |
5 | GPIO_NUM = 2 # Builtin led (D4)
6 |
7 | # Wi-Fi configuration
8 | STA_SSID = "MEE_MI"
9 | STA_PSK = "PinkFloyd1969"
10 |
11 | # Disable AP interface
12 | ap_if = network.WLAN(network.AP_IF)
13 | if ap_if.active():
14 | ap_if.active(False)
15 |
16 | # Connect to Wi-Fi if not connected
17 | sta_if = network.WLAN(network.STA_IF)
18 | if not ap_if.active():
19 | sta_if.active(True)
20 | if not sta_if.isconnected():
21 | sta_if.connect(STA_SSID, STA_PSK)
22 | # Wait for connecting to Wi-Fi
23 | while not sta_if.isconnected():
24 | pass
25 |
26 | # Show IP address
27 | print("Server started @", sta_if.ifconfig()[0])
28 |
29 | # Get pin object for controlling builtin LED
30 | pin = machine.Pin(GPIO_NUM, machine.Pin.OUT)
31 | pin.on() # Turn LED off (it use sinking input)
32 |
33 | # Dictionary for template file
34 | ledData = {
35 | "title":"Remote LED",
36 | "color":"red",
37 | "status":"Off",
38 | "switch":"on"
39 | }
40 |
41 | # Update information
42 | def updateInfo(socket):
43 | global ledData, color, status, switch
44 | ledData["color"] = "red" if pin.value() else "green"
45 | ledData["status"] = "Off" if pin.value() else "On"
46 | ledData["switch"] = "on" if pin.value() else "off"
47 | ESPWebServer.ok(
48 | socket,
49 | "200",
50 | ledData["status"])
51 |
52 | # Handler for path "/cmd?led=[on|off]"
53 | def handleCmd(socket, args):
54 | if 'led' in args:
55 | if args['led'] == 'on':
56 | pin.off()
57 | elif args['led'] == 'off':
58 | pin.on()
59 | updateInfo(socket)
60 | else:
61 | ESPWebServer.err(socket, "400", "Bad Request")
62 |
63 | # handler for path "/switch"
64 | def handleSwitch(socket, args):
65 | pin.value(not pin.value()) # Switch back and forth
66 | updateInfo(socket)
67 |
68 | # Start the server @ port 8899
69 | # ESPWebServer.begin(8899)
70 | ESPWebServer.begin() # use default 80 port
71 |
72 | # Register handler for each path
73 | # ESPWebServer.onPath("/", handleRoot)
74 | ESPWebServer.onPath("/cmd", handleCmd)
75 | ESPWebServer.onPath("/switch", handleSwitch)
76 |
77 | # Setting the path to documents
78 | ESPWebServer.setDocPath("/")
79 |
80 | # Setting data for template
81 | ESPWebServer.setTplData(ledData)
82 |
83 | try:
84 | while True:
85 | # Let server process requests
86 | ESPWebServer.handleClient()
87 | except:
88 | ESPWebServer.close()
89 |
--------------------------------------------------------------------------------
/index.p.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {title}
6 |
7 | {title} Status:{status}
8 | Turn {switch}
9 | HOME
10 |
11 |
12 |
19 |
20 |