Запускаем приложение на Express.js в Yandex Cloud Functions

от автора

Node.js — удобная масштабируемая серверная платформа для работы с JavaScript. С помощью нее и различных поддерживаемых фреймворков, таких как Express, Connect или Koa, можно создавать полноценные приложения.   

Если идти по пути упрощения администрирования, возникает желание загрузить приложение в Yandex Cloud Functions и вызывать его из облака. К сожалению, пока нельзя просто так взять и запустить в облаке приложение, написанное на любом популярном node.js-фреймворке. Фреймворки пишут ответ в сокет HTTP(S). Рантайм функций ожидает получить от пользовательского кода функции объект определенного содержания.   

{          "statusCode": <HTTP код ответа>,     "headers": <словарь со строковыми значениями HTTP-заголовков>,     "multiValueHeaders": <словарь со списками значений HTTP-заголовков>,     "body": "<содержимое ответа>",     "isBase64Encoded": <true или false>  }

«Из коробки» это работать не будет, но можно научить приложение возвращать ответ в ожидаемом формате. Разберем, как это сделать, на примере приложения Express.js с двумя эндпоинтами.  

Создаем и запускаем новый проект

Создаем новую директорию и инициируем в ней новый проект:  

mkdir sample-app && cd sample-app npm init -y npm install express touch index.js

Далее в index.js добавляем следующий код:  

const express = require('express'); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.get('/api/info', (req, res) => {     res.send({ application: 'sample-app', version: '1.0' }); }); app.post('/api/v1/getback', (req, res) => {     res.send({ ...req.body }); }); app.listen(3000, () => console.log(`Listening on: 3000`)); 

Запускаем проект и проверяем, что приходят ожидаемые ответы:  

$ curl 'http://localhost:3000/api/info' {"application":"sample-app","version":"1"}

Адаптируем проект под Serverless  

Интегрируем модуль serverless-http:  

npm i --save serverless-http

Это универсальный враппер, он поддерживает не только Express, но и Connect, Koa, restana, а также экспериментально другие фреймворки: Sails, Hapi, Fastify, Restify, Polka и LoopBack.  

Затем модифицируем наш пример. Заменяем запуск сервера на порте 3000 экспортом функции-обработчика, которая будет вызываться serverless-рантаймом облака: 

const express = require('express'); const app = express(); const serverless = require('serverless-http'); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.get('/api/info', (req, res) => {     res.send({ application: 'sample-app', version: '1.0' }); }); app.post('/api/v1/getback', (req, res) => {     res.send({ ...req.body }); }); //app.listen(3000, () => console.log(`Listening on: 3000`));  module.exports.handler = serverless(app);

Теперь наше приложение готово к запуску в облаке.  

Развертываем приложение в облаке 

Для того чтобы развернуть код в облаке, проще всего воспользоваться утилитой serverless. У Yandex.Cloud есть свой плагин, который позволяет деплоить функции. Из него пока нельзя развернуть еще один ключевой компонент системы — Yandex API Gateway, мы чуть позже сделаем это вручную через консоль. 

Устанавливаем Serverless Framework и плагин к нему: 

npm i -g serverless serverless-yandex-cloud 

Далее создаем в проекте файл serverless.yaml с содержимым:   

service: sample-app frameworkVersion: ">=1.1.0" configValidationMode: off  provider:   name: yandex-cloud   runtime: nodejs12-preview  plugins:   - serverless-yandex-cloud  package:   exclude:     - ./**   include:     - ./package.json     - ./**/*.js  functions:   express:     # this is formatted as <FILENAME>.<HANDLER>     handler: index.handler     memory: 128     timeout: 5

Деплоим функцию командой: 

serverless deploy 

Если сделать функцию публичной и вызвать ее по предложенному URL, передав путь /api/info , то в ответ мы получим следующую ошибку: 

$ curl 'https://functions.yandexcloud.net/%function-id%/api/info' {"errorCode":400,"errorMessage":"Invalid functionID: /%function-id%/api/info", "errorType":"ProxyIntegrationError"}

 необходима настройка API Gateway.  

Создание API Gateway 

Спецификация должна соответствовать стандарту OpenAPI 3.0, для нашего простого API ее можно написать руками: 

openapi: 3.0.0 info:   title: Sample API   version: 1.0.0 paths:   /api/info:     get:       responses:         '200':           description: Ok       x-yc-apigateway-integration:         type: cloud_functions         function_id: %function_id%         tag: $latest         service_account_id: %service_account_id%   /api/v1/getback:     post:       responses:         '200':           description: Ok           content:             application/json:               schema:                 $ref: '#/components/schemas/Test'       requestBody:         required: false         content:           application/json:             schema:               $ref: '#/components/schemas/Test'       x-yc-apigateway-integration:         type: cloud_functions         function_id: %function_id%         tag: $latest         service_account_id: %service_account_id%  components:   schemas:     Test:       type: object

Не забудьте поменять %function_id% и %service_account_id% на ваши значения. У сервисного аккаунта должна быть роль serverless.functions.invoker или выше, если вы оставили функцию без публичного доступа. 

 

В более сложных случаях можно попробовать сгенерировать спецификацию OpenAPI на основе уже имеющегося кода API. Для этого подойдет express-oas-generator

Теперь наше приложение работает и доступно по URL. 

$ curl 'https://%api-gw-id%.apigw.yandexcloud.net/api/info' {"application":"sample-app","version":"1"}

Кстати, к API Gateway можно привязать свой домен. Как приязать домен — читайте в этом посте.  

Новый параметр API Gateway 

Совсем недавно в API Gateway появилась возможность указать параметр вида {param+}. В этом случае будут матчиться и вложенные пути. 

paths:   /api/{proxy+}:     get:       x-yc-apigateway-integration:         type: cloud_functions         function_id: d4e***         tag: $latest         service_account_id: aje***       responses:         200:           description: Ok       parameters:         - explode: true           in: path           name: proxy           required: true           schema:             type: string           style: simple

В первом параметре функции event в проперти path будет лежать значение вида /api/%7Bproxy+%7D и роутер Express.js будет ломаться. 

Решения как минимум два: 

  • написать честный provider для Yandex.Cloud по образу того, что сейчас есть для AWS

  • пропатчить объект event, положив в path значение из url (строки 13–19 в примере ниже). 

 Пример готового скрипта можно скачать.  

const express = require('express'); const serverless = require('serverless-http'); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.get('/api/info', (req, res) => {     res.send({ application: 'sample-app', version: '1.0' }); }); app.get('/api/pet/:name?', (req, res) => {     res.send({ ...req.params }); });  module.exports.handler = (event, context) => {     const patchedEvent = {         ...event,         path: event.url,         originalPath: event.path,     }     return serverless(app)(patchedEvent, context); }

Вы можете бесплатно попробовать запустить приложений Express.js на Yandex Cloud Functions по программе free tier: сервис не тарифицирует первый миллион вызовов функций и первые 10 ГБ×час выполнения функций. А любые вопросы о работе сервисов можно обсудить как с их пользователями, так и с их создателями в чате Yandex Serverless Ecosystem

 

ссылка на оригинал статьи https://habr.com/ru/post/555108/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *