GithubHelp home page GithubHelp logo

awslabs / aws-lambda-dart-runtime Goto Github PK

View Code? Open in Web Editor NEW
144.0 12.0 29.0 2.22 MB

A Dart runtime for AWS Lambda

Home Page: https://awslabs.github.io/aws-lambda-dart-runtime/

License: Apache License 2.0

Shell 0.48% Dart 99.52%
dart lambda aws

aws-lambda-dart-runtime's Introduction

Dart Runtime for AWS Lambda

Main License Apache 2

A ๐ŸŽฏ Dart Runtime for ฦ› AWS Lambda


Read Introducing a Dart runtime for AWS Lambda

๐Ÿš€ Experimental support for โšก๏ธ serverless framework

If you need to access AWS APIs in your Lambda function, please search on pub.dev for packages provided by Agilord

Features

  • Great performance < 10ms on event processing and < 50MB memory consumption
  • No need to ship the Dart runtime
  • Multiple event handlers
  • Typed events
  • Custom events

this package requires Dart >= 2.6 currently dart2native only supports building for the platform it is run on, so you must either build on a Linux machine or use docker

๐Ÿš€ Introduction

Dart is an unsupported AWS Lambda runtime language. However, with a custom runtime you can support virtually every programming language.

There are two ways in which you could use Dart. You could bundle the Dart Runtime in a Lambda layer and use JIT compilation within the lambda execution to run a Dart program. The other is to compile a shippable binary of the Dart program.

Dart >= 2.6 introduced dart2native. The tool uses AOT (ahead-of-time) to compile a Dart program to native x64 machine code. This standalone executable is native machine code that's compiled from the specific Dart file and its dependencies, plus a small Dart runtime that handles type checking and garbage collection.

We decided to use the latter approach rather then the just-in-time compilation of Dart files. The main reason for this decision is that we wanted to avoid having to ship and maintain a standalone Dart runtime version. We would eventually have to deprecate versions, or always update the version when moving forward. Furthermore, shipping a binary has the advantage of having an always runnable version of your function in addition to performance benefits.

We want to highlight Firecracker open-source innovation from re:Invent 2019 which gives you a brief overview of Firecracker which is the underlying technology of AWS Lambda.

๐Ÿ“ฆ Use

Add the following snippet to your pubspec file in pubspec.yaml.

dependencies:
  aws_lambda_dart_runtime: ^1.0.3+2

Docs are available. They are also accessible in the docs folder.

# access the docs local
pub global activate dhttpd
dhttpd --path docs

you can generate the docs with dartdoc --output docs

Build and deploy the Dart functions by the serverless framework or by custom deployment.

๐Ÿงช Serverless Framework (experimental)

Checkout serverless-dart to create your functions with serverless.

You can start your next project using the serverless-aws-dart template.

$ npx serverless install \
  --url https://github.com/katallaxie/serverless-aws-dart \
  --name hello

Every serverless workflow command should work out of the box. The template also includes an example GitHub actions configuration file which can unlock a virtuous cycle of continuous integration and deployment ( i.e all tests are run on prs and every push to master results in a deployment).

Custom deployment

The deployment is a manual task right now. We have a example/build.sh script which makes the process a bit easier. There are three steps to get your code ready to be shipped.

  1. Compile your Dart program with dart2native main.dart -o bootstrap
  2. Create a .zip file with zip lambda.zip bootstrap
  3. Upload the lambda.zip to a S3 bucket or use the AWS CLI to upload it

again, you have to build this on Linux, because dart2native does not support cross-compiling

When you created your function and upload it via the the console. Please, replace arn:aws:iam::xxx:xxx with the role you created for your lambda.

aws lambda create-function --function-name dartTest \
  --handler hello.apigateway \
  --zip-file fileb://./lambda.zip \
  --runtime provided \
  --role arn:aws:iam::xxx:xxx \
  --environment Variables={DART_BACKTRACE=1} \
  --tracing-config Mode=Active

Updating a function is a fairly easy task. Rebuild your lambda.zip package and execute the following command.

aws lambda update-function-code --function-name dartTest --zip-file fileb://./lambda.zip

Events

There are a number of events that come with the Dart Runtime.

  • Application Load Balancer
  • Alexa
  • API Gateway
  • AppSync
  • Cloudwatch
  • Cognito
  • DynamoDB
  • Kinesis
  • S3
  • SQS

You can also register custom events.

import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart';

class MyCustomEvent {
  factory MyCustomEvent.fromJson(Map<String, dynamic> json) =>
      MyCustomEvent(json);

  const MyCustomEvent();
}

void main() async {
  final Handler<MyCustomEvent> successHandler =
      (context, event) async {
    return InvocationResult(context.requestId, "SUCCESS");
  };

  Runtime()
    ..registerEvent<MyCustomEvent>((Map<String, dynamic> json) => MyCustomEvent.from(json))
    ..registerHandler<MyCustomEvent>("doesnt.matter", successHandler)
    ..invoke();
}

Example

The example in main.dart show how the package is intended to be used. Because dart2native does not support cross-platform compilation, you can use the google/dart (:warning: if you are on Linux you can ignore this) container to build the project. The build.sh script automates the build process in the container.

  # will build the binary in the container
  cd example; docker run -v $PWD:/app --entrypoint app/build.sh google/dart && zip lambda.zip bootstrap && rm bootstrap

You will see the lambda.zip which you can upload manually, or use the client of your choice.

What you see in the example is an example of the interface to the Dart Runtime that we created.

You will have to make aws_lambda_dart_runtime a dependency of your project.

...
dependencies:
  aws_lambda_dart_runtime:
...

We support using multiple handlers in one executable. The following example shows to register one handler.

import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart';

void main() async {
  /// This demo's handling an API Gateway request.
  final Handler<AwsApiGatewayEvent> helloApiGateway = (context, event) async {
    final response = {"message": "hello ${context.requestId}"};

    /// it returns an encoded response to the gateway
    return InvocationResult(
        context.requestId, AwsApiGatewayResponse.fromJson(response));
  };

  /// The Runtime is a singleton. You can define the handlers as you wish.
  Runtime()
    ..registerHandler<AwsApiGatewayEvent>("hello.apigateway", helloApiGateway)
    ..invoke();
}

This example registers the hello.apigateway handler with the function to execute for this handler. The handler function is typed to receive a Amazon API Gateway Event and it returns a response to the invoking gateway. We support many other events. Handler functions get a Context injected with the needed information about the invocation. You can also register your own custom events via Runtime.registerEvent<T>(Handler<T>) (see events).

Limitations

  • No Just-in-time (JIT) support
  • Requires Dart >= 2.6
  • No cross-platform compile support (see #28617).

Development

If you want to use the Repository directly you can clone it and overwrite the dependency in your pubspec.yaml as follows.

dependency_overrides:
  aws_lambda_dart_runtime:
    path: <path_to_source>

The data folder contains examples of the used events. We use this to run our tests, but you can also use them to implement new features. If you want to request the processing of a new event, you may provide a payload here.

# run the tests
pub run test

License

Apache 2.0

We ๐Ÿ’™ Dart.

aws-lambda-dart-runtime's People

Contributors

ekremkenter avatar kapilt avatar katallaxie avatar michaelcharles avatar schwusch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aws-lambda-dart-runtime's Issues

[Request] Better error message when there is no handler

When there is no registered handler(or e.g. misspelled handler) this error is printed:

Unhandled exception:
NoSuchMethodError: The getter 'type' was called on null.
Receiver: null
Tried calling: type
#0      Runtime.invoke (package:aws_lambda_dart_runtime/runtime/runtime.dart:107)

It is a little bit confusing and hard to track down the cause(no registered handler by that name).

CDK examples needed, amazing work so far

Would be great to CDK support and examples in the readme on how to use this with CDK.

  • Easily reference a dart file using CDK lambda dir (entry or code) refs.
  • Have CDK build and deploy the dart lambda automatically.

AwsApiGatewayEventHeaders does not pass through custom headers

This package has been working great for me in prod! I ran into a use case today that I thought could be improvd. AwsApiGatewayEventHeaders is parsing some standard headers like userAgent. This is great however I can not get access to custom headers or even simple caching headers like If-None-Match.

Would it be possible to add raw or rawHeaders (naming is hard) to AwsApiGatewayEventHeaders? This would expose a way to access any possible header that comes through while keeping the existing functionality.

Example:

event.headers.raw['x-my-custom-header']

or 

event.headers.raw.contains('x-my-custom-header')

raw here being the entire headers map.

`nextInvocation` could be null for certain exceptions

The runtime's invoke function contains an exception catcher while getting the next invocation from AWS API. It goes like this,

} on Exception catch (error, stacktrace) {
    await _client.postInvocationError(
		nextInvocation.requestId, InvocationError(error, stacktrace));
}

The code assumes that nextInvocation is never null and contains the requestId. But there are certain sceranios where the nextInvocation is null. One of them is when the HTTP request inside the _client.getInvocation function throws an HttpException: Connection closed before full header was received exception.

// inside invoke function's try closure
nextInvocation = await _client.getNextInvocation();

// inside the `getNextInvocation` function
final request = await _client.getUrl(Uri.parse(
    'http://${runtimeApi}/${runtimeApiVersion}/runtime/invocation/next'));
final response = await request.close();
return NextInvocation.fromResponse(response);

The error is reproducible and happens on multiple & regular occasions. When it happens, the requestId is being called on null and throws a process exception and never ends the request. But it could be easily solved by checking whether the nextInvocation is null or not.

} on Exception catch (error, stacktrace) {
	if (nextInvocation != null) {
	      await _client.postInvocationError(
			  nextInvocation.requestId, InvocationError(error, stacktrace));
	}
}

I'll submit a PR.

Match this with client-side features to communicate with Lambda, and more.

This is really amazing for the server side. Congrats to AWS for finally acknowledging the existence of Flutter. However, for AWS to really be able to compete with Firebase in Flutter, you need a few more things in the CLIENT-SIDE:

โ€ข Auth (Cognito) โžœ So that the lambda can authenticate the users.

โ€ข An easier way to send/receive Json to Lambda (a few functions to use instead of forcing people to manually use https).

โ€ข AppSync โžœ This one is the most important for you to compete with Firebase. We need realtime. It may use Platform Channels, but it would be perfect if it were all done in Dart.

โ€ข An easier way to send files to S3. (a few functions to use instead of forcing people to manually use https).

[Question] How can I test the lambda?

I am, not just unit testing, but the app itself. When I try to run it I get the error:

Unhandled exception:
NoSuchMethodError: The getter 'requestId' was called on null.
Receiver: null
Tried calling: requestId
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1      Runtime.invoke (package:aws_lambda_dart_runtime/runtime/runtime.dart:117:28)
<asynchronous suspension>
#2      main (file:///C:/Users/shina/Desenvolvimento/dart_projects/lambda_test/bin/lambda_test.dart:62:7)
#3      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#4      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

There is a way to run it (for testing purposes) locally, without having to upload the zip file to aws?

Publish on pub.dev

First of all THANKYOU for such amazing repository.

There is any reason for why this project isn't listed on pub.dev?
Pub is the dart's official package repository and is often used by other companies to search for the capabilities of a language. Having an AWS Lambda support there would be a great showcase for the language.

Downside, everything must be marked async/await

The whole program has to be instrumented with async/await. As everything must be waited for to properly complete before your lambda function handler returns. No events can be left on the event queue when your handler returns or they will non-deterministically fail.

AWS freezes the lambda process between lambda calls (which it identify is over when you return a response from your handler). Anything left going after that in the background will see a timejump to when the lambda is called next, and will see its in-use connections reset.

Ideally the framework would wait until the event queue is empty (remove the frameworks own events). But dart might not expose the ability to do this in its own API. So perhaps just line in the documentation to warn about this issue.

Null safe migration

Is there anything on the roadmap to migrate this package to null safety? ๐ŸŽ‰

Posting invocation error fails some times

When the runtime tries posting an invocation error I got this

