Utilisation d’AWS Lambda avec API Gateway

Utilisation de AWS Lambda avec API Gateway

1. Vue d'ensemble

AWS Lambda est un service informatique sans serveur fourni par Amazon Web Services.

Dans deux articles précédents, nous avons discuté dehow to create an AWS Lambda function using Java, ainsi que dehow to access DynamoDB from a Lambda function.

Dans ce didacticiel, nous aborderonshow to publish a Lambda function as a REST endpoint, using AWS Gateway.

Nous examinerons en détail les sujets suivants:

  • Concepts de base et termes de la passerelle API

  • Intégration des fonctions Lambda à API Gateway à l'aide de l'intégration du mandataire Lambda

  • Création d'une API, sa structure et comment mapper les ressources de l'API sur les fonctions Lambda

  • Déploiement et test de l'API

2. Principes de base et conditions

API Gateway est unfully managed service that enables developers to create, publish, maintain, monitor, and secure APIs at any scale.

Nous pouvons implémenter une interface de programmation basée sur HTTP cohérente et évolutive (également appelée services RESTful)to access backend services like Lambda functions, further AWS services (e.g., EC2, S3, DynamoDB), and any HTTP endpoints.

Les fonctionnalités incluent, mais ne sont pas limitées à:

  • Gestion du trafic routier

  • Autorisation et contrôle d'accès

  • surveillance

  • Gestion de version d'API

  • Limiter les requêtes pour empêcher les attaques

Comme AWS Lambda, la passerelle API est automatiquement mise à l'échelle et facturée par appel à l'API.

Des informations détaillées peuvent être trouvées dans lesofficial documentation.

2.1. termes

API Gateway est un service AWS qui prend en charge la création, le déploiement et la gestion d'une interface de programmation d'application RESTful pour exposer les points de terminaison HTTP backend, les fonctions AWS Lambda et d'autres services AWS.

UnAPI Gateway API est une collection de ressources et de méthodes qui peuvent être intégrées aux fonctions Lambda, à d'autres services AWS ou aux points de terminaison HTTP dans le backend. L'API est constitué de ressources qui forment la structure de l'API. Chaque ressource API peut exposer une ou plusieurs méthodes API qui doivent avoir des verbes HTTP uniques.

Pour publier une API, nous devons créer unAPI deployment et l'associer à un soi-disantstage. Une étape est comme un instantané dans le temps de l'API. Si nous redéployons une API, nous pouvons mettre à jour une étape existante ou en créer une nouvelle. Par cela, différentes versions d'une API en même temps sont possibles, par exemple une étapedev, une étapetest, et même plusieurs versions de production, commev1,v2, etc.

Lambda Proxy integration est une configuration simplifiée pour l'intégration entre les fonctions Lambda et API Gateway.

La passerelle API envoie l'intégralité de la demande en tant qu'entrée à une fonction Lambda principale. En ce qui concerne les réponses, API Gateway transforme la sortie de la fonction Lambda en une réponse HTTP frontale.

3. Les dépendances

Nous aurons besoin des mêmes dépendances que dans l'articleAWS Lambda Using DynamoDB With Java.

En plus de cela, nous avons également besoin de la bibliothèqueJSON Simple:


    com.googlecode.json-simple
    json-simple
    1.1.1

4. Développement et déploiement des fonctions Lambda

Dans cette section, nous développerons et créerons nos fonctions Lambda en Java, nous les déploierons à l'aide d'AWS Console et nous exécuterons un test rapide.

Afin de démontrer les fonctionnalités de base de l'intégration d'API Gateway à Lambda, nous allons créer deux fonctions:

  • Function 1: reçoit une charge utile de l'API, en utilisant une méthode PUT

  • Function 2: montre comment utiliser un paramètre de chemin HTTP ou un paramètre de requête HTTP provenant de l'API

Au niveau de l'implémentation, nous allons créer une classeRequestHandler, qui a deux méthodes - une pour chaque fonction.

