Serverless development is a hot topic lately. Development & operations of a web service can be greatly simplified by writing your application logic as short-lived functions, and relying on outside organizations for the development of all the other components in your stack (e.g. databases, gateways, container engines, etc). The term “serverless” is a bit funny because of course there are still servers in your stack, and they may even be your own servers, but the main idea is you no longer have to worry about your own long-running application code.
This all sounds great, but an issue arises: in this serverless world, how do you support long-lived connections (e.g. HTTP streaming/WebSocket connections) for realtime data push, without long-running application code? By delegating connection management to another component, of course! In this article we’ll talk about how to build a simple chat service with Pushpin, using Microcule for running the backend worker function.
First, a little about the tools being used:
Pushpin is an open source project that makes it easy to build realtime web services. It works as a proxy server to an HTTP backend. Traffic is forwarded through Pushpin to the backend service, and the backend decides how Pushpin should behave. Response traffic can be sent back unmodified, or it can include special instructions that Pushpin should act upon. For example, the backend could instruct Pushpin to keep an incoming connection open and associate it with one or more publish-subscribe channels, so that data can be sent down the connection later on.
Microcule is an open source project that makes it easy to run HTTP microservices. You can think of it like an open source AWS Lambda. Each incoming HTTP request invokes a program in an isolated process to handle the request and then terminate. This ensures the backend code will be stateless as each request is given its own process. Microcule supports numerous programming languages and is used by the Hook.io service.
For this article we’ve made a basic WebSocket service, where incoming messages are broadcasted to all open connections. Here’s the complete backend code, written in Python:
The code can be run as a microservice like this:
By default, Microcule listens on port 3000. Now we’ll start a Pushpin instance to point at it:
By default, Pushpin listens on port 7999 for external client traffic and port 5561 for receiving internal control commands.
You can then connect to the service, for example with
wscat, to chat:
It’s important to point out here that the backend worker only executes while it is processing an incoming WebSocket event. Otherwise it is not running at all. WebSocket connections stay open because they are being managed by Pushpin. You can even modify the backend handler code between executions without disconnecting clients.
Now let’s walk through some key parts of the code.
pub object here is used for publishing data through Pushpin. Note that it doesn’t immediately connect to the specified URI. Instead, a connection is made only if
publish is called.
Pushpin exchanges a list of WebSocket events with the backend. Both the HTTP request and the response may contain events. The protocol is described here. We use the gripcontrol library to serialize/deserialize the events. The code in the handler loops over the incoming events, and assembles a list of output events to be sent back at the end.
WebSocketEvent objects contain
The above code tells Microcule about response headers, using special Microcule control messages over stderr.
OPEN event is received, then we send an
OPEN event back to Pushpin. This is how a connection is acknowledged. We enable the
grip WebSocket extension, which allows the handler to send control messages to Pushpin using WebSocket messages. Without this extension, Pushpin will treat the connection as a passthrough and not attempt to hijack messages. We also take this moment to send one such control message, to subscribe the connection to a channel called
Here we route incoming messages out to all connections subscribed to the
Pushpin and serverless backends are a powerful combination. Realtime services are normally incredibly complicated to build, as well as stateful. With Pushpin and Microcule, you can make realtime services that are easy to understand and maintain, with backend code that is stateless. Such a system is straightforward to scale, too. Both Pushpin and Microcule are horizontally scalable, and instances of each tier don’t need to talk to each other.
The example source is also on GitHub.