基本概念

Webhooks是一个api概念,是微服务api的使用范式之一,也被成为反向api,即:前端不主动发送请求,完全由后端推送。我们能用事件描述的事物越多,Webhook的作用范围也就越大。Webhook作为一个轻量的事件处理应用,正变得越来越有用。简单来说就是一种反向API机制,类似于触发器的一样。
场景模拟:
在传统的web server设计中,我们项目A想要获取项目B的数据,通常项目B需要提供一个API,然后项目A去请求项目B的API,从而获得数据,这样的过程我们称之为“拉”数据。(通过Webhook机制,对客户端-服务端的模式进行了逆转
需求变更:
新增了一个需求,项目A需要实时获取项目B的最新数据。在传统做法中,需要不停的向项目B轮询,以便获取最新数据,这样的效率和性能非常低下。
Webhook机制:
项目A提供一个hook url,每次项目B创建新数据时,便会向项目A的hook地址进行请求,项目A收到项目B的请求,然后对数据进行处理。
点击查看【processon】
简单来说,WebHook就是一个接收HTTP POST(或GET,PUT,DELETE)的URL。一个实现了WebHook的API提供商就是在当事件发生的时候会向这个配置好的URL发送一条信息。与请求-响应式不同,使用WebHooks,你可以实时接受到变化。
  这又是一种对客户机-服务器模式的逆转,在传统方法中,客户端从服务器请求数据,然后服务器提供给客户端数据(客户端是在拉数据)。在Webhook范式下,服务器更新所需提供的资源,然后自动将其作为更新发送到客户端(服务器是在推数据),客户端不是请求者,而是被动接收方。这种控制关系的反转可以用来促进许多原本需要在远程服务器上进行更复杂的请求和不断的轮询的通信请求。通过简单地接收资源而不是直接发送请求,我们可以更新远程代码库,轻松地分配资源,甚至将其集成到现有系统中来根据API的需要来更新端点和相关数据,唯一的缺点是初始建立困难。

基本使用

使用Webhook就需要为对应的服务端设计一个hook url,用于接收服务端的请求。例如:

  1. http://www.abcd.com/api/video/hook

通常Webhook请求过来的数据格式为XML和json两种,在现代Web应用中,都能很好的解析和对这两种数据进行交互。

应用场景

Webhook通常应用于异步编程中,如:高安全性要求的支付、微信登录(OAuth)、资源同步、资源创建与更新、耗时较长。

  • 场景1

你需要向视频处理服务器上传一个视频,需要获得视频处理后的结果,然而视频处理服务器上视频很多,在排队进行处理,不能立刻获取到视频的处理结果,此时你可以设计一个hook url,当视频处理完成后,视频处理服务器自动向你的hook url发送请求,告诉你视频已经处理完毕。

  • 场景2

你的好友发了一条朋友圈,后端将这条消息推送给所有其他好友的客户端,就是Webhooks的典型场景。另外,对于微信公众号开发者,微信要求我们为公众号设定一个回调地址,当有用户向公众号发送消息时,这个回调地址所指的服务器回收到来自微信的消息。这也是WebHook的一种实现。

  • 场景3

更新客户端,在资源新建或者更新时提供更新的、指定的数据。

  • 场景4

git代码远程感知更新。我们可以通过制定Webhook来检测我们的gitlab.com (或者GitHub.com)上的各种事件。最常见的莫过于push事件。如果你设置了一个监测push时间的Webhook,那么每当你的这个项目有了任何提交,这个Webhook都会被触发,这时gitlab(或github)就会发送一个HTTP POST请求到你配置好的地址。**Webhook基础入门 - 图1

  • 场景5

告警通知:在AlertManager或Grafana端绑定hook url,当指标达到告警阈值时,触发Webhook调用。
Webhook基础入门 - 图2

安全问题

由于Webhook会向公网上的hook url发送数据,这就意味着某些不好心的人可能会找到这个url,从而进行发送错误的数据,但是我们可以通过以下一些技术手段来解决这个问题:

  1. 增加token机制。
  2. 增加auth认证。
  3. 只接收对应服务端domain或IP请求。
  4. 数据签名。

    使用说明

    使用Webhook机制时需要特别注意以下问题:

  5. 当服务提供者通过Webhook将数据发送到服务端后,就不会再去关注这些数据。假设服务端此时出现了崩溃,或者无法请求成功等原因,就需要主动去尝试请求数据。

  6. Webhook会发出大量的请求,可能会造成应用阻塞,在此需要确保你的应用能够处理好这些请求。

    简单示例(JAVA版)

  • httpclient

依赖引入:

  1. <dependency>
  2. <groupId>org.apache.httpcomponents</groupId>
  3. <artifactId>httpclient</artifactId>
  4. <version>4.5.3</version>
  5. </dependency>

代码:

  1. import org.apache.http.HttpResponse;
  2. import org.apache.http.HttpStatus;
  3. import org.apache.http.client.HttpClient;
  4. import org.apache.http.client.methods.HttpPost;
  5. import org.apache.http.entity.StringEntity;
  6. import org.apache.http.impl.client.HttpClients;
  7. import org.apache.http.util.EntityUtils;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import java.io.IOException;
  11. /**
  12. * @Description 发送告警消息到钉钉(HttpClient)
  13. */
  14. public class SendMsgToDingtalkHttpClient {
  15. private static final Logger log = LoggerFactory.getLogger(SendMsgToDingtalkHttpClient.class);
  16. public static final String WEBHOOK_TOKEN = "https://oapi.dingtalk.com/robot/send?access_token=31f7005ecd6ec1775d085b4cbdc18a4639d999063a9ff3ead38d245b7e75a8f6";
  17. public static void main(String args[]) throws IOException {
  18. // 配置了Webhook机器人关键字“告警测试”,则消息中需要携带,否则无法发出
  19. // 更多配置请参考:https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq
  20. HttpClient httpclient = HttpClients.createDefault();
  21. HttpPost httppost = new HttpPost(WEBHOOK_TOKEN);
  22. httppost.addHeader("Content-Type", "application/json; charset=utf-8");
  23. // 构建一个json格式字符串textMsg,其内容是接收方需要的参数和消息内容
  24. String textMsg = "{\"msgtype\":\"text\",\"text\":{\"content\":\"告警测试:This is a warning test.\"},\"at\":{\"atMobiles\":[\"13632608950\"],\"isAtAll\":false}}";
  25. StringEntity se = new StringEntity(textMsg, "utf-8");
  26. httppost.setEntity(se);
  27. HttpResponse response = httpclient.execute(httppost);
  28. if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
  29. String result = EntityUtils.toString(response.getEntity(), "utf-8");
  30. log.info(result);
  31. }else{
  32. log.error("error code:"+response.getStatusLine().getStatusCode());
  33. }
  34. }
  35. }
  • okhttp

