Expand description

§Nanooctopus
Nanooctopus is a small async HTTP server crate aimed primarily at no_std and embedded targets.
It is designed for environments such as RP2040-class MCUs with WiFi or other networking capabilities, while still being able to run on common desktop targets through a Tokio backend.
The project is server-focused. Its core idea is to keep the HTTP layer platform-agnostic and move platform-specific networking into socket abstractions. That lets the same handler code run in embedded firmware, host-side tests, and local emulators while keeping the architecture close to Embassy-style systems.
§Key Ideas
- Embedded-first HTTP server for
no_stdenvironments - Platform-agnostic socket abstraction for portable handlers and host-side testing
- Embassy-oriented design with a concrete Tokio backend for desktop development
- Worker-based request processing so multiple server tasks can handle requests concurrently
- Socket-pool support for keeping more TCP connections open than there are request workers
- Streaming response builder that writes directly to the socket
- Optional WebSocket upgrade support behind the
wsfeature - No heap requirement on embedded targets for the core request/response flow
§What Problem It Solves
Modern browsers and network clients often open several connections in advance. On small MCUs, it is usually too expensive to dedicate a full request worker to every open TCP socket.
Nanooctopus separates these concerns:
- the socket layer can keep several connections alive
- the worker layer can stay relatively small
- the HTTP handler remains independent from the underlying runtime
That makes it practical to serve HTTP on constrained devices without forcing the whole system into a one-socket-per-worker design.
§Current Scope
Nanooctopus currently provides:
- an HTTP server
- request parsing
- a staged response builder for streaming replies
- an Embassy socket-pool backend
- a Tokio backend for desktop and host-side runs
- optional WebSocket upgrade handling
The crate does not currently document or position itself as an HTTP client library.
§Installation
§Embedded / Embassy
[dependencies]
nanooctopus = { version = "0.2.0", default-features = false, features = ["embassy_impl"] }§Embedded / Embassy with WebSockets and defmt
[dependencies]
nanooctopus = { version = "0.2.0", default-features = false, features = ["embassy_impl", "ws", "defmt"] }§Desktop / Host Testing with Tokio
[dependencies]
nanooctopus = { version = "0.2.0", features = ["tokio_impl", "log"] }§Feature Flags
embassy_impl: enables the Embassy networking backendtokio_impl: enables the Tokio backend andstdsupportws: enables WebSocket upgrade and frame handling supportdefmt: enables embedded logging withdefmtand is intended for Embassy buildslog: enables logging for host-side and Tokio-based buildsproto-ipv6: forwards IPv6 support to the Embassy/socket stackstd: enabled automatically bytokio_impl; usually not needed directly
embassy_impl and tokio_impl are mutually exclusive.
§Architecture
At a high level, Nanooctopus is split into three layers:
§1. Socket backend
The server depends on socket traits rather than directly on Embassy or Tokio types. This is what makes the crate portable across embedded and desktop runtimes.
§2. HTTP server core
The core server:
- accepts a connection from a listener or socket pool
- parses the incoming HTTP request into
HttpRequest - invokes your
HttpHandler - streams the response back through
HttpResponseBuilder
§3. Worker memory
Each worker receives its own scratch buffer through HttpWorkerMemory.
That memory is used for request parsing and related temporary data.
You choose the size based on your request shape and device constraints.
§Concurrency Model
Nanooctopus is intended to run with multiple worker tasks.
Each worker calls HttpServer::serve(...) with its own HttpWorkerMemory and context id.
In the Embassy-oriented setup, a socket pool can keep more sockets available than the number of workers actively processing requests. This is useful for browsers that open several TCP connections before they are all needed.
Typical pattern:
- one socket-pool runner task manages TCP sockets
- several HTTP worker tasks process requests
- each worker has its own parsing memory
- all workers share the same server instance
§Basic Tokio Example
The Tokio example is the fastest way to understand the current API.
use nanooctopus::*;
struct HelloWorldHandler;
#[cfg(feature = "tokio_impl")]
impl http_handler::HttpHandler for HelloWorldHandler {
async fn handle_request(
&mut self,
_allocator: &mut http_handler::HttpAllocator<'_>, // unused in this simple handler
request: &http_handler::HttpRequest<'_>,
http_socket: &mut impl http_handler::HttpSocketWrite,
context_id: usize,
) -> Result<http_handler::HttpResponse, http_handler::Error> {
// Stream the response directly to the socket: status → headers → body.
http_handler::HttpResponseBuilder::new(http_socket)
.with_status(http_handler::StatusCode::Ok)
.await?
.with_header("Content-Type", "text/plain")
.await?
.with_body_from_slice(b"Hello, World!")
.await
}
}
#[tokio::main(flavor = "local")]
async fn main() {
// `spawn_local` keeps everything on the current thread, matching the
// single-threaded (`flavor = "local"`) Tokio runtime used here.
#[cfg(feature = "tokio_impl")]
tokio::task::spawn_local(async move {
// Bind the TCP listener to localhost:8080.
let listener =
server::socket_listener::TokioTcpListener::new(server::SocketEndpoint::new([127, 0, 0, 1].into(), 8080))
.await;
let server = server::HttpServer::new(listener, server::ServerTimeouts::default());
// 1024-byte scratch buffer for parsing incoming HTTP headers.
// A single worker (context_id = 1) handles requests sequentially.
server
.serve(server::HttpWorkerMemory::<1024>::new(), HelloWorldHandler, 1)
.await
})
.await
.unwrap();
}This example exists in:
§Embassy / RP2040 Example
The Raspberry Pico W example shows the intended embedded deployment model:
- initialize CYW43 WiFi
- bring up Embassy networking
- create a TCP socket pool
- spawn the socket-pool runner
- spawn several HTTP server workers
The example is in:
The current example uses these fixed-size resources:
SOCKETS = 5HTTP_SERVER_WORKERS = 2WORKER_MEMORY = 4096
That setup demonstrates the main idea of the crate: a device may keep more sockets open than the number of HTTP workers actively serving requests.
§Writing a Handler
Request handling is done by implementing http_handler::HttpHandler.
The important method is:
async fn handle_request(
&mut self,
allocator: &mut http_handler::HttpAllocator<'_>,
request: &http_handler::HttpRequest<'_>,
http_socket: &mut impl http_handler::HttpSocketWrite,
context_id: usize,
) -> Result<http_handler::HttpResponse, http_handler::Error>Handler inputs:
allocator: scratch allocator for request-scoped temporary datarequest: parsed HTTP requesthttp_socket: response sink used byHttpResponseBuildercontext_id: worker id, useful for diagnostics and per-worker behavior
The parsed request currently exposes:
methodpathversionheadersbody
With the ws feature enabled, WebSocket upgrade information is also recognized and routed through handle_websocket_connection.
§Building Responses
Responses are streamed in stages. A typical flow is:
http_handler::HttpResponseBuilder::new(http_socket)
.with_status(http_handler::StatusCode::Ok)
.await?
.with_header("Content-Type", "text/plain; charset=utf-8")
.await?
.with_body_from_str("hello")
.awaitThe response builder supports:
- status line construction
- incremental header writing
- fixed-size body writing from
&stror&[u8] - chunked transfer encoding
- convenience helpers for plain text, HTML, compressed pages, and preflight responses
§Timeouts
The server exposes:
let timeouts = server::ServerTimeouts::new(read_timeout_secs, handler_timeout_secs);Current defaults are:
- read timeout:
30s - handler timeout:
60s
§WebSocket Support
When the ws feature is enabled, Nanooctopus can detect WebSocket upgrade requests and hand the connection to:
handle_websocket_connection(...)If you do not implement that method, incoming WebSocket connections are closed by default.
§Limitations
- The library is currently centered on the HTTP server.
- Server-side TLS termination is not provided by Nanooctopus itself.
- On embedded targets, you are responsible for sizing socket buffers, worker count, and worker memory according to your traffic profile.
embassy_implandtokio_implcannot be enabled together.
§Demos
demos/rasberry_pico_w: Embassy + CYW43 + RP2040 + socket pool + multiple workersdemos/tokio_hello_world: minimal host-side Tokio server
§Development Notes
Nanooctopus is structured to make embedded development less painful:
- the Tokio backend helps run handlers on a normal OS during development
- the platform abstraction helps with host-side tests and emulators
- the Embassy-oriented architecture stays close to the intended MCU deployment model
If you want to understand the current project state, start with the demos rather than older historical documentation.
§License
Modules§
- http_
handler - This module re-exports the main HTTP handling traits and types for easier access by users of the library.
- server
- HTTP server implementation and related types.
- socket
- This module contains the implementation of the abstract socket traits and utilities, providing a common interface for socket operations.