-
Notifications
You must be signed in to change notification settings - Fork 114
[core] Implement Lambda streaming with custom HTTP headers #521
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
Changes from all commits
01fa243
b6fb60c
83aa018
8d35daf
c7343b6
b0d5a13
a0eb791
9b8a71c
2e5d059
450927b
90501a0
58fc8c2
d1f15ca
074138f
f5dc5b1
4458b0c
b1bc3fb
15c68cb
9946ac7
24690f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,5 +12,4 @@ Package.resolved | |
.vscode | ||
Makefile | ||
.devcontainer | ||
.amazonq | ||
samconfig.toml | ||
.amazonq |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftAWSLambdaRuntime open source project | ||
// | ||
// Copyright (c) 2017-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 NIOCore | ||
|
||
#if canImport(FoundationEssentials) | ||
import FoundationEssentials | ||
#else | ||
import Foundation | ||
#endif | ||
|
||
/// A response structure specifically designed for streaming Lambda responses that contains | ||
/// HTTP status code and headers without body content. | ||
/// | ||
/// This structure is used with `LambdaResponseStreamWriter.writeStatusAndHeaders(_:)` to send | ||
/// HTTP response metadata before streaming the response body. | ||
public struct StreamingLambdaStatusAndHeadersResponse: Codable, Sendable { | ||
/// The HTTP status code for the response (e.g., 200, 404, 500) | ||
public let statusCode: Int | ||
|
||
/// Dictionary of single-value HTTP headers | ||
public let headers: [String: String]? | ||
|
||
/// Dictionary of multi-value HTTP headers (e.g., Set-Cookie headers) | ||
public let multiValueHeaders: [String: [String]]? | ||
|
||
/// Creates a new streaming Lambda response with status code and optional headers | ||
/// | ||
/// - Parameters: | ||
/// - statusCode: The HTTP status code for the response | ||
/// - headers: Optional dictionary of single-value HTTP headers | ||
/// - multiValueHeaders: Optional dictionary of multi-value HTTP headers | ||
public init( | ||
statusCode: Int, | ||
headers: [String: String]? = nil, | ||
multiValueHeaders: [String: [String]]? = nil | ||
) { | ||
self.statusCode = statusCode | ||
self.headers = headers | ||
self.multiValueHeaders = multiValueHeaders | ||
} | ||
} | ||
|
||
extension LambdaResponseStreamWriter { | ||
/// Writes the HTTP status code and headers to the response stream. | ||
/// | ||
/// This method serializes the status and headers as JSON and writes them to the stream, | ||
/// followed by eight null bytes as a separator before the response body. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any documentation anywhere we can link to that provides a reason for writing 8 null bytes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is undocumented at the moment I received the information from an SDM in the Lambda service team and I have confirmed by looking at the NodeJS runtime implementation. The value of the "magic" header is here The 8 x 0 bytes are defined here BTW, at the moment, the NodeJS runtime is the only runtime supporting this capability. When we will release the Swift runtime, it will be second to offer this possibility, before all the AWS managed runtime |
||
/// | ||
/// - Parameters: | ||
/// - response: The status and headers response to write | ||
/// - encoder: The encoder to use for serializing the response, | ||
sebsto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// - Throws: An error if JSON serialization or writing fails | ||
public func writeStatusAndHeaders<Encoder: LambdaOutputEncoder>( | ||
_ response: StreamingLambdaStatusAndHeadersResponse, | ||
encoder: Encoder | ||
) async throws where Encoder.Output == StreamingLambdaStatusAndHeadersResponse { | ||
|
||
// Convert JSON headers to an array of bytes in a ByteBuffer | ||
var buffer = ByteBuffer() | ||
try encoder.encode(response, into: &buffer) | ||
|
||
// Write eight null bytes as separator | ||
buffer.writeBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) | ||
|
||
// Write the JSON data and the separator | ||
try await self.write(buffer, hasCustomHeaders: true) | ||
} | ||
|
||
/// Write a response part into the stream. Bytes written are streamed continually. | ||
/// This implementation avoids having to modify all the tests and other part of the code that use this function signature | ||
/// - Parameter buffer: The buffer to write. | ||
public func write(_ buffer: ByteBuffer) async throws { | ||
// Write the buffer to the response stream | ||
try await self.write(buffer, hasCustomHeaders: false) | ||
} | ||
} | ||
|
||
extension LambdaResponseStreamWriter { | ||
/// Writes the HTTP status code and headers to the response stream. | ||
/// | ||
/// This method serializes the status and headers as JSON and writes them to the stream, | ||
/// followed by eight null bytes as a separator before the response body. | ||
/// | ||
/// - Parameters: | ||
/// - response: The status and headers response to write | ||
/// - encoder: The encoder to use for serializing the response, use JSONEncoder by default | ||
/// - Throws: An error if JSON serialization or writing fails | ||
public func writeStatusAndHeaders( | ||
_ response: StreamingLambdaStatusAndHeadersResponse, | ||
encoder: JSONEncoder = JSONEncoder() | ||
) async throws { | ||
encoder.outputFormatting = .withoutEscapingSlashes | ||
try await self.writeStatusAndHeaders(response, encoder: LambdaJSONOutputEncoder(encoder)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason we aren't use swift-http-types here?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because there is no dependency on HTTPTypes in this library and I did not want to pull a new dependency just for this. The data format is well controlled. It's between the Swift runtime and the Lambda data plane.