依赖引入:

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>fastjson</artifactId>
  4. <version>1.2.29</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.squareup.okhttp</groupId>
  8. <artifactId>okhttp</artifactId>
  9. <version>2.7.5</version>
  10. </dependency>

代码:

  1. import com.alibaba.fastjson.JSONObject;
  2. import com.squareup.okhttp.MediaType;
  3. import com.squareup.okhttp.OkHttpClient;
  4. import com.squareup.okhttp.Request;
  5. import com.squareup.okhttp.RequestBody;
  6. import com.squareup.okhttp.Response;
  7. import org.slf4j.Logger;
  8. import org.slf4j.LoggerFactory;
  9. /**
  10. * @Description 发送告警消息到钉钉(OkHttp)
  11. */
  12. public class SendMsgToDingtalkOkHttp {
  13. private static final Logger log = LoggerFactory.getLogger(SendMsgToDingtalkOkHttp.class);
  14. private static final MediaType JSON = MediaType.parse("application/json;charset=utf-8");
  15. public static final String ALERT_MSG_ROBOT_ID = "https://oapi.dingtalk.com/robot/send?access_token=${钉钉机器人}";
  16. public static void main(String[] args) {
  17. // 配置了Webhook机器人关键字“告警测试”,则消息中需要携带,否则无法发出
  18. // 更多配置请参考:https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq
  19. String msg=String.format("告警测试:%s","This is a warning test.");
  20. if(sendMsg(ALERT_MSG_ROBOT_ID,msg)){
  21. log.info("发送消息到钉钉成功!");
  22. }
  23. }
  24. /**
  25. * 发送机器人消息
  26. * @param robotId
  27. * @param msg
  28. * @return
  29. */
  30. public static boolean sendMsg(String robotId, String msg) {
  31. JSONObject text = new JSONObject();
  32. text.put("content", msg);
  33. JSONObject object = new JSONObject();
  34. object.put("msgtype", "text");
  35. object.put("text", text);
  36. OkHttpClient okHttpClient = new OkHttpClient();
  37. RequestBody requestBody = RequestBody.create(JSON, object.toJSONString());
  38. Request request = new Request.Builder().url(robotId).post(requestBody).build();
  39. try {
  40. Response response = okHttpClient.newCall(request).execute();
  41. return response.isSuccessful();
  42. } catch (Exception e) {
  43. log.error(String.format("发送Webhook[%s,%s]失败:"+e.getMessage(),robotId,msg));
  44. return false;
  45. }
  46. }
  47. }
  • dingtalk-service-sdk

