Creación de su primer servicio sin servidor con funciones AWS Lambda

Índice
  1. Tu primer servicio sin servidor
  2. configuración CORS
  3. ¡Guau, ahora llamémoslo!
  4. Construyendo una Lambda útil
  5. Ahora podemos escribir algo de código.
  6. Un desvío opcional: agrupar
  7. Retazos
  8. Terminando

Muchos desarrolladores están al menos marginalmente familiarizados con las funciones de AWS Lambda. Son razonablemente sencillos de configurar, pero el vasto panorama de AWS puede dificultar la visión del panorama general. Con tantas piezas diferentes, puede resultar desalentador y frustrantemente difícil ver cómo encajan perfectamente en una aplicación web normal.

El marco sin servidor es de gran ayuda aquí. Agiliza la creación, implementación y, lo más importante, la integración de funciones Lambda en una aplicación web. Para ser claros, hace mucho, mucho más que eso, pero estas son las piezas en las que me centraré. Con suerte, esta publicación despertará tu interés y te animará a comprobar las muchas otras cosas que admiten Serverless. Si es completamente nuevo en Lambda, es posible que primero desee consultar esta introducción de AWS.

No hay manera de que pueda cubrir la instalación y configuración inicial mejor que la guía de inicio rápido, así que comience allí para comenzar a funcionar. Suponiendo que ya tiene una cuenta de AWS, es posible que esté operativa en 5 a 10 minutos; y si no lo hace, la guía también lo cubre.

Tu primer servicio sin servidor

Antes de pasar a cosas interesantes como la carga de archivos y los depósitos de S3, creemos una función Lambda básica, conéctela a un punto final HTTP y llamémosla desde una aplicación web existente. Lambda no hará nada útil o interesante, pero nos dará una buena oportunidad de ver lo agradable que es trabajar con Serverless.

Primero, creemos nuestro servicio. Abra cualquier aplicación web nueva o existente que pueda tener (crear-reaccionar-aplicación es una excelente manera de crear rápidamente una nueva) y busque un lugar para crear nuestros servicios. Para mí, es mi lambdacarpeta. Cualquiera que sea el directorio que elija, cdacceda a él desde la terminal y ejecute el siguiente comando:

sls create -t aws-nodejs --path hello-world

Eso crea un nuevo directorio llamado hello-world. Abrámoslo y veamos qué hay allí.

Si miras en handler.js, deberías ver una función asíncrona que devuelve un mensaje. Podríamos acceder sls deploya nuestra terminal ahora mismo e implementar esa función Lambda, que luego podría invocarse. Pero antes de hacer eso, hagámoslo invocable a través de la web.

Al trabajar con AWS manualmente, normalmente necesitaríamos ingresar a AWS API Gateway, crear un punto final, luego crear una etapa y decirle que se conecte a nuestro Lambda. Sin servidor, todo lo que necesitamos es un poco de configuración.

¿Sigues en el hello-worlddirectorio? Abra el archivo serverless.yaml que se creó allí.

El archivo de configuración en realidad viene con un texto estándar para las configuraciones más comunes. Quitemos el comentario de las httpentradas y agreguemos una ruta más sensata. Algo como esto:

functions:  hello:    handler: handler.hello#   The following are a few example events you can configure#   NOTE: Please make sure to change your handler code to work with those events#   Check the event documentation for details    events:      - http:        path: msg        method: get

Eso es todo. Serverless hace todo el trabajo duro descrito anteriormente.

configuración CORS

Idealmente, queremos llamar a esto desde el código JavaScript front-end con Fetch API, pero eso desafortunadamente significa que necesitamos configurar CORS. Esta sección lo guiará a través de eso.

Debajo de la configuración anterior, agregue cors: true, así

functions:  hello:    handler: handler.hello    events:      - http:        path: msg        method: get        cors: true 

¡Esa es la sección! CORS ahora está configurado en nuestro punto final API, lo que permite la comunicación entre orígenes.

Ajuste CORS Lambda

Si bien nuestro punto final HTTP está configurado para CORS, depende de nuestro Lambda devolver los encabezados correctos. Así es como funciona CORS. Automáticamente eso regresando a handler.js y agregando esta función:

const CorsResponse = obj = ({  statusCode: 200,  headers: {    "Access-Control-Allow-Origin": "*",    "Access-Control-Allow-Headers": "*",    "Access-Control-Allow-Methods": "*"  },  body: JSON.stringify(obj)});

Antes de regresar de Lambda, enviaremos el valor de retorno a través de esa función. Aquí está el handler.js completo con todo lo que hemos hecho hasta este punto:

'use strict';const CorsResponse = obj = ({  statusCode: 200,  headers: {    "Access-Control-Allow-Origin": "*",    "Access-Control-Allow-Headers": "*",    "Access-Control-Allow-Methods": "*"  },  body: JSON.stringify(obj)});
module.exports.hello = async event = {  return CorsResponse("HELLO, WORLD!");};

Ejecutémoslo. Escribe sls deployen tu terminal desde la hello-worldcarpeta.

Cuando eso se ejecute, habremos implementar nuestra función Lambda en un punto final HTTP al que podemos llamar a través de Fetch. ¿Pero dónde está? Podríamos abrir nuestra consola de AWS, encontrar la API de puerta de enlace que la tecnología sin servidor creó para nosotros y luego encontrar la URL de invocación. Se vería algo como esto.

Afortunadamente existe una forma más sencilla que es teclear sls infoen nuestra terminal:

Así, podemos ver que nuestra función Lambda está disponible en la siguiente ruta:

https://6xpmc3g0ch.execute-api.us-east-1.amazonaws.com/dev/ms

¡Guau, ahora llamémoslo!

Ahora abramos una aplicación web e intentamos buscarla. Así es como verá nuestro Fetch:

fetch("https://6xpmc3g0ch.execute-api.us-east-1.amazonaws.com/dev/msg")  .then(resp = resp.json())  .then(resp = {    console.log(resp);  });

Deberíamos ver nuestro mensaje en la consola de desarrollo.

Ahora que nos hemos mojado los pies, repitamos este proceso. Esta vez, sin embargo, hagamos un servicio más interesante y útil. Específicamente, hagamos el Lambda canónico “cambiar el tamaño de una imagen”, pero en lugar de activarlo mediante la carga de un nuevo depósito S3, permitamos que el usuario cargue una imagen directamente en nuestro Lambda. Eso eliminará la necesidad de agrupar cualquier tipo de aws-sdkrecursos en nuestro paquete del lado del cliente.

Construyendo una Lambda útil

¡Bien, desde el principio! Este Lambda en particular tomará una imagen, le cambiará el tamaño y luego la cargará en un depósito de S3. Primero, creamos un nuevo servicio. Lo llamo cover-artpero ciertamente podría ser cualquier otra cosa.

sls create -t aws-nodejs --path cover-art

Como antes, agregaremos una ruta a nuestro punto final HTTP (que en este caso será POST, en lugar de GET, ya que enviaremos el archivo en lugar de recibirlo) y habilitaremos CORS:

// Same as before  events:    - http:      path: upload      method: post      cors: true

A continuación, otorguemos a nuestro Lambda acceso a cualquier depósito de S3 que vayamos a usar para la carga. Busque en su archivo YAML; Debería haber una iamRoleStatementssección que contenga código repetitivo que haya sido comentado. Podemos aprovechar algo de eso descomentándolo. Aquí está la configuración que usaremos para habilitar los depósitos de S3 que queremos:

iamRoleStatements: - Effect: "Allow"   Action:     - "s3:*"   Resource: ["arn:aws:s3:::your-bucket-name/*"]

