Integrating Pushover with Zato

Sending notifications is a major use case for an ESB. As well as sending messages between systems, we want to be notified of important events without having to check applications or trawl through log files.

One option is email, but let’s say we want to get push notification to our phones and don’t want to clutter up our inboxes. There are various phone apps that help us do that, but I’ve chosen Pushover.

Pushover has a really simple API. We just need to send a message with the necessary parameters and it’ll push a notification to our devices. Adding this as an outgoing channel in Zato is a breeze, and is a nice example of the benefits of an ESB and Zato in particular.

Here’s the example code that Pushover supply for sending messages with Python:

import httplib, urllib
conn = httplib.HTTPSConnection("api.pushover.net:443")
conn.request("POST", "/1/messages.json",
  urllib.urlencode({
   "token": "APP_TOKEN",
   "user": "USER_KEY",
   "message": "hello world",
   }), 
   { "Content-type": "application/x-www-form-urlencoded" })

conn.getresponse() 

Let’s convert that into a Zato service, but first of all let’s think about the design. If we make it generic enough it’ll be easy to swap to a different notification method if we no longer want to use Pushover.

So let’s have our service take four parameters:

subject: this will be a brief heading, used for the title in Pushover messages, but we could also use it as a subject line for email.
message: self explanatory, the message itself.

user: the recipient. This will be a username and we’ll look up the address in our code – a Pushover user key for now, but it could also be an email address.

testing: this an optional extra. If this is set, we’ll just log the outgoing message and not send it. It’ll be useful when developing services that call our notification service so that we don’t get inundated with notifications while developing.

We’ll start off by storing the Pushover app token and the user details in Redis, but we can easily change that later. I’m using the prefix “app” for application specific keys and a “users” prefix for my user details. The users. key will store a hash so that we can have multiple fields for each user – their Pushover key and email address for example.

Let’s start off by creating them manually using the Redis command line:

> hset users.sean pushover USER_KEY
> set app.pushover.token APP_TOKEN

Now we’ll create the outbound connection to Pushover in the admin GUI under Connections – Outgoing – Plain HTTP:

zato ougoing channel for pushover

That will create a connection called “pushover” that we can reference in our code. It also means that if Pushover change the URL we don’t need to change our code, we just need to change the parameters in the connection definition.

Now let’s put the code in notify.py:

# Send to Pushover
import urllib
import bunch
from zato.server.service import Service

class SendMessage(Service):

  class SimpleIO:
    input_optional = {'subject', 'testing'}
    input_required = {'user', 'message'}
    output_required = {'status', 'request'}
    default = ""

  def handle(self):

    request = urllib.urlencode(
      { "user": self.kvdb.conn.hget('users.'
                   + self.request.input.user, 'pushover'),
        "token": self.kvdb.conn.get('app.pushover.token'),
        "message": self.request.input.message,
        "title": self.request.input.subject
       },
       {"Content-type": "application/x-www-form-urlencoded" 
       })

    if self.request.input.testing:
      self.response.payload = '{"status": 1, 
                                "request": "test"}'
      self.logger.info(request)            
    else:
      pushover = self.outgoing.plain_http.get('pushover')
      response = pushover.conn.send(self.cid, request)
      self.response.payload = response.text

Rather unusually, Pushover requires plain url-encoded POST messages but returns JSON. Quite why it doesn’t take JSON input is a mystery, but at least it illustrates the benefit of using a Python based API.

We have the full Python library available to us and can use urllib to encode the message. Most ESBs expect messages in JSON or XML and if you’ve ever tried to figure out how to get them to work with other formats you’ll appreciate the simplicity of Zato.

We can now call this service from within other services:

self.invoke('notify.send-message', 
      {“subject": “test", 
       “message": “test", 
       “user”: “sean"})

We can also add a plain HTTP channel to call this directly. In the admin GUI, go to Connections -> Channels -> Plain HTTP and create a new channel:

zato channel for pushover

Now we can test our push notifications from the command line:

curl -v http://myserver:11223/testing/pushnotify -d '{"subject": "Test message", "message": "test", "user": "sean"}'

And sure enough we get the message:

Pushover notification

Doing it this way gives us maximum flexibility. We can completely change the implementation of the notify service without having to touch any of our services that call it. Those services don’t need to know whether the message will be sent via Pushover, email or some other means – they just need to specify the message and the user.

We can also easily switch from Redis to another method of storing our user data, perhaps by linking to other services that provide that data. We could also extend our service to allow management of the user details via a REST api.

The possible improvements are endless, but I hope this shows how easy it is to get started.

in Home Automation

Related Posts

Add a Comment

Your email address will not be published. All comments will be reviewed.

This site uses Akismet to reduce spam. Learn how your comment data is processed.