概要
URL短縮サービスを AWS API Gateway + Lambda で実装した例になります。
データストアには DynamoDB を使用しています。
掲載しているコードはこちらでも公開しています。
セットアップ
デプロイにはServerlessフレームワークを使うのでなければインストールします。
serverless-hooks-plugin
はデプロイコマンドをフックするためのプラグインです。
mkdir simple-url-shortener && cd simple-url-shortener
npm install -g serverless
npm install serverless-hooks-plugin
以下で使用するライブラリのGemfileを作っておきます。
gem 'aws-record'
gem 'hashids'
ハンドラーの作成
今回作成するルートは2です。
1つはURLを登録して短縮URLにして返す/url/shortener
もう一つは短縮URLへアクセスした時に登録したURLへリダイレクトさせる/
です。
短縮URLモデル
URLの保存先にはDynamoDBを使います。
aws-record
というDynamoDBのAPIラッパーを使って短縮URLを表すモデルを作成します。
require 'aws-record'
class UrlShortener
include Aws::Record
set_table_name ENV['DYNAMODB_TABLE']
string_attr :id, hash_key: true
string_attr :short_url
string_attr :long_url
string_attr :date
# Check url valid
def valid?
url = begin
URI.parse(long_url)
rescue StandardError
false
end
url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS)
end
end
URLを登録して短縮URLを生成
ランダムなIDの生成にはHashids
というライブラリを使っています。
require_relative '../models/url_shortener'
require 'aws-record'
require 'hashids'
def handler(event:, context:)
data = JSON.parse(event['body'])
# Check whether longUrl is valid
url = UrlShortener.new
url.long_url = data['longUrl']
return { statusCode: 401, body: 'Invalid long url' } unless url.valid?
# Create url code
now = Time.now
hashids = Hashids.new(ENV['HASHIDS_SALT'])
hash = hashids.encode(now.to_i, now.usec)
base_url = ENV['BASE_URL'] || "https://#{event['headers']['Host']}/#{event['requestContext']['stage']}"
url.id = hash
url.short_url = "#{base_url}/#{url.id}"
url.date = now.to_s
begin
url.save
rescue Aws::Record::Errors::RecordError => e
puts e
{ statusCode: 500, body: e }
end
{ statusCode: 200, body: url.short_url }
end
短縮URLへアクセスしたら登録URLへリダイレクト
require_relative '../models/url_shortener'
def handler(event:, context:)
code = event['pathParameters']['code']
begin
url = UrlShortener.find(id: code)
rescue Aws::Record::Errors::NotFound => e
puts e
{ statusCode: 404, body: 'URL not found' }
rescue Aws::Record::Errors::RecordError => e
puts e
{ statusCode: 500, body: 'Internal server error' }
end
{ statusCode: 301, headers: { Location: url.long_url } }
end
デプロイ
serverless.yml を作成してデプロイコマンドを実行します。
service: simple-url-shortener
provider:
name: aws
region: ${opt:region, 'us-east-1'}
runtime: ruby2.5
environment:
DYNAMODB_TABLE: url-shortener-${opt:stage, 'dev'}
HASHIDS_SALT: 'This is my salt'
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: 'arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}'
plugins:
- serverless-hooks-plugin
custom:
hooks:
package:initialize:
- bundle install --deployment
functions:
redirectOriginal:
handler: routes/index.handler
events:
- http:
path: /{code}
method: get
shortener:
handler: routes/url.handler
events:
- http:
path: url/shortener
method: post
cors: true
resources:
Resources:
UrlShortenerTable:
Type: 'AWS::DynamoDB::Table'
Properties:
TableName: ${self:provider.environment.DYNAMODB_TABLE}
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
これで準備が整ったのでいよいよデプロイです。
bundle
sls deploy
テスト
デプロイしたAPIをテストしてみます。
返って来たURLをブラウザで開いてみてhttps://example.com
に飛んでいれば成功です。
curl -d '{"longUrl":"https://example.com"}' https://{api}.execute-api.{region}.amazonaws.com/url/shortener
さいごに
今回作成したAWSのリソースは以下のコマンドで削除できます。
sls remove