Сокращатель ссылок
С помощью этого сценария вы создадите сервис для сокращения URL, используя serverless-технологии, доступные в Yandex Cloud.
Сервис принимает обращения пользователей через публичный API-шлюз. Пользователь получает с хостинга HTML-страницу с полем для ввода URL. Функция отправляет введенный URL на хранение в serverless-базу данных, сокращает его и возвращает пользователю. При обращении пользователя по сокращенному URL, функция находит в БД полный URL и перенаправляет на него запрос пользователя.
Чтобы настроить и протестировать сервис:
- Подготовьте облако к работе.
- Настройте хостинг страницы сокращателя.
- Создайте сервисный аккаунт.
- Создайте БД в Yandex Managed Service for YDB.
- Настройте функцию в Yandex Cloud Functions.
- Опубликуйте сервис через Yandex API Gateway.
- Проверьте работу сокращателя.
Если созданные ресурсы вам больше не нужны, удалите их.
Подготовьте облако к работе
Зарегистрируйтесь в Yandex Cloud и создайте платежный аккаунт:
- Перейдите в консоль управления
, затем войдите в Yandex Cloud или зарегистрируйтесь. - На странице Yandex Cloud Billing
убедитесь, что у вас подключен платежный аккаунт, и он находится в статусеACTIVE
илиTRIAL_ACTIVE
. Если платежного аккаунта нет, создайте его и привяжите к нему облако.
Если у вас есть активный платежный аккаунт, вы можете создать или выбрать каталог, в котором будет работать ваша инфраструктура, на странице облака
Подробнее об облаках и каталогах.
Необходимые платные ресурсы
В стоимость ресурсов для сценария входят:
- Плата за использование хранилища (см. тарифы Yandex Object Storage).
- Плата за обращения к базе данных (см. тарифы Managed Service for YDB).
- Плата за вызовы функции (см. тарифы Cloud Functions).
- Плата за запросы к API-шлюзу (см. тарифы API Gateway).
Настройте хостинг страницы сокращателя
Чтобы создать бакет, разместить в нем HTML-страницу вашего сервиса и настроить бакет для хостинга статических сайтов:
-
В консоли управления
выберите свой рабочий каталог. -
Выберите сервис Object Storage.
-
Нажмите кнопку Создать бакет.
-
На странице создания бакета:
-
Введите имя бакета.
Важно
Имена бакетов уникальны для всего Object Storage, т. е. нельзя создать два бакета с одинаковыми именами даже в разных каталогах разных облаков.
-
Задайте максимальный размер
1 ГБ
. -
Выберите доступ на чтение объектов
Публичный
. -
Нажмите кнопку Создать бакет для завершения операции.
-
-
Скопируйте HTML-код и вставьте его в файл
index.html
:HTML-код
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Сокращатель URL</title> <!-- предостережет от лишнего GET запроса на адрес /favicon.ico --> <link rel="icon" href="data:;base64,iVBORw0KGgo="> </head> <body> <h1>Добро пожаловать</h1> <form action="javascript:shorten()"> <label for="url">Введите ссылку:</label><br> <input id="url" name="url" type="text"><br> <input type="submit" value="Сократить"> </form> <p id="shortened"></p> </body> <script> function shorten() { const link = document.getElementById("url").value fetch("/shorten", { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: link }) .then(response => response.json()) .then(data => { const url = data.url document.getElementById("shortened").innerHTML = `<a href=${url}>${url}</a>` }) .catch(error => { document.getElementById("shortened").innerHTML = `<p>Произошла ошибка ${error}, попробуйте еще раз</p>` }) } </script> </html>
-
Нажмите на имя созданного бакета.
-
Нажмите кнопку Загрузить объекты.
-
Укажите подготовленный ранее файл
index.html
. -
Нажмите кнопку Загрузить.
-
На панели слева выберите Настройки.
-
На вкладке Веб-сайт:
- Выберите
Хостинг
. - Укажите главную страницу сайта —
index.html
.
- Выберите
-
Нажмите кнопку Сохранить.
Создайте сервисный аккаунт
Чтобы создать сервисный аккаунт для взаимодействия компонентов сервиса:
-
Перейдите в свой рабочий каталог.
-
В списке сервисов выберите Identity and Access Management.
-
Нажмите кнопку Создать сервисный аккаунт.
-
Введите имя сервисного аккаунта
serverless-shortener
. -
Нажмите Добавить роль и выберите роль
editor
. -
Нажмите кнопку Создать.
-
Нажмите на имя созданного сервисного аккаунта.
Сохраните идентификатор созданного сервисного аккаунта, он понадобится на следующих шагах.
Создайте БД в Managed Service for YDB
Чтобы создать базу данных Managed Service for YDB и настроить ее для хранения ссылок:
-
Перейдите в свой рабочий каталог.
-
В списке сервисов выберите Managed Service for YDB.
-
Нажмите кнопку Создать базу данных.
-
Введите имя базы
for-serverless-shortener
. -
Выберите тип базы данных Serverless.
-
Нажмите кнопку Создать базу данных.
-
Дождитесь запуска базы данных.
В процессе создания база будет иметь статус
Provisioning
, а когда станет готова к использованию — статус изменится наRunning
. -
Нажмите на имя созданной БД.
Сохраните значение поля Эндпоинт, оно понадобится на следующих шагах.
-
На панели слева выберите вкладку Навигация.
-
Нажмите кнопку Новый SQL-запрос.
-
Скопируйте SQL-запрос и вставьте его в поле Запрос:
CREATE TABLE links ( id Utf8, link Utf8, PRIMARY KEY (id) );
-
Нажмите кнопку Выполнить.
Настройте функцию в Cloud Functions
Чтобы создать и настроить функцию сокращения URL:
-
Перейдите в свой рабочий каталог.
-
В списке сервисов выберите Cloud Functions.
-
Нажмите кнопку Создать функцию.
-
Введите имя
for-serverless-shortener
. -
Нажмите кнопку Создать.
-
В выпадающем списке Python выберите среду выполнения
python312
. -
Нажмите кнопку Продолжить.
-
Скопируйте код функции и вставьте его в файл
index.py
в блоке Код функции.Код функции
import ydb import urllib.parse import hashlib import base64 import json import os def decode(event, body): # Тело запроса может быть закодировано. is_base64_encoded = event.get('isBase64Encoded') if is_base64_encoded: body = str(base64.b64decode(body), 'utf-8') return body def response(statusCode, headers, isBase64Encoded, body): return { 'statusCode': statusCode, 'headers': headers, 'isBase64Encoded': isBase64Encoded, 'body': body, } def get_config(): endpoint = os.getenv("endpoint") database = os.getenv("database") if endpoint is None or database is None: raise AssertionError("Нужно указать обе переменные окружения") credentials = ydb.iam.MetadataUrlCredentials() return ydb.DriverConfig(endpoint, database, credentials=credentials) def execute(config, query, params): with ydb.Driver(config) as driver: try: driver.wait(timeout=5, fail_fast=True) except TimeoutError: print("Connect failed to YDB") print("Last reported errors by discovery:") print(driver.discovery_debug_details()) return None session = driver.table_client.session().create() prepared_query = session.prepare(query) return session.transaction(ydb.SerializableReadWrite()).execute( prepared_query, params, commit_tx=True ) def insert_link(id, link): config = get_config() query = """ DECLARE $id AS Utf8; DECLARE $link AS Utf8; UPSERT INTO links (id, link) VALUES ($id, $link); """ params = {'$id': id, '$link': link} execute(config, query, params) def find_link(id): print(id) config = get_config() query = """ DECLARE $id AS Utf8; SELECT link FROM links where id=$id; """ params = {'$id': id} result_set = execute(config, query, params) if not result_set or not result_set[0].rows: return None return result_set[0].rows[0].link def shorten(event): body = event.get('body') if body: body = decode(event, body) original_host = event.get('headers').get('Origin') link_id = hashlib.sha256(body.encode('utf8')).hexdigest()[:6] # В ссылке могут быть закодированные символы, например, %. Это помешает работе API Gateway при редиректе, # поэтому следует избавиться от них вызовом urllib.parse.unquote. insert_link(link_id, urllib.parse.unquote(body)) return response(200, {'Content-Type': 'application/json'}, False, json.dumps({'url': f'{original_host}/r/{link_id}'})) return response(400, {}, False, 'В теле запроса отсутствует параметр url') def redirect(event): link_id = event.get('pathParams').get('id') redirect_to = find_link(link_id) if redirect_to: return response(302, {'Location': redirect_to}, False, '') return response(404, {}, False, 'Данной ссылки не существует') # Эти проверки нужны, поскольку функция у нас одна. # В идеале сделать по функции на каждый путь в API Gateway. def get_result(url, event): if url == "/shorten": return shorten(event) if url.startswith("/r/"): return redirect(event) return response(404, {}, False, 'Данного пути не существует') def handler(event, context): url = event.get('url') if url: # Из API Gateway URL может прийти со знаком вопроса на конце. if url[-1] == '?': url = url[:-1] return get_result(url, event) return response(404, {}, False, 'Эту функцию следует вызывать при помощи API Gateway.')
-
В блоке Код функции создайте файл
requirements.txt
и вставьте в него следующий текст:ydb
-
Укажите точку входа
index.handler
. -
Задайте таймаут
5
. -
Выберите сервисный аккаунт
serverless-shortener
. -
Добавьте переменные окружения:
endpoint
— введите первую часть сохраненного ранее поля Эндпоинт (часть до вхождения/?database=
). Например,grpcs://ydb.serverless.yandexcloud.net:2135
.database
— введите вторую часть сохраненного ранее поля Эндпоинт (часть после вхождения/?database=
). Например,/ru-central1/r1gra875baom********/g5n22e7ejfr1********
.
-
Нажмите кнопку Сохранить изменения.
-
В блоке Обзор включите опцию Публичная функция.
Сохраните идентификатор созданной функции, он понадобится на следующих шагах.
Опубликуйте сервис через API Gateway
Чтобы опубликовать сервис через API Gateway:
-
Перейдите в свой рабочий каталог.
-
В списке сервисов выберите API Gateway.
-
Нажмите кнопку Создать API-шлюз.
-
В поле Имя введите
for-serverless-shortener
. -
Скопируйте и вставьте следующий код в блок Спецификация:
Спецификация
openapi: 3.0.0 info: title: for-serverless-shortener version: 1.0.0 paths: /: get: x-yc-apigateway-integration: type: object_storage bucket: <имя_бакета> # <-- имя бакета object: index.html # <-- имя html-файла presigned_redirect: false service_account: <service_account_id> # <-- ID сервисного аккаунта operationId: static /shorten: post: x-yc-apigateway-integration: type: cloud_functions function_id: <function_id> # <-- ID функции operationId: shorten /r/{id}: get: x-yc-apigateway-integration: type: cloud_functions function_id: <function_id> # <-- ID функции operationId: redirect parameters: - description: id of the url explode: false in: path name: id required: true schema: type: string style: simple
Внесите правки в код спецификации:
- Замените
<service_account_id>
на идентификатор созданного ранее сервисного аккаунта. - Замените
<function_id>
на идентификатор созданной ранее функции. - Замените
<имя_бакета>
на имя созданного ранее бакета.
- Замените
-
Нажмите кнопку Создать.
-
Нажмите на имя созданного API-шлюза.
-
Скопируйте значение
url
из спецификации.Используйте этот URL для работы с созданным сервисом.
Проверьте работу сокращателя
Чтобы проверить правильность взаимодействия компонентов сервиса:
-
Откройте в браузере скопированный URL.
-
В поле для ввода введите ссылку, которую вы хотите сократить.
-
Нажмите кнопку Сократить.
Ниже отобразится сокращенная ссылка.
-
Перейдите по сокращенной ссылке, должна открыться та же страница, что и по ссылке до сокращения.
Как удалить созданные ресурсы
Чтобы перестать платить за созданные ресурсы: