diff --git a/Makefile b/Makefile
index 47adcb9..c18b2ad 100644
--- a/Makefile
+++ b/Makefile
@@ -55,7 +55,6 @@ toolchain:
.PHONY: build
build: $(MPY_TARGETS) $(INIT_TARGETS)
@mkdir -p $(BUILD_DIR)
- $(MAKE) docs
# Compile .py -> .mpy
$(BUILD_DIR)/%.mpy: $(SRC_DIR)/%.py
diff --git a/README.md b/README.md
index 11aefb9..f904e5b 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ from pyrobusta.server.http_server import HttpServer
async def main():
server = HttpServer()
- asyncio.create_task(server.start_socket_server())
+ server.start_socket_server()
while True:
await asyncio.sleep(1)
@@ -95,7 +95,7 @@ def mem_usage(http_ctx, _):
async def main():
server = http_server.HttpServer()
- asyncio.create_task(server.start_socket_server())
+ server.start_socket_server()
while True:
await asyncio.sleep(1)
diff --git a/docs/application_development/concepts.md b/docs/application_development/concepts.md
index 9c7e9d4..2c75c29 100644
--- a/docs/application_development/concepts.md
+++ b/docs/application_development/concepts.md
@@ -3,7 +3,7 @@
[← Back](index.md)
This chapter introduces the architectural concepts and design principles that underpin PyRobusta.
-Detailed information about configuration, routing, and request handling is covered in the correspondingchapters.
+Detailed information about configuration, routing, and request handling is covered in the corresponding chapters.
---
@@ -28,7 +28,7 @@ long-term reliability on devices with limited RAM.
## System Overview
PyRobusta is organized into a small number of runtime components, each with well-defined responsibilities.
-`HttpServer` admits new connections and provisions stream buffers from a shared resource pool.
+`HttpServer` accepts new connections and provisions stream buffers from a shared resource pool.
`HttpConnection` manages the lifetime of a client connection, including request and response streams and socket I/O.
`HttpEngine` dispatches requests and ensures protocol correctness with incremental request
parsing.
@@ -38,7 +38,7 @@ Together, these components isolate networking, protocol processing, and applicat
flowchart LR
server["HttpServer
(Connection Admission)"] --> con["HttpConnection
(Connection Management)
"]
con --> parser["HttpEngine
(Request Processing)"]
- parser --> app["Application Handler"]
+ parser --> app["Route Handler"]
```
### Components
@@ -47,7 +47,7 @@ flowchart LR
| HttpServer | Connection management, stream buffer provisioning |
| HttpConnection | Socket I/O, connection lifecycle (keep-alive, timeout) |
| HttpEngine | Incremental stream parsing, HTTP protocol validation, request routing |
-| Application Handler | Application-specific processing, response generation |
+| Route Handler | Application-specific processing, response generation |
## Execution Model
@@ -56,7 +56,7 @@ PyRobusta is based on the uasyncio I/O scheduler implemented in MicroPython.
and associates each connection with a `StreamReader` and `StreamWriter` pair for network I/O.
Each client connection executes as an independent asynchronous task, allowing multiple connections
-to make progress concurrently. `HttpServer` maintains the set of active connections and admits
+to make progress concurrently. `HttpServer` maintains the set of active connections and accepts
new connections until the configured maximum number of concurrent connections is reached. `HttpServer`
closes idle connections after the configured timeout to reclaim resources. Because each connection is
processed independently, slow clients do not block unrelated connections. However, requests within
@@ -97,7 +97,7 @@ sequenceDiagram
PyRobusta ensures bounded memory usage by allocating a fixed number of per-connection stream buffer pairs from
a shared buffer pool. The maximum number of concurrent connections determines the size of this pool.
**Because stream buffers have a fixed size, memory usage per connection is bounded. Memory required
-for normal request processing is reserved when a connection is admitted, rather than during request handling.**
+for normal request processing is reserved when a connection is accepted, rather than during request handling.**
For each new connection, the server reserves a request and response stream buffer from the shared buffer pool.
When all stream buffers are reserved, new connections are blocked until a buffer pair becomes available
@@ -111,8 +111,8 @@ fragmentation, improving memory stability and reducing the risk of runtime alloc
Memoryviews share the same underlying bytearray with stream buffers used for socket I/O, allowing application code
to access request bodies without copying the underlying data.
-Incremental request parsing requires buffers that support incremental consumption of parsed data while new bytes
-continue to arrive. PyRobusta therefore uses stream buffers designed for incremental processing rather than
+Incremental request parsing requires stream buffers that allow consumed data to be discarded while additional
+bytes continue to arrive. PyRobusta therefore uses stream buffers designed for incremental processing rather than
fixed byte buffers without consumption semantics.
Request processing is constrained by the size of the per-connection stream buffer. While request lines and header
@@ -130,7 +130,7 @@ processed without buffering the complete request body in memory.**
### Ownership
* Every request is processed by exactly one `HttpConnection`.
* Every request is dispatched through `HttpEngine`.
-* Connections are admitted only when the required stream buffers are available.
+* Connections are accepted only when the required stream buffers are available.
* Each active connection owns a dedicated request and response stream buffer.
### Processing
@@ -138,6 +138,10 @@ processed without buffering the complete request body in memory.**
* Application callbacks are invoked only after the corresponding protocol state has been successfully validated.
* All protocol errors are converted into HTTP error responses.
+### Resource Usage
+* Stream buffers are allocated during server initialization.
+* Memory usage remains bounded by the configured number and size of stream buffers.
+
Together, these architectural decisions enable PyRobusta to handle HTTP requests with predictable resource usage,
incremental request processing, and deterministic behavior suitable for resource-constrained embedded systems.
diff --git a/docs/application_development/index.md b/docs/application_development/index.md
index e3c791c..8731cb7 100644
--- a/docs/application_development/index.md
+++ b/docs/application_development/index.md
@@ -9,7 +9,7 @@ Then continue with the remaining guides in the order listed below.
## Available Resources
-* [Introduction](introduction.md)
+* [Getting Started](introduction.md)
* [Concepts](concepts.md)
* [Configuration](configuration.md)
* [Routing](routing.md)
diff --git a/docs/application_development/introduction.md b/docs/application_development/introduction.md
index bfc59d9..02f53b4 100644
--- a/docs/application_development/introduction.md
+++ b/docs/application_development/introduction.md
@@ -1,4 +1,4 @@
-# Introduction
+# Getting Started
[← Back](index.md)
@@ -8,7 +8,7 @@ This page provides practical examples for using your server.
## Table of Contents
-* [Introduction](#introduction)
+* [Getting Started](#getting-started)
+ [Demo Application](#demo-application)
+ [Deployment with mpremote](#deployment-with-mpremote)
@@ -16,76 +16,39 @@ This page provides practical examples for using your server.
## Demo Application
-The following application demonstrates common use cases for handling headers, status codes, query parameters, and wildcard routes.
-
-1. **/version** returns the version of the application.
-
- The server version can optionally be included in the response by setting the detailed query parameter to true.
-2. **/app/version** or **/server/version** returns the designated version string, handled by a single route handler with a wildcard URL.
+The following application demonstrates the basic structure of a PyRobusta application, including route registration,
+response generation, and server initialization. The application implements a simple HTTP API that returns the application version.
+The example includes boot.py, which starts the application, and app.py, which registers routes and starts the HTTP server.
```
-# /app.py
-
import asyncio
from pyrobusta.server import http_server
from pyrobusta.protocol.http import HttpEngine
-from pyrobusta.utils.config import PYROBUSTA_VERSION
-APP_VERSION = "v0.0.1"
+APP_VERSION = "v0.1.0"
@HttpEngine.route("/version", "GET")
def version(http_ctx, _):
- include_server_version = False
-
- if http_ctx.query:
- is_detailed = http_ctx.get_query_param(
- "detailed", default="false"
- ).lower()
-
- if is_detailed not in ("true", "false"):
- http_ctx.terminate(400)
- return "text/plain", "Invalid query"
-
- include_server_version = is_detailed == "true"
-
if http_ctx.headers.get("accept") == "application/json":
- version_dict = {"app_version": APP_VERSION}
- if include_server_version:
- version_dict["server_version"] = PYROBUSTA_VERSION
- return "application/json", version_dict
-
- version_text = f"app_version: {APP_VERSION}\n"
- if include_server_version:
- version_text += f"server_version: {PYROBUSTA_VERSION}\n"
- return "text/plain", version_text
-
-@HttpEngine.route("/{app_or_server}/version", "GET")
-def version(http_ctx, _):
- resource = http_ctx.path_segment(0)
-
- if resource not in ("app", "server"):
- http_ctx.terminate(404)
- return "text/plain", "Not found"
+ return "application/json", {
+ "version": APP_VERSION
+ }
- version_string = APP_VERSION if resource == "app" else PYROBUSTA_VERSION
-
- if http_ctx.headers.get("accept") == "application/json":
- return "application/json", {"version": version_string}
-
- return "text/plain", f"{version_string}\n"
+ return "text/plain", f"{APP_VERSION}\n"
async def main():
server = http_server.HttpServer()
- asyncio.create_task(server.start_socket_server())
+ server.start_socket_server()
+
while True:
await asyncio.sleep(1)
```
```
# /boot.py
+# This file is executed on every boot
-# This file is executed on every boot (including wake-boot from deepsleep)
import machine
from os import listdir
@@ -99,9 +62,12 @@ if connected and not machine.reset_cause() == machine.SOFT_RESET:
asyncio.run(app.main())
```
+In the example, `boot.py` conditionally starts the server when no REPL session is active.
+This allows `mpremote` to connect after a soft reset and upload files during development.
+
## Deployment with mpremote
-Perform a soft reset and upload app.py and boot.py using mpremote.
+Perform a soft reset and upload app.py and boot.py using `mpremote`.
```
$ mpremote a0 soft-reset
@@ -129,26 +95,10 @@ Use curl to test the application.
```
$ curl "http://192.168.1.101/version"
-app_version: v0.0.1
-
-$ curl "http://192.168.1.101/version?detailed=True"
-app_version: v0.0.1
-server_version: v0.5.0
-
-$ curl -H "Accept: application/json" "http://192.168.1.101/version?detailed=True"
-{"server_version": "v0.5.0", "app_version": "v0.0.1"}
-
-$ curl "http://192.168.1.101/app/version"
-v0.0.1
-
-$ curl "http://192.168.1.101/server/version"
-v0.5.0
-
-$ curl -H "Accept: application/json" "http://192.168.1.101/server/version"
-{"version": "v0.5.0"}
+v0.1.0
-$ curl 192.168.1.101/application/version
-Not found
+$ curl -H "Accept: application/json" "http://192.168.1.101/version"
+{"version": "v0.1.0"}
```
---
diff --git a/docs/application_development/request.md b/docs/application_development/request.md
index d3a4c2c..daa8769 100644
--- a/docs/application_development/request.md
+++ b/docs/application_development/request.md
@@ -43,16 +43,15 @@ def query_param_handler(http_ctx, _):
is_detailed = False
- if http_ctx.query:
- param = http_ctx.get_query_param(
- "detailed", default="false"
- ).lower()
+ param = http_ctx.get_query_param(
+ "detailed", default="false"
+ ).lower()
- if param not in ("true", "false"):
- http_ctx.terminate(400)
- return "text/plain", "Invalid query"
+ if param not in ("true", "false"):
+ http_ctx.terminate(400)
+ return "text/plain", "Invalid query"
- is_detailed = param == "true"
+ is_detailed = param == "true"
resource = "resource content\n"
if is_detailed:
@@ -142,28 +141,34 @@ def upload_chunks(http_ctx, payload: bytes):
async def main():
server = http_server.HttpServer()
- asyncio.create_task(server.start_socket_server())
+ await server.start_socket_server()
while True:
await asyncio.sleep(1)
```
## Multipart Requests
-Multipart requests allow clients to send composite payloads with
-the option of varying content metadata or multiple resources in a single request.
-Similar to streamed requests, multipart requests are processed one part at a time.
-The route handler is invoked once for each part. Unlike regular request bodies, multipart
-parts are parsed by the server before being passed to the application. Peprocessed parts
-consist of headers (dictionary) and the raw part body (bytes), passed as a tuple.
+Multipart requests allow clients to send multiple data parts within a single HTTP request.
+Multipart requests are delivered to the application one part at a time.
+The same route handler instance is invoked for each multipart segment of the same request.
+Unlike regular request bodies, multipart parts are parsed by the server before being passed
+to the application. Preprocessed parts consist of headers (dictionary) and the raw part body
+(bytes), passed as a tuple.
**Multipart state tracking**
The HTTP context exposes the boolean attributes
`mp_is_first` and
`mp_is_last`
-to identify the first and final part of a multipart request. This allows stateful processing
-of multiple parts belonging to the same request.
+to identify the first and final part of a multipart request. This allows the handler to maintain per-request
+state across multiple parts of the same multipart request.
```
+"""
+This example demonstrates end-to-end processing of a multipart
+upload using request-scoped temporary file buffering.
+"""
+
+import asyncio
from os import listdir, remove, rename, mkdir
import pyrobusta.server.http_server as http_server
@@ -172,25 +177,14 @@ from pyrobusta.utils.helpers import normalize_path
@HttpEngine.route("/app/parts", "POST")
def handle_parts(http_ctx, payload: tuple):
- """
- Route handler for demonstrating multipart processing.
- """
if not http_ctx.is_multipart():
http_ctx.terminate(400)
- return "text/plain", "Bad request"
-
- part_headers, part_body = payload
+ return "text/plain", "Multipart request required"
- tmp_dir = normalize_path("/tmp")
- tmp_path = normalize_path("/tmp/parts.txt")
- target_path = normalize_path("/www/user_data/parts.txt")
+ _part_headers, part_body = payload
- # Clean stale partial uploads
- if http_ctx.mp_is_first:
- if not "tmp" in listdir(normalize_path("/")):
- mkdir(tmp_dir)
- if "parts.txt" in listdir(tmp_dir):
- remove(tmp_path)
+ tmp_path = normalize_path(f"/tmp/parts.{http_ctx.id}.txt")
+ target_path = normalize_path(f"/www/user_data/parts.{http_ctx.id}.txt")
with open(tmp_path, "ab") as f:
f.write(part_body)
@@ -202,8 +196,9 @@ def handle_parts(http_ctx, payload: tuple):
return "text/plain", "OK"
async def main():
+ mkdir(normalize_path("/tmp"))
server = http_server.HttpServer()
- asyncio.create_task(server.start_socket_server())
+ await server.start_socket_server()
while True:
await asyncio.sleep(1)
```
diff --git a/docs/application_development/response.md b/docs/application_development/response.md
index 39cbe67..d555e86 100644
--- a/docs/application_development/response.md
+++ b/docs/application_development/response.md
@@ -26,29 +26,27 @@ Route handlers may optionally set the status code of the HTTP response.
If unspecified, the server defaults to HTTP 200. The status code can be overridden
through the `terminate()` method of the HTTP context.
-The `terminate()` method updates the response status code and marks the
-request as complete, but does not interrupt execution. Route handlers should still return
-an appropriate response body.
+The `terminate()` method sets the HTTP status code and transitions the request into a
+terminal state. Execution of the handler continues, but the response is considered
+finalized. Route handlers can still return a response body.
```
from pyrobusta.protocol.http import HttpEngine
@HttpEngine.route("/app/{resource}", "GET")
-def inventory_manager(http_ctx, _):
+def handler(http_ctx, _):
resource = http_ctx.path_segment(1)
if resource == "items":
# Default HTTP 200
- return "application/json", ["item-1", "item-2", "item-3"]
+ return "application/json", ["item-1", "item-2"]
- elif resource == "version":
+ if resource == "version":
# Default HTTP 200
return "text/plain", "v0.1.0"
- else:
- # Set 404 status code explicitly
- http_ctx.terminate(404)
- return "text/plain", "Not found"
+ http_ctx.terminate(404)
+ return "text/plain", "Resource not found"
```
## Response Headers
@@ -58,44 +56,19 @@ Response headers and response bodies can be configured through methods exposed b
Alternatively, route handlers may return a `(content_type, body)` tuple.
```
-import json
-
from pyrobusta.protocol.http import HttpEngine
-config = {"max-items": 5}
-items = set(["apple", "orange", "grapes"])
+@HttpEngine.route("/app", "POST")
+def handler(http_ctx, payload):
+ if not payload:
+ http_ctx.set_response_header(b"x-error", b"empty-payload")
+ http_ctx.terminate(400)
+ return "text/plain", "Missing payload"
-@HttpEngine.route("/inventory/{resource}", "POST")
-def inventory_manager(http_ctx, payload):
- resource = http_ctx.path_segment(1)
+ # Set a custom response header
+ http_ctx.set_response_header(b"x-app-version", b"1.0")
- if resource == "items":
- if http_ctx.headers.get("content-type") != "text/plain":
- http_ctx.set_response_header(b"accept-post", b"text/plain")
- http_ctx.terminate(415)
- return "text/plain", "Unsupported type"
- if len(items) >= config["max-items"]:
- http_ctx.terminate(400)
- return "text/plain", "Inventory full"
- items.add(payload.decode())
- return "text/plain", ", ".join(items)
-
- elif resource == "config":
- if http_ctx.headers.get("content-type") != "application/json":
- http_ctx.set_response_header(b"accept-post", b"application/json")
- http_ctx.terminate(415)
- return "text/plain", "Unsupported type"
- payload_json = json.loads(payload)
- if any([key not in config for key in payload_json]) or \
- any([type(value) != type(config[key]) for key, value in payload_json.items()]):
- http_ctx.terminate(400)
- return "text/plain", "Invalid config"
- config.update(payload_json)
- return "application/json", config
-
- else:
- http_ctx.terminate(404)
- return "text/plain", "Not found"
+ return "text/plain", "Request processed"
```
## Cache Control
@@ -152,7 +125,7 @@ from pyrobusta.server import http_server
from pyrobusta.protocol.http import HttpEngine
@HttpEngine.route("/stream", "GET")
-def stream(http_ctx, _):
+def stream_handler(http_ctx, _):
def generate_chunks(tx):
for i in range(10):
@@ -174,7 +147,7 @@ def stream(http_ctx, _):
async def main():
server = http_server.HttpServer()
- asyncio.create_task(server.start_socket_server())
+ await server.start_socket_server()
while True:
await asyncio.sleep(1)
```
@@ -232,7 +205,7 @@ def multipart_handler(http_ctx, _):
async def main():
server = http_server.HttpServer()
- asyncio.create_task(server.start_socket_server())
+ await server.start_socket_server()
while True:
await asyncio.sleep(1)
```
diff --git a/docs/application_development/routing.md b/docs/application_development/routing.md
index 1ca4bbe..c163af8 100644
--- a/docs/application_development/routing.md
+++ b/docs/application_development/routing.md
@@ -120,46 +120,28 @@ remove functionality at runtime.
```
from pyrobusta.protocol.http import HttpEngine
-sensor_values = {
- "dht22": {
- "temperature_c": 22,
- "rel_hum": 45
- },
- "ldr": {
- "illuminance_lx": 50
- }
+sensor_value = {
+ "temperature_c": 22,
+ "rel_hum": 45
}
-def dht22_handler(http_ctx, _):
- return "application/json", sensor_values["dht22"]
-
-def ldr_handler(http_ctx, _):
- return "application/json", sensor_values["ldr"]
+def sensor_handler(http_ctx, _):
+ return "application/json", sensor_value
-@HttpEngine.route("/sensor/{name}/enabled", "PUT")
+@HttpEngine.route("/sensor/enabled", "PUT")
def enable_sensor(http_ctx, enabled):
- sensor_name = http_ctx.path_segment(1)
+ enabled = enabled.decode().lower()
- if sensor_name not in sensor_values:
- http_ctx.terminate(404)
- return "text/plain", "Not found"
-
- if enabled.decode().lower() not in ("true", "false"):
+ if enabled not in ("true", "false"):
http_ctx.terminate(400)
return "text/plain", "Invalid value"
- is_enabled = enabled.decode().lower() == "true"
-
- if sensor_name == "dht22":
- route_handler = dht22_handler
-
- elif sensor_name == "ldr":
- route_handler = ldr_handler
+ is_enabled = enabled == "true"
if is_enabled:
- HttpEngine.register(f"/sensor/{sensor_name}/value", route_handler, "GET")
+ HttpEngine.register(f"/sensor/value", sensor_handler, "GET")
else:
- HttpEngine.deregister(f"/sensor/{sensor_name}/value", "GET")
+ HttpEngine.deregister(f"/sensor/value", "GET")
return "text/plain", "OK"
```
diff --git a/example/demo_app/app.py b/example/demo_app/app.py
index 12426df..6fa1b8e 100644
--- a/example/demo_app/app.py
+++ b/example/demo_app/app.py
@@ -53,7 +53,7 @@ def version(http_ctx, _):
async def main():
server = http_server.HttpServer()
- asyncio.create_task(server.start_socket_server())
+ await server.start_socket_server()
while True:
await asyncio.sleep(1)
diff --git a/example/mem_usage/app.py b/example/mem_usage/app.py
index 2adafb1..f5c63dd 100644
--- a/example/mem_usage/app.py
+++ b/example/mem_usage/app.py
@@ -45,7 +45,7 @@ def mem_usage(http_ctx, _):
async def main():
server = http_server.HttpServer()
- asyncio.create_task(server.start_socket_server())
+ await server.start_socket_server()
while True:
await asyncio.sleep(1)
diff --git a/example/mip_repo/app.py b/example/mip_repo/app.py
index 282921d..8332230 100644
--- a/example/mip_repo/app.py
+++ b/example/mip_repo/app.py
@@ -43,7 +43,7 @@ def self_serve_mip_package(http_ctx, _):
async def main():
server = http_server.HttpServer()
- asyncio.create_task(server.start_socket_server())
+ await server.start_socket_server()
while True:
await asyncio.sleep(1)
diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py
index 9ccdf65..d5c4b67 100644
--- a/scripts/generate_docs.py
+++ b/scripts/generate_docs.py
@@ -144,7 +144,7 @@ def render_mermaid_svg(source: str) -> str:
"sh",
"-c",
"""
- npx -y svgo /data/diagram.svg \
+ npx --silent -y svgo /data/diagram.svg \
-o /data/diagram.min.svg \
--multipass --precision=1 \
--config=/data/svgo.config.js
diff --git a/tests/functional/test_http.py b/tests/functional/test_http.py
index 90c2eaf..f459dad 100644
--- a/tests/functional/test_http.py
+++ b/tests/functional/test_http.py
@@ -3,7 +3,7 @@
import json
import gc
-from os import mkdir, remove, rmdir, stat, listdir
+from os import mkdir, remove, rmdir, listdir
from pyrobusta.server import http_server
from pyrobusta.protocol.http import HttpEngine
@@ -126,15 +126,15 @@ async def start_server():
Start an HTTP server as a background task
"""
server = http_server.HttpServer()
- server_task = asyncio.create_task(server.start_socket_server())
+ await server.start_socket_server()
await asyncio.sleep_ms(100)
- return server, server_task
+ return server
@garbage_collect
async def test_simple_response(tls_enabled):
setup_config(tls_enabled=tls_enabled)
- server, server_task = await start_server()
+ server = await start_server()
try:
# Test: text/plain
@@ -177,14 +177,13 @@ async def test_simple_response(tls_enabled):
True,
)
finally:
- server_task.cancel()
await server.terminate()
@garbage_collect
async def test_server_busy():
setup_config()
- server, server_task = await start_server()
+ server = await start_server()
try:
plain_response = await send_request(
@@ -198,7 +197,6 @@ async def test_server_busy():
True,
)
finally:
- server_task.cancel()
await server.terminate()
@@ -206,7 +204,7 @@ async def test_server_busy():
async def test_chunked_transfer_encoding():
setup_config()
create_chunked_route_handler("/test/chunked")
- server, server_task = await start_server()
+ server = await start_server()
try:
json_response = await send_request(
@@ -226,14 +224,13 @@ async def test_chunked_transfer_encoding():
["chunking\r\ntest\r\ncase", "chunking\r\ntest", "chunking"],
)
finally:
- server_task.cancel()
await server.terminate()
@garbage_collect
async def test_fs_access_control():
setup_config(served_paths="/www/test/allowed")
- server, server_task = await start_server()
+ server = await start_server()
www_root = normalize_path("/www")
test_root = normalize_path("/www/test")
fmkdir(www_root)
@@ -282,14 +279,13 @@ async def test_fs_access_control():
)
finally:
delete_path(test_root)
- server_task.cancel()
await server.terminate()
@garbage_collect
async def test_keepalive():
setup_config()
- server, server_task = await start_server()
+ server = await start_server()
try:
# ----------------------------------
@@ -367,7 +363,6 @@ async def test_keepalive():
1,
)
finally:
- server_task.cancel()
await server.terminate()
diff --git a/tests/functional/test_http_file_server.py b/tests/functional/test_http_file_server.py
index 0f1bc2d..618d860 100644
--- a/tests/functional/test_http_file_server.py
+++ b/tests/functional/test_http_file_server.py
@@ -119,15 +119,15 @@ async def start_server():
Start an HTTP server as a background task.
"""
server = http_server.HttpServer()
- server_task = asyncio.create_task(server.start_socket_server())
+ await server.start_socket_server()
await asyncio.sleep_ms(100)
- return server, server_task
+ return server
@garbage_collect
async def test_fs_path_traversal():
setup_config(served_paths="/test")
- server, server_task = await start_server()
+ server = await start_server()
test_root = normalize_path("/test")
styles_dir = normalize_path("/test/style")
fmkdir(test_root)
@@ -182,14 +182,13 @@ async def test_fs_path_traversal():
)
finally:
delete_path(test_root)
- server_task.cancel()
await server.terminate()
@garbage_collect
async def test_fs_access_control():
setup_config(served_paths="/test/allowed")
- server, server_task = await start_server()
+ server = await start_server()
test_root = normalize_path("/test")
fmkdir(test_root)
@@ -237,14 +236,13 @@ async def test_fs_access_control():
)
finally:
delete_path(test_root)
- server_task.cancel()
await server.terminate()
@garbage_collect
async def test_bulk_file_upload():
setup_config(http_multipart_enabled=True)
- server, server_task = await start_server()
+ server = await start_server()
user_data = normalize_path("/www/user_data")
tmp_dir = normalize_path("/tmp")
@@ -295,14 +293,13 @@ async def test_bulk_file_upload():
finally:
delete_path(user_data)
delete_path(tmp_dir)
- server_task.cancel()
await server.terminate()
@garbage_collect
async def test_chunked_file_upload():
setup_config()
- server, server_task = await start_server()
+ server = await start_server()
user_data = normalize_path("/www/user_data")
tmp_dir = normalize_path("/tmp")
@@ -341,7 +338,6 @@ async def test_chunked_file_upload():
finally:
delete_path(user_data)
delete_path(tmp_dir)
- server_task.cancel()
await server.terminate()
diff --git a/tests/functional/test_http_multipart.py b/tests/functional/test_http_multipart.py
index b21d6d4..6fde540 100644
--- a/tests/functional/test_http_multipart.py
+++ b/tests/functional/test_http_multipart.py
@@ -121,15 +121,15 @@ async def start_server():
Start an HTTP server as a background task.
"""
server = http_server.HttpServer()
- server_task = asyncio.create_task(server.start_socket_server())
+ await server.start_socket_server()
await asyncio.sleep_ms(100)
- return server, server_task
+ return server
@garbage_collect
async def test_multipart_response(tls_enabled):
setup_config(tls_enabled=tls_enabled)
- server, server_task = await start_server()
+ server = await start_server()
# Test: 1 part
plain_response = await send_request(
@@ -160,7 +160,6 @@ async def test_multipart_response(tls_enabled):
[True] * 10,
)
- server_task.cancel()
await server.terminate()
diff --git a/tests/system/http_dimensioning/boot.py b/tests/system/http_dimensioning/boot.py
index e5458e9..3b3d0cb 100644
--- a/tests/system/http_dimensioning/boot.py
+++ b/tests/system/http_dimensioning/boot.py
@@ -41,7 +41,7 @@ async def main():
app_multipart.load()
- asyncio.create_task(server.start_socket_server())
+ await server.start_socket_server()
asyncio.create_task(mem_usage())
while True: