URL shortener
With this script, you will create a URL shortening service using serverless technologies available in Yandex Cloud.
The service accepts user requests via a public API gateway. The hosting service sends the user an HTML page with a field for entering the URL. The function sends the entered URL for storage in a serverless database, shortens it, and returns it to the user. When the user enters the shortened URL, the function finds the full URL in the database and redirects the user's request to it.
To configure and test the service:
- Prepare your cloud.
- Set up hosting for the URL shortener page.
- Create a service account.
- Create a database in Yandex Managed Service for YDB.
- Set up a function in Yandex Cloud Functions.
- Publish the service via Yandex API Gateway.
- Test the URL shortener.
If you no longer need the resources you created, delete them.
Prepare your cloud
Sign up for Yandex Cloud and create a billing account:
- Go to the management console
and log in to Yandex Cloud or create an account if you do not have one yet. - On the Billing
page, make sure you have a billing account linked and it has theACTIVE
orTRIAL_ACTIVE
status. If you do not have a billing account, create one.
If you have an active billing account, you can go to the cloud page
Learn more about clouds and folders.
Required paid resources
The cost of resources for the script includes:
- Fee for using the storage (see Yandex Object Storage pricing).
- Fee for accessing the database (see Managed Service for YDB pricing).
- Fee for function calls (see Cloud Functions pricing).
- Fee for requests to the API gateway (see API Gateway pricing).
Set up hosting for the URL shortener page
To create a bucket to place the HTML page of your service in and configure it for hosting static websites:
-
In the management console
, select your working folder. -
Select Object Storage.
-
Click Create bucket.
-
On the bucket creation page:
-
Enter the name of the bucket.
Warning
Bucket names are unique throughout Object Storage, which means you cannot create two buckets with the same name, even in different folders belonging to different clouds.
-
Set the maximum size to
1 GB
. -
Choose
Public
access to read objects. -
Click Create bucket to complete the operation.
-
-
Copy the HTML code and paste it into the
index.html
file:HTML code<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>URL shortener</title> <!-- will help you avoid an extra GET request to an address /favicon.ico --> <link rel="icon" href="data:;base64,iVBORw0KGgo="> </head> <body> <h1>Welcome</h1> <form action="javascript:shorten()"> <label for="url">Enter a link:</label><br> <input id="url" name="url" type="text"><br> <input type="submit" value="Shorten"> </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>The error ${error} has occurred, try again</p>` }) } </script> </html>
-
Click the name of the created bucket.
-
Click Upload objects.
-
Specify the prepared
index.html
file. -
Click Upload.
-
In the left-hand panel, select the Website tab.
-
Select Hosting.
-
Specify the website's homepage:
index.html
. -
Click Save.
Create a service account
To create a service account for the service components to interact:
-
Go to your working folder.
-
At the top of the screen, go to the Service accounts tab.
-
Click Create service account.
-
Enter the name of the service account:
serverless-shortener
. -
Click Add role and select the
editor
role. -
Click Create.
-
Click on the name of the created service account.
Save the service account ID, you'll need it in the next steps.
Create a database in Managed Service for YDB
To create a Managed Service for YDB database and configure it to store URLs:
-
Go to your working folder.
-
In the list of services, select Managed Service for YDB.
-
Click Create a database.
-
Enter the DB name:
for-serverless-shortener
. -
Select the Serverless database type.
-
Click Create a database.
-
Wait until the database starts.
When a database is being created, it has the
Provisioning
status. When it's ready for use, the status changes toRunning
. -
Click the name of the created database.
Save the Endpoint field value, you will need it in the next steps.
-
In the left-hand panel, select the Navigation tab.
-
Click New SQL query.
-
Copy the SQL query and paste it into the Query field:
CREATE TABLE links ( id Utf8, link Utf8, PRIMARY KEY (id) );
-
Click Run.
Set up a function in Cloud Functions
To create and set up a URL shortening function:
-
Go to your working folder.
-
In the list of services, select Cloud Functions.
-
Click Create function.
-
Enter the function name:
for-serverless-shortener
. -
Click Create.
-
In the Python drop-down list, choose the
python312
runtime environment. -
Click Continue.
-
Copy the function code and paste it into the
index.py
file under Function code.Function codeimport ydb import urllib.parse import hashlib import base64 import json import os def decode(event, body): # The request body can be encoded. 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("Enter both environment variables") 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] # The link might include encoded characters, for example, %. They might create issues with API Gateway operation on redirect, # Make sure to remove them by calling 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, 'The url parameter is missing in the request body') 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, 'This link does not exist') # We need these checks because we have only one function. # Ideally, each path in the API Gateway should have its own function. def get_result(url, event): if url == "/shorten": return shorten(event) if url.startswith("/r/"): return redirect(event) return response(404, {}, False, 'This path does not exist') def handler(event, context): url = event.get('url') if url: # The API Gateway may return a URL with a trailing question mark. if url[-1] == '?': url = url[:-1] return get_result(url, event) return response(404, {}, False, 'This function should be called using the API Gateway.')
-
Under Function code, create a file named
requirements.txt
and paste the following text into it:ydb
-
Specify the entry point:
index.handler
. -
Set the timeout value to
5
. -
Select the
serverless-shortener
service account. -
Add environment variables:
endpoint
: Enter the first part of the previously saved Endpoint field value (preceding/?database=
), e.g.,grpcs://ydb.serverless.yandexcloud.net:2135
.database
: Enter the second part of the previously saved Endpoint field value (following/?database=
), e.g.,/ru-central1/r1gra875baom********/g5n22e7ejfr1********
.
-
Click Save changes.
-
Under Overview, enable the Public function option.
Save the function ID, you will need it at the next steps.
Publish the service via API Gateway
To publish the service via API Gateway:
-
Go to your working folder.
-
In the list of services, select API Gateway.
-
Click Create API gateway.
-
In the Name field, enter
for-serverless-shortener
. -
Copy and paste the following code into the Specification section:
Specificationopenapi: 3.0.0 info: title: for-serverless-shortener version: 1.0.0 paths: /: get: x-yc-apigateway-integration: type: object_storage bucket: <bucket_name> # <-- name of the bucket object: index.html # <-- HTML file name presigned_redirect: false service_account: <service_account_id> # <-- service-account-ID operationId: static /shorten: post: x-yc-apigateway-integration: type: cloud_functions function_id: <function_id> # <-- function ID operationId: shorten /r/{id}: get: x-yc-apigateway-integration: type: cloud_functions function_id: <function_id> # <-- function ID operationId: redirect parameters: - description: id of the url explode: false in: path name: id required: true schema: type: string style: simple
Edit the specification code:
- Replace
<service_account_id>
with the ID of the previously created service account. - Replace
<function_id>
with the ID of the previously created function. - Replace
<bucket_name>
with the name of the previously created bucket.
- Replace
-
Click Create.
-
Click on the name of the created API gateway.
-
Copy the
url
value from the specification.Use this URL to work with the created service.
Test the URL shortener
To check that the service components interact properly:
-
Open the copied URL in the browser.
-
In the input field, enter the URL that you want to shorten.
-
Click Shorten.
You'll see the shortened URL below.
-
Follow this link. As a result, the same page should open as when using the full URL.
How to delete the resources you created
To stop paying for the resources you created: