From 9648dc92d413b028b21436f19399c859a723746d Mon Sep 17 00:00:00 2001 From: szeka9 Date: Sun, 21 Jun 2026 17:01:11 +0200 Subject: [PATCH 1/2] Add documentation and guide about supported features --- assets/www/configuration.html | 173 ++++++++++ assets/www/file_server.html | 207 ++++++++++++ assets/www/index.html | 38 +-- .../www/{examples.html => introduction.html} | 108 ++---- assets/www/request.html | 243 ++++++++++++++ assets/www/response.html | 311 ++++++++++++++++++ assets/www/routing.html | 197 +++++++++++ assets/www/static_content.html | 130 ++++++++ assets/www/styles.css | 50 +++ docs/configuration.md | 12 +- example/mem_usage/app.py | 9 +- 11 files changed, 1368 insertions(+), 110 deletions(-) create mode 100644 assets/www/configuration.html create mode 100644 assets/www/file_server.html rename assets/www/{examples.html => introduction.html} (54%) create mode 100644 assets/www/request.html create mode 100644 assets/www/response.html create mode 100644 assets/www/routing.html create mode 100644 assets/www/static_content.html create mode 100644 assets/www/styles.css diff --git a/assets/www/configuration.html b/assets/www/configuration.html new file mode 100644 index 0000000..f777411 --- /dev/null +++ b/assets/www/configuration.html @@ -0,0 +1,173 @@ + + + + + + PyRobusta Home + + + + +

Configuration

+ + ← Back + +

+ This page documents PyRobusta configuration options, + configuration deployment using mpremote, + and runtime access to configuration values through the + configuration API. +

+ +
+ +

Table of Contents

+ + Configuration
+ ├── Configuration Format & Deployment
+ ├── Parameter Description
+ └── Configuration API
+ +
+ + +

Configuration Format & Deployment

+ +

+ Configuration overrides can be provided through pyrobusta.env, using standard .env syntax. + pyrobusta.env must be stored in the server root. Inline comments are supported using #. +

+ + + +

Perform a soft reset and upload pyrobusta.env using mpremote.

+ + + +

Parameter Description

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionDefault
wifi_ssidName of the Wi-Fi network. When empty, Wi-Fi is not initialized by the built-in wifi.py module.None
wifi_passwordPassword of the Wi-Fi network. When empty, Wi-Fi is not initialized by the built-in wifi.py module.None
http_portPort number for HTTP.80
https_portPort number for HTTPS.443
http_multipartEnables or disables multipart request and response processing. Enabling multipart support increases memory usage.False
http_mem_cap + Fraction of available heap memory reserved for stream buffers. Valid range: (0, 1]. + 0.1
http_served_paths + Space-separated list of filesystem paths that may be served over HTTP. + /www /lib/pyrobusta
http_files_api + Enables or disables the file management API endpoint (/files), allowing upload, download, and listing of files. + False
socket_max_conMaximum number of simultaneous socket connections.2
tls + Enables or disables TLS. When enabled, cert.der and key.der must be installed at the server root. + False
log_levelLogging level. Can be one of: warning, info, debug.info
+ +

Configuration API

+ +

+ Configuration values can be accessed through the + pyrobusta.utils.config module. + Values are loaded from pyrobusta.env during server initialization. + Configuration values can be retrieved using + get_config() together with one of the + predefined CONF_* constants. +

+ +

+ After initialization, configuration values are retrieved from an internal cache. + The cached values are normalized to their expected runtime types to avoid repeated + parsing of environment strings. +

+ +

+ Configuration values are treated as immutable during runtime. + Changes are applied only when the configuration cache is reloaded. + The configuration cache can be reloaded by calling + read_config(), which re-reads pyrobusta.env + and rebuilds the internal normalized cache. +

+ + + + + + diff --git a/assets/www/file_server.html b/assets/www/file_server.html new file mode 100644 index 0000000..e54769b --- /dev/null +++ b/assets/www/file_server.html @@ -0,0 +1,207 @@ + + + + + + PyRobusta Home + + + + +

