In the first part we set up a small HTTP server that could answer GET requests. With this we could configure our wifi. In this part we want to extend the HTTP server so that it can also process POST requests.
Since the last time I have rearranged a few things. I have outsourced the HTTP server to the ep_http.py file and the network functionality to the ep_network.py file. This is only for the sake of clarity. I also moved a few blocks into separate functions so that we can recycle them. The HTTP server continues to run in a separate thread. We can leave it running at the same time and do not have to interrupt the actual work of the ESP32 to answer HTTP requests.
My files now look like this:
import ep_http import ep_network import _thread manager = ep_network.manager () wlan = manager.connect_or_start_ap () http = ep_http.http_config_server () t = _thread.start_new_thread (http.start, ())
import ujson import network import utime class manager: def __init __ (self, credentials_file = "credentials.json"): self.credentials_file = credentials_file def connect_or_start_ap (self): try: with open (self.credentials_file, "r") as f: credentials = ujson.load (f) except: credentials = {"wifi": {}} wlan = None if len (credentials ["wifi"])> 0: wlan = network.WLAN (network.STA_IF) wlan.active ( True) nets = sorted (wlan.scan (), key = lambda x: x [3], reverse = True) nets = [x for x in nets if x [0] .decode ("ascii") in credentials [" wifi "]] for ssid in credentials [" wifi "]: if ssid in [net [0] .decode (" ascii ") for net in nets]: wlan.connect (ssid, credentials [" wifi "] [ssid] ) i = 5 while not wlan.isconnected () and i> 0: utime.sleep (1) i - = 1 if wlan.isconnected (): break if not wlan.isconnected (): wlan.active (False) wlan = None if wlan is None: wlan = network.WLAN (network.AP_IF) wlan.active (True) return wlan
import usocket import ure import uos import ujson class http_config_server: def __init__(self): pass def start(self): s = usocket.socket() ai = usocket.getaddrinfo("0.0.0.0", 80) addr = ai[0][-1] s.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1) s.bind(addr) s.listen(5) while True: client_sock, client_addr = s.accept() req = client_sock.readline() req = self.split_request(req) if req["type"] == "GET": self.process_GET_req(req, client_sock) client_sock.close() def process_GET_req(self, req, sock): header = self.split_header(sock) if req["file"] in ["", "/"]: req["file"] = "config.html" print(req["file"]) fields = self.split_fields(req["query"]) self.process_query(req["file"], fields) self.return_file(req["file"], sock) def return_file(self, file, sock): if file in uos.listdir("html"): with open("html/"+file, "r") as f: sock.write(f.read()) def split_header(self, sock): header = {} while True: h = sock.readline().decode("ascii") if h == "" or h == "\r\n": break if ":" in h: key, val = h.split(": ") header[key] = val[:-2] return header def split_fields(self, query, encoding="application/x-www-form-urlencoded"): fields = {} if encoding == "application/x-www-form-urlencoded": if "=" in query: for field in query.split("&"): key, val = field.split("=") fields[key] = self.urlencode(val) return fields def split_request(self, req): g = ure.search(r'(GET|POST) (.*?) HTTP\/(.*)', req.decode("ascii")) request = { "type": g.group(1), "ressource": g.group(2)[1:], "version": g.group(3)[:-2] } if "?" in request["ressource"]: file, query = request["ressource"].split("?") else: file = request["ressource"] query = "" request["file"] = file request["query"] = query return request def process_query(self, file, fields): if file == "config.html": if ("ssid" in fields) & ("pass" in fields): credentials = {"wifi":{}} try: with open("credentials.json", "r") as f: credentials = ujson.load(f) except: pass credentials["wifi"][fields["ssid"]] = fields["pass"] with open("credentials.json", "w") as f: credentials["wifi"][fields["ssid"]] = fields["pass"] ujson.dump(credentials, f) def urlencode(self, line): d = { r"+": " ", r"%20": "!", r"%21": "!", r"%22": '"', r"%23": "#", r"%24": "$", r"%25": "%", r"%26": "&", r"%27": "'", r"%28": "(", r"%29": ")", r"%2A": "*", r"%2B": "+", r"%2C": ",", r"%2D": "-", r"%2E": ".", r"%2F": "/", r"%3A": ":", r"%3B": ";", r"%3C": "<", r"%3D": "=", r"%3E": ">", r"%3F": "?", r"%4A": "@", r"%5B": "[", r"%5C": "\\", r"%5D": "]", r"%7B": "{", r"%7C": "|", r"%7D": "}", } for key in d: line = line.replace(key, d[key]) return line
process_POST_req
. The difference between POST and GET requests is that with the POST request the data is not in the URL, but is not visibly transmitted after the header. But be careful: just because they are not safe for us doesn't mean they are protected from attackers. To do this, we would have to encrypt the data and use https, which we don't do at this point. The new function does almost the same as the old one process_GET_req
, only that it reads in the POST data after the header. But we cannot wait here until an end of line is sent. That is not sent here. Instead, we are told in the header how much we should read in:def process_POST_req (self, req, sock): header = self.split_header (sock) if req ["file"] in ["", "/"]: req ["file"] = "config.html" query = sock .read (int (header ["Content-Length"])). decode ("ascii") fields = self.split_fields (query) self.process_query (req ["file"], fields) self.return_file (req [" file "], sock)
and that's it. To try the whole thing, we can add the
return_file
-Function:def return_file (self, file, sock): if file == "favicon.ico": file = "favicon.png" if file in uos.listdir ("html"): with open ("html /" + file, " r ") as f: sock.write (f.read ())
In order to get rid of the IP address that is displayed in the address line, we can change the dhcp hostname. This means that a meaningful name is also displayed in the router (so far it has just been espressive). Simply add the following line in ep_network.py after we have activated the WiFi interface:
wlan.active (True) wlan.config (dhcp_hostname = "foo-bar-baz")
Now we can simply enter “http: // foo-bar-baz /” or just “foo-bar-baz” in the address line and get to our ESP32.
Aus diesem kleinen Tutorial habe ich eine kleine Bibliothek zusammengestellt, die ich ständig erweitere. Ihr könnt sie gern kostenlos nutzen: ep_http auf github
Eydam prototyping
Saccasner Strasse 19
03096 Schmogrow-Fehrow
Germany