Skip to content

Commit 19148a5

Browse files
committed
Extensive updates and expansion of docs and examples
1 parent 223086d commit 19148a5

20 files changed

+351
-159
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ HTTP Server for CircuitPython.
3030
- Gives access to request headers, query parameters, body and client's address, the one from which the request came.
3131
- Supports chunked transfer encoding.
3232
- Supports URL parameters and wildcard URLs.
33+
- Supports HTTP Basic and Bearer Authentication on both server and route per level.
3334

3435

3536
Dependencies

adafruit_httpserver/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
`adafruit_httpserver`
66
================================================================================
77
8-
Simple HTTP Server for CircuitPython
8+
Socket based HTTP Server for CircuitPython
99
1010
1111
* Author(s): Dan Halbert

adafruit_httpserver/methods.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,19 @@
99

1010

1111
GET = "GET"
12-
"""GET method."""
1312

1413
POST = "POST"
15-
"""POST method."""
1614

1715
PUT = "PUT"
18-
"""PUT method"""
1916

2017
DELETE = "DELETE"
21-
"""DELETE method"""
2218

2319
PATCH = "PATCH"
24-
"""PATCH method"""
2520

2621
HEAD = "HEAD"
27-
"""HEAD method"""
2822

2923
OPTIONS = "OPTIONS"
30-
"""OPTIONS method"""
3124

3225
TRACE = "TRACE"
33-
"""TRACE method"""
3426

3527
CONNECT = "CONNECT"
36-
"""CONNECT method"""

