鐵人賽-文章列表

大家好,我是 NiJia
以下是我最近參加 IT 邦幫忙 30 天鐵人賽的文章列表,主要是紀錄從我進入新公司後學到的Serverless,並透過結合 LINE API 實現在 AWS 上,歡迎大家參考取用 😃
分類

大家好,我是 NiJia
以下是我最近參加 IT 邦幫忙 30 天鐵人賽的文章列表,主要是紀錄從我進入新公司後學到的Serverless,並透過結合 LINE API 實現在 AWS 上,歡迎大家參考取用 😃
其實個家園端平台多少都會有這問題,畢竟大家都是靠流量賺錢,自然會需要個方法限制使用者,不然免費爽爽用他們也不是做慈善的 🤣,像我最常用的 Heroku,只要 30 分鐘沒有使用他就會進入睡眠時間(參考),叫醒的時間需要 10~15 秒,不過對於我這個免費宅來說測試夠用了 🤣
AWS以我實際測試起來大概 2 秒左右,回應時間看起來不會太差,其實第一天就有提到這部分,需要的話可以參考。
若是需要它隨時都在都活著的話就要搭配 warm-up,抑或是搭配 crontab 去設定特定時間執行 warm-up。
在快速成長的前、中期是很適合這個服務的, 畢竟開發者只要將 serverless.yml 基本設定完之後就可以先好好開發,等到中期之後把設定檔稍微整理一下又是一尾活龍了,且平台大多都擁有 auto-scaling 的功能,在服務快扛不住流量的時候只要錢拿出來平台就幫你解決。
只是說當流量越來越大之後相對錢也會越付越多,或許當流量 > 10K 之後代表你的服務應該也有不少人使用了,自己架主機應該不會是什麼難事(?)
幫忙推廣一下 Backend 大大的 高流量心得雜談
在 FaaS 架構下其實很適合 Chatbot 的服務,以概念來說實作時只需要一個路由**(Function)**即個完成,不需要額外太多的資源去處理 web server 的問題,像是這個系列的只要透過 serverless + WSGI 就可以馬上就可以進行本地測試,部署只要下 sls deploy,實在是超級快又輕鬆,不用像以前一樣還要去處理 Nginx 等等相關的問題 😁。
且 AWS、Google 在使用 python 跟 nodejs 上支援度很高,也不用怕找不到資源使用。
使用 Node JS 的朋友可以參考 C.T Lin 大大的文章,他們家的 bottender 超猛的!
在 Log 這部分我覺得 AWS 有點不太好的地方就是他們的 Cloud Watch 應該是獨立服務,當我的 Lambda 輸出紀錄時大概要過 10 秒才會印出來,不像以前自己架主機時只要進 server 就可以馬上切到目錄看紀錄抓蟲,在使用這雲端服務可能在抓蟲有一半的時間都在等 log 出來,有時候急的時候還真的會被氣死 😭。
像是我前一陣子發的一篇文章,SNS 被觸發事件之後回來的東西是舊的,那時候看到錯誤訊息真的是心很涼,就得用一個 Lambda 排程一直去抓 DB 的 updated_at 欄位有沒有變更時間而去更新狀態,這樣情況就會多浪費一個 Function 去處理這樣的事件,不得不說專門來支援 AWS 服務的 boto3 套件(python)在某些時候做的事不是那麼完善,得使用旁門做到的方法去完成這件事,讓整個開發體驗不是那麼好,若他們可以把一些 error handle 弄更好那就完美了。
其實還有好多服務沒有玩到
但其實使用 AWS 來實作 Serverless 的服務也是這一陣子加入新公司所學到的新技能,過程中總是會用舊的開發思維去想,但常常都因為這樣落坑 😓,但也因此讓我更了解像 AWS 這類的雲服務他們是如何開發每個服務並如何串接,同時也研究了 Serverless 相關的 plugins,看他們是如何支援雲平台讓開發者在開發時可以更像平常時在開發 web,也因為這樣我去貢獻了 example 到了 Serverless 的範例專案上面(參考),慶祝完成人生第一場鐵三十,跟自己的競賽也告一段落,發現趕著寫文章品質真的會差很多,看來是時候該整理一下,謝謝您的收看 <(_ _)>
Imagemap

Richmenu
設計工具


API 文件

LIFF (LINE Front-end Framework)
推播 500 則
LINE Notify 嚕!客服與機器人的切換
個人的 mur mur 🤣
當然整體來說還是很喜歡使用 LINE 這個平台(絕對不是因為熊大 🐻),東西也不會像一開始在開發時什麼都找不太到,並且功能一直 release,讓服務可以透過不同的整合方法去達成目標,接下來 LINE 應該還會再繼續釋出不同的東西,就請大家拭目以待,或者...來參加 chatbot 小聚直接問問 LINE 的傳教士如何呢 😁
在之前的文章都有將 Notify、LINE Login所需要的靜態頁面放在 S3 上面,讓其他使用者去點,雖然把前端放在程式裡面用 render template 比較好,可是在求快的情況下我把靜態頁面寫完就扔上去了(被拖走),其實也可以透過這個方法去幫忙轉址,不過接下來我會用我的臉書做個範例提供參考~
照著之前的範例來建立一個名為 facebook.nijialin.com 來做 S3 轉址的範例
建立完之後因為我們需要把網址公開給其他使用者點選,在第三個分頁中先把第一個選項的 Block all public access 更換成 off 狀態
到第二個按鈕選項選擇 Public access,暴力一點把所有選項都打開 🤣
前面都已經把權限開完,選擇Static Website hosting這個選項,這個是 S3 提供來讓他有另一種功能,不過我是覺得這功能在這邊有點奇怪 XD

接著就來到 Route 53,點選左欄的 Hosted Zone
點選上面藍色的按鈕,右邊會出現輸入的地方讓你填
這邊我會輸入跟 S3 bucket 的名字一樣,然後 Alias 選擇 Yes,下面輸入框點下去會看到自己剛剛輸入的 S3 website endpoints
建立完之後就可以在瀏覽器上輸入網址,他就會導去你預設的網站囉!向我這邊就是拿我的 facebook 來做示範。
我透過這個方法幫我把 medium 的部落格網址重新導向,因為我想之後若是有獨立建立部落格的話網址會是一樣的,當有點粉絲的話至少他們認得的網址會是一樣的 😁。
不管是建立虛擬機(EC2),又或是像我們建立 Serverless 的服務(Lambda),總會挑選離自己家近的節點(東京),又或者可能因為價錢的關係選擇相對便宜的地點,但不管機器在哪,對於在台灣的我們來說都是一樣跨海啊!但好險台灣擁有 CloudFront 的節點,透過快取讓使用者可以更快的取得服務器上的資源,可能第一次都會比較慢,但只要讓 CloudFront 看過一次就會幫忙快取起來,下次使用者在用的時候就不會那麼慢囉!
首先我們先進去 CloudFront 的頁面,按下左欄的Distribution後並 Create的藍色按鈕
因為我們是做 Web API 相關的,這邊當然就是選 Web 囉!
到了這邊,AWS 很有心的在這些輸入框大多都有下拉式選單,這邊就選建立完的那個專案名稱
選完之後下面三個就照著這樣點選,Comment 會幫你自動填入
CNAMEs 的部分這邊我填入我在 serverless.yml裡 create_domain 所設定的 line.nijia.lin 字串,SSL 就選擇上一篇所建立的 Certificate
建立完成之後就會看到他 In progress,CloudFront 就開始撒點告訴其他節點有建立這個名為 line.nijia.lin,有看到他的時候記得幫他做個 Cache
等個大概 15 分鐘後他就部署完成啦~
點進來可以確認自己剛剛設定的資訊對不對,若有設定錯誤在按 Edit 來修正

現在每個服務都很流行 CDN,若有在寫前端的朋友常常就會用到 jQuery、Bootstrap、Vue 等等可以再標頭檔就引入的 CDN 路徑,CDN 不僅加速我們平常在抓伺服器資料的速度,也便利了我們有時候需要寫一些簡單的靜態頁面時可以透過抓取 CDN 路徑來快速取得需要的資源,但是像是 AWS 他們都是收取流量費,用得很爽時別忘了看一下自己的帳單是不是在哀嚎 🤣
繼上一篇 warm-up 機制之後來了解一下如何使用 AWS 的 CDN 服務 - cloudfont,CDN 顧名思義就是要讓使用者可以到最接近的主機上去拉到對應的資料,透過撒點的方式讓主機去挑選離自己近的服務據點,藉此加速服務。
像我之前部署在 ohio,想想光網路的時間 + Cold start 的時間就已經去了一大半,即便我們有熱開機,服務沒有 Timeout 我該慶幸了,既然如此就不得不認識一下 CDN 服務,而 AWS 的 CDN 服務就是 cloudfont 啦!
只是說原本的 domain 已經有 SSL 了,但是代理沒有這個東西,所以我們要 ban 出一個 Certificate,那在在建立 cloudfont 以前需要先到 Certificate Manager,接著會用圖片帶大家一步一步做

我已經有先在 Route53 註冊了一個 nijialin.com 的 domain,.com大概 10 美金左右,若是有在其他地方註冊域名的話要找一下相關文章把它導進 Route53 哦!





