Webhooks¶
As of helga version 1.3, helga includes support for pluggable webhooks that can interact with the running bot or communicate via IRC. The webhook architecture is extensible much in the way that plugins work, allowing you to create new or custom HTTP services.
Overview¶
The webhooks system has two important aspects and core concepts: the HTTP server and routes.
HTTP Server¶
The webhooks system consists of an HTTP server that is managed by a command plugin named
webooks. This plugin is enabled by default and handles starting the
HTTP server is started when helga successfully signs on to IRC. The server process is configured to
listen on a port specified by the setting WEBHOOKS_PORT
.
The actual implementation of this HTTP server is a combination of a TCP listner using the Twisted
reactor, and twisted.web.server.Site
with a single root resource (see
WebhookRoot
) that manages each registered URL route.
Note
This server is managed via a plugin only so it can be controlled via IRC.
Routes¶
Routes are the plugins of the webhook system. They are essentially registered URL paths that have
some programmed behavior. For example, http://localhost:8080/github
, or /github
specifically,
might be the registered route for a webhook that announces github code pushes on an IRC channel.
Routes are declared using a decorator (see The @route Decorator), which will feel familiar
to anyone with flask experience. At this time, routes also support HTTP basic authentication,
which is configurable with a setting WEBHOOKS_CREDENTIALS
.
The @route
Decorator¶
Much like the plugin system, webhook routes are created using an easy to use decorator API. At
the core of this API is a single decorator @route
, which
will feel familiar to anyone with flask experience:
-
helga.plugins.webhooks.
route
(path, methods=None) Decorator to register a webhook route. This requires a path regular expression, and optionally a list of HTTP methods to accept, which defaults to accepting
GET
requests only. Incoming HTTP requests that use a non-allowed method will receive a 405 HTTP response.Parameters: - path – a regular expression string for the URL path of the route
- methods – a list of accepted HTTP methods for this route, defaulting to
['GET']
Decorated routes must follow this pattern:
-
helga.plugins.webhooks.
func
(request, client) Parameters: - request – The incoming HTTP request,
twisted.web.http.Request
- client – The client connection. An instance of
helga.comm.irc.Client
orhelga.comm.xmpp.Client
Returns: a string HTTP response
- request – The incoming HTTP request,
For example:
from helga.plugins.webhooks import route
@route(r'/foo')
def foo(request, client):
client.msg('#foo', 'someone hit the /foo endpoint')
return 'message sent'
Routes can be configured to also support URL parameters, which act similarly to django’s URL routing mechanisms. By introducing named pattern groups in the regular expression string. These will be passed as keyword arguments to the decorated route handler:
from helga.plugins.webhooks import route
@route(r'/foo/(?P<bar>[0-9]+)')
def foo(request, client, bar):
client.msg('#foo', 'someone hit the /foo endpoint with bar {0}'.format(bar))
return 'message sent'
Authenticated Routes¶
The webhooks system includes mechanisms for restricting routes to authenticated users. Note,
that this is only supported to handle HTTP basic authentication. Auth credentials are currently
limited to hard-coded username and password pairs configured as a list of two-tuples, the setting
WEBHOOKS_CREDENTIALS
. Routes are declared as requiring authentication
using the @authenticated
decorator:
For example:
from helga.plugins.webhooks import authenticated, route
@route(r'/foo')
@authenticated
def foo(request, client):
client.msg('#foo', 'someone hit the /foo endpoint')
return 'message sent'
Important
The @authenticated
decorator must be the
first decorator used for a route handler, otherwise the authentication check will not happen
prior to a route being handled. This requirement may change in the future.
Sending Non-200 Responses¶
By default, route handlers will send a 200 response to any incoming request. However, in some cases it may be necessary to explicitly return a non-200 response. In order to accomplish this, a route handler can manually set the response status code on the request object:
from helga.plugins.webhooks import route
@route(r'/foo')
def foo(request, client):
request.setResponseCode(404)
return 'foo is always 404'
In addition to this, route handlers can also raise helga.plugins.webhooks.HttpError
:
from helga.plugins.webhooks import route, HttpError
@route(r'/foo')
def foo(request, client):
raise HttpError(404, 'foo is always 404')
Using Templates¶
When installed, helga will have pystache installed as well, which can be used for templating
webhooks that produce HTML responses. It is important though that any webhooks be packaged so that
any external .mustache
templates are packaged as well, which can be done by adding to a
MANIFEST.in
file (see Packaging and Distribution):
recursive-include . *.mustache
Handling Unicode¶
Handling unicode for webhooks is not as strict as with plugins, but the same guidelines should follow. For example, webhooks should return unicode, but know that unicode strings are explicitly encoded as UTF-8 byte strings. See the plugin documentation Handling Unicode.
Accessing The Database¶
Database access for webhooks follows the same rules as for plugins. See the plugin documentation Accessing The Database
Requiring Settings¶
Requiring settings for webhooks follows the same rules as for plugins. See the plugin documentation Requiring Settings
Packaging and Distribution¶
Much like plugins, webhooks are also installable python modules. For that reason, the rules for
packaging and distributing webhooks are the same as with plugins (see plugin Packaging and Distribution).
However, there is one minor difference with respect to declaring the webhook entry point. Rather
than indicating the webhook as a helga_plugins
entry point, it should be placed in an entry
point section named helga_webhooks
. For example:
setup(
entry_points=dict(
helga_webhooks=[
'api = myapi:decorated_route'
]
)
)
Installing Webhooks¶
Webhooks are installed in the same manner that plugins are installed (see plugin Installing Plugins).
And much like plugins, there are settings to control both a whitelist and blacklist for loading webhook
routes (see ENABLED_PLUGINS
and DISABLED_PLUGINS
). To
explicitly whitelist webhook routes to be loaded, use ENABLED_WEBHOOKS
. To
explicitly blacklist webhook routes from being loaded, use DISABLED_WEBHOOKS
.