ぶちのブログ

競プロとCTFが趣味なWebエンジニアのアウトプットの場

S3を永続化層に使って、サーバレスで安価なURL短縮サービスを作ってみた(AWS lambda + API Gateway + CloudFront + S3)

はじめに

URL短縮サービスを作って遊んだので、terraformのコードを含めて共有します。

参考記事

以下の記事の構成を参考にしましたが、CloudFormationは使わずに一から作ってみました。

engineer.retty.me

CloudFormationを使わなかった理由は、常用しているterraformでのコード管理との相性が悪いと感じたためです。
また、必要ない機能は極力省き、最小構成での実現を心がけました。

構成

CloudFrontにGETリクエストが来た際はS3の静的サイトホスティングの内容をそのまま返す。
S3の静的サイトホスティングではメタデータを使ったリダイレクトを設定してあるため、リダイレクトされる。
POSTリクエストが来た際は、API Gatewayからlambdaを起動し、S3のファイルを作成する。

f:id:betit0919:20200921171522p:plain
システム構成

リポジトリ

この記事では概要のみ説明するため、詳細な実装は以下のリポジトリを参考にしてください。

github.com

実際の手順

S3の作成

静的サイトホスティングに最低限必要なものがあれば十分です。

今回の例だとindex_documentにはアクセスされることを想定しないので、適当に設定しましょう。

また、S3にライフサイクルポリシーを設定することで、短縮されたURLを一定期間後に失効させることも可能です。(今回は設定していません)

lambdaの作成

s3:ListBucketの権限を付与しないと、ファイルの存在確認ができないことに注意する必要があります。

rubyスクリプトは以下の通りです。URLの文字数はユースケースに応じて適宜変更してください。

# frozen_string_literal: true

require 'aws-sdk-s3'
require 'json'
require 'securerandom'
require 'uri'

BUCKET_NAME = 'your-url-shortener'
BUCKET_WEBSITE_URL = 'https://your-domain'

def lambda_handler(event:, context:)
  s3 = Aws::S3::Resource.new
  url_str = event['url']
  return { statusCode: 400, body: 'Invalid url parameter.' } unless valid_url?(url_str)

  loop do
    file_name = SecureRandom.alphanumeric(6)
    obj = s3.bucket(BUCKET_NAME).object(file_name)
    unless file_exists?(obj)
      obj.put(body: '', website_redirect_location: url_str, acl: 'public-read')
      return { statusCode: 200, body: "#{BUCKET_WEBSITE_URL}/#{file_name}" }
    end
  end
end

private

def valid_url?(url_str)
  uri = URI.parse(url_str)
  uri.is_a?(URI::HTTP) && !uri.host.nil?
rescue URI::InvalidURIError
  false
end

def file_exists?(obj)
  obj.get
  true
rescue Aws::S3::Errors::NoSuchKey
  false
end

API Gateway

lambdaをエンドポイントから叩けるようにするだけで、特に工夫する点はありません。
REST APIで作成しました。

CloudFront

ここまでで動かすだけならば必要はありません。
作成する際はAPI Gatewayのエンドポイントを叩き、その後はS3のリンクを直接使えば良いです。
ただ、ドメインが一つにならないことと、S3のリンクが長くて短縮の効率が下がってしまうことを避けるために、CloudFrontを使いました。

本番運用の場合はキャッシュを適切に設定することによって、非常にパフォーマンスの良いシステムになります。

感想

URL短縮サービスの構成として、永続化層にS3を使うというのは、非常に面白い発想でした。
集計周りにやや難がある以外は、安価でシンプルでスケーラブルという素晴らしい構成だと思います。

ただ、この構成はガチガチのベンダーロックインではあるので、そちらについては注意する必要がありそうです。