最後就等他完成嚕!將將
接著我們到 API Gateway 找到左邊的 Custom Domain Name,我們要來建立屬於這個 API 的 Domain 了
按下藍色的按鈕之後,輸入Domain name以及選擇剛剛註冊的 Certificate 後按下 Save
他就會開始初始化剛剛的設定,這邊大概需要等 15 分鐘左右
在此同時我們就去新增專案裡的套件 Domain-Manager
npm install serverless-domain-manager --save-dev
並且在plugin下加入套件
plugins:
- serverless-domain-manager
在custom底下加入
custom:
domainName:
default:
domainName: line.nijialin.com
certificateName: "*.nijialin.com"
createRoute53Record: true
endpointType: edge
等待前面初始化成功之後部署這個專案sls deploy之後就會看到剛剛註冊的域名啟動囉!

在建立 Certificate 那邊倒沒什麼問題,畢竟需要一個 SSL,只是到了建立 domain 這邊遇到了很怪的問題,一般來說使用 serverless 框架來跑的話只要照的文件裡說的 serverless create_domain就會幫忙註冊,而且可以依照自己開發環境去自動設定,在公司的專案中這樣使用是沒問題的,在是在寫這篇文章時卻只能使用以上的方法來替代使用,雖然結果是一樣的,但是用起來實在是很不符合邏輯,google 也沒找到類似的問題,或許這個問題還要多實測幾次才夠...😭
本篇會介紹 npm 套件庫裡的 Serverless-plugin-warmup,裡面介紹了很多參數可以使用,以下會簡介如何引入它。
透過npm來安裝套件
npm install --save-dev serverless-plugin-warmup
在serverless.yml的plugin加入套件
plugins:
- serverless-plugin-warmup
在custom加入warm up的設定參數
custom:
warmup:
enabled: true
cleanFolder: false
memorySize: 128
name: "LINE-warmup-pop"
events:
- schedule: "cron(0/5 0-12 ? * MON-FRI *)"
timeout: 10
.gitignore加入以下兩個_warmup/以及.requirements.zip,他在部署前建立一個_warp的資料夾並放一個 Lambda 來打已建立的 api 以及 Lambda 們,部署後會建立.requirements.zip,所以這邊就加入這兩行來防止被 git 推上去。
或許會有疑問不是
cleanFolder設定 true 就可以,這邊我自己實測時在部署會因為 Size 過大而無法上傳,把這參數設定掉他才會乖乖的上傳成功,但這部分有待驗證。
這邊我在lib/下新增一個decorator.py,加入以下的 code
from functools import wraps
from logging import Logger
def lambda_warm_up(func):
@wraps(func)
def wrapper(*args, **kwargs):
source = args[0].get('source')
if (source == 'serverless-plugin-warmup'):
Logger.info('WarmUp - Lambda is warm!')
return {}
return func(*args, **kwargs)
return wrapper
主要功能是透過 python decorator 的特性讓我們在 function 之前用個前綴符號就可以引用它,很像 po 文在 tag 別人一樣 🤣
warm-up 建立的 Lambda 會固定對其他的 Lambda 打serverless-plugin-warmup的字串,這邊就寫個判斷式判斷掉後直接 return
你以為這樣就結束了?還有 IAM role 的問題呢,詳細問題已不可考,想試看看的可以把 IAM 拔掉再部署一次,問題就會出現了,以下為設定檔:
iamRoleStatements:
- Effect: "Allow"
Action:
- "lambda:InvokeFunction"
Resource:
- Fn::Join:
- ":"
- - arn:aws:lambda
- Ref: AWS::Region
- Ref: AWS::AccountId
- function:*
如果你的目錄結構跟我一樣,或是跟到今天的實作,加入以下的 code,這邊主要是告訴你的 Lambda 要在每次的 warm-up 字串來的時候呼叫這個 decorator 來幫忙處理一下
from lib.decorator import lambda_warm_up
@lambda_warm_up
def xxx():
pass
我的 Lambda 都加了,那原本的 flask 建立的 api 我要加在哪?
看看這個網址,serverless-wsgi 在實作的時候已經有考慮到這件事了,所以 api 這邊就不用管他,他自己會去把它處理掉!

Warm-up 的機制就是透過建立一個 Lambda 來打一個 flag 到其他 Lambda 來他們別進入睡眠模式,但總是會有人考慮費用的問題,可以參考下圖,若是你建立了一個簡單的服務然後上線了,AWS 給你了一百萬個請求的量,我覺得一個月的請求可以到 100 萬應該是服務有點規模的情況,一個月能到這個量相信你也有一定的能力養它 🤣,爾後每一百萬個請求才六塊台幣,可能比 GCP 還貴,但其實以我來說這樣已經很夠用,而且還扛得住

以下就為大家帶來簡單的實作以及如何去使用它。
在views/下新增一個liff_test.html並加入以下內容
<html>
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script>
<script>
function initializeApp(data) {
$("#userid").val(data.context.userId);
}
$(function() {
liff.init(function(data) {
initializeApp(data);
});
$("#ButtonGetProfile").click(function() {
liff.getProfile().then(profile => {
$("#UserInfo").val(profile.displayName);
alert("done");
});
});
$("#ButtonSendMsg").click(function() {
liff
.sendMessages([
{
type: "text",
text: $("#msg").val()
}
])
.then(() => {
alert("done");
liff.closeWindow(); // 關掉頁面
});
});
});
</script>
</head>
<body>
<button class="liff_btn">Get user profile</button>
<label>user id:</label>
<input class="form-control" type="text" id="userid" /> <br />
<button class="btn btn-primary" id="ButtonGetProfile">Get Profile</button>
<input class="form-control" type="text" id="UserInfo" /><br />
<label>要傳送的訊息:</label>
<input class="form-control" type="text" id="msg" value="測試" /><br />
<button class="btn btn-primary" id="ButtonSendMsg">要傳送的訊息</button>
</body>
</html>
以上參考來自董老師的部落格
詳細的 LIFF API 文件可以參考
接下來按著之前做過的一樣把這個檔案送到S3裡,接著它的網址複製起來

接著來去之前建立機器人的開發者頁面裡按下最右邊的LIFF
按下去之後呢,再來按下add
跑出這個頁面後,Name好辨認為主、Size選擇滿版(Full)、Endpoint URL就把剛剛複製起來的 S3 網址放進去後按Confirm
大概內容會長得像這樣
建立成功後往下滑會看到 LINE 給你一個屬於他們的網址,接著可以把它貼到你的機器人上(或者任何聊天室)
點下去之後就會看到來自 LIFF 的 Webview 囉!
接下來就可以開始玩裡面的功能了 🤣
LIFF 整合的功能讓開發者在 Bot 裡不好實現的功能透過 Webview 的方式讓使用者可以到頁面上做邏輯處理,透過兩種的結合讓整體的應用就有更多的變化,這邊若要給使用者個話就把 line://xxxoxx 製作成 flex message 或者乾脆傳給使用者,引導他點選,如此就能讓使用者到自己設計的頁面上了。
這邊額外補充一點,有看到程式碼裡面可以拉到Query string嗎?事實上可以透過Query string讓使用者帶些參數來做更多的判斷,像是卡米哥做的Kamiliff就是讓使用者在點選後判斷這個 LIFF 的 Size,在導去對應的路由去做處理,這樣子使用者在建立 LIFF 的時候就不用一個頁面弄一個,否則這樣在 dev 轉 prod 的時候應該會弄到很生氣 🤣。
LIFF (LINE Front-end Framework)顧名思義就是 LINE 開發在 LINE 這個通訊軟體裡面的 Web view(好饒舌),他是一個很精簡的內建瀏覽器,主要是能夠讓開發者可以使用 HTML & Javascript 可以去處理一些在 bot 不好實現的功能,也讓開發者可以透過使用 JS SDK 去拉使用者的資訊,藉此可以做出更多種的應用。
網址的樣式都像是這樣line://app/ZZZZZ-AAAAAAA,透過 deep link 的方法讓 LINE 本身可以認得這個藉此去導向設定對應的網站,畢竟做平台的公司內部要互相溝通也是,就透過這種方法去讓不同部門的工程師可以互相使用好像也是不錯(?)
它也可以搭配在之前做的 LINE 服務上,Notify、Message api 上都可以串上去搭配不同種應用,而這個簡易的 Web view 有下面三種的顯示格式可以使用,開發者可以搭配不同應用場景去做搭配。

一開始都只能使用 curl 來增加項目,使用方法可以參考這篇,現在可以直接在 LINE Developer 頁面線上新增,像下圖片在這個頁面就可以新增了
curl -X POST \
-H "Authorization: Bearer YOUR_CHANNEL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"view": {
"type": "LIFF_SIZE",
"url": "URL_OF_YOUR_APPLICATION"
}
}' \
https://api.line.me/liff/v1/apps

這邊推薦一個 kamiliff,卡米哥就用一個路由帶著 query string 來判斷,再轉打去其他 controller,如此一來就不需要為了每個頁面去獨立建立 LIFF,如此一來測試跟上線都很好管理,推薦大家去參考卡米哥的 kamiliff 的實作方法~
這個功能最大的好處就是可以在 LINE 裡面就可以直接用,若是把使用者導出頁面基本上轉換率以及使用者體驗一定會變很差,所以若是有在開發 LINE Bot 的話很推薦使用這個。
以前我曾嘗試過使用 LIFF 使用 websocket 去跟 MQTT 溝通( IoT 裝置常常使用的通訊格式),只是他的 websocket 似乎有開,但是總是沒有收到數值,這部分實作也是年初時使用,現在這時候就不知道能不能用了 🤣,若不行就期待哪天他能提供出這個服務了~
在第 Day 13 Python SDK 試玩的時候使用過 get_profile,當時使用時就是在每次訊息來的時候才去 SQL 儲存使用者資訊,若只是單純想要使用者資訊的話這樣用好像有點多餘 😓,以下就帶各位簡單的實作。
我們要將上篇的前端頁面(line_login_auth.html)改造一下
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>LINE Login</title>
</head>
<body></body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<script>
$.ajax({
url: "https://YOUR_SLS_DOMAIN.us-east-1.amazonaws.com/dev/line/auth",
method: "POST",
success: function(data) {
window.location.replace(data.result);
}
});
</script>
</html>
這邊我將 button 的 onclick 事件以及 button 本身移除掉,目的是要讓使用者一到這個頁面之後就直接透過 AJAX call api
到了這裡應該有感應到什麼了吧!接著就是把這個 html 檔的 S3 路徑網址使用 QR code 包起來,讓他看起來像是 LINE bot 的 QR code
到了這邊基本上已經快完成了,參考 LINE 的好友說明或下圖