File Server API

+ + ← Back + +

This API provides file management capabilities, allowing clients to upload, + retrieve, and manage files through various HTTP methods. + http_files_api must be set to True in + pyrobusta.env to enable this API. +

+ +
+ +

Table of Contents

+ + Static Content
+ ├── Summary
+ ├── File Retrieval
+ ├── File Upload / Overwrite
+ ├── Bulk File Upload
+ └── File Delete
+ +
+ +

Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPathDescription
GET/files/{path}Lists or retrieves metadata about files.
PUT/files/{file path}Uploads or overwrites a file at the specified path.
POST/filesUploads multiple files in multipart/form-data.
DELETE/files/{file path}Deletes a file at the specified path.
+ +
+

File Retrieval / Listing

+

Endpoint: GET /files/{path}

+ +

+ This method allows general file system interaction, enabling operations + such as listing directory contents, retrieving metadata, and downloading + files. +

+ + + +

Example Request

+ + +
+ +
+

File Upload / Overwrite

+

Endpoint: PUT /files/{file path}

+ +

+ This method uploads a file or overwrites an existing file at a specific + path. The upload path is restricted to + /www/user_data. +

+ + + +

Example Request

+ + +
+ +
+

Bulk File Upload

+

Endpoint: POST /files

+ +

+ This method handles general file uploads, designed for uploading multiple + files with per-file chunking supported. Only + multipart/form-data is accepted. +

+ +

+ Uploads are restricted to /www/user_data. The + Content-Disposition header only needs to specify the file + name; the upload directory is prepended automatically. +

+ +

+ http_multipart must be set to True in the + configuration to use this method. +

+ + + +

Example Request

+ + +
+ +
+

File Delete

+

Endpoint: DELETE /files/{file path}

+ +

+ This method deletes a file at a specific path. The path is restricted to + /www/user_data. +

+ + + +

Example Request

+ + +
+ + + + + diff --git a/assets/www/index.html b/assets/www/index.html index b3be74b..7abaf0e 100644 --- a/assets/www/index.html +++ b/assets/www/index.html @@ -4,33 +4,7 @@ PyRobusta Home - + @@ -42,7 +16,13 @@

PyRobusta Home

Available Resources

@@ -52,4 +32,4 @@

Available Resources

- \ No newline at end of file + diff --git a/assets/www/examples.html b/assets/www/introduction.html similarity index 54% rename from assets/www/examples.html rename to assets/www/introduction.html index e0cdfd9..baa1d8c 100644 --- a/assets/www/examples.html +++ b/assets/www/introduction.html @@ -4,76 +4,38 @@ PyRobusta Home - + -

Getting Started

+

Introduction

← Back -

This page presents useful examples to configure your server.

+

This page provides practical examples for using your server.


-

Server configuration

- -

+ Introduction
+ ├── Demo Application
+ └── Deployment with mpremote

-

Demo Application

-

The below application demonstrates common use cases for handling headers, status codes, query parameters, and wilcard URLs.

-
    -
  1. /version returns the version of the application. -
    Optionally, the server version is also included in the response if the 'detailed' query parameter is set to true. -
  2. -
  3. /app/version or /server/version returns the designated version string, handled by a single endpoint definition with a wildcard URL. -
  4. -
- - -

Soft reset the device and upload app.py and boot.py with mpremote.

- -

Hard reset the device to start the application and connect over REPL.

- -

Use curl to test your application.

- + +

Request Headers

+ +

Headers received in a request are available in the headers attribute of the HTTP context. + The headers attribute is a dictionary of key-value pairs. Header names and values are exposed as strings. + As a convenience, the Content-Length header is automatically converted to an integer because it is frequently used for + payload size calculations. Headers are normalized to lower case, so the key "Content-Length" is equivalent to the key + "content-length". +

+ +

Request headers must contain only a subset of US-ASCII characters:

+ +
+ +
+ + + +

Request Bodies

+ +

A request payload is passed as the second positional argument of the + route handler. Unless the request uses streaming or multipart encoding, the payload + is provided as a memoryview to avoid unnecessary memory allocations. Deserialization must be done + by the user application. +

+ + + +

Streamed Requests

+ +

PyRobusta supports streaming requests by processing individual + chunks of the request body as they are received. To enforce bounded memory usage, + request chunks are processed individually by calling registered route handlers + for each chunk received. As a result, the application must process the request body + incrementally rather than assuming the full payload is available at once. +

+ + + +

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 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. +

+ + + + + + diff --git a/assets/www/response.html b/assets/www/response.html new file mode 100644 index 0000000..3951bbc --- /dev/null +++ b/assets/www/response.html @@ -0,0 +1,311 @@ + + + + + + PyRobusta Home + + + + +

Response Processing

+ + ← Back + +

Response processing controls how route handlers construct HTTP responses. + This includes setting status codes, configuring response headers, serializing response bodies, + and generating streamed or multipart responses. +

+
+ +

Table of Contents

+ + Response Processing
+ ├── Status Codes
+ ├── Response Headers
+ ├── Cache Control
+ ├── Content Types & Serialization
+ ├── Streamed Responses
+ └── Streamed Multipart Responses
+ +
+ +

Status Codes

+ +

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. +

+ + + +

Response Headers

+ +

Response headers and response bodies can be configured through methods exposed by the HTTP context + (set_response_header(), set_response_body()). + Alternatively, route handlers may return a (content_type, body) tuple. +

+ + + + +

Cache Control

+ +

+ PyRobusta implements a simple caching policy. + Unless overridden by the application, all HTTP responses include the + header Cache-Control: no-store. + + Conditional requests and cache validation mechanisms are not supported. + This includes ETag, Last-Modified, + If-None-Match, and If-Modified-Since. + + This design reduces implementation complexity and avoids additional + filesystem metadata lookups on resource-constrained devices. +

+ +

Content Types & Serialization

+ +

PyRobusta can automatically serialize a limited set of built-in types and data structures. + Unsupported types must be serialized by the application before being returned as either a string or a bytes-like + object. The following response body types are currently supported: +

+ + + +

For non-streamed responses, the entire response body must exist in memory before it is + transmitted. As a result, the maximum response size is limited by the available heap. In the meantime, a + response buffer has a fixed size depending on the configuration. Internally, response bodies are wrapped + in a BytesIO object so that both fixed-size and streamed responses can be written through the same + buffer-oriented interface. Each response body returned by a route handler has a known size, allowing the + content-length header to be filled by the server. +

+ +

Streamed Responses

+

+ Responses can be streamed in chunks when the application cannot determine the size of the response body in advance. + Such responses must use chunked transfer encoding, indicated by the Transfer-Encoding header. With chunked encoding, + the Content-Length header must be omitted; instead the size of each chunk must be indicated as the chunks are sent. + The server automatically generates the required chunk metadata. +

+ +

+ When chunked transfer encoding is enabled, the server automatically generates the required encoding format. + The application must assign a generator function to http_ctx.resp_handler. The server then invokes the generator + when data is ready to be transmitted. The generator may be resumed multiple times until the stream is complete. + The following requirements must be fulfilled by the generator: +

+ +
+
    +
  1. the generator function must accept a single response buffer argument (tx) used to write response data
  2. +
  3. the generator yields False after producing a chunk while additional data remains
  4. +
  5. the generator yields True exactly once to indicate that the stream is complete
  6. +
  7. the generator verifies the writable capacity of the buffer and + writes at most that much data to the buffer before yielding
  8. +
+
+ + + + + +

Streamed Multipart Responses

+

+ Multipart responses allow a single HTTP response to contain multiple independently typed payloads, + each with its own headers and body. Similar to streamed responses, PyRobusta uses response producer + functions to generate multipart response parts on demand. Because the total response size is not known + in advance, the server automatically uses chunked transfer encoding for multipart responses. It is + explicitly enabled in the example below for completeness. +

