Pushpin Documentation

repository·main·Indexed 26 days ago

https://github.com/fastly/pushpin

Pushpin is a reverse proxy server designed to enable realtime push capabilities such as WebSockets, HTTP streaming, and HTTP long-polling. It acts as a gateway that translates client-side realtime connections into standard HTTP requests for backend applications using the Generic Realtime Intermediary Protocol (GRIP), allowing developers to build realtime APIs in any language. It supports horizontal scaling, ZeroMQ communication, and provides a connection manager CLI called pushpin-connmgr.

Tokens
13.4K
Snippets
30
Records
85
Agent score
87%

What's inside Pushpin

  1. Implement WebSockets with Pushpin

    main

    Pushpin supports WebSockets by converting connection activity and messages into HTTP requests sent to your backend. This allows for stateless backend handling.

    Key concepts for WebSocket integration:

    • Connection Lifecycle: When a new connection arrives, the backend receives a request where wsContext.isOpening() is true. You must call wsContext.accept() and optionally wsContext.subscribe(channel) to establish the connection.
    • Message Handling: Messages are received via wsContext.recv(). If recv() returns null, the connection is closed.
    • Publishing: Use a publisher to broadcast messages to channels using publisher.publishFormats(channel, format).
    • Statelessness: The wsContext only exists for the duration of the handler invocation. The backend does not need to maintain long-lived socket connections.
    const { WebSocketMessageFormat } = require( '@fanoutio/grip' );
    
    app.post('/websocket', async (req, res) => {
        const { wsContext } = req.grip;
    
        // If this is a new connection, accept it and subscribe it to a channel
        if (wsContext.isOpening()) {
            wsContext.accept();
            wsContext.subscribe('all');
        }
    
        while (wsContext.canRecv()) {
            var message = wsContext.recv();
    
            // If return value is null then connection is closed
            if (message == null) {
                wsContext.close();
                break;
            }
    
            // broadcast the message to everyone connected
            await publisher.publishFormats('all', WebSocketMessageFormat(message));
        }
    
        res.end();
    });
  2. Install Pushpin

    main

    Pushpin can be installed via package managers for Linux (Debian, Ubuntu, CentOS, Red Hat) and macOS (Homebrew), or built from source.

    By default, Pushpin listens on port 7999. You can verify a successful installation by navigating to http://localhost:7999/. To route traffic to your actual backend, you must modify the routes configuration file.

  3. Implement HTTP streaming with Pushpin

    main

    To create an HTTP streaming connection, your backend must respond to a proxied request with two specific headers: Grip-Hold: stream and Grip-Channel: <channel_name>.

    When Pushpin receives these headers, it converts the response to Transfer-Encoding: chunked for the client and holds the connection open, subscribing the client to the specified channel. The backend request is then considered complete.

    To push data to the stream, make an HTTP POST request to Pushpin's private control API (default: http://localhost:5561/publish/) with a JSON payload specifying the channel and the content format.

    HTTP/1.1 200 OK
    Content-Type: text/plain
    Content-Length: 22
    Grip-Hold: stream
    Grip-Channel: test
    
    welcome to the stream
    curl -d '{ "items": [ { "channel": "test", "formats": { "http-stream": { "content": "hello there\n" } } } ] }' \
        http://localhost:5561/publish
  4. Scale Pushpin horizontally

    main

    Pushpin is designed to be horizontally scalable. Because instances do not communicate with each other, sticky routing is not required. To ensure clients connected to any instance receive data, backends must publish data to all active Pushpin instances.

    Most backend libraries allow you to configure multiple Pushpin instances so that a single publish call broadcasts data to all instances simultaneously.

    Alternatively, you can use ZeroMQ PUB/SUB to send data to Pushpin instead of using HTTP POST. When using ZeroMQ, subscription information is forwarded to each publisher, ensuring data is only published to instances that currently have active listeners.

  5. Configure Pushpin with a custom configuration volume

    main

    To use your own Pushpin configuration instead of the default, mount a local directory containing your configuration files to /etc/pushpin/ inside the container using a volume.

    docker run \
      -d \
      -p 7999:7999 \
      -p 5560-5563:5560-5563 \
      -v $(pwd)/config:/etc/pushpin/ \
      --rm \
      --name pushpin \
      fanout/pushpin
  6. Install build tools for Debian packaging

    main

    To package Pushpin for Debian, you must first install the necessary system tools and the cargo-deb utility. Note that cargo-deb@2.12.1 is recommended to maintain compatibility with older rustc versions found in some distributions.

    apt install dpkg dpkg-dev devscripts vim rustc cargo
    cargo install --locked cargo-deb@2.12.1
  7. Connect to Pushpin via ZeroMQ

    main

    For low-level services without a webserver, Pushpin can communicate with backends via ZeroMQ using TNetStrings.

    1. Configure a route in Pushpin's routes file using the zhttpreq prefix: * zhttpreq/tcp://<address>:<port>
    2. The backend should implement a REP (Reply) socket.
    3. To activate an HTTP stream, the backend must respond with a TNetString containing a dictionary that includes Grip-Hold: stream and Grip-Channel: <channel> in the headers list.
    * zhttpreq/tcp://127.0.0.1:10000
    import zmq
    import tnetstring
    
    zmq_context = zmq.Context()
    sock = zmq_context.socket(zmq.REP)
    sock.connect('tcp://127.0.0.1:10000')
    
    while True:
        req = tnetstring.loads(sock.recv()[1:])
    
        resp = {
            'id': req['id'],
            'code': 200,
            'reason': 'OK',
            'headers': [
                ['Grip-Hold', 'stream'],
                ['Grip-Channel', 'test'],
                ['Content-Type', 'text/plain']
            ],
            'body': 'welcome to the stream\n'
        }
    
        sock.send('T' + tnetstring.dumps(resp))
  8. Build and package Pushpin .deb files

    main

    Follow these steps to build Pushpin from a specific git tag and generate Debian packages. This process should be run from the root of the pushpin repository.

    1. Prepare distribution files: Build and install Pushpin into the ./dist directory using a specific git tag.
    2. Generate .deb package: Use cargo deb with the --no-build flag (since files are already in ./dist) and specify a distribution revision. The output will be placed in ./target/debian.

    Repeat the cargo deb step for each target distribution revision (e.g., 1~noble1, 1~jammy1).