Skip to content

Commit 99a4f3d

Browse files
committed
fix decoding the body inside the FunctionUrlRequest
1 parent d9eecc8 commit 99a4f3d

File tree

4 files changed

+193
-8
lines changed

4 files changed

+193
-8
lines changed
Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,49 @@
11
{
2-
"count": 5,
3-
"message": "Hello from streaming Lambda!",
4-
"delayMs": 1000
5-
}
2+
"version": "2.0",
3+
"routeKey": "$default",
4+
"rawPath": "/",
5+
"rawQueryString": "",
6+
"body": "{\"count\": 5, \"message\": \"Hello from streaming Lambda!\", \"delayMs\": 1000}",
7+
"headers": {
8+
"x-amzn-tls-cipher-suite": "TLS_AES_128_GCM_SHA256",
9+
"x-amzn-tls-version": "TLSv1.3",
10+
"x-amzn-trace-id": "Root=1-68762f44-4f6a87d1639e7fc356aa6f96",
11+
"x-amz-date": "20250715T103651Z",
12+
"x-forwarded-proto": "https",
13+
"host": "zvnsvhpx7u5gn3l3euimg4jjou0jvbfe.lambda-url.us-east-1.on.aws",
14+
"x-forwarded-port": "443",
15+
"x-forwarded-for": "2a01:cb0c:6de:8300:a1be:8004:e31a:b9f",
16+
"accept": "*/*",
17+
"user-agent": "curl/8.7.1"
18+
},
19+
"requestContext": {
20+
"accountId": "0123456789",
21+
"apiId": "zvnsvhpx7u5gn3l3euimg4jjou0jvbfe",
22+
"authorizer": {
23+
"iam": {
24+
"accessKey": "AKIA....",
25+
"accountId": "0123456789",
26+
"callerId": "AIDA...",
27+
"cognitoIdentity": null,
28+
"principalOrgId": "o-rlrup7z3ao",
29+
"userArn": "arn:aws:iam::0123456789:user/sst",
30+
"userId": "AIDA..."
31+
}
32+
},
33+
"domainName": "zvnsvhpx7u5gn3l3euimg4jjou0jvbfe.lambda-url.us-east-1.on.aws",
34+
"domainPrefix": "zvnsvhpx7u5gn3l3euimg4jjou0jvbfe",
35+
"http": {
36+
"method": "GET",
37+
"path": "/",
38+
"protocol": "HTTP/1.1",
39+
"sourceIp": "2a01:...:b9f",
40+
"userAgent": "curl/8.7.1"
41+
},
42+
"requestId": "f942509a-283f-4c4f-94f8-0d4ccc4a00f8",
43+
"routeKey": "$default",
44+
"stage": "$default",
45+
"time": "15/Jul/2025:10:36:52 +0000",
46+
"timeEpoch": 1752575812081
47+
},
48+
"isBase64Encoded": false
49+
}

Examples/StreamingFromEvent/template.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Description: SAM Template for StreamingfromEvent Example
44

55
Resources:
66
# Lambda function
7-
StreamingNumbers:
7+
StreamingFromEvent:
88
Type: AWS::Serverless::Function
99
Properties:
1010
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingFromEvent/StreamingFromEvent.zip
@@ -22,4 +22,4 @@ Outputs:
2222
# print Lambda function URL
2323
LambdaURL:
2424
Description: Lambda URL
25-
Value: !GetAtt StreamingNumbersUrl.FunctionUrl
25+
Value: !GetAtt StreamingFromEventUrl.FunctionUrl
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2017-2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
#if canImport(FoundationEssentials)
16+
import FoundationEssentials
17+
#else
18+
import Foundation
19+
#endif
20+
21+
// https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html
22+
23+
/// FunctionURLRequest contains data coming from a bare Lambda Function URL
24+
/// This is a simplified version of teh request structure, with no dependencies on the HTTPType module.
25+
public struct FunctionURLRequest: Codable, Sendable {
26+
public struct Context: Codable, Sendable {
27+
public struct Authorizer: Codable, Sendable {
28+
public struct IAMAuthorizer: Codable, Sendable {
29+
public let accessKey: String
30+
31+
public let accountId: String
32+
public let callerId: String
33+
public let cognitoIdentity: String?
34+
35+
public let principalOrgId: String?
36+
37+
public let userArn: String
38+
public let userId: String
39+
}
40+
41+
public let iam: IAMAuthorizer?
42+
}
43+
44+
public struct HTTP: Codable, Sendable {
45+
public let method: String
46+
public let path: String
47+
public let `protocol`: String
48+
public let sourceIp: String
49+
public let userAgent: String
50+
}
51+
52+
public let accountId: String
53+
public let apiId: String
54+
public let authentication: String?
55+
public let authorizer: Authorizer?
56+
public let domainName: String
57+
public let domainPrefix: String
58+
public let http: HTTP
59+
60+
public let requestId: String
61+
public let routeKey: String
62+
public let stage: String
63+
64+
public let time: String
65+
public let timeEpoch: Int
66+
}
67+
68+
public let version: String
69+
70+
public let routeKey: String
71+
public let rawPath: String
72+
public let rawQueryString: String
73+
public let cookies: [String]?
74+
public let headers: [String: String]
75+
public let queryStringParameters: [String: String]?
76+
77+
public let requestContext: Context
78+
79+
public let body: String?
80+
public let pathParameters: [String: String]?
81+
public let isBase64Encoded: Bool
82+
83+
public let stageVariables: [String: String]?
84+
}
85+
86+
// MARK: - Response -
87+
88+
public struct FunctionURLResponse: Codable, Sendable {
89+
public var statusCode: Int
90+
public var headers: [String: String]?
91+
public var body: String?
92+
public let cookies: [String]?
93+
public var isBase64Encoded: Bool?
94+
95+
public init(
96+
statusCode: Int,
97+
headers: [String: String]? = nil,
98+
body: String? = nil,
99+
cookies: [String]? = nil,
100+
isBase64Encoded: Bool? = nil
101+
) {
102+
self.statusCode = statusCode
103+
self.headers = headers
104+
self.body = body
105+
self.cookies = cookies
106+
self.isBase64Encoded = isBase64Encoded
107+
}
108+
}

Sources/AWSLambdaRuntime/LambdaStreaming+Codable.swift

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
import Logging
1616
import NIOCore
1717

18+
#if canImport(FoundationEssentials)
19+
import FoundationEssentials
20+
#else
21+
import Foundation
22+
#endif
23+
1824
/// A streaming handler protocol that receives a decoded JSON event and can stream responses.
1925
/// This handler protocol supports response streaming and background work execution.
2026
/// Background work can be executed after closing the response stream by calling
@@ -75,8 +81,35 @@ public struct StreamingLambdaCodableAdapter<
7581
responseWriter: some LambdaResponseStreamWriter,
7682
context: LambdaContext
7783
) async throws {
78-
let decodedEvent = try self.decoder.decode(Handler.Event.self, from: event)
79-
try await self.handler.handle(decodedEvent, responseWriter: responseWriter, context: context)
84+
85+
// for some reasons I don't understand the "body" param contains the complete FunctionURL request
86+
// so, 1/ we decode the event we receive, 2/ we base64 decode the body, 3/ we decode a FunnctionURLRequest again,
87+
// then 4/ we can access the actual payload body, decode it pass it to the handler
88+
let functionUrlEvent1 = try self.decoder.decode(FunctionURLRequest.self, from: event)
89+
if let base64EncodedString = functionUrlEvent1.body,
90+
91+
// this is the minimal way to base64 decode without importing new dependecies
92+
let decodedData = Data(base64Encoded: base64EncodedString),
93+
let decodedString = String(data: decodedData, encoding: .utf8)
94+
{
95+
96+
// decode the FunCtionURL event inside the body
97+
let functionUrlEvent2 = try self.decoder.decode(
98+
FunctionURLRequest.self,
99+
from: ByteBuffer(string: decodedString)
100+
)
101+
102+
// finally decode the actual payload passed by the caller
103+
let decodedEvent = try self.decoder.decode(
104+
Handler.Event.self,
105+
from: ByteBuffer(string: functionUrlEvent2.body ?? "")
106+
)
107+
108+
// and invoke the user-provided handler
109+
try await self.handler.handle(decodedEvent, responseWriter: responseWriter, context: context)
110+
} else {
111+
context.logger.trace("Can't decode FunctionURLRequest's body", metadata: ["Event": "\(event)"])
112+
}
80113
}
81114
}
82115

0 commit comments

Comments
 (0)