接著到機器人的開發者頁面,並且往下滑會看到自己機器人的 QR code 以及 Bot 的 ID,把他複製下來並製作機器人的加好友網址

像是這樣子
https://line.me/R/@1234
接著到 controller/line_login_controller.py的def get(self)最後一行,原本我們這邊做的是回傳 JSON
if dt:
return {'result': dt}, 200
改用
from flask import redirect
if dt:
return redirect("https://line.me/R/ti/p//@127ojvgz", code=302)
如此一來在登入驗證完之後就會把使用者導向到加好友的頁面,在手機上若已經加過好友則會直接導到機器人對話窗上,如此一來就可以讓使用者在加入好友的同時也綁定完帳號,在未來使用的時候 api 這邊也比較好控管使用者的權限問題。
像是我自己做的健身記錄機器人,就是以這個方法下去實作,有聽過一些朋友跟我反應這樣的方法多此一舉,但我認為若是有網頁的話也是套用 LINE SSO 來實作,就可以讓兩個入口去統一,在狀態流上就能比較好控制了。

現在有規模的平台提供商基本上都會提供 Single Sign On (SSO)的登入機制,藉由他們的平台讓一般小網站可以讓使用者免密碼登入,雖然有很多種 Oauth2 的套件,不過我還是喜歡自己打 🤣,下面就開始嚕!
在views/建立一個line_login_auth.html,填入以下內容,主要是實作一個按鈕並透過 javascript 來互叫 api 並導向到 LINE 頁面
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>LINE Login</title>
</head>
<body>
<button class="auth" onclick="auth()">LINE LOGIN</button>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<script>
function auth() {
$.ajax({
url: 'https://YOUR_SERVERLESS_DOMAIN.amazonaws.com/dev/line/auth',
method: 'POST',
success: function(data) {
window.location.replace(data.result);
},
});
}
</script>
</html>
接著我們在.env加入 LINE Login 的環境變數並輸入對應的參數
LINE_LOGIN_CLIENT_ID=
LINE_LOGIN_SECRET=
LINE_LOGIN_URI=https://YOUR_SERVERLESS_DOMAIN.amazonaws.com/dev/line/auth
最後就是我們最重要的部分了,實作 GET a& POST 的 api,這邊我先從 POST 講起,這邊主要是把環境變數組在網址中回傳給前端,這邊需要注意的是state他是在回傳回來的 JWT 需要解碼時所使用的,這邊我簡單打個字串,然而實際上會隨機產個變數,確保解碼的安全。
GET 則是負責處理從 LINE 回來的參數們,這邊我只用回傳回來的state把id_token解開,拿到裡面的使用者的參數,後續可以參照應用把資訊存下來,放入 session 並告知前端已登入。
from flask import request
from flask_restful import Resource
import requests
import webbrowser
import os
import jwt
import json
class LineLoginController(Resource):
def get(self):
r = requests.post(
"https://api.line.me/oauth2/v2.1/token",
data={
"grant_type": "authorization_code",
"code": request.args.get('CODE'),
"redirect_uri": os.environ.get('LINE_LOGIN_URI'),
"client_id": os.environ.get('LINE_LOGIN_CLIENT_ID'),
"client_secret": os.environ.get('LINE_LOGIN_SECRET'),
}, headers={"Content-Type": "application/x-www-form-urlencoded"})
payload = json.loads(r.text)
print(payload)
token = payload.get("id_token")
if token is None:
return {'result': payload['error_description']}, 400
state = request.args.get('state')
token = token.encode()
dt = jwt.decode(token, state, None, algorithms=['HS256'])
if dt:
return {'result': dt}, 200
def post(self):
r_uri = os.environ.get("LINE_LOGIN_URI")
client = os.environ.get("LINE_LOGIN_CLIENT_ID")
state = "nostate" # it will be random value
uri = f"https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id={client}&redirect_uri={r_uri}&scope=profile%20openid%20email&state={state}"
return {'result': uri}
JWT 解開的參數如下,sub就是 LINE 的唯一值 ID,一般拿到這個就可以知道是來自 LINE 的使用者了。
{
"result": {
"iss": "https://access.line.me",
"sub": "Ud6ab866a67xxxxxxxxb12b0baffb8ac",
"aud": "1622939248",
"exp": 1569925548,
"iat": 1569921948,
"amr": ["linesso"],
"name": "NiJia Lin",
"picture": "https://profile.line-scdn.net/0hKvTocMuLFFlpFj9HpKdrDlVTGjxxxxxxxxx1YHACMOPRtEHmkWJVIKUyUJNkQWGT0X",
"email": "loxxxxx09@gmail.com"
}
}
最後別忘了要在api.py加入路由才會被導出去ㄛ
from controller.line_login_controller import LineLoginController
api.add_resource(LineLoginController, '/line/auth')
在實作的時候不知道為什麼一直不能讓 python 去幫我把用戶導去 LINE 認證的頁面,我記得之前寫 Ruby 可以啊(?),最後只好讓前端去幫我把使用者導出去,這邊就帶大家做到這嚕!
上篇介紹了好多了,這邊就不廢話了,直接就帶大家申請一個 LINE Login 的服務。
首先到自己的開發者頁面按+,並選擇最左邊的選項
接著輸入App name(服務名稱)、App description(服務簡介),因為我們是實作 API ,所以選擇Use WEB的選項,接著再輸入email
把該填的填完之後按下送出就會看到有一個新的服務嚕!
點進去後往下滑,可以看到有個 link to this channel 的選項,這邊選擇一開始建立的那隻機器人,綁定之後就變它的形狀啦(?)
接著再往下會看到 Email 的狀態是 Unapplied,然後按下右邊的Submit
會看到這樣子的一個框框,把兩個框框打勾並選擇一張圖片上傳,這部分就算是申請帳號下面的同意服務條款一樣,圖片就上傳自己希望當使用者到你頁面的時候想看到的圖片 🤣,送出之後就會看到狀態改成 Applied 了

很多時候實作對我們來說已經不是什麼問題了,重點是在找不到服務要怎麼去申請 😓,這篇也算是記錄一下申請的步驟細節,接下來會實作個簡單的認證頁面以及 API,打完收工嚕!
LINE 中文版也上線啦 URL
去某些網站登入你可能會有像下面這樣
或者是你有玩過 LINE Rangers (怎麼感覺我好老),就會看到他們登入畫面有個登入鈕,按下去按確認後就登入,使用者不用輸入帳號密碼。
這個機制被稱為 SSO (single sign-on),它是透過 OAuth2 的機制來做認證,因為不儲存密碼除了可以降低儲存第三方網站的風險,以及使用者不需要記憶各種不同的密碼所帶來困擾,像是 Facebook、Google、LINE...都有相關的機制可以串連 SSO 的服務。
以下會介紹 LINE Login 的流程。
這裡我使用 LINE Login

redirect_uri 還給你一個 authorization code 以及 state 在 Query string 上。authorization code 打到https://api.line.me/oauth2/v2.1/tokenaccess token 嚕!可以拿透過 social API 去拿到相關資訊
下一篇再帶著大家申請一個 LINE Login 的服務並建立一個簡單的註冊頁面~
過往寫了這麼多的 api,總是會有特定的功能會需要輪詢資料庫或是監聽某些事件,以前還要到弄個虛擬機寫腳本用 crontab,這時候 Lambda 的用處就來啦!不只可以處理 api 的事情,還可以設定排程做事,太舒服啦,以下就來介紹一下~
首先在 consumer 資料夾下line_bot_alert.py,再寫一段需要定時跑的程式,這邊我去抓中央氣象局的資料,大概簡單判斷一下區域以及算出他的範圍:
import json
import requests
def alert_handler(event, context):
air = requests.get(
'http://opendata.epa.gov.tw/webapi/Data/REWIQA/?$orderby=SiteName&$skip=0&$top=1000&format=json')
air_body = json.loads(air.text)
air_status = ["良好", "普通", "對敏感族群不健康", "對所有族群不健康", "非常不健康", "危害", "資料有誤"]
total, count = 0, 0
for fetch in air_body:
if (fetch['County'] == "臺中市" and (fetch['SiteName'] == "西屯" or fetch['SiteName'] == "沙鹿")):
count += 1
if fetch['AQI'] != 0:
total = total + int(fetch['AQI'])
switch = 0
pm = int(total / count)
print(pm)
if pm in range(0, 50):
switch = 0
elif pm in range(51, 100):
switch = 1
elif pm in range(101, 150):
switch = 2
elif pm in range(151, 200):
switch = 3
elif pm in range(201, 250):
switch = 4
elif pm in range(251, 300):
switch = 5
else:
switch = 6
payload = f"\n空氣品質: {air_status[switch]}"
r = requests.post('https://1uv2o723o4.execute-api.us-east-1.amazonaws.com/dev/notify/sqs',
data={'message': payload})
print(r)
在serverless.yml的 function 下面加入 Lambda,handler 就是 資料夾/檔案.類別,設定事件(event)排程一分鐘一次
function:
line_bot_alert:
handler: consumer/line_bot_alert_handler.alert_handler
events:
- schedule: rate(1 minute)
AWS Lambda 有兩種設定排程的方法,
rate以及cron,若是比較固定時建議用rate,比較複雜的排程就可以讓cron來,下面兩張圖分別是來自 AWS >

