mirror of
https://github.com/swift-server/swift-aws-lambda-runtime.git
synced 2026-05-03 07:22:27 +00:00
3ddd64087d
## Overview This PR reorganizes and enhances the streaming Lambda examples by splitting them into two distinct examples that demonstrate different invocation methods: 1. **Streaming+FunctionUrl** - Streaming responses via Lambda Function URLs 2. **Streaming+APIGateway** - Streaming responses via API Gateway REST API ## Changes ### 🔄 Restructured Examples - **Renamed**: `Examples/Streaming/` → `Examples/Streaming+FunctionUrl/` - Maintains the original streaming example using Lambda Function URLs - Updated documentation to clarify Function URL-specific configuration - Improved AWS credentials handling in curl examples - **New**: `Examples/Streaming+APIGateway/` - Comprehensive example demonstrating API Gateway REST API with response streaming - Complete SAM template with proper IAM roles and streaming configuration - Detailed documentation covering API Gateway-specific setup ### 📚 Documentation Improvements #### Streaming+FunctionUrl - Clarified that this example uses Lambda Function URLs - Updated curl examples to use `eval $(aws configure export-credentials --format env)` for cleaner credential handling - Maintained all existing functionality and deployment instructions #### Streaming+APIGateway (New) - **316-line comprehensive README** covering: - Response streaming concepts and benefits - HTTP status code and header configuration - Streaming response body patterns - Local testing instructions - Complete SAM deployment guide with detailed template explanation - API Gateway-specific invocation with AWS Sigv4 authentication - Payload format documentation with example JSON - Security and reliability best practices - How API Gateway streaming works under the hood ### 🛠️ Technical Details #### API Gateway Streaming Configuration The new example demonstrates: - Special Lambda URI: `/response-streaming-invocations` endpoint - `responseTransferMode: STREAM` configuration - IAM role with both `lambda:InvokeFunction` and `lambda:InvokeWithResponseStream` permissions - Proper timeout configuration (60s) to accommodate streaming duration #### SAM Template Features ```yaml - Lambda function with streaming support (arm64, provided.al2) - API Gateway REST API with OpenAPI 3.0 definition - IAM execution role for API Gateway to invoke Lambda with streaming - Complete outputs for easy testing (API URL and Lambda ARN) ``` ### 🔐 Security Enhancements Both examples now include comprehensive security best practices: - API Gateway access logging - Throttling configuration - AWS WAF integration recommendations - Lambda concurrent execution limits - Environment variable encryption - Dead Letter Queue (DLQ) configuration - VPC configuration guidance ### 🧪 Testing Both examples support: - **Local testing**: `swift run` with curl invocation on port 7000 - **AWS deployment**: Complete SAM templates with deployment instructions - **Authenticated invocation**: AWS Sigv4 examples with proper credential handling ## Benefits 1. **Clearer separation**: Developers can now easily choose between Function URLs and API Gateway based on their use case 2. **Better documentation**: Each example has tailored documentation for its specific invocation method 3. **Production-ready**: Includes security best practices and proper IAM configuration 4. **Easier testing**: Improved credential handling in curl examples ## Breaking Changes None - this is purely additive. The original streaming example is preserved as `Streaming+FunctionUrl`. ## Testing Checklist - [x] Local testing works for both examples - [x] SAM deployment templates are valid - [x] Documentation is comprehensive and accurate - [x] Security best practices are documented - [x] Curl examples work with proper authentication ## Related Documentation - [AWS Lambda Response Streaming](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html) - [API Gateway Lambda Proxy Integration with Streaming](https://docs.aws.amazon.com/apigateway/latest/developerguide/response-streaming-lambda-configure.html) - [Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html) EOF --------- Co-authored-by: Sebastien Stormacq <stormacq@amazon.lu>
193 lines
8.5 KiB
Swift
193 lines
8.5 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftAWSLambdaRuntime open source project
|
|
//
|
|
// Copyright SwiftAWSLambdaRuntime project authors
|
|
// Copyright (c) Amazon.com, Inc. or its affiliates.
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import AWSLambdaEvents
|
|
import AWSLambdaRuntime
|
|
import Logging
|
|
import NIOCore
|
|
|
|
#if canImport(FoundationEssentials)
|
|
import FoundationEssentials
|
|
#else
|
|
import Foundation
|
|
#endif
|
|
|
|
/// A streaming handler protocol that receives a decoded JSON event and can stream responses.
|
|
/// This handler protocol supports response streaming and background work execution.
|
|
/// Background work can be executed after closing the response stream by calling
|
|
/// ``LambdaResponseStreamWriter/finish()`` or ``LambdaResponseStreamWriter/writeAndFinish(_:)``.
|
|
public protocol StreamingLambdaHandlerWithEvent: _Lambda_SendableMetatype {
|
|
/// Generic input type that will be decoded from JSON.
|
|
associatedtype Event: Decodable
|
|
|
|
/// The handler function that receives a decoded event and can stream responses.
|
|
/// - Parameters:
|
|
/// - event: The decoded event object.
|
|
/// - responseWriter: A ``LambdaResponseStreamWriter`` to write the invocation's response to.
|
|
/// If no response or error is written to `responseWriter` an error will be reported to the invoker.
|
|
/// - context: The ``LambdaContext`` containing the invocation's metadata.
|
|
/// - Throws:
|
|
/// How the thrown error will be handled by the runtime:
|
|
/// - An invocation error will be reported if the error is thrown before the first call to
|
|
/// ``LambdaResponseStreamWriter/write(_:)``.
|
|
/// - If the error is thrown after call(s) to ``LambdaResponseStreamWriter/write(_:)`` but before
|
|
/// a call to ``LambdaResponseStreamWriter/finish()``, the response stream will be closed and trailing
|
|
/// headers will be sent.
|
|
/// - If ``LambdaResponseStreamWriter/finish()`` has already been called before the error is thrown, the
|
|
/// error will be logged.
|
|
mutating func handle(
|
|
_ event: Event,
|
|
responseWriter: some LambdaResponseStreamWriter,
|
|
context: LambdaContext
|
|
) async throws
|
|
}
|
|
|
|
/// Adapts a ``StreamingLambdaHandlerWithEvent`` to work as a ``StreamingLambdaHandler``
|
|
/// by handling JSON decoding of the input event.
|
|
public struct StreamingLambdaCodableAdapter<
|
|
Handler: StreamingLambdaHandlerWithEvent,
|
|
Decoder: LambdaEventDecoder
|
|
>: StreamingLambdaHandler where Handler.Event: Decodable {
|
|
@usableFromInline var handler: Handler
|
|
@usableFromInline let decoder: Decoder
|
|
|
|
/// Initialize with a custom decoder and handler.
|
|
/// - Parameters:
|
|
/// - decoder: The decoder to use for parsing the input event.
|
|
/// - handler: The streaming handler that works with decoded events.
|
|
@inlinable
|
|
public init(decoder: sending Decoder, handler: sending Handler) {
|
|
self.decoder = decoder
|
|
self.handler = handler
|
|
}
|
|
|
|
/// Handles the raw ByteBuffer by decoding it and passing to the underlying handler.
|
|
/// This function attempts to decode the event as a `FunctionURLRequest` first, which allows for
|
|
/// handling Function URL requests that may have a base64-encoded body.
|
|
/// If the decoding fails, it falls back to decoding the event "as-is" with the provided JSON type.
|
|
/// - Parameters:
|
|
/// - event: The raw ByteBuffer event to decode.
|
|
/// - responseWriter: The response writer to pass to the underlying handler.
|
|
/// - context: The Lambda context.
|
|
@inlinable
|
|
public mutating func handle(
|
|
_ event: ByteBuffer,
|
|
responseWriter: some LambdaResponseStreamWriter,
|
|
context: LambdaContext
|
|
) async throws {
|
|
|
|
var decodedBody: Handler.Event!
|
|
|
|
// Try to decode the event. It first tries FunctionURLRequest, then APIGatewayRequest, then "as-is"
|
|
|
|
// try to decode the event as a FunctionURLRequest, then fetch its body attribute
|
|
if let request = try? self.decoder.decode(FunctionURLRequest.self, from: event) {
|
|
// decode the body as user-provided JSON type
|
|
// this function handles the base64 decoding when needed
|
|
decodedBody = try request.decodeBody(Handler.Event.self)
|
|
} else if let request = try? self.decoder.decode(APIGatewayRequest.self, from: event) {
|
|
// decode the body as user-provided JSON type
|
|
// this function handles the base64 decoding when needed
|
|
decodedBody = try request.decodeBody(Handler.Event.self)
|
|
} else {
|
|
// try to decode the event "as-is" with the provided JSON type
|
|
decodedBody = try self.decoder.decode(Handler.Event.self, from: event)
|
|
}
|
|
|
|
// and pass it to the handler
|
|
try await self.handler.handle(decodedBody, responseWriter: responseWriter, context: context)
|
|
}
|
|
}
|
|
|
|
/// A closure-based streaming handler that works with decoded JSON events.
|
|
/// Allows for a streaming handler to be defined in a clean manner, leveraging Swift's trailing closure syntax.
|
|
public struct StreamingFromEventClosureHandler<Event: Decodable>: StreamingLambdaHandlerWithEvent {
|
|
let body: @Sendable (Event, LambdaResponseStreamWriter, LambdaContext) async throws -> Void
|
|
|
|
/// Initialize with a closure that receives a decoded event.
|
|
/// - Parameter body: The handler closure that receives a decoded event, response writer, and context.
|
|
public init(
|
|
body: @Sendable @escaping (Event, LambdaResponseStreamWriter, LambdaContext) async throws -> Void
|
|
) {
|
|
self.body = body
|
|
}
|
|
|
|
/// Calls the provided closure with the decoded event.
|
|
/// - Parameters:
|
|
/// - event: The decoded event object.
|
|
/// - responseWriter: The response writer for streaming output.
|
|
/// - context: The Lambda context.
|
|
public func handle(
|
|
_ event: Event,
|
|
responseWriter: some LambdaResponseStreamWriter,
|
|
context: LambdaContext
|
|
) async throws {
|
|
try await self.body(event, responseWriter, context)
|
|
}
|
|
}
|
|
|
|
extension StreamingLambdaCodableAdapter {
|
|
/// Initialize with a JSON decoder and handler.
|
|
/// - Parameters:
|
|
/// - decoder: The JSON decoder to use. Defaults to `JSONDecoder()`.
|
|
/// - handler: The streaming handler that works with decoded events.
|
|
public init(
|
|
decoder: JSONDecoder = JSONDecoder(),
|
|
handler: sending Handler
|
|
) where Decoder == LambdaJSONEventDecoder {
|
|
self.init(decoder: LambdaJSONEventDecoder(decoder), handler: handler)
|
|
}
|
|
}
|
|
|
|
extension LambdaRuntime {
|
|
/// Initialize with a streaming handler that receives decoded JSON events.
|
|
/// - Parameters:
|
|
/// - decoder: The JSON decoder to use. Defaults to `JSONDecoder()`.
|
|
/// - logger: The logger to use. Defaults to a logger with label "LambdaRuntime".
|
|
/// - streamingBody: The handler closure that receives a decoded event.
|
|
public convenience init<Event: Decodable>(
|
|
decoder: JSONDecoder = JSONDecoder(),
|
|
logger: Logger = Logger(label: "LambdaRuntime"),
|
|
streamingBody: @Sendable @escaping (Event, LambdaResponseStreamWriter, LambdaContext) async throws -> Void
|
|
)
|
|
where
|
|
Handler == StreamingLambdaCodableAdapter<
|
|
StreamingFromEventClosureHandler<Event>,
|
|
LambdaJSONEventDecoder
|
|
>
|
|
{
|
|
let closureHandler = StreamingFromEventClosureHandler(body: streamingBody)
|
|
let adapter = StreamingLambdaCodableAdapter(
|
|
decoder: decoder,
|
|
handler: closureHandler
|
|
)
|
|
self.init(handler: adapter, logger: logger)
|
|
}
|
|
|
|
/// Initialize with a custom streaming handler that receives decoded events.
|
|
/// - Parameters:
|
|
/// - decoder: The decoder to use for parsing input events.
|
|
/// - handler: The streaming handler.
|
|
/// - logger: The logger to use.
|
|
public convenience init<StreamingHandler: StreamingLambdaHandlerWithEvent, Decoder: LambdaEventDecoder>(
|
|
decoder: sending Decoder,
|
|
handler: sending StreamingHandler,
|
|
logger: Logger = Logger(label: "LambdaRuntime")
|
|
) where Handler == StreamingLambdaCodableAdapter<StreamingHandler, Decoder> {
|
|
let adapter = StreamingLambdaCodableAdapter(decoder: decoder, handler: handler)
|
|
self.init(handler: adapter, logger: logger)
|
|
}
|
|
}
|