跳至主要内容

处理 Webhook 投递

了解如何编写代码来监听并响应 webhook 投递。

简介

当您创建 webhook 时,需要指定一个 URL 并订阅事件类型。当您订阅的事件发生时,GitHub 会向您指定的 URL 发送包含该事件数据的 HTTP 请求。如果您的服务器已在该 URL 上监听 webhook 投递,则在收到投递时可以采取相应操作。

本文介绍如何编写代码,使您的服务器能够监听并响应 webhook 投递。您将使用自己的电脑或 Codespace 作为本地服务器来测试代码。

设置

为了在本地测试 webhook,您可以使用 webhook 代理 URL 将 GitHub 的 webhook 转发到您的电脑或 Codespace。本文使用 smee.io 提供 webhook 代理 URL 并转发 webhook。

获取 Webhook 代理 URL

  1. 在浏览器中打开 https://smee.io/
  2. 点击 启动新通道
  3. 复制 “Webhook Proxy URL” 下的完整 URL。您将在后续的设置步骤中使用此 URL。

转发 webhook

  1. 如果您还未安装 smee-client,请在终端中运行以下命令

    Shell
    npm install --global smee-client
    
  2. 要接收来自 smee.io 的转发 webhook,请在终端中运行以下命令。将 WEBHOOK_PROXY_URL 替换为您之前获取的 webhook 代理 URL。

    Shell
    smee --url WEBHOOK_PROXY_URL --path /webhook --port 3000
    

    您应该会看到类似如下的输出,其中 WEBHOOK_PROXY_URL 为您的 webhook 代理 URL

    Shell
    Forwarding WEBHOOK_PROXY_URL to http://127.0.0.1:3000/webhook
    Connected WEBHOOK_PROXY_URL
    

    请注意路径为 /webhook,端口为 3000。稍后在编写处理 webhook 投递的代码时将会使用这些值。

  3. 在测试 webhook 时请保持该进程运行。若要停止转发 webhook,输入 Ctrl+C

创建 webhook

  1. 使用以下设置创建 webhook。更多信息请参阅 创建 webhook

    • URL 请使用之前获取的 webhook 代理 URL。
    • 如果可以选择内容类型,请选择 JSON。

编写代码处理 webhook 投递

为了处理 webhook 投递,您需要编写能够

  • 初始化服务器以监听对 webhook URL 的请求
  • 读取请求的 HTTP 头部和主体
  • 根据请求执行所需的操作

您可以使用服务器上能够运行的任何编程语言。

以下示例在收到 webhook 投递时会打印一条消息。不过,您可以修改代码以执行其他操作,例如向 GitHub API 发起请求或在 Slack 中发送消息。

Ruby 示例

此示例使用 Ruby gem Sinatra 定义路由并处理 HTTP 请求。更多信息请参阅 Sinatra README

Ruby 示例:安装依赖

要使用此示例,必须在 Ruby 项目中安装 sinatra gem。例如,您可以使用 Bundler 来完成。

  1. 如果尚未安装 Bundler,请在终端中运行以下命令

    Shell
    gem install bundler
    
  2. 如果您的应用尚未拥有 Gemfile,请在终端中运行以下命令

    Shell
    bundle init
    
  3. 如果您的应用尚未拥有 Gemfile.lock,请在终端中运行以下命令

    Shell
    bundle install
    
  4. 在终端中运行以下命令以安装 Sinatra gem

    Shell
    bundle add sinatra
    

Ruby 示例:编写代码

创建一个包含以下内容的 Ruby 文件。根据您的 webhook 订阅的事件类型以及 GitHub 在创建 webhook 时发送的 ping 事件修改代码。本示例处理 issuesping 事件。

Ruby
require 'sinatra'
require 'json'

这些是本代码的依赖项。您已经在前面安装了 sinatra gem。更多信息请参阅 Ruby 示例:安装依赖json 库是 Ruby 标准库,无需额外安装。

post '/webhook' do

/webhook 路由与您在 smee.io 转发中指定的路径相匹配。更多信息请参阅 转发 webhook

部署代码到服务器并更新 webhook URL 后,您应将此路径改为与 webhook URL 的路径部分相匹配。

  status 202

响应以指示投递已成功接收。您的服务器应在收到 webhook 投递后 10 秒内返回 2XX 响应。如果响应时间超过此限制,GitHub 将终止连接并将投递标记为失败。

  github_event = request.env['HTTP_X_GITHUB_EVENT']

检查 X-GitHub-Event 头部以了解发送的事件类型。Sinatra 会将 X-GitHub-Event 转换为 HTTP_X_GITHUB_EVENT

  if github_event == "issues"
    data = JSON.parse(request.body.read)
    action = data['action']
    if action == "opened"
      puts "An issue was opened with this title: #{data['issue']['title']}"
    elsif action == "closed"
      puts "An issue was closed by #{data['issue']['user']['login']}"
    else
      puts "Unhandled action for the issue event: #{action}"
    end
  elsif github_event == "ping"
    puts "GitHub sent the ping event"
  else
    puts "Unhandled event: #{github_event}"
  end
end

您应为 webhook 订阅的每种事件类型添加处理逻辑。例如,此代码处理 issuesping 事件。

如果某些事件包含 action 字段,请为您关心的每个操作添加相应逻辑。例如,此代码处理 issue 事件的 openedclosed 操作。

有关每种事件类型的可用数据的更多信息,请参阅 Webhook 事件和负载

# These are the dependencies for this code. You installed the `sinatra` gem earlier. For more information, see [Ruby example: Install dependencies](#ruby-example-install-dependencies). The `json` library is a standard Ruby library, so you don't need to install it.
require 'sinatra'
require 'json'

# The `/webhook` route matches the path that you specified for the smee.io forwarding. For more information, see [Forward webhooks](#forward-webhooks).
#
# Once you deploy your code to a server and update your webhook URL, you should change this to match the path portion of the URL for your webhook.
post '/webhook' do

  # Respond to indicate that the delivery was successfully received.
  # Your server should respond with a 2XX response within 10 seconds of receiving a webhook delivery. If your server takes longer than that to respond, then GitHub terminates the connection and considers the delivery a failure.
  status 202

  # Check the `X-GitHub-Event` header to learn what event type was sent.
  # Sinatra changes `X-GitHub-Event` to `HTTP_X_GITHUB_EVENT`.
  github_event = request.env['HTTP_X_GITHUB_EVENT']

  # You should add logic to handle each event type that your webhook is subscribed to.
  # For example, this code handles the `issues` and `ping` events.
  #
  # If any events have an `action` field, you should also add logic to handle each action that you are interested in.
  # For example, this code handles the `opened` and `closed` actions for the `issue` event.
  #
  # For more information about the data that you can expect for each event type, see [AUTOTITLE](/webhooks/webhook-events-and-payloads).
  if github_event == "issues"
    data = JSON.parse(request.body.read)
    action = data['action']
    if action == "opened"
      puts "An issue was opened with this title: #{data['issue']['title']}"
    elsif action == "closed"
      puts "An issue was closed by #{data['issue']['user']['login']}"
    else
      puts "Unhandled action for the issue event: #{action}"
    end
  elsif github_event == "ping"
    puts "GitHub sent the ping event"
  else
    puts "Unhandled event: #{github_event}"
  end
end

Ruby 示例:测试代码

要测试 webhook,您可以使用电脑或 Codespace 充当本地服务器。如在此过程中遇到问题,请参阅 故障排除

  1. 确保您正在转发 webhook。如果不再转发,请重新执行 转发 webhook 中的步骤。

  2. 在另一个终端窗口中运行以下命令,在电脑或 Codespace 上启动本地服务器。将 FILE_PATH 替换为前一节代码文件的路径。请注意 PORT=3000 与前一步中为 webhook 转发指定的端口相匹配。

    Shell
    PORT=3000 ruby FILE_NAME
    

    您应看到类似 “Sinatra has taken the stage on 3000” 的输出。

  3. 触发您的 webhook。例如,若您创建了订阅 issues 事件的仓库 webhook,则在仓库中打开一个 issue。您也可以重新投递先前的 webhook 投递。更多信息请参阅 重新投递 webhook

  4. 在 smee.io 上访问您的 webhook 代理 URL。您应看到与刚才触发或重新投递的事件相对应的事件记录,这表明 GitHub 已成功将 webhook 投递发送到您指定的 payload URL。

  5. 在运行 smee --url WEBHOOK_PROXY_URL --path /webhook --port 3000 的终端窗口中,您应看到类似 POST http://127.0.0.1:3000/webhook - 202 的输出。这表明 smee 已成功将 webhook 转发到本地服务器。

  6. 在运行 PORT=3000 ruby FILE_NAME 的终端窗口中,您应看到与发送的事件相对应的消息。例如,使用上面的示例代码并重新投递 ping 事件时,应看到 “GitHub sent the ping event”。您可能还会看到 Sinatra 自动打印的其他行。

  7. 在两个终端窗口中,输入 Ctrl+C 停止本地服务器并停止监听转发的 webhook。

现在您已在本地测试完代码,可以对代码进行修改,以便在生产环境中使用 webhook。更多信息请参阅 后续步骤。如果测试代码时遇到困难,请尝试 故障排除 中的步骤。

JavaScript 示例

此示例使用 Node.js 和 Express 库定义路由并处理 HTTP 请求。更多信息请参阅 expressjs.com

若想查看使用 GitHub Octokit.js SDK 的示例,请参阅 构建响应 webhook 事件的 GitHub App

此示例要求您的电脑或 Codespace 运行 Node.js 12 以上版本和 npm 6.12.0 以上版本。更多信息请参阅 Node.js

JavaScript 示例:安装依赖

要使用此示例,必须在 Node.js 项目中安装 express 库。例如

Shell
npm install express

JavaScript 示例:编写代码

创建一个包含以下内容的 JavaScript 文件。根据您的 webhook 订阅的事件类型以及 GitHub 在创建 webhook 时发送的 ping 事件修改代码。本示例处理 issuesping 事件。

JavaScript
const express = require('express');

您已在前面安装了 express 库。更多信息请参阅 JavaScript 示例:安装依赖

const app = express();

这会初始化一个新的 Express 应用。

app.post('/webhook', express.json({type: 'application/json'}), (request, response) => {

这会在 /webhook 路径上定义一个 POST 路由。该路径与您在 smee.io 转发时指定的路径相匹配。更多信息请参阅 转发 webhook

部署代码到服务器并更新 webhook URL 后,您应将此路径改为与 webhook URL 的路径部分相匹配。

  response.status(202).send('Accepted');

响应以指示投递已成功接收。您的服务器应在收到 webhook 投递后 10 秒内返回 2XX 响应。如果响应时间超过此限制,GitHub 将终止连接并将投递标记为失败。

  const githubEvent = request.headers['x-github-event'];

检查 x-github-event 头部以了解发送的事件类型。

  if (githubEvent === 'issues') {
    const data = request.body;
    const action = data.action;
    if (action === 'opened') {
      console.log(`An issue was opened with this title: ${data.issue.title}`);
    } else if (action === 'closed') {
      console.log(`An issue was closed by ${data.issue.user.login}`);
    } else {
      console.log(`Unhandled action for the issue event: ${action}`);
    }
  } else if (githubEvent === 'ping') {
    console.log('GitHub sent the ping event');
  } else {
    console.log(`Unhandled event: ${githubEvent}`);
  }
});

您应为 webhook 订阅的每种事件类型添加处理逻辑。例如,此代码处理 issuesping 事件。

如果某些事件包含 action 字段,请为您关心的每个操作添加相应逻辑。例如,此代码处理 issue 事件的 openedclosed 操作。

有关每种事件类型的可用数据的更多信息,请参阅 Webhook 事件和负载

const port = 3000;

这会定义服务器应监听的端口。3000 与您为 webhook 转发指定的端口相匹配。更多信息请参阅 转发 webhook

部署代码到服务器后,您应将此端口改为服务器实际监听的端口。

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

这会启动服务器并让其在指定端口上监听。

// You installed the `express` library earlier. For more information, see [JavaScript example: Install dependencies](#javascript-example-install-dependencies).
const express = require('express');

// This initializes a new Express application.
const app = express();

// This defines a POST route at the `/webhook` path. This path matches the path that you specified for the smee.io forwarding. For more information, see [Forward webhooks](#forward-webhooks).
//
// Once you deploy your code to a server and update your webhook URL, you should change this to match the path portion of the URL for your webhook.
app.post('/webhook', express.json({type: 'application/json'}), (request, response) => {

  // Respond to indicate that the delivery was successfully received.
  // Your server should respond with a 2XX response within 10 seconds of receiving a webhook delivery. If your server takes longer than that to respond, then GitHub terminates the connection and considers the delivery a failure.
  response.status(202).send('Accepted');

  // Check the `x-github-event` header to learn what event type was sent.
  const githubEvent = request.headers['x-github-event'];

  // You should add logic to handle each event type that your webhook is subscribed to.
  // For example, this code handles the `issues` and `ping` events.
  //
  // If any events have an `action` field, you should also add logic to handle each action that you are interested in.
  // For example, this code handles the `opened` and `closed` actions for the `issue` event.
  //
  // For more information about the data that you can expect for each event type, see [AUTOTITLE](/webhooks/webhook-events-and-payloads).
  if (githubEvent === 'issues') {
    const data = request.body;
    const action = data.action;
    if (action === 'opened') {
      console.log(`An issue was opened with this title: ${data.issue.title}`);
    } else if (action === 'closed') {
      console.log(`An issue was closed by ${data.issue.user.login}`);
    } else {
      console.log(`Unhandled action for the issue event: ${action}`);
    }
  } else if (githubEvent === 'ping') {
    console.log('GitHub sent the ping event');
  } else {
    console.log(`Unhandled event: ${githubEvent}`);
  }
});

// This defines the port where your server should listen.
// 3000 matches the port that you specified for webhook forwarding. For more information, see [Forward webhooks](#forward-webhooks).
//
// Once you deploy your code to a server, you should change this to match the port where your server is listening.
const port = 3000;

// This starts the server and tells it to listen at the specified port.
app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

JavaScript 示例:测试代码

要测试 webhook,您可以使用电脑或 Codespace 充当本地服务器。如在此过程中遇到问题,请参阅 故障排除

  1. 确保您正在转发 webhook。如果不再转发,请重新执行 转发 webhook 中的步骤。

  2. 在另一个终端窗口中运行以下命令,在电脑或 Codespace 上启动本地服务器。将 FILE_PATH 替换为前一节代码文件的路径。

    Shell
    node FILE_NAME
    

    您应看到输出 Server is running on port 3000

  3. 触发您的 webhook。例如,若您创建了订阅 issues 事件的仓库 webhook,则在仓库中打开一个 issue。您也可以重新投递先前的 webhook 投递。更多信息请参阅 重新投递 webhook

  4. 在 smee.io 上访问您的 webhook 代理 URL。您应看到与刚才触发或重新投递的事件相对应的事件记录,这表明 GitHub 已成功将 webhook 投递发送到您指定的 payload URL。

  5. 在运行 smee --url WEBHOOK_PROXY_URL --path /webhook --port 3000 的终端窗口中,您应看到类似 POST http://127.0.0.1:3000/webhook - 202 的输出。这表明 smee 已成功将 webhook 转发到本地服务器。

  6. 在运行 node FILE_NAME 的终端窗口中,您应看到与发送的事件相对应的消息。例如,使用上述示例代码并重新投递 ping 事件时,应看到 “GitHub sent the ping event”。

  7. 在两个终端窗口中,输入 Ctrl+C 停止本地服务器并停止监听转发的 webhook。

现在您已在本地测试完代码,可以对代码进行修改,以便在生产环境中使用 webhook。更多信息请参阅 后续步骤。如果测试代码时遇到困难,请尝试 故障排除 中的步骤。

故障排除

如果没有看到测试步骤中描述的预期结果,请尝试以下操作

  • 确保您的 webhook 正在使用 webhook 代理 URL(Smee.io URL)。有关 webhook 代理 URL 的更多信息,请参阅 获取 webhook 代理 URL。有关 webhook 设置的更多信息,请参阅 创建 webhook
  • 如果可以选择内容类型,请确保 webhook 使用 JSON 内容类型。有关 webhook 设置的更多信息,请参阅 创建 webhook
  • 确保 smee 客户端和本地服务器都在运行。您需要在两个不同的终端窗口中分别启动这两个进程。
  • 确保您的服务器监听的端口与 smee.io 转发 webhook 时使用的端口相同。本文所有示例均使用 3000 端口。
  • 确保 smee.io 转发 webhook 的路径与代码中定义的路由相匹配。本文所有示例均使用 /webhooks 路径。
  • 检查运行 smee 客户端和本地服务器的终端窗口中是否有错误信息。
  • 在 GitHub 上检查是否触发了 webhook 投递。更多信息请参阅 查看 webhook 投递
  • 在 smee.io 上检查您的 webhook 代理 URL。您应看到与刚才触发或重新投递的事件相对应的记录,这表明 GitHub 已成功将 webhook 投递发送到您指定的 payload URL。

后续步骤

本文演示了如何编写代码来处理 webhook 投递,并展示了如何通过使用电脑或 Codespace 作为本地服务器、并通过 smee.io 将 GitHub 的 webhook 投递转发到本地服务器来测试代码。完成测试后,您可能需要修改代码并将其部署到服务器上。

修改代码

本文提供的基础示例在收到 webhook 投递时仅打印一条消息。您可能希望修改代码以执行其它操作。例如,您可以将代码改为

  • 向 GitHub API 发起请求
  • 在 Slack 上发送消息
  • 记录事件
  • 更新外部项目管理工具

验证投递来自 GitHub

在处理 webhook 投递的代码中,您应在进一步处理投递之前验证其确实来自 GitHub。更多信息请参阅 验证 webhook 投递

将代码部署到服务器

本文演示了在开发代码时如何将电脑或 Codespace 用作服务器。代码准备好用于生产后,您应将其部署到专用服务器。

部署时,可能需要更新代码以匹配服务器监听的主机名和端口。

更新 webhook URL

当您拥有可以接收来自 GitHub 的 webhook 流量的服务器后,请在 webhook 设置中更新 URL。您可能需要更新代码中处理的路由,以匹配新 URL 的路径部分。例如,如果新的 webhook URL 为 https://example.com/github-webhooks,则应将本示例中的路由从 /webhooks 改为 /github-webhooks

生产环境中不应使用 smee.io 转发 webhook。

遵循最佳实践

您应当尽量遵循 webhook 的最佳实践。更多信息请参阅 使用 webhook 的最佳实践

延伸阅读

© . This site is unofficial and not affiliated with GitHub, Inc.