Skip to content

Commit

Permalink
Copy headers from APIGateway request to HBRequest (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-fowler committed Jan 29, 2024
1 parent d6cb0a9 commit b059347
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 14 deletions.
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

0 comments on commit b059347

Please sign in to comment.