简介
创建 Webhook 时,要指定 URL 并订阅事件类型。 发生 Webhook 订阅的事件时,GitHub 会将包含有关该事件数据的 HTTP 请求发送到指定 URL。 如果服务器设置为侦听该 URL 处的 Webhook 交付,则可在收到 Webhook 交付时采取措施。
本文旨在介绍如何编写代码让服务器侦听和响应 Webhook 交付。 你将使用你的计算机或 codespace 作为本地服务器来测试你的代码。
安装
若要在本地测试 Webhook,可以使用 Webhook 代理 URL 将 Webhook 从 GitHub 转发到计算机或 codespace。 本文使用 Smee.io 提供 Webhook 代理 URL 和转发 Webhook。
获取 Webhook 代理 URL
- 在浏览器中,导航到 https://smee.io/ 。
- 单击启动新频道。
- 复制“Webhook 代理 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 代理 URL:Shell 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。 有关详细信息,请参阅“创建网络钩子”。
- 对于 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 自述文件。
Ruby 示例:安装依赖关系
若要使用此示例,必须在 Ruby 项目中安装 Sinatra gem。 例如,可以通过使用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 订阅的事件类型,以及创建 Webhook 时 GitHub 发送的 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'These are the dependencies for this code. You installed the sinatra gem earlier. For more information, see Ruby example: Install dependencies. The json library is a standard Ruby library, so you don't need to install it.
post '/webhook' doThe /webhook route matches the path that you specified for the smee.io forwarding. For more information, see 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.
status 202Respond 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.
github_event = request.env['HTTP_X_GITHUB_EVENT']Check the X-GitHub-Event header to learn what event type was sent.
Sinatra changes X-GitHub-Event to 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
endYou 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.
# 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。 如果您不再转发 webhooks,请再次按转发 webhooks中的步骤进行操作。
-
在单独的终端窗口中运行以下命令,在计算机上或 codespace 上启动本地服务器。 将
FILE_PATH替换为存储前文代码的文件的路径。 请注意,PORT=3000与在上一步中为 Webhook 转发指定的端口匹配。Shell PORT=3000 ruby FILE_NAME
PORT=3000 ruby FILE_NAME应该会看到类似“Sinatra 已在 3000 上运行”的输出。
-
触发你的 Webhook。 例如,如果创建了订阅
issues事件的存储库 Webhook,可以在存储库中提出问题。 您还可以重新发送先前的 Webhook 传送。 有关详细信息,请参阅“重新传递 Webhook”。 -
访问 smee.io 上的 Webhook 代理 URL。 应该会看到与已触发或已重新交付事件对应的事件。 这表示 GitHub 已成功将 Webhook 传递到您指定的负载 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 发送了 ping 事件”。 还可以看到 Sinatra 自动打印的其他行。 -
在这两个终端窗口中,输入 Ctrl+C 停止本地服务器并停止侦听转发的 Webhook。
现已在本地完成代码测试,可以根据情况进行更改,以便在生产环境中使用 Webhook。 有关详细信息,请参阅后续步骤。 如果在测试代码时遇到问题,请尝试“疑难解答”中的步骤。
JavaScript 示例
此示例使用 Node.js 和 Express 库来定义路由并处理 HTTP 请求。 有关详细信息,请参阅 expressjs.com。
有关使用 GitHub Octokit.js SDK 的示例,请参阅“构建响应 Webhook 事件的GitHub应用”。
本示例要求计算机或 codespace 运行 Node.js 版本 12 或更高版本和 npm 版本 6.12.0 或更高版本。 有关详细信息,请参阅 Node.js。
JavaScript 示例:安装依赖关系
若要使用此示例,必须在 Node.js 项目中安装 express 库。 例如:
npm install express
npm install express
JavaScript 示例:编写代码
创建具有以下内容的 JavaScript 文件。 修改代码以处理你的 Webhook 所订阅的事件类型,以及你创建 Webhook 时由 GitHub 发送的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');You installed the express library earlier. For more information, see JavaScript example: Install dependencies.
const app = express();This initializes a new Express application.
app.post('/webhook', express.json({type: 'application/json'}), (request, response) => {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.
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.
response.status(202).send('Accepted');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.
const githubEvent = request.headers['x-github-event'];Check the x-github-event header to learn what event type was sent.
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}`);
}
});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.
const port = 3000;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.
Once you deploy your code to a server, you should change this to match the port where your server is listening.
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});This starts the server and tells it to listen at the specified 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。 如果您不再转发 webhooks,请再次按照转发 webhooks中的步骤操作。
-
在单独的终端窗口中运行以下命令,在计算机上或 codespace 上启动本地服务器。 将
FILE_PATH替换为存储前文代码的文件的路径。Shell node FILE_NAME
node FILE_NAME应会看到输出显示
Server is running on port 3000。 -
触发你的 Webhook。 例如,如果创建了订阅
issues事件的存储库 Webhook,可以在存储库中提出问题。 您还可以重新发送先前的 Webhook 传送。 有关详细信息,请参阅“重新传递 Webhook”。 -
访问 smee.io 上的 Webhook 代理 URL。 应该会看到与已触发或已重新交付事件对应的事件。 这表示 GitHub 已成功将 Webhook 传递到您指定的负载 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 发送了 ping 事件”。 -
在这两个终端窗口中,输入 Ctrl+C 停止本地服务器并停止侦听转发的 Webhook。
现已在本地完成代码测试,可以根据情况进行更改,以便在生产环境中使用 Webhook。 有关详细信息,请参阅后续步骤。 如果在测试代码时遇到问题,请尝试“疑难解答”中的步骤。
故障排除
如果未看到测试步骤中所述的预期结果,请尝试以下操作:
- 确保 Webhook 使用的是 Webhook 代理 URL (Smee.io URL)。 有关 Webhook 代理 URL 的详细信息,请参阅获取 Webhook 代理 URL。 有关 Webhook 设置的详细信息,请参阅“创建网络钩子”。
- 如果可以选择要使用的内容类型,请确保 Webhook 使用 JSON 内容类型。 有关 Webhook 设置的详细信息,请参阅“创建网络钩子”。
- 确保 smee 客户端和本地服务器都正在运行。 将在两个单独的终端窗口中运行这些进程。
- 确保服务器正在侦听 smee.io 转发 Webhook 的同一端口。 本文中的所有示例都使用端口 3000。
- 确保 smee.io 转发 Webhook 的路径匹配代码中定义的路由。 本文中的所有示例使用
/webhooks路径。 - 检查正在运行 smee 客户端和本地服务器的终端窗口中是否有错误消息。
- 检查 GitHub,验证是否已触发 Webhook 交付。 有关详细信息,请参阅“查看 web 挂钩交付”。
- 请在 smee.io 上检查您的 webhook 代理 URL。 应该会看到与已触发或已重新交付事件对应的事件。 这表示 GitHub 已成功将 Webhook 传递到您指定的负载 URL。
后续步骤
本文演示了如何编写代码来处理 Webhook 交付。 文中还演示了如何使用计算机或 codespace 作为本地服务器来测试代码,以及如何通过 smee.io 将 Webhook 交付从 GitHub 转发到本地服务器。 测试代码后,可能需要修改代码并将代码部署到服务器。
修改代码
本文提供了在收到 Webhook 交付时打印消息的基本示例。 若要执行其他操作,可以对代码进行修改。 例如,可以修改代码,从而:
- 向 GitHub API 发送请求
- 在 Slack 上发送消息
- 日志事件
- 更新外部项目管理工具
验证交付来自 GitHub
在进一步处理交付之前,应在处理 Webhook 交付的代码中验证交付是否来自 GitHub。 有关详细信息,请参阅“验证 Webhook 交付”。
将代码部署到服务器
本文演示了如何在开发代码时使用计算机或 codespace 作为服务器。 代码可供生产使用后,应将应用部署到专用服务器。
执行此操作时,可能需要更新代码来反映服务器正在侦听的主机和端口。
更新 webhook 的链接地址
将服务器设置为从 GitHub 接收 Webhook 流量后,请在 Webhook 设置中更新 URL。 可能需要更新代码处理的路径以匹配新 URL 的路由部分。 例如,若新的 Webhook URL 为 https://example.com/github-webhooks,则应将这些示例中的路由从 /webhooks 更改为 /github-webhooks。
不应使用 Smee.io 在生产环境中转发 Webhook。
遵循最佳做法
应该遵循 Webhook 的最佳做法。 有关详细信息,请参阅“使用 Webhook 的最佳做法”。
其他阅读材料
-
[AUTOTITLE](/apps/creating-github-apps/writing-code-for-a-github-app/building-a-github-app-that-responds-to-webhook-events) -
[AUTOTITLE](/webhooks/using-webhooks/best-practices-for-using-webhooks)