下sls deploy部署之後就會看到剛剛做line_bot_alert的function,他每分鐘都在跑,若玩完之後不想用了可以下sls remove把上面有的東西刪掉嚕!

其實還有很多應用可以實作排程,像是有朋友就用 GCP 的來定時發文 🤣,而且可以把腳本寫得像是 api 一樣,讓整個靈活性都增加了不少,下一篇就帶大家來做 LINE Login 嚕!
用 Serverless 用得這麼爽,也是時候來看一下他的資源在哪,距離我上次自己手動上傳 python 檔已經是 N 個月之前的事了(遠望),當時也沒用過什麼Api Gateway相關的服務,以下就帶大家來介紹一下嚕!
CloudFormation 是一個 AWS 所提供的 JSON-base 服務,主要是用來讓使用者能夠快速建立 AWS 的服務。
這邊也不用擔心設定的問題,Serverless 再 deploy 的過程中會幫忙 CloudFormation 的 json 檔
透過 CloudFormation 建立 JSON-based 的 template 你可以快速建立所需要的資源堆疊(stack)。
按下Template後如下圖所示,他會顯示你已經有建立的服務 Map,整個線牽起來看起來就很爽 XD

Serverless 建立過程中也會透過 CloudFormation 幫忙建立 Api Gateway,因為我們是用 WSGI,所以只會看到兩個ANY的路由。
點進去後就可以看到 Client 是怎麼進出的,最右邊是打到 AWS Lambda 上,可以點選上面的字進入 Lambda 所在地。

點選進來之後可以看到 Lambda 接了什麼服務,以我到目前的程式就會有API Gateway、SQS以及預設的CloudWatch(都是透過這個來看 log)
接著往下會看到之前設定的環境變數以及STAGE,這些都是托Serverless的福免去這些麻煩的設定

Serverless 在過程中會在 S3 幫忙建立一個 Bucket,並在後面加個 hash key 以免 bucket 重複。

在開始測試後 AWS 會幫忙在這個地方輸出 Log,

Serverless deploy --verbose 就可以看到所有部署中的基本資訊了首先因為我們有使用.env,所以在一開始時會先設定環境變數,接著就是把相依套件的東西都放進去,再去啟動 CloudFormation

接著會建立一個 S3 的 Bucket,將程式透過 CloudFormation 處理成 zip的檔案格式並上傳到 S3 上面

接著就會部署到 Lambda 以及 Api Gateway 上,並做對應 CloudFormation JSON 檔的設定

在最後部署完之後就可以看到所有的基本資訊,物件名稱、部署階段、區域、網域以及 Function 們(因為 Lambda 是 Function-base 的服務)等等的基本資訊都會在此

以目前的架構基本上到這麼基本資訊都顯示出來了,若是有使用像是Route 53、CloudFront等等的服務它就會顯示在這邊了。雖然說 CloudFormation 在設定上已經很方便了,但不得不說 Serverless 真的幫忙做了很多事,讓開發者又更能專注在打 Code 上了~
看著很多人的機器人都有上下頁的功能就心癢癢,一直沒有實際去操作,雖然看起來好像不難,但身為一個肥宅就是只有懶,到了現在才把他實作出來(羞),以下就帶大家來做嚕!
首先我做了兩個超級陽春的圖片,來展示一下上下一頁的樣子(超弱)
(1)

(2)

我把上一篇的 richmenu_api_controller.py 的 create 部分改編了一下,
增加了讀 state Query string 的一個參數,用up&down來簡單判斷上傳的內容要傳什麼
if usage == 'create':
rich_menu_to_create = None
if request.args.get('state') == 'up':
rich_menu_to_create = RichMenu(
size=RichMenuSize(width=2500, height=843),
selected=False,
name="Nice richmenu", # display name
chat_bar_text="我是第一頁",
areas=[RichMenuArea( # 這邊是陣列的格式,可以動態設定自己要的區域想要有什麼功能
bounds=RichMenuBounds(
x=0, y=0, width=2500, height=843),
action=MessageAction(
label='message',
text='下一頁'
))]
)
elif request.args.get('state') == 'down':
rich_menu_to_create = RichMenu(
size=RichMenuSize(width=2500, height=843),
selected=False,
name="Nice richmenu", # display name
chat_bar_text="我是第二頁",
areas=[RichMenuArea(
bounds=RichMenuBounds(
x=0, y=0, width=2500, height=843),
action=MessageAction(
label='message',
text='上一頁'
))]
)
else:
rich_menu_to_create = RichMenu(
size=RichMenuSize(width=2500, height=843),
selected=False,
name="Nice richmenu", # display name
chat_bar_text="我是測試使用",
areas=[RichMenuArea( # 這邊是陣列的格式,可以動態設定自己要的區域想要有什麼功能
bounds=RichMenuBounds(
x=0, y=0, width=2500, height=843),
action=URIAction(label='Go to line.me', uri='https://line.me'))]
)
rich_menu_id = self.line_bot_api.create_rich_menu(
rich_menu=rich_menu_to_create)
print(rich_menu_id)
return {'id': rich_menu_id}, 200
接著回到 message_api_controller.py 把前幾篇的 reply SDK code 先註解掉,並加入以下的 code
message = event['events'][0]['message']['text']
if message == "上一頁" or message == "下一頁":
try:
rich_menu_id = self.line_bot_api.get_rich_menu_id_of_user(id)
except:
# link default rich menu
self.line_bot_api.link_rich_menu_to_user(id, "richmenu-269cc28b8e8497d76c2df062b274a2ce")
if rich_menu_id == "richmenu-269cc28b8e8497d76c2df062b274a2ce":
self.line_bot_api.link_rich_menu_to_user(id, "richmenu-e31be74ad7e577b4752ab70c9c2a3fba")
else:
self.line_bot_api.link_rich_menu_to_user(id, "richmenu-269cc28b8e8497d76c2df062b274a2ce")
else:
self.line_bot_api.reply_message(token, TextSendMessage(text=message))
這邊簡單判斷讀取使用者回應來進入控制的程式碼,透過get_rich_menu_id_of_user來抓取當前使用者的 rich menu,但因為他是會返回 Error response,所以這邊就使用try except來包
except 部分就是透過link_rich_menu_to_user來設定當前使用者為第一張圖片的 richmenu id,下面的判斷式就是讓他互換成對應的 rich menu id 來換頁。
Richmenu 那邊事實上是要用
postback,因為要 demo 的關係我就弄成一般的文字格式,因為這個上下頁字串的理論上是不該讓使用者看到的,只要用圖片來引導使用者就好。

透過這次練習大概把 LINE SDK 玩得差不多了(message api & richmenu api),寫到了今天真的是苦讀 LINE 的文件啊,都要以防萬一我這狗眼沒有看錯而寫出錯誤的程式碼 XD,到這打完收工,繼續期待下一篇~
之前我在使用時都是直接在 Postman 上直接打 API,本來是打算自己刻 接 Richmenu API 的,但剛剛看到Python SDK 很佛心的也提供 Richmenu 相關的 SDK,如此一來就不用自己包一包了。
注意 Richmenu 圖片的方向是 由上到下、由左到右 哦!算位置記得別算錯XD
首先先使用 create_rich_menu(self, rich_menu, timeout=None)告訴 LINE 說你的 Richmenu 的基本設定是什麼,格式區域什麼個別要做什麼對應的事情,
Creates a rich menu. You must upload a rich menu image and link the rich menu to a user for the rich menu to be displayed. You can create up to 10 rich menus for one bot.
https://developers.line.biz/en/reference/messaging-api/#create-rich-menu
if usage == 'create':
rich_menu_to_create = RichMenu(
size=RichMenuSize(width=2500, height=843),
selected=False,
name="Nice richmenu", # display name
chat_bar_text="我是測試使用",
areas=[RichMenuArea( # 這邊是陣列的格式,可以動態設定自己要的區域想要有什麼功能
bounds=RichMenuBounds(x=0, y=0, width=2500, height=843),
action=URIAction(label='Go to line.me', uri='https://line.me'))]
)
rich_menu_id = self.line_bot_api.create_rich_menu(
rich_menu=rich_menu_to_create)
print(rich_menu_id)
return {'id': rich_menu_id}, 200
上面設定完圖片裡每個座標區間要幹嘛了,接著就可以自己弄圖片或是下載官網提供的圖片
這邊教大家一個小技巧,可以點選想使用的照片,在預覽程式裡的選擇工具->調整大小
接著看到這畫面,把依比例縮放的勾勾取消,然後把長寬調到對應的大小後就完成圖片大小的設定嚕!

https://developers.line.biz/en/reference/messaging-api/#upload-rich-menu-image

