/var/

Various programming stuff

Hello! I see that you have an ad blocker. If you find something useful here and want to support me somehow please consider disabling your ad blocker for this site. It won't cost you anything.

Calling the REST API of Pusher from python

Introduction

Pusher is one of the best real time frameworks right now. Using it you can add real time events in your projects without the need to configure and use HTTP servers that support real-time events in your environment. I used it recently in a project and it worked really good, having a very simple API and a nice interface for debugging your requests.

The only problem I’ve found was that the Pusher python API misses some features that the APIs for other languages have, specifically finding out the users on a presence channel.

Pusher supports real-time events through the use of “channels”. Each pusher client will subscribe to a channel and receive messages that are sent to that channel. A special kind of channel are presence channels which keep a list of their subscribers. You can query the Pusher REST API (or f.e the Pusher Javascript API) to find out the names of the users in a presence channel - however this is not currently possible with the python API.

Unfortuanately, calling the Pusher REST API is not so easy, since it needs a complicated singining of each request, so I’ve written this post to help developers that need to call this API from python (to get the users of a presence channel or for any other method the REST API supports).

Signing the request

Quoting from the Pusher REST API, to sign a request we need a signature, which:

The signature is a HMAC SHA256 hex digest. This is generated by signing a string made up of the following components concatenated with newline characters \n:

  • The uppercase request method (e.g. POST)
  • The request path (e.g. /some/resource)
  • The query parameters sorted by key, with keys converted to lowercase, then joined as in the query string. Note that the string must not be url escaped (e.g. given the keys auth_key: foo, Name: Something else, you get auth_key=foo&name=Something else)

So, we need to create a string and then sign it using our Pusher api_key and secret. To help with this, we create a Token class which will be initialzed with out pusher key/secret and correctly sign a string:

class Token(object,):
    def __init__(self, key, secret):
        self.key = key
        self.secret = secret

    def sign(self, string):
        return hmac.new(self.secret, string, hashlib.sha256).hexdigest()

It uses the hmac and hashlib python modules.

Generating the complete query string

We can now create a function that will sign a request using an instance of the above token:

def create_signed_query_string(token, partial_path, request_params):
    params = {
        'auth_key': token.key,
        'auth_timestamp': int(time.time()),
        'auth_version': '1.0'
    }
    params.update(request_params)
    keys = sorted(params.keys() )
    params_list = []
    for k in keys:
        params_list.append( '{0}={1}'.format(k, params[k]) )

    query_string = '&'.join(params_list)

    sign_data = '\n'.join(['GET', partial_path, query_string])
    query_string += '&auth_signature=' + token.sign(sign_data);
    return query_string

create_signed_query_string receives an instance of a Token, the path that we want to request without the server part (for example /apps/33/users/my-channel) and a dictionary of request parameters. It then adds three extra fields to the request parameters (auth_key, auth_timestamp, auth_version) and creates a list of these parameters in the key=value form, where the keys are alphabetically sorted. After that it joins the above key=value parameters using & to create the query_string and then it creates the string to be signed (sign_data) by concatenating the HTTP methdo (GET) with the path and the query_string. Finally, it appends the signing result as an extra query parameter named (auth_signature).

Requesting the users of the presence channel

The create_signed_query_string can now be used to get the users of a presence channel like this:

def get_users(app_id, key, secret, channel):
    partial_path =  '/apps/{0}/channels/{1}/users'.format(app_id, channel)
    token = Token(key, secret)
    qs = create_signed_query_string(token, partial_path, {})
    full_path = 'http://api.pusherapp.com/{0}?{1}'.format(partial_path, qs)
    r = requests.get(full_path)
    return r.text

The get_users function will generate the path of the pusher REST API (using our pusher app_id and channel name) and initialize a signing Token using the pusher key and secret. It will then pass the previous to create_signed_query_string to generate the complete query_string and generate the full_path to which a simple HTTP GET request is issued. The result will be a JSON list of the users in the presence channel.

Complete example

A complete example of getting the presence users of a channel is the following:

import time
import hashlib
import hmac
import requests

app_id = 'pusher_app_id'
key = 'pusher_key'
secret = 'pusher_secret'
channel = 'pusher_presence_channel'


class Token(object,):
    def __init__(self, key, secret):
        self.key = key
        self.secret = secret

    def sign(self, string):
        return hmac.new(self.secret, string, hashlib.sha256).hexdigest()


def create_signed_query_string(token, partial_path, method, request_params):
    params = {
        'auth_key': token.key,
        'auth_timestamp': int(time.time()),
        'auth_version': '1.0'
    }
    params.update(request_params)
    keys = sorted(params.keys() )
    params_list = []
    for k in keys:
        params_list.append( '{0}={1}'.format(k, params[k]) )

    query_string = '&'.join(params_list)

    sign_data = '\n'.join([method, partial_path, query_string])
    query_string += '&auth_signature=' + token.sign(sign_data);
    return query_string


def get_users(channel):
    partial_path =  '/apps/{0}/channels/{1}/users'.format(app_id, channel)
    token = Token(key, secret)
    qs =  create_signed_query_string(token, partial_path, 'GET' {})
    full_path = 'http://api.pusherapp.com/{0}?{1}'.format(partial_path, qs)
    r = requests.get(full_path)
    return r.text

print get_users(channel)

Conclusion

With the above we are able to not only easily get the users of a Pusher presence channel in python but to also call any method we want from the Pusher REST API by implementing a function similar to get_users.

Comments