设计WebHook
Posted 刘贤松handler
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计WebHook相关的知识,希望对你有一定的参考价值。
WebHook 的设计
1、接口设计
WebHook 需要具备良好的自解性,也就是调用 Client 接口时,将自己完整的信息传达到客户端
// POST
"source":
"platform": "A Niubility Platform",
"other": "other infomation"
,
"target":
"name": "Barret Lee",
"version": "1.0.0",
"other": "other infomation"
,
"data":
//...
,
"needCallback": true,
"serverTime": "2016-12-26 11:55:45"
在接口中需要详细说明:
- 数据源从哪里来(source)
- 数据需要穿给谁(target)
- 传递那些数据(data)
- 是否需要对方发送回执(needCallback),以及其他信息。
这样做的目的是为了避免发送出现错误,比如发错了对象;即便出错也方便通过日志记录排查问题。
2、多 Hook 设计
URI | 备注 | 操作 |
---|---|---|
http://example.com/receiveHook | 通知小李修 bug | 删除 / 编辑 |
http://example2.com/receiveHook | 通知小王修 bug | 删除 / 编辑 |
WebHook 的设计一定要支持多 Hook,你永远都不知道下一个系统对接需求会在什么时候到来。
对于复杂的 Hook 设计,表格中可能还有:是否需要回执、是否停用、安全 Token、数据配置等项。
3、安全性设计
这里的安全性是为 Client 考虑的,Client 可能对 refer 或者 origin 做了限制,但这远远不够。当用户在 Server 端注册 WebHook 时,就应该开始考虑 Hook 的安全性了:
// POST
response.setHeader("x-webhook-sign", SHA1(webhook));
Client 在接收到 WebHook 时需要验证 x-webhook-sign
字段,如果不正确应该向服务器响应的错误码(或许此时服务器收到错误码后应该停用这个 Hook)。
4、retry 机制
极有可能因为 Client 的不稳定,导致 Hook 调用失败,此时可以考虑多次尝试:
GitHub - avast/retry-go: Simple golang library for retry mechanism
package retry_test
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"
"github.com/avast/retry-go/v4"
"github.com/stretchr/testify/assert"
)
// RetriableError is a custom error that contains a positive duration for the next retry
type RetriableError struct
Err error
RetryAfter time.Duration
// Error returns error message and a Retry-After duration
func (e *RetriableError) Error() string
return fmt.Sprintf("%s (retry after %v)", e.Err.Error(), e.RetryAfter)
var _ error = (*RetriableError)(nil)
// TestCustomRetryFunction shows how to use a custom retry function
func TestCustomRetryFunction(t *testing.T)
attempts := 5 // server succeeds after 5 attempts
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)
if attempts > 0
// inform the client to retry after one second using standard
// HTTP 429 status code with Retry-After header in seconds
w.Header().Add("Retry-After", "1")
w.WriteHeader(http.StatusTooManyRequests)
w.Write([]byte("Server limit reached"))
attempts--
return
w.WriteHeader(http.StatusOK)
w.Write([]byte("hello"))
))
defer ts.Close()
var body []byte
err := retry.Do(
func() error
resp, err := http.Get(ts.URL)
if err == nil
defer func()
if err := resp.Body.Close(); err != nil
panic(err)
()
body, err = ioutil.ReadAll(resp.Body)
if resp.StatusCode != 200
err = fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
if resp.StatusCode == http.StatusTooManyRequests
// check Retry-After header if it contains seconds to wait for the next retry
if retryAfter, e := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 32); e == nil
// the server returns 0 to inform that the operation cannot be retried
if retryAfter <= 0
return retry.Unrecoverable(err)
return &RetriableError
Err: err,
RetryAfter: time.Duration(retryAfter) * time.Second,
// A real implementation should also try to http.Parse the retryAfter response header
// to conform with HTTP specification. Herein we know here that we return only seconds.
return err
,
retry.DelayType(func(n uint, err error, config *retry.Config) time.Duration
fmt.Println("Server fails with: " + err.Error())
if retriable, ok := err.(*RetriableError); ok
fmt.Printf("Client follows server recommendation to retry after %v\\n", retriable.RetryAfter)
return retriable.RetryAfter
// apply a default exponential back off strategy
return retry.BackOffDelay(n, err, config)
),
)
fmt.Println("Server responds with: " + string(body))
assert.NoError(t, err)
assert.Equal(t, "hello", string(body))
以上是关于设计WebHook的主要内容,如果未能解决你的问题,请参考以下文章