Skip to content

Latest commit

 

History

History
182 lines (114 loc) · 7.78 KB

File metadata and controls

182 lines (114 loc) · 7.78 KB

Discord 基礎範例調查兵團 [1]

在前一篇文章跟著 Discord 官方教學啟動範例程式,我們啟動了官方教學文件的第一個 Discord 應用程式。儘管我們什麼都沒有做,但得到了一個具體的實例可以作為「研究與學習」Discord 應用程式開發的立足點。

開大門走大路

Getting Started app for Discord 中的程式啟動說明如下:

node app.js

即使沒有 node.js 開發經驗的人,也可以看得出來這就是所謂的「正門」:應用程式的進入點。作為研究一份陌生的程式,看看他怎麼開頭應該會有點幫助。幸運的是,我們將所有的細節摺疊起來後,竟然只有短短的幾行:

他在第 19 行,建立了一個 express app 中間做了一點設定,在最後的 178 行,還呼叫了 listen 函式。挺符合我們對他的期望:一個簡易的 HTTP Server。可以先猜測 express 可能就是 node.js 用來建立 Web 應用程式的第三方 library。

有了這個猜想,我們可以簡單地組合一下關鍵字,拿去 Google 一下:

nodejs express

看起來這似乎是個熱門的 library (因為要社群的群體夠大,才有機會出現英文以外的說明)。知道來源了,讓我們逛一逛他的基礎教學唄:https://expressjs.com/en/starter/hello-world.html

Hello world 的範例,看起來跟我們拿到的 Discord 學習範例的結構是相似的,只是他在第 5 行用的是 app.get 接收一個 HTTP GET 的請求。而我們的 Discord 學習範例是使用 app.post 推論,大概會是接收 HTTP POST 的請求:

app.post('/interactions', async function (req, res) {
  // Interaction type and data
  const { type, id, data } = req.body;
  // ... skipped ...
}

為了驗證我們的推論,進一步查詢 API Reference:他的 express 物件實作了各種 HTTP Method 讓開發者使用,這些 method 被稱為 routing methods

獲得了上述的知識後,我們可以重新描述範例程式:

在 express app 中,實作針對路徑 /interactions 的 routing

app.post('/interactions', async function (req, res) {
  // Interaction type and data
  const { type, id, data } = req.body;
  // ... skipped ...
}

而他的 callback 參數就放 middleware function 即可,以我們的範例程式來說,看得了一個 callback 函式,有 reqres 變數,參考一下文件中的使用範例:

app.post('/', function (req, res) {
  res.send('POST request to homepage')
})

可以推論:

  • req 即為 HTTP Request 的封裝
  • res 即為 HTTP Response 的封裝

試著深入一點點

對於好奇的調查兵團,除了文件上的說明當然極有興趣知道,他在 Runtime 時會是什麼物件。這件事很容易,因為我們有可以玩的實例了,只要 Debugger 開下去就會知道:

他們分別是 IncomingMessageServerResponse。進一步,他是一個 Open Source 專案,你還可以去他的 GitHub Repo 上查一下這些物件如何建立出來的:

看起來,分別自 lib/request.jslib/response.js,但我們就先不深入了,畢竟主要是先探索一下對理解 Discord 範例程式有用的知識,目前深入太多層了,要提醒自己適時地停下,若將層數寫出來,大概會是:

  • Discord 範例程式
  • express 文件
  • express 的 routing 寫法
  • routing method 中的 callback 函式的用法
  • callback 函式的參數到底是什麼呢?

在心中大致有底後,我們小結一下目前的發現。在 Discord 範例中:

  • 它利用 express library 提供了一個簡單的 Web 應用程式
  • 針對 /interactions URI,處理 HTTP POST 的請求

退回一步,檢查遺漏項目

在理解完基本的 routing method 實作後,我們似乎還有一些遺漏的部分沒有去弄懂:

// 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 上附的搜尋功能,可以很快找到我們感興趣的文件:

他說 app.use 是去載入 middleware 用的。那麼問題來了 middleware 是什麼呢?繼續點開 middleware 的連結:

文件上表示 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 的說明,在不同的地方都有實作 middleware 的 interface。所以,除了我們剛剛範例看到的 routing middleware 之外,還有這些:

因此,我們終於知道了 app.use 就是 Application-level middleware。他提供了一個記錄 Request Time 的範例:

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 繼續查詢。

由它的說明看起來 express.json 是一個內建的 middleware function,它會看 Request 物件的 Content-Type 將符合 JSON 格式的 Request 轉成 Javascript 物件。同時,它也會看 Request Header 有沒有標示為壓縮的資料,如果有就自動解壓縮。

除了上述基本的功能,還可以知道它提供 Configuration 可以讓開發者填入一些參數,例如 Discord 範例程式中用到的 verify 就要填寫一個函式:

依名稱來看 verify 應該是用來驗證 Request 內容之用,搭配範例程式填入的函式名,推測應該為驗證來自 Discord Server 的 Request 是否合法:

app.use(express.json({ verify: VerifyDiscordRequest(process.env.PUBLIC_KEY) }));

研究到此,可以先簡單記一個 TODO,要在 VerifyDiscordRequest 學習如何驗證來自 Discord Server 的 Request 是否合法。

回到 Discord 的部分

大致理解完 Application 層級的相依工具後,我們可以回到 Discord 本身的 Domain Knowledge 了。要深入的目標很明確:

  • VerifyDiscordRequest 在驗證什麼?
  • /interactions 提供哪些功能。

這部分的探索,我們會再拍一集解說 (老高化!?)