4.1. Modèle

Avant de mettre en œuvre le gestionnaire de requêtes, jetons un coup d'œil à notre modèle de données:

public class Person {

    private int id;
    private String name;

    public Person(String json) {
        Gson gson = new Gson();
        Person request = gson.fromJson(json, Person.class);
        this.id = request.getId();
        this.name = request.getName();
    }

    public String toString() {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        return gson.toJson(this);
    }

    // getters and setters
}

Notre modèle se compose d'une simple classePerson, qui a deux propriétés. La seule partie notable est le constructeurPerson(String), qui accepte une chaîne JSON.

4.2. Implémentation de la classe RequestHandler

Tout comme dans l'articleAWS Lambda With Java, nous allons créer une implémentation de l'interfaceRequestStreamHandler:

public class APIDemoHandler implements RequestStreamHandler {

    private static final String DYNAMODB_TABLE_NAME = System.getenv("TABLE_NAME");

    @Override
    public void handleRequest(
      InputStream inputStream, OutputStream outputStream, Context context)
      throws IOException {

        // implementation
    }

    public void handleGetByParam(
      InputStream inputStream, OutputStream outputStream, Context context)
      throws IOException {

        // implementation
    }
}

Comme nous pouvons le voir, l'interfaceRequestStreamHander ne définit qu'une seule méthode,handeRequest(). Quoi qu'il en soit, nous pouvons définir d'autres fonctions dans la même classe, comme nous l'avons fait ici. Une autre option serait de créer une implémentation deRequestStreamHander pour chaque fonction.

Dans notre cas spécifique, nous avons choisi le premier pour la simplicité. Cependant, le choix doit être fait au cas par cas, en tenant compte de facteurs tels que les performances et la maintenabilité du code.

Nous lisons également le nom de notre table DynamoDB à partir de la variable d'environnementTABLE_NAME . Nous définirons cette variable plus tard lors du déploiement.

4.3. Mise en œuvre de la fonction 1

Dans notre première fonction, nous voulons démontrerhow to get a payload (like from a PUT or POST request) from the API Gateway:

public void handleRequest(
  InputStream inputStream,
  OutputStream outputStream,
  Context context)
  throws IOException {

    JSONParser parser = new JSONParser();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    JSONObject responseJson = new JSONObject();

    AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient();
    DynamoDB dynamoDb = new DynamoDB(client);

    try {
        JSONObject event = (JSONObject) parser.parse(reader);

        if (event.get("body") != null) {
            Person person = new Person((String) event.get("body"));

            dynamoDb.getTable(DYNAMODB_TABLE_NAME)
              .putItem(new PutItemSpec().withItem(new Item().withNumber("id", person.getId())
                .withString("name", person.getName())));
        }

        JSONObject responseBody = new JSONObject();
        responseBody.put("message", "New item created");

        JSONObject headerJson = new JSONObject();
        headerJson.put("x-custom-header", "my custom header value");

        responseJson.put("statusCode", 200);
        responseJson.put("headers", headerJson);
        responseJson.put("body", responseBody.toString());

    } catch (ParseException pex) {
        responseJson.put("statusCode", 400);
        responseJson.put("exception", pex);
    }

    OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8");
    writer.write(responseJson.toString());
    writer.close();
}

Comme indiqué précédemment, nous configurerons l'API ultérieurement pour utiliser l'intégration du proxy Lambda. Nous nous attendons à ce que la passerelle API transmette la requête complète à la fonction Lambda dans le paramètreInputStream.

Tout ce que nous avons à faire est de sélectionner les attributs pertinents dans la structure JSON contenue.

Comme on peut le constater, la méthode comprend essentiellement trois étapes:

  1. Récupérer l'objetbody de notre flux d'entrée et créer un objetPerson à partir de celui-ci

  2. Stockage de cet objetPerson dans une table DynamoDB

  3. Construire un objet JSON, qui peut contenir plusieurs attributs, comme unbody pour la réponse, des en-têtes personnalisés, ainsi qu'un code d'état HTTP

