# Discord 基礎範例調查兵團 [1] 在前一篇文章[跟著 Discord 官方教學啟動範例程式](https://hackmd.io/@iD40lBm-QAqgh62DVHbjPA/B1UUUU53s),我們啟動了官方教學文件的第一個 Discord 應用程式。儘管我們什麼都沒有做,但得到了一個**具體的實例**可以作為「研究與學習」Discord 應用程式開發的立足點。 ## 開大門走大路 在 [Getting Started app for Discord](https://github.com/discord/discord-example-app) 中的程式啟動說明如下: ```bash node app.js ``` 即使沒有 node.js 開發經驗的人,也可以看得出來這就是所謂的「正門」:應用程式的進入點。作為研究一份陌生的程式,看看他怎麼開頭應該會有點幫助。幸運的是,我們將所有的細節摺疊起來後,竟然只有短短的幾行: ![](https://i.imgur.com/sjZ2JWL.png) 他在第 19 行,建立了一個 `express app` 中間做了一點設定,在最後的 178 行,還呼叫了 `listen` 函式。挺符合我們對他的期望:一個簡易的 HTTP Server。可以先猜測 `express` 可能就是 node.js 用來建立 Web 應用程式的第三方 library。 有了這個猜想,我們可以簡單地組合一下關鍵字,拿去 Google 一下: ```bash nodejs express ``` ![](https://i.imgur.com/mdVfzF1.png) 看起來這似乎是個熱門的 library (因為要社群的群體夠大,才有機會出現英文以外的說明)。知道來源了,讓我們逛一逛他的基礎教學唄:[https://expressjs.com/en/starter/hello-world.html](https://expressjs.com/en/starter/hello-world.html) ![](https://i.imgur.com/MZvmLC2.png) 在 `Hello world` 的範例,看起來跟我們拿到的 Discord 學習範例的結構是相似的,只是他在第 5 行用的是 `app.get` 接收一個 `HTTP GET` 的請求。而我們的 Discord 學習範例是使用 `app.post` 推論,大概會是接收 `HTTP POST` 的請求: ```jsx app.post('/interactions', async function (req, res) { // Interaction type and data const { type, id, data } = req.body; // ... skipped ... } ``` 為了驗證我們的推論,進一步查詢 [API Reference](https://expressjs.com/en/4x/api.html):他的 `express` 物件實作了各種 HTTP Method 讓開發者使用,這些 method 被稱為 `routing methods`: ![](https://i.imgur.com/bohSbQO.png) 獲得了上述的知識後,我們可以重新描述範例程式: > 在 express app 中,實作針對路徑 `/interactions` 的 routing > ```jsx app.post('/interactions', async function (req, res) { // Interaction type and data const { type, id, data } = req.body; // ... skipped ... } ``` 而他的 callback 參數就放 `middleware function` 即可,以我們的範例程式來說,看得了一個 callback 函式,有 `req` 與 `res` 變數,參考一下文件中的使用範例: ```jsx app.post('/', function (req, res) { res.send('POST request to homepage') }) ``` 可以推論: - req 即為 HTTP Request 的封裝 - res 即為 HTTP Response 的封裝 ## 試著深入一點點 對於**好奇的調查兵團**,除了文件上的說明當然極有興趣知道,他在 Runtime 時會是什麼物件。這件事很容易,因為我們有可以玩的實例了,只要 Debugger 開下去就會知道: ![](https://i.imgur.com/xontcrJ.png) 他們分別是 `IncomingMessage` 與 `ServerResponse`。進一步,他是一個 Open Source 專案,你還可以去他的 GitHub Repo 上查一下這些物件如何建立出來的: ![](https://i.imgur.com/KV7wbFt.png) ![](https://i.imgur.com/ACbtDRL.png) 看起來,分別自 `lib/request.js` 與 `lib/response.js`,但我們就先不深入了,畢竟主要是先探索一下對理解 Discord 範例程式有用的知識,目前深入太多層了,要提醒自己適時地停下,若將層數寫出來,大概會是: - Discord 範例程式 - express 文件 - express 的 routing 寫法 - routing method 中的 callback 函式的用法 - callback 函式的參數到底是什麼呢? 在心中大致有底後,我們小結一下目前的發現。在 Discord 範例中: - 它利用 express library 提供了一個簡單的 Web 應用程式 - 針對 `/interactions` URI,處理 HTTP POST 的請求 ## 退回一步,檢查遺漏項目 在理解完基本的 routing method 實作後,我們似乎還有一些遺漏的部分沒有去弄懂: ```jsx // Create an express app const app = express(); // Get port, or default to 3000 const PORT = process.env.PORT || 3000; // Parse request body and verifies incoming requests using discord-interactions package app.use(express.json({ verify: VerifyDiscordRequest(process.env.PUBLIC_KEY) })); ``` 那個 `app.use` 是什麼東西呢?利用 API Reference 上附的搜尋功能,可以很快找到我們感興趣的文件: ![](https://i.imgur.com/geSyRMv.png) 他說 `app.use` 是去載入 `middleware` 用的。那麼問題來了 middleware 是什麼呢?繼續點開 middleware 的連結: ![](https://i.imgur.com/yyejEb8.png) 文件上表示 `middleware` 是一種函式,它可以存取 `requet object` 還有 `response object` 以及 `next` 函式。我們可以用它來: - 執行任何東西 - 變更 request 或 response 的內容 - 結果 `request-response` 的 cycle (白話的意思,就是完成 request 並以 response 回應給 HTTP Client) - 呼叫 `下一個` middleware 這裡可以看到,它範例的 `middleware callback` 比我們先前看到的多一個 `next`,他呼叫 `next()` 來繼續 `request-response` 的流程。有沒有感受到 Chain of Responsibility 了呢?再看 [Using middleware](http://expressjs.com/en/guide/using-middleware.html) 的說明,在不同的地方都有實作 middleware 的 interface。所以,除了我們剛剛範例看到的 routing middleware 之外,還有這些: ![](https://i.imgur.com/LtcMRJL.png) 因此,我們終於知道了 `app.use` 就是 Application-level middleware。他提供了一個記錄 Request Time 的範例: ```jsx const express = require('express') const app = express() const requestTime = function (req, res, next) { req.requestTime = Date.now() next() } app.use(requestTime) app.get('/', (req, res) => { let responseText = 'Hello World!<br>' responseText += `<small>Requested at: ${req.requestTime}</small>` res.send(responseText) }) app.listen(3000) ``` 由於 `requestTime` callback 內呼叫了 `next()`。因此 middleware 把任務交棒給下一個 middleware 了,就輪到了 `app.get` 處理。而 `app.get` 並沒有再次申明要有下一個 middleware,所以它就是 `request-response` cycle 的終點了。 ## express.json middleware 弄明白了 `app.use` 是個 Application-level middleware 後,下一個問題就是 `express.json` 這個 middleware 的功能是什麼?同樣的,利用 API Reference 繼續查詢。 ![](https://i.imgur.com/Y1vyefQ.png) 由它的說明看起來 `express.json` 是一個內建的 middleware function,它會看 Request 物件的 `Content-Type` 將符合 JSON 格式的 Request 轉成 Javascript 物件。同時,它也會看 Request Header 有沒有標示為壓縮的資料,如果有就自動解壓縮。 除了上述基本的功能,還可以知道它提供 Configuration 可以讓開發者填入一些參數,例如 Discord 範例程式中用到的 `verify` 就要填寫一個函式: ![](https://i.imgur.com/6DQZPs9.png) 依名稱來看 `verify` 應該是用來驗證 Request 內容之用,搭配範例程式填入的函式名,推測應該為驗證來自 Discord Server 的 Request 是否合法: ```jsx app.use(express.json({ verify: VerifyDiscordRequest(process.env.PUBLIC_KEY) })); ``` > 研究到此,可以先簡單記一個 TODO,要在 `VerifyDiscordRequest` 學習如何驗證來自 Discord Server 的 Request 是否合法。 > ## 回到 Discord 的部分 大致理解完 Application 層級的相依工具後,我們可以回到 Discord 本身的 Domain Knowledge 了。要深入的目標很明確: - `VerifyDiscordRequest` 在驗證什麼? - `/interactions` 提供哪些功能。 這部分的探索,我們會再拍一集解說 (老高化!?)