Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webhook health check endpoint #272

Merged
merged 1 commit into from
Jan 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Emits `message` when a message arrives.
| [options.webHook.pfx] | <code>String</code> | | Path to file with PFX private key and certificate chain for webHook server. The file is read **synchronously**! |
| [options.webHook.autoOpen] | <code>Boolean</code> | <code>true</code> | Open webHook immediately |
| [options.webHook.https] | <code>Object</code> | | Options to be passed to `https.createServer()`. Note that `options.webHook.key`, `options.webHook.cert` and `options.webHook.pfx`, if provided, will be used to override `key`, `cert` and `pfx` in this object, respectively. See https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener for more information. |
| [options.webHook.healthEndpoint] | <code>String</code> | <code>/healthz</code> | An endpoint for health checks that always responds with 200 OK |
| [options.onlyFirstMatch] | <code>Boolean</code> | <code>false</code> | Set to true to stop after first match. Otherwise, all regexps are executed |
| [options.request] | <code>Object</code> | | Options which will be added for all requests to telegram api. See https://github.com/request/request#requestoptions-callback for more information. |
| [options.baseApiUrl] | <code>String</code> | <code>https://api.telegram.org</code> | API Base URl; useful for proxying and testing |
Expand Down
1 change: 1 addition & 0 deletions src/telegram.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class TelegramBot extends EventEmitter {
* Note that `options.webHook.key`, `options.webHook.cert` and `options.webHook.pfx`, if provided, will be
* used to override `key`, `cert` and `pfx` in this object, respectively.
* See https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener for more information.
* @param {String} [options.webHook.healthEndpoint=/healthz] An endpoint for health checks that always responds with 200 OK
* @param {Boolean} [options.onlyFirstMatch=false] Set to true to stop after first match. Otherwise, all regexps are executed
* @param {Object} [options.request] Options which will be added for all requests to telegram api.
* See https://github.com/request/request#requestoptions-callback for more information.
Expand Down
31 changes: 20 additions & 11 deletions src/telegramWebHook.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class TelegramBotWebHook {
* @param {String} token Telegram API token
* @param {Boolean|Object} options WebHook options
* @param {Number} [options.port=8443] Port to bind to
* @param {String} [options.healthEndpoint=/healthz] An endpoint for health checks that always responds with 200 OK
* @param {Function} callback Function for process a new update
*/
constructor(token, options, callback) {
Expand All @@ -27,6 +28,7 @@ class TelegramBotWebHook {
this.options.https = options.https || {};
this.callback = callback;
this._regex = new RegExp(this.token);
this._healthRegex = new RegExp(options.healthEndpoint || '/healthz');
this._webServer = null;
this._open = false;
this._requestListener = this._requestListener.bind(this);
Expand Down Expand Up @@ -133,20 +135,27 @@ class TelegramBotWebHook {
debug('WebHook request URL: %s', req.url);
debug('WebHook request headers: %j', req.headers);

// If there isn't token on URL
if (!this._regex.test(req.url)) {
if (this._regex.test(req.url)) {
if (req.method === 'POST') {
req
.pipe(bl(this._parseBody))
.on('finish', () => res.end('OK'));
} else {
// Authorized but not a POST
debug('WebHook request isn\'t a POST');
res.statusCode = 418; // I'm a teabot!
res.end();
}
} else if (this._healthRegex.test(req.url)) {
// If this is a health check
debug('WebHook health check passed');
res.statusCode = 200;
res.end('OK');
} else if (!this._regex.test(req.url)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should test for the token first, then the health endpoint, considering that we do not want to waste too much CPU with each and every update we receive on the webhook! What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, changed the order.

// If there isn't token on URL
debug('WebHook request unauthorized');
res.statusCode = 401;
res.end();
} else if (req.method === 'POST') {
req
.pipe(bl(this._parseBody))
.on('finish', () => res.end('OK'));
} else {
// Authorized but not a POST
debug('WebHook request isn\'t a POST');
res.statusCode = 418; // I'm a teabot!
res.end();
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions test/telegram.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ describe('TelegramBot', function telegramSuite() {
});

describe('WebHook', function webHookSuite() {
it('returns OK for healthz endpoint', function test(done) {
utils.sendWebHookRequest(webHookPort2, '/healthz', { json: false }).then(resp => {
assert.equal(resp, 'OK');
return done();
});
});
it('returns 401 error if token is wrong', function test(done) {
utils.sendWebHookMessage(webHookPort2, 'wrong-token').catch(resp => {
assert.equal(resp.statusCode, 401);
Expand Down
27 changes: 23 additions & 4 deletions test/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ exports = module.exports = {
* @return {Promise}
*/
isPollingMockServer,
/**
* Send a message to the webhook at the specified port and path.
* @param {Number} port
* @param {String} path
* @param {Object} [options]
* @param {String} [options.method=POST] Method to use
* @param {Object} [options.message] Message to send. Default to a generic text message
* @param {Boolean} [options.https=false] Use https
* @return {Promise}
*/
sendWebHookRequest,
/**
* Send a message to the webhook at the specified port.
* @param {Number} port
Expand Down Expand Up @@ -134,23 +145,31 @@ function hasOpenWebHook(port, reverse) {
}


function sendWebHookMessage(port, token, options = {}) {
function sendWebHookRequest(port, path, options = {}) {
assert.ok(port);
assert.ok(token);
assert.ok(path);
const protocol = options.https ? 'https' : 'http';
const url = `${protocol}://127.0.0.1:${port}/bot${token}`;
const url = `${protocol}://127.0.0.1:${port}${path}`;
return request({
url,
method: options.method || 'POST',
body: {
update_id: 1,
message: options.message || { text: 'test' }
},
json: true,
json: options.json || true,
});
}


function sendWebHookMessage(port, token, options = {}) {
assert.ok(port);
assert.ok(token);
const path = `/bot${token}`;
return sendWebHookRequest(port, path, options);
}


function handleRatelimit(bot, methodName, suite) {
const backupMethodName = `__${methodName}`;
if (!bot[backupMethodName]) bot[backupMethodName] = bot[methodName];
Expand Down