Nous venons de découvrir comment Serverless Framework permet de déployer simplement une fonction Node.JS sur AWS Lambda. Ce framework supporte également l’API Gateway d’AWS. Or, nous avions déjà vu comment exposer une fonction Lambda avec l’API Gateway. Voyons maintenant comment le faire avec ce framework.

La fonction Lambda

Pour rappel, vous pouvez initier un nouveau projet serverless avec la commande suivante :

$ serverless create --template aws-nodejs --name hello-world

Ensuite, voici la fonction lambda à exposer et qui doit se trouver dans le fichier handler.js :

'use strict';

module.exports.hello = function(event, context, callback) {
  var body = JSON.parse(event.body);
  const response = {
    statusCode: 200,
    body: JSON.stringify({ "message": "Hello " + body.name })
  };

  callback(null, response);
};

API Gateway

Une architecture serverless est essentiellement orientée événements. Dans notre cas, la fonction lambda doit être exécutée sur la réception d’un événement de l’API Gateway.

De plus, il faut savoir que Serverless Framework propose 2 modes d’intégration de l’API Gateway :

  • lambda-proxy : passe l’ensemble des éléments HTTP à la lambda et elle peut elle-même configurer l’ensemble des éléments de réponse HTTP
  • lambda : l’ensemble des éléments HTTP sont configurés dans l’API Gateway et non dans le code de la fonction lambda

La première méthode est conseillée car plus simple. C’est d’ailleurs la méthode utilisée par défaut.

La fonction lambda sera déclenchée par un appel POST sur la ressource /hello-world. Pour configurer ce comportement, il faut éditer le fichier serverless.yml et compléter la configuration de la fonction hello :

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          method: post
          path: hello-world

Vous n’avez plus qu’à demander le déploiement de l’API et de la fonction :

$ serverless deploy
Serverless: Creating Stack…
Serverless: Checking Stack create progress…
.....
Serverless: Stack create finished…
Serverless: Deprecation Notice: Starting with the next update, we will drop support for Lambda to implicitly create LogGroups. Please remove your log groups and set "provider.cfLogs: true", for CloudFormation to explicitly create them for you.
Serverless: Packaging service…
Serverless: Uploading CloudFormation file to S3…
Serverless: Uploading service .zip file to S3…
Serverless: Updating Stack…
Serverless: Checking Stack update progress…
..............................
Serverless: Stack update finished…

Service Information
service: hello-world
stage: dev
region: us-east-1
endpoints:
  POST - https://api-id.execute-api.us-east-1.amazonaws.com/dev/hello-world
functions:
  hello-world-dev-hello: arn:aws:lambda:us-east-1:client-id:function:hello-world-dev-hello

Invocation de l’API

La méthode la plus simple pour invoquer note API reste d’utiliser curl :

$ curl -d '{"name": "you"}' https://api-id.execute-api.us-east-1.amazonaws.com/dev/hello-world
{"message":"Hello you"}

Activer le CORS

Votre API est destinée à être appelée depuis des tiers. Il faut surement activer le CORS. Pour cela, il suffi ajouter un attribut dans le fichier serverless.yml :

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          method: post
          path: hello-world
          cors: true

A noter que si vous utilisez le mode lambda-proxy, il est nécessaire d’ajouter le CORS dans l’objet de réponse. Le code de la fonction lambda devient donc :

'use strict';

module.exports.hello = function(event, context, callback) {
  var body = JSON.parse(event.body);
  const response = {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Origin" : "*"
    },
    body: JSON.stringify({ "message": "Hello " + body.name })
  };

  callback(null, response);
};

Il ne reste plus qu’à déployer à nouveau l’API et tester en affichant les entêtes HTTP :

# déploiement de la lambda et de l'API
$ serverless deploy
Serverless: Deprecation Notice: Starting with the next update, we will drop support for Lambda to implicitly create LogGroups. Please remove your log groups and set "provider.cfLogs: true", for CloudFormation to explicitly create them for you.
Serverless: Packaging service…
Serverless: Uploading CloudFormation file to S3…
Serverless: Uploading service .zip file to S3…
Serverless: Updating Stack…
Serverless: Checking Stack update progress…
...............
Serverless: Stack update finished…

Service Information
service: hello-world
stage: dev
region: us-east-1
endpoints:
  POST - https://api-id.execute-api.us-east-1.amazonaws.com/dev/hello-world
functions:
  hello-world-dev-hello: arn:aws:lambda:us-east-1:client-id:function:hello-world-dev-hello

# appel de l'API
$ curl -i -d '{"name": "you"}' https://api-id.execute-api.us-east-1.amazonaws.com/dev/hello-world
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 23
Connection: keep-alive
Date: Mon, 21 Nov 2016 05:25:31 GMT
Access-Control-Allow-Origin: *

{"message":"Hello you"}

Ajouter une clé d’API