Unhandled exception:
type '_InternalLinkedHashMap<String, String>' is not a subtype of type 'String'
#0      Client.postInvocationError (package:aws_lambda_dart_runtime/client/client.dart:156)
#1      _RootZone.runUnary (dart:async/zone.dart:1381)
#2      _FutureListener.handleValue (dart:async/future_impl.dart:139)
#3      Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:680)
#4      Future._propagateToListeners (dart:async/future_impl.dart:709)
#5      Future._completeWithValue (dart:async/future_impl.dart:524)
#6      Future._asyncComplete.<anonymous closure> (dart:async/future_impl.dart:554)
#7      _microtaskLoop (dart:async/schedule_microtask.dart:43)
#8      _startMicrotaskLoop (dart:async/schedule_microtask.dart:52)
#9      _Timer._runTimers (dart:isolate-patch/timer_impl.dart:393)
#10     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:418)
#11     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174)

When I changed this line:

request.add(utf8.encode(err.error.toJson()));

to this:

request.add(utf8.encode(jsonEncode(err.error)));

It got past that line. If the error object has a toJson() (which is not guaranteed), customary in Dart is to return Map<String, dynamic>, which is not what utf8.encode() expects.
I think a more stable solution is to simply call toString() on the error object.

Receiving error Runtime.InvalidEntrypoint

Hey guys, I am trying to develop my first function using this dart runtime but I my tests giving this error:

Response:
{
  "errorMessage": "RequestId: a81f0097-a81f-48ac-88e7-cdecf090bc91 Error: fork/exec /var/task/bootstrap: permission denied",
  "errorType": "Runtime.InvalidEntrypoint"
}

Request ID:
"a81f0097-a81f-48ac-88e7-cdecf090bc91"

Function logs:
START RequestId: a81f0097-a81f-48ac-88e7-cdecf090bc91 Version: $LATEST
END RequestId: a81f0097-a81f-48ac-88e7-cdecf090bc91
REPORT RequestId: a81f0097-a81f-48ac-88e7-cdecf090bc91	Duration: 0.88 ms	Billed Duration: 1 ms	Memory Size: 128 MB	Max Memory Used: 6 MB	
RequestId: a81f0097-a81f-48ac-88e7-cdecf090bc91 Error: fork/exec /var/task/bootstrap: permission denied
Runtime.InvalidEntrypoint

The weird stuff is that in my console, it shows a tab with 'null' title on it with a spinner that never stops spinning:

image

This is my dart code:

import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart';

void main() async {
  print('STARTING THIS LAMDBA TEST IN DART!');

  /// This demo's handling an API Gateway request.
  final helloApiGateway = (context, event) async {
    final response = {
      'message': 'hello ${context.requestId}',
    };

    /// it returns an encoded response to the gateway
    return InvocationResult(
      context.requestId,
      AwsApiGatewayResponse.fromJson(response),
    );
  };

  /// The Runtime is a singleton. You can define the handlers as you wish.
  Runtime()
    ..registerHandler<AwsApiGatewayEvent>('hello.apigateway', helloApiGateway)
    ..invoke();
}

And this is the test event I am using:

{
    "requestContext": {
        "elb": {
            "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a"
        }
    },
    "httpMethod": "GET",
    "path": "/lambda",
    "queryStringParameters": {
        "query": "1234ABCD"
    },
    "headers": {
        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "accept-encoding": "gzip",
        "accept-language": "en-US,en;q=0.9",
        "connection": "keep-alive",
        "host": "lambda-alb-123578498.us-east-2.elb.amazonaws.com",
        "upgrade-insecure-requests": "1",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
        "x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476",
        "x-forwarded-for": "72.12.164.125",
        "x-forwarded-port": "80",
        "x-forwarded-proto": "http",
        "x-imforwards": "20"
    },
    "body": "",
    "isBase64Encoded": false
}

This event test can, maybe, be causing this error?

Please Add Contributors

Hello, I use this package and would be happy to help maintain it and publish updates to pub.dev. @katallaxie if you are no longer able or willing to work on this package could you please add me as a contributor here on github and pub.dev?

AwsApiGatewayResponse constructor arguments is all dynamic due to missing type signatures

It is a bit confusing that all constructor parameters is dynamic, when supplying the wrong type will throw a runtime error.

AwsApiGatewayResponse({
    body, 
    isBase64Encoded,
    headers,
    statusCode,
  }) {
    this.body = body ?? '';
    this.isBase64Encoded = isBase64Encoded ?? false;
    this.headers = headers ?? {"Content-Type": "application/json"};
    this.statusCode = statusCode ?? HttpStatus.ok;
  }

