Node.jsудобнаямасштабируемая сервернаяплатформа для работы сJavaScript. С помощьюнееи различных поддерживаемыхфреймворков,таких как Express, Connect или Koa, можно создавать полноценные приложения.
Еслиидтипо пути упрощенияадминистрирования, возникает желаниезагрузитьприложение вYandexCloudFunctionsи вызывать его из облака.Ксожалению,пока нельзяпросто так взять и запуститьв облакеприложение, написанное на любом популярномnode.js-фреймворке.Фреймворкипишут ответвсокетHTTP(S).Рантаймфункций ожидает получить от пользовательского кода функции объект определенного содержания.
{ "statusCode": <HTTP код ответа>, "headers": <словарь со строковыми значениями HTTP-заголовков>, "multiValueHeaders": <словарь со списками значений HTTP-заголовков>, "body": "<содержимое ответа>", "isBase64Encoded": <true или false> }
Из коробкиэто работать не будет, но можно научить приложение возвращать ответ в ожидаемом формате.Разберем,как это сделать,на примереприложенияExpress.jsс двумяэндпоинтами.
Создаем и запускаем новый проект
Создаемновую директорию и инициируем в ней новый проект:
mkdir sample-app && cd sample-appnpm init -ynpm install expresstouch 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естьсвойплагин,который позволяетдеплоитьфункции.Из него пока нельзя развернуть еще один ключевой компонент системы YandexAPIGateway,мычуть позже сделаем это вручнуючерез консоль.
УстанавливаемServerlessFrameworkи плагин к нему:
npm i -g serverless serverless-yandex-cloud
Далее создаем в проектефайлserverless.yamlс содержимым:
service: sample-appframeworkVersion: ">=1.1.0"configValidationMode: offprovider: name: yandex-cloud runtime: nodejs12-previewplugins: - serverless-yandex-cloudpackage: exclude: - ./** include: - ./package.json - ./**/*.jsfunctions: express: # this is formatted as <FILENAME>.<HANDLER> handler: index.handler memory: 128 timeout: 5
Деплоимфункцию командой:
serverlessdeploy
Еслисделатьфункцию публичной и вызвать ее по предложенному URL, передав путь/api/info , то в ответ мы получим следующую ошибку:
$ curl 'https://functions.yandexcloud.net/%function-id%/api/info'{"errorCode":400,"errorMessage":"Invalid functionID: /%function-id%/api/info","errorType":"ProxyIntegrationError"}
необходима настройка APIGateway.
Создание APIGateway
Спецификация должна соответствоватьстандартуOpenAPI3.0, для нашего простого APIееможнонаписатьруками:
openapi: 3.0.0info: title: Sample API version: 1.0.0paths: /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"}
Кстати,кAPIGatewayможно привязать свой домен.Какприязатьдоменчитайтев этомпосте.
Новый параметрAPIGateway
Совсем недавно вAPIGatewayпоявилась возможность указать параметр вида{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(строки1319в примере ниже).
Пример готового скриптаможноскачать.
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 на YandexCloudFunctionsпо программеfreetier:сервис не тарифицируетпервыймиллионвызовов функций и первые 10ГБчасвыполнения функций.А любые вопросыоработесервисов можно обсудить как с их пользователями, так ис ихсоздателямивчате Yandex Serverless Ecosystem.