Skip to content

Streaming ​

Sword expone dos formas de streaming: StreamRequest, para trabajar con el body de la request como flujo sin bufferizarlo en memoria, y Server-Sent Events (SSE), para enviar respuestas en tiempo real desde el servidor al cliente.

StreamRequest ​

StreamRequest es la variante de Request para cuando no quieres bufferizar el body completo en memoria. Mantiene el body como flujo de Axum y te deja decidir cuándo y cómo consumirlo.

  • Usa Request cuando quieras un acceso ergonómico a body, query, params, cookies y helpers de extracción.
  • Usa StreamRequest cuando necesites trabajar con el body como flujo y evitar su carga completa en memoria.

Interceptores para streaming ​

Cuando el handler recibe StreamRequest en lugar de Request, el interceptor web debe implementar el trait de streaming correspondiente.

El Trait OnRequestStream ​

rust
use sword::prelude::*;
use sword::web::*;

#[derive(Interceptor)]
struct StreamTagInterceptor;

impl OnRequestStream for StreamTagInterceptor {
    async fn on_request(&self, mut req: StreamRequest) -> WebInterceptorResult {
        req.extensions.insert("stream-ok".to_string());
        req.next().await
    }
}

El Trait OnRequestStreamWithConfig ​

rust
use sword::prelude::*;
use sword::web::*;

#[derive(Interceptor)]
struct StreamConfigInterceptor;

impl OnRequestStreamWithConfig<&'static str> for StreamConfigInterceptor {
    async fn on_request(
        &self,
        config: &'static str,
        mut req: StreamRequest,
    ) -> WebInterceptorResult {
        req.extensions.insert(config.to_string());
        req.next().await
    }
}

Nota importante sobre StreamRequest

Las rutas que usan StreamRequest no pueden combinarse con interceptores Sword definidos a nivel de controlador. En ese caso, aplica el interceptor directamente sobre la ruta.

Server-Sent Events (SSE) ​

Server-Sent Events (SSE) es una tecnología de push del servidor que permite a un cliente recibir actualizaciones automáticas desde el servidor a través de una conexión HTTP.

En Sword se declara con el atributo #[sse]. El handler devuelve un Sse que envuelve un flujo de eventos, y la ruta se sirve por GET con content-type text/event-stream.

rust
use async_stream::stream;
use std::time::Duration;
use tokio::time::sleep;

use sword::prelude::*;
use sword::web::*;

#[controller(kind = Controller::Web, path = "/sse")]
struct SseController;

impl SseController {
    #[sse("/countdown")]
    async fn countdown(&self) -> Sse<impl EventStream + use<>> {
        let events = stream! {
            for i in (1..=5).rev() {
                sleep(Duration::from_millis(250)).await;
                yield Ok(Event::default().event("countdown").data(i.to_string()));
            }

            yield Ok(Event::default().event("done").data("The countdown has finished!"));
        };

        Sse::new(events)
    }
}

El flujo se construye con el macro stream!: cada yield produce un evento que se envía al cliente. Event::default() crea un evento, .event(name) define su tipo y .data(value) su contenido.

El tipo de retorno DEBE ser Sse<impl EventStream + use<>> el cual evita tener que nombrar el tipo concreto del flujo.

En conexiones de larga duración, .keep_alive(KeepAlive::default()) mantiene viva la conexión enviando comentarios periódicos.

Nota sobre request-timeout

Las conexiones SSE son long-lived. Si request-timeout está habilitado en la configuración, la layer de timeout global terminará el flujo.