Skip to main content

Server

Struct Server 

Source
pub struct Server<const P: usize = DEFAULT_HANDLER_TASKS_COUNT, const B: usize = DEFAULT_BUF_SIZE, const N: usize = DEFAULT_MAX_HEADERS_COUNT>(/* private fields */);
Expand description

An HTTP server that can handle multiple requests concurrently.

The server needs an implementation of edge_nal::TcpAccept to accept incoming connections.

Implementations§

Source§

impl<const P: usize, const B: usize, const N: usize> Server<P, B, N>

Source

pub const fn new() -> Self

Create a new HTTP server

Source

pub async fn run<A, H>( &mut self, keepalive_timeout_ms: Option<u32>, acceptor: A, handler: H, ) -> Result<(), Error<A::Error>>
where A: TcpAccept, H: Handler,

Run the server with the specified acceptor and handler

A note on timeouts:

  • The function does NOT - by default - establish any timeouts on the IO operations except an optional timeout on idle connections, so that they can be closed. It is up to the caller to wrap the acceptor type with edge_nal::WithTimeout to establish timeouts on the socket produced by the acceptor.
  • Similarly, the function does NOT establish any timeouts on the complete request-response cycle. It is up to the caller to wrap their complete or partial handling logic with edge_nal::with_timeout, or its whole handler with edge_nal::WithTimeout, so as to establish a global or semi-global request-response timeout.

A note on concurrent connection acceptance:

  • All handler tasks concurrently call accept() to maximize connection acceptance capacity.
  • This is critical for TCP stacks without accept queues (e.g., smoltcp/embassy-net), where incoming connections can only be accepted if at least one task is actively waiting in accept().
  • When a task is busy handling a request, other tasks remain available to accept new connections.
  • Threading safety: The acceptor must be safe to use from multiple async tasks. For single-threaded async executors (e.g., embassy-executor on embedded platforms), acceptors using non-Sync types like Cell for internal state are safe. For multi-threaded executors, the acceptor’s internal state must be properly synchronized (e.g., using atomics or locks).

Consider using run_with_socket_queue() instead for better connection handling with TCP stacks that lack accept queues (e.g., smoltcp/embassy-net). The socket queue architecture decouples connection acceptance from HTTP processing, allowing connections to be accepted even when all worker tasks are busy.

Parameters:

  • keepalive_timeout_ms: An optional timeout in milliseconds for detecting an idle keepalive connection that should be closed. If not provided, the function will not close idle connections and the connection - in the absence of other timeouts - will remain active forever.
  • acceptor: An implementation of edge_nal::TcpAccept to accept incoming connections
  • handler: An implementation of Handler to handle incoming requests If not provided, a default timeout of 50 seconds is used.
Source

pub async fn run_with_socket_queue<A, H, const Q: usize>( &mut self, keepalive_timeout_ms: Option<u32>, acceptor: A, handler: H, ) -> Result<(), Error<A::Error>>
where A: TcpAccept, H: Handler,

Run the server with a socket queue architecture (recommended for smoltcp/embassy-net)

This method addresses the limitation of TCP stacks without accept queues (e.g., smoltcp/embassy-net) by using a signal-based coordination mechanism between acceptor and worker tasks. This ensures that the number of sockets in use never exceeds the available socket pool size, preventing resource exhaustion.

§Type Parameters
  • Q: Number of acceptor tasks and maximum sockets that can be allocated simultaneously (default: 8)
§Important Constraints

CRITICAL: The Q parameter must satisfy these constraints or the function will panic:

  • Q must be less than or equal to the number of sockets in your smoltcp/embassy-net socket pool
    • If Q exceeds the socket pool size, accept() calls will fail and cause panics
  • Q should be greater than or equal to P for the architecture to provide benefits
    • If Q < P, you lose the advantage of decoupling acceptance from processing
    • Recommended: Q >= P (e.g., Q=8 with P=4)
§Timeout Configuration

The function does NOT establish timeouts by default (except optional keepalive timeout):

  • Wrap the acceptor with edge_nal::WithTimeout to set socket-level timeouts
  • Wrap handler logic with edge_nal::with_timeout for request-response timeouts
§Parameters
  • keepalive_timeout_ms: Optional timeout in milliseconds for idle keepalive connections
  • acceptor: An implementation of edge_nal::TcpAccept to accept incoming connections
  • handler: An implementation of Handler to handle incoming requests

Trait Implementations§

Source§

impl<const P: usize, const B: usize, const N: usize> Default for Server<P, B, N>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<const P: usize, const B: usize, const N: usize> Freeze for Server<P, B, N>

§

impl<const P: usize, const B: usize, const N: usize> RefUnwindSafe for Server<P, B, N>

§

impl<const P: usize, const B: usize, const N: usize> Send for Server<P, B, N>

§

impl<const P: usize, const B: usize, const N: usize> Sync for Server<P, B, N>

§

impl<const P: usize, const B: usize, const N: usize> Unpin for Server<P, B, N>

§

impl<const P: usize, const B: usize, const N: usize> UnsafeUnpin for Server<P, B, N>

§

impl<const P: usize, const B: usize, const N: usize> UnwindSafe for Server<P, B, N>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.