Un point à mentionner ici: API Gateway s'attend à ce que lesbody soient desString (à la fois pour la demande et la réponse).

Comme nous nous attendons à obtenir unString en tant quebody de la passerelle API, nous convertissons lesbody enString et initialisons notre objetPerson:

Person person = new Person((String) event.get("body"));

API Gateway s'attend également à ce que la réponsebody soit unString:

responseJson.put("body", responseBody.toString());

Ce sujet n'est pas mentionné explicitement dans la documentation officielle. Cependant, si nous y regardons de près, nous pouvons voir que l'attribut body est unString dans les deux extraitsfor the request ainsi quefor the response.

L'avantage doit être clair: même si JSON est le format entre API Gateway et la fonction Lambda, le corps réel peut contenir du texte brut, JSON, XML ou autre. Il est ensuite de la responsabilité de la fonction Lambda de gérer correctement le format.

Nous verrons à quoi ressemblent la requête et le corps de la réponse plus tard, lorsque nous testerons nos fonctions dans AWS Console.

Il en va de même pour les deux fonctions suivantes.

4.4. Implementation of Fonction 2

Dans un deuxième temps, nous voulons démontrerhow to use a path parameter or a query string parameter pour récupérer un élémentPerson de la base de données en utilisant son ID:

public void handleGetByParam(
  InputStream inputStream, OutputStream outputStream, Context context)
  throws IOException {

    JSONParser parser = new JSONParser();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    JSONObject responseJson = new JSONObject();

    AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient();
    DynamoDB dynamoDb = new DynamoDB(client);

    Item result = null;
    try {
        JSONObject event = (JSONObject) parser.parse(reader);
        JSONObject responseBody = new JSONObject();

        if (event.get("pathParameters") != null) {
            JSONObject pps = (JSONObject) event.get("pathParameters");
            if (pps.get("id") != null) {
                int id = Integer.parseInt((String) pps.get("id"));
                result = dynamoDb.getTable(DYNAMODB_TABLE_NAME).getItem("id", id);
            }
        } else if (event.get("queryStringParameters") != null) {
            JSONObject qps = (JSONObject) event.get("queryStringParameters");
            if (qps.get("id") != null) {

                int id = Integer.parseInt((String) qps.get("id"));
                result = dynamoDb.getTable(DYNAMODB_TABLE_NAME)
                  .getItem("id", id);
            }
        }
        if (result != null) {
            Person person = new Person(result.toJSON());
            responseBody.put("Person", person);
            responseJson.put("statusCode", 200);
        } else {
            responseBody.put("message", "No item found");
            responseJson.put("statusCode", 404);
        }

        JSONObject headerJson = new JSONObject();
        headerJson.put("x-custom-header", "my custom header value");

        responseJson.put("headers", headerJson);
        responseJson.put("body", responseBody.toString());

    } catch (ParseException pex) {
        responseJson.put("statusCode", 400);
        responseJson.put("exception", pex);
    }

    OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8");
    writer.write(responseJson.toString());
    writer.close();
}

Encore une fois, trois étapes sont pertinentes:

  1. Nous vérifions si un tableaupathParameters ouqueryStringParameters avec un attributid est présent.

  2. Sitrue, nous utilisons la valeur d'appartenance pour demander un élémentPerson avec cet ID à partir de la base de données.

  3. Nous ajoutons une représentation JSON de l'élément reçu à la réponse.

La documentation officielle fournit une explication plus détaillée deinput format etoutput format pour l'intégration de proxy.

4.5. Code de construction

Encore une fois, nous pouvons simplement construire notre code en utilisant Maven:

mvn clean package shade:shade

Le fichier JAR sera créé dans le dossiertarget.

4.6. Création de la table DynamoDB

Nous pouvons créer la table comme expliqué dansAWS Lambda Using DynamoDB With Java.

ChoisissonsPerson comme nom de table,id comme nom de clé primaire etNumber comme type de clé primaire.

4.7. Déploiement du code via AWS Console

Après avoir construit notre code et créé la table, nous pouvons maintenant créer les fonctions et télécharger le code.

Cela peut être fait en répétant les étapes 1 à 5 de l'articleAWS Lambda with Java, une fois pour chacune de nos deux méthodes.

Utilisons les noms de fonction suivants:

  • StorePersonFunction pour la méthodehandleRequest (fonction 1)

  • GetPersonByHTTPParamFunction  pour la méthodehandleGetByParam (fonction 2)

Nous devons également définir une variable d'environnementTABLE_NAME avec la valeur“Person”.

4.8. Test des fonctions

Avant de continuer avec la partie API Gateway, nous pouvons exécuter un test rapide dans la console AWS, juste pour vérifier que nos fonctions Lambda s'exécutent correctement et peuvent gérer le format Proxy Integration.

Le test d'une fonction Lambda à partir de la console AWS fonctionne comme décrit dans l'article deAWS Lambda with Java.

Cependant,when we create a test event, we have to consider the special Proxy Integration format, que nos fonctions attendent. Nous pouvons soit utiliser le modèleAPI Gateway AWS Proxy et le personnaliser selon nos besoins, soit copier et coller les événements suivants:

Pour lesStorePersonFunction, nous devrions utiliser ceci:

{
    "body": "{\"id\": 1, \"name\": \"John Doe\"}"
}

Comme indiqué précédemment, lesbody doivent avoir le typeString, même s'ils contiennent une structure JSON. La raison en est que la passerelle API enverra ses demandes dans le même format.

La réponse suivante devrait être renvoyée:

{
    "isBase64Encoded": false,
    "headers": {
        "x-custom-header": "my custom header value"
    },
    "body": "{\"message\":\"New item created\"}",
    "statusCode": 200
}

Ici, nous pouvons voir que lebody de notre réponse est unString, bien qu'il contienne une structure JSON.

Regardons l'entrée pour lesGetPersonByHTTPParamFunction.

Pour tester la fonctionnalité du paramètre path, l'entrée ressemblerait à ceci:

{
    "pathParameters": {
        "id": "1"
    }
}

Et l'entrée pour l'envoi d'un paramètre de chaîne de requête serait:

{
    "queryStringParameters": {
        "id": "1"
    }
}

En réponse, nous devrions obtenir les méthodes suivantes pour les deux cas:

{
  "headers": {
    "x-custom-header": "my custom header value"
  },
  "body": "{\"Person\":{\n  \"id\": 88,\n  \"name\": \"John Doe\"\n}}",
  "statusCode": 200
}

Encore une fois, lebody est unString.

5. Créer et tester l'API

Après avoir créé et déployé les fonctions Lambda dans la section précédente,we can now create the actual API using the AWS Console.

Examinons le flux de travail de base:

  1. Créez une API dans notre compte AWS.

  2. Ajoutez une ressource à la hiérarchie des ressources de l'API.

  3. Créez une ou plusieurs méthodes pour la ressource.

  4. Configurez l'intégration entre une méthode et la fonction Lambda correspondante.

Nous allons répéter les étapes 2 à 4 pour chacune de nos deux fonctions dans les sections suivantes.

5.1. Créer l'API

Pour créer l'API, nous devrons:

  1. Connectez-vous à la console API Gateway àhttps://console.aws.amazon.com/apigateway

  2. Cliquez sur «Get Started», puis sélectionnez «New API».

  3. Tapez le nom de notre API (TestAPI) et confirmez en cliquant sur «Créer une API»

Après avoir créé l'API, nous pouvons maintenant créer la structure de l'API et la lier à nos fonctions Lambda.

5.2. Structure de l'API pour la fonction 1

