webhook 是什麼以及如何建立
我們在網上做的工作,大部分其實就是事件,webhooks 已經成為了連線系統的主要方式,不管是使用者建立、支付成功、DockerHub 映象推送或者 Git 倉庫上的合併請求,這些都是非常有用並且輕量級的共享資訊的方式
那麼,webhook 究竟是什麼呢?webhook 是應用給其它應用提供實時資訊的一種方式。資訊一產生,webhook 就會把它傳送給已經註冊的應用,這就意味著你能實時得到資料。不像傳統的 APIs 方式,你需要用輪詢的方式來獲得儘可能實時的資料。這一點使得 webhook 不管是在傳送端還是接收端都非常高效。由於大部分服務提供商對 API 的訪問有一定限制,所以要麼採用 webhook 方式,要麼採用傳統的輪詢方式,不過這樣客戶端資料會有一些(或者比較多的)滯後。上面的圖是使用者發起的一個典型的支付流程的示例。
Webhook 有時被叫做「反向 APIs」,因為它返回的資訊和正常 API 返回的差不多,並且你還得設計一個 webhook 可以使用的 API。webhook 會向你的應用傳送一個 HTTP 請求(通常使用 POST 方式),然後你的應用負責解析這個請求。你可以把它當成一個客戶端發來的常規 API 請求,不過這時候它是一個你依賴的一個第三方的服務。
現在許多開發者都在使用比較流行的服務(比如 Strip、GitHub)提供的 webhook,你可能也想讓使用者接收你的應用產生的 webhook。這篇文章裡,我們就是要建立這樣一個簡單的應用,讓其它使用者可以註冊並且接收這個應用產生的事件。
建立一個 webhhook 分發應用
我們的這個應用將使用 Go 語言編寫( ofollow,noindex" target="_blank">這裡 有 Go 語言的安裝指南),但是你可以選擇任何其它的語言來實現這個應用,這只是一個展示 webhook 功能的簡單的例子。程式碼非常簡單,即使你對 Go 不熟悉也很容易讀懂。
package main import ( "bytes" "encoding/json" "fmt" "log" "net/http" "sync" "time" ) // port - default port to start application on const port = ":8090" type WebhookRequest struct { Namestring Destination string } func main() { dispatcher := &Dispatcher{ client:&http.Client{}, destinations: make(map[string]string), mu:&sync.Mutex{}, } // preparing HTTP server srv := &http.Server{Addr: port, Handler: http.DefaultServeMux} // webhook registration handler http.HandleFunc("/webhooks", func(resp http.ResponseWriter, req *http.Request) { dec := json.NewDecoder(req.Body) var wr WebhookRequest err := dec.Decode(≀) if err != nil { resp.WriteHeader(http.StatusBadRequest) return } dispatcher.add(wr.Name, wr.Destination) }) // start dispatching webhooks go dispatcher.Start() fmt.Printf("Create webhooks on http://localhost%s/webhooks \n", port) // starting server err := srv.ListenAndServe() if err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } } type Dispatcher struct { client*http.Client destinations map[string]string mu*sync.Mutex } func (d *Dispatcher) Start() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: d.dispatch() } } } func (d *Dispatcher) add(name, destination string) { d.mu.Lock() d.destinations[name] = destination d.mu.Unlock() } func (d *Dispatcher) dispatch() { d.mu.Lock() defer d.mu.Unlock() for user, destination := range d.destinations { go func(user, destination string) { req, err := http.NewRequest("POST", destination, bytes.NewBufferString(fmt.Sprintf("Hello %s, current time is %s", user, time.Now().String()))) if err != nil { // probably don't allow creating invalid destinations return } resp, err := d.client.Do(req) if err != nil { // should probably check response status code and retry if it's timeout or 500 return } fmt.Printf("Webhook to '%s' dispatched, response code: %d \n", destination, resp.StatusCode) }(user, destination) } }
執行這個webhook 應用
要使用這個 webhook 應用,我們需要一個可以接收 webhook 訊息並且除錯的一個終端。為了完成這個任務,我們選擇了 https://bin.webhookrelay.com/ 這個免費的服務。開啟這個連結後,會被重定向到一個唯一的地址,那個就是我們要使用的終端地址,後面我們很快就會用到這個地址。
接下來讓我們啟動這個應用:
$ go run main.go Create webhooks on http://localhost:8090/webhooks
現在把剛才生成的終端地址註冊到我們的應用裡:
curl --request POST \ --url http://localhost:8090/webhooks \ --header 'content-type: application/json' \ --data '{ "name": "joe", "destination": "https://bin.webhookrelay.com/v1/webhooks/821024d7-12a0-4b41-99f2-71fcc2906989" }'
註冊完以後,我們應該很快看到日誌資訊:
在網頁終端裡應該會看到應用發來的請求:
總結
總的說,webhook 和普通的 API 請求是一樣的,都是事件,都是為了在系統間共享資訊。API 輪詢在之前可能是一個比較好的解決方案,但是如果有過多的使用者採用這種方式,可能給伺服器帶來很大的負擔甚至導致當機。
要成功實現一個 webhook 需要考慮以下幾點:
- 使用者應該可以指定 webhook 的地址
- 大多數系統只允許連線到一個 webhook, 你可能需要允許連結到多個
- 如果請求的返回碼大於 500, 則重新請求。可能有人認為當請求返回的是 4 開頭的值時,才應該重新請求,因為這說明剛才的請求是無效的
如果對 webhook 感興趣,可以看一下我們寫的一些 例子 ,從這些例子裡你可以學會如何接收本地或者區域網 webhook 傳送的資訊。總之 webhook 非常靈活。
2018 年 7 月 13 號發表於 webhookrelay.com