+ +

+ Routes producing streamed multipart responses must satisfy the following requirements: +

+ +
+
    +
  1. the route must return a tuple containing the multipart content type and a callable response producer
  2. +
  3. the response producer must return a tuple containing the content type and payload of each part
  4. +
  5. after producing the final part, the response producer must return None
  6. +
+
+ + + +

+ Try the X-Part-Count and X-Part-Size headers to arbitrarily configure the response size. +

+ + + + + + diff --git a/assets/www/routing.html b/assets/www/routing.html new file mode 100644 index 0000000..10c14c7 --- /dev/null +++ b/assets/www/routing.html @@ -0,0 +1,197 @@ + + + + + + PyRobusta Home + + + + +

Routing

+ + ← Back + +

Routing maps incoming HTTP requests to application-defined route handlers. + This page describes how routes are defined, how route handlers receive requests, and how wildcard routes + can be used to match dynamic URL paths. +

+ +
+ +

Table of Contents

+ + Routing
+ ├── Route Definitions
+ ├── Route Handlers
+ ├── Wildcard Routes
+ └── Route Registration & Deregistration
+ +
+ +

Route Definitions

+ +

Routes map HTTP requests to server-side handler functions that + process requests and manage resources. Similar to common web frameworks, PyRobusta + utilizes function decorators to map handler functions to URL paths. A route handler + can only be mapped to a single URL path and HTTP method. The same URL path may be + associated with multiple route handlers provided that each handler uses a different + HTTP method. +

+ + + +

Route Handlers

+ +

In PyRobusta, a route handler is a synchronous function registered to a + specific URL path and HTTP method. PyRobusta invokes a route handler whenever it receives a + request whose URL path and HTTP method match the registered route. Route handlers must accept + exactly two positional arguments: +

+ + +

HTTP Context

+ +

The HTTP context is an instance of the HttpEngine class that exposes the + public API used to inspect requests and construct responses. The HTTP context provides public + methods and attributes that enable user applications to process headers and structure responses. + By convention, non-public attributes and methods are prefixed with an underscore. + +

+ + Apart from the public API, the HTTP context also encapsulates the state associated with the current + request and response exchange. Internally, the HTTP context ensures protocol correctness and assists + with request routing. +

+ +

Request Body

+ +

Depending on the request type, the body argument may contain either the complete + request body or a partial payload chunk. Partial request bodies are passed to route handlers + when the request uses multipart encoding or chunked transfer encoding, with each chunk of the payload + fed incrementally to the route handler. Such request processing is documented in the + Request Processing guide. +

+ +

Wildcard Routes

+ +

Wildcard routes use placeholders in one or more segments of a URL path. + These placeholders match varying path values, allowing multiple URL paths to be mapped to + a single handler function. A placeholder matches a single path segment by default. Alternatively, + a placeholder can match multiple segments by using the :path suffix. For example: + +

/path/to/{resource:path}

+ + Placeholders that match multiple path segments are only allowed at the end of a route. For example, + /path/{to:path}/resource is disallowed because it would result in ambiguous route resolution. +

+ + + +

Route Registration & Deregistration

+ +

By convention, user applications should use decorators + to register route handlers in most cases. This approach ensures that routes are registered + during application initialization, and routes remain available for the lifetime of the application. + The public API exposed by HttpEngine allows route registration and deregistration + during an application's lifecycle, enabling applications to dynamically expose or + remove functionality at runtime. +

+ + + + + + diff --git a/assets/www/static_content.html b/assets/www/static_content.html new file mode 100644 index 0000000..344b32c --- /dev/null +++ b/assets/www/static_content.html @@ -0,0 +1,130 @@ + + + + + + PyRobusta Home + + + + +

Static Content

+ + ← Back + +