Les étapes suivantes sont nécessaires pour nosStorePersonFunction:

  1. Choisissez l'élément de ressource parent dans l'arborescence «Ressources», puis sélectionnez «Créer une ressource» dans le menu déroulant «Actions». Ensuite, nous devons procéder comme suit dans le volet «Nouvelle ressource enfant»:

    • Tapez "Personnes" comme nom dans le champ de saisie "Nom de la ressource"

    • Laissez la valeur par défaut dans le champ de saisie «Chemin de la ressource»

    • Choisissez «Créer une ressource»

  2. Choisissez la ressource que vous venez de créer, choisissez «Créer une méthode» dans le menu déroulant «Actions», puis procédez comme suit:

    • Choisissez PUT dans la liste déroulante Méthode HTTP, puis choisissez l’icône de coche pour enregistrer le choix.

    • Laissez le type d'intégration «Fonction Lambda» et sélectionnez l'option «Utiliser l'intégration du proxy Lambda».

    • Choisissez la région dans “Région Lambda”, où nous avons déployé nos fonctions Lambda avant

    • Saisissez“StorePersonFunction” dans "Fonction Lambda"

  3. Choisissez «Sauvegarder» et validez avec «OK» lorsque vous y êtes invité avec «Ajouter une autorisation à la fonction Lambda»

5.3. Structure de l'API pour la fonction 2 - Paramètres de chemin

Les étapes pour récupérer les paramètres de chemin sont similaires:

  1. Choisissez l'élément de ressource/persons sous l'arborescence «Ressources», puis sélectionnez «Créer une ressource» dans le menu déroulant «Actions». Ensuite, nous devons procéder comme suit dans le volet Nouvelle ressource enfant:

    • Saisissez“Person” comme nom dans le champ de saisie "Nom de la ressource"

    • Remplacez le champ de texte de saisie «Chemin de la ressource» par“{id}”

    • Choisissez «Créer une ressource»

  2. Choisissez la ressource que vous venez de créer, sélectionnez «Créer une méthode» dans le menu déroulant «Actions», puis procédez comme suit:

    • Choisissez GET dans la liste déroulante Méthode HTTP, puis choisissez l’icône de coche pour enregistrer le choix.

    • Laissez le type d'intégration «Fonction Lambda» et sélectionnez l'option «Utiliser l'intégration du proxy Lambda».

    • Choisissez la région dans “Région Lambda”, où nous avons déployé nos fonctions Lambda avant

    • Saisissez“GetPersonByHTTPParamFunction” dans "Fonction Lambda"

  3. Choisissez «Sauvegarder» et validez avec «OK» lorsque vous y êtes invité avec «Ajouter une autorisation à la fonction Lambda»

Remarque: il est important ici de définir le paramètre «Chemin de la ressource» sur“{id}”, car notreGetPersonByPathParamFunction considère que ce paramètre doit être nommé exactement comme ceci.

5.4. Structure de l'API pour la fonction 2 - Paramètres de chaîne de requête

Les étapes de réception des paramètres de chaîne de requête sont un peu différentes, carwe don’t have to create a resource, but instead have to create a query parameter for the id parameter:

  1. Choisissez l'élément de ressource/persons sous l'arborescence «Ressources», sélectionnez «Créer une méthode» dans le menu déroulant «Actions» et procédez comme suit:

    • Choisissez GET dans la liste déroulante Méthode HTTP, puis sélectionnez l’icône de coche pour enregistrer le choix.

    • Laissez le type d'intégration «Fonction Lambda» et sélectionnez l'option «Utiliser l'intégration du proxy Lambda».

    • Choisissez la région dans “Région Lambda”, où nous avons déployé nos fonctions Lambda avant

    • Tapez“GetPersonByHTTPParamFunction” dans «Fonction Lambda».

  2. Choisissez «Sauvegarder» et validez avec «OK» lorsque vous y êtes invité avec «Ajouter une autorisation à la fonction Lambda»

  3. Choisissez «Method Request» à droite et suivez les étapes suivantes:

    • Développez la liste de paramètres de chaîne de requête d'URL

    • Cliquez sur "Ajouter une chaîne de requête"

    • Tapez“id” dans le champ de nom et choisissez l'icône de coche pour enregistrer

    • Cochez la case "Obligatoire"

    • Cliquez sur le symbole du stylo à côté de "Demander validateur" en haut du panneau, sélectionnez "Valider les paramètres de la chaîne de requête et les en-têtes", puis choisissez la coche.

