-
Notifications
You must be signed in to change notification settings - Fork 369
Expose streaming API #1013
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Expose streaming API #1013
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
197206c
Expose streaming API
s0l0ist 55ab900
fmt
s0l0ist 1c39e2e
silence clippy
s0l0ist 2ef3e0a
Merge branch 'main' into nangelou-expose-streaming-api
s0l0ist 2f44c5a
udpate
s0l0ist 024876e
update
s0l0ist ccd3585
wip
s0l0ist 42fc6fd
StreamAdapter
s0l0ist 035c0b0
use futures_util::Stream
s0l0ist 1cdeb92
wip
s0l0ist ce636ac
update
s0l0ist e87ad74
update
s0l0ist f45ca50
update
s0l0ist 5c23616
add axum streaming example
s0l0ist ff39706
Update examples
s0l0ist c475254
remove
s0l0ist 34b12e3
Merge branch 'main' into nangelou-expose-streaming-api
s0l0ist 1ce5c3e
add back
s0l0ist b39ab05
update
s0l0ist e3e8de7
update docs
s0l0ist 45f01e7
update
s0l0ist File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
[package] | ||
name = "http-axum-streaming-otel" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
axum = "0.8" | ||
bytes = "1" | ||
lambda_http = { path = "../../lambda-http", default-features = false, features = [ | ||
"apigw_http", "tracing", "opentelemetry" | ||
] } | ||
opentelemetry = "0.30" | ||
opentelemetry_sdk = { version = "0.30", features = ["rt-tokio"] } | ||
opentelemetry-stdout = { version = "0.30", features = ["trace"] } | ||
thiserror = "2.0" | ||
tokio = { version = "1", features = ["macros"] } | ||
tokio-stream = "0.1.2" | ||
tracing = "0.1" | ||
tracing-opentelemetry = "0.31" | ||
tracing-subscriber = "0.3" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# AWS Lambda Function example | ||
|
||
This example shows how to build a **streaming HTTP response** with `Axum` and | ||
run it on AWS Lambda using a custom runtime with OpenTelemetry (OTel) support. | ||
|
||
Tracing data is exported as console log entries visible in CloudWatch. Note that | ||
CloudWatch assigns a `Timestamp` to each log entry based on when it receives the | ||
data (batch exported). To see when work actually occurred, look at the span's | ||
event attributes, which include the precise local timestamps of those events. | ||
|
||
## Build & Deploy | ||
|
||
1. Install | ||
[cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) | ||
2. Build the function with `cargo lambda build --release` | ||
3. Deploy the function to AWS Lambda with: | ||
- `cargo lambda deploy --enable-function-url --iam-role YOUR_ROLE` to stream words | ||
4. Enable Lambda streaming response on Lambda console: change the function url's | ||
invoke mode to `RESPONSE_STREAM` | ||
5. Verify the function works: `curl -N <function-url>`. The results should be | ||
streamed back with 0.5 second pause between each word. | ||
|
||
## Build for ARM 64 | ||
|
||
Build the function with `cargo lambda build --release --arm64` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
//! # Example: Axum Streaming Responses on AWS Lambda with OTel | ||
//! | ||
//! Demonstrates serving **incremental streaming responses** from Axum handlers | ||
//! running in AWS Lambda using a **custom** `lambda_runtime::Runtime` with | ||
//! OpenTelemetry (OTel) support. | ||
//! | ||
//! - Runs with a custom `Runtime` + `StreamAdapter`, which convert Axum | ||
//! responses into streaming bodies delivered as data is produced (unlike the | ||
//! default `run_with_streaming_response` helper). | ||
|
||
use axum::{ | ||
body::Body, | ||
http::{ | ||
self, | ||
header::{CACHE_CONTROL, CONTENT_TYPE}, | ||
StatusCode, | ||
}, | ||
response::{IntoResponse, Response}, | ||
routing::get, | ||
Router, | ||
}; | ||
use bytes::Bytes; | ||
use core::{convert::Infallible, time::Duration}; | ||
use lambda_http::{ | ||
lambda_runtime::{ | ||
layers::{OpenTelemetryFaasTrigger, OpenTelemetryLayer as OtelLayer}, | ||
tracing::Instrument, | ||
Runtime, | ||
}, | ||
tracing, Error, StreamAdapter, | ||
}; | ||
use opentelemetry::trace::TracerProvider; | ||
use opentelemetry_sdk::trace; | ||
use thiserror::Error; | ||
use tokio::sync::mpsc; | ||
use tokio_stream::wrappers::ReceiverStream; | ||
use tracing_subscriber::prelude::*; | ||
|
||
#[derive(Debug, Error)] | ||
pub enum AppError { | ||
#[error("{0}")] | ||
Http(#[from] http::Error), | ||
} | ||
|
||
impl IntoResponse for AppError { | ||
fn into_response(self) -> Response { | ||
(StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response() | ||
} | ||
} | ||
|
||
#[tracing::instrument(skip_all)] | ||
async fn stream_words() -> Result<Response, AppError> { | ||
let (tx, rx) = mpsc::channel::<Result<Bytes, Infallible>>(8); | ||
let body = Body::from_stream(ReceiverStream::new(rx)); | ||
|
||
tokio::spawn( | ||
async move { | ||
for (idx, msg) in ["Hello", "world", "from", "Lambda!"].iter().enumerate() { | ||
tokio::time::sleep(Duration::from_millis(500)).await; | ||
let line = format!("{msg}\n"); | ||
tracing::info!(chunk.idx = idx, bytes = line.len(), "emit"); | ||
if tx.send(Ok(Bytes::from(line))).await.is_err() { | ||
break; | ||
} | ||
} | ||
} | ||
.instrument(tracing::info_span!("producer.stream_words")), | ||
); | ||
|
||
Ok(Response::builder() | ||
.status(StatusCode::OK) | ||
.header(CONTENT_TYPE, "text/plain; charset=utf-8") | ||
.header(CACHE_CONTROL, "no-cache") | ||
.body(body)?) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), Error> { | ||
// Set up OpenTelemetry tracer provider that writes spans to stdout for | ||
// debugging purposes | ||
let exporter = opentelemetry_stdout::SpanExporter::default(); | ||
let tracer_provider = trace::SdkTracerProvider::builder() | ||
.with_batch_exporter(exporter) | ||
.build(); | ||
|
||
// Set up link between OpenTelemetry and tracing crate | ||
tracing_subscriber::registry() | ||
.with(tracing_opentelemetry::OpenTelemetryLayer::new( | ||
tracer_provider.tracer("my-streaming-app"), | ||
)) | ||
.init(); | ||
|
||
let svc = Router::new().route("/", get(stream_words)); | ||
|
||
// Initialize the Lambda runtime and add OpenTelemetry tracing | ||
let runtime = Runtime::new(StreamAdapter::from(svc)).layer( | ||
OtelLayer::new(|| { | ||
if let Err(err) = tracer_provider.force_flush() { | ||
eprintln!("Error flushing traces: {err:#?}"); | ||
} | ||
}) | ||
.with_trigger(OpenTelemetryFaasTrigger::Http), | ||
); | ||
|
||
runtime.run().await | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "http-axum-streaming" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
axum = "0.8" | ||
bytes = "1" | ||
lambda_http = { path = "../../lambda-http", default-features = false, features = [ | ||
"apigw_http", "tracing" | ||
] } | ||
thiserror = "2.0" | ||
tokio = { version = "1", features = ["macros"] } | ||
tokio-stream = "0.1.2" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# AWS Lambda Function example | ||
|
||
This example demonstrates building a **streaming** HTTP response with Axum, | ||
deployed on AWS Lambda using a custom runtime. | ||
|
||
## Build & Deploy | ||
|
||
1. Install | ||
[cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) | ||
2. Build the function with `cargo lambda build --release` | ||
3. Deploy the function to AWS Lambda with: | ||
- `cargo lambda deploy --enable-function-url --iam-role YOUR_ROLE` to stream words | ||
4. Enable Lambda streaming response on Lambda console: change the function url's | ||
invoke mode to `RESPONSE_STREAM` | ||
5. Verify the function works: `curl -N <function-url>`. The results should be | ||
streamed back with 0.5 second pause between each word. | ||
|
||
## Build for ARM 64 | ||
|
||
Build the function with `cargo lambda build --release --arm64` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
//! # Example: Axum Streaming Responses on AWS Lambda | ||
//! | ||
//! Demonstrates serving **incremental streaming responses** from Axum handlers | ||
//! running in AWS Lambda. | ||
//! | ||
//! - Runs with `run_with_streaming_response`, which uses the **default Lambda | ||
//! runtime** to convert Axum responses into streaming bodies delivered as | ||
//! data is produced (unlike the OTel example, which used a custom `Runtime` + | ||
//! `StreamAdapter`). | ||
|
||
use axum::{ | ||
body::Body, | ||
http::{ | ||
self, | ||
header::{CACHE_CONTROL, CONTENT_TYPE}, | ||
StatusCode, | ||
}, | ||
response::{IntoResponse, Response}, | ||
routing::get, | ||
Router, | ||
}; | ||
use bytes::Bytes; | ||
use core::{convert::Infallible, time::Duration}; | ||
use lambda_http::{run_with_streaming_response, tracing, Error}; | ||
use thiserror::Error; | ||
use tokio::sync::mpsc; | ||
use tokio_stream::wrappers::ReceiverStream; | ||
|
||
#[derive(Debug, Error)] | ||
pub enum AppError { | ||
#[error("{0}")] | ||
Http(#[from] http::Error), | ||
} | ||
|
||
impl IntoResponse for AppError { | ||
fn into_response(self) -> Response { | ||
(StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response() | ||
} | ||
} | ||
|
||
async fn stream_words() -> Result<Response, AppError> { | ||
let (tx, rx) = mpsc::channel::<Result<Bytes, Infallible>>(8); | ||
let body = Body::from_stream(ReceiverStream::new(rx)); | ||
|
||
tokio::spawn(async move { | ||
for msg in ["Hello", "world", "from", "Lambda!"] { | ||
tokio::time::sleep(Duration::from_millis(500)).await; | ||
if tx.send(Ok(Bytes::from(format!("{msg}\n")))).await.is_err() { | ||
break; | ||
} | ||
} | ||
}); | ||
|
||
Ok(Response::builder() | ||
.status(StatusCode::OK) | ||
.header(CONTENT_TYPE, "text/plain; charset=utf-8") | ||
.header(CACHE_CONTROL, "no-cache") | ||
.body(body)?) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), Error> { | ||
tracing::init_default_subscriber(); | ||
|
||
let svc = Router::new().route("/", get(stream_words)); | ||
|
||
// Automatically convert the service into a streaming response with a | ||
// default runtime. | ||
run_with_streaming_response(svc).await | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.