Amazon API Gateway velmi flexibilní cloudová služba nejčastěji využívaná k tvorbě REST API poháněného lambda funkcemi. Ukážeme si jak vytvořit pomocí Serverless Frameworku jednoduchý HTTP GET endpoint, který bude vracet v odpovědi JSON.

I přes to, že následující ukázka je jednoduchá, API Gateway je velmi mocný nástroj, který umí řešit věci jako např.:

  • Serverless Websockety
  • Validace vstupních dat podle vlastního JSON schematu
  • Generování Swagger dokumentace
  • Autorizace pomocí API klíče
  • Monitoring HTTP požadavků a odpovědí
  • Autorizace řízená vlastní lambda funkcí
  • Nastavování různých limitů a škrcení požadavků pro jednotlivé API klíče
  • AWS REST API proxy (návrhový vzor pro pokročilé)
  • a další...

Kompletní zdrojový kód ukázky:

purple-technology/serverless-cz
💜🚀 Podklady pro tutorial sérii o Serrverlessu. Contribute to purple-technology/serverless-cz development by creating an account on GitHub.

Serverless.yml

V serverless.yml souboru si nadefinujeme "customers" funkci, která bude zpracovávat API požadavky a nastavíme ji v events tzv. "http" event source, který zajistí to, aby se funkce spustila při zavolání GET požadavku na adresu /customers

service: four-api-gateway

plugins:
  - serverless-offline

provider:
  name: aws
  runtime: nodejs12.x
  region: eu-central-1

functions:
  customers:
    handler: src/customers.handler
    events:
      - http:
          path: /customers
          method: get

Handler

Každá lambda funkce musí mít takzvanou “handler” funkci, která se zavolá při každém jejím spuštění. V případě Javascriptu může mít handler dvě podoby - Promise (async/await) - tak jak v našem příkladu - nebo klasický callback (více informací zde).

'use strict'

module.exports.handler = async (event) => {
  console.log('Načítám klienty z databáze...')
  console.log(JSON.stringify(event, null, 2))

  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      customers: [
        {
          id: 1,
          name: 'Karel'
        },
        {
          id: 2,
          name: 'Pepa'
        },
        {
          id: 3,
          name: 'Janek'
        }
      ]
    })
  }
}

Struktura odpovědi

  • statusCode - HTTP status kód odpovědi
  • headers - HTTP hlavičky, které chceme odeslat v odpovědi
  • body - tělo HTTP odpovědi, které vždy musí být string - proto JSON.stringify

Příklad obsahu vstupního eventu API Gateway

{
  "resource": "/customers",
  "path": "/customers",
  "httpMethod": "GET",
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
    "CloudFront-Forwarded-Proto": "https",
    "CloudFront-Is-Desktop-Viewer": "true",
    "CloudFront-Is-Mobile-Viewer": "false",
    "CloudFront-Is-SmartTV-Viewer": "false",
    "CloudFront-Is-Tablet-Viewer": "false",
    "CloudFront-Viewer-Country": "CZ",
    "Cookie": "ajs_user_id=null",
    "Host": "96g84zqz75.execute-api.eu-central-1.amazonaws.com",
    "sec-fetch-dest": "document",
    "sec-fetch-mode": "navigate",
    "sec-fetch-site": "none",
    "sec-fetch-user": "?1",
    "upgrade-insecure-requests": "1",
    "User-Agent": "......",
    "Via": "2.0 7da8d24daaa6257fb28a90cd4a3bbe5d.cloudfront.net (CloudFront)",
    "X-Amz-Cf-Id": "cALrq-Aso-SFccHhkGlqespelPm3PnDWOFLo7NhnJk5Cx-m0jliUdg==",
    "X-Amzn-Trace-Id": "Root=1-5f08d25f-3851b6fdbb7c12f7da82c599",
    "X-Forwarded-For": "88.112.141.53, 130.176.34.83",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https"
  },
  "multiValueHeaders": {
    "Accept": [
      "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
    ],
    "Accept-Encoding": ["gzip, deflate, br"],
    "Accept-Language": ["en-GB,en-US;q=0.9,en;q=0.8"],
    "CloudFront-Forwarded-Proto": ["https"],
    "CloudFront-Is-Desktop-Viewer": ["true"],
    "CloudFront-Is-Mobile-Viewer": ["false"],
    "CloudFront-Is-SmartTV-Viewer": ["false"],
    "CloudFront-Is-Tablet-Viewer": ["false"],
    "CloudFront-Viewer-Country": ["CZ"],
    "Cookie": ["ajs_user_id=null"],
    "Host": ["96g84zqz75.execute-api.eu-central-1.amazonaws.com"],
    "sec-fetch-dest": ["document"],
    "sec-fetch-mode": ["navigate"],
    "sec-fetch-site": ["none"],
    "sec-fetch-user": ["?1"],
    "upgrade-insecure-requests": ["1"],
    "User-Agent": [
      "......"
    ],
    "Via": ["2.0 7da8d24baay6657fb28a90cd4a3bbe5d.cloudfront.net (CloudFront)"],
    "X-Amz-Cf-Id": ["cALrq-Aso-SFtcHhkGlqespelPm5PnDWOFLo7NhnKk5Cx-m0jliUdg=="],
    "X-Amzn-Trace-Id": ["Root=1-5f08d25f-1851b6fdbb7c12f7da02w199"],
    "X-Forwarded-For": ["85.212.151.53, 120.876.44.13"],
    "X-Forwarded-Port": ["443"],
    "X-Forwarded-Proto": ["https"]
  },
  "queryStringParameters": null,
  "multiValueQueryStringParameters": null,
  "pathParameters": null,
  "stageVariables": null,
  "requestContext": {
    "resourceId": "n0mk11",
    "resourcePath": "/customers",
    "httpMethod": "GET",
    "extendedRequestId": "PeXO5H6DFiAFgxw=",
    "requestTime": "10/Jul/2020:20:41:03 +0000",
    "path": "/dev/customers",
    "accountId": ".......",
    "protocol": "HTTP/1.1",
    "stage": "dev",
    "domainPrefix": "96g8b1h47j",
    "requestTimeEpoch": 1594413663007,
    "requestId": "77691ybt-n351-4cde-9db7-f8b5f64e0137",
    "identity": {
      "cognitoIdentityPoolId": null,
      "accountId": null,
      "cognitoIdentityId": null,
      "caller": null,
      "sourceIp": "81.412.743.14",
      "principalOrgId": null,
      "accessKey": null,
      "cognitoAuthenticationType": null,
      "cognitoAuthenticationProvider": null,
      "userArn": null,
      "userAgent": "......",
      "user": null
    },
    "domainName": "96g8b1h47j.execute-api.eu-central-1.amazonaws.com",
    "apiId": "96g8b1h47j"
  },
  "body": null,
  "isBase64Encoded": false
}

Lokální vývoj

serverless-offline

$ serverless offline

je populární plugin pro Serverless Framework, který umožňuje lokální vývoj REST API.

Instalace: $ npm install serverless-offline --save-dev

V ukázkovém repozitáři se lokální server spouští pomocí $ npm run dev

$ npm run dev

> 4-api-gateway@1.0.0 dev ......./4-api-gateway
> serverless offline

Serverless: Starting Offline: dev/eu-central-1.

Serverless: Routes for customers:
Serverless: GET /customers
Serverless: POST /{apiVersion}/functions/four-api-gateway-dev-customers/invocations

Serverless: Offline [HTTP] listening on http://localhost:3000
Serverless: Enter "rp" to replay the last request

Testovací požadavek na lokální API - funguje 🎉

$ curl http://localhost:3000/customers

{"customers":[{"id":1,"name":"Karel"},{"id":2,"name":"Pepa"},{"id":3,"name":"Janek"}]}   

Nasazení na AWS

$ serverless deploy

V ukázkovém repozitáři se nasazení na AWS provádí pomocí příkazu:
$ npm run deploy

$ npm run deploy

> 4-api-gateway@1.0.0 deploy ......./4-api-gateway
> serverless deploy

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service four-api-gateway.zip file to S3 (91.23 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...................................
Serverless: Stack update finished...
Service Information
service: four-api-gateway
stage: dev
region: eu-central-1
stack: four-api-gateway-dev
resources: 11
api keys:
  None
endpoints:
  GET - https://n7rex1gz2i.execute-api.eu-central-1.amazonaws.com/dev/customers
functions:
  customers: four-api-gateway-dev-customers
layers:
  None

Testovací požadavek na nasazené API - funguje 🎉

$ curl https://n7rex1gz2i.execute-api.eu-central-1.amazonaws.com/dev/customers

{"customers":[{"id":1,"name":"Karel"},{"id":2,"name":"Pepa"},{"id":3,"name":"Janek"}]}

Logy

Logy z "customers" lambdy jsou k dispozici ve službě Amazon CloudWatch v log groupě s názvem: /aws/lambda/four-api-gateway-dev-customers

Logy z "customers" lambda funkce

Detail API v AWS konzoli

Nově vytvořené API si také můžete prohlédnout v AWS konzoli. Nicméně nedoporučoval bych tu dělat ručně jakékoliv změny, aby kvůli tomu pak nenastaly problémy se Serverless Frameworkem...

Informace o nasazené aplikaci

$ serverless info -v

Pokud budete chtít zpětně získat např. adresu "customers" endpointu, tak stačí v případě ukázkového repozitáře spustit $ npm run info

$ npm run info

> 4-api-gateway@1.0.0 info ......./4-api-gateway
> serverless info -v

Service Information
service: four-api-gateway
stage: dev
region: eu-central-1
stack: four-api-gateway-dev
resources: 11
api keys:
  None
endpoints:
  GET - https://n7rex1gz2i.execute-api.eu-central-1.amazonaws.com/dev/customers
functions:
  customers: four-api-gateway-dev-customers
layers:
  None

Stack Outputs
CustomersLambdaFunctionQualifiedArn: arn:aws:lambda:eu-central-1:124515691641:function:four-api-gateway-dev-customers:3
ServiceEndpoint: https://n7rex1gz2i.execute-api.eu-central-1.amazonaws.com/dev
ServerlessDeploymentBucketName: four-api-gateway-dev-serverlessdeploymentbucket-1ujue1xbdlkvw

Smazání z AWS

$ serverless remove

V ukázkovém repozitáři se smazání z AWS provádí pomocí příkazu:
$ npm run remove

$ npm run remove

> 4-api-gateway@1.0.0 remove ......./4-api-gateway
> 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...

Testovací požadavek na smazané API - nefunguje 🚫 👍

$ curl https://n7rex1gz2i.execute-api.eu-central-1.amazonaws.com/dev/customers

curl: (6) Could not resolve host: n7rex1gz2i.execute-api.eu-central-1.amazonaws.com

Závěr

Jak jsem již v úvodu zmínil, API Gateway je velmi mocný nástroj a v budoucnu o něm ještě pár článků na našem blogu určitě uvidíte.

V případě jakýchkoliv otázek mě neváhejte kontaktovat na Twitteru @FilipPyrek.

With ❤️ made in Brno.