Skip to content

Commit

Permalink
AWS Lambda Function URL Guide (#1365)
Browse files Browse the repository at this point in the history
* AWS Lambda Function URL Guide

./gradlew mnServerlessFunctionAwsLambdaFunctionUrlBuild

* twitter card

* Remove unused imports, semicolons and non-groovy/kotlin isms

---------

Co-authored-by: Tim Yates <tim.yates@gmail.com>
  • Loading branch information
sdelamo and timyates authored Oct 17, 2023
1 parent ce012c4 commit 562c404
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package example.micronaut

import io.micronaut.function.aws.MicronautRequestHandler
import io.micronaut.json.JsonMapper
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
import jakarta.inject.Inject

class FunctionRequestHandler extends MicronautRequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

@Inject
JsonMapper objectMapper

@Override
APIGatewayProxyResponseEvent execute(APIGatewayProxyRequestEvent input) {
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent()
try {
String json = new String(objectMapper.writeValueAsBytes([message: "Hello World"]))
response.statusCode = 200
response.body = json
} catch (IOException e) {
response.statusCode = 500
}
response
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package example.micronaut

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class FunctionRequestHandlerSpec extends Specification {

@AutoCleanup
@Shared
FunctionRequestHandler handler = new FunctionRequestHandler()

void "test Handler"() {
given:
APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent()
request.httpMethod = "GET"
request.path = "/"

when:
APIGatewayProxyResponseEvent response = handler.execute(request)

then:
response.getStatusCode().intValue() == 200
response.body == '{"message":"Hello World"}'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package example.micronaut;

import io.micronaut.function.aws.MicronautRequestHandler;
import java.io.IOException;
import io.micronaut.json.JsonMapper;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import jakarta.inject.Inject;
import java.util.Collections;

public class FunctionRequestHandler extends MicronautRequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

@Inject
JsonMapper objectMapper;

@Override
public APIGatewayProxyResponseEvent execute(APIGatewayProxyRequestEvent input) {
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
try {
String json = new String(objectMapper.writeValueAsBytes(Collections.singletonMap("message", "Hello World")));
response.setStatusCode(200);
response.setBody(json);
} catch (IOException e) {
response.setStatusCode(500);
}
return response;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package example.micronaut;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class FunctionRequestHandlerTest {

private static FunctionRequestHandler handler;

@BeforeAll
public static void setupServer() {
handler = new FunctionRequestHandler();
}

@AfterAll
public static void stopServer() {
if (handler != null) {
handler.getApplicationContext().close();
}
}

@Test
void testHandler() {
APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent();
request.setHttpMethod("GET");
request.setPath("/");
APIGatewayProxyResponseEvent response = handler.execute(request);
assertEquals(200, response.getStatusCode().intValue());
assertEquals("{\"message\":\"Hello World\"}", response.getBody());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package example.micronaut

import io.micronaut.function.aws.MicronautRequestHandler
import java.io.IOException
import io.micronaut.json.JsonMapper
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
import jakarta.inject.Inject

class FunctionRequestHandler : MicronautRequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent>() {

@Inject
lateinit var objectMapper: JsonMapper

override fun execute(input: APIGatewayProxyRequestEvent): APIGatewayProxyResponseEvent =
APIGatewayProxyResponseEvent().apply {
try {
val json = String(objectMapper.writeValueAsBytes(mapOf("message" to "Hello World")))
statusCode = 200
body = json
} catch (e: IOException) {
statusCode = 500
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package example.micronaut

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class FunctionRequestHandlerTest {

@Test
fun testHandler() {
val handler = FunctionRequestHandler()
val request = APIGatewayProxyRequestEvent()
request.httpMethod = "GET"
request.path = "/"
val response = handler.execute(request)
assertEquals(200, response.statusCode.toInt())
assertEquals("{\"message\":\"Hello World\"}", response.body)
handler.close()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "AWS Lambda Function URL and a Micronaut function",
"intro": "Deploy a Micronaut function to AWS Lambda Java 21 runtime and invoke it with a Lambda function URL.",
"authors": ["Sergio del Amo"],
"tags": ["aws-lambda-function-url"],
"categories": ["AWS Lambda"],
"publicationDate": "2023-10-16",
"apps": [
{
"applicationType": "function",
"name": "default",
"features": ["aws-lambda"]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
common:header-top.adoc[]

In this guide, **we will deploy a Micronaut serverless function to AWS Lambda Java 17 runtime and invoke it with a https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html[Lambda function URL]**.

common:gettingStarted.adoc[]

common:requirements.adoc[]

common:completesolution.adoc[]

== Writing the Application

common:cli-or-launch.adoc[]

[source,bash]
----
mn create-function-app example.micronaut.micronautguide --features=@features@ --build=@build@ --lang=@lang@
----

common:build-lang-arguments.adoc[]

If you use https://launch.micronaut.io[Micronaut Launch], select _serverless function_ as application type and add the `aws-lambda` and `aws-lambda-function-url` features.

common:default-package.adoc[]

https://guides.micronaut.io/latest/micronaut-intellij-idea-ide-setup.html[Setup IntelliJ IDEA to develop Micronaut Applications].

== Handler

Create a class extending https://micronaut-projects.github.io/micronaut-aws/latest/api/io/micronaut/function/aws/MicronautRequestHandler.html[MicronautRequestHandler] and define input and output types.

source:FunctionRequestHandler[]

== Handler Test

Write a test which verifies the function behaviour:

test:FunctionRequestHandlerTest[]

* When you instantiate the Handler, the application context starts.
* Remember to close your application context when you end your test. You can use your handler to obtain it.
* Invoke the `execute` method of the handler.

common:testApp.adoc[]

== Lambda

Create a Lambda Function. As a runtime, select Java 17 (Correto).

image::create-function.png[]

Enable **function URLs** and set `Auth Type` as `NONE`.

image::enable-function-url.png[]

=== Upload Code

common:executable-jar.adoc[]

Upload it:

image::upload-function-code.png[]

=== Handler

As Handler, set:

`example.micronaut.FunctionRequestHandler`

image::handler-2.png[]

=== Obtain the Function URL

You can obtain the Function URL in the AWS Console:

image:aws-lambda-function-url.png[]

Invoke it, via a cURL command:

[source, bash]
----
% curl -i https://xxxxxxxx.lambda-url.xxxx.on.aws/
{"message":"Hello World"}
----

common:next.adoc[]

Read more about:

* https://micronaut-projects.github.io/micronaut-aws/latest/guide/#lambda[Micronaut AWS Lambda Support]

* https://aws.amazon.com/lambda/[AWS Lambda]

common:helpWithMicronaut.adoc[]
Binary file added src/docs/images/aws-lambda-function-url.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/docs/images/create-function.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/docs/images/enable-function-url.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/docs/images/handler-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/docs/images/upload-function-code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 562c404

Please sign in to comment.