Package: micropython-phew
phew!the Pico (or Python) HTTP Endpoint Wrangler:warning:Heads up! This is a very new project and should be considered, at best,
alpha stage.A small webserver and templating library specifically designed for MicroPython
on the Pico W. It aims to provide a complete toolkit for easily creating high
quality web based interfaces for your projects.phew!is ideal for creating web based provisioning interfaces for connected projects
using theRaspberry Pi Pico W.Whatphew!doesBasic exampleFunction referenceserver moduleadd_routeset_catchallrunTypesRequest typeResponse typeShorthandTemplatesrender_templateTemplate expressionsVariablesConditional displayIncludesdns modulerun_catchallWhatphew!does:a basic web serveroptimised for speed (atimportand during execution)minimal use of memoryparameterised routing rules/greet/<name>templating engine that allows inline python expressions{{name.lower()}}GET,POSTrequest methodsquery string decoding and parsingcatchall handler for unrouted requestsmultipart/form-data,x-www-form-urlencoded, and JSONPOSTbodiesstring, byte, or generator based responsesconnect_to_wificonvenience methodWhere possiblephew!tries to minimise the amount of code and setup that you,
the developer, has to do in favour of picking sane defaults and hiding away bits
of minutiae that rarely needs to be tweaked.Basic exampleAn example web server that returns a random number between 1 and 100 (or optionally
the range specified by the callee) when requested:fromphewimportserver,connect_to_wificonnect_to_wifi("<ssid>","<password>")@server.route("/random",methods=["GET"])defrandom_number(request):importrandommin=int(request.query.get("min",0))max=int(request.query.get("max",100))returnstr(random.randint(min,max))@server.catchall()defcatchall(request):return"Not found",404server.run()phewis designed specifically with performance and minimal resource use in mind.
Generally this means it will prioritise doing as little work as possible including
assuming the correctness of incoming requests.Function referenceserver moduleTheservermodule provides all functionality for running a web server with
route handlers.add_routeserver.add_route(path,handler,methods=["GET"])Adds a new route into the routing table. When an incoming request is received the server checks each route to find the most specific one that matches the request based on the path and method. If a route is found then thehandlerfunction is called with arequestparameter
that contains details about the request.defmy_handler(request):return"I got it!",200server.add_route("/testpath",my_handler,methods=["GET"])Or, alternatively, using a decorator:@server.route("/testpath",methods=["GET"])defmy_handler(request):return"I got it!",200set_catchallserver.set_catchall(handler)Provide a catchall method for requests that didn't match a route.defmy_catchall(request):return"No matching route",404server.set_catchall(my_catchall)Or, alternatively, using a decorator:@server.set_catchall()defmy_catchall(request):return"No matching route",404runserver.run(host="0.0.0.0",port=80)Starts up the web server and begins handling incoming requests.server.run()TypesRequestTheRequestobject contains all of the information that was parsed out of the
incoming request including form data, query string parameters, HTTP method, path,
and more.Handler functions provided toadd_routeandset_catchallwill recieve aRequestobject as their first parameter.memberexampletypedescriptionprotocol"HTTP/1.1"stringprotocol versionmethod"GET"or"POST"stringHTTP method used for this requesturi"/path/to/page?parameter=foo"stringfull URI of the requestpath"/path/to/page"stringjust the path part of the URIquery_string"parameter=foo"stringjust the query string part of the URIform{"foo": "bar", "name": "geoff"}dictPOSTbody parsed asmultipart/form-datadata[{"name": "jenny"}, {"name": "geoff"}]anyPOSTbody parsed as JSONquery{"parameter": "foo"}dictresult of parsing the query stringAt the time your route handler is being called the request has been fully parsed and you can access any properties that are relevant to the request (e.g. theformdictionary for amultipart/form-datarequest) any irrelevant properties will be set toNone.@server.add_route("/login",["POST"])deflogin_form(request):username=request.form.get("username",None)password=request.form.get("password",None)# check the user credentials with your own code# for example:## logged_in = authenticate_user(username, password)ifnotlogged_in:return"Username or password not recognised",401return"Logged in!",200ResponseTheResponseobject encapsulates all of the attributes of your programs response
to an incoming request. This include the status code of the result (e.g. 200 OK!)
, the data to return, and any associated headers.Handler functions can create and return aResponseobject explicitly or use a couple
of shorthand forms to avoid writing the boilerplate needed.memberexampletypedescriptionstatus200intHTTP status codeheaders{"Content-Type": "text/html"}dictdictionary of headers to returnbody"this is the response body"string or generatorthe content to be returned@server.add_route("/greeting/<name>",["GET"])defuser_details(request):returnResponse(f"Hello,{name}",status=200,{"Content-Type":"text/html"})ShorthandAs shorthand instead of returning aResponseobject the handle may also return atuplewith between
one and three values:body - either a string or generator methodstatus code - defaults to200if not providedheaders - defaults to{"Content-Type": "text/html"}if not providedFor example:@server.add_route("/greeting/<name>",["GET"])defuser_details(request,name):returnf"Hello,{name}",200TemplatesA web server isn't much use without something to serve. While it's straightforward
to serve the contents of a file or some generated JSON things get more complicated
when we want to present a dynamically generated web page to the user.phew!provides a templating engine which allows you to write normal HTML with
fragments of Python code embedded to output variable values, parse input, or dynamically
load assets.render_templaterender_template(template,param1="foo",param2="bar",...):Therender_templatemethod takes a path to a template file on the filesystem and
a list of named paramaters which will be passed into the template when parsing.The method is a generator which yields the parsing result in chunks, minimising the
amount of memory used to hold the results as they can be streamed directly out rather
than having to build the entire result as a string first.Generally you will callrender_templateto create the body of aResponsein one
of your handler methods.Template expressionsTemplates are not much use if you can't inject dynamic data into them. Withphew!you can embed Python expressions with{{<expression here>}}which will be evaluated
during parsing.VariablesIn the simplest form you can embed a simple value by just enclosing it in double curly braces.
It's also possible to perform more complicated transformations using any built in Python method.<divid="name">{{name}}</div><divid="name">{{name.upper()}}</div><divid="name">{{"/".join(name.split(" "))}}</div>Conditional displayIf you want to show a value only if some other condition is met then you can use the
(slightly clunky) Python tenary operator.<div>You won
  {{"1st" if prize == 1 else ""}}
  {{"2nd" if prize == 2 else ""}}
  {{"3rd" if prize == 3 else ""}}
  prize!</div>or<div>You won
  {{["1st", "2nd", "3rd"][prize]}}
  prize!</div>While a bit unweildy this methods works. An alternative would be to select the appropriate
value in your handler and simply pass it into the template as a parameter however that
would mean having some of your copy embedded into your Python code rather than all of it
in one place in the template file.IncludesYou can include another template by callingrender_template()again within your outer template.include.htmlHello there {{name}}!main.html<!DOCTYPE html><body>{{render_template("include.html", name=name)}}</body>:warning: Note: you need to explicitly pass through template parameters into the included template - they are not available by default.dns moduleTo make implementing device provisioning interfaces (via captive portal) simplephew!provides a catchall DNS server.If you put the Pico W into access point mode and then run the catchall DNS server it will route all DNS requests back to the local device so that they can be handled.run_catchalldns.run_catchall(ip_address)Pass in the IP address of your device once in access point mode.