It would make the API more friendly and less error prone if the parameters had types:

AwsApiGatewayResponse({
    String body, 
    bool isBase64Encoded,
    Map<String, String> headers,
    int statusCode,
  }) {
    this.body = body ?? '';
    this.isBase64Encoded = isBase64Encoded ?? false;
    this.headers = headers ?? {"Content-Type": "application/json"};
    this.statusCode = statusCode ?? HttpStatus.ok;
  }

Converting object to an encodable object failed for Instance of InvocationResult ApiGateway

I am trying to follow api gateway example in readme
My code looks like

import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart';

void main() async {
  print("hello from inside dart 1");

  /// This demo's handling an API Gateway request.
  final Handler<AwsApiGatewayEvent> helloApiGateway = (context, event) async {
    print("hello from inside dart 2");
    final response = {"message": "hello ${context.requestId}"};
    print("hello from inside dart 3");

    /// it returns an encoded response to the gateway
    final resp = InvocationResult(
        context.requestId, AwsApiGatewayResponse.fromJson(response));

    print("hello from inside dart 4");
    return resp;
  };

  /// The Runtime is a singleton. You can define the handlers as you wish.
  Runtime()
    ..registerHandler<AwsApiGatewayEvent>("main.hello", helloApiGateway)
    ..invoke();
}

When endpoint invoked via curl, I get
{"message": "Internal server error"}
but I see all hello... prints in the lambda cloudwatch logs and request end without any exception.
When enpoint is invoked using AWS console for apigateway test UI, I see

Received response. Status: 200, Integration latency: 24 ms
Thu May 27 23:15:24 UTC 2021 : Endpoint response headers: {Date=Thu, 27 May 2021 23:15:24 GMT, Content-Type=application/json, Content-Length=128, Connection=keep-alive, x-amzn-RequestId=d71971ef-c9f7-4999-a306-2cec2aee846d, X-Amz-Function-Error=Unhandled, x-amzn-Remapped-Content-Length=0, X-Amz-Executed-Version=$LATEST, X-Amzn-Trace-Id=root=1-60b0280c-20ab219fdb20cbc96a699b53;sampled=0}
Thu May 27 23:15:24 UTC 2021 : Endpoint response body before transformations: {"errorMessage":"Converting object to an encodable object failed: Instance of 'InvocationResult'","errorType":"InvocationError"}
Thu May 27 23:15:24 UTC 2021 : Lambda execution failed with status 200 due to customer function error: Converting object to an encodable object failed: Instance of 'InvocationResult'. Lambda request id: d71971ef-c9f7-4999-a306-2cec2aee846d
Thu May 27 23:15:24 UTC 2021 : Method completed with status: 502

I tried changing line
return resp; to ut8.encode(json.encode(resp)) and a few other things. No luck.
I am not sure what to do. Any help.

I am deploying using serverless.
I had to set the runtime as provided.al2 since it would not do it with runtime as dart

AwsAlexaEvent parsing problem

Check this for the parsing of the AwsAlexaEvent

image

It is expecting and header and a payload object.

The problem is, when we are testing the lambdas on aws, this is the json provided by the lambda testing tool:

{
  "version": "1.0",
  "session": {
    "new": true,
    "sessionId": "amzn1.echo-api.session.123456789012",
    "application": {
      "applicationId": "amzn1.ask.skill.987654321"
    },
    "user": {
      "userId": "amzn1.ask.account.testUser"
    },
    "attributes": {}
  },
  "context": {
    "AudioPlayer": {
      "playerActivity": "IDLE"
    },
    "System": {
      "application": {
        "applicationId": "amzn1.ask.skill.987654321"
      },
      "user": {
        "userId": "amzn1.ask.account.testUser"
      },
      "device": {
        "supportedInterfaces": {
          "AudioPlayer": {}
        }
      }
    }
  },
  "request": {
    "type": "LaunchRequest",
    "requestId": "amzn1.echo-api.request.1234",
    "timestamp": "2016-10-27T18:21:44Z",
    "locale": "en-US"
  }
}

As you can see there is no header or payload objects on this JSON, with ends by returning an empty AwsAlexaEvent.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.