Votre API va être partagée avec des applications tiers mais vous souhaitez contrôler l’accès. Il est commun de faire ce contrôle via une clé d’API que vous fournissez à l’application cliente. L’API Gateway propose la gestion d’API Key et Serverless Framework supporte cette fonctionnalité.

Dans un premier temps, il est possible de demander la création automatique de vos clés dans le fichier serverless.yml dans la section provider.apiKeys. Voici comment demander la création de la clé sample-client :

provider:
  name: aws
  runtime: nodejs4.3
  apiKeys:
    - sample-client

Ensuite, il faut préciser pour chaque API si elle doit être sécurisée avec une clé l’API avec l’attribut private :

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          method: post
          path: hello-world
          cors: true
          private: true

Redéployez et testez :

# déploiement de l'API
$ serverless deploy
Serverless: Deprecation Notice: Starting with the next update, we will drop support for Lambda to implicitly create LogGroups. Please remove your log groups and set "provider.cfLogs: true", for CloudFormation to explicitly create them for you.
Serverless: Packaging service…
Serverless: Uploading CloudFormation file to S3…
Serverless: Uploading service .zip file to S3…
Serverless: Updating Stack…
Serverless: Checking Stack update progress…
...............
Serverless: Stack update finished…

Service Information
service: hello-world
stage: dev
region: us-east-1
api keys:
  sample-client: 6W6OqWFAaq8aw7jYXKlLo5lxp5zCJFOh8VrNt32V
endpoints:
  POST - https://api-id.execute-api.us-east-1.amazonaws.com/dev/hello-world
functions:
  hello-world-dev-hello: arn:aws:lambda:us-east-1:client-id:function:hello-world-dev-hello

# test sans la clé d'API
$ curl -d '{"name": "you"}' https://api-id.execute-api.us-east-1.amazonaws.com/dev/hello-world
{"message":"Forbidden"}

# test avec la clé d'API
$ curl -d '{"name": "you"}' -H 'x-api-key: 6W6OqWFAaq8aw7jYXKlLo5lxp5zCJFOh8VrNt32V' https://api-id.execute-api.us-east-1.amazonaws.com/dev/hello-world
{"message":"Forbidden"}

Comme vous avez pu le constater, l’appel à l’API est refusé même en fournissant la clé d’API en entête HTTP. Cette erreur est due à la nouvelle fonctionnalité de Usage Plan proposée par l’API Gateway. Cette notion est obligatoire pour les clés d’API mais n’est pas encore supportée par Serverless Framework. Enfin c’est même l’association d’une clé d’API avec un plan d’utilisation qui n’est pas encore supporté par CloudFormation. Nous palier à ce problème, nous devons créer le plan à la main :

# création du plan d'utilisation
$ aws apigateway create-usage-plan --name dev-hello-world-plan --api-stages apiId=api-id,stage=dev --region us-east-1
{
    "apiStages": [
        {
            "apiId": "api-id",
            "stage": "dev"
        }
    ],
    "id": "wtru1v",
    "name": "dev-hello-world-plan"
}

# récupération de l'identifiant de la clé d'API
aws apigateway get-api-keys --name-query sample-client --region us-east-1
{
    "items": [
        {
            "name": "sample-client",
            "enabled": true,
            "stageKeys": [
                "api-id/dev"
            ],
            "lastUpdatedDate": 1479342533,
            "createdDate": 1479342533,
            "id": "e9bo3lrzsi "
        }
    ]
}

# association de la clé au plan
$ aws apigateway create-usage-plan-key --usage-plan-id wtru1v --key-id e9bo3lrzsi --key-type "API_KEY" --region us-east-1
{
    "type": "API_KEY",
    "id": "e9bo3lrzsi",
    "name": "sample-client"
}

# appel de l'API avec la clé
$ curl -d '{"name": "you"}' -H 'x-api-key: 6W6OqWFAaq8aw7jYXKlLo5lxp5zCJFOh8VrNt32V' https://api-id.execute-api.us-east-1.amazonaws.com/dev/hello-world
{"message":"Hello you"}

Nettoyage

Après avoir testé l’intégration de l’API Gateway et AWS Lambda, un petit nettoyage s’impose.

$ serverless remove
Serverless: Getting all objects in S3 bucket…
Serverless: Removing objects in S3 bucket…
Serverless: Removing Stack…
Serverless: Checking Stack removal progress…
......................
Serverless: Stack removal finished…
$ aws logs delete-log-group --log-group-name "/aws/lambda/hello-world-dev-hello"

Conclusion

C’est avec l’exposition d’une fonction lambda au travers de l’API Gateway que Serverless Framework prend toute sa dimension. En effet, il ne faut que 4 lignes de configuration YAML pour exposer la fonction sous forme d’API REST. De même, l’ajout de fonctionnalités telles que le CORS ou l’API Key se révèle très simple.
Néanmoins, cet exemple nous montre qu’en ajoutant une couche d’abstraction, nous devenons dépendant du support des évolutions de la plateforme AWS par ce framework.