Tenga en cuenta el /*final. No enumeramos nombres de depósitos específicos de forma aislada, sino rutas a los recursos; en este caso, se trata de cualquier recurso que exista en el interior your-bucket-name.

Como queremos cargar archivos directamente a nuestro Lambda, necesitamos hacer un ajuste más. Específicamente, necesitamos configurar el punto final de la API para que acepte multipart/form-datacomo tipo de medio binario. Localice la providersección en el archivo YAML:

provider:  name: aws  runtime: nodejs12.x

...y modifíquelo para:

provider:  name: aws  runtime: nodejs12.x  apiGateway:    binaryMediaTypes:      - 'multipart/form-data'

Por si acaso, démosle a nuestra función un nombre inteligente. Reemplace handler: handler.hellocon handler: handler.uploady luego cambie module.exports.helloa module.exports.uploaden handler.js.

Ahora podemos escribir algo de código.

Primero, busquemos algunos ayudantes.

npm i jimp uuid lambda-multipart-parser

Espera, ¿qué es Jimp? Es la biblioteca que estoy usando para cambiar el tamaño de las imágenes cargadas. uuid será para crear nombres de archivos nuevos y únicos de los recursos dimensionados, antes de cargarlos en S3. Oh y lambda-multipart-parser? Eso es para analizar la información del archivo dentro de nuestro Lambda.

A continuación, creemos un asistente conveniente para la carga de S3:

const uploadToS3 = (fileName, body) = {  const s3 = new S3({});  const  params = { Bucket: "your-bucket-name", Key: `/${fileName}`, Body: body };
  return new Promise(res = {    s3.upload(params, function(err, data) {      if (err) {        return res(CorsResponse({ error: true, message: err }));      }      res(CorsResponse({         success: true,         url: `https://${params.Bucket}.s3.amazonaws.com/${params.Key}`       }));    });  });};

Por último, insertaremos un código que lea los archivos cargados, los cambie de tamaño con Jimp (si es necesario) y cargue el resultado en S3. El resultado final está a continuación.

'use strict';const AWS = require("aws-sdk");const { S3 } = AWS;const path = require("path");const Jimp = require("jimp");const uuid = require("uuid/v4");const awsMultiPartParser = require("lambda-multipart-parser");
const CorsResponse = obj = ({  statusCode: 200,  headers: {    "Access-Control-Allow-Origin": "*",    "Access-Control-Allow-Headers": "*",    "Access-Control-Allow-Methods": "*"  },  body: JSON.stringify(obj)});
const uploadToS3 = (fileName, body) = {  const s3 = new S3({});  var params = { Bucket: "your-bucket-name", Key: `/${fileName}`, Body: body };  return new Promise(res = {    s3.upload(params, function(err, data) {      if (err) {        return res(CorsResponse({ error: true, message: err }));      }      res(CorsResponse({         success: true,         url: `https://${params.Bucket}.s3.amazonaws.com/${params.Key}`       }));    });  });};
module.exports.upload = async event = {  const formPayload = await awsMultiPartParser.parse(event);  const MAX_WIDTH = 50;  return new Promise(res = {    Jimp.read(formPayload.files[0].content, function(err, image) {      if (err || !image) {        return res(CorsResponse({ error: true, message: err }));      }      const newName = `${uuid()}${path.extname(formPayload.files[0].filename)}`;      if (image.bitmap.width  MAX_WIDTH) {        image.resize(MAX_WIDTH, Jimp.AUTO);        image.getBuffer(image.getMIME(), (err, body) = {          if (err) {            return res(CorsResponse({ error: true, message: err }));          }          return res(uploadToS3(newName, body));        });      } else {        image.getBuffer(image.getMIME(), (err, body) = {          if (err) {            return res(CorsResponse({ error: true, message: err }));          }          return res(uploadToS3(newName, body));        });      }    });  });};

Lamento arrojarle tanto código pero, siendo esta una publicación sobre Amazon Lambda y sin servidor, prefiero no insistir en el trabajo duro dentro de la función sin servidor. Por supuesto, la tuya puede verse completamente diferente si usas una biblioteca de imágenes que no sea Jimp.

Ejecutémoslo cargando un archivo desde nuestro cliente. Estoy usando la biblioteca reaccionar-dropzone, por lo que mi JSX se ve así:

Dropzone  onDrop={files = onDrop(files)}  multiple={false}  divClick or drag to upload a new cover/div/Dropzone

La onDropfunción se ve así:

const onDrop = files = {  let request = new FormData();  request.append("fileUploaded", files[0]);
  fetch("https://yb1ihnzpy8.execute-api.us-east-1.amazonaws.com/dev/upload", {    method: "POST",    mode: "cors",    body: request    })  .then(resp = resp.json())  .then(res = {    if (res.error) {      // handle errors    } else {      // success - woo hoo - update state as needed    }  });};

¡Y así, podemos cargar un archivo y verlo aparecer en nuestro depósito S3!

Un desvío opcional: agrupar

Hay una mejora opcional que podríamos hacer en nuestra configuración. En este momento, cuando implementamos nuestro servicio, Serverless comprime toda la carpeta de servicios y la envía toda a nuestro Lambda. El contenido actualmente pesa 10 MB, ya que todos nosotros node_modulesestamos siendo arrastrados por el viaje. Podemos usar un paquete para reducir drásticamente ese tamaño. No solo eso, sino que un paquete reducirá el tiempo de implementación, el uso de datos, el rendimiento del arranque en frío, etc. En otras palabras, es bueno tenerlo.

Afortunadamente para nosotros, existe un complemento que integra fácilmente el paquete web en el proceso de compilación sin servidor. Instalémoslo con:

npm i serverless-webpack --save-dev

…y agréguelo a través de nuestro archivo de configuración YAML. Podemos colocar esto al final:

// Same as beforeplugins:  - serverless-webpack

Naturalmente, necesitamos un archivo webpack.config.js, así que agreguémoslo a la mezcla:

const path = require("path");module.exports = {  entry: "./handler.js",  output: {    libraryTarget: 'commonjs2',    path: path.join(__dirname, '.webpack'),    filename: 'handler.js',  },  target: "node",  mode: "production",  externals: ["aws-sdk"],  resolve: {    mainFields: ["main"]  }};

Tenga en cuenta que estamos configurando target: nodepara que los activos específicos del nodo se traten correctamente. También tenga en cuenta que es posible que deba configurar el nombre del archivo de salida en handler.js. También estoy agregando aws-sdka la matriz externa para que el paquete web no lo incluya en absoluto; en su lugar, dejará la llamada const AWS = require("aws-sdk");sola, lo que permitirá que nuestro Lamdba la maneje en tiempo de ejecución. Esto está bien ya que Lambdas ya tiene aws-sdkdisponible implícitamente, lo que significa que no es necesario que lo enviemos por cable. Finalmente, mainFields: ["main"]es decirle al paquete web que ignore cualquier modulecampo de ESM. Esto es necesario para solucionar algunos problemas con la biblioteca Jimp.

Ahora volvamos a implementarlo y, con suerte, veremos el paquete web ejecutándose.

Ahora nuestro código está muy bien empaquetado en un solo archivo de 935K, que se reduce aún más a solo 337K. ¡Eso es un gran ahorro!

Retazos

Si se pregunta cómo enviaría otros datos a Lambda, agregaría lo que desee al objeto de solicitud, de tipo FormData, de antes. Por ejemplo:

request.append("xyz", "Hi there");

…y luego leer formPayload.xyzen Lambda. Esto puede resultar útil si necesita enviar un token de seguridad u otra información de archivo.

Si se pregunta cómo puede configurar las variables env para su Lambda, es posible que ya haya adivinado que es tan simple como agregar algunos campos a su archivo serverless.yaml. Incluso admite la lectura de valores de un archivo externo (presumiblemente no comprometido con git). Esta publicación de blog de Philipp Müns lo cubre bien.

Terminando

Serverless es un marco increíble. Lo prometo, apenas hemos arañado la superficie. Esperamos que esta publicación te haya mostrado su potencial y te haya motivado a seguir leyendo.

Si está interesado en obtener más información, le recomiendo los materiales de aprendizaje de David Wells, ingeniero de Netlify y ex miembro del equipo sin servidor, así como el Manual sin servidor de Swizec Teller.

  • Taller sin servidor: un repositorio para aprender los conceptos básicos de la tecnología sin servidor
  • Estrategias de autenticación sin servidor: un repositorio que analiza diferentes estrategias para autorizar el acceso a funciones.
  • Netlify Functions Worksop: Lecciones de Netlify sobre los conceptos centrales del uso de funciones sin servidor
  • Manual sin servidor: introducción a las tecnologías sin servidor
SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion