# 处理 Webhook 交付

了解如何编写代码来侦听和响应 Webhook 交付。

## 简介

创建 Webhook 时，要指定 URL 并订阅事件类型。 当您的 Webhook 所订阅的事件发生时，GitHub 会向您指定的 URL 发送一个 HTTP 请求，其中包含该事件的相关数据。 如果服务器设置为侦听该 URL 处的 Webhook 交付，则可在收到 Webhook 交付时采取措施。

本文旨在介绍如何编写代码让服务器侦听和响应 Webhook 交付。 你将使用你的计算机或 codespace 作为本地服务器来测试你的代码。

## 安装

为了在本地测试 Webhook，可以使用 Webhook 代理 URL 将来自 GitHub 的 Webhook 转发到你的计算机或 codespace。 本文使用 Smee.io 提供 Webhook 代理 URL 和转发 Webhook。

### 获取 Webhook 代理 URL

1. 在浏览器中，导航到  <https://smee.io/> 。
2. 单击**启动新频道**。
3. 复制“Webhook 代理 URL”下的完整 URL。 后续设置步骤会用到此 URL。

### 转发 Webhook

1. 如果尚未安装 [smee-client](https://www.npmjs.com/package/smee-client)，请在终端中运行以下命令：

   ```shell copy
   npm install --global smee-client
   ```

2. 若要通过 smee.io 接收转发的 Webhook，请在终端中运行以下命令。 将 `WEBHOOK_PROXY_URL` 替换为前面提到的 Webhook 代理 URL。

   ```shell copy
   smee --url WEBHOOK_PROXY_URL --path /webhook --port 3000
   ```

   应会看到如下所示的输出，其中 `WEBHOOK_PROXY_URL` 是 Webhook 代理 URL：

   ```shell copy
   Forwarding WEBHOOK_PROXY_URL to http://127.0.0.1:3000/webhook
   Connected WEBHOOK_PROXY_URL
   ```

   请注意，路径为 `/webhook`，端口为 `3000`。 这些值会在稍后编写代码来处理 Webhook 交付时用到。

3. 在测试 Webhook 时保持此运行状态。 如果要停止转发 Webhook，请输入 <kbd>Ctrl</kbd>+<kbd>C</kbd>。

### 创建 Webhook (网络钩子)

1. 使用以下设置创建 Webhook。 有关详细信息，请参阅“[创建网络钩子](/zh/enterprise-server@3.22/webhooks/using-webhooks/creating-webhooks)”。

   * 对于 URL，请使用先前指定的 Webhook 代理 URL。
   * 如果可以选择内容类型，请使用 JSON。

## 编写代码来处理 Webhook 交付

若要处理 Webhook 交付，需要编写具有以下功能的代码：

* 初始化服务器来侦听对 Webhook URL 的请求
* 从请求中读取 HTTP 头和正文
* 执行所需操作来响应请求

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

以下示例会在收到 Webhook 交付时打印消息。 但是，可以修改代码以执行其他操作，例如向 GitHub API 发出请求或发送 Slack 消息。

* [Ruby 示例](#ruby-example)
* [JavaScript 示例](#javascript-example)

### Ruby 示例

此示例使用 Ruby gem Sinatra 来定义路由并处理 HTTP 请求。 有关详细信息，请参阅 [Sinatra 自述文件](https://github.com/sinatra/sinatra#readme)。

#### Ruby 示例：安装依赖关系

若要使用此示例，必须在 Ruby 项目中安装 Sinatra gem。 例如，可以通过使用[Bundler](https://bundler.io/)来执行此操作：

1. 如果尚未安装捆绑程序，请在终端中运行以下命令：

   ```shell copy
   gem install bundler
   ```

2. 如果应用还没有 Gemfile，请在终端中运行以下命令：

   ```shell copy
   bundle init
   ```

3. 如果应用还没有 Gemfile.lock，请在终端中运行以下命令：

   ```shell copy
   bundle install
   ```

4. 在终端中运行以下命令来安装 Sinatra gem：

   ```shell copy
   bundle add sinatra
   ```

#### Ruby 示例：编写代码

创建具有以下内容的 Ruby 文件。 修改代码，以处理你的 Webhook 所订阅的事件类型，以及 `ping` 在你创建 Webhook 时发送的 GitHub 事件。 此示例处理 `issues` 和 `ping` 事件。

```ruby copy annotate
# 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 30 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 充当本地服务器。 如果在执行这些步骤时遇到问题，请参阅[疑难解答](#troubleshooting)。

1. 确保正在转发 Webhook。 如果您不再转发 webhooks，请再次按[转发 webhooks](#forward-webhooks)中的步骤进行操作。

2. 在单独的终端窗口中运行以下命令，在计算机上或 codespace 上启动本地服务器。 将 `FILE_PATH` 替换为存储前文代码的文件的路径。 请注意，`PORT=3000` 与在上一步中为 Webhook 转发指定的端口匹配。

   ```shell copy
   PORT=3000 ruby FILE_NAME
   ```

   应该会看到类似“Sinatra 已在 3000 上运行”的输出。

3. 触发你的 Webhook。 例如，如果创建了订阅 `issues` 事件的存储库 Webhook，可以在存储库中提出问题。 您还可以重新发送先前的 Webhook 传送。 有关详细信息，请参阅“[重新传递 Webhook](/zh/enterprise-server@3.22/webhooks/testing-and-troubleshooting-webhooks/redelivering-webhooks)”。

4. 访问 smee.io 上的 Webhook 代理 URL。 应该会看到与已触发或已重新交付事件对应的事件。 这表示 GitHub 已成功将 Webhook 投递到你指定的载荷 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 发送 ping 事件”。 还可以看到 Sinatra 自动打印的其他行。

7. 在这两个终端窗口中，输入 <kbd>Ctrl</kbd>+<kbd>C</kbd> 停止本地服务器并停止侦听转发的 Webhook。

现已在本地完成代码测试，可以根据情况进行更改，以便在生产环境中使用 Webhook。 有关详细信息，请参阅[后续步骤](#next-steps)。 如果在测试代码时遇到问题，请尝试“[疑难解答](#troubleshooting)”中的步骤。

### JavaScript 示例

此示例使用 Node.js 和 Express 库来定义路由并处理 HTTP 请求。 有关详细信息，请参阅 [expressjs.com](https://expressjs.com)。

有关使用 GitHub'Octokit.js SDK 的示例，请参阅 [构建响应 Webhook 事件的GitHub应用](/zh/enterprise-server@3.22/apps/creating-github-apps/writing-code-for-a-github-app/building-a-github-app-that-responds-to-webhook-events)。

本示例要求计算机或 codespace 运行 Node.js 版本 12 或更高版本和 npm 版本 6.12.0 或更高版本。 有关详细信息，请参阅 [Node.js](https://nodejs.org)。

#### JavaScript 示例：安装依赖关系

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

```shell copy
npm install express
```

#### JavaScript 示例：编写代码

创建具有以下内容的 JavaScript 文件。 修改代码，以处理你的 Webhook 所订阅的事件类型，以及 `ping` 在你创建 Webhook 时发送的 GitHub 事件。 此示例处理 `issues` 和 `ping` 事件。

```javascript copy annotate
// 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 30 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 充当本地服务器。 如果在执行这些步骤时遇到问题，请参阅[疑难解答](#troubleshooting)。

1. 确保正在转发 Webhook。 如果您不再转发 webhooks，请再次按[转发 webhooks](#forward-webhooks)中的步骤进行操作。

2. 在单独的终端窗口中运行以下命令，在计算机上或 codespace 上启动本地服务器。 将 `FILE_PATH` 替换为存储前文代码的文件的路径。

   ```shell copy
   node FILE_NAME
   ```

   应会看到输出显示 `Server is running on port 3000`。

3. 触发你的 Webhook。 例如，如果创建了订阅 `issues` 事件的存储库 Webhook，可以在存储库中提出问题。 您还可以重新发送先前的 Webhook 传送。 有关详细信息，请参阅“[重新传递 Webhook](/zh/enterprise-server@3.22/webhooks/testing-and-troubleshooting-webhooks/redelivering-webhooks)”。

4. 访问 smee.io 上的 Webhook 代理 URL。 应该会看到与已触发或已重新交付事件对应的事件。 这表示 GitHub 已成功将 Webhook 投递到你指定的载荷 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 发送 ping 事件”。

7. 在这两个终端窗口中，输入 <kbd>Ctrl</kbd>+<kbd>C</kbd> 停止本地服务器并停止侦听转发的 Webhook。

现已在本地完成代码测试，可以根据情况进行更改，以便在生产环境中使用 Webhook。 有关详细信息，请参阅[后续步骤](#next-steps)。 如果在测试代码时遇到问题，请尝试“[疑难解答](#troubleshooting)”中的步骤。

## 故障排除

如果未看到测试步骤中所述的预期结果，请尝试以下操作：

* 确保 Webhook 使用的是 Webhook 代理 URL (Smee.io URL)。 有关 Webhook 代理 URL 的详细信息，请参阅[获取 Webhook 代理 URL](#get-a-webhook-proxy-url)。 有关 Webhook 设置的详细信息，请参阅“[创建网络钩子](/zh/enterprise-server@3.22/webhooks/using-webhooks/creating-webhooks)”。
* 如果可以选择要使用的内容类型，请确保 Webhook 使用 JSON 内容类型。 有关 Webhook 设置的详细信息，请参阅“[创建网络钩子](/zh/enterprise-server@3.22/webhooks/using-webhooks/creating-webhooks)”。
* 确保 smee 客户端和本地服务器都正在运行。 将在两个单独的终端窗口中运行这些进程。
* 确保服务器正在侦听 smee.io 转发 Webhook 的同一端口。 本文中的所有示例都使用端口 3000。
* 确保 smee.io 转发 Webhook 的路径匹配代码中定义的路由。 本文中的所有示例使用 `/webhooks` 路径。
* 检查正在运行 smee 客户端和本地服务器的终端窗口中是否有错误消息。
* 检查 GitHub 以确认是否触发了 Webhook 传递。 有关详细信息，请参阅“[查看 web 挂钩交付](/zh/enterprise-server@3.22/webhooks/testing-and-troubleshooting-webhooks/viewing-webhook-deliveries)”。
* 请在 smee.io 上检查您的 webhook 代理 URL。 应该会看到与已触发或已重新交付事件对应的事件。 这表示 GitHub 已成功将 Webhook 投递到你指定的载荷 URL。

## 后续步骤

本文演示了如何编写代码来处理 Webhook 交付。 它还演示了如何将计算机或 codespace 用作本地服务器来测试代码，以及如何通过 smee.io 将来自 GitHub 的 Webhook 传递转发到本地服务器。 测试代码后，可能需要修改代码并将代码部署到服务器。

### 修改代码

本文提供了在收到 Webhook 交付时打印消息的基本示例。 若要执行其他操作，可以对代码进行修改。 例如，可以修改代码，从而：

* 向 GitHub API 发出请求
* 在 Slack 上发送消息
* 日志事件
* 更新外部项目管理工具

### 验证传递是否来自 GitHub

在处理 Webhook 投递的代码中，在进一步处理该投递之前，应先验证该投递是否来自 GitHub。 有关详细信息，请参阅“[验证 Webhook 交付](/zh/enterprise-server@3.22/webhooks/using-webhooks/validating-webhook-deliveries)”。

### 将代码部署到服务器

本文演示了如何在开发代码时使用计算机或 codespace 作为服务器。 代码可供生产使用后，应将应用部署到专用服务器。

执行此操作时，可能需要更新代码来反映服务器正在侦听的主机和端口。

### 更新 webhook 的链接地址

设置好可接收来自 GitHub 的 webhook 流量的服务器后，请在 webhook 设置中更新该 URL。 可能需要更新代码处理的路径以匹配新 URL 的路由部分。 例如，若新的 Webhook URL 为 `https://example.com/github-webhooks`，则应将这些示例中的路由从 `/webhooks` 更改为 `/github-webhooks`。

不应使用 Smee.io 在生产环境中转发 Webhook。

### 遵循最佳做法

应该遵循 Webhook 的最佳做法。 有关详细信息，请参阅“[使用 Webhook 的最佳做法](/zh/enterprise-server@3.22/webhooks/using-webhooks/best-practices-for-using-webhooks)”。

## 其他阅读材料

* [构建响应 Webhook 事件的GitHub应用](/zh/enterprise-server@3.22/apps/creating-github-apps/writing-code-for-a-github-app/building-a-github-app-that-responds-to-webhook-events)
* [使用 Webhook 的最佳做法](/zh/enterprise-server@3.22/webhooks/using-webhooks/best-practices-for-using-webhooks)