Skip to main content

API Gateway: treure el Lambda a internet

La funció de la lliçó anterior només la pots invocar tu, amb credencials d'AWS. Per obrir-la al món hi ha tres camins, i triar malament et costa diners o feina.

OpcióPreu per milióQuan la vols
Function URL0 $Un webhook intern, una prova, res de públic
HTTP API1,00 $El 90 % dels casos
REST API3,50 $Necessites claus d'API, plans d'ús, WAF o transformacions

La REST API és tres vegades i mitja més cara i porta vint anys de funcionalitats que probablement no faràs servir. Comença sempre per HTTP API; si algun dia et falta alguna cosa, ja canviaràs.


Muntar-la amb quatre comandes

COMPTE=$(aws sts get-caller-identity --query Account --output text)
REGIO=eu-west-1

API_ID=$(aws apigatewayv2 create-api \
--name hola-api --protocol-type HTTP \
--query ApiId --output text --region $REGIO)

INTEG_ID=$(aws apigatewayv2 create-integration \
--api-id "$API_ID" \
--integration-type AWS_PROXY \
--integration-uri "arn:aws:lambda:${REGIO}:${COMPTE}:function:hola" \
--payload-format-version 2.0 \
--query IntegrationId --output text --region $REGIO)

aws apigatewayv2 create-route \
--api-id "$API_ID" \
--route-key 'GET /hola' \
--target "integrations/${INTEG_ID}" --region $REGIO

aws apigatewayv2 create-stage \
--api-id "$API_ID" --stage-name '$default' --auto-deploy --region $REGIO

Falta la peça que oblida tothom. API Gateway no té permís per invocar la teva funció només perquè els hagis connectat; el permís viu al costat de la Lambda, en una política de recursos:

aws lambda add-permission \
--function-name hola \
--statement-id apigw-hola \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:${REGIO}:${COMPTE}:${API_ID}/*/*/hola" \
--region $REGIO

Ara ja tens URL pública:

curl "https://${API_ID}.execute-api.${REGIO}.amazonaws.com/hola"

L'etapa $default és el que fa que la URL no porti prefix. Si li dius prod, la ruta passa a ser /prod/hola i te'l menges per sempre a totes les integracions.


Què li arriba realment a la funció

Amb payload-format-version 2.0, l'esdeveniment té aquesta forma. És diferent de la 1.0, i mesclar-les és la font d'errors número u:

// src/index.ts
import type { APIGatewayProxyEventV2 } from "aws-lambda";

export const handler = async (event: APIGatewayProxyEventV2) => {
const metode = event.requestContext.http.method; // NO event.httpMethod
const ruta = event.rawPath; // NO event.path
const nom = event.queryStringParameters?.nom ?? "món";
const cos = event.body ? JSON.parse(event.body) : null;

return {
statusCode: 200,
headers: { "content-type": "application/json; charset=utf-8" },
body: JSON.stringify({ missatge: `Hola, ${nom}`, metode, ruta, cos }),
};
};

Dues coses del format 2.0 que et faran perdre una tarda:

  • event.body sempre és una cadena, mai un objecte. Si el client envia JSON, el parseges tu. I si isBase64Encoded és true, primer el descodifiques.
  • Si retornes un objecte sense statusCode, API Gateway l'interpreta com a cos i respon 200 amb el JSON sencer. Sembla màgia i va bé per a proves, però et deixa sense manera de retornar un 404. Retorna sempre l'objecte complet.

Els paràmetres de ruta funcionen amb claus entre claudàtors:

aws apigatewayv2 create-route --api-id "$API_ID" \
--route-key 'GET /usuaris/{id}' \
--target "integrations/${INTEG_ID}" --region $REGIO

i els llegeixes a event.pathParameters.id.


CORS

Si l'API la crida un navegador des d'un altre domini, necessites CORS. Es configura a l'API, no a la funció:

aws apigatewayv2 update-api --api-id "$API_ID" --region $REGIO \
--cors-configuration \
AllowOrigins="https://formacio.joansanfeliu.com",\
AllowMethods="GET,POST,OPTIONS",\
AllowHeaders="content-type",\
MaxAge=3600

Regla: o ho fas aquí, o ho fas als headers de la Lambda. Mai les dues coses. Si poses Access-Control-Allow-Origin als dos llocs, el navegador rep la capçalera duplicada i la petició falla amb un missatge que no menciona enlloc que el problema és una duplicació.

Amb la configuració a l'API, les peticions OPTIONS de preflight les respon API Gateway sol i no arriben mai a la teva funció. Això, a més de ser correcte, t'estalvia la meitat de les invocacions.


El límit dels 30 segons

Una integració d'HTTP API talla als 30 segons. No és configurable, no hi ha quota que demanar, i el client rep un 504.

No és un problema petit: vol dir que qualsevol cosa que pugui trigar més d'aquest temps —generar un PDF gros, cridar un model d'IA, processar un fitxer que t'han pujat— no pot viure darrere d'una petició HTTP síncrona. El patró correcte és respondre 202 de seguida amb un identificador, fer la feina en una altra Lambda invocada de manera asíncrona o per cua, i que el client consulti l'estat. Val més descobrir-ho ara que quan ho tinguis en producció.


Què costa això

ConceptePreuNivell gratuït
HTTP API1,00 $ per milió de peticions (primers 300M)1M/mes durant 12 mesos
REST API3,50 $ per milió (primers 333M)1M/mes durant 12 mesos
Function URL0 $
Transferència de dades cap a fora~0,09 $ per GB100 GB/mes

Un exemple real: una API amb 500.000 peticions al mes, cada una amb una Lambda de 256 MB i 150 ms, et surt per 0,50 $ d'API Gateway + 0,10 $ de Lambda. Seixanta cèntims. Aquí no és on tens el problema de costos.

On sí que el tens: transferència de dades. Si serveixes imatges o fitxers a través de l'API, els 0,09 $/GB de sortida es mengen tota la resta. Els fitxers estàtics no passen mai per API Gateway; van a S3 amb CloudFront al davant, que és la lliçó 05.


Errors habituals

  • {"message":"Not Found"} amb 404. La clau de ruta no coincideix. GET /hola no atén /Hola ni /hola/. Comprova-ho amb aws apigatewayv2 get-routes --api-id "$API_ID".
  • 500 «Internal Server Error» i la Lambda sense cap log. Si al grup de logs de la funció no hi ha absolutament res, la petició no hi ha arribat mai: t'has deixat el add-permission. És el motiu més freqüent de tots.
  • 502 «Bad Gateway». La funció ha respost, però amb una forma que API Gateway no entén: un body que és un objecte en comptes d'una cadena, o un statusCode en text. Serialitza sempre amb JSON.stringify.
  • event.path és undefined. Estàs llegint el format 1.0 en una integració 2.0. Els tipus de @types/aws-lambda t'ho haurien dit: fes servir APIGatewayProxyEventV2 i deixa que el compilador treballi per tu.

T'aviso quan publiqui

Un correu per setmana com a molt, i només quan publiqui alguna cosa. De tant en tant t'hi explico el que faig: cursos, sessions, producte. Te'n pots donar de baixa quan vulguis, amb un clic.