項(xiàng)目需要,百度了一下Github Webhooks,內(nèi)容基本雷同,也沒有找到我想要的如下兩個(gè)功能說(shuō)明:
- 提交時(shí)做了哪些變動(dòng)?
- secret的使用
查看原文
罷了,想拿來(lái)即用的偷懶之路走不通,只能老老實(shí)實(shí)啃官方文檔了。
Webhooks干嘛用的?
Github Webhooks提供了一堆事件,這些事件在用戶特定的操作下會(huì)被觸發(fā),比如創(chuàng)建分支(Branch)、庫(kù)被fork、項(xiàng)目被star、用戶push了代碼等等。
我們可以自己寫一個(gè)服務(wù),將服務(wù)的URL交給Webhooks,當(dāng)上述事件被觸發(fā)時(shí),Webhook會(huì)向這個(gè)服務(wù)發(fā)送一個(gè)POST請(qǐng)求,請(qǐng)求中附帶著該事件相關(guān)的詳細(xì)描述信息(即Payload)。
這樣,我們就可以在自己服務(wù)中知道Github的什么事件被觸發(fā)了,事件的內(nèi)容是什么?據(jù)此我們就可以干一些自己想干的事了。能干什么呢?官方說(shuō)You're only limited by your imagination,就是說(shuō)想干什么都行,就看你的想像力夠不夠 :)
Webhooks配置
進(jìn)入要hook的庫(kù)-->Settings-->Webhooks-->Add Webhook

Add Webhook界面長(zhǎng)這樣:

Payload URL
就是我們用來(lái)接收事件詳情(Payload)的服務(wù)URL-
Content type
即Webhooks用什么樣的數(shù)據(jù)格式給我們發(fā)送Payload,Webhooks支持以下兩種類型的數(shù)據(jù)- application/json
- application/x-www-form-urlencoded
Secret
這里可以提供一個(gè)加密Key,Webhooks會(huì)以此key生成Payload的一個(gè)數(shù)字簽名隨POST請(qǐng)求發(fā)送給你,你可以通過(guò)此簽名驗(yàn)證數(shù)據(jù)的合法性。如何驗(yàn)證,見后文所附代碼。-
Webhook event
Webhooks事件的選擇項(xiàng):- Just the push event
僅hook Push事件 - Send me everything
hook所有事件 - Let me select indiidual event
讓用戶自己選擇hook什么事件
默認(rèn)是
Just the push event.,我們不改了,用它得了。 - Just the push event
Post了什么東東
在寫服務(wù)之前,我們先看看Webhooks給我們發(fā)送了什么。
-
Post Request Header
Post Request Header- Request URL
即前面配置中填寫的“Payload URL” - content-type
即前面配置中選擇的“Content type” - X-Hub-Signature
是對(duì)Payload計(jì)算得出的簽名。當(dāng)我們?cè)谇懊娴呐渲弥休斎肓恕癝ecret”后,Header中才會(huì)出現(xiàn)此項(xiàng)。官方文檔對(duì)Secret作了詳細(xì)說(shuō)明,后面我們也會(huì)在代碼中實(shí)現(xiàn)對(duì)它的校驗(yàn)。
- Request URL
-
Post Request Body
Post Request Body這是觸發(fā)Push事件時(shí)Webhooks發(fā)給我們的Payload,藍(lán)色框中的內(nèi)容就是我期望得到的庫(kù)文件的變動(dòng)信息。很直白,不再解釋。有了數(shù)據(jù)結(jié)構(gòu),拿到想要的數(shù)據(jù)就不是難事了,所以后面代碼中也不會(huì)有這部分內(nèi)容了。
我們的服務(wù)怎么寫?
本文中使用Gin實(shí)現(xiàn)所需功能,代碼僅用于演示,請(qǐng)輕拍 :)
func Init(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {
// Middlewares.
g.Use(mw...)
// 404 處理
g.NoRoute(func(c *gin.Context) {
c.String(http.StatusNotFound, "The incorrect API route.")
})
// 路由
sv := g.Group("/sv")
{
sv.POST("/gitpush", gitpush)
// 其它路由定義......
}
return g
}
// Github Webhooks Post請(qǐng)求處理函數(shù)
func gitpush(c *gin.Context) {
// 驗(yàn)證簽名
if matched, _ := verifySignature(c); !matched {
err := "Signatures didn't match!"
c.String(http.StatusForbidden, err)
fmt.Println(err)
return
}
fmt.Println("Signatures is match! go!")
// 你自己的業(yè)務(wù)邏輯......
c.String(http.StatusOK, "OK")
}
// 驗(yàn)證簽名
func verifySignature(c *gin.Context) (bool, error) {
payloadBody, err := c.GetRawData()
if err != nil {
return false, err
}
// 獲取請(qǐng)求頭中的簽名信息
hSignature := c.GetHeader("X-Hub-Signature")
// 計(jì)算Payload簽名
signature := hmacSha1(payloadBody)
return (hSignature == signature), nil
}
// hmac-sha1
func hmacSha1(payloadBody []byte) string {
h := hmac.New(sha1.New, []byte("password"))
h.Write(payloadBody)
return "sha1=" + hex.EncodeToString(h.Sum(nil))
}

