Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial import

  • Loading branch information...
commit b27a916bc471e095a4cd64f512a39498ae32d5c4 1 parent 13dc6da
unknown authored
Showing with 389 additions and 0 deletions.
  1. +262 −0 Pusher.java
  2. +89 −0 PusherRequest.java
  3. +38 −0 README.md
View
262 Pusher.java
@@ -0,0 +1,262 @@
+
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.mortbay.log.Log;
+
+import com.google.appengine.api.urlfetch.HTTPHeader;
+import com.google.appengine.api.urlfetch.HTTPMethod;
+import com.google.appengine.api.urlfetch.HTTPRequest;
+import com.google.appengine.api.urlfetch.HTTPResponse;
+import com.google.appengine.api.urlfetch.URLFetchService;
+import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
+
+/**
+ * Static class to send messages to Pusher's REST API.
+ *
+ * Please set pusherApplicationId, pusherApplicationKey, pusherApplicationSecret accordingly
+ * before sending any request.
+ *
+ * @author Stephan Scheuermann
+ * Copyright 2010. Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+ */
+public class Pusher {
+
+ /**
+ * Pusher Host name
+ */
+ private final static String pusherHost = "api.pusherapp.com";
+
+ /**
+ * Pusher Application Identifier
+ */
+ private final static String pusherApplicationId = "";
+
+ /**
+ * Pusher Application Key
+ */
+ private final static String pusherApplicationKey = "";
+
+ /**
+ * Pusher Secret
+ */
+ private final static String pusherApplicationSecret = "";
+
+ /**
+ * Converts a byte array to a string representation
+ * @param data
+ * @return
+ */
+ private static String byteArrayToString(byte[] data){
+ BigInteger bigInteger = new BigInteger(1,data);
+ String hash = bigInteger.toString(16);
+ // Zero pad it
+ while(hash.length() < 32 ){
+ hash = "0" + hash;
+ }
+ return hash;
+ }
+
+ /**
+ * Returns a md5 representation of the given string
+ * @param data
+ * @return
+ */
+ private static String md5Representation(String data) {
+ try {
+ //Get MD5 MessageDigest
+ MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+ byte[] digest = messageDigest.digest(data.getBytes("US-ASCII"));
+ return byteArrayToString(digest);
+ } catch (NoSuchAlgorithmException nsae) {
+ //We should never come here, because GAE has a MD5 algorithm
+ throw new RuntimeException("No MD5 algorithm");
+ } catch (UnsupportedEncodingException e) {
+ //We should never come here, because UTF-8 should be available
+ throw new RuntimeException("No UTF-8");
+ }
+ }
+
+ /**
+ * Returns a HMAC/SHA256 representation of the given string
+ * @param data
+ * @return
+ */
+ private static String hmacsha256Representation(String data) {
+ try {
+ // Create the HMAC/SHA256 key from application secret
+ final SecretKeySpec signingKey = new SecretKeySpec( pusherApplicationSecret.getBytes(), "HmacSHA256");
+
+ // Create the message authentication code (MAC)
+ final Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(signingKey);
+
+ //Process and return data
+ byte[] digest = mac.doFinal(data.getBytes("UTF-8"));
+ digest = mac.doFinal(data.getBytes());
+ return byteArrayToString(digest);
+ } catch (NoSuchAlgorithmException nsae) {
+ //We should never come here, because GAE has HMac SHA256
+ throw new RuntimeException("No HMac SHA256 algorithm");
+ } catch (UnsupportedEncodingException e) {
+ //We should never come here, because UTF-8 should be available
+ throw new RuntimeException("No UTF-8");
+ } catch (InvalidKeyException e) {
+ throw new RuntimeException("Invalid key exception while converting to HMac SHA256");
+ }
+ }
+
+ /**
+ * Build query string that will be appended to the URI and HMAC/SHA256 encoded
+ * @param eventName
+ * @param jsonData
+ * @return
+ */
+ private static String buildQuery(String eventName, String jsonData, String socketID){
+ StringBuffer buffer = new StringBuffer();
+ //Auth_Key
+ buffer.append("auth_key=");
+ buffer.append(pusherApplicationKey);
+ //Timestamp
+ buffer.append("&auth_timestamp=");
+ buffer.append(System.currentTimeMillis() / 1000);
+ //Auth_version
+ buffer.append("&auth_version=1.0");
+ //MD5 body
+ buffer.append("&body_md5=");
+ buffer.append(md5Representation(jsonData));
+ //Event Name
+ buffer.append("&name=");
+ buffer.append(eventName);
+ //Append socket id if set
+ if (!socketID.isEmpty()) {
+ buffer.append("&socket_id=");
+ buffer.append(socketID);
+ }
+ //Return content of buffer
+ return buffer.toString();
+ }
+
+ /**
+ * Build path of the URI that is also required for Authentication
+ * @return
+ */
+ private static String buildURIPath(String channelName){
+ StringBuffer buffer = new StringBuffer();
+ //Application ID
+ buffer.append("/apps/");
+ buffer.append(pusherApplicationId);
+ //Channel name
+ buffer.append("/channels/");
+ buffer.append(channelName);
+ //Event
+ buffer.append("/events");
+ //Return content of buffer
+ return buffer.toString();
+ }
+
+ /**
+ * Build authentication signature to assure that our event is recognized by Pusher
+ * @param uriPath
+ * @param query
+ * @return
+ */
+ private static String buildAuthenticationSignature(String uriPath, String query){
+ StringBuffer buffer = new StringBuffer();
+ //request method
+ buffer.append("POST\n");
+ //URI Path
+ buffer.append(uriPath);
+ buffer.append("\n");
+ //Query string
+ buffer.append(query);
+ //Encode data
+ String h = buffer.toString();
+ return hmacsha256Representation(h);
+ }
+
+ /**
+ * Build URI where request is send to
+ * @param uriPath
+ * @param query
+ * @param signature
+ * @return
+ */
+ private static URL buildURI(String uriPath, String query, String signature){
+ StringBuffer buffer = new StringBuffer();
+ //Protocol
+ buffer.append("http://");
+ //Host
+ buffer.append(pusherHost);
+ //URI Path
+ buffer.append(uriPath);
+ //Query string
+ buffer.append("?");
+ buffer.append(query);
+ //Authentication signature
+ buffer.append("&auth_signature=");
+ buffer.append(signature);
+ //Build URI
+ try {
+ return new URL(buffer.toString());
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Malformed URI");
+ }
+ }
+
+ /**
+ * Delivers a message to the Pusher API without providing a socket_id
+ * @param channel
+ * @param event
+ * @param jsonData
+ * @return
+ */
+ public static HTTPResponse triggerPush(String channel, String event, String jsonData){
+ return triggerPush(channel, event, jsonData, "");
+ }
+
+ /**
+ * Delivers a message to the Pusher API
+ * @param channel
+ * @param event
+ * @param jsonData
+ * @param socketId
+ * @return
+ */
+ public static HTTPResponse triggerPush(String channel, String event, String jsonData, String socketId){
+ //Build URI path
+ String uriPath = buildURIPath(channel);
+ //Build query
+ String query = buildQuery(event, jsonData, socketId);
+ //Generate signature
+ String signature = buildAuthenticationSignature(uriPath, query);
+ //Build URI
+ URL url = buildURI(uriPath, query, signature);
+
+ //Create Google APP Engine Fetch URL service and request
+ URLFetchService urlFetchService = URLFetchServiceFactory.getURLFetchService();
+ HTTPRequest request = new HTTPRequest(url, HTTPMethod.POST);
+ request.addHeader(new HTTPHeader("Content-Type", "application/json"));
+ request.setPayload(jsonData.getBytes());
+
+ //Start request
+ try {
+ return urlFetchService.fetch(request);
+ } catch (IOException e) {
+ //Log warning
+ Log.warn("Pusher request could not be send to the following URI " + url.toString());
+ return null;
+ }
+ }
+
+}
View
89 PusherRequest.java
@@ -0,0 +1,89 @@
+
+
+import com.google.appengine.api.urlfetch.HTTPResponse;
+
+/**
+ * An instance of this class can be used to prepopulate properties (event or channel name) with default values
+ *
+ * @author Stephan Scheuermann
+ * Copyright 2010. Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+ */
+public class PusherRequest {
+
+ /**
+ * Name of the event
+ */
+ private String eventName;
+
+ /**
+ * Channel identifier
+ */
+ private String channelName;
+
+ /**
+ * Create an instance and assign a channelName that is used for each request
+ * @param channelName
+ */
+ public PusherRequest(String channelName) {
+ super();
+ this.channelName = channelName;
+ }
+
+ /**
+ * Create an instance and assign both channelName and eventName that are used for each request
+ * @param eventName
+ * @param channelName
+ */
+ public PusherRequest(String channelName, String eventName) {
+ super();
+ this.channelName = channelName;
+ this.eventName = eventName;
+ }
+
+ /**
+ * Triggers a new push with default channelName, eventName properties
+ * @param jsonData
+ * @return
+ */
+ public HTTPResponse triggerPush(String jsonData){
+ return Pusher.triggerPush(channelName, eventName, jsonData);
+ }
+
+ /**
+ * Triggers a new push with default channelName property
+ * @param jsonData
+ * @param eventName
+ * @return
+ */
+ public HTTPResponse triggerPush(String jsonData, String eventName){
+ return Pusher.triggerPush(channelName, eventName, jsonData);
+ }
+
+ /**
+ * Triggers a new push with default channelName property
+ * @param jsonData
+ * @param eventName
+ * @param socketId
+ * @return
+ */
+ public HTTPResponse triggerPush(String jsonData, String eventName, String socketId){
+ return Pusher.triggerPush(channelName, eventName, jsonData, socketId);
+ }
+
+ public void setEventName(String eventName) {
+ this.eventName = eventName;
+ }
+
+ public String getEventName() {
+ return eventName;
+ }
+
+ public void setChannelName(String channelName) {
+ this.channelName = channelName;
+ }
+
+ public String getChannelName() {
+ return channelName;
+ }
+
+}
View
38 README.md
@@ -0,0 +1,38 @@
+Pusher Java classes for Google App Engine
+=========================================
+
+This Java classes can be used to communicate very easily with the Pusher REST API (http://www.pusherapp.com) from any Google App engine Web application.
+
+Get Started
+-----------
+Simply replace the Pusher specific constants in Pusher.java:
+
+ private final static String pusherApplicationId = "";
+
+ private final static String pusherApplicationKey = "";
+
+ private final static String pusherApplicationSecret = "";
+
+Call one of the two available static methods called "triggerPush" and pass channel name, event name and the message body (JSON encoded data) as parameters:
+
+ Pusher.triggerPush("test_channel", "my_event", jsonData);
+
+The second "triggerPush" method provides an additional parameter for the socket_id:
+
+ Pusher.triggerPush("test_channel", "my_event", jsonData, socketId);
+
+That's it.
+
+Default values
+--------------
+Sometimes it can be very convenient to prepulate a PusherRequest instance with default channel and/or event name:
+
+ PusherRequest p = new PusherRequest("test_channel","my_event");
+
+To send a request to the Pusher API just call "triggerPush" on this instance:
+
+ p.triggerPush(jsondata);
+
+License
+-------
+Copyright 2010, Stephan Scheuermann. Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
Please sign in to comment.
Something went wrong with that request. Please try again.