mirror of
https://github.com/swift-server/swift-aws-lambda-runtime.git
synced 2026-06-02 07:27:33 +00:00
Add user-facing API for Streaming Lambda functions that receives JSON
events
### Motivation:
Streaming Lambda functions developed by developers had no choice but to
implement a handler that receives incoming data as a `ByteBuffer`. While
this is useful for low-level development, I assume most developers will
want to receive a JSON event to trigger their streaming Lambda function.
Going efficiently from a `ByteBuffer` to a Swift struct requires some
code implemented in the `JSON+ByteBuffer.swift` file of the librray. We
propose to further help developers by providing them with a new
`handler()` function that directly receives their `Decodable` type.
### Modifications:
This PR adds a public facing API (+ unit test + updated README) allowing
developers to write a handler method accepting any `Decodable` struct as
input.
```swift
import AWSLambdaRuntime
import NIOCore
// Define your input event structure
struct StreamingRequest: Decodable {
let count: Int
let message: String
let delayMs: Int?
}
// Use the new streaming handler with JSON decoding
let runtime = LambdaRuntime { (event: StreamingRequest, responseWriter, context: LambdaContext) in
context.logger.info("Received request to send \(event.count) messages")
// Stream the messages
for i in 1...event.count {
let response = "Message \(i)/\(event.count): \(event.message)\n"
try await responseWriter.write(ByteBuffer(string: response))
// Optional delay between messages
if let delay = event.delayMs, delay > 0 {
try await Task.sleep(for: .milliseconds(delay))
}
}
// Finish the stream
try await responseWriter.finish()
// Optional: Execute background work after response is sent
context.logger.info("Background work: processing completed")
}
try await runtime.run()
```
This interface provides:
- **Type-safe JSON input**: Automatic decoding of JSON events into Swift
structs
- **Streaming responses**: Full control over when and how to stream data
back to clients
- **Background work support**: Ability to execute code after the
response stream is finished
- **Familiar API**: Uses the same closure-based pattern as regular
Lambda handlers
Because streaming Lambda functions can be invoked either directly
through the API or through Lambda Function URL, this PR adds the
decoding logic to support both types, shielding developers from working
with Function URL requests and base64 encoding.
We understand these choice will have an impact on the raw performance
for event handling. Those advanced users that want to get the maximum
might use the existing `handler(_ event: ByteBuffer, writer:
LambaStreamingWriter)` function to implement their own custom decoding
logic.
This PR provides a balance between ease of use for 80% of the users vs
ultimate performance, without closing the door for the 20% who need it.
### Result:
Lambda function developers can now use arbitrary `Decodable` Swift
struct or Lambda events to trigger their streaming functions. 🎉
---------
Co-authored-by: Tim Condon <0xTim@users.noreply.github.com>
72 lines
2.4 KiB
Swift
72 lines
2.4 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftAWSLambdaRuntime open source project
|
|
//
|
|
// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors
|
|
// 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 AWSLambdaRuntime
|
|
import NIOCore
|
|
|
|
#if canImport(FoundationEssentials)
|
|
import FoundationEssentials
|
|
#else
|
|
import Foundation
|
|
#endif
|
|
|
|
// Define your input event structure
|
|
struct StreamingRequest: Decodable {
|
|
let count: Int
|
|
let message: String
|
|
let delayMs: Int?
|
|
|
|
// Provide default values for optional fields
|
|
var delay: Int {
|
|
delayMs ?? 500
|
|
}
|
|
}
|
|
|
|
// Use the new streaming handler with JSON decoding
|
|
let runtime = LambdaRuntime { (event: StreamingRequest, responseWriter, context: LambdaContext) in
|
|
context.logger.info("Received request to send \(event.count) messages: '\(event.message)'")
|
|
|
|
// Validate input
|
|
guard event.count > 0 && event.count <= 100 else {
|
|
let errorMessage = "Count must be between 1 and 100, got: \(event.count)"
|
|
context.logger.error("\(errorMessage)")
|
|
try await responseWriter.writeAndFinish(ByteBuffer(string: "Error: \(errorMessage)\n"))
|
|
return
|
|
}
|
|
|
|
// Stream the messages
|
|
for i in 1...event.count {
|
|
let response = "[\(Date().ISO8601Format())] Message \(i)/\(event.count): \(event.message)\n"
|
|
try await responseWriter.write(ByteBuffer(string: response))
|
|
|
|
// Optional delay between messages
|
|
if event.delay > 0 {
|
|
try await Task.sleep(for: .milliseconds(event.delay))
|
|
}
|
|
}
|
|
|
|
// Send completion message and finish the stream
|
|
let completionMessage = "✅ Successfully sent \(event.count) messages\n"
|
|
try await responseWriter.writeAndFinish(ByteBuffer(string: completionMessage))
|
|
|
|
// Optional: Do background work here after response is sent
|
|
context.logger.info("Background work: cleaning up resources and logging metrics")
|
|
|
|
// Simulate some background processing
|
|
try await Task.sleep(for: .milliseconds(100))
|
|
context.logger.info("Background work completed")
|
|
}
|
|
|
|
try await runtime.run()
|