0%

簡介

LINE 在 2016 年 9 月底發表了 Messaging API 功能,用戶可以透過 Messaging API 將自家的服務內容串聯到 LINE@ 上。

接著在 2019/04/18 之後 LINE@ 2.0 正式上線,在 5/22 後有升級的選項並會陸續更新,升級完的官方帳號也可以申請專屬 ID、進行帳號認證。

我認為比前一版本的最大的好處是在好友無上限的這部分,以前只能有 50 位好友,若一般開發者的機器人好不容易上線了並開放測試時被限制住,除了再開一隻機器人或是其他方法就超麻煩,現在好友無上限了,就不會有上述的問題了。

若您以前有開發過的機器人到現在還沒升級的話,可以參考 -> URL

這邊需要注意 1 on 1 聊天與 Messaging API 是無法同時使用的,但可以在後台切換看要使用哪種模式與顧客互動。對話記錄也會保存在各自的模式下,若要查看完整的對話記錄的話會建議以固定模式與顧客進行互動哦!

主要的功能環繞在以下兩點

  1. Push mode: 主動推播訊息給用戶,2.0 每個機器人都有 500 則免費推播則數,超過則數的話可以參考以下的價目表:

  1. Reply mode: 這個模式是接受使用者的訊息並透過 webhook 打到伺服器上讓伺服器去做對應訊息的處理,重點是它是是免費、免費、免費的!所以若能引導使用者去輸入訊息的話用這個模式是不用錢的哦 🎉

Message API 支援以下格式

  • Text message
  • Sticker message
  • Image message
  • Video message
  • Audio message
  • Location message
  • Imagemap message
  • Template message
  • Flex message

詳細內容可以參考 LINE doc

line sample 1
line sample 2

回傳格式

Message API 回傳格式都是一個陣列的格式,一開始在接的時候都沒注意到一直瘋狂出錯,事實上這樣陣列對 Server 有個好處就是當一次需要送較多訊息時格式會比較統一,只是對於有潔癖的開發者來說每次都要多打[0]會覺得有點髒 🤣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
'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 上

參考

功能介紹】Messaging API
LINE message template

首先先建立一個consumer/資料夾,新增__init__.py以及notify_handler.py

notify_handler.py輸入以下程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 的套件

1
boto3==1.9.189

加入SQS_URL以及SQS_ARN.env裡面

1
2
SQS_URL=sqs url
SQS_ARN=your sqs arn

add controller/notify_sqs_controller.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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

1
2
from controller.notify_sqs_controller import SendNotifyBySQSController
api.add_resource(SendNotifyBySQSController, '/notify/sqs')

接著透過wsgi在本地起一個 server

1
sls wsgi serve

再搭配 postman 來做測試,測試內容如下

1
2
3
{
"message": "test Content"
}

接著透過sls deploy部署上會遇到一個問題,會有 Access Denied,所以要在serverless.yml加入 IAM role 的設定

add iam in provider

1
2
3
4
5
6
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 讓他們排隊一個一個來就在適合不過了 🎉。

為什麼選擇 AWS SQS

Amazon Simple Queue Service (SQS) 是全受管訊息佇列服務,可讓您分離和擴展微型服務、分散式系統及無伺服器應用程式。SQS 可免除與管理和操作訊息導向中介軟體相關的複雜性及開銷,也可讓開發人員專注在與眾不同的工作上。您可以使用 SQS 在軟體元件之間傳送、存放和接收不限數量的訊息,不會遺失訊息或需要其他服務可用。使用 AWS 主控台、命令列界面或自選的 SDK 以及三個簡單的命令,即可在幾分鐘內開始使用 SQS。(參考 AWS)

SQS 有在免費方案裡面,只要一個月別高於 100 萬個請求就不會被收錢啦 💪

AWS 的免費方案可以參考 -> LINK

建立 SQS

接著就要開始引入 SQS 嚕,首先到 AWS 上的 SQS 頁面,如下


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


建立完之後就可以看到下面有建立完的資訊

ARN 以及 URL 接下來都會用到哦

結論

接下來會帶各位使用 Notify 接在 SQS 上,這篇就先帶各位介紹並先手動建立一個 Queue,之後會在大家使用 python 的 boto3 的套件來搭配使用 AWS 服務使用 👏

前言

看著前幾天的文章,像我們 notify 驗證的 API 有使用到 REDIRECT_URICLIENT_ID 以及 CLIENT_SECRET,或是像 PostgreSQL 的帳號密碼,
只是說若今天當程式碼變多的時候,抑或是這個參數有給其他 API 使用,那在尋找的時候不僅費工又浪費時間
那接下來就帶著大家在 serverless.yml 一步步加入變數值,並更改 code。

動手吧!

用 npm 安裝 serverless 的 dotenv 套件

1
npm i -D serverless-dotenv-plugin

接著加入新的套件到serverless.yml

1
2
plugins:
- serverless-dotenv-plugin

新增dotenv到 requirements.txt

1
python-dotenv==0.10.3

到 api.py 加入下面內容

1
2
3
from dotenv import load_dotenv
env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path)

接著新增.env並輸入對應的內容

1
2
3
4
5
6
7
8
9
10
11
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=

接著修改有使用到他們的地方,範例如下

1
2
import os
os.getenv("SQS_ARN")

既然有安裝 serverless 的套件了,那 yml 檔也可以使用哦!

1
2
3
4
provider:
name: aws
runtime: python3.7
region: ${env:REGION}

後記

有時候把專案抓下來的時候要找到這些輸入的地方很容易找不到(我有點癡呆),一般 open source 也都會有一個.env的,用serverless-dotenv-plugin來幫忙弄就方便許多了,後續有需要再繼續往裡面新增就好了~

最後在搭配 python 的 dot env 套件使用就讓整個好用多了 🤣,只是說 html 因為目前還不是透過 serverless 來幫忙部署,所以這邊的參數就沒辦法吃設定檔了 😓

參考

os.environ
env setting

繼上一篇我們已經可以讓使用者註冊 Notify 並將 token 放入資料庫,接著就帶各位使用 query string 的方式讓你的 Notify 可以送訊息給所有註冊過的 Notify。

首先我們進入我們已經建立過的 controller/notify_controller.py,會看到 class 下有我們之前建立的 post method,這時我們就在前面加入 get method,並加入以下的 code,讓這個 class 看起來比較有順序(潔癖)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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下填入參數

1
2
3
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 服務

第一步先到 LINE Notify 管力登入服務去建立一個服務

接著按下登入服務

把該填的填一填,要注意的是 Callback Url 這邊先暫時先打 localhost:5500,等頁面上傳後再回來改 domain


建立完成之後就到剛剛填的信箱去收信點下網址並啟動服務

這樣第一步就建立完成嚕!

動工

接著上次的透過上次的專案並使用 flask_restful 來改造成 Restful 格式的專案(後面開發會比較輕鬆)
因為我們會讓前端呼叫,CORS 設定成*比較輕鬆,若是有要設定跨域問題的在這邊設定哦 👏
加入套件到 requirements.txt

1
2
3
4
flask==1.0.2
Flask-RESTful==0.3.6
Flask-Cors==3.0.6
requests==2.22.0

透過使用 flask_restful 幫忙轉發並管理路由
將 api.py 改為以下的 code

1
2
3
4
5
6
7
8
9
10
11
12
13
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

以下這三個要設定成你的當時輸入的字串

  • YOUR_REDIRECT_URI
  • YOUR_CLIENT_ID
  • YOUR_CLIENT_SECRET

若不清楚 Client_id & Client_secret 的話就回到 Notify 頁面並點選剛剛建立的服務就能看到 token 了

建立一個 views/ 的資料夾,並建立兩個檔案

  • notify_index.html

    這邊需要修改 client_id 以及 redirect_uri
    這個檔案主要功能是讓使用者有個可以按按鈕觸發的地方,當然這只是個範例,實際上線可能就會直接 call API 導向 LINE Notify 頁面等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!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>
  • notify_confirm.html

    修改 ajax 裡的 url,將網址換成之前部署的網址,若忘了可以在部署一次 sls deploy
    當導回來的時候透過 ajax 來呼叫 serverless api 讓後端發 requests 給https://notify-bot.line.me/oauth/token來獲取access_token

因為這個路由有 CORS 的問題,若透過瀏覽器來的話會出錯,這邊則是讓後端去處理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!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

參考

資源管理器
flask CORS
CORS

前言

Amazon Simple Storage Service(簡稱 AWS S3)是 AWS 的一個線上儲存服務,用戶能夠輕易把檔案儲存到網路伺服器上,可能有些朋友會把它當成雲端硬碟看待,但其實還能像是傳靜態檔案上去,若有寫前端框架的朋友通常都會需要把程式碼打包成靜態檔案再部署,然而這邊把他打包送上去後,S3 會給一個名為 Object Url 的網址,可以透過這個網址去執行網頁裡面的內容(如 AJAX、Axios…),並可以透過 cloudfont 去弄成 CDN(扯遠了),簡單說他還有很多不一樣的功能應用等著大家去用~

因為 serverless 只能放關於 API 的程式碼,因此我們需要把 html 靜態檔案放在 S3 裡,

接下來就來建立一個公開的 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的檔案來測試

1
2
3
<html>
<h1>Hello world</h1>
</html>

上傳後下拉式選單選擇public read的選項,因為一個檔案在 S3 就算是一個 Object,所以一定要讓它能對外,然後按下左下的 Upload

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

參考

AWS S3 wiki

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 則是只能讓通知服務所使用的一個鑰匙

1
更詳細的流程可以參考 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

接著我們來看看流程圖
https://notify-bot.line.me/doc/en/

  • 當使用者拜訪你的網站時,會導向 LINE 請求認證
  • 認證過了之後會回傳一個名為 code 的參數
  • 接著網站需要持這個 code 在去找 LINE 討東西
  • 討成功後就會拿到一個 access_token
  • 網站就會知道這個 access_token = 來註冊的使用者
  • 然後就可以透過 access_token 發送通知給使用者了?

事前準備

首先就是要先加入他好友,如果之前有不小心封鎖的話要記得解除封鎖哦,不然後續會收不到消息。
https://ithelp.ithome.com.tw/upload/images/20190903/20111481Zno98NSHwL.png

下一篇會帶著時做出簡單的 index.html + 使用 Serverless 蓋我們第一個 API 來做認證。
會使用到 LINE Notify 的 API 為以下三個,不清楚裡面實際上功能的朋友可以嗑一下官網文件

1
2
3
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,如果只想自己用的話可以參考我之前簡報

LINE Notify
如何快速建置一個 LINE Notify 的服務

透過 npm 全域安裝 serverless 指令

1
npm install serverless -g

因為我是用 python3 在 AWS 上實作

  • --template可以選擇你想要的模板 (參考)
  • name service 的名稱
  • --path 專案路徑,若找不到資料夾時會自動新增
1
serverless create --template aws-python3 --name aws-wsgi-flask --path aws-wsgi-flask

加入套件

接著後面會需要使用套件,而 python 這邊會認得 requirements.txt 這個檔案,因此我們需要用 serverless 的 plugin 幫我們處理,讓專案在 deploy 上去之後會透過 requirements.txt 去安裝套件。

而接下來我們會需要一個介面幫我們處理一些麻煩事,這邊就選用 wsgi 來處理。詳細參考

1
2
sls plugin install -n serverless-python-requirements
sls plugin install -n serverless-wsgi

接著去看 serverless.yml 檔案最後出現下面這樣就成功安裝了。

1
2
3
plugins:
- serverless-python-requirements
- serverless-wsgi

然後新增一個 setup.cfg 並加入以下內容:

1
2
[install]
prefix=

由於 AWS Lambda 上都會將套件安裝在本地端(Lambda 上),因此在執行 pip install -t . -r requirements.txt 時會需要 setup.cfg。

接著選用 Flask 框架作我們開發的

1
2
echo "Flask==1.0.2" > requirements.txt
pip install -r requirements.txt -—user

serverless-python-requirements 同樣也支持 pipenv 哦,如果想完全擋掉的話可以參考

更改設定檔

置換以下內容至 serverless.yml,讓 WSGI 去處理路由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

測試 wsgi

因為我們不是單純只用 python 架設,所以要將 handler.py 替換成 api.py,並加入以下內容

1
2
3
4
5
6
7
8
9
from flask import Flask
app = Flask(__name__)

@app.route("/", methods=['GET'])
def handler():
return 'hello world'

if __name__ == '__main__':
app.run(debug=True)

到這邊基礎設施都弄好了,但總不能部署才測試吧,所以這時候我們就出動

1
serverless wsgi serve

透過 wsgi 去幫忙起一個 local server 供使用者可以測試,而且每次儲存都重新整理一次哦!詳細用法請參考

接著用 postman 來測試一下

燈燈,hello world 出來了~

最後也是最重要的一個步驟『部署』
只是說部署前需要有 AWS 的 secret key & token

建立 AWS 存取金鑰

首先要先有個 AWS 帳號(廢話) ,如果沒有綁定信用卡記得去綁定才能繼續,接下來就可以去就可以去建立鑰匙囉!

如上圖,按下紅色框框的部分,接著按箭頭指的按鈕,就會建立一個我們要的金鑰囉!

如上圖所示,可以下載 key,AWS 不會幫忙保存 Secret key,若這視窗關掉的話 key 就不見了,所以使用者可以自行下載檔案保存,以免哪天要部署的時候找不到 key。
接著就將這兩個參數加入環境變數中:

1
2
export AWS_ACCESS_KEY_ID=<your-key-here>
export AWS_SECRET_ACCESS_KEY=<your-secret-key-here>

或是藉由 serverless 指令來幫忙加入參數:

1
serverless config credentials --provider aws --key <your-key-here> --secret <your-secret-key-here>

這步驟很重要哦,不然 AWS 不認識你就不給你上傳了 ?

最後就是下 serverlesss deploy進行部署

這邊 AWS 幫忙建立一個含有 SSL 的 domain,可以直接對這個網址下

1
2
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

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


Cold start 問題

依照我目前使用的結果下來,在 heroku 以及 AWS Lambda 同時睡著的情況下,AWS 起床的回應速度大概 1 秒左右,而 heroku 則大概落在 10~15 秒(參考)。

如下圖所示的免費方案,基本上開發階段應該是不至於到 100 萬/月 個請求吧 ?,所以這部分就別擔新大力地給他用下去!

AWS Lambda 限制

安裝與使用

serverless.yaml 的設定參考
serverless.yaml 的變數傳遞


結論

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

環境

參考