elif usage == 'upload':
file = request.files['the_file'].read()
rich_menu_id = request.form['richmenu_id']
content_type = "image/png"
try:
self.line_bot_api.set_rich_menu_image(
rich_menu_id, content_type, file)
except Exception as e:
print("===Upload Exception===")
raise BadRequest(e)
return {'result': 'upload ok'}, 200
設定完了之後要幹嘛?就是要把他設定default啦!
不囉唆, show you the code.
Sets the default rich menu. https://developers.line.biz/en/reference/messaging-api/#set-default-rich-menu
elif usage == 'set':
rich_menu_id = request.form['richmenu_id']
try:
self.line_bot_api.set_default_rich_menu(rich_menu_id)
except:
raise BadRequest("Maybe your richmenu id error.")
return {'result': 'set default ok!'}, 200
糟糕,我好像瘋狂建立卻不知道有幾組?用這個就對了!
Gets the ID of the default rich menu set with the Messaging API. https://developers.line.biz/en/reference/messaging-api/#get-default-rich-menu-id
elif usage == 'get':
rich_menu_list = self.line_bot_api.get_rich_menu_list()
total = []
for rich_menu in rich_menu_list:
total.append(rich_menu.rich_menu_id)
return {'result': total}, 200
不行,我弄的座標什麼全錯怎麼辦?有時候上傳的圖片可能不是自己想的那樣?,透過這個方法來刪掉它
Deletes a rich menu. https://developers.line.biz/en/reference/messaging-api/#delete-rich-menu
elif usage == 'delete':
rich_menu_id = request.form['richmenu_id']
try:
self.line_bot_api.delete_rich_menu(rich_menu_id)
except:
raise BadRequest("Maybe your richmenu id error.")
return {'result': f'{rich_menu_id} is delete!'}, 200
我都把 raise 打成 rails (大誤),LINE 幫忙做的 SDK 真的是好用,透過這次練習我也多學了好多不一樣的用法,順便把自己的腦袋也整理一下 😃,程式碼同步在這邊哦 URL。
Rich menu 是一個 LINE 提供給 chatbot 的圖文選單,可以再上面設定很多各式各樣的功能,下圖為一個範例


有很多的文章都有介紹過類似的功能,這邊簡介一下限制以及下篇要做的步驟~

以前從花落落的文件中找到一個方法,像是使用 push message
我都會笨笨的 call API 的方法去執行功能,殊不知 SDK 早就包好了,然後邊做邊罵自己為什麼要解這些 JSON (拍桌)
到了現在比較會看文件,才知道原來以前是自己雷自己 (捶心肝)...接下來就試玩幾個我覺得常會用到的 method 好了不廢話了,來簡單介紹一下一些常用的方法 😎
回傳使用者的個人狀態,可以透過每次的 reply 去拿到使用者的資訊,一般使用的話都話把它存下來,等之後有介接其他服務時方便使用 userId 去判斷使用者。
profile = line_bot_api.get_profile(event['events'][0]['source']['userId'])
try:
with Database() as db, db.connect() as conn, conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
cur.execute(
f"INSERT INTO users(id, name, picture) VALUES ('{id}', '{name}', '{picture}')")
except:
pass # 這邊簡單處理相同 id 的問題(id我有設 Private key 哦!)
state = f"Hello 👉 `{profile.display_name}` 👈"
line_bot_api.reply_message(token, [
TextSendMessage(text=state),
ImageSendMessage(original_content_url=profile.picture_url, preview_image_url=profile.picture_url)
])
這邊搭配 ImageSendMessage 來幫忙發送圖片,他主要是透過 original_content_url 以及 preview_image_url 這兩個參數來幫忙轉成圖片
既然都取到使用者的 id 了,下一步就是推訊息給使用者啦!
建立一個 GET 的路由在 /webhook 上,收到 query string 的 msg 的值並找到所有使用者的 id 並逐一推送訊息。
line_bot_api = LineBotApi(os.getenv('LINE_CHANNEL_TOKEN'))
handler = WebhookHandler(os.getenv('LINE_CHANNEL_SECRET_KEY'))
def get(self):
msg = request.args.get('msg')
with Database() as db, db.connect() as conn, conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
cur.execute("SELECT * FROM users")
fetch = cur.fetchall()
for user in fetch:
self.line_bot_api.push_message(
user['id'], TextSendMessage(text=msg))
return {'message': msg}, 200


這邊直接拿 SDK 的範例來用,若是有在做商家功能的機器人可以使用這個方式讓使用者接收到商家訊息~
self.line_bot_api.reply_message(token, LocationSendMessage(
title='my location',
address='Tokyo',
latitude=35.65910807942215,
longitude=139.70372892916203
))
這個方法可以搭配使用,讓整個回應看起來跟真實吧!
不知道清單能用什麼可以參考,
self.line_bot_api.reply_message(token, StickerSendMessage(package_id='1',sticker_id='1'))
buttons_template_message = TemplateSendMessage(
alt_text='Buttons template',
template=ButtonsTemplate(
thumbnail_image_url=f'{picture}.jpg',
title='Menu',
text='Please select',
actions=[
PostbackAction(
label='postback',
display_text='postback text',
data='action=buy&itemid=1'
),
MessageAction(
label='message',
text='message text'
),
URIAction(
label='uri',
uri='http://example.com/'
)
]
)
)
self.line_bot_api.reply_message(token, buttons_template_message)

簡單玩了幾個 API,大概就這幾個比較常搭配著使用,用過之後才知道其實有好多 API 我都還不是很清楚用法 🤣
而 LINE 最近更新了他們文件的功能,多了一個Try的按鈕提供使用者可以線上直接測 API,只是像是 push messages 這個就剛好沒有 😓,但至少有很多 API 不用通靈去測試,直接拿著 Token 在網頁上測試嚕!
Doc Sample

Code is here
新增兩個 LINE Channel key 至 .env
LINE_CHANNEL_TOKEN=
LINE_CHANNEL_SECRET_KEY=
接著加入 LINE 的 python SDK 至 requirements.txt
line-bot-sdk==1.12.1
透過 pip 安裝一下
pip install -r requirements.txt --user
在 controller/ 底下新增一個 message_api_controller.py 並輸入以下程式
from flask import Flask, request, abort
from flask_restful import Resource
import json
import os
from linebot import (
LineBotApi, WebhookHandler
)
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
MessageEvent, TextMessage, TextSendMessage,
)
class LineMessageApiWebhookController(Resource):
def post(self):
line_bot_api = LineBotApi(os.getenv('LINE_CHANNEL_TOKEN'))
handler = WebhookHandler(os.getenv('LINE_CHANNEL_SECRET_KEY'))
# get X-Line-Signature header value
signature = request.headers['X-Line-Signature']
body = request.get_data(as_text=True)
event = json.loads(body)
print(event)
try:
handler.handle(body, signature)
except InvalidSignatureError:
print(
"Invalid signature. Please check your channel access token/channel secret.")
abort(400)
token = event['events'][0]['replyToken']
if token == "00000000000000000000000000000000":
pass
else:
line_bot_api.reply_message(token, TextSendMessage(
text=event['events'][0]['message']['text']))
return 'OK'
用任何語言都會看到回傳會有 200, 'OK'...回傳的成功訊息,在內部系統的部分在處理時要考慮到回傳時間的問題,一般來說如果回傳的時間大於 1 秒的話就有可能失效,或許有些朋友有測試過大於 1 秒也可以,但最好還是於時間內回覆哦!
以下圖片來自 https://engineering.linecorp.com/zh-hant/blog/line-device-10/

接著在 api.py 加入下面兩段 code,新增一個webhook的路由
from controller.message_api_controller import LineMessageApiWebhookController
api.add_resource(LineMessageApiWebhookController, '/webhook')
這邊可以使用sls wsgi serve + ngrok去搭配做測試
接著就部署啦
sls deploy
現在在接這種第三方的 API 時最好都使用
https,而使用 Serverless 的好處就是 AWS 在部署完之後會送你一個含有 SSL 的網址~
把網址貼到 webhook URL 上並在尾端加上 /webhook

測試結果如下

這次整合了以前寫的 Repo,加入了.env讓我在抓下來使用時也更方便,也整合成 Restful 格式讓之後有需要的人在看 code 時可以更容易理解了~
專案也會持續更新,更多詳情可以 follow 我的專案 aws-python-line-api。
今天會帶大家如何一步一步的建立 Message api 以及需要注意設定的地方。
Add new provider



Message API並按下Create Channel






停用,並將 Webhook 打開,讓之後的串接可以透過 Webhook 打到 Serverless 上,若是前兩個回應的選項沒有關的話即使 Webhook 有啟動也會因為優先權問題被擋下來哦!

LINE 在 2016 年 9 月底發表了 Messaging API 功能,用戶可以透過 Messaging API 將自家的服務內容串聯到 LINE@ 上。
接著在 2019/04/18 之後 LINE@ 2.0 正式上線,在 5/22 後有升級的選項並會陸續更新,升級完的官方帳號也可以申請專屬 ID、進行帳號認證。
我認為比前一版本的最大的好處是在好友無上限的這部分,以前只能有 50 位好友,若一般開發者的機器人好不容易上線了並開放測試時被限制住,除了再開一隻機器人或是其他方法就超麻煩,現在好友無上限了,就不會有上述的問題了。
若您以前有開發過的機器人到現在還沒升級的話,可以參考 -> URL
這邊需要注意 1 on 1 聊天與 Messaging API 是無法同時使用的,但可以在後台切換看要使用哪種模式與顧客互動。對話記錄也會保存在各自的模式下,若要查看完整的對話記錄的話會建議以固定模式與顧客進行互動哦!


Message API 支援以下格式
詳細內容可以參考 LINE doc

Message API 回傳格式都是一個陣列的格式,一開始在接的時候都沒注意到一直瘋狂出錯,事實上這樣陣列對 Server 有個好處就是當一次需要送較多訊息時格式會比較統一,只是對於有潔癖的開發者來說每次都要多打[0]會覺得有點髒 🤣
{
'events': [{
'replyToken': '00000000000000000000000000000000',
'type': 'message',
'timestamp': 1568983962754,
'source': {
'type': 'user',
'userId': 'Udeadbeefdaaaaefdeadbeefdeadbeef'
},
'message': {
'id': '100001',
'type': 'text',
'text': 'Hello, world'
}
}, {
'replyToken': 'ffffffffffffffffffffffffffffffff',
'type': 'message',
'timestamp': 1568983962754,
'source': {
'type': 'user',
'userId': 'Udeadbeeaaaaefdeadbeefdeadbeef'
},
'message': {
'id': '100002',
'type': 'sticker',
'packageId': '1',
'stickerId': '1'
}
}]
}
以前剛開始接 LINE Message API 的時候都覺得他的文件不太人性,更新到現在文件內容也越來完整,讓開發者在開發的過程中不用再為了找不到文件而放棄了(我以前就是 🤣),下一篇將會帶來如何申請一隻機器人到如何建立一個 webhook API 到 AWS 上
首先先建立一個consumer/資料夾,新增__init__.py以及notify_handler.py
在notify_handler.py輸入以下程式碼
import json
import requests
def line_notify_handler(event, context):
print(event)
body = json.loads(event['Records'][0]['body'])
print(body)
headers = {
'Authorization': f"Bearer {body['token']}",
'Content-Type': 'application/x-www-form-urlencoded'
}
r = requests.post('https://notify-api.line.me/api/notify',
headers=headers,
data={'message': body['message']})
print(r)
# AWS record sample
# {'Records': [{'messageId': 'fddc42ba-a122-4581-965e-d0144ac8a5ad', 'receiptHandle': 'AQEBjO32gY5pXOfOrmDR0hD4k1av9KyjbHFpc+rIBPV2Brif7Lo+jqnGevSjfFwlICyGf+BhWwKaxFw8XdB3QTzRbw0vnLURjnQeDSBrJHa/S57SRs9TOLRBq38maycAVg69iZbetg9VhLMBCcLtOtPHTzKkmo+/Sosm51WA5CzXK7A0rteikx6nxS1CUIpq6MAujodupP0Hgr5RjK5nH/nmxA4Db0leWEmLokalZbtlx4W14tp7PZxPOrQOLDaGrH//p4h32tY8IN3MkCqi+gyNT7kCU4KwCGOIrybb07ZWyKBTKw+KOMNr/Ykj4z2N1qxIvTM55UY9d8V29YsH32OjrZTei5P7Nke/51E2tWkmkqoFAlqzxDjQPvpP+Pvvr8aazeeZ6opkr59UefAiiyM71Q==', 'body': 'hi', 'attributes': {'ApproximateReceiveCount': '9', 'SentTimestamp': '1566621263072', 'SenderId': '901588721449', 'ApproximateFirstReceiveTimestamp': '1566621263072'}, 'messageAttributes': {}, 'md5OfBody': '49f68a5c8493ec2c0bf489821c21fc3b', 'eventSource': 'aws:sqs', 'eventSourceARN': 'arn:aws:sqs:us-east-1:901588721449:LINE_notify_consumer', 'awsRegion': 'us-east-1'}]}
接著在requirements.txt加入boto3,他是一個使用 python 介接 AWS 的套件
boto3==1.9.189
加入SQS_URL以及SQS_ARN到.env裡面
SQS_URL=sqs url
SQS_ARN=your sqs arn
add controller/notify_sqs_controller.py
from flask_restful import Resource, reqparse
import json
from lib.db import Database
import psycopg2.extras
import os
import boto3
def send_message(url, attr, body, delay=0):
cli.send_message(
QueueUrl=url,
DelaySeconds=0,
MessageAttributes=attr,
MessageBody=body,
)
class SendNotifyBySQSController(Resource):
cli = boto3.client("sqs", region_name=os.environ("region"))
def post(self):
parser = reqparse.RequestParser()
parser.add_argument(
'message', required=True, help='message can not be blank!')
args = parser.parse_args()
msg = args['message']
with Database() as db, db.connect() as conn:
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
cur.execute(
f"SELECT token FROM notify")
fetch = cur.fetchall()
for f in fetch:
body = {
'token': f"Bearer {f['token']}",
'message': f"Hello everyone, {msg}"
}
cli.send_message(
QueueUrl=os.environ("SQS_URL"),
DelaySeconds=0,
MessageAttributes={},
MessageBody=json.dumps(body),
)
return {'result': 'ok'}, 200
程式寫完了就是要加一條路由/notify/sqs
from controller.notify_sqs_controller import SendNotifyBySQSController
api.add_resource(SendNotifyBySQSController, '/notify/sqs')
接著透過wsgi在本地起一個 server
sls wsgi serve
再搭配 postman 來做測試,測試內容如下
{
"message": "test Content"
}
接著透過sls deploy部署上會遇到一個問題,會有 Access Denied,所以要在serverless.yml加入 IAM role 的設定
add iam in provider
iamRoleStatements:
- Effect: Allow
Action:
- sqs:SendMessage
Resource:
- ${env:SQS_ARN}

使用 SQS 這類服務都會需要透過boto3來幫忙串接,最需要注意的就是 IAM role,因為在本地端的 key 通常權限都是最大的,但上到 AWS 上就會有權限的問題,所以要記得加入 IAM 哦!
Code is here
專案也會持續更新,更多詳情可以 follow 我的專案 aws-python-line-api。
以前自己在架伺服器的時候,提到訊息佇列不外乎都是 RabbitMQ 或是 Kafka,只是現在 AWS、GCP 這類的雲端平台都越來越火紅了,手動按一下服務就建好了(信用卡也在哭泣),省去很多建立服務的時間,只是說一般時候根本找不到項目練習 😓,剛好最近在複習 LINE Notfiy,就趁這個機會順便練習並記錄一下 ✌️
當 Http requests 非常大量到一定程度,database 已經跟不上處理的速度,尤其是 relational database,這時就需要 queue 來緩衝;所以社群網站像 Facebook or LinkedIn 都使用大量的 message queue and cache. By -> Leonard Lee
SQS 就是 managed queue service,主要就是 async, central messaging (對相對應的程式來說,通常處理 api 的會有多個 instances 同時存在,像是 auto-scaling),像是 fb 通知、寄送 email 認證信這種不用即時處理的情況,只要給訊息給 queue 讓其他服務或程式去處理,可以把原本的邏輯簡化(以及責任區分),甚至些事件是預期同時會有多個 listener 會需要處理的情況,在一個 api 裡面去處理這些會讓邏輯變很複雜/不好維護。 By -> Bill Chung
還有就是如果負責處理 request 的 host 有問題,message queue 可以用來短暫儲存還未被處理的 requests 使他們不至於丟失,直到 host 回復正常或是換了一個好的 host 後,message queue 裡面的的 requests 就可以繼續被處理 By -> 盧元駿
以上來自我之前寫過的文章 使用 Serverless 讓 AWS SQS 幫你發送 LINE Notify
今天在需要發個請求去呼叫 LINE API(或是其他服務的 API),都可能會碰到圖片,圖片不管他怎麼壓縮,終究還是比文字肥,在數量多的情況下可能就會發現 API 罷工(就會像被斷詠唱一樣)。雖然平常使用可能不會這麼平凡呼叫,但若在商業用途上使用者多的時候一次呼叫就會有一大筆,這時候用 Queue 讓他們排隊一個一個來就在適合不過了 🎉。
Amazon Simple Queue Service (SQS) 是全受管訊息佇列服務,可讓您分離和擴展微型服務、分散式系統及無伺服器應用程式。SQS 可免除與管理和操作訊息導向中介軟體相關的複雜性及開銷,也可讓開發人員專注在與眾不同的工作上。您可以使用 SQS 在軟體元件之間傳送、存放和接收不限數量的訊息,不會遺失訊息或需要其他服務可用。使用 AWS 主控台、命令列界面或自選的 SDK 以及三個簡單的命令,即可在幾分鐘內開始使用 SQS。(參考 AWS)
SQS 有在免費方案裡面,只要一個月別高於 100 萬個請求就不會被收錢啦 💪

AWS 的免費方案可以參考 -> LINK
接著就要開始引入 SQS 嚕,首先到 AWS 上的 SQS 頁面,如下

接著選左邊的Standard Queue並填入 name 就好

建立完之後就可以看到下面有建立完的資訊
ARN 以及 URL 接下來都會用到哦

