Récemment, nous avons vu comment déployer du code sous forme de fonction Lambda et comment les exposer sous forme d’API sur Internet. Mais que peut-on faire vraiment avec ce code ?

Pourquoi ne pas faire un gestionnaire de téléchargement ? L’idée est de créer une fonction Lambda qui va lancer un téléchargement (d’une page HTML dans l’exemple) et stocker le résultat dans un bucket S3 pour le consulter plus tard.

Implémentation

Le code Node.JS de cette fonction est assez simple :

var http = require('http');
var crypto = require('crypto');
var AWS = require('aws-sdk');
// instanciation du client S3
var s3 = new AWS.S3();

exports.handler = function(event, context, callback) {
  console.log('downloading ' + event.hostname + event.path + '...');
  var options = event;
  // appel du client HTTP avec les options passées en paramètre de la fonction Lambda
  var req = http.request(options, function (result) {
    console.log('HTTP Success, with: ' + result.statusCode);
    result.setEncoding('utf8');
    result.on('data', function (chunk) {
      // création de la clé d'accès au fichier sur S3
      var s3Key = crypto.createHash('md5').update(event.hostname+event.path).digest("hex");
      var s3Params = {Bucket: 'my-project-downloader-inbox', Key: s3Key, Body: chunk};
      // envoi du fichier sur S3
      s3.putObject(s3Params, function(s3Err, s3Data) {
          if (s3Err)
            console.log('S3 Error, with: ' + s3Err);
          else
            console.log('S3 Success');

          context.done(null, result.statusMessage);
      });
    });
  }).on('error', function (err) {
    console.log('HTTP Error, with: ' + err.message);
    context.done("Failed");
  });
  req.end();
};

En admettant que cette fonction se trouve dans le fichier http_downloader.js, il faut l’empaqueter dans une archive ZIP :

$ zip http_downloader.zip http_downloader.js

Déploiement

Le déploiement de cette fonction Lambda est très similaire aux précédents exemples à la différence prêt qu’il faut donner les droits d’accès sur S3 à la fonction :

# création du rôle pour la lambda
$ aws iam create-role \
  --role-name myproject-lambdas \
  --assume-role-policy-document file://./lambdas-role.json

{
    "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "lambda.amazonaws.com"
                }
            }
        },
        "RoleId": "AROAJVEBFZIEII7GQGCYW",
        "CreateDate": "2016-07-06T22:08:03.949Z",
        "RoleName": "myproject-lambdas",
        "Path": "/",
        "Arn": "arn:aws:iam::client-id:role/myproject-lambdas"
    }
}

# attachement de la policy pour l'accès à S3
$ aws iam attach-role-policy \
  --role-name myproject-lambdas \
  --policy-arn "arn:aws:iam::aws:policy/AWSLambdaExecute"

# ajout de la policy pour l'accès aux logs
$ aws iam put-role-policy \
  --role-name myproject-lambdas \
  --policy-name myproject-lambdas-policy \
  --policy-document file://./lambdas-policy.json

$ export CLIENT_ID=client-id

# déploiement de la fonction Lambda
$ aws lambda create-function \
  --function-name MyHttpDownloader  \
  --zip-file fileb://./http_downloaded.zip \
  --role arn:aws:iam::$CLIENT_ID:role/myproject-lambdas  \
  --handler http_downloader.handler \
  --runtime nodejs4.3

{
    "CodeSha256": "/Fvo33ICAXsmBi93V3UznKLw7lAYeeTDiuZnizdBj2Y=",
    "FunctionName": "MyHttpDownloader",
    "CodeSize": 417,
    "MemorySize": 128,
    "FunctionArn": "arn:aws:lambda:eu-west-1:client-id:function:MyHttpDownloader",
    "Version": "$LATEST",
    "Role": "arn:aws:iam::client-id:role/myproject-lambdas",
    "Timeout": 3,
    "LastModified": "2016-07-06T22:16:02.966+0000",
    "Handler": "http_downloader.handler",
    "Runtime": "nodejs4.3",
    "Description": ""
}

# création du bucket S3 qui doit recevoir les fichiers téléchargés
$ aws s3api create-bucket \
  --bucket my-project-downloader-inbox

{
    "Location": "/my-project-downloader-inbox"
}

Utilisation

Cette fonction accepte en paramètre d’entrée un fichier JSON définissant l’URL et la façon d’y accéder avec le client HTTP. Voici un exemple de fichier input.json permettant de récupérer le résultat d’une recherche sur DuckDuckGo :

{
  "hostname": "duckduckgo.com",
  "path": "/?q=toaster&t=ha&ia=products",
  "method": "GET",
  "headers": {
    "Accept": "*/*"
  }
}

La fonction peut être testée en ligne de commande de cette façon :

$ aws lambda invoke \
  --invocation-type RequestResponse \
  --function-name MyHttpDownloader \
  --payload file://./input.json \
  output.txt

{
    "StatusCode": 200
}

$ cat output.txt
"OK"

Une fois la fonction exécutée avec succès, il est possible de vérifier que le fichier est dans le bucket S3 et de le télécharger pour vérifier son contenu :

$ aws s3 ls s3://my-project-downloader-inbox

2016-07-08 07:16:47      13151 82d0ba824f3cf24362a649630174d3fc
2016-07-08 07:19:58      31997 83cbfeb8342b0414ff4f4644d934d3b4

$ aws s3 cp s3://my-project-downloader-inbox/83cbfeb8342b0414ff4f4644d934d3b4 ./83cbfeb8342b 0414ff4f4644d934d3b4.html
download: s3://my-project-downloader-inbox/83cbfeb8342b0414ff4f4644d934d3b4 to .\83cbfeb8342b0414ff4f4644d934d3b4.html

Nettoyage

Comme d’habitude voici les commandes pour supprimer tout ce qui vient d’être créé pour cet exemple :

$ aws s3 rm s3://my-project-downloader-inbox  --recursive

delete: s3://my-project-downloader-inbox/83cbfeb8342b0414ff4f4644d934d3b4
delete: s3://my-project-downloader-inbox/82d0ba824f3cf24362a649630174d3fc

$ aws s3api delete-bucket \
  --bucket my-project-downloader-inbox

$ aws logs delete-log-group \
  --log-group-name /aws/lambda/MyHttpDownloader

$ aws lambda delete-function \
  --function-name MyHttpDownloader

$ aws iam delete-role-policy \
  --role-name myproject-lambdas \
  --policy-name myproject-lambdas-policy     

$ aws iam delete-role \
  --role-name myproject-lambdas

Conclusion

Avec très peu de code et surtout sans la création de serveur, nous avons pu créer un gestionnaire de téléchargement de fichiers en HTTP.

Un axe d’amélioration possible serait de limiter le droit d’accès de la fonction Lambda seulement sur un seul bucket S3 et seulement en écriture. Ceci est possible en créant une policy dans ce genre :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::my-project-downloader-inbox/*"
    }
  ]
}

Pour aller plus loin, il faudrait exposer ce gestionnaire via une API REST avec API Gateway et de rendre paramétrable le bucket où doivent être déposés les fichiers téléchargés.

Cet exemple donne un premier aperçu du type de traitement que peut faire une fonction Lambda AWS. Bien entendu, il existe toutes sortes d’autres cas d’usage. A vous de les imaginer, concevoir et déployer !