Remarque: Il est important de définir le paramètre «Chaîne de requête» sur“id”, car notreGetPersonByHTTPParamFunction considère que ce paramètre doit être nommé exactement comme ceci.

5.5. Tester l'API

Notre API est maintenant prête, mais elle n’est pas encore publique. Before we publish it, we want to run a quick test from the Console first.

Pour cela, nous pouvons sélectionner la méthode à tester dans l’arborescence «Ressources» et cliquer sur le bouton «Test». Sur l'écran suivant, nous pouvons saisir notre entrée, comme nous l'enverrions avec un client via HTTP.

PourStorePersonFunction, nous devons taper la structure suivante dans le champ «Request Body»:

{
    "id": 2,
    "name": "Jane Doe"
}

Pour lesGetPersonByHTTPParamFunction avec des paramètres de chemin, nous devons taper2 comme valeur dans le champ «{id}» sous «Chemin».

Pour les paramètres de chaîne de requêteGetPersonByHTTPParamFunction with, nous devons taperid=2 comme valeur dans le champ «{personnes}» sous «Chaînes de requête».

5.6. Déployer l'API

Jusqu'à présent, notre API n'était pas publique et n'était donc disponible qu'à partir de la console AWS.

Comme indiqué précédemment,when we deploy an API, we have to associate it with a stage, which is like a snapshot in time of the API. If we redeploy an API, we can either update an existing stage or create a new one.

Voyons à quoi ressemblera le schéma d'URL de notre API:

https://{restapi-id}.execute-api.{region}.amazonaws.com/{stageName}

Les étapes suivantes sont requises pour le déploiement:

  1. Choisissez l'API particulière dans le volet de navigation «API»

  2. Choisissez «Actions» dans le volet de navigation Ressources et sélectionnez «Deploy API» dans le menu déroulant «Actions».

  3. Choisissez «[Nouvelle étape]» dans la liste déroulante «Étape de déploiement», saisissez“test” dans «Nom de l'étape» et fournissez éventuellement une description de l'étape et du déploiement.

  4. Déclenchez le déploiement en choisissant «Deploy»

Après la dernière étape, la console fournira l'URL racine de l'API, par exemple,https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test.

5.7. Appel du point de terminaison

Comme l'API est maintenant publique,we can call it using any HTTP client we want.

AveccURL, les appels ressembleraient à ce qui suit.

StorePersonFunction:

curl -X PUT 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons' \
  -H 'content-type: application/json' \
  -d '{"id": 3, "name": "Richard Roe"}'

GetPersonByHTTPParamFunction  pour les paramètres de chemin:

curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons/3' \
  -H 'content-type: application/json'

GetPersonByHTTPParamFunction pour les paramètres de chaîne de requête:

curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons?id=3' \
  -H 'content-type: application/json'

6. Conclusion

Dans cet article, nous avons examiné comment rendre les fonctions AWS Lambda disponibles en tant que points de terminaison REST, à l'aide de AWS API Gateway.

Nous avons exploré les concepts de base et la terminologie d’API Gateway, et nous avons appris à intégrer les fonctions Lambda à l’aide de l’intégration Lambda Proxy.

Enfin, nous avons vu comment créer, déployer et tester une API.

Comme d'habitude, tout le code de cet article est disponible suron GitHub.