简介
当您创建 webhook 时,需要指定一个 URL 并订阅事件类型。当您订阅的事件发生时,GitHub 会向您指定的 URL 发送包含该事件数据的 HTTP 请求。如果您的服务器已在该 URL 上监听 webhook 投递,则在收到投递时可以采取相应操作。
本文介绍如何编写代码,使您的服务器能够监听并响应 webhook 投递。您将使用自己的电脑或 Codespace 作为本地服务器来测试代码。
设置
为了在本地测试 webhook,您可以使用 webhook 代理 URL 将 GitHub 的 webhook 转发到您的电脑或 Codespace。本文使用 smee.io 提供 webhook 代理 URL 并转发 webhook。
获取 Webhook 代理 URL
- 在浏览器中打开 https://smee.io/。
- 点击 启动新通道。
- 复制 “Webhook Proxy URL” 下的完整 URL。您将在后续的设置步骤中使用此 URL。
转发 webhook
-
如果您还未安装 smee-client,请在终端中运行以下命令
Shell npm install --global smee-client
npm install --global smee-client -
要接收来自 smee.io 的转发 webhook,请在终端中运行以下命令。将
WEBHOOK_PROXY_URL替换为您之前获取的 webhook 代理 URL。Shell smee --url WEBHOOK_PROXY_URL --path /webhook --port 3000
smee --url WEBHOOK_PROXY_URL --path /webhook --port 3000您应该会看到类似如下的输出,其中
WEBHOOK_PROXY_URL为您的 webhook 代理 URLShell Forwarding WEBHOOK_PROXY_URL to http://127.0.0.1:3000/webhook Connected WEBHOOK_PROXY_URL
Forwarding WEBHOOK_PROXY_URL to http://127.0.0.1:3000/webhook Connected WEBHOOK_PROXY_URL请注意路径为
/webhook,端口为3000。稍后在编写处理 webhook 投递的代码时将会使用这些值。 -
在测试 webhook 时请保持该进程运行。若要停止转发 webhook,输入 Ctrl+C。
创建 webhook
-
使用以下设置创建 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 来完成。
-
如果尚未安装 Bundler,请在终端中运行以下命令
Shell gem install bundler
gem install bundler -
如果您的应用尚未拥有 Gemfile,请在终端中运行以下命令
Shell bundle init
bundle init -
如果您的应用尚未拥有 Gemfile.lock,请在终端中运行以下命令
Shell bundle install
bundle install -
在终端中运行以下命令以安装 Sinatra gem
Shell bundle add sinatra
bundle add sinatra
Ruby 示例:编写代码
创建一个包含以下内容的 Ruby 文件。根据您的 webhook 订阅的事件类型以及 GitHub 在创建 webhook 时发送的 ping 事件修改代码。本示例处理 issues 和 ping 事件。
# 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
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 订阅的每种事件类型添加处理逻辑。例如,此代码处理 issues 与 ping 事件。
如果某些事件包含 action 字段,请为您关心的每个操作添加相应逻辑。例如,此代码处理 issue 事件的 opened 与 closed 操作。
有关每种事件类型的可用数据的更多信息,请参阅 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 充当本地服务器。如在此过程中遇到问题,请参阅 故障排除。
-
确保您正在转发 webhook。如果不再转发,请重新执行 转发 webhook 中的步骤。
-
在另一个终端窗口中运行以下命令,在电脑或 Codespace 上启动本地服务器。将
FILE_PATH替换为前一节代码文件的路径。请注意PORT=3000与前一步中为 webhook 转发指定的端口相匹配。Shell PORT=3000 ruby FILE_NAME
PORT=3000 ruby FILE_NAME您应看到类似 “Sinatra has taken the stage on 3000” 的输出。
-
触发您的 webhook。例如,若您创建了订阅
issues事件的仓库 webhook,则在仓库中打开一个 issue。您也可以重新投递先前的 webhook 投递。更多信息请参阅 重新投递 webhook。 -
在 smee.io 上访问您的 webhook 代理 URL。您应看到与刚才触发或重新投递的事件相对应的事件记录,这表明 GitHub 已成功将 webhook 投递发送到您指定的 payload URL。
-
在运行
smee --url WEBHOOK_PROXY_URL --path /webhook --port 3000的终端窗口中,您应看到类似POST http://127.0.0.1:3000/webhook - 202的输出。这表明 smee 已成功将 webhook 转发到本地服务器。 -
在运行
PORT=3000 ruby FILE_NAME的终端窗口中,您应看到与发送的事件相对应的消息。例如,使用上面的示例代码并重新投递ping事件时,应看到 “GitHub sent the ping event”。您可能还会看到 Sinatra 自动打印的其他行。 -
在两个终端窗口中,输入 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 库。例如
npm install express
npm install express
JavaScript 示例:编写代码
创建一个包含以下内容的 JavaScript 文件。根据您的 webhook 订阅的事件类型以及 GitHub 在创建 webhook 时发送的 ping 事件修改代码。本示例处理 issues 与 ping 事件。
// 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}`);
});
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 订阅的每种事件类型添加处理逻辑。例如,此代码处理 issues 与 ping 事件。
如果某些事件包含 action 字段,请为您关心的每个操作添加相应逻辑。例如,此代码处理 issue 事件的 opened 与 closed 操作。
有关每种事件类型的可用数据的更多信息,请参阅 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 充当本地服务器。如在此过程中遇到问题,请参阅 故障排除。
-
确保您正在转发 webhook。如果不再转发,请重新执行 转发 webhook 中的步骤。
-
在另一个终端窗口中运行以下命令,在电脑或 Codespace 上启动本地服务器。将
FILE_PATH替换为前一节代码文件的路径。Shell node FILE_NAME
node FILE_NAME您应看到输出
Server is running on port 3000。 -
触发您的 webhook。例如,若您创建了订阅
issues事件的仓库 webhook,则在仓库中打开一个 issue。您也可以重新投递先前的 webhook 投递。更多信息请参阅 重新投递 webhook。 -
在 smee.io 上访问您的 webhook 代理 URL。您应看到与刚才触发或重新投递的事件相对应的事件记录,这表明 GitHub 已成功将 webhook 投递发送到您指定的 payload URL。
-
在运行
smee --url WEBHOOK_PROXY_URL --path /webhook --port 3000的终端窗口中,您应看到类似POST http://127.0.0.1:3000/webhook - 202的输出。这表明 smee 已成功将 webhook 转发到本地服务器。 -
在运行
node FILE_NAME的终端窗口中,您应看到与发送的事件相对应的消息。例如,使用上述示例代码并重新投递ping事件时,应看到 “GitHub sent the ping event”。 -
在两个终端窗口中,输入 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 的最佳实践。