+ PyRobusta serves static content directly from the filesystem. This page + describes the directory structure, file resolution rules, and MIME type + handling used by the server. +

+ +
+ +

Table of Contents

+ + Static Content
+ ├── Static File Serving
+ ├── Directory Structure
+ └── MIME Type Handling
+ +
+ +

Static File Serving

+ +

Files stored under /www are served as static content. + Requests to the server root (/) return the default landing page (index.html). + For static content requests, the server automatically prepends /www to the requested path before + resolving the corresponding file on the filesystem. +

+ +

When the file management API is enabled (http_files_api=True), + additional filesystem locations can be exposed through the http_served_paths + configuration option. See the Server Configuration + and File Server API guide for additional details. +

+ +

Directory Structure

+ +
+root/
+├── www                     document root for static content
+│   ├── index.html
+│   ├── introduction.html
+│   ├── ...
+│   └── user_data           root for user uploads
+│       └── ...
+├── lib                     root for installed MIP packages
+│   ├── pyrobusta
+│   │   ├── bindings
+│   │   ├── connectivity
+│   │   └── ...
+│   └── <other packages>
+├── cert.der                TLS certificate
+└── key.der                 TLS key
+    
+ +

MIME Type Handling

+ +

PyRobusta automatically determines the Content-Type header for + static files based on their filename extension. The selected content type depends on the file extension. + Unknown extensions are mapped to application/octet-stream. + The following mapping between extensions and content types is maintained by the server: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionContent-Type header
.htmltext/html
.csstext/css
.jsapplication/javascript
.jsonapplication/json
.icoimage/x-icon
.jpegimage/jpeg
.jpgimage/jpeg
.pngimage/png
.txttext/plain
.gifimage/gif
.raw, unknown extensionsapplication/octet-stream
+ + + + diff --git a/assets/www/styles.css b/assets/www/styles.css new file mode 100644 index 0000000..9076f4a --- /dev/null +++ b/assets/www/styles.css @@ -0,0 +1,50 @@ +body { + font-family: serif; + margin: 40px; + background: white; + color: black; +} + +h1 { + border-bottom: 1px solid #999; + padding-bottom: 10px; +} + +hr { + margin: 20px 0; +} + +a { + color: blue; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.description { + width: clamp(300px, 70vw, 800px); +} + +table, th, td { + border: 1px solid black; + border-collapse: collapse; +} + +textarea { + resize: none; + background-color: rgb(37, 37, 37); + color: white; + box-sizing: border-box; + width: 100%; + padding-left: 10px; + field-sizing: content; + white-space: pre; +} + +.footer { + font-size: 0.9em; + color: #555; + margin-top: 30px; +} diff --git a/docs/configuration.md b/docs/configuration.md index b6eb3da..b74211e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -9,10 +9,10 @@ to upload it to the root directory of the target device. | wifi_password | Password of the Wi-Fi network. When empty, Wi-Fi is not initalized by the built-in wifi.py module. | None | | http_port | Port number for HTTP. | 80 | | https_port | Port number for HTTPS. | 443 | -| http_multipart | Enable multipart HTTP requests/responses. | False | -| http_mem_cap | Max memory cap (% × 0.01) of usable heap for HTTP request/response stream buffers. | 0.1 | -| http_served_paths | Space delimited list of filesystem paths allowed to be served through HTTP. | "/www /lib/pyrobusta" | +| http_multipart | Enables or disables multipart request and response processing. Enabling multipart support increases memory usage. | False | +| http_mem_cap | Fraction of available heap memory reserved for stream buffers; (0;1] | 0.1 | +| http_served_paths | Space-separated list of filesystem paths that may be served over HTTP. | "/www /lib/pyrobusta" | | http_files_api | Enables or disables the [file management API](./api.md#file-management-api) endpoint (/files), allowing to upload, download, and list files. | False | -| socket_max_con | Max number of socket connections of any enabled application server. | 2 | -| tls | Enables or disables TLS. When turned on, cert.der/key.der must be installed at the root. | False | -| log_level | Can be one of: warning, info, debug. | "info" | +| socket_max_con | Maximum number of simultaneous socket connections. | 2 | +| tls | Enables or disables TLS. When enabled, cert.der and key.der must be installed at the server root. | False | +| log_level | Logging level. Can be one of: warning, info, debug. | "info" | diff --git a/example/mem_usage/app.py b/example/mem_usage/app.py index f4bb084..2adafb1 100644 --- a/example/mem_usage/app.py +++ b/example/mem_usage/app.py @@ -15,7 +15,8 @@ def mem_usage(http_ctx, _): if http_ctx.query: value_format = http_ctx.get_query_param("format", "bytes") if value_format not in ("%", "bytes"): - raise ValueError("invalid format") + http_ctx.terminate(400) + return "text/plain", "Invalid query" selector = http_ctx.get_query_param("key", "") if selector == "free": @@ -30,7 +31,9 @@ def mem_usage(http_ctx, _): return "text/plain", f"Total [bytes]: {used + free}\n" if selector: - raise ValueError("invalid key") + http_ctx.terminate(400) + return "text/plain", "Invalid query" + return "text/plain", ( f"Currently used: {usage_percentage:.2f}%\n" @@ -48,4 +51,4 @@ async def main(): if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) From 5eaab5a0079f1ceb021ccc73d7b810f666e69c95 Mon Sep 17 00:00:00 2001 From: szeka9 Date: Sun, 21 Jun 2026 18:30:11 +0200 Subject: [PATCH 2/2] Unify terminology; use "route" instead of "endpoint" The terms "endpoint" and "route handler" are used inconsistently throughout the source code and documentation. This change applies the following renaming: - "callback" / "callback function" -> "route handler" - "endpoint" -> "route" The term "endpoint" is retained where it refers to an application endpoint exposed to clients rather than a specific route definition. --- assets/www/response.html | 2 +- dist/pyrobusta/assets/www/examples.html | 2 +- docs/architecture/state_machine.md | 14 +-- src/pyrobusta/protocol/http.py | 108 ++++++++++---------- src/pyrobusta/protocol/http_file_server.py | 10 +- src/pyrobusta/protocol/http_multipart.py | 20 ++-- src/pyrobusta/server/http_server.py | 4 +- tests/functional/test_http.py | 24 ++--- tests/functional/test_http_multipart.py | 8 +- tests/system/http_dimensioning/boot.py | 6 +- tests/system/http_dimensioning/http_user.py | 10 +- tests/unit/test_http.py | 60 ++++++----- tests/unit/test_http_file_server.py | 28 ++--- tests/unit/test_http_multipart.py | 36 +++---- 14 files changed, 164 insertions(+), 168 deletions(-) diff --git a/assets/www/response.html b/assets/www/response.html index 3951bbc..f149882 100644 --- a/assets/www/response.html +++ b/assets/www/response.html @@ -268,7 +268,7 @@

Streamed Multipart Responses

@HttpEngine.route("/multipart", "GET") -def multipart_callback(http_ctx, _): +def multipart_handler(http_ctx, _): http_ctx.set_response_header(b"transfer-encoding", b"chunked") part_count = int(http_ctx.headers.get("x-part-count", 1)) part_size = int(http_ctx.headers.get("x-part-size", 1024)) diff --git a/dist/pyrobusta/assets/www/examples.html b/dist/pyrobusta/assets/www/examples.html index 99e0481..fd5d501 100644 --- a/dist/pyrobusta/assets/www/examples.html +++ b/dist/pyrobusta/assets/www/examples.html @@ -70,7 +70,7 @@

Demo Application

  • /version returns the version of the application.
    Optionally, the server version is also included in the response if the 'detailed' query parameter is set to true.
  • -
  • /app/version or /server/version returns the designated version string, handled by a single endpoint definition with a wildcard URL. +
  • /app/version or /server/version returns the designated version string, handled by a single route handler with a wildcard URL.