Maven仓库:

  1. <repositories>
  2. <repository>
  3. <id>sonatype-nexus-staging</id>
  4. <name>Sonatype Nexus Staging</name>
  5. <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
  6. <releases>
  7. <enabled>true</enabled>
  8. </releases>
  9. <snapshots>
  10. <enabled>true</enabled>
  11. </snapshots>
  12. </repository>
  13. </repositories>

依赖引入:

  1. <dependency>
  2. <groupId>com.aliyun</groupId>
  3. <artifactId>alibaba-dingtalk-service-sdk</artifactId>
  4. <version>1.0.1</version>
  5. </dependency>

代码:

  1. import com.dingtalk.api.DefaultDingTalkClient;
  2. import com.dingtalk.api.DingTalkClient;
  3. import com.dingtalk.api.request.OapiRobotSendRequest;
  4. import com.dingtalk.api.response.OapiRobotSendResponse;
  5. import com.squareup.okhttp.*;
  6. import com.taobao.api.ApiException;
  7. import org.slf4j.Logger;
  8. import org.slf4j.LoggerFactory;
  9. /**
  10. * @Description 发送告警消息到钉钉(SDK)
  11. */
  12. public class SendMsgToDingtalkSDK {
  13. private static final Logger log = LoggerFactory.getLogger(SendMsgToDingtalkSDK.class);
  14. private static final MediaType JSON = MediaType.parse("application/json;charset=utf-8");
  15. public static final String ALERT_MSG_ROBOT_ID = "https://oapi.dingtalk.com/robot/send?access_token=31f7005ecd6ec1775d085b4cbdc18a4639d999063a9ff3ead38d245b7e75a8f6";
  16. public static void main(String[] args) throws ApiException {
  17. // 配置了Webhook机器人关键字“告警测试”,则消息中需要携带,否则无法发出
  18. // 更多配置请参考:https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq
  19. String msg="#### xxx大数据平台 @13632608950\n" +
  20. "> 内存使用率:84%\n\n" +
  21. "> ![screenshot](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1595400380087&di=1d5656aa2d536eec8c11a827e43610a1&imgtype=0&src=http%3A%2F%2Fimg0.imgtn.bdimg.com%2Fit%2Fu%3D684369095%2C514272752%26fm%3D214%26gp%3D0.jpg)\n" +
  22. "> ###### 10点20分发布 [告警测试](https://www.seniverse.com/) \n";
  23. if(sendMsg(ALERT_MSG_ROBOT_ID,msg)){
  24. log.info("发送消息到钉钉成功!");
  25. }
  26. }
  27. /**
  28. * 发送机器人消息
  29. * @param robotId
  30. * @param msg
  31. * @return
  32. */
  33. public static boolean sendMsg(String robotId, String msg) throws ApiException {
  34. DingTalkClient client = new DefaultDingTalkClient(robotId);
  35. OapiRobotSendRequest request = new OapiRobotSendRequest();
  36. request.setMsgtype("markdown");
  37. OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown();
  38. markdown.setTitle("告警测试");
  39. markdown.setText(msg);
  40. request.setMarkdown(markdown);
  41. OapiRobotSendResponse response = client.execute(request);
  42. return response.isSuccess();
  43. }
  44. }