From fd3416ee0686493848054dff3df2725a837895a3 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Wed, 7 Jan 2026 22:48:11 +0000 Subject: [PATCH 1/7] feat: support Lambda Managed Instance --- Cargo.lock | 16 +-- Cargo.toml | 3 +- README.md | 26 +++- .../fastapi-response-streaming-lmi/.gitignore | 1 + .../fastapi-response-streaming-lmi/README.md | 121 ++++++++++++++++++ .../__init__.py | 0 .../app/__init__.py | 0 .../app/main.py | 28 ++++ .../app/requirements.txt | 2 + .../fastapi-response-streaming-lmi/app/run.sh | 5 + .../template.yaml | 61 +++++++++ src/lib.rs | 68 ++++++++-- 12 files changed, 304 insertions(+), 27 deletions(-) create mode 100644 examples/fastapi-response-streaming-lmi/.gitignore create mode 100644 examples/fastapi-response-streaming-lmi/README.md create mode 100644 examples/fastapi-response-streaming-lmi/__init__.py create mode 100644 examples/fastapi-response-streaming-lmi/app/__init__.py create mode 100644 examples/fastapi-response-streaming-lmi/app/main.py create mode 100644 examples/fastapi-response-streaming-lmi/app/requirements.txt create mode 100755 examples/fastapi-response-streaming-lmi/app/run.sh create mode 100644 examples/fastapi-response-streaming-lmi/template.yaml diff --git a/Cargo.lock b/Cargo.lock index bfdb4764..941d2b45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -260,9 +260,9 @@ dependencies = [ [[package]] name = "aws_lambda_events" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac59c9b189a3bd75146633e1c87f35fb727f53b03c7f331af789ff1293a9f350" +checksum = "ca106ceeb46420f22b33b863f8a667214afbf6b0457bc209f8c97de2282bedae" dependencies = [ "base64", "bytes", @@ -1234,9 +1234,9 @@ dependencies = [ [[package]] name = "lambda_http" -version = "1.0.1" +version = "1.1.0-rc1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a106755a9079a3ed20b4262e32c9a5efa127c97546cb2ecf69bbf3d17dbf970b" +checksum = "12ca353a921b753e2a464cf3286381a0be7cdb6cc6e685daa2fdc5b95a6f4402" dependencies = [ "aws_lambda_events", "bytes", @@ -1259,9 +1259,9 @@ dependencies = [ [[package]] name = "lambda_runtime" -version = "1.0.1" +version = "1.1.0-rc1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e566d19773ae483b0d32cc6045c5420d7b3eec6192ecb1c26ffa4e0091388a" +checksum = "3ef345d40ef6f1bcfb051a06cee05ed3cf8815ef32552610b8327b9675788615" dependencies = [ "async-stream", "base64", @@ -1284,9 +1284,9 @@ dependencies = [ [[package]] name = "lambda_runtime_api_client" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c039f06329949692a81a993ede4cfaa6abab3ac8e72590cf7c5e6a64a9ec7b1" +checksum = "7b4873061514cb57ffb6a599b77c46c65d6d783efe9bad8fd56b7cba7f0459ef" dependencies = [ "bytes", "futures-channel", diff --git a/Cargo.toml b/Cargo.toml index e1cdf189..146a1b64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,12 +22,13 @@ http = "1.2.0" http-body = "1.0.1" hyper = { version = "1.5.2", features = ["client"] } hyper-util = "0.1.10" -lambda_http = { version = "1.0.1", default-features = false, features = [ +lambda_http = { version = "1.1.0-rc1", default-features = false, features = [ "apigw_http", "apigw_rest", "alb", "pass_through", "tracing", + "experimental-concurrency" ] } serde_json = "1.0.135" tokio = { version = "1.48.0", features = [ diff --git a/README.md b/README.md index 63d19e39..30b89842 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ The same docker image can run on AWS Lambda, Amazon EC2, AWS Fargate, and local - Run web applications on AWS Lambda - Supports Amazon API Gateway Rest API and Http API endpoints, Lambda Function URLs, and Application Load Balancer - Supports Lambda managed runtimes, custom runtimes and docker OCI images +- Supports Lambda Managed Instances for multi-concurrent request handling - Supports any web frameworks and languages, no new code dependency to include - Automatic encode binary response - Enables graceful shutdown @@ -95,14 +96,13 @@ The readiness check port/path and traffic port can be configured using environme | Environment Variable | Description | Default | |--------------------------------------------------------------|--------------------------------------------------------------------------------------|------------| | AWS_LWA_PORT | traffic port (falls back to `PORT`) | "8080" | -| AWS_LWA_HOST | traffic host | "127.0.0.1"| | AWS_LWA_READINESS_CHECK_PORT | readiness check port, default to the traffic port | AWS_LWA_PORT | | AWS_LWA_READINESS_CHECK_PATH | readiness check path | "/" | | AWS_LWA_READINESS_CHECK_PROTOCOL | readiness check protocol: "http" or "tcp", default is "http" | "http" | | AWS_LWA_READINESS_CHECK_HEALTHY_STATUS | HTTP status codes considered healthy (e.g., "200-399" or "200,201,204,301-399") | "100-499" | | AWS_LWA_ASYNC_INIT | enable asynchronous initialization for long initialization functions | "false" | | AWS_LWA_REMOVE_BASE_PATH | the base path to be removed from request path | None | -| AWS_LWA_ENABLE_COMPRESSION | enable gzip compression for response body | "false" | +| AWS_LWA_ENABLE_COMPRESSION | enable gzip/br compression for response body (buffered mode only) | "false" | | AWS_LWA_INVOKE_MODE | Lambda function invoke mode: "buffered" or "response_stream", default is "buffered" | "buffered" | | AWS_LWA_PASS_THROUGH_PATH | the path for receiving event payloads that are passed through from non-http triggers | "/events" | | AWS_LWA_AUTHORIZATION_SOURCE | a header name to be replaced to `Authorization` | None | @@ -131,8 +131,8 @@ For example, you could have configured your API Gateway to have a /orders/{proxy Each resource is handled by a separate Lambda functions. For this reason, the application inside Lambda may not be aware of the fact that the /orders path exists. Use AWS_LWA_REMOVE_BASE_PATH to remove the /orders prefix when routing requests to the application. Defaults to empty string. Checkout [SpringBoot](examples/springboot) example. -**AWS_LWA_ENABLE_COMPRESSION** - Lambda Web Adapter supports gzip compression for response body. This feature is disabled by default. Enable it by setting environment variable `AWS_LWA_ENABLE_COMPRESSION` to `true`. -When enabled, this will compress responses unless it's an image as determined by the content-type starting with `image` or the response is less than 32 bytes. This will also compress HTTP/1.1 chunked streaming response. +**AWS_LWA_ENABLE_COMPRESSION** - Lambda Web Adapter supports gzip/br compression for response body. This feature is disabled by default. Enable it by setting environment variable `AWS_LWA_ENABLE_COMPRESSION` to `true`. +When enabled, this will compress responses unless it's an image as determined by the content-type starting with `image` or the response is less than 32 bytes. Compression is not supported with response streaming (`AWS_LWA_INVOKE_MODE=response_stream`). If both are enabled, compression will be automatically disabled with a warning. **AWS_LWA_INVOKE_MODE** - Lambda function invoke mode, this should match Function Url invoke mode. The default is "buffered". When configured as "response_stream", Lambda Web Adapter will stream response to Lambda service [blog](https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/). Please check out [FastAPI with Response Streaming](examples/fastapi-response-streaming) example. @@ -172,6 +172,23 @@ Lambda Web Adapter forwards this information to the web application in a Http He Lambda Web Adapter forwards this information to the web application in a Http Header named "x-amzn-lambda-context". In the web application, you can retrieve the value of this http header and deserialize it into a JSON object. Check out [Express.js in Zip](examples/expressjs-zip) on how to use it. +## Lambda Managed Instances + +Lambda Web Adapter supports [Lambda Managed Instances](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html), which allows a single Lambda execution environment to handle multiple concurrent requests. This can improve throughput and reduce costs for I/O-bound workloads. + +When running on Lambda Managed Instances, Lambda Web Adapter automatically handles concurrent invocations by forwarding multiple requests to your web application simultaneously. Since most web frameworks (Express.js, FastAPI, Spring Boot, etc.) are already designed to handle concurrent requests, your application should work without modification. + +### Considerations for Multi-Concurrency + +When using Lambda Managed Instances, keep these points in mind: + +- **Shared state**: Global variables and in-memory caches are shared across concurrent requests. Ensure your application handles shared state safely. +- **Connection pooling**: Use connection pools for databases and external services rather than single connections. +- **File system**: The `/tmp` directory is shared across concurrent requests. Use unique file names or implement file locking to avoid conflicts. +- **Resource limits**: Memory and CPU are shared across concurrent requests. Monitor resource usage under concurrent load. + +Lambda Managed Instances works with both buffered and response streaming modes. + ## Graceful Shutdown For a function with Lambda Extensions registered, Lambda enables shutdown phase for the function. When Lambda service is about to shut down a Lambda execution environment, @@ -203,6 +220,7 @@ The `AWS_LWA_LAMBDA_RUNTIME_API_PROXY` environment varible makes the Lambda Web - [FastAPI with Background Tasks](examples/fastapi-background-tasks) - [FastAPI with Response Streaming](examples/fastapi-response-streaming) - [FastAPI with Response Streaming in Zip](examples/fastapi-response-streaming-zip) +- [FastAPI with Response Streaming on Lambda Managed Instances](examples/fastapi-response-streaming-lmi) - [FastAPI Response Streaming Backend with IAM Auth](examples/fastapi-backend-only-response-streaming/) - [Flask](examples/flask) - [Flask in Zip](examples/flask-zip) diff --git a/examples/fastapi-response-streaming-lmi/.gitignore b/examples/fastapi-response-streaming-lmi/.gitignore new file mode 100644 index 00000000..9984c2e5 --- /dev/null +++ b/examples/fastapi-response-streaming-lmi/.gitignore @@ -0,0 +1 @@ +.aws-sam/ diff --git a/examples/fastapi-response-streaming-lmi/README.md b/examples/fastapi-response-streaming-lmi/README.md new file mode 100644 index 00000000..761b70e0 --- /dev/null +++ b/examples/fastapi-response-streaming-lmi/README.md @@ -0,0 +1,121 @@ +# FastAPI Response Streaming with Lambda Managed Instances + +This example shows how to use Lambda Web Adapter to run a FastAPI application with response streaming on [Lambda Managed Instances](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html) (LMI). + +Lambda Managed Instances allows a single Lambda execution environment to handle multiple concurrent requests, improving throughput and reducing costs for I/O-bound workloads like streaming responses. + +## Prerequisites + +Lambda Managed Instances requires a VPC with: +- At least one subnet (two subnets recommended) +- A security group that allows outbound traffic + +If you don't have a VPC configured, you can use the default VPC or create one. + +## How does it work? + +This example combines three Lambda features: + +1. **Lambda Web Adapter** - Runs your FastAPI app on Lambda without code changes +2. **Response Streaming** - Streams responses back to clients as they're generated +3. **Lambda Managed Instances** - Handles multiple concurrent requests per execution environment + +### Key Configuration + +```yaml +LMICapacityProvider: + Type: AWS::Lambda::CapacityProvider + Properties: + Name: !Sub "${AWS::StackName}-capacity-provider" + VpcConfig: + SubnetIds: !Ref SubnetIds + SecurityGroupIds: !Ref SecurityGroupIds + InstanceRequirements: + Architectures: + - x86_64 + AllowedTypes: + - m7i.large + ScalingConfig: + MaxVCpuCount: 2 + +FastAPIFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: app/ + Handler: run.sh + Runtime: python3.13 + MemorySize: 2048 + Environment: + Variables: + AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap + AWS_LWA_INVOKE_MODE: response_stream + PORT: 8000 + Layers: + - !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:25 + CapacityProviderConfig: + Arn: !Ref LMICapacityProvider + PerExecutionEnvironmentMaxConcurrency: 64 + FunctionUrlConfig: + AuthType: NONE + InvokeMode: RESPONSE_STREAM +``` + +- `AWS::Lambda::CapacityProvider` - Creates the LMI capacity provider with VPC configuration +- `CapacityProviderConfig.Arn` - References the capacity provider +- `CapacityProviderConfig.PerExecutionEnvironmentMaxConcurrency: 64` - Up to 64 concurrent requests per instance +- `AWS_LWA_INVOKE_MODE: response_stream` - Configures Lambda Web Adapter for streaming +- `FunctionUrlConfig.InvokeMode: RESPONSE_STREAM` - Enables streaming on the Function URL + +## Build and Deploy + +First, get your VPC subnet and security group IDs: + +```bash +# List subnets in your default VPC +aws ec2 describe-subnets --filters "Name=default-for-az,Values=true" \ + --query 'Subnets[*].[SubnetId,AvailabilityZone]' --output table + +# List security groups +aws ec2 describe-security-groups --filters "Name=group-name,Values=default" \ + --query 'SecurityGroups[*].[GroupId,GroupName]' --output table +``` + +Build and deploy: + +```bash +sam build --use-container +sam deploy --guided +``` + +During guided deployment, you'll be prompted for: +- `SubnetIds` - Comma-separated list of subnet IDs (e.g., `subnet-abc123,subnet-def456`) +- `SecurityGroupIds` - Comma-separated list of security group IDs (e.g., `sg-abc123`) + +## Verify it works + +Open the Function URL in a browser. You should see a message stream back character by character, with a unique request ID prefix like `[a1b2c3d4] This is streaming from Lambda Managed Instances!`. + +### Test concurrent requests + +To verify LMI is working, send multiple concurrent requests: + +```bash +# Get your function URL +URL=$(aws cloudformation describe-stacks --stack-name fastapi-response-streaming-lmi \ + --query 'Stacks[0].Outputs[?OutputKey==`FastAPIFunctionUrl`].OutputValue' --output text) + +# Send 10 concurrent requests +for i in {1..10}; do curl -s "$URL" & done; wait +``` + +Each response will have a different request ID, but they may share the same execution environment (visible in CloudWatch logs). + +## Considerations + +When using LMI with streaming: + +- **VPC**: LMI requires VPC configuration. Ensure your subnets have internet access (via NAT Gateway) if your function needs to call external services +- **Shared state**: FastAPI/Uvicorn handles concurrency natively, but avoid mutable global state +- **Memory**: With 64 concurrent requests, ensure sufficient memory (2048MB in this example) +- **Timeouts**: Streaming responses can run up to 15 minutes with Function URLs +- **Scaling**: `MaxInstanceCount` controls the maximum number of EC2 instances in the capacity provider diff --git a/examples/fastapi-response-streaming-lmi/__init__.py b/examples/fastapi-response-streaming-lmi/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/fastapi-response-streaming-lmi/app/__init__.py b/examples/fastapi-response-streaming-lmi/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/fastapi-response-streaming-lmi/app/main.py b/examples/fastapi-response-streaming-lmi/app/main.py new file mode 100644 index 00000000..b8d1d0b0 --- /dev/null +++ b/examples/fastapi-response-streaming-lmi/app/main.py @@ -0,0 +1,28 @@ +from fastapi import FastAPI +from fastapi.responses import StreamingResponse +import asyncio +import uuid + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +app = FastAPI() + +@app.get("/health") +async def health(): + return {"status": "healthy"} + + +async def streamer(request_id: str): + """Stream a message character by character with request ID for tracing.""" + message = f"[{request_id}] This is streaming from Lambda Managed Instances!\n" + for char in message: + yield char + await asyncio.sleep(0.05) + + +@app.get("/{path:path}") +async def index(path: str, request: Request): + """Stream response - each concurrent request gets a unique ID.""" + request_id = str(uuid.uuid4())[:8] + return StreamingResponse(streamer(request_id), media_type="text/plain") diff --git a/examples/fastapi-response-streaming-lmi/app/requirements.txt b/examples/fastapi-response-streaming-lmi/app/requirements.txt new file mode 100644 index 00000000..87c06ac2 --- /dev/null +++ b/examples/fastapi-response-streaming-lmi/app/requirements.txt @@ -0,0 +1,2 @@ +fastapi==0.115.5 +uvicorn==0.32.0 diff --git a/examples/fastapi-response-streaming-lmi/app/run.sh b/examples/fastapi-response-streaming-lmi/app/run.sh new file mode 100755 index 00000000..a630f442 --- /dev/null +++ b/examples/fastapi-response-streaming-lmi/app/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +PATH=$PATH:$LAMBDA_TASK_ROOT/bin \ + PYTHONPATH=$PYTHONPATH:/opt/python:$LAMBDA_RUNTIME_DIR \ + exec python -m uvicorn --port=$PORT main:app diff --git a/examples/fastapi-response-streaming-lmi/template.yaml b/examples/fastapi-response-streaming-lmi/template.yaml new file mode 100644 index 00000000..be9745fc --- /dev/null +++ b/examples/fastapi-response-streaming-lmi/template.yaml @@ -0,0 +1,61 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: FastAPI response streaming with Lambda Managed Instances + +Parameters: + SubnetIds: + Type: List + Description: Subnet IDs for the Lambda Managed Instances capacity provider + SecurityGroupIds: + Type: List + Description: Security Group IDs for the Lambda Managed Instances capacity provider + +Globals: + Function: + Timeout: 120 + +Resources: + LMICapacityProvider: + Type: AWS::Serverless::CapacityProvider + Properties: + CapacityProviderName: !Sub "${AWS::StackName}-capacity-provider" + VpcConfig: + SubnetIds: !Ref SubnetIds + SecurityGroupIds: !Ref SecurityGroupIds + ScalingConfig: + MaxVCpuCount: 20 + AverageCPUUtilization: 70.0 + + FastAPIFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: app/ + Handler: run.sh + Runtime: python3.13 + MemorySize: 2048 + AutoPublishAlias: live + Environment: + Variables: + AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap + AWS_LWA_INVOKE_MODE: response_stream + PORT: 8000 + Layers: + # - !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:25 + - arn:aws:lambda:us-west-2:048972532408:layer:LambdaAdapterLayerX86:8 + CapacityProviderConfig: + Arn: !GetAtt LMICapacityProvider.Arn + PerExecutionEnvironmentMaxConcurrency: 64 + FunctionUrlConfig: + AuthType: AWS_IAM + InvokeMode: RESPONSE_STREAM + +Outputs: + FastAPIFunctionUrl: + Description: "Function URL for FastAPI function" + Value: !GetAtt FastAPIFunctionUrl.FunctionUrl + FastAPIFunction: + Description: "FastAPI Lambda Function ARN" + Value: !GetAtt FastAPIFunction.Arn + CapacityProviderArn: + Description: "Capacity Provider ARN" + Value: !GetAtt LMICapacityProvider.Arn diff --git a/src/lib.rs b/src/lib.rs index 42b73a87..48092c30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,7 +54,7 @@ //! | `AWS_LWA_ASYNC_INIT` | Enable async initialization | `false` | //! | `AWS_LWA_REMOVE_BASE_PATH` | Base path to strip from requests | None | //! | `AWS_LWA_INVOKE_MODE` | Lambda invoke mode (`buffered` or `response_stream`) | `buffered` | -//! | `AWS_LWA_ENABLE_COMPRESSION` | Enable response compression | `false` | +//! | `AWS_LWA_ENABLE_COMPRESSION` | Enable response compression (buffered mode only) | `false` | //! //! ## Response Streaming //! @@ -323,6 +323,10 @@ pub struct AdapterOptions { /// When `true`, responses will be compressed using gzip, deflate, or brotli /// based on the `Accept-Encoding` header. /// + /// Note: Compression is not supported with response streaming + /// (`LambdaInvokeMode::ResponseStream`). If both are enabled, compression + /// will be automatically disabled with a warning. + /// /// Default: `false` pub compression: bool, @@ -603,6 +607,15 @@ impl Adapter { } } + let compression = if options.compression && options.invoke_mode == LambdaInvokeMode::ResponseStream { + tracing::warn!( + "Compression is not supported with response streaming. Disabling compression." + ); + false + } else { + options.compression + }; + Ok(Adapter { client: Arc::new(client), healthcheck_url, @@ -613,7 +626,7 @@ impl Adapter { pass_through_path: options.pass_through_path.clone(), async_init: options.async_init, ready_at_init: Arc::new(AtomicBool::new(false)), - compression: options.compression, + compression, invoke_mode: options.invoke_mode, authorization_source: options.authorization_source.clone(), error_status_codes: options.error_status_codes.clone(), @@ -820,19 +833,14 @@ impl Adapter { /// # } /// ``` pub async fn run(self) -> Result<(), Error> { - let compression = self.compression; - let invoke_mode = self.invoke_mode; - - if compression { - let svc = ServiceBuilder::new().layer(CompressionLayer::new()).service(self); - match invoke_mode { - LambdaInvokeMode::Buffered => lambda_http::run(svc).await, - LambdaInvokeMode::ResponseStream => lambda_http::run_with_streaming_response(svc).await, + match (self.compression, self.invoke_mode) { + (true, LambdaInvokeMode::Buffered) => { + let svc = ServiceBuilder::new().layer(CompressionLayer::new()).service(self); + lambda_http::run_concurrent(svc).await } - } else { - match invoke_mode { - LambdaInvokeMode::Buffered => lambda_http::run(self).await, - LambdaInvokeMode::ResponseStream => lambda_http::run_with_streaming_response(self).await, + (_, LambdaInvokeMode::Buffered) => lambda_http::run_concurrent(self).await, + (_, LambdaInvokeMode::ResponseStream) => { + lambda_http::run_with_streaming_response_concurrent(self).await } } } @@ -1256,4 +1264,36 @@ mod tests { env::remove_var(ENV_LAMBDA_RUNTIME_API_PROXY); env::remove_var(ENV_LAMBDA_RUNTIME_API); } + + #[test] + fn test_compression_disabled_with_response_stream() { + #[allow(deprecated)] + let options = AdapterOptions { + compression: true, + invoke_mode: LambdaInvokeMode::ResponseStream, + ..Default::default() + }; + + let adapter = Adapter::new(&options).expect("Failed to create adapter"); + assert!( + !adapter.compression, + "Compression should be disabled when invoke mode is ResponseStream" + ); + } + + #[test] + fn test_compression_enabled_with_buffered() { + #[allow(deprecated)] + let options = AdapterOptions { + compression: true, + invoke_mode: LambdaInvokeMode::Buffered, + ..Default::default() + }; + + let adapter = Adapter::new(&options).expect("Failed to create adapter"); + assert!( + adapter.compression, + "Compression should remain enabled when invoke mode is Buffered" + ); + } } From d5a766b95269e168929b075c00e4a8c767b0c614 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Wed, 7 Jan 2026 23:36:26 +0000 Subject: [PATCH 2/7] fix example and tests --- examples/fastapi-response-streaming-lmi/app/main.py | 7 ++----- examples/fastapi-response-streaming-lmi/template.yaml | 5 ++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/fastapi-response-streaming-lmi/app/main.py b/examples/fastapi-response-streaming-lmi/app/main.py index b8d1d0b0..612b8bc2 100644 --- a/examples/fastapi-response-streaming-lmi/app/main.py +++ b/examples/fastapi-response-streaming-lmi/app/main.py @@ -3,9 +3,6 @@ import asyncio import uuid -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - app = FastAPI() @app.get("/health") @@ -21,8 +18,8 @@ async def streamer(request_id: str): await asyncio.sleep(0.05) -@app.get("/{path:path}") -async def index(path: str, request: Request): +@app.get("/") +async def index(): """Stream response - each concurrent request gets a unique ID.""" request_id = str(uuid.uuid4())[:8] return StreamingResponse(streamer(request_id), media_type="text/plain") diff --git a/examples/fastapi-response-streaming-lmi/template.yaml b/examples/fastapi-response-streaming-lmi/template.yaml index be9745fc..c52196f1 100644 --- a/examples/fastapi-response-streaming-lmi/template.yaml +++ b/examples/fastapi-response-streaming-lmi/template.yaml @@ -40,13 +40,12 @@ Resources: AWS_LWA_INVOKE_MODE: response_stream PORT: 8000 Layers: - # - !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:25 - - arn:aws:lambda:us-west-2:048972532408:layer:LambdaAdapterLayerX86:8 + - !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:26 CapacityProviderConfig: Arn: !GetAtt LMICapacityProvider.Arn PerExecutionEnvironmentMaxConcurrency: 64 FunctionUrlConfig: - AuthType: AWS_IAM + AuthType: NONE InvokeMode: RESPONSE_STREAM Outputs: From fc01db9dc358b504bd2a0a121b896945b0da1f7b Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 8 Feb 2026 04:20:23 +0000 Subject: [PATCH 3/7] chore: update dependencies --- Cargo.lock | 278 ++++++++++++++++++++++++++++------------------------- 1 file changed, 148 insertions(+), 130 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 941d2b45..bd26fd2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,22 +56,21 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.35" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07a926debf178f2d355197f9caddb08e54a9329d44748034bba349c5848cb519" +checksum = "68650b7df54f0293fd061972a0fb05aaf4fc0879d3b3d21a638a182c5c543b9f" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener", "event-listener-strategy", @@ -135,9 +134,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-credential-types" -version = "1.2.10" +version = "1.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01c9521fa01558f750d183c8c68c81b0155b9d193a4ba7f84c36bd1b6d04a06" +checksum = "3cd362783681b15d136480ad555a099e82ecd8e2d10a841e14dfd0078d67fee3" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -147,9 +146,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.15.1" +version = "1.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" dependencies = [ "aws-lc-sys", "zeroize", @@ -157,9 +156,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.34.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" dependencies = [ "cc", "cmake", @@ -169,9 +168,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.3.6" +version = "1.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35452ec3f001e1f2f6db107b6373f1f48f05ec63ba2c5c9fa91f07dad32af11" +checksum = "efa49f3c607b92daae0c078d48a4571f599f966dce3caee5f1ea55c4d9073f99" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -191,9 +190,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.6" +version = "1.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "127fcfad33b7dfc531141fda7e1c402ac65f88aca5511a4d31e2e3d2cd01ce9c" +checksum = "52eec3db979d18cb807fc1070961cc51d87d069abe9ab57917769687368a8c6c" dependencies = [ "futures-util", "pin-project-lite", @@ -202,9 +201,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.62.5" +version = "0.63.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445d5d720c99eed0b4aa674ed00d835d9b1427dd73e04adaf2f94c6b2d6f9fca" +checksum = "630e67f2a31094ffa51b210ae030855cb8f3b7ee1329bdd8d085aaf61e8b97fc" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -212,9 +211,9 @@ dependencies = [ "bytes-utils", "futures-core", "futures-util", - "http 0.2.12", "http 1.4.0", - "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", "percent-encoding", "pin-project-lite", "pin-utils", @@ -223,9 +222,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.9.2" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7204f9fd94749a7c53b26da1b961b4ac36bf070ef1e0b94bb09f79d4f6c193" +checksum = "49952c52f7eebb72ce2a754d3866cc0f87b97d2a46146b79f80f3a93fb2b3716" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -240,15 +239,18 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f535879a207fce0db74b679cfc3e91a3159c8144d717d55f5832aea9eef46e" +checksum = "3b3a26048eeab0ddeba4b4f9d51654c79af8c3b32357dc5f336cee85ab331c33" dependencies = [ "base64-simd", "bytes", "bytes-utils", "http 0.2.12", + "http 1.4.0", "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", "itoa", "num-integer", "pin-project-lite", @@ -334,9 +336,9 @@ checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -359,9 +361,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.49" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -404,18 +406,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstyle", "clap_lex", @@ -429,18 +431,18 @@ checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "cmake" -version = "0.1.54" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] [[package]] name = "compression-codecs" -version = "0.4.34" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a3cbbb8b6eca96f3a5c4bf6938d5b27ced3675d69f95bb51948722870bc323" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" dependencies = [ "brotli", "compression-core", @@ -634,6 +636,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -657,15 +669,15 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -799,9 +811,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -822,9 +834,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -980,9 +992,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "httpmock" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511f510e9b1888d67f10bab4397f8b019d2a9b249a2c10acbce2d705b1b32e26" +checksum = "bf4888a4d02d8e1f92ffb6b4965cf5ff56dda36ef41975f41c6fa0f6bde78c4e" dependencies = [ "assert-json-diff", "async-object-pool", @@ -1055,13 +1067,12 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "http 1.4.0", "http-body 1.0.1", @@ -1122,9 +1133,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1136,9 +1147,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -1178,9 +1189,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -1208,9 +1219,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" @@ -1338,9 +1349,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "litemap" @@ -1365,9 +1376,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -1398,9 +1409,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -1434,9 +1445,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "outref" @@ -1551,9 +1562,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -1571,9 +1582,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -1611,7 +1622,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -1636,9 +1647,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1648,9 +1659,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1659,9 +1670,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "ring" @@ -1671,7 +1682,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -1679,9 +1690,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "log", @@ -1694,9 +1705,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -1706,18 +1717,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring", @@ -1733,9 +1744,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -1810,15 +1821,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -1893,10 +1904,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -1914,9 +1926,9 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -1926,9 +1938,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -1954,9 +1966,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -1991,18 +2003,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -2020,29 +2032,29 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -2070,9 +2082,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -2118,9 +2130,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -2129,9 +2141,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -2142,9 +2154,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -2187,9 +2199,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -2210,9 +2222,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -2278,9 +2290,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -2339,9 +2351,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] @@ -2574,9 +2586,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -2609,18 +2621,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -2686,3 +2698,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" From e2ae6c343b8374a0311e8602a5ddcc18e2a47456 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 8 Feb 2026 04:26:20 +0000 Subject: [PATCH 4/7] chore: fix formatting --- src/lib.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 48092c30..125095c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -608,9 +608,7 @@ impl Adapter { } let compression = if options.compression && options.invoke_mode == LambdaInvokeMode::ResponseStream { - tracing::warn!( - "Compression is not supported with response streaming. Disabling compression." - ); + tracing::warn!("Compression is not supported with response streaming. Disabling compression."); false } else { options.compression @@ -839,9 +837,7 @@ impl Adapter { lambda_http::run_concurrent(svc).await } (_, LambdaInvokeMode::Buffered) => lambda_http::run_concurrent(self).await, - (_, LambdaInvokeMode::ResponseStream) => { - lambda_http::run_with_streaming_response_concurrent(self).await - } + (_, LambdaInvokeMode::ResponseStream) => lambda_http::run_with_streaming_response_concurrent(self).await, } } From 85938a94709de8e84865c05a044a25e26ce9c7b2 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 8 Feb 2026 05:05:22 +0000 Subject: [PATCH 5/7] docs: fix LMI example README to match SAM template --- .../fastapi-response-streaming-lmi/README.md | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/examples/fastapi-response-streaming-lmi/README.md b/examples/fastapi-response-streaming-lmi/README.md index 761b70e0..1d221e76 100644 --- a/examples/fastapi-response-streaming-lmi/README.md +++ b/examples/fastapi-response-streaming-lmi/README.md @@ -24,19 +24,15 @@ This example combines three Lambda features: ```yaml LMICapacityProvider: - Type: AWS::Lambda::CapacityProvider + Type: AWS::Serverless::CapacityProvider Properties: - Name: !Sub "${AWS::StackName}-capacity-provider" + CapacityProviderName: !Sub "${AWS::StackName}-capacity-provider" VpcConfig: SubnetIds: !Ref SubnetIds SecurityGroupIds: !Ref SecurityGroupIds - InstanceRequirements: - Architectures: - - x86_64 - AllowedTypes: - - m7i.large ScalingConfig: - MaxVCpuCount: 2 + MaxVCpuCount: 20 + AverageCPUUtilization: 70.0 FastAPIFunction: Type: AWS::Serverless::Function @@ -51,16 +47,16 @@ FastAPIFunction: AWS_LWA_INVOKE_MODE: response_stream PORT: 8000 Layers: - - !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:25 + - !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:26 CapacityProviderConfig: - Arn: !Ref LMICapacityProvider + Arn: !GetAtt LMICapacityProvider.Arn PerExecutionEnvironmentMaxConcurrency: 64 FunctionUrlConfig: AuthType: NONE InvokeMode: RESPONSE_STREAM ``` -- `AWS::Lambda::CapacityProvider` - Creates the LMI capacity provider with VPC configuration +- `AWS::Serverless::CapacityProvider` - Creates the LMI capacity provider with VPC configuration - `CapacityProviderConfig.Arn` - References the capacity provider - `CapacityProviderConfig.PerExecutionEnvironmentMaxConcurrency: 64` - Up to 64 concurrent requests per instance - `AWS_LWA_INVOKE_MODE: response_stream` - Configures Lambda Web Adapter for streaming @@ -118,4 +114,4 @@ When using LMI with streaming: - **Shared state**: FastAPI/Uvicorn handles concurrency natively, but avoid mutable global state - **Memory**: With 64 concurrent requests, ensure sufficient memory (2048MB in this example) - **Timeouts**: Streaming responses can run up to 15 minutes with Function URLs -- **Scaling**: `MaxInstanceCount` controls the maximum number of EC2 instances in the capacity provider +- **Scaling**: `MaxVCpuCount` controls the maximum vCPUs the capacity provider can provision across all instances From 0d6bf0b33e18a6787702079d696059d3c694ed1a Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 8 Feb 2026 05:46:19 +0000 Subject: [PATCH 6/7] test: add concurrent request forwarding and body isolation tests --- tests/integ_tests/main.rs | 101 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/tests/integ_tests/main.rs b/tests/integ_tests/main.rs index 3e61a826..425cbaec 100644 --- a/tests/integ_tests/main.rs +++ b/tests/integ_tests/main.rs @@ -1200,3 +1200,104 @@ fn add_lambda_context_to_request(request: &mut Request) { // add Context to the request request.extensions_mut().insert(context); } + +#[tokio::test] +async fn test_concurrent_request_forwarding() { + let app_server = MockServer::start(); + let endpoint_a = app_server.mock(|when, then| { + when.method(GET).path("/a"); + then.status(200).body("response_a"); + }); + let endpoint_b = app_server.mock(|when, then| { + when.method(GET).path("/b"); + then.status(200).body("response_b"); + }); + + let adapter = Adapter::new(&AdapterOptions { + host: app_server.host(), + port: app_server.port().to_string(), + readiness_check_port: app_server.port().to_string(), + readiness_check_path: "/".to_string(), + ..Default::default() + }) + .expect("Failed to create adapter"); + + // Fire 4 concurrent requests through cloned adapters + let mut join_set = tokio::task::JoinSet::new(); + for path in ["/a", "/b", "/a", "/b"] { + let mut a = adapter.clone(); + let req = LambdaEventBuilder::new().with_path(path).build(); + let mut request = Request::from(req); + add_lambda_context_to_request(&mut request); + join_set.spawn(async move { (path.to_string(), a.call(request).await) }); + } + + while let Some(result) = join_set.join_next().await { + let (path, response) = result.expect("Task panicked"); + let response = response.expect("Request failed"); + assert_eq!(200, response.status()); + let expected_body = format!("response_{}", &path[1..]); + assert_eq!(expected_body, body_to_string(response).await); + } + + endpoint_a.assert_calls(2); + endpoint_b.assert_calls(2); +} + +#[tokio::test] +async fn test_concurrent_post_body_isolation() { + let app_server = MockServer::start(); + + // Endpoints that echo back a specific field to verify body isolation + let json_a = app_server.mock(|when, then| { + when.method(POST) + .path("/submit") + .body(r#"{"id":"a"}"#); + then.status(200).body("ack_a"); + }); + let json_b = app_server.mock(|when, then| { + when.method(POST) + .path("/submit") + .body(r#"{"id":"b"}"#); + then.status(200).body("ack_b"); + }); + + let adapter = Adapter::new(&AdapterOptions { + host: app_server.host(), + port: app_server.port().to_string(), + readiness_check_port: app_server.port().to_string(), + readiness_check_path: "/".to_string(), + ..Default::default() + }) + .expect("Failed to create adapter"); + + let mut join_set = tokio::task::JoinSet::new(); + for (body, expected) in [ + (r#"{"id":"a"}"#, "ack_a"), + (r#"{"id":"b"}"#, "ack_b"), + (r#"{"id":"a"}"#, "ack_a"), + (r#"{"id":"b"}"#, "ack_b"), + ] { + let mut a = adapter.clone(); + let req = LambdaEventBuilder::new() + .with_method(Method::POST) + .with_path("/submit") + .with_body(body) + .build(); + let mut request = Request::from(req); + add_lambda_context_to_request(&mut request); + let expected = expected.to_string(); + join_set.spawn(async move { (expected, a.call(request).await) }); + } + + while let Some(result) = join_set.join_next().await { + let (expected, response) = result.expect("Task panicked"); + let response = response.expect("Request failed"); + assert_eq!(200, response.status()); + assert_eq!(expected, body_to_string(response).await); + } + + json_a.assert_calls(2); + json_b.assert_calls(2); +} + From b52471f43a45b447cc22d47606703721c494170b Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Mon, 9 Feb 2026 02:42:48 +0000 Subject: [PATCH 7/7] test: add concurrent request forwarding tests for LMI support --- tests/integ_tests/main.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/integ_tests/main.rs b/tests/integ_tests/main.rs index 425cbaec..b7b7f2fc 100644 --- a/tests/integ_tests/main.rs +++ b/tests/integ_tests/main.rs @@ -1250,15 +1250,11 @@ async fn test_concurrent_post_body_isolation() { // Endpoints that echo back a specific field to verify body isolation let json_a = app_server.mock(|when, then| { - when.method(POST) - .path("/submit") - .body(r#"{"id":"a"}"#); + when.method(POST).path("/submit").body(r#"{"id":"a"}"#); then.status(200).body("ack_a"); }); let json_b = app_server.mock(|when, then| { - when.method(POST) - .path("/submit") - .body(r#"{"id":"b"}"#); + when.method(POST).path("/submit").body(r#"{"id":"b"}"#); then.status(200).body("ack_b"); }); @@ -1300,4 +1296,3 @@ async fn test_concurrent_post_body_isolation() { json_a.assert_calls(2); json_b.assert_calls(2); } -