ПР. Создание HTTP API с помощью Cloud Functions и API Gatewa
Кратко:
- Создание HTTP API с помощью Cloud Functions и API Gateway.
- Создание спецификации hello-world.yaml для API-шлюза.
- Тестирование запроса с параметрами на API-шлюзе.
- Создание функции function-for-user-requests.py для работы с PostgreSQL.
- Обновление спецификации API Gateway для предоставления публичного доступа к функции.
- Тестирование функции в браузере и проверка работы с параметрами.
Практическая работа. Создание HTTP API с помощью Cloud Functions и API Gateway
На предыдущем практическом занятии мы создали простую систему, которая проверяет доступность сайта yandex.ru и измеряет время ответа на запрос. Полученную информацию функция записывала в базу данных PostgreSQL. На этом уроке мы доработаем начатый проект и добавим REST API, который позволит получать до 50 результатов проверки из базы данных.
Шаг 1. Проверить наличие сервисного аккаунта
Для работы нам понадобится сервисный аккаунт с именем
service-account-for-cf
и ролями editor
, serverless.mdbProxies.user
, который мы создали ранее.Шаг 2. Yandex API Gateway
Создание спецификации
В рабочем каталоге создадим спецификацию
hello-world.yaml
:
openapi: "3.0.0"
info:
version: 1.0.0
title: Test API
paths:
/hello:
get:
summary: Say hello
operationId: hello
parameters:
- name: user
in: query
description: User name to appear in greetings
required: false
schema:
type: string
default: 'world'
responses:
'200':
description: Greeting
content:
'text/plain':
schema:
type: "string"
x-yc-apigateway-integration:
type: dummy
http_code: 200
http_headers:
'Content-Type': "text/plain"
content:
'text/plain': "Hello, {user}!\n"
Мы можем создать API-шлюз с помощью консоли управления, но сейчас воспользуемся CLI.
Инициализация спецификации
Чтобы развернуть API-шлюз, используем спецификацию
hello-world.yaml
:
yc serverless api-gateway create \
--name hello-world \
--spec=hello-world.yaml \
--description "hello world"
В результате успешного создания API-шлюза получим значение параметра
domain
:
yc serverless api-gateway list
yc serverless api-gateway get --name hello-world
Скопируем служебный домен, чтобы проверить работоспособность API-шлюза. Вставим его в адресную строку браузера и допишем в конец
/hello
. Должно получиться следующее:
https://<идентификатор API Gateway>.apigw.yandexcloud.net/hello
Теперь протестируем запрос с параметрами. Добавьте к предыдущему запросу
?user=my_user
. Должно получиться следующее:
https://<идентификатор API Gateway>.apigw.yandexcloud.net/hello?user=my_user
В первом случае в окне браузера вы увидите «Hello, world!», во втором «Hello, my_user!».
Шаг 3. Создание функции
Работа с библиотеками и переменными
До этого момента мы использовали рантайм python37, который не требовал явного указания библиотек, но начиная с версии python39, нужно указывать библиотеки явно. Для работы с
requirements.txt
можно воспользоваться удобной Python-библиотекой pipreqs: чтобы сгенерировать requirements.txt
с помощью pipreqs, достаточно указать рабочий каталог.👉 Чтобы сформировать файл
requirements.txt
, команда pipreqs анализирует все Python-скрипты в текущей папке и вычисляет нужные зависимости. Поэтому создавайте для выполнения практических работ этого курса отдельные папки.В большинстве интерпретаторов Linux для указания текущего каталога предусмотрена переменная
$PWD
. Если файл requirements.txt
уже существует, актуализируйте его с помощью флага --force
, например:
pip install pipreqs
pipreqs $PWD --print
pipreqs $PWD --force
Чтобы создать функцию, проверим доступность переменных для инициации подключения
CONNECTION_ID
, DB_USER
, DB_HOST
, которые мы создали в предыдущей работе с помощью следующих команд:
echo "export CONNECTION_ID=<CONNECTION_ID>" >> ~/.bashrc && . ~/.bashrc
echo "export DB_USER=<DB_USER>" >> ~/.bashrc && . ~/.bashrc
echo "export DB_HOST=<DB_HOST>" >> ~/.bashrc && . ~/.bashrc
Создание функции
Создадим функцию
function-for-user-requests.py
:
import json
import logging
import requests
import os
#Эти библиотеки нужны для работы с PostgreSQL
import psycopg2
import psycopg2.errors
import psycopg2.extras
CONNECTION_ID = os.getenv("CONNECTION_ID")
DB_USER = os.getenv("DB_USER")
DB_HOST = os.getenv("DB_HOST")
# Настраиваем функцию для записи информации в журнал функции
# Получаем стандартный логер языка Python
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# Вычитываем переменную VERBOSE_LOG, которую мы указываем в переменных окружения
verboseLogging = eval(os.environ['VERBOSE_LOG']) ## Convert to bool
#Функция log, которая запишет текст в журнал выполнения функции, если в переменной окружения VERBOSE_LOG будет значение True
def log(logString):
if verboseLogging:
logger.info(logString)
#Запись в базу данных
def save(result, time, context):
connection = psycopg2.connect(
database=CONNECTION_ID, # Идентификатор подключения
user=DB_USER, # Пользователь БД
password=context.token["access_token"],
host=DB_HOST, # Точка входа
port=6432,
sslmode="require")
cursor = connection.cursor()
postgres_insert_query = """INSERT INTO measurements (result, time) VALUES (%s,%s)"""
record_to_insert = (result, time)
cursor.execute(postgres_insert_query, record_to_insert)
connection.commit()
#Формируем запрос
def generateQuery():
select = f"SELECT * FROM measurements LIMIT 50"
result = select
return result
#Получаем подключение
def getConnString(context):
"""
Extract env variables to connect to DB and return a db string
Raise an error if the env variables are not set
:return: string
"""
connection = psycopg2.connect(
database=CONNECTION_ID, # Идентификатор подключения
user=DB_USER, # Пользователь БД
password=context.token["access_token"],
host=DB_HOST, # Точка входа
port=6432,
sslmode="require")
return connection
def handler(event, context):
try:
secret = event['queryStringParameters']['secret']
if secret != 'cecfb23c-bc86-4ca2-b611-e79bc77e5c31':
raise Exception()
except Exception as error:
logger.error(error)
statusCode = 401
return {
'statusCode': statusCode
}
sql = generateQuery()
log(f'Exec: {sql}')
connection = getConnString(context)
log(f'Connecting: {connection}')
cursor = connection.cursor()
try:
cursor.execute(sql)
statusCode = 200
return {
'statusCode': statusCode,
'body': json.dumps(cursor.fetchall()),
}
except psycopg2.errors.UndefinedTable as error:
connection.rollback()
logger.error(error)
statusCode = 500
except Exception as error:
logger.error(error)
statusCode = 500
cursor.close()
connection.close()
return {
'statusCode': statusCode,
'body': json.dumps({
'event': event,
}),
}
Обратите внимание, в коде функции мы заложили параметр
secret
и его значение cecfb23c-bc86-4ca2-b611-e79bc77e5c31
, при котором функция будет выполняться. Таким образом мы обеспечиваем дополнительную защиту при доступе к БД.При создании функции сразу зададим все необходимые переменные и сервисный аккаунт:
yc serverless function create \
--name function-for-user-requests \
--description "function for response to user"
yc serverless function version create \
--function-name=function-for-user-requests \
--memory=256m \
--execution-timeout=5s \
--runtime=python37 \
--entrypoint=function-for-user-requests.handler \
--service-account-id $SERVICE_ACCOUNT_ID \
--environment VERBOSE_LOG=True \
--environment CONNECTION_ID=$CONNECTION_ID \
--environment DB_USER=$DB_USER \
--environment DB_HOST=$DB_HOST \
--source-path function-for-user-requests.py
Шаг 4. Обновление спецификации API Gateway
Наша функция готова, но по умолчанию она не является публичной. Предоставим доступ к этой функции с помощью API-шлюза — обновим ранее созданную спецификацию
hello-world.yaml
. Не забудьте вставить в файл идентификаторы вашей функции и вашего сервисного аккаунта:
openapi: "3.0.0"
info:
version: 1.0.0
title: Updated API
paths:
/results:
get:
x-yc-apigateway-integration:
type: cloud-functions
function_id: <идентификатор функции>
service_account_id: <идентификатор сервисного аккаунта>
operationId: function-for-user-requests
Вызовем перезагрузку нашей спецификации:
yc serverless api-gateway update \
--name hello-world \
--spec=hello-world.yaml
Для тестирования вызовем функцию в браузере сначала без параметра
secret
, а затем — с ним:
https://<идентификатор API Gateway>.apigw.yandexcloud.net/results
https://<идентификатор API Gateway>.apigw.yandexcloud.net/results?secret=cecfb23c-bc86-4ca2-b611-e79bc77e5c31
В ответе увидим результаты тестирования сервиса yandex.ru из базы данных.
Иногда приходится тестировать функцию в процессе разработки: для этого в консоли управления на странице функции перейдите на вкладку Тестирование, в поле Шаблон данных выберите HTTPS-вызов. Нажмите кнопку Запустить тест, и вы увидите код ошибки.

Код функции проверяет параметр
secret
для авторизации, то есть при вызове вы должны передать секретную последовательность, чтобы функция выдала результат. Добавим secret
в параметры запроса в поле Входные данные:
"queryStringParameters": {
"a": "2",
"b": "1",
"secret": "cecfb23c-bc86-4ca2-b611-e79bc77e5c31"
},
Запустим тест ещё раз. В ответе отобразятся данные из базы, как и с запросами через браузер.
