├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── api-gw.sh ├── bootstrap ├── build.sh ├── deploy.sh ├── install.sh ├── mvnw ├── policy.json ├── pom.xml ├── run.sh ├── src └── main │ ├── java │ ├── SVMSubstitutions.java │ ├── lambda │ │ ├── EchoLambda.java │ │ └── QOTDLambda.java │ └── vertx │ │ └── lambda │ │ ├── Lambda.java │ │ └── LambdaBootstrap.java │ └── resources │ └── META-INF │ ├── native-image │ └── io.vertx │ │ └── vertx-core │ │ ├── native-image.properties │ │ └── reflection.json │ └── services │ └── vertx.lambda.Lambda └── test └── java └── lambda ├── EchoLambdaTest.java └── QOTDLambdaTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | *.iml 3 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertjan/graal-native-image-aws-lambda/3191d6af417ae1a15edad64c1e0dbe527aa0113f/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aws-lambda-graal-native-image 2 | Example for running Java / Vert.x code as Graal native image on AWS Lambda custom runtime, exposed by the AWS API gateway. 3 | 4 | Acknowledgements 5 | --- 6 | This repository is based on the following resources: 7 | - http://how-to.vertx.io/aws-native-image-lambda-howto/ 8 | - https://github.com/pmlopes/aws-lambda-native-vertx 9 | 10 | Credits go to @pmlopes and the Vert.x team for sharing the resources above. 11 | 12 | Preparation/installation of build environment 13 | --- 14 | You need a Linux box to build a native image for Linux. I used an AWS EC2 instance. A Linux laptop or Windows laptop with Linux subsystem should/might work too. YMMV ;-) 15 | 16 | Fire up an AWS Linux instance. If you're going for t2 (burstable), use at least a t2.large instance. 17 | I used AMI `Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type`. 18 | 19 | Log in to the Linux instance. 20 | 21 | Configure the AWS cli via `aws configure` 22 | Install prerequisites needed for building: 23 | ``` 24 | sudo yum install git 25 | sudo yum install gcc 26 | sudo yum install zlib-devel 27 | ``` 28 | 29 | Clone this repo: 30 | `git clone https://github.com/bertjan/graal-native-image-aws-lambda` 31 | 32 | Install Graal: 33 | ``` 34 | wget https://github.com/oracle/graal/releases/download/vm-1.0.0-rc16/graalvm-ce-1.0.0-rc16-linux-amd64.tar.gz 35 | tar xfzv graalvm-ce-1.0.0-rc16-linux-amd64.tar.gz 36 | ``` 37 | 38 | Create an IAM role for the lambda: 39 | ``` 40 | aws iam delete-role --role-name lambda-role 41 | aws iam create-role --role-name lambda-role --path "/service-role/" --assume-role-policy-document file://policy.json 42 | ``` 43 | 44 | Note the ARN that is returned, you need the account number (part of the ARN) in one of the next steps. 45 | 46 | 47 | Building the native image 48 | --- 49 | Set JAVA_HOME to the Graal install dir and run maven build: 50 | ``` 51 | export JAVA_HOME=../graalvm-ce-1.0.0-rc16 52 | ./mvnw clean package 53 | ``` 54 | 55 | Deploying the lambda 56 | --- 57 | ``` 58 | # set this to the role ARN returned in the install step 59 | LAMBDA_ARN="027298914325" 60 | 61 | rm -f function.zip 62 | zip -r function.zip bootstrap target/lambda 63 | aws lambda delete-function --function-name vertxNativeTester 64 | aws lambda create-function --function-name vertxNativeTester --zip-file fileb://function.zip --handler lambda.EchoLambda --runtime provided --role arn:aws:iam::${LAMBDA_ARN}:role/service-role/lambda-role 65 | aws lambda update-function-configuration --function-name vertxNativeTester --layers arn:aws:lambda:eu-west-1:${LAMBDA_ARN}:layer:vertx-native-example:1 66 | ``` 67 | 68 | 69 | Testing the lambda 70 | --- 71 | Use the AWS CLI to invoke the lambda directly: 72 | ``` 73 | aws lambda invoke --function-name vertxNativeTester --payload '{"message":"Hello World"}' --log-type Tail response.txt | grep "LogResult" | awk -F'"' '{print $4}' | base64 --decode 74 | cat response.txt 75 | ``` 76 | 77 | 78 | Accessing over http 79 | --- 80 | Steps here are based on this tutorial: https://docs.aws.amazon.com/lambda/latest/dg/with-on-demand-https-example.html. 81 | 82 | Define variables: 83 | ``` 84 | API_GW_NAME="APIGatewayTest1" 85 | ACCOUNT_ID="027298914325" 86 | LAMBDA_NAME="vertxNativeTester" 87 | PATH_PART="APIGatewayPathPart1" 88 | ``` 89 | 90 | Create the REST API: 91 | ``` 92 | aws apigateway create-rest-api --name $API_GW_NAME 93 | API= 94 | ``` 95 | 96 | Get the resource ID: 97 | ``` 98 | aws apigateway get-resources --rest-api-id $API 99 | PARENT_ID= 100 | ``` 101 | 102 | Create a resource: 103 | ``` 104 | aws apigateway create-resource --rest-api-id $API --path-part $PATH_PART --parent-id $PARENT_ID 105 | RESOURCE= 106 | ``` 107 | 108 | Configure the POST method: 109 | ``` 110 | aws apigateway put-method --rest-api-id $API --resource-id $RESOURCE --http-method POST --authorization-type NONE 111 | ``` 112 | 113 | Integrate, create deployment, add permissions: 114 | ``` 115 | REGION=eu-west-1 116 | ACCOUNT=$ACCOUNT_ID 117 | aws apigateway put-integration --rest-api-id $API --resource-id $RESOURCE --http-method POST --type AWS --integration-http-method POST --uri arn:aws:apigateway:$REGION:lambda:path/2015-03-31/functions/arn:aws:lambda:$REGION:$ACCOUNT:function:${LAMBDA_NAME}/invocations 118 | aws apigateway put-method-response --rest-api-id $API --resource-id $RESOURCE --http-method POST --status-code 200 --response-models application/json=Empty 119 | aws apigateway put-integration-response --rest-api-id $API --resource-id $RESOURCE --http-method POST --status-code 200 --response-templates application/json="" 120 | aws apigateway create-deployment --rest-api-id $API --stage-name prod 121 | aws lambda add-permission --function-name $LAMBDA_NAME --statement-id apigateway-test-${API_GW_NAME} --action lambda:InvokeFunction --principal apigateway.amazonaws.com --source-arn "arn:aws:execute-api:$REGION:$ACCOUNT:$API/*/POST/${PATH_PART}" 122 | aws lambda add-permission --function-name $LAMBDA_NAME --statement-id apigateway-prod-${API_GW_NAME} --action lambda:InvokeFunction --principal apigateway.amazonaws.com --source-arn "arn:aws:execute-api:$REGION:$ACCOUNT:$API/prod/POST/${PATH_PART}" 123 | ``` 124 | 125 | Get the API gateway URL 126 | ``` 127 | echo https://${API}.execute-api.$REGION.amazonaws.com/prod/${PATH_PART} 128 | ``` 129 | -------------------------------------------------------------------------------- /api-gw.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | API_GW_NAME="APIGatewayTest1" 3 | ACCOUNT_ID="027298914325" 4 | LAMBDA_NAME="vertxNativeTester" 5 | PATH_PART="APIGatewayPathPart1" 6 | 7 | aws apigateway create-rest-api --name $API_GW_NAME 8 | API= 9 | aws apigateway get-resources --rest-api-id $API 10 | PARENT_ID= 11 | aws apigateway create-resource --rest-api-id $API --path-part $PATH_PART --parent-id $PARENT_ID 12 | RESOURCE= 13 | aws apigateway put-method --rest-api-id $API --resource-id $RESOURCE --http-method POST --authorization-type NONE 14 | REGION=eu-west-1 15 | ACCOUNT=$ACCOUNT_ID 16 | aws apigateway put-integration --rest-api-id $API --resource-id $RESOURCE --http-method POST --type AWS --integration-http-method POST --uri arn:aws:apigateway:$REGION:lambda:path/2015-03-31/functions/arn:aws:lambda:$REGION:$ACCOUNT:function:${LAMBDA_NAME}/invocations 17 | aws apigateway put-method-response --rest-api-id $API --resource-id $RESOURCE --http-method POST --status-code 200 --response-models application/json=Empty 18 | aws apigateway put-integration-response --rest-api-id $API --resource-id $RESOURCE --http-method POST --status-code 200 --response-templates application/json="" 19 | aws apigateway create-deployment --rest-api-id $API --stage-name prod 20 | aws lambda add-permission --function-name $LAMBDA_NAME --statement-id apigateway-test-${API_GW_NAME} --action lambda:InvokeFunction --principal apigateway.amazonaws.com --source-arn "arn:aws:execute-api:$REGION:$ACCOUNT:$API/*/POST/${PATH_PART}" 21 | aws lambda add-permission --function-name $LAMBDA_NAME --statement-id apigateway-prod-${API_GW_NAME} --action lambda:InvokeFunction --principal apigateway.amazonaws.com --source-arn "arn:aws:execute-api:$REGION:$ACCOUNT:$API/prod/POST/${PATH_PART}" 22 | 23 | # get url 24 | URL=https://${API}.execute-api.$REGION.amazonaws.com/prod/${PATH_PART} 25 | echo $URL 26 | 27 | # take it for a spin: 28 | curl -XPOST $URL 29 | -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ./target/lambda 3 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export JAVA_HOME=../graalvm-ce-1.0.0-rc16 3 | ./mvnw clean package 4 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # set this to the role ARN returned in the install step 4 | LAMBDA_ARN="027298914325" 5 | 6 | rm -f function.zip 7 | zip -r function.zip bootstrap target/lambda 8 | aws lambda delete-function --function-name vertxNativeTester 9 | aws lambda create-function --function-name vertxNativeTester --zip-file fileb://function.zip --handler lambda.QOTDLambda --runtime provided --role arn:aws:iam::${LAMBDA_ARN}:role/service-role/lambda-role 10 | aws lambda update-function-configuration --function-name vertxNativeTester --layers arn:aws:lambda:eu-west-1:${LAMBDA_ARN}:layer:vertx-native-example:1 11 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Run on AWS Linux instance t2.large 4 | sudo yum install git 5 | sudo yum install gcc 6 | sudo yum install zlib-devel 7 | 8 | git clone https://github.com/bertjan/graal-native-image-aws-lambda 9 | wget https://github.com/oracle/graal/releases/download/vm-1.0.0-rc16/graalvm-ce-1.0.0-rc16-linux-amd64.tar.gz 10 | tar xfzv graalvm-ce-1.0.0-rc16-linux-amd64.tar.gz 11 | 12 | # run 'aws configure' first to configure the AWS cli 13 | 14 | aws iam delete-role --role-name lambda-role 15 | aws iam create-role --role-name lambda-role --path "/service-role/" --assume-role-policy-document file://policy.json 16 | 17 | # note the ARN that is returned, you need it in one of the next steps 18 | 19 | cd graal-native-image-aws-lambda 20 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | if [ "$MVNW_VERBOSE" = true ]; then 205 | echo $MAVEN_PROJECTBASEDIR 206 | fi 207 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 208 | 209 | # For Cygwin, switch paths to Windows format before running java 210 | if $cygwin; then 211 | [ -n "$M2_HOME" ] && 212 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 213 | [ -n "$JAVA_HOME" ] && 214 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 215 | [ -n "$CLASSPATH" ] && 216 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 217 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 218 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 219 | fi 220 | 221 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 222 | 223 | exec "$JAVACMD" \ 224 | $MAVEN_OPTS \ 225 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 226 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 227 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 228 | -------------------------------------------------------------------------------- /policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Principal": { 7 | "Service": "lambda.amazonaws.com" 8 | }, 9 | "Action": "sts:AssumeRole" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | 7 | nl.openvalue 8 | graal-native-image-aws-lambda 9 | 0.1-SNAPSHOT 10 | 11 | graal-native-image-aws-lambda 12 | 13 | 14 | vertx.lambda.LambdaBootstrap 15 | 16 | UTF-8 17 | 1.8 18 | 1.8 19 | 1.8 20 | 1.8 21 | 1.0.0-rc16 22 | 23 | 24 | 25 | 26 | 27 | io.vertx 28 | vertx-stack-depchain 29 | 3.6.3 30 | pom 31 | import 32 | 33 | 34 | 35 | 36 | 37 | 38 | com.oracle.substratevm 39 | svm-driver 40 | ${graal.version} 41 | provided 42 | 43 | 44 | io.vertx 45 | vertx-core 46 | 47 | 48 | io.vertx 49 | vertx-web-client 50 | 51 | 52 | io.vertx 53 | vertx-unit 54 | test 55 | 56 | 57 | junit 58 | junit 59 | 4.12 60 | test 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.apache.maven.plugins 68 | maven-jar-plugin 69 | 3.0.2 70 | 71 | 72 | 73 | ${vertx.verticle} 74 | 75 | 76 | 77 | 78 | 79 | com.oracle.substratevm 80 | native-image-maven-plugin 81 | ${graal.version} 82 | 83 | 84 | 85 | native-image 86 | 87 | package 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | aws lambda invoke --function-name vertxNativeTester --payload '{"message":"Hello World"}' --log-type Tail response.txt | grep "LogResult" | awk -F'"' '{print $4}' | base64 --decode 4 | cat response.txt 5 | -------------------------------------------------------------------------------- /src/main/java/SVMSubstitutions.java: -------------------------------------------------------------------------------- 1 | import com.oracle.svm.core.annotate.*; 2 | import org.graalvm.nativeimage.*; 3 | 4 | import io.netty.util.internal.logging.*; 5 | import io.vertx.core.Vertx; 6 | import io.vertx.core.dns.AddressResolverOptions; 7 | import io.vertx.core.impl.resolver.DefaultResolverProvider; 8 | import io.vertx.core.spi.resolver.ResolverProvider; 9 | 10 | /** 11 | * This substitution avoid having loggers added to the build 12 | */ 13 | @TargetClass(className = "io.netty.util.internal.logging.InternalLoggerFactory") 14 | final class TargetInternalLoggerFactory { 15 | @Substitute 16 | private static InternalLoggerFactory newDefaultFactory(String name) { 17 | return JdkLoggerFactory.INSTANCE; 18 | } 19 | } 20 | 21 | /** 22 | * This substitution allows the usage of platform specific code to do low level buffer related tasks 23 | */ 24 | @TargetClass(className = "io.netty.util.internal.CleanerJava6") 25 | final class TargetCleanerJava6 { 26 | @Alias 27 | @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FieldOffset, declClassName = "java.nio.DirectByteBuffer", name = "cleaner") 28 | private static long CLEANER_FIELD_OFFSET; 29 | } 30 | 31 | /** 32 | * This substitution allows the usage of platform specific code to do low level buffer related tasks 33 | */ 34 | @TargetClass(className = "io.netty.util.internal.PlatformDependent0") 35 | final class TargetPlatformDependent0 { 36 | @Alias 37 | @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FieldOffset, declClassName = "java.nio.Buffer", name = "address") 38 | private static long ADDRESS_FIELD_OFFSET; 39 | } 40 | 41 | /** 42 | * This substitution allows the usage of platform specific code to do low level buffer related tasks 43 | */ 44 | @TargetClass(className = "io.netty.util.internal.shaded.org.jctools.util.UnsafeRefArrayAccess") 45 | final class TargetUnsafeRefArrayAccess { 46 | @Alias 47 | @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.ArrayIndexShift, declClass = Object[].class) 48 | public static int REF_ELEMENT_SHIFT; 49 | } 50 | 51 | /** 52 | * This substitution forces the usage of the blocking DNS resolver 53 | */ 54 | @TargetClass(className = "io.vertx.core.spi.resolver.ResolverProvider") 55 | final class TargetResolverProvider { 56 | 57 | @Substitute 58 | public static ResolverProvider factory(Vertx vertx, AddressResolverOptions options) { 59 | return new DefaultResolverProvider(); 60 | } 61 | } 62 | 63 | @AutomaticFeature 64 | class RuntimeReflectionRegistrationFeature implements Feature { 65 | public void beforeAnalysis(BeforeAnalysisAccess access) { 66 | try { 67 | RuntimeReflection.register(java.util.LinkedHashMap.class.getDeclaredConstructor()); 68 | } catch (NoSuchMethodException e) { 69 | throw new RuntimeException(e); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/lambda/EchoLambda.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Paulo Lopes. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package lambda; 17 | 18 | import io.vertx.core.Future; 19 | import io.vertx.core.MultiMap; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.core.buffer.Buffer; 22 | import vertx.lambda.Lambda; 23 | 24 | /** 25 | * This is a simple example of an echo Lambda. 26 | */ 27 | public class EchoLambda implements Lambda { 28 | 29 | @Override 30 | public Future call(Vertx vertx, MultiMap headers, Buffer body) { 31 | return Future.succeededFuture(body); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/lambda/QOTDLambda.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Paulo Lopes. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package lambda; 17 | 18 | import io.vertx.core.Future; 19 | import io.vertx.core.MultiMap; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.core.buffer.Buffer; 22 | import vertx.lambda.Lambda; 23 | 24 | import java.util.Random; 25 | 26 | /** 27 | * A simple QOTD Lambda 28 | */ 29 | public class QOTDLambda implements Lambda { 30 | 31 | private static final Random RANDOM = new Random(); 32 | 33 | private static final String[] QUOTES = { 34 | "If you find a random command on the internet, try running it as root. What's the worse that could happen?", 35 | "Can root create a process that even root can't kill?", 36 | "There's no place like ~", 37 | "Thou shalt not kill -9.", 38 | "The 'n' in unmount is missing, leaving it as umount! If you find it, please write to Bell Labs, 600 Mountain Avenue Murray Hill, NJ.", 39 | "echo \"Just another useless use of cat.\" | cat", 40 | "`echo \"Just another useless use of backticks.\"`", 41 | "We've just created a special character device for handling complaints, conveniently located at /dev/null.", 42 | "false - do nothing, unsuccessfully" 43 | }; 44 | 45 | @Override 46 | public Future call(Vertx vertx, MultiMap headers, Buffer body) { 47 | // return a random qotd 48 | return Future.succeededFuture(Buffer.buffer(QUOTES[RANDOM.nextInt(QUOTES.length)])); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/vertx/lambda/Lambda.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Paulo Lopes. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package vertx.lambda; 17 | 18 | import io.vertx.core.Future; 19 | import io.vertx.core.MultiMap; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.core.buffer.Buffer; 22 | 23 | /** 24 | * The functional interface that represents a lambda 25 | */ 26 | @FunctionalInterface 27 | public interface Lambda { 28 | 29 | /** 30 | * Responses are asynchronous. 31 | * 32 | * @param vertx the vertx instance if needed for more IO 33 | * @param headers the request headers 34 | * @param body the request body (null if no body) 35 | * @return return a future with the buffer to be returned. 36 | */ 37 | Future call(Vertx vertx, MultiMap headers, Buffer body); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/vertx/lambda/LambdaBootstrap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Paulo Lopes. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package vertx.lambda; 17 | 18 | import io.vertx.core.AsyncResult; 19 | import io.vertx.core.Future; 20 | import io.vertx.core.Handler; 21 | import io.vertx.core.Vertx; 22 | import io.vertx.core.buffer.Buffer; 23 | import io.vertx.core.json.JsonObject; 24 | import io.vertx.ext.web.client.HttpResponse; 25 | import io.vertx.ext.web.client.WebClient; 26 | 27 | import static java.lang.System.getenv; 28 | 29 | import java.text.MessageFormat; 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | import java.util.ServiceLoader; 33 | 34 | /** 35 | * Main entrypoint for the application. 36 | */ 37 | public class LambdaBootstrap { 38 | 39 | private static final String LAMBDA_VERSION_DATE = "2018-06-01"; 40 | 41 | private static final String LAMBDA_RUNTIME_TEMPLATE = "/{0}/runtime/invocation/next"; 42 | private static final String LAMBDA_INVOCATION_TEMPLATE = "/{0}/runtime/invocation/{1}/response"; 43 | private static final String LAMBDA_INIT_ERROR_TEMPLATE = "/{0}/runtime/init/error"; 44 | private static final String LAMBDA_ERROR_TEMPLATE = "/{0}/runtime/invocation/{1}/error"; 45 | 46 | private static final Map HANDLERS = new HashMap<>(); 47 | 48 | static { 49 | // load all handlers available, if this becomes a performance 50 | ServiceLoader serviceLoader = ServiceLoader.load(Lambda.class); 51 | for (Lambda fn : serviceLoader) { 52 | HANDLERS.put(fn.getClass().getName(), fn); 53 | } 54 | } 55 | 56 | public static void main(String[] args) { 57 | try { 58 | new LambdaBootstrap(Vertx.vertx()); 59 | } catch (RuntimeException e) { 60 | e.printStackTrace(); 61 | System.exit(1); 62 | } 63 | } 64 | 65 | private final WebClient client; 66 | 67 | private final Lambda fn; 68 | 69 | private final String host; 70 | private final int port; 71 | 72 | private LambdaBootstrap(Vertx vertx) { 73 | // create an WebClient 74 | this.client = WebClient.create(vertx); 75 | 76 | String runtimeApi = getenv("AWS_LAMBDA_RUNTIME_API"); 77 | 78 | int sep = runtimeApi.indexOf(':'); 79 | if (sep != -1) { 80 | host = runtimeApi.substring(0, sep); 81 | port = Integer.parseInt(runtimeApi.substring(sep + 1)); 82 | } else { 83 | host = runtimeApi; 84 | port = 80; 85 | } 86 | 87 | // Get the handler class and method name from the Lambda Configuration in the format of 88 | this.fn = HANDLERS.get(getenv("_HANDLER")); 89 | final String runtimeUrl = MessageFormat.format(LAMBDA_RUNTIME_TEMPLATE, LAMBDA_VERSION_DATE); 90 | 91 | if (fn == null) { 92 | // Not much else to do handler can't be found. 93 | final String uri = MessageFormat.format(LAMBDA_INIT_ERROR_TEMPLATE, LAMBDA_VERSION_DATE); 94 | fail(uri, "Could not find handler method", "InitError"); 95 | } else { 96 | process(vertx, runtimeUrl); 97 | } 98 | } 99 | 100 | private void process(Vertx vertx, String runtimeUrl) { 101 | client.get(port, host, runtimeUrl).send(getAbs -> { 102 | if (getAbs.succeeded()) { 103 | HttpResponse response = getAbs.result(); 104 | 105 | if (response.statusCode() != 200) { 106 | System.exit(0); 107 | } 108 | 109 | String requestId = response.getHeader("Lambda-Runtime-Aws-Request-Id"); 110 | 111 | try { 112 | // Invoke Handler Method 113 | fn.call(vertx, response.headers(), response.body()) 114 | .setHandler(ar -> { 115 | if (ar.succeeded()) { 116 | // Post the results of Handler Invocation 117 | String invocationUrl = MessageFormat.format(LAMBDA_INVOCATION_TEMPLATE, LAMBDA_VERSION_DATE, requestId); 118 | success(invocationUrl, ar.result(), ack -> { 119 | if (ack.failed()) { 120 | ack.cause().printStackTrace(); 121 | // terminate the process 122 | System.exit(1); 123 | } else { 124 | // process the next call 125 | // run on context to avoid large stacks 126 | vertx.runOnContext(v -> process(vertx, runtimeUrl)); 127 | } 128 | }); 129 | } else { 130 | String initErrorUrl = MessageFormat.format(LAMBDA_ERROR_TEMPLATE, LAMBDA_VERSION_DATE, requestId); 131 | fail(initErrorUrl, "Invocation Error", "RuntimeError"); 132 | } 133 | }); 134 | 135 | } catch (Exception e) { 136 | String initErrorUrl = MessageFormat.format(LAMBDA_ERROR_TEMPLATE, LAMBDA_VERSION_DATE, requestId); 137 | fail(initErrorUrl, "Invocation Error", "RuntimeError"); 138 | } 139 | } else { 140 | getAbs.cause().printStackTrace(); 141 | System.exit(1); 142 | } 143 | }); 144 | } 145 | 146 | private void success(String requestURI, Buffer result, Handler> handler) { 147 | client.post(port, host, requestURI) 148 | .sendBuffer(result, ar -> { 149 | if (ar.succeeded()) { 150 | // we don't really care about the response 151 | handler.handle(Future.succeededFuture()); 152 | } else { 153 | handler.handle(Future.failedFuture(ar.cause())); 154 | } 155 | }); 156 | } 157 | 158 | private void fail(String requestURI, String errMsg, String errType) { 159 | final JsonObject error = new JsonObject() 160 | .put("errorMessage", errMsg) 161 | .put("errorType", errType); 162 | 163 | client.post(port, host, requestURI) 164 | .sendJson(error, ar -> { 165 | if (ar.failed()) { 166 | ar.cause().printStackTrace(); 167 | } 168 | // terminate the process 169 | System.exit(1); 170 | }); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/native-image/io.vertx/vertx-core/native-image.properties: -------------------------------------------------------------------------------- 1 | Args = -H:+ReportUnsupportedElementsAtRuntime \ 2 | --allow-incomplete-classpath \ 3 | --rerun-class-initialization-at-runtime=io.netty.handler.codec.http2.Http2CodecUtil \ 4 | --delay-class-initialization-to-runtime=io.netty.handler.codec.http.HttpObjectEncoder,io.netty.handler.codec.http2.DefaultHttp2FrameWriter,io.netty.handler.codec.http.websocketx.WebSocket00FrameEncoder,io.netty.handler.ssl.JdkNpnApplicationProtocolNegotiator,io.netty.handler.ssl.ReferenceCountedOpenSslEngine,io.vertx.core.net.impl.transport.EpollTransport,io.vertx.core.net.impl.transport.KQueueTransport,io.netty.handler.ssl.ReferenceCountedOpenSslClientContext,io.netty.handler.ssl.ReferenceCountedOpenSslServerContext,io.netty.handler.ssl.ConscryptAlpnSslEngine,io.netty.handler.ssl.JettyNpnSslEngine \ 5 | -H:IncludeResources=(META-INF/vertx|META-INF/services|static|webroot|template)/.* \ 6 | -H:ReflectionConfigurationFiles=classes/${.}/reflection.json \ 7 | -H:Name=lambda 8 | 9 | JavaArgs = -Dvertx.disableDnsResolver=true \ 10 | -Dvertx.cacheDirBase=/tmp/vertx-cache \ 11 | -Djava.net.preferIPv4Stack=true 12 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/native-image/io.vertx/vertx-core/reflection.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "java.util.LinkedHashMap", 4 | "methods": [ 5 | { "name": "", "parameterTypes": [] } 6 | ] 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/vertx.lambda.Lambda: -------------------------------------------------------------------------------- 1 | lambda.EchoLambda 2 | lambda.QOTDLambda 3 | -------------------------------------------------------------------------------- /test/java/lambda/EchoLambdaTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Paulo Lopes. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package lambda; 17 | 18 | import io.vertx.core.Future; 19 | import io.vertx.core.MultiMap; 20 | import io.vertx.core.buffer.Buffer; 21 | import io.vertx.ext.unit.Async; 22 | import io.vertx.ext.unit.TestContext; 23 | import io.vertx.ext.unit.junit.RunTestOnContext; 24 | import io.vertx.ext.unit.junit.VertxUnitRunner; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | 29 | @RunWith(VertxUnitRunner.class) 30 | public class EchoLambdaTest { 31 | 32 | private final EchoLambda fn = new EchoLambda(); 33 | 34 | @Rule 35 | public RunTestOnContext rule = new RunTestOnContext(); 36 | 37 | @Test 38 | public void shouldGetAnEchoMessage(TestContext should) { 39 | final Async test = should.async(); 40 | 41 | Future fut = fn.call(rule.vertx(), MultiMap.caseInsensitiveMultiMap(), Buffer.buffer("Hello World")); 42 | 43 | fut.setHandler(call -> { 44 | if (call.failed()) { 45 | should.fail(call.cause()); 46 | } else { 47 | should.assertEquals(Buffer.buffer("Hello World"), call.result()); 48 | test.complete(); 49 | } 50 | }); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /test/java/lambda/QOTDLambdaTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Paulo Lopes. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package lambda; 17 | 18 | import io.vertx.core.Future; 19 | import io.vertx.core.MultiMap; 20 | import io.vertx.core.buffer.Buffer; 21 | import io.vertx.ext.unit.Async; 22 | import io.vertx.ext.unit.TestContext; 23 | import io.vertx.ext.unit.junit.RunTestOnContext; 24 | import io.vertx.ext.unit.junit.VertxUnitRunner; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | 29 | @RunWith(VertxUnitRunner.class) 30 | public class QOTDLambdaTest { 31 | 32 | private final QOTDLambda fn = new QOTDLambda(); 33 | 34 | @Rule 35 | public RunTestOnContext rule = new RunTestOnContext(); 36 | 37 | @Test 38 | public void shouldGetAQOTD(TestContext should) { 39 | final Async test = should.async(); 40 | 41 | Future fut = fn.call(rule.vertx(), MultiMap.caseInsensitiveMultiMap(), null); 42 | 43 | fut.setHandler(call -> { 44 | if (call.failed()) { 45 | should.fail(call.cause()); 46 | } else { 47 | should.assertNotNull(call.result()); 48 | should.assertTrue(call.result().length() > 0); 49 | test.complete(); 50 | } 51 | }); 52 | } 53 | 54 | } 55 | --------------------------------------------------------------------------------