├── src ├── v.mod ├── bootstrap │ ├── bootstrap_test.v │ └── bootstrap.v └── main.v ├── .dockerignore ├── .gitignore ├── serverless.yml ├── tools ├── aws-lambda-rie │ └── Dockerfile └── serverless-framework │ └── Dockerfile ├── LICENSE.txt ├── docker-compose.yml ├── Dockerfile └── README.md /src/v.mod: -------------------------------------------------------------------------------- 1 | Module { 2 | name: 'vlang-aws-lambda' 3 | description: '' 4 | version: '' 5 | license: '' 6 | dependencies: [] 7 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | build/ 2 | build-serverless-package/ 3 | build-aws-linux/ 4 | aws-lambda-rie/ 5 | docker-compose.yml 6 | *.code-workspace 7 | .vscode/ 8 | vls.log 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vls.log 2 | .vscode/settings.json 3 | vlang-aws-lambda.code-workspace 4 | /aws-lambda-rie/ 5 | /build/ 6 | /v/ 7 | /build-serverless/ 8 | /build-serverless-package/ 9 | /build-aws-linux/ -------------------------------------------------------------------------------- /src/bootstrap/bootstrap_test.v: -------------------------------------------------------------------------------- 1 | module bootstrap 2 | 3 | import os 4 | 5 | const ( 6 | aws_runtime_api = '127.0.0.1:9001' 7 | aws_handler = 'my-handler' 8 | lambda_task_root = '/var/task' 9 | ) 10 | 11 | fn testsuite_begin() { 12 | os.setenv('AWS_LAMBDA_RUNTIME_API', aws_runtime_api, true) 13 | os.setenv('_HANDLER', aws_handler, true) 14 | os.setenv('LAMBDA_TASK_ROOT', lambda_task_root, true) 15 | } 16 | 17 | fn test_new_lambda_api() { 18 | data := new_lambda_api() 19 | assert data.aws_lambda_runtime_api == aws_runtime_api 20 | assert data.req_incocation_next.url == 'http://$aws_runtime_api/2018-06-01/runtime/invocation/next' 21 | } 22 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: ah-vlang-test 2 | 3 | configValidationMode: error 4 | provider: 5 | name: aws 6 | runtime: provided 7 | lambdaHashingVersion: 20201221 8 | versionFunctions: false 9 | region: eu-west-1 10 | memorySize: 128 11 | 12 | stage: dev 13 | 14 | 15 | layers: 16 | libraries: 17 | path: ./ 18 | package: 19 | patterns: 20 | - '!bootstrap' 21 | 22 | 23 | functions: 24 | lambda_trigger: 25 | handler: default 26 | layers: 27 | - { Ref: LibrariesLambdaLayer } 28 | package: 29 | individually: true 30 | patterns: 31 | - '!lib/**' 32 | events: 33 | - httpApi: 34 | path: /hello 35 | method: get 36 | -------------------------------------------------------------------------------- /tools/aws-lambda-rie/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.14 AS lambda_rie 2 | # AWS lambda Runtime Interface Emulator 3 | RUN git clone https://github.com/aws/aws-lambda-runtime-interface-emulator.git /aws-lambda-rie && \ 4 | cd /aws-lambda-rie && \ 5 | go build -ldflags "-s -w" -o aws-lambda-rie ./cmd/aws-lambda-rie 6 | FROM public.ecr.aws/lambda/provided:al2 AS prod 7 | ENV LOG_LEVEL "info" 8 | ENV HANDLER "default" 9 | COPY --from=lambda_rie /aws-lambda-rie/aws-lambda-rie /usr/bin/aws-lambda-rie 10 | RUN echo $'#!/bin/sh\n\ 11 | set -x\n\ 12 | /usr/bin/aws-lambda-rie --log-level ${LOG_LEVEL} /var/task/bootstrap ${HANDLER}\n\ 13 | ' > /entry_script.sh && \ 14 | chmod +x /entry_script.sh 15 | ENTRYPOINT [ "/entry_script.sh" ] 16 | -------------------------------------------------------------------------------- /src/main.v: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | import bootstrap 4 | import x.json2 5 | 6 | const app_version = '0.0.8' 7 | 8 | fn main() { 9 | runtime := bootstrap.BootstrapConfig{ 10 | handlers: { 11 | 'default': my_handler 12 | } 13 | } 14 | 15 | runtime.process() 16 | } 17 | 18 | fn my_handler(event json2.Any, context bootstrap.Context) string { 19 | // dump(event) 20 | // dump(context) 21 | event_txt := event.str() 22 | handler_response := 'ECHO $app_version \n {"event":$event_txt},\n"context":$context' 23 | // create api gateway response 24 | // TODO: detect api gateway event 25 | mut api_gateway_response := map[string]json2.Any{} 26 | api_gateway_response['statusCode'] = 200 27 | // api_gateway_response['header']=map[string]json2.Any{} 28 | api_gateway_response['body'] = handler_response 29 | return api_gateway_response.str() 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, Andreas Heissenberger 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | build: 4 | # environment: 5 | # VLANG_BUILD_OPTIONS: "-prod -gc boehm_full_opt" 6 | # VLANG_LAMBDA_FUNC_NAME: "main.v" 7 | build: 8 | context: . 9 | target: devbuild 10 | # args: 11 | # vlang_repo_url: "https://github.com/aheissenberger/v.git" 12 | # vlang_repo_branch: "feature/feat-request-timeout" 13 | volumes: 14 | - "./src:/src:ro" 15 | - "./build-aws-linux:/var/task" 16 | lambda: 17 | environment: 18 | - "DOCKER_LAMBDA_STAY_OPEN=1" 19 | - "LOG_LEVEL=info" 20 | - "HANDLER=default" 21 | build: 22 | context: ./tools/aws-lambda-rie 23 | ports: 24 | - "9000:8080" 25 | volumes: 26 | - "./src:/src:ro" 27 | - "./build-aws-linux:/var/task" 28 | deploy: 29 | build: 30 | context: ./tools/serverless-framework 31 | volumes: 32 | - "~/.aws:/root/.aws" 33 | - "./build-aws-linux:/opt/app/src:ro" 34 | - "./build-serverless-package:/opt/app/build" 35 | - "./serverless.yml:/opt/app/config/serverless.yml:ro" 36 | entrypoint: 37 | - "/deploy.sh" 38 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/lambda/provided:al2 AS basesys 2 | RUN yum update -y && \ 3 | yum install -y @'Development Tools' \ 4 | # V net module needs openssl 1.1 5 | openssl11 \ 6 | openssl11-devel \ 7 | # needed for v -gc boehm 8 | gc-devel 9 | 10 | FROM basesys AS vlang 11 | ARG vlang_repo_url=https://github.com/vlang/v 12 | ARG vlang_repo_branch=master 13 | RUN mkdir -p /var/task/lib 14 | 15 | # compile V lang 16 | RUN git clone --branch ${vlang_repo_branch} ${vlang_repo_url} /vlang 17 | RUN cd /vlang && \ 18 | git pull && \ 19 | make && \ 20 | /vlang/v symlink 21 | 22 | FROM vlang AS devbuild 23 | ENV VLANG_BUILD_OPTIONS="-prod -gc boehm_full_opt" 24 | ENV VLANG_LAMBDA_FUNC_NAME="main.v" 25 | # create mount points 26 | RUN mkdir /src && \ 27 | mkdir -p /var/task 28 | VOLUME ["/src","/var/task"] 29 | 30 | RUN echo $'#!/bin/sh\n\ 31 | set -e # exit on error \n\ 32 | set -x \n\ 33 | # compile V bootstrap and handler to binary \n\ 34 | cd /src\n\ 35 | [ -f "/var/task/bootstrap" ] && rm /var/task/bootstrap\n\ 36 | v ${VLANG_BUILD_OPTIONS} -o /var/task/bootstrap ${VLANG_LAMBDA_FUNC_NAME} \n\ 37 | # prepare lambda shared libs \n\ 38 | # copy all shared libs which are linked with the binary \n\ 39 | [ -d "/var/task/lib" ] && rm -fr /var/task/lib\n\ 40 | mkdir -p /var/task/lib\n\ 41 | ldd /var/task/bootstrap | egrep -o '/[a-z0-9/\_\.\-]+' | xargs -I{} -P1 cp -v {} /var/task/lib' > /build.sh &&\ 42 | chmod +x /build.sh 43 | ENTRYPOINT [ "/build.sh" ] -------------------------------------------------------------------------------- /tools/serverless-framework/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | # SERVERLESS_VERSION is set explicitly in the Makefile used to build, otherwise 3 | # use latest version. 4 | ARG SERVERLESS_VERSION=latest 5 | ENV SERVERLESS_VERSION $SERVERLESS_VERSION 6 | 7 | RUN apk --no-cache add python python3 python3-dev py-pip ca-certificates groff less bash make jq curl wget g++ zip git openssh && \ 8 | pip --no-cache-dir install awscli && \ 9 | update-ca-certificates 10 | 11 | RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \ 12 | wget -q https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-2.25-r0.apk && \ 13 | apk add glibc-2.25-r0.apk && \ 14 | rm -f glibc-2.25-r0.apk 15 | 16 | RUN mkdir -p /tmp/yarn && \ 17 | mkdir -p /opt/yarn/dist && \ 18 | cd /tmp/yarn && \ 19 | wget -q https://yarnpkg.com/latest.tar.gz && \ 20 | tar zvxf latest.tar.gz && \ 21 | find /tmp/yarn -maxdepth 2 -mindepth 2 -exec mv {} /opt/yarn/dist/ \; && \ 22 | rm -rf /tmp/yarn 23 | 24 | RUN ln -sf /opt/yarn/dist/bin/yarn /usr/local/bin/yarn && \ 25 | ln -sf /opt/yarn/dist/bin/yarn /usr/local/bin/yarnpkg && \ 26 | yarn --version 27 | 28 | RUN yarn global add serverless@$SERVERLESS_VERSION 29 | 30 | RUN cd /opt/app; mkdir src build config 31 | 32 | RUN echo $'#!/bin/sh\n\ 33 | set -e # exit on error \n\ 34 | set -x \n\ 35 | if [ -d "/opt/app/build" ]; then\n\ 36 | rm -fr /opt/app/build/*\n\ 37 | else\n\ 38 | mkdir /opt/app/build\n\ 39 | fi\n\ 40 | cp /opt/app/config/serverless.yml /opt/app/build/serverless.yml\n\ 41 | cp -R /opt/app/src/* /opt/app/build\n\ 42 | cd /opt/app/build\n\ 43 | /usr/local/bin/serverless "${@:-deploy}"' > /deploy.sh 44 | RUN chmod +x /deploy.sh 45 | 46 | WORKDIR /opt/app -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vlang / AWS Lambda Custom Runtime Setup 2 | 3 | Use V lang for AWS Lambda functions. This template provides a setup 4 | to compile, test local and deploy to AWS with the [serverless framework](https://www.serverless.com/framework/docs/providers/aws/guide/intro/) which provides easy creation of additional AWS Cloud resources. 5 | ## Installation 6 | 7 | create a project from the template 8 | 9 | ```bash 10 | mkdir my-project && cd $_ 11 | git init 12 | git pull --depth 1 https://github.com/aheissenberger/vlang-aws-lambda.git 13 | ``` 14 | 15 | ## Requirements 16 | 17 | * `V` language [setup](https://vlang.io) 18 | * [Docker Desktop](https://www.docker.com/products/docker-desktop) 19 | **Hint:** This project uses the latest version of Docker Desktop. If your version does not provide `docker compose` replace all mentioned commands with `docker-compose`. 20 | * [AWS credentials setup](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-set-up-credentials.html) 21 | 22 | ## Usage 23 | 24 | ### Write Lambda functions 25 | 26 | add the code for your functions to `src/bootstrap.v` 27 | 28 | ### Build binary for AWS Lambda 29 | 30 | ```sh 31 | docker compose run --rm build 32 | ``` 33 | 34 | ### Test local 35 | #### A) Docker with AWS Lambda Runtime Emulator 36 | 37 | start the AWS Lambda Emulator as background process: 38 | ```sh 39 | docker compose up -d lambda my-handler 40 | ``` 41 | 42 | invoke your function with a AWS Event: 43 | ```sh 44 | curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}' 45 | ``` 46 | 47 | Lambda Logs: 48 | ```sh 49 | docker compose logs -f lambda 50 | ``` 51 | 52 | shutdown background process: 53 | ```sh 54 | docker compose down 55 | ``` 56 | **Hint:** you need to restart if you built new binaries! 57 | 58 | #### B) Native go binary with AWS Lambda Runtime Emulator 59 | 60 | This is the option you will use if you need to debug your V function with `lldb`or `gdb`. 61 | 62 | **build** 63 | requires local golang development environment 64 | ``` 65 | git clone git@github.com:aws/aws-lambda-runtime-interface-emulator.git aws-lambda-rie 66 | cd aws-lambda-rie 67 | go build -ldflags "-s -w" -o aws-lambda-rie ./cmd/aws-lambda-rie 68 | ``` 69 | 70 | **run** 71 | 1. compile V lang handler 72 | ```sh 73 | v -prod -gc boehm_full_opt src/lambda_function.v -o build/bootstrap 74 | ``` 75 | 76 | 2. run AWS Lambda Runtime Interface Emulation 77 | ``` 78 | ./aws-lambda-rie/aws-lambda-rie --log-level debug ./build/bootstrap myhandler 79 | ``` 80 | 3. call the AWS Lambda Runtime Interface on port 8080 (different to docker image) 81 | 82 | ```sh 83 | curl -XPOST "http://localhost:8080/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}' 84 | ``` 85 | 86 | ### Deploy to AWS Cloud 87 | 88 | 1. Modify configuration 89 | 90 | check and adapt `serverless.yml`: 91 | * `service: ` should be short and will be part of the lambda function name 92 | * `region: eu-west-1` adapt [region](https://docs.aws.amazon.com/general/latest/gr/lambda-service.html) to your location 93 | * `stage: dev` 94 | for more information check [serverless framework documentation](https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml/) 95 | 96 | 2. [Setup AWS credentials](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-set-up-credentials.html) 97 | 98 | 3. Deploy 99 | ```sh 100 | docker compose run --rm deploy 101 | ``` 102 | 103 | ## Options 104 | 105 | ### `docker compose run build` 106 | 107 | Check the `environment:` and `args:` sections of the `docker-compose.yml` file for possible options. 108 | 109 | ### `docker compose up lambda` 110 | 111 | Check the `entrypoint:` sections of the `docker-compose.yml` file for possible options. 112 | 113 | ### `docker compose run deploy` 114 | 115 | by default this will do: 116 | `serverless deploy` 117 | 118 | you can add any valid [serverless cli command](https://www.serverless.com/framework/docs/providers/aws/cli-reference/) to the command: 119 | `docker compose run deploy ` 120 | 121 | remove the whole project from AWS Cloud: 122 | `docker compose run deploy remove --stage test` 123 | 124 | deploy to a different stage as defined in `serverless.yml`: 125 | `docker compose run deploy deploy --stage test` 126 | 127 | ## Bootstrap API 128 | 129 | The bootstrap module will handle the communication to the AWS Runtime API. 130 | The only information which needs to be provided is a mapping of handler names to handler functions. 131 | 132 | ```v 133 | fn main() { 134 | runtime := bootstrap.BootstrapConfig{ 135 | handlers: map{ 136 | 'default': my_handler 137 | } 138 | } 139 | 140 | runtime.process() 141 | } 142 | 143 | fn my_handler(event json2.Any, context bootstrap.Context) string { 144 | return result 145 | } 146 | ``` 147 | If only one function is needed use the name 'default' which is allready used as a default for the local lambda test setup. 148 | 149 | ### Roadmap 150 | 151 | - [X] Build pipeline to create AWS Linux 2 native binaries and bundle shared libraries 152 | - [X] Local Lambda Testenvironment 153 | - [X] Integrated AWS Cloud deployment with the serverless framework 154 | - [X] Encapsulate the V lang custome runtime in a v module 155 | - [X] Deploy all libraries in an extra layer 156 | - [ ] Include example which uses the AWS C++ SDK 157 | 158 | ### Contribution 159 | 160 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated. 161 | 162 | 1. Fork the Project 163 | 1. Create your Feature Branch (git checkout -b feature/AmazingFeature) 164 | 1. Commit your Changes (git commit -m 'Add some AmazingFeature') 165 | 1. Push to the Branch (git push origin feature/AmazingFeature) 166 | 1. Open a Pull Request 167 | 168 | ### Resources 169 | These are resources which helped to land this project 170 | * https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html 171 | * https://github.com/aws/aws-lambda-runtime-interface-emulator 172 | * https://stackoverflow.com/questions/58548580/npm-package-pem-doesnt-seem-to-work-in-aws-lambda-nodejs-10-x-results-in-ope/60232433#60232433 173 | * https://github.com/softprops/lambda-rust 174 | * http://jamesmcm.github.io/blog/2020/10/24/lambda-runtime/ 175 | * https://github.com/awslabs/aws-lambda-cpp 176 | * https://gallery.ecr.aws/lambda/provided 177 | 178 | when extra tools from centos epel required: 179 | ``` 180 | RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm && \ 181 | yum update -y && \ 182 | yum install -y inotify-tools 183 | ``` 184 | 185 | ### License 186 | 187 | Distributed under the "bsd-2-clause" License. See [LICENSE.txt](LICENSE.txt) for more information. 188 | -------------------------------------------------------------------------------- /src/bootstrap/bootstrap.v: -------------------------------------------------------------------------------- 1 | module bootstrap 2 | 3 | import os 4 | import net.http 5 | import x.json2 6 | 7 | // pub interface Handler { 8 | // execute(string ,string) string 9 | // } 10 | 11 | // pub struct BootstrapConfig { 12 | // mut: 13 | // handlers map[string]Handler 14 | // } 15 | 16 | pub type EventType = json2.Any 17 | 18 | pub type HandlerFunction = fn (json2.Any, Context) string 19 | 20 | pub struct BootstrapConfig { 21 | pub mut: 22 | handlers map[string]HandlerFunction 23 | } 24 | 25 | pub fn (b BootstrapConfig) process() { 26 | mut api := new_lambda_api() 27 | 28 | handler := b.handlers[api.handler] or { panic('handler "$api.handler" not found!') } 29 | 30 | for { 31 | // Get an event. The HTTP request will block until one is received 32 | event_data := api.invocation_next() or { panic('invocation api failed: $err') } 33 | dump(event_data) 34 | if event_data.status_code != 200 { 35 | panic('request not 200: $event_data.status_code') 36 | } 37 | api.update_context(event_data.header) or { panic('Extract request ID: $err') } 38 | // # Extract request ID by scraping response headers received above 39 | aws_request_id := api.context.aws_request_id 40 | 41 | content_type := event_data.header.get(http.CommonHeader.content_type) or { 42 | panic('response without content type!') 43 | } 44 | if content_type != 'application/json' { 45 | panic('expected content type application/json - got "$content_type"') 46 | } 47 | event := json2.raw_decode(event_data.text) or { panic('json decode $err') } 48 | 49 | // Run the handler function from the script 50 | handler_response := handler(event, api.context) 51 | 52 | api.response(aws_request_id, handler_response) or { panic('response: $err') } 53 | } 54 | } 55 | 56 | struct LambdaRuntimeEnvironment { 57 | aws_lambda_runtime_api string // AWS_LAMBDA_RUNTIME_API – (Custom runtime) The host and port of the runtime API. 58 | handler string // _HANDLER – The handler location configured on the function. 59 | aws_region string // AWS_REGION – The AWS Region where the Lambda function is executed. 60 | aws_execution_env string // AWS_EXECUTION_ENV – The runtime identifier, prefixed by AWS_Lambda_—for example, AWS_Lambda_java8. 61 | aws_lambda_function_name string // AWS_LAMBDA_FUNCTION_NAME – The name of the function. 62 | aws_lambda_function_memory_size string // AWS_LAMBDA_FUNCTION_MEMORY_SIZE – The amount of memory available to the function in MB. 63 | } 64 | 65 | pub struct Context { 66 | // once on init from environment 67 | memory_limit_in_mb int = os.getenv('AWS_LAMBDA_FUNCTION_MEMORY_SIZE').int() // The amount of memory available to the function in MB. 68 | function_name string = os.getenv('AWS_LAMBDA_FUNCTION_NAME') // The name of the function. 69 | function_version string = os.getenv('AWS_LAMBDA_FUNCTION_VERSION') // The version of the function being executed. 70 | log_stream_name string = os.getenv('AWS_LAMBDA_LOG_STREAM_NAME') // The name of the Amazon CloudWatch Logs group and stream for the function. 71 | log_group_name string = os.getenv('AWS_LAMBDA_LOG_GROUP_NAME') 72 | pub mut: // on every request from header 73 | client_context json2.Any // The client context sent by the AWS Mobile SDK with the invocation request. This value is returned by the Lambda Runtime APIs as a header. This value is populated only if the invocation request originated from an AWS Mobile SDK or an SDK that attached the client context information to the request. 74 | identity json2.Any // The information of the Cognito identity that sent the invocation request to the Lambda service. This value is returned by the Lambda Runtime APIs in a header and it's only populated if the invocation request was performed with AWS credentials federated through the Cognito identity service. 75 | deadline i64 // Lambda-Runtime-Deadline-Ms 76 | invoked_function_arn string // he fully qualified ARN (Amazon Resource Name) for the function invocation event. This value is returned by the Lambda Runtime APIs as a header. 77 | aws_request_id string // The AWS request ID for the current invocation event. This value is returned by the Lambda Runtime APIs as a header. 78 | xray_trace_id string // The X-Ray trace ID for the current invocation. This value is returned by the Lambda Runtime APIs as a header. Developers can use this value with the AWS SDK to create new, custom sub-segments to the current invocation. 79 | } 80 | 81 | // TODO: implement ClientContext 82 | // pub struct ClientContext { 83 | // } 84 | 85 | // type ClientContextType = json2.Any | none 86 | 87 | // TODO: implement CognitoIdentity 88 | // pub struct CognitoIdentity {} 89 | 90 | // type CognitoIdentityType = json2.Any | none 91 | 92 | struct ErrorRequest { 93 | error_message string 94 | error_type string 95 | stack_trace []string 96 | } 97 | 98 | pub fn (er ErrorRequest) to_json() string { 99 | mut obj := map[string]json2.Any{} 100 | obj['errorMessage'] = er.error_message 101 | obj['errorType'] = er.error_type 102 | mut stack_trace := []json2.Any{} 103 | for line in er.stack_trace { 104 | stack_trace << line 105 | } 106 | obj['stackTrace'] = stack_trace 107 | return obj.str() 108 | } 109 | 110 | struct LambdaAPI { 111 | aws_lambda_runtime_api string 112 | handler string 113 | // invocation_next string = 'http://${os.getenv('AWS_LAMBDA_RUNTIME_API')}/2018-06-01/runtime/invocation/next' 114 | req_incocation_next http.Request 115 | mut: 116 | context Context 117 | } 118 | 119 | fn (lr LambdaAPI) response_url(request_id string) string { 120 | return 'http://$lr.aws_lambda_runtime_api/2018-06-01/runtime/invocation/$request_id/response' 121 | } 122 | 123 | fn new_lambda_api() LambdaAPI { 124 | aws_lambda_runtime_api := os.getenv('AWS_LAMBDA_RUNTIME_API') 125 | handler := os.getenv('_HANDLER') 126 | return LambdaAPI{ 127 | aws_lambda_runtime_api: aws_lambda_runtime_api 128 | handler: handler 129 | context: Context{} 130 | req_incocation_next: http.Request{ 131 | method: http.Method.get 132 | url: 'http://$aws_lambda_runtime_api/2018-06-01/runtime/invocation/next' 133 | read_timeout: -1 // wait for ever 134 | } 135 | } 136 | } 137 | 138 | fn (lr LambdaAPI) invocation_next() ?http.Response { 139 | return lr.req_incocation_next.do() 140 | } 141 | 142 | fn (lr LambdaAPI) response(request_id string, body string) ? { 143 | http.post('http://$lr.aws_lambda_runtime_api/2018-06-01/runtime/invocation/$request_id/response', 144 | body) ? 145 | } 146 | 147 | fn (mut lr LambdaAPI) update_context(header http.Header) ? { 148 | lr.context.invoked_function_arn = header.get_custom('Lambda-Runtime-Invoked-Function-Arn') ? 149 | lr.context.aws_request_id = header.get_custom('Lambda-Runtime-Aws-Request-Id') ? 150 | if trace_id := header.get_custom('X-Amzn-Trace-Id') { 151 | lr.context.xray_trace_id = trace_id 152 | } 153 | if deadline := header.get_custom('Lambda-Runtime-Deadline-Ms') { 154 | lr.context.deadline = deadline.i64() 155 | } 156 | if cc := header.get_custom('Lambda-Runtime-Client-Context') { 157 | lr.context.client_context = json2.raw_decode(cc) ? 158 | } 159 | if identity := header.get_custom('Lambda-Runtime-Cognito-Identity') { 160 | lr.context.identity = json2.raw_decode(identity) ? 161 | } 162 | } 163 | 164 | // https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-initerror 165 | fn (lr LambdaAPI) error_initialization(category_reason string, error_request ErrorRequest) { 166 | mut header := http.new_header(key: .content_type, value: 'application/json') 167 | header.add_custom('Lambda-Runtime-Function-Error-Type', category_reason) or { panic(err) } 168 | println('http://$lr.aws_lambda_runtime_api/runtime/init/error') 169 | resp := http.fetch(http.FetchConfig{ 170 | url: 'http://$lr.aws_lambda_runtime_api/runtime/init/error' 171 | method: http.Method.post 172 | header: header 173 | data: json2.encode(error_request) 174 | }) or { panic('error post error_initialization: $err') } 175 | if resp.status_code != 202 { 176 | println(resp.text) 177 | panic('error error_initialization status_code: $resp.status_code') 178 | } 179 | exit(1) 180 | } 181 | 182 | // https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-initerror 183 | fn (lr LambdaAPI) error_invocation(category_reason string, error_request ErrorRequest, request_id string) { 184 | mut header := http.new_header(key: .content_type, value: 'application/json') 185 | header.add_custom('Lambda-Runtime-Function-Error-Type', category_reason) or { panic(err) } 186 | println('http://$lr.aws_lambda_runtime_api/runtime/init/error') 187 | resp := http.fetch(http.FetchConfig{ 188 | url: 'http://$lr.aws_lambda_runtime_api/runtime/invocation/$request_id/error' 189 | method: http.Method.post 190 | header: header 191 | data: json2.encode(error_request) 192 | }) or { panic('error post error_invocation: $err') } 193 | if resp.status_code != 202 { 194 | println(resp.text) 195 | panic('error error_invocation status_code: $resp.status_code') 196 | } 197 | exit(1) 198 | } --------------------------------------------------------------------------------