adafruit_httpserver/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def require_authentication(self, auths: List[Union[Basic, Bearer]]) -> None:
262262
Example::
263263
264264
server = Server(pool, "/static")
265-
server.restrict_access([Basic("user", "pass")])
265+
server.require_authentication([Basic("user", "pass")])
266266
"""
267267
self._auths = auths
268268

docs/api.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
.. automodule:: adafruit_httpserver.methods
2626
:members:
2727

28-
.. automodule:: adafruit_httpserver.mime_type
28+
.. automodule:: adafruit_httpserver.mime_types
2929
:members:
3030

3131
.. automodule:: adafruit_httpserver.exceptions

docs/examples.rst

Lines changed: 91 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,65 @@
11
Simple Test
2-
-------------------
2+
-----------
3+
4+
This is the minimal example of using the library.
5+
This example is serving a simple static text message.
6+
7+
It also manually connects to the WiFi network.
8+
9+
.. literalinclude:: ../examples/httpserver_simpletest_manual.py
10+
:caption: examples/httpserver_simpletest_manual.py
11+
:linenos:
12+
13+
Although there is nothing wrong with this approach, from the version 8.0.0 of CircuitPython,
14+
`it is possible to use the environment variables <https://docs.circuitpython.org/en/latest/docs/environment.html#circuitpython-behavior>`_
15+
defined in ``settings.toml`` file to store secrets and configure the WiFi network.
16+
17+
This is the same example as above, but it uses the ``settings.toml`` file to configure the WiFi network.
18+
19+
**From now on, all the examples will use the** ``settings.toml`` **file to configure the WiFi network.**
20+
21+
.. literalinclude:: ../examples/settings.toml
22+
:caption: settings.toml
23+
:linenos:
324

4-
Serving a simple static text message.
25+
Note that we still need to import ``socketpool`` and ``wifi`` modules.
526

6-
.. literalinclude:: ../examples/httpserver_simpletest.py
7-
:caption: examples/httpserver_simpletest.py
27+
.. literalinclude:: ../examples/httpserver_simpletest_auto.py
28+
:caption: examples/httpserver_simpletest_auto.py
829
:linenos:
930

31+
Serving static files
32+
--------------------
33+
34+
It is possible to serve static files from the filesystem.
35+
In this example we are serving files from the ``/static`` directory.
36+
37+
In order to save memory, we are unregistering unused MIME types and registering additional ones.
38+
`More about MIME types. <https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types>`_
39+
40+
.. literalinclude:: ../examples/httpserver_static_files_serving.py
41+
:caption: examples/httpserver_static_files_serving.py
42+
:linenos:
43+
44+
You can also serve a specific file from the handler.
45+
By default ``Response.send_file()`` looks for the file in the server's ``root_path`` directory, but you can change it.
46+
47+
.. literalinclude:: ../examples/httpserver_handler_serves_file.py
48+
:caption: examples/httpserver_handler_serves_file.py
49+
:linenos:
50+
51+
Tasks in the background
52+
-----------------------
53+
1054
If you want your code to do more than just serve web pages,
1155
use the ``.start()``/``.poll()`` methods as shown in this example.
1256

1357
Between calling ``.poll()`` you can do something useful,
1458
for example read a sensor and capture an average or
1559
a running total of the last 10 samples.
1660

17-
.. literalinclude:: ../examples/httpserver_simple_poll.py
18-
:caption: examples/httpserver_simple_poll.py
61+
.. literalinclude:: ../examples/httpserver_start_and_poll.py
62+
:caption: examples/httpserver_start_and_poll.py
1963
:linenos:
2064

2165
Server with MDNS
@@ -30,6 +74,22 @@ In this example, the server is accessible via ``http://custom-mdns-hostname/`` a
3074
:caption: examples/httpserver_mdns.py
3175
:linenos:
3276

77+
Handling different methods
78+
---------------------------------------
79+
80+
On every ``server.route()`` call you can specify which HTTP methods are allowed.
81+
By default, only ``GET`` method is allowed.
82+
83+
You can pass a list of methods or a single method as a string.
84+
85+
It is recommended to use the the values in ``adafruit_httpserver.methods`` module to avoid typos and for future proofness.
86+
87+
In example below, handler for ``/api`` route will be called when any of ``GET``, ``POST``, ``PUT``, ``DELETE`` methods is used.
88+
89+
.. literalinclude:: ../examples/httpserver_methods.py
90+
:caption: examples/httpserver_methods.py
91+
:linenos:
92+
3393
Change NeoPixel color
3494
---------------------
3595

@@ -45,7 +105,7 @@ Tested on ESP32-S2 Feather.
45105
:linenos:
46106

47107
Get CPU information
48-
---------------------
108+
-------------------
49109

50110
You can return data from sensors or any computed value as JSON.
51111
That makes it easy to use the data in other applications.
@@ -55,23 +115,23 @@ That makes it easy to use the data in other applications.
55115
:linenos:
56116

57117
Chunked response
58-
---------------------
118+
----------------
59119

60120
Library supports chunked responses. This is useful for streaming data.
61-
To use it, you need to set the ``chunked=True`` when creating a ``HTTPResponse`` object.
121+
To use it, you need to set the ``chunked=True`` when creating a ``Response`` object.
62122

63123
.. literalinclude:: ../examples/httpserver_chunked.py
64124
:caption: examples/httpserver_chunked.py
65125
:linenos:
66126

67127
URL parameters
68-
---------------------
128+
--------------
69129

70130
Alternatively to using query parameters, you can use URL parameters.
71131

72-
In order to use URL parameters, you need to wrap them inside ``<>`` in ``HTTPServer.route``, e.g. ``<my_parameter>``.
132+
In order to use URL parameters, you need to wrap them inside ``<>`` in ``Server.route``, e.g. ``<my_parameter>``.
73133

74-
All URL parameters are **passed as positional (not keyword) arguments** to the handler function, in order they are specified in ``HTTPServer.route``.
134+
All URL parameters values are **passed as keyword arguments** to the handler function.
75135

76136
Notice how the handler function in example below accepts two additional arguments : ``device_id`` and ``action``.
77137

@@ -80,11 +140,26 @@ make sure to add default values for all the ones that might not be passed.
80140
In the example below the second route has only one URL parameter, so the ``action`` parameter has a default value.
81141

82142
Keep in mind that URL parameters are always passed as strings, so you need to convert them to the desired type.
83-
Also note that the names of the function parameters **do not have to match** with the ones used in route, but they **must** be in the same order.
84-
Look at the example below to see how the ``route_param_1`` and ``route_param_1`` are named differently in the handler function.
85-
86-
Although it is possible, it makes more sense be consistent with the names of the parameters in the route and in the handler function.
143+
Also note that the names of the function parameters **have to match** with the ones used in route, but they **do not have to** be in the same order.
87144

88145
.. literalinclude:: ../examples/httpserver_url_parameters.py
89146
:caption: examples/httpserver_url_parameters.py
90147
:linenos:
148+
149+
Authentication
150+
--------------
151+
152+
In order to increase security of your server, you can use ``Basic`` and ``Bearer`` authentication.
153+
154+
If you want to apply authentication to the whole server, you need to call ``.require_authentication`` on ``Server`` instance.
155+
156+
.. literalinclude:: ../examples/httpserver_authentication_server.py
157+
:caption: examples/httpserver_authentication_server.py
158+
:linenos:
159+
160+
On the other hand, if you want to apply authentication to a set of routes, you need to call ``require_authentication`` function.
161+
In both cases you can check if ``request`` is authenticated by calling ``check_authentication`` on it.
162+
163+
.. literalinclude:: ../examples/httpserver_authentication_handlers.py
164+
:caption: examples/httpserver_authentication_handlers.py
165+
:linenos:
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# SPDX-FileCopyrightText: 2022 Dan Halbert for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
5+
import socketpool
6+
import wifi
7+
8+
from adafruit_httpserver import Server, Request, Response, UNATUHORIZED_401
9+
from adafruit_httpserver.authentication import (
10+
AuthenticationError,
11+
Basic,
12+
Bearer,
13+
check_authentication,
14+
require_authentication,
15+
)
16+
17+
18+
pool = socketpool.SocketPool(wifi.radio)
19+
server = Server(pool)
20+
21+
# Create a list of available authentication methods.
22+
auths = [
23+
Basic("user", "password"),
24+
Bearer("642ec696-2a79-4d60-be3a-7c9a3164d766"),
25+
]
26+
27+
28+
@server.route("/check")
29+
def check_if_authenticated(request: Request):
30+
"""
31+
Check if the request is authenticated and return a appropriate response.
32+
"""
33+
is_authenticated = check_authentication(request, auths)
34+
35+
with Response(request, content_type="text/plain") as response:
36+
response.send("Authenticated" if is_authenticated else "Not authenticated")
37+
38+
39+
@server.route("/require-or-401")
40+
def require_authentication_or_401(request: Request):
41+
"""
42+
Require authentication and return a default server 401 response if not authenticated.
43+
"""
44+
require_authentication(request, auths)
45+
46+
with Response(request, content_type="text/plain") as response:
47+
response.send("Authenticated")
48+
49+
50+
@server.route("/require-or-handle")
51+
def require_authentication_or_manually_handle(request: Request):
52+
"""
53+
Require authentication and manually handle request if not authenticated.
54+
"""
55+
56+
try:
57+
require_authentication(request, auths)
58+
59+
with Response(request, content_type="text/plain") as response:
60+
response.send("Authenticated")
61+
62+
except AuthenticationError:
63+
with Response(request, status=UNATUHORIZED_401) as response:
64+
response.send("Not authenticated - Manually handled")
65+
66+
67+
print(f"Listening on http://{wifi.radio.ipv4_address}:80")
68+
server.serve_forever(str(wifi.radio.ipv4_address))
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# SPDX-FileCopyrightText: 2022 Dan Halbert for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
5+
import socketpool
6+
import wifi
7+
8+
from adafruit_httpserver import Server, Request, Response, Basic, Bearer
9+
10+
11+
# Create a list of available authentication methods.
12+
auths = [
13+
Basic("user", "password"),
14+
Bearer("642ec696-2a79-4d60-be3a-7c9a3164d766"),
15+
]
16+
17+
pool = socketpool.SocketPool(wifi.radio)
18+
server = Server(pool, "/static")
19+
server.require_authentication(auths)
20+
21+
22+
@server.route("/implicit-require")
23+
def implicit_require_authentication(request: Request):
24+
"""
25+
Implicitly require authentication because of the server.require_authentication() call.
26+
"""
27+
28+
with Response(request, content_type="text/plain") as response:
29+
response.send("Authenticated")
30+
31+
32+
print(f"Listening on http://{wifi.radio.ipv4_address}:80")
33+
server.serve_forever(str(wifi.radio.ipv4_address))

examples/httpserver_chunked.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,23 @@
22
#
33
# SPDX-License-Identifier: Unlicense
44

5-
import os
6-
75
import socketpool
86
import wifi
97

10-
from adafruit_httpserver.request import HTTPRequest
11-
from adafruit_httpserver.response import HTTPResponse
12-
from adafruit_httpserver.server import HTTPServer
13-
14-
15-
ssid = os.getenv("WIFI_SSID")
16-
password = os.getenv("WIFI_PASSWORD")
8+
from adafruit_httpserver import Server, Request, Response
179

18-
print("Connecting to", ssid)
19-
wifi.radio.connect(ssid, password)
20-
print("Connected to", ssid)
2110

2211
pool = socketpool.SocketPool(wifi.radio)
23-
server = HTTPServer(pool, "/static")
12+
server = Server(pool)
2413

2514

2615
@server.route("/chunked")
27-
def chunked(request: HTTPRequest):
16+
def chunked(request: Request):
2817
"""
2918
Return the response with ``Transfer-Encoding: chunked``.
3019
"""
3120

32-
with HTTPResponse(request, chunked=True) as response:
21+
with Response(request, chunked=True) as response:
3322
response.send_chunk("Adaf")
3423
response.send_chunk("ruit")
3524
response.send_chunk(" Indus")

0 commit comments

Comments
 (0)