接下來會帶各位使用 Notify 接在 SQS 上,這篇就先帶各位介紹並先手動建立一個 Queue,之後會在大家使用 python 的 boto3 的套件來搭配使用 AWS 服務使用 👏
看著前幾天的文章,像我們 notify 驗證的 API 有使用到 REDIRECT_URI、CLIENT_ID 以及 CLIENT_SECRET,或是像 PostgreSQL 的帳號密碼,
只是說若今天當程式碼變多的時候,抑或是這個參數有給其他 API 使用,那在尋找的時候不僅費工又浪費時間
那接下來就帶著大家在 serverless.yml 一步步加入變數值,並更改 code。
用 npm 安裝 serverless 的 dotenv 套件
npm i -D serverless-dotenv-plugin
接著加入新的套件到serverless.yml
plugins:
- serverless-dotenv-plugin
新增dotenv到 requirements.txt
python-dotenv==0.10.3
到 api.py 加入下面內容
from dotenv import load_dotenv
env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path)
接著新增.env並輸入對應的內容
NOTIFY_REDIRECT_URI=
NOTIFY_CLIENT_ID=
NOTIFY_CLIENT_SECRET=
REGION=us-east-2
SQS_URL=
SQS_ARN=
PG_DB=
PG_HOST=
PG_NAME=
PG_PWD=
PG_PORT=
接著修改有使用到他們的地方,範例如下
import os
os.getenv("SQS_ARN")
既然有安裝 serverless 的套件了,那 yml 檔也可以使用哦!
provider:
name: aws
runtime: python3.7
region: ${env:REGION}
有時候把專案抓下來的時候要找到這些輸入的地方很容易找不到(我有點癡呆),一般 open source 也都會有一個.env的,用serverless-dotenv-plugin來幫忙弄就方便許多了,後續有需要再繼續往裡面新增就好了~
最後在搭配 python 的 dot env 套件使用就讓整個好用多了 🤣,只是說 html 因為目前還不是透過 serverless 來幫忙部署,所以這邊的參數就沒辦法吃設定檔了 😓
繼上一篇我們已經可以讓使用者註冊 Notify 並將 token 放入資料庫,接著就帶各位使用 query string 的方式讓你的 Notify 可以送訊息給所有註冊過的 Notify。
首先我們進入我們已經建立過的 controller/notify_controller.py,會看到 class 下有我們之前建立的 post method,這時我們就在前面加入 get method,並加入以下的 code,讓這個 class 看起來比較有順序(潔癖)
from flask import request
class NotifyController(Resource):
def get(self):
msg = request.args.get('msg')
with Database() as db, db.connect() as conn:
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
cur.execute(
f"SELECT token FROM notify")
fetch = cur.fetchall()
for f in fetch:
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': f"Bearer {f['token']}"
}
payload = {'message': msg}
r = requests.post(
'https://notify-api.line.me/api/notify', data=payload, headers=headers)
return {'result': 'ok'}, 200
def post(self):
...
這次使用的套件比上次多 import 一個 flask 底下的 request (注意沒有 s 哦),這個主要是讓我們可以抓到網址問號後面接的參數,這邊我設定打 API 的人會打一個 msg 的參數。
接著就是使用進 DB 撈我們的 token 們,並用一個迴圈來跑
再來是設定 headers 以及 payload
headers 的部分參考 Notify 的文件,如下圖
我們需要設定 Content-Type 以及 Authorization,需要注意的是 Authorization 是使用 Bearer 格式(參考),他中間是有加空白鍵的,這邊很多朋友都會錯在這裡,千萬要記得檢查這邊!
接著是要送出去的東西是要用什麼送出,本篇只用 message 做範例,若需要使用到其他的功能,像是圖片、貼圖等等的可參考以下的圖片

接著我們就可以使用sls deploy來部署我們的程式啦
等等!
這邊有個需要注意的地方是,照著這次的範例使用的話會需要用 docker 來幫忙跑編譯,因為 psycopg2-binary 這個套件如果不是在 Linux 的環境下會需要透過 docker 幫忙編譯完再丟過去
這部分需要在serverless.yml下填入參數
custom:
pythonRequirements:
dockerizePip: true
如此一來當前環境若不是 Linux 的話他就會使用 docker 來幫忙,記得 docker 要開哦 🙏
當然最簡單的方法就是把它直接抓下來放在專案中,只是這樣就會比較髒一點,可以從這邊抓 -> 參考
這邊可以先在環境變數上加上
SLS_DEBUG=*接著在加上 sls deploy 後面加上--verbose可以看到 serverless 到底都在背地裡做了什麼事情 🤣
接著我們就使用 Postman 來幫我們送字串出去,參考下圖,當回傳 ok 就成功了 🎉
你的 Notify 應該要回你了~

這邊帶大家做一個簡單的應用,一般來說我覺得放在 query string 讓其他 API 來呼叫的時候帶參數來就可以直接用是很方便的,不用再特地自己寫方法去呼叫 LINE Notify 來幫我們送,反正 AWS Lambda 有一百萬次的請求不怕 🤣
第一步先到 LINE Notify 管力登入服務去建立一個服務
接著按下登入服務
把該填的填一填,要注意的是 Callback Url 這邊先暫時先打 localhost:5500,等頁面上傳後再回來改 domain


建立完成之後就到剛剛填的信箱去收信點下網址並啟動服務
這樣第一步就建立完成嚕!
接著上次的透過上次的專案並使用 flask_restful 來改造成 Restful 格式的專案(後面開發會比較輕鬆) 因為我們會讓前端呼叫,CORS 設定成*比較輕鬆,若是有要設定跨域問題的在這邊設定哦 👏 加入套件到 requirements.txt
flask==1.0.2
Flask-RESTful==0.3.6
Flask-Cors==3.0.6
requests==2.22.0
透過使用 flask_restful 幫忙轉發並管理路由 將 api.py 改為以下的 code
from flask import Flask
from flask_restful import Api
from flask_cors import CORS
from controller.notify_controller import NotifyController
app = Flask(__name__)
CORS(app, resources={r"*": {"origins": "*", "supports_credentials": True}})
api = Api(app)
api.add_resource(NotifyController, '/notify')
if __name__ == '__main__':
app.run(debug=True)
這邊我使用 Postgresql,並在專案底下建立一個 lib/ 的資料夾,裡頭放著名為 db.py (這裡還是要記得建立 init.py 哦!) 這邊只簡單建立 Context Manager 讓連線可以被我呼叫。 db.py:
import psycopg2
import psycopg2.extras
class Database():
conns = []
def __enter__(self):
return self
def connect(self):
conn = psycopg2.connect(
database=PG_DB,
user=PG_USER,
password=PG_PWD,
host=PG_HOST,
port=PG_PORT
)
self.conns.append(conn)
return conn
def __exit__(self, type, value, traceback):
for conn in self.conns:
conn.close()
self.conns.clear()
新增一下資料夾名為 controller,建立 init.py & notify_controller.py,這邊搭配 with 做資源管理器, DB 部分則見黎一個 notify 表,然後一個 token 的欄位。
在這個 class 類別下方法只要輸入
def get/post/put/patch/delete的函式就可以對應 CRUD 的功能了!
notify_controller.py code is:
from flask_restful import Resource, reqparse
import requests
import json
from lib.db import Database
import psycopg2.extras
class NotifyController(Resource):
def post(self):
parser = reqparse.RequestParser()
parser.add_argument('code', required=True, help='code can not be blank!')
args = parser.parse_args()
code = args['code']
client = {'grant_type': 'authorization_code', 'code': code, 'redirect_uri': 'YOUR_REDIRECT_URI',
'client_id': 'YOUR_CLIENT_ID', 'client_secret': 'YOUR_CLIENT_SECRET'}
r = requests.post(
'https://notify-bot.line.me/oauth/token', data=client)
req = json.loads(r.text)
if req['status'] == 200:
token = req['access_token']
# Here is use PostgreSQL, you can change your love db
with Database() as db:
with db.connect() as conn:
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
cur.execute(
f"INSERT INTO notify(token) VALUES ('{token}')")
return {'access_token': req['access_token']}, 200
else:
return {'message': r.text}, 200
以下這三個要設定成你的當時輸入的字串
若不清楚 Client_id & Client_secret 的話就回到 Notify 頁面並點選剛剛建立的服務就能看到 token 了

建立一個 views/ 的資料夾,並建立兩個檔案
這邊需要修改
client_id以及redirect_uri這個檔案主要功能是讓使用者有個可以按按鈕觸發的地方,當然這只是個範例,實際上線可能就會直接 call API 導向 LINE Notify 頁面等等
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Link Notify</title>
</head>
<body>
<button onclick="Auth();">點選這裡連結到LineNotify</button>
<script>
function Auth() {
var URL = "https://notify-bot.line.me/oauth/authorize?";
URL += "response_type=code";
URL += "&client_id=YOUR_CLIENT_ID";
URL += "&redirect_uri=YOUR_REDIRECT_URI";
URL += "&scope=notify";
URL += "&state=nostate";
window.location.href = URL;
}
</script>
</body>
</html>
修改 ajax 裡的 url,將網址換成之前部署的網址,若忘了可以在部署一次
sls deploy當導回來的時候透過 ajax 來呼叫 serverless api 讓後端發 requests 給https://notify-bot.line.me/oauth/token來獲取access_token。
因為這個路由有 CORS 的問題,若透過瀏覽器來的話會出錯,這邊則是讓後端去處理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>message check</title>
</head>
<body>
verify messate:
<div class="message"></div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<script>
var urlParams = new URLSearchParams(window.location.search);
var notify_code = urlParams.get("code");
var state = urlParams.get("state");
$(function() {
$.ajax({
url: "https://SLS_URI.execute-api.us-east-2.amazonaws.com/dev/notify",
type: "POST",
dataType: "json",
data: { code: notify_code },
success: function(response) {
console.log(response);
$(".message").text(response["access_token"]);
},
error: function(xhr) {
console.log(xhr);
alert("Ajax request 發生錯誤");
}
});
});
</script>
</html>
接著就是測試程式碼是不是正常的,把這兩個檔案丟到 S3 上面


把 object url 複製到 LINE Notify 上的 callback url 欄位,這樣就可以實機測試了 🤣

接著按下 index page 的按鈕應該就會看到連動成功(進資料庫應該也會被 insert 一筆)

到了這邊如果沒問題真的是恭喜你,我踩了好多坑 Orz
Amazon Simple Storage Service(簡稱 AWS S3)是 AWS 的一個線上儲存服務,用戶能夠輕易把檔案儲存到網路伺服器上,可能有些朋友會把它當成雲端硬碟看待,但其實還能像是傳靜態檔案上去,若有寫前端框架的朋友通常都會需要把程式碼打包成靜態檔案再部署,然而這邊把他打包送上去後,S3 會給一個名為 Object Url 的網址,可以透過這個網址去執行網頁裡面的內容(如 AJAX、Axios...),並可以透過 cloudfont 去弄成 CDN(扯遠了),簡單說他還有很多不一樣的功能應用等著大家去用~
因為 serverless 只能放關於 API 的程式碼,因此我們需要把 html 靜態檔案放在 S3 裡,
接下來就來建立一個公開的 S3 容器,這邊就不會提到權限如何控管哦!
建立一個名為2019-it-30-bucket的容器,地區就選與 Lambda 同樣的地區,直接按下左下角的create來建立。

建立完會像這樣
接著點進去後在點到第三個選項會看到 Block of public access 是啟動的狀態,因為我們接下來需要讓我們的頁面可以外部使用
就把 check box 的按掉,在按下 Save
要輸入confirm就修改完成嚕
接著一樣點到第三個個選項,選到Access Control List,看到Access for bucket owner這個選項的Canonical ID,選項都全部打勾之後再來調整,這樣之後的 HTML 檔案外面就看得到了。

接著上傳一個index.html的檔案來測試
<html>
<h1>Hello world</h1>
</html>
上傳後下拉式選單選擇public read的選項,因為一個檔案在 S3 就算是一個 Object,所以一定要讓它能對外,然後按下左下的 Upload

成功後點進這個檔案,並按下方的Object URL,看到 Hello World 的話就代表成功囉,這時可以開個無痕測一次,確認一下沒有錯誤 😃

LINE Notify 顧名思義就是通知屬性的服務,這個服務不是 LINE 的 Message API,千外別把這兩個搞在一起哦!
在實作前要先認識一下在接的 api 服務原理 首先先參考LINE Notify 官網 開頭的介紹:
Overview: Becomes a provider based on OAuth2 (https://tools.ietf.org/html/rfc6749). The authentication method is authorization_code. The access token acquired here can only be used for notification services
不負責任翻譯: 這個服務是基於 OAuth2 實作的,授權模式(grant_type)是 authorization_code 參考 access_token 則是只能讓通知服務所使用的一個鑰匙
更詳細的流程可以參考 https://blog.yorkxin.org/2013/09/30/oauth2-4-1-auth-code-grant-flow.html
The host name for authentication API endpoint is notify-bot.line.me.
然後 API 的網址是 notify-bot.line.me
接著我們來看看流程圖

首先就是要先加入他好友,如果之前有不小心封鎖的話要記得解除封鎖哦,不然後續會收不到消息。

下一篇會帶著時做出簡單的 index.html + 使用 Serverless 蓋我們第一個 API 來做認證。 會使用到 LINE Notify 的 API 為以下三個,不清楚裡面實際上功能的朋友可以嗑一下官網文件
GET https: //notify-bot.line.me/oauth/authorize -> 前往認證拿到 code 參數
POST https://notify-bot.line.me/oauth/token -> 拿 code 參數換 access_token
POST https://notify-api.line.me/api/notify -> 發送訊息
今年中有帶著朝陽的學弟妹手把手實作 LINE Notify,如果只想自己用的話可以參考我之前簡報
透過 npm 全域安裝 serverless 指令
npm install serverless -g
因為我是用 python3 在 AWS 上實作
--template可以選擇你想要的模板 (參考)name service 的名稱--path 專案路徑,若找不到資料夾時會自動新增serverless create --template aws-python3 --name aws-wsgi-flask --path aws-wsgi-flask

接著後面會需要使用套件,而 python 這邊會認得 requirements.txt 這個檔案,因此我們需要用 serverless 的 plugin 幫我們處理,讓專案在 deploy 上去之後會透過 requirements.txt 去安裝套件。
而接下來我們會需要一個介面幫我們處理一些麻煩事,這邊就選用 wsgi 來處理。詳細參考
sls plugin install -n serverless-python-requirements
sls plugin install -n serverless-wsgi
接著去看 serverless.yml 檔案最後出現下面這樣就成功安裝了。
plugins:
- serverless-python-requirements
- serverless-wsgi
然後新增一個 setup.cfg 並加入以下內容:
[install]
prefix=
由於 AWS Lambda 上都會將套件安裝在本地端(Lambda 上),因此在執行
pip install -t . -r requirements.txt時會需要 setup.cfg。
接著選用 Flask 框架作我們開發的
echo "Flask==1.0.2" > requirements.txt
pip install -r requirements.txt -—user
serverless-python-requirements 同樣也支持 pipenv 哦,如果想完全擋掉的話可以參考
置換以下內容至 serverless.yml,讓 WSGI 去處理路由。
service: aws-wsgi-flask
provider:
name: aws
runtime: python3.7
functions:
api:
handler: wsgi_handler.handler
events:
- http: ANY /
- http: ANY {proxy+}
custom:
wsgi:
app: api.app
plugins:
- serverless-python-requirements
- serverless-wsgi
因為我們不是單純只用 python 架設,所以要將 handler.py 替換成 api.py,並加入以下內容
from flask import Flask
app = Flask(__name__)
@app.route("/", methods=['GET'])
def handler():
return 'hello world'
if __name__ == '__main__':
app.run(debug=True)
到這邊基礎設施都弄好了,但總不能部署才測試吧,所以這時候我們就出動
serverless wsgi serve
透過 wsgi 去幫忙起一個 local server 供使用者可以測試,而且每次儲存都重新整理一次哦!詳細用法請參考

接著用 postman 來測試一下

燈燈,hello world 出來了~
最後也是最重要的一個步驟『部署』 只是說部署前需要有 AWS 的 secret key & token
首先要先有個 AWS 帳號(廢話) ,如果沒有綁定信用卡記得去綁定才能繼續,接下來就可以去就可以去建立鑰匙囉!
如上圖,按下紅色框框的部分,接著按箭頭指的按鈕,就會建立一個我們要的金鑰囉!
如上圖所示,可以下載 key,AWS 不會幫忙保存 Secret key,若這視窗關掉的話 key 就不見了,所以使用者可以自行下載檔案保存,以免哪天要部署的時候找不到 key。
接著就將這兩個參數加入環境變數中:
export AWS_ACCESS_KEY_ID=<your-key-here>
export AWS_SECRET_ACCESS_KEY=<your-secret-key-here>
或是藉由 serverless 指令來幫忙加入參數:
serverless config credentials --provider aws --key <your-key-here> --secret <your-secret-key-here>
這步驟很重要哦,不然 AWS 不認識你就不給你上傳了 ?
最後就是下 serverlesss deploy進行部署

這邊 AWS 幫忙建立一個含有 SSL 的 domain,可以直接對這個網址下
endpoints: ANY - https://4omvn4z7re.execute-api.us-east-1.amazonaws.com/dev
ANY - https://4omvn4z7re.execute-api.us-east-1.amazonaws.com/dev/{proxy+}
接著把網址丟到瀏覽器上來試試看
出現 hello world就成功囉!

那因為現任公司的服務都是基於 AWS,如此這般我就接觸到 Serverless(以下簡稱 sls) 這個框架啦 (想更深入了解 FaaS 架構可以參考 AWS)
Serverless 架構是一個基於 FaaS(Function as a Service) 實作的一個服務,讓開發者可以更專注在開發功能,將 yml 檔設定好其餘維運的問題都交給 AWS、Google、Azure 這些服務商去處理,只要把信用卡準備好就好(?),讓開發者在寫完程式之後不用煩惱部署得問題,減少的不少的麻煩事。
以用一個 Ruby on Rails 寫的機器人為例,在寫完個 webhook function 後,設定路由然後部署,一般主機或是虛擬機就很麻煩,要安裝佈署環境以及一安裝堆相關套件,完了之後設定 Domain 什麼的有夠花時間。但若是使用 heroku 部署就很方便,Login-Create-Deploy 馬上就結束,機器人馬上就上線了,實在是俗又大碗呀~
但我覺得像是 LINE Message 這種 stateless 的服務,且只要一個 function + route 就能實作的程式最適合 Serverless 了,讓專案可以簡潔有力,只要寫一個 function+yml 設定並打個指令就部署了,而且 domain 還附贈 SSL,將服務交給 AWS 也不需要擔心,整個就是超級方便啊!
serverless.yaml 是該專案的設定檔,可以把它想像成是 CloudFormation 的 wrapper,事實上也的確是這樣,serverless 背後會把他轉成 CloudFormation 的 template 去發佈。這個設定檔是 serverless 的精髓所在,一切有關 API Gateway 和 Lambda 的設定都在這邊,而底層所需要的資源,他都幫你配置好了,不需要操心。 ref
依照我目前使用的結果下來,在 heroku 以及 AWS Lambda 同時睡著的情況下,AWS 起床的回應速度大概 1 秒左右,而 heroku 則大概落在 10~15 秒(參考)。
如下圖所示的免費方案,基本上開發階段應該是不至於到 100 萬/月 個請求吧 ?,所以這部分就別擔新大力地給他用下去!


serverless.yaml 的設定參考 serverless.yaml 的變數傳遞
接下來會是使用 Serverless 這個框架去實作的,使用的語言為 Python,至於為什麼會選它呢,最主要原因還是在官網上有很多 plugin 可以用 ( NodeJS & Python ),開發時資源相對多,接著是 AWS Lambda 的 Code-start 最快的就屬於 NodeJS & Python 了,而我原本就是寫 Ruby 來的,自然就選一個寫法相近的語言啦(好像有點牽強),如果說你想了解 runtimes 相關的問題的話可以參考這篇。