Skip to content
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

Copy APIGateway request headers into HBRequest #19

Merged
merged 1 commit into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 46 additions & 11 deletions Sources/HummingbirdLambda/Request+APIGateway.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import AWSLambdaEvents
import AWSLambdaRuntime
import ExtrasBase64
import Foundation
import HTTPTypes
import Hummingbird
import NIOCore
import NIOHTTP1

protocol APIRequest {
var path: String { get }
Expand Down Expand Up @@ -59,13 +59,9 @@ extension HBRequest {
uri += "?\(queryParams.joined(separator: "&"))"
}
// construct headers
var headers = NIOHTTP1.HTTPHeaders(from.headers.map { ($0.key, $0.value) })
from.multiValueHeaders.forEach { multiValueHeader in
headers.remove(name: multiValueHeader.key)
for header in multiValueHeader.value {
headers.add(name: multiValueHeader.key, value: header)
}
}
var authority: String?
let headers = HTTPFields(headers: from.headers, multiValueHeaders: from.multiValueHeaders, authority: &authority)

// get body
let body: ByteBuffer?
if let apiGatewayBody = from.body {
Expand All @@ -82,15 +78,54 @@ extension HBRequest {
self.init(
head: .init(
method: method,
scheme: nil,
authority: nil,
path: uri
scheme: "https",
authority: authority,
path: uri,
headerFields: headers
),
body: body.map(HBRequestBody.byteBuffer) ?? .byteBuffer(.init())
)
}
}

extension HTTPFields {
/// Initialize HTTPFields from HTTP headers and multivalue headers in an APIGateway request
/// - Parameters:
/// - headers: headers
/// - multiValueHeaders: multi-value headers
/// - authority: reference to authority string
init(headers: AWSLambdaEvents.HTTPHeaders, multiValueHeaders: HTTPMultiValueHeaders, authority: inout String?) {
self.init()
self.reserveCapacity(headers.count)
var firstHost = true
for (name, values) in multiValueHeaders {
if firstHost, name.lowercased() == "host" {
if let value = values.first {
firstHost = false
authority = value
continue
}
}
if let fieldName = HTTPField.Name(name) {
for value in values {
self.append(HTTPField(name: fieldName, value: value))
}
}
}
for (name, value) in headers {
if firstHost, name.lowercased() == "host" {
firstHost = false
authority = value
continue
}
if let fieldName = HTTPField.Name(name) {
if self[fieldName] != nil { continue }
self.append(HTTPField(name: fieldName, value: value))
}
}
}
}

extension CharacterSet {
static var urlQueryComponentAllowed: CharacterSet = {
var cs = CharacterSet.urlQueryAllowed
Expand Down
75 changes: 72 additions & 3 deletions Tests/HummingbirdLambdaTests/LambdaTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,72 @@ final class LambdaTests: XCTestCase {
func newEvent(uri: String, method: String, body: ByteBuffer? = nil) throws -> APIGatewayRequest {
let base64Body = body.map { "\"\(String(base64Encoding: $0.readableBytesView))\"" } ?? "null"
let request = """
{"httpMethod": "\(method)", "body": \(base64Body), "resource": "\(uri)", "requestContext": {"resourceId": "123456", "apiId": "1234567890", "resourcePath": "\(uri)", "httpMethod": "\(method)", "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", "accountId": "123456789012", "stage": "Prod", "identity": {"apiKey": null, "userArn": null, "cognitoAuthenticationType": null, "caller": null, "userAgent": "Custom User Agent String", "user": null, "cognitoIdentityPoolId": null, "cognitoAuthenticationProvider": null, "sourceIp": "127.0.0.1", "accountId": null}, "extendedRequestId": null, "path": "\(uri)"}, "queryStringParameters": null, "multiValueQueryStringParameters": null, "headers": {"Host": "127.0.0.1:3000", "Connection": "keep-alive", "Cache-Control": "max-age=0", "Dnt": "1", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36 Edg/78.0.276.24", "Sec-Fetch-User": "?1", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", "Sec-Fetch-Site": "none", "Sec-Fetch-Mode": "navigate", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US,en;q=0.9", "X-Forwarded-Proto": "http", "X-Forwarded-Port": "3000"}, "multiValueHeaders": {"Host": ["127.0.0.1:3000"], "Connection": ["keep-alive"], "Cache-Control": ["max-age=0"], "Dnt": ["1"], "Upgrade-Insecure-Requests": ["1"], "User-Agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36 Edg/78.0.276.24"], "Sec-Fetch-User": ["?1"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"], "Sec-Fetch-Site": ["none"], "Sec-Fetch-Mode": ["navigate"], "Accept-Encoding": ["gzip, deflate, br"], "Accept-Language": ["en-US,en;q=0.9"], "X-Forwarded-Proto": ["http"], "X-Forwarded-Port": ["3000"]}, "pathParameters": null, "stageVariables": null, "path": "\(uri)", "isBase64Encoded": \(body != nil)}
{
"httpMethod": "\(method)",
"body": \(base64Body),
"resource": "\(uri)",
"requestContext": {
"resourceId": "123456",
"apiId": "1234567890",
"resourcePath": "\(uri)",
"httpMethod": "\(method)",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"accountId": "123456789012",
"stage": "Prod",
"identity": {
"apiKey": null,
"userArn": null,
"cognitoAuthenticationType": null,
"caller": null,
"userAgent": "Custom User Agent String",
"user": null,
"cognitoIdentityPoolId": null,
"cognitoAuthenticationProvider": null,
"sourceIp": "127.0.0.1",
"accountId": null
},
"extendedRequestId": null,
"path": "\(uri)"
},
"queryStringParameters": null,
"multiValueQueryStringParameters": null,
"headers": {
"Host": "127.0.0.1:3000",
"Connection": "keep-alive",
"Cache-Control": "max-age=0",
"Dnt": "1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36 Edg/78.0.276.24",
"Sec-Fetch-User": "?1",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Sec-Fetch-Site": "none",
"Sec-Fetch-Mode": "navigate",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"X-Forwarded-Proto": "http",
"X-Forwarded-Port": "3000"
},
"multiValueHeaders": {
"Host": ["127.0.0.1:3000"],
"Connection": ["keep-alive"],
"Cache-Control": ["max-age=0"],
"Dnt": ["1"],
"Upgrade-Insecure-Requests": ["1"],
"User-Agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36 Edg/78.0.276.24"],
"Sec-Fetch-User": ["?1"],
"Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"],
"Sec-Fetch-Site": ["none"],
"Sec-Fetch-Mode": ["navigate"],
"Accept-Encoding": ["gzip, deflate, br"],
"Accept-Language": ["en-US,en;q=0.9"],
"X-Forwarded-Proto": ["http"],
"X-Forwarded-Port": ["3000"]
},
"pathParameters": null,
"stageVariables": null,
"path": "\(uri)",
"isBase64Encoded": \(body != nil)
}
"""
return try JSONDecoder().decode(APIGatewayRequest.self, from: Data(request.utf8))
}
Expand Down Expand Up @@ -122,7 +187,9 @@ final class LambdaTests: XCTestCase {
func buildResponder() -> some HBResponder<Context> {
let router = HBRouter(context: Context.self)
router.middlewares.add(HBLogRequestsMiddleware(.debug))
router.get("hello") { _, _ in
router.get("hello") { request, _ in
XCTAssertEqual(request.head.authority, "127.0.0.1:3000")
XCTAssertEqual(request.headers[.userAgent], "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36 Edg/78.0.276.24")
return "Hello"
}
return router.buildResponder()
Expand Down Expand Up @@ -173,7 +240,9 @@ final class LambdaTests: XCTestCase {
func buildResponder() -> some HBResponder<Context> {
let router = HBRouter(context: Context.self)
router.middlewares.add(HBLogRequestsMiddleware(.debug))
router.post { _, _ in
router.post { request, _ in
XCTAssertEqual(request.headers[.authorization], "Bearer abc123")
XCTAssertEqual(request.head.authority, "hello.test.com")
return "hello"
}
return router.buildResponder()
Expand Down
Loading