在想用 Serverless 可以做點什么簡單的在線應用后,我想到了一個是在線短鏈生成服務。最后的結(jié)果見:http://x.pho.im/,一個非常簡單的在線應用。
這里的代碼基于:https://github.com/vannio/serverless-shrink。
因為上面的代碼中,不能自動創(chuàng)建域名。然后,再針對數(shù)據(jù)庫進行了一些優(yōu)化。
代碼邏輯
這里的代碼邏輯比如簡單:
- 創(chuàng)建短鏈時,使用生成一個四位的字符串
- 將原有的 URL 和生成的 URL 存儲到 DynamoDB 中
- 在返回的 HTML 中,輸出對應的 URL
- 重定向時,從 DynamoDB 讀取對應的短鏈
- 如果短鏈存在,則執(zhí)行 302 重定向;如果不存在,則返回一個 404。
創(chuàng)建首頁
首頁只是一個簡單的 HTML 表單:
const base_page = `<html>
<h1>Hi!</h1>
<form method="POST" action="">
<label for="uri">Link:</label>
<input type="text" id="link" name="link" size="40" autofocus />
<br/>
<br/>
<input type="submit" value="Shorten it!" />
</form>
</html>`
module.exports.handler = (event, context, callback) => {
console.log(JSON.stringify(event));
callback(
null,
{
statusCode: 200,
body: base_page,
headers: {'Content-Type': 'text/html'},
}
);
}
當我們提交的時候,就會觸發(fā)對應的 POST 請求。
生成短鏈
如上所述,對于個短鏈請求,我們要做這么幾件事:
- 解析出提交表單中的鏈接
- 根據(jù) URL 生成對應的短鏈
- 將對應的 URL 和短鏈的對應關系存儲到 DynamoDB 中
- 如果成功,則返回生成的短鏈;失敗則,返回一個 400
事實上,在存儲 URL 和短鏈的 map 之前,我們應該先判斷一下數(shù)據(jù)中是否已經(jīng)有相應的短鏈。不過,對于這種只針對于我一個用戶的短鏈服務來說,這個步驟有點浪費錢——畢竟要去掃描一遍數(shù)據(jù)庫。所以,我也不想去添加這樣的擴展功能。
接下來,讓我們回到代碼中去,代碼的主要邏輯都是在 Promise 里,按順序往下執(zhí)行。
解析出提交表單中的鏈接
首先,我們通過 querystring 庫來解決中表單中的鏈接。
const submitted = querystring.parse(event.body).link;
根據(jù) URL 生成對應的短鏈
接著,使用 Node.js 中的 crypto.randomBytes 方法來生成八位的偽隨機碼。
crypto.randomBytes(8)
.toString('base64')
.replace(/[=+/]/g, '')
.substring(0, 4)
由于生成的偽隨機碼是 Buffer 類型,因此需要轉(zhuǎn)換為字符串。同時,因為生成的短鏈中不應該有 "=+/",它會導致生成的 URL 有異常。于是,我們便替換掉偽隨機碼中的這些特殊字體。最后,截取生成的字符串的前 4 位。
現(xiàn)在,我們就可以將其存儲到數(shù)據(jù)中了。
存儲到 Dynamo 數(shù)據(jù)庫中。
對應的存儲邏輯如下所示,我們 new 了一個 DocumentClient 對象,然后直接存儲到數(shù)據(jù)庫中。put 函數(shù)中的對象,即是對應的參數(shù)。
return docClient.put({
TableName: tableName,
Item: {
slug: slug,
url: submitted
},
Expected: {
url: {Exists: false}
}
}).promise().then(() => { return slug; });
最后,我們返回了 slug,用于接下來的處理。
返回短鏈給用戶
一切處理正常的話,我們將向用戶返回最后的內(nèi)容:
return callback(
null,
{
statusCode: 200,
body: RenderPage(path.join(prefix, slug).replace(':/', '://'), prefix),
headers: {'Content-Type': 'text/html'}
}
);
其中的 HTML 部分的渲染邏輯如下所示:
function RenderPage (link, submitted) {
return `
<html>
<body>
<h3>
<a href="${link}">${link}</a>
</h3>
<p>URL ${submitted} was shortened to:
<a href="${link}">${link}</a>
</p>
</body>
</html>`
};
是的,只是返回短鏈和原有的鏈接了。
好了,現(xiàn)在我們已經(jīng)擁有這個短鏈了。接下來,就是點擊這個短鏈,看看背后會發(fā)生些什么?
重定向短鏈
首先,我們先在我們的 serverless.yml 中,將短鏈的路徑配置為參數(shù):
functions :
...
redirect:
handler: redirect/index.handler
events:
- http:
path: /{slug}
method: get
然后,從數(shù)據(jù)庫中按短鏈的 slug 查找對應的 URL:
const slug = event.pathParameters.slug;
docClient.get({
TableName: tableName,
Key: {
slug: slug
}
}, (err, data) => {
})
如果存在對應的短鏈,則 302 重定向?qū)υ械?URL:
const item = data.Item;
if (item && item.url) {
callback(
null,
{
statusCode: 302,
body: item.url,
headers: {
'Location': item.url,
'Content-Type': 'text/plain'
}
}
)
}
如果沒有,則返回一個 404。
我們的代碼就是這么的簡單,現(xiàn)在讓我們來部署測試一下。
部署及測試短鏈服務
如果你還沒有 clone 代碼的話,執(zhí)行下面的命令來安裝:
serverless install -u https://github.com/phodal/serverless-guide/tree/master/url-shorter -n url-shorter
然后執(zhí)行 yarn install 來安裝對應的依賴。
如果你在 Route53 上注冊有相應的域名,修改一下 serverless.yml 文件中的域名,我們就可以使用 serverless create_domain 來創(chuàng)建域名的路由。
緊接著,執(zhí)行 serverless deploy 來部署。
api keys:
None
endpoints:
GET - https://4rr5ndhaw3.execute-api.us-east-1.amazonaws.com/dev/
POST - https://4rr5ndhaw3.execute-api.us-east-1.amazonaws.com/dev/
GET - https://4rr5ndhaw3.execute-api.us-east-1.amazonaws.com/dev/{slug}
functions:
main: url-shorter-dev-main
create: url-shorter-dev-create
redirect: url-shorter-dev-redirect
Serverless Domain Manager Summary
Domain Name
x.pho.im
Distribution Domain Name
d2s4y0p5nuw3k7.cloudfront.net
Serverless: Removing old service versions...
一切準備就緒了。
- 訪問 https://x.pho.im/
- 然后輸入一個鏈接,如:https://github.com/phodal/serverless-guide
- 復制生成的地址:https://x.pho.im/rgQC,并返回
- 看是否會重定向到我們的網(wǎng)站上。
Done!