この記事は コネヒト Advent Calendar 2019 2日目 の記事です。
はじめに
社内のSlackでURL短縮サービスが自社にあればよいなー、という呟きがあったので、昼休みに一人ハッカソンをしてみた時の話です。(※実運用はしていないです)
概要
https://s.hyiromori.com/
にアクセスするとURL短縮用の画面が表示されて https://s.hyiromori.com/[5文字の乱数]
のようなURLを発行できるようなWebアプリを作りました。
ドメインが短くないと、あまり短縮された感じがないですが、まだ実験段階なのでご勘弁ください。
ソースは GitHub にあります。
技術スタック
使い慣れている serverless フレームワークを使って、作ってみました。
使用しているAWSのサービスは以下のようになります。
タイムライン
コミットをサボっていたので、覚えている限りで書きます。
1. プロジェクトの作成
何個か作ったことがあったので、以下のファイルを別プロジェクトからコピペしました。
- serverless.yml: Serverlessの設定ファイル
- package.json: nodeパッケージの定義とか
.eslint.json
とかもコピペしましたが、本質ではないので割愛します。
2. serverless.yml の定義
DynamoDBの定義
DynamoDBはAWSマネージドなNoSQLデータベースです。 serverless と相性が良いです。
serverless
の resource
セクションは CloudFormation で書くので、公式ドキュメント を参考に記述します。
resources:
Resources:
ShortenTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: serverless-shorten
AttributeDefinitions:
- AttributeName: key
AttributeType: S
KeySchema:
- AttributeName: key
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 3
WriteCapacityUnits: 3
ここでは、短縮したURLのキーを key
という名前のハッシュキーで格納する、純粋なKey-Valueストアとして定義しています。
一応ですが、IAMポリシーも定義しておかないとアクセスできないので注意してください(詳しくはGitHubリポジトリを参照してください)
エンドポイントの定義
functions
セクションに handler
(Lambda
のエントリポイント)を記述して events
にHTTPの設定らしきものを書くと、serverless が自動的に API Gateway
と Lambda
を設定してくれます。便利!
functions:
index:
handler: handler/index.handler
events:
- http:
method: GET
path: ''
cors: true
post:
handler: handler/post.handler
events:
- http:
method: POST
path: ''
cors: true
get:
handler: handler/get.handler
events:
- http:
method: GET
path: '{key}'
cors: true
ここでは、以下の3つのエンドポイントを定義しています。
-
GET /
: URL変換を実行するためのHTMLを返すエンドポイント -
POST /
: URL変換を実行して、短縮したURLを返すAPI用エンドポイント -
GET /{key}
: 短縮URLでアクセスして、リダイレクトするエンドポイント
3. エンドポイントの実装1: GET /
表示用の HTML
を返却するだけのエンドポイントです。
const html = `
<!doctype html>
<html lang="ja">
<!-- 省略 -->
</html>
`;
module.exports.handler = async () => ({
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/html',
},
body: html,
});
面倒くさかった 時間なかったので、JSにそのままHTMLを記述してそれを返しています。
当然、別ファイルにした方が良いですので、あまり真似しないでください。
いつか暇な時に対応しようと思います。
4. エンドポイントの実装2: POST /
短縮URLを生成するエンドポイントです。
キーとなる5文字の乱数を生成して、ポストされたURLと一緒に DynamoDB
に保存して、短縮したURLを返却しています。
ちなみに、もし乱数が衝突しても無言で上書きします😨
今回はとりあえず作ることを目的にしているので省略しますが、ちゃんと運用する時はチェックを忘れないようにしましょう!
const crypto = require('crypto');
const { DynamoDB } = require('aws-sdk');
// DynamoDB のクライアントの設定
const client = new DynamoDB.DocumentClient({ apiVersion: '2012-10-08', region: 'ap-northeast-1' });
module.exports.handler = async (event) => {
const { Origin } = event.headers;
const { url } = JSON.parse(event.body);
// URLをチェックして、不正なURLの場合は400を返却する
if (!url.match(/^(http|https):\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$/g)) {
return {
statusCode: 400,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'ERROR: Invalid URL.' }),
};
}
// ランダムなキーを生成する
const key = crypto.randomBytes(10).toString('base64').replace(/[+/]/g, '').substr(0, 5);
// DynamoDB にキーとURL情報を保存する
const params = {
TableName: process.env.DYNAMODB_TABLE,
Item: { key, url },
};
await client.put(params).promise();
// 短縮したURLを返却する
const payload = {
url: `${Origin}/${key}`,
};
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
};
};
5. エンドポイントの実装3: GET /{key}
短縮URLでアクセスされ、元のURLにリダイレクトするためのエンドポイントです。
アクセスされたパスをキーに、元のURLを DynamoDB
から取得して 302
でリダイレクトさせています。
const { DynamoDB } = require('aws-sdk');
// DynamoDB のクライアントの設定
const client = new DynamoDB.DocumentClient({ apiVersion: '2012-10-08', region: 'ap-northeast-1' });
module.exports.handler = async (event) => {
const { key } = event.pathParameters;
// キーに該当するURLを取得する
const params = {
TableName: process.env.DYNAMODB_TABLE,
Key: { key },
};
const res = await client.get(params).promise();
// 該当するURLが見つからない場合は404を返却する
if (!res || !res.Item || !res.Item.url) {
return {
statusCode: 404,
headers: { 'Content-Type': 'text/plain' },
body: '404 Not Found',
};
}
// 該当のURLにリダイレクトさせる
const { url } = res.Item;
return {
statusCode: 302,
headers: {
'Content-Type': 'text/plain',
Location: url,
},
body: `Redirect to: ${url}`,
};
};
※ 「DynamoDB のクライアントの設定」あたりは PUT /
と同じ処理ですが、理解しやすくするためにワザと重複を許容しています。本来はこういう共通処理は切り出してまとめたほうが良いです。
6. デプロイ
AWS CLI の設定ができていれば npm run deploy
とコマンドを打てば一発でデプロイできます!
ちなみに私は aws-vault をAWSの認証情報を管理しているので、こんな感じで実行しています。
aws-vault exec [profile] -- npm run deploy
とてもセキュアになるので、非常におすすめです。
これもいつか記事にしよう。
7. 完成
https://s.hyiromori.com/
にアクセスして、動作することを確認します。
生成された s.hyiromori.com/IDuhK
にアクセスすると無事 https://example.com/
にリダイレクトされました!
おわりに
いかがでしたか?
serverless を使うと、こんなに手軽に実装&デプロイができますよー。
そして、インフラの管理がいらない&費用も(アクセスがあまりないので)ほとんどかからない、と良いことたくさんです!
皆さんも、とりあえずなんかWebサービス作ってみたいと思ったら、serverless 使ってみてくださいね!
今後の課題
- ドメイン短くしたい!
- アクセス数とか記録しておくと良さそう。
- ついでに分析とかまでできると完璧!
PR
コネヒトでは、一緒に働いてくれるエンジニア募集中です!興味があればこちらもみてください!
パパも大活躍!!ママリのwebフロントエンドを支えるエンジニア募集!
※追記(2019/12/02 13:30)
継続的に運用するつもりはないので、アドベントカレンダー終了後にクローズ予定です。
クローズしました。(2019/12/27 23:00)