Skip to content

Commit 19cecbf

Browse files
Filters work on APIGatewayProxyRequestEvent and APIGatewayProxyRespon… (#22)
* Filters work on APIGatewayProxyRequestEvent and APIGatewayProxyResponseEvent This makes it easier to run the filters also in error scenarios. * Update router/src/main/kotlin/io/moia/router/RequestHandler.kt Co-Authored-By: moia-sven-ole <[email protected]> * Fix lint error
1 parent 608388a commit 19cecbf

File tree

3 files changed

+49
-30
lines changed

3 files changed

+49
-30
lines changed

router/src/main/kotlin/io/moia/router/RequestHandler.kt

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,13 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
2525
private val serializationHandlerChain by lazy { SerializationHandlerChain(serializationHandlers()) }
2626
private val deserializationHandlerChain by lazy { DeserializationHandlerChain(deserializationHandlers()) }
2727

28+
override fun handleRequest(input: APIGatewayProxyRequestEvent, context: Context): APIGatewayProxyResponseEvent =
29+
input
30+
.apply { headers = headers.mapKeys { it.key.toLowerCase() } }
31+
.let { router.filter.then(this::handleRequest)(it) }
32+
2833
@Suppress("UNCHECKED_CAST")
29-
override fun handleRequest(input: APIGatewayProxyRequestEvent, context: Context): APIGatewayProxyResponseEvent {
34+
private fun handleRequest(input: APIGatewayProxyRequestEvent): APIGatewayProxyResponseEvent {
3035
log.debug("handling request with method '${input.httpMethod}' and path '${input.path}' - Accept:${input.acceptHeader()} Content-Type:${input.contentType()} $input")
3136
val routes = router.routes as List<RouterFunction<Any, Any>>
3237
val matchResults: List<RequestMatchResult> = routes.map { routerFunction: RouterFunction<Any, Any> ->
@@ -37,19 +42,12 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
3742
val matchedAcceptType = routerFunction.requestPredicate.matchedAcceptType(input.acceptedMediaTypes())
3843
?: MediaType.parse(router.defaultContentType)
3944

40-
// Phase 1: Deserialization
41-
// TODO: find a way to also invoke the filter chain on failed deserialization
4245
val handler: HandlerFunction<Any, Any> = routerFunction.handler
43-
val requestBody = try {
44-
deserializeRequest(handler, input)
45-
} catch (e: Exception) {
46-
return createResponse(matchedAcceptType, exceptionToResponseEntity(e))
47-
}
4846

49-
// Phase 2: Content Handling
50-
val request = Request(input, requestBody, routerFunction.requestPredicate.pathPattern)
51-
return createResponse(matchedAcceptType, router.filter.then {
47+
val response =
5248
try {
49+
val requestBody = deserializeRequest(handler, input)
50+
val request = Request(input, requestBody, routerFunction.requestPredicate.pathPattern)
5351
when {
5452
missingPermissions(input, routerFunction) ->
5553
ResponseEntity(403, ApiError("missing permissions", "MISSING_PERMISSIONS"))
@@ -58,7 +56,7 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
5856
} catch (e: Exception) {
5957
exceptionToResponseEntity(e, input)
6058
}
61-
}(request))
59+
return createResponse(matchedAcceptType, response)
6260
}
6361
matchResult
6462
}
@@ -106,22 +104,22 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
106104
when {
107105
matchResults.any { it.matchPath && it.matchMethod && !it.matchContentType } ->
108106
ApiException(
109-
httpResponseStatus = 415,
110-
message = "Unsupported Media Type",
111-
code = "UNSUPPORTED_MEDIA_TYPE"
112-
)
107+
httpResponseStatus = 415,
108+
message = "Unsupported Media Type",
109+
code = "UNSUPPORTED_MEDIA_TYPE"
110+
)
113111
matchResults.any { it.matchPath && it.matchMethod && !it.matchAcceptType } ->
114112
ApiException(
115-
httpResponseStatus = 406,
116-
message = "Not Acceptable",
117-
code = "NOT_ACCEPTABLE"
118-
)
113+
httpResponseStatus = 406,
114+
message = "Not Acceptable",
115+
code = "NOT_ACCEPTABLE"
116+
)
119117
matchResults.any { it.matchPath && !it.matchMethod } ->
120118
ApiException(
121119
httpResponseStatus = 405,
122120
message = "Method Not Allowed",
123121
code = "METHOD_NOT_ALLOWED"
124-
)
122+
)
125123
else -> ApiException(
126124
httpResponseStatus = 404,
127125
message = "Not found",
@@ -215,4 +213,4 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
215213
companion object {
216214
val log: Logger = LoggerFactory.getLogger(RequestHandler::class.java)
217215
}
218-
}
216+
}

router/src/main/kotlin/io/moia/router/Router.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.moia.router
22

33
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
4+
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
45

56
class Router {
67

@@ -46,22 +47,21 @@ class Router {
4647
}
4748
}
4849

49-
interface Filter : (HandlerFunction<*, *>) -> HandlerFunction<*, *> {
50+
interface Filter : (APIGatewayRequestHandlerFunction) -> APIGatewayRequestHandlerFunction {
5051
companion object {
51-
operator fun invoke(fn: (HandlerFunction<*, *>) -> HandlerFunction<*, *>): Filter = object :
52+
operator fun invoke(fn: (APIGatewayRequestHandlerFunction) -> APIGatewayRequestHandlerFunction): Filter = object :
5253
Filter {
53-
override operator fun invoke(next: HandlerFunction<*, *>): HandlerFunction<*, *> = fn(next)
54+
override operator fun invoke(next: APIGatewayRequestHandlerFunction): APIGatewayRequestHandlerFunction = fn(next)
5455
}
5556
}
5657
}
5758

5859
val Filter.Companion.NoOp: Filter get() = Filter { next -> { next(it) } }
5960

60-
fun Filter.then(next: Filter): Filter =
61-
Filter { this(next(it)) }
62-
63-
fun Filter.then(next: HandlerFunction<*, *>): HandlerFunction<*, *> = { this(next)(it) }
61+
fun Filter.then(next: Filter): Filter = Filter { this(next(it)) }
62+
fun Filter.then(next: APIGatewayRequestHandlerFunction): APIGatewayRequestHandlerFunction = { this(next)(it) }
6463

64+
typealias APIGatewayRequestHandlerFunction = (APIGatewayProxyRequestEvent) -> APIGatewayProxyResponseEvent
6565
typealias HandlerFunction<I, T> = (request: Request<I>) -> ResponseEntity<T>
6666

6767
class RouterFunction<I, T>(

router/src/test/kotlin/io/moia/router/RequestHandlerTest.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,27 @@ class RequestHandlerTest {
479479
assertEquals("[CustomObject(text=foo, number=1), CustomObject(text=bar, number=2)]", plainTextResponse.body)
480480
}
481481

482+
@Test
483+
fun `headers should be case insensitive`() {
484+
val request = APIGatewayProxyRequestEvent()
485+
.withPath("/some")
486+
.withHttpMethod("GET")
487+
.withHeaders(
488+
mapOf(
489+
"Accept" to "Application/Json",
490+
"User-Agent" to "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"
491+
)
492+
)
493+
val response = testRequestHandler.handleRequest(request, mockk())
494+
495+
assert(request.headers["accept"].toString()).isEqualTo("Application/Json")
496+
assert(request.headers["user-agent"].toString())
497+
.isEqualTo(
498+
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"
499+
)
500+
assert(response.statusCode).isEqualTo(200)
501+
}
502+
482503
class TestRequestHandlerAuthorization : RequestHandler() {
483504
override val router = router {
484505
GET("/some") { _: Request<Unit> ->
@@ -518,7 +539,7 @@ class RequestHandlerTest {
518539
private val incrementingFilter = Filter { next ->
519540
{ request ->
520541
filterInvocations += 1
521-
next(request).copy(headers = mapOf("header" to "value"))
542+
next(request).apply { headers = headers + ("header" to "value") }
522543
}
523544
}
524545
override val router = router {

0 commit comments

Comments
 (0)