Showing posts with label play2. Show all posts
Showing posts with label play2. Show all posts

Wednesday, March 26, 2014

Play Framework Comet / Chunking Support



So my first choice when it comes to full duplex communication with a web server is websockets. Play has amazing support (non-blocking/async) support for websockets and there are plenty of example inside activator templates. But what do we do for those browsers that don't support websockets? In particular Android default browser 4.3 (Jellybeen) and below.

Note: 4.4+ Kitkat will now have chrome as the native browser [websockets, webrtc, webgl, and much more]

In cases like this we need to fallback to some form of "comet" or "long-polling" technique. However in the spirit of play we want to be non-blocking and asynchronous with our approach. With a little bit of digging on google we can find some info on how this was done in play 2.0. This stackoverflow article talks a bit about how comet works in play 2.0

public static void newMessages() {
    List messages = Message.find("date > ?", request.date).fetch();
    if (messages.isEmpty()) {
        suspend("1s");
    }
    renderJSON(messages);
}

The key bit here is suspend("1s") which is what holds the HTTP request open, checking for new data once per second.

However suspend is not going to work for us in play 2.1+ so we need to find another solution. A number of things changed from 2.0 to 2.1 and in particular the "SimpleResult" is now the return type for all actions. They have done away with the other result types and folded them in under simple result. There are some good notes here on migrating to play 2.2. One of the things we can see is the use of a Chunked result

Working with chunked results

Previously the stream method on Status was used to produce chunked results. This has been deprecated, replaced with a chunked method, that makes it clear that the result is going to be chunked. For example:

def cometAction = Action {
  Ok.chunked(Enumerator("a", "b", "c") &> Comet(callback = "parent.cometMessage"))
}

Advanced uses that created or used ChunkedResult directly should be replaced with code that manually sets/checks the TransferEncoding: chunked header, and uses the new Results.chunk and Results.dechunk enumeratees.

So this looks promising. However we still need to make a choice on what we are going to do as a generator for the Enumerator. My first idea was to use to use Enumerator.fromCallback1. This looked like it should work, and even compiles fine, but no chuncked response. After doing some digging I found that my return type looked a little strange and that this may be a bug in "fromCallback1". I found someone else having the same issue in this post here.

Next I played around with a Promise.timeout and a Enumerator.generateM. There is an example of this style of comet usage on the comet-clock. The lines to note here are the following:

Enumerator.generateM {
   Promise.timeout(Some(dateFormat.format(new Date)), 100 milliseconds)
}

...

def liveClock = Action {
   Ok.chunked(clock &> Comet(callback = "parent.clockChanged"))
}

After some more time trying to get this new approach to work, I stumbled into a discussion around Concurrent.broadcast. Which as it turns out... is exactly how I am doing my producer / consumer for my websocket... doh! Should have looked here right at the start :)

Concurrent.broadcast

This article on stackoverflow talks about the Concurrent.broadcast. After a little bit of time playing with this I got a working example. Here are the steps involved.

First you need to get you producer and consumer tuple.

   val (cometOut, cometChanel) = Concurrent.broadcast[JsValue]

Now you can use the cometChannel to push Json data into the cometOut Enumerator.

   cometChanel.push(Json.obj("data" -> "test"))

And finally you can plug your enumerator into the play Comet helper for a chuncked data response.

   Ok.chunked(cometOut &> Comet(callback = "parent.cometMessage"))

Chuncked response and what can go wrong

First off the comet helper in play does some "padding" to get around an issue with chunking. This can be seen with this gist.

 def apply[E](callback: String, initialChunk: Html = Html(Array.fill[Char](5 * 1024)(' ').mkString + ""))(implicit encoder: CometMessage[E]) = new Enumeratee[E, Html] {

Note the Array.fill[Char](5 * 1024)(' ').mkString. So if you are not using the Comet helper in play, you will need to do something similar to have your chunked response work. You can find more information on this here.

Nginx and HTTP 1.1

If you are using nginx to proxy your request like I am, then there are further problems to deal with. First Nginx defaults to HTTP 1.0 which does not have chucking support. You need to explicitly tell your nginx config to use HTTP 1.1.

   location / {
     proxy_http_version 1.1;
     proxy_pass http://localhost:9009/;
     proxy_set_header Host $http_host;
  }

There is another problem

If you view your endpoint now using curl, everything will work fine... but wait. If you try your comet solution in chrome or firefox you will get the infinite loading spinner and you will never see any chunked data until the http request is closed.

If you point your browser directly at play however you will notice that everything is working fine. This again points to a problem within nginx. After using curl to examine the headers for both request, I noticed that nginx response headers contain the line Connection: keep-alive. Browsers seem to wait for the connection to close for a chunked transfer before returning the data.

So we are left trying to get rid of the Connection: keep-alive from the nginx response headers. Unfortunately this is not a configurable option inside nginx and you are left patching it from source. I have posted a comment here.

Update: The solution is to turn GZIP off for nginx
The white space that play pushes into the buffer (described about) obviously compresses very well with gzip... totally defeating the purpose of the buffer to begin with. The solution is to turn off gzip. NOTE: that I don't like this solution and will be looking for alternatives to play http 1.1 chunking.

Update: There is more info surrounding this issue in the above post

Nginx Websocket configuration

https://github.com/dryan/decss-sync/issues/1

Also ran into problems with nginx closing the socket after 1 min. This is related to proxy_read_timeout.. more about this here http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout.

Thursday, December 26, 2013

Neo4J user service plugin for secure social (play framework/scala)

SecureSocial-Neo4JUserService

Neo4J user service plugin for secure social (play framework/scala)

Project on GitHub: SecureSocial-Neo4JUserService


Background

Building reactive web applications with play framework and scala often starts with a social login system. One great way to get started here is to use Secure Social. They provide a simple way to get up and running with a large number of providers. Once you are running you will need to store your account information and more the likely the relationships between your social users "friends". This is where Neo4J really shines.

Requirements

Setup

Once you have secure social up and running all you need to do is add this scala file to your play project. I created a app/servicies directory to place the file. Next you simply need to add a line to your play.plugins

play.plugins

9998:service.Neo4JUserService

Neo4J Structure

Users will now be added to your neo4J with the following structure

(u:User)-[:HAS_ACCOUNT]->(p:Provider)

You can also use the utility methods outlined below to make users friends

(u1:User)-[:FRIEND]->(u2:User)

Helper methods

Here are number of usefull methods for helping you to work with secure social and Neo4J

object  Neo4JUserService{
  def socialUserFromMap( umap: Map[String, String]): Identity = {
    ...
  }

  def makeUsersFriends( uuid1: String, uuid2: String ) = future{
    ...
  }

  def socialUserFromToken(provider: String, token: String) = future{
    ...
  }

  def uuidFromProviderInfo( provider: String, id: String, name: String = "" ) = future {
    ...
  }


  private def createUserAndProvider(name: String, id: String, fullname: String) = {
    ...
  }
}

Things to consider.

You will still need a way for users to "link" multiple providers on your back-end. Currently if a user signs in using another provider, they will get another user and provider record in Neo4J. You could try to combat this at the login level by looking for emails that are same as other providers (you would want to verify the email before linking for security reasons)

Another way would be a settings section on your site when a user is loged in, that would allow them to "link" their other accounts. In this manor you would want to create to continue to build a structure like the following

        /[:HAS_ACCOUNT]->(p0:Provider)
(u:User)-[:HAS_ACCOUNT]->(p1:Provider)
        \[:HAS_ACCOUNT]->(p2:Provider)

Friday, November 29, 2013

Zero to Play with Linux Neo4j, Play2 and nginx

This is a guide for taking your (debian like) linux system from a fresh install to staging server.

sudo -s
apt-get update
apt-get dist-upgrade

# install Sun java 7
echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu precise main" | tee -a /etc/apt/sources.list
echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu precise main" | tee -a /etc/apt/sources.list
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886
apt-get update
apt-get install oracle-java7-installer

# install scala
apt-get install scala

# build tools
apt-get install build-essential

# install recent nodejs
sudo apt-get update
sudo apt-get install -y python-software-properties python g++ make
sudo add-apt-repository ppa:richarvey/nodejs #or sudo apt-get-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs nodejs-dev npm

# install nginx
apt-get install nginx


# get play
wget http://downloads.typesafe.com/play/2.2.1/play-2.2.1.zip

# unzip this somewhere
vim .bashrc

# add play to path
export PATH=$PATH:/path/to/play

# install neo4j

# start root shell
sudo -s
# Import our signing key
wget -O - http://debian.neo4j.org/neotechnology.gpg.key | apt-key add - 
# Create an Apt sources.list file
echo 'deb http://debian.neo4j.org/repo testing/' > /etc/apt/sources.list.d/neo4j.list
# Find out about the files in our repository
apt-get update
# Install Neo4j, community edition
apt-get install neo4j
# start neo4j server, available at http://localhost:7474 of the target machine
/var/lib/neo4j/bin/neo4j start


# install git
apt-get install git

# clone projects
...
...

# make sure things are good
cd projects/dir/
play compile

# test running the server
play run

# compile staging package
play clean compile stage


# setup your init scripts to keep it alive
vim /etc/init.d/play.myapp

###################################################
## Init Script
###################################################
#!/bin/bash

APPLICATION_PATH=/home/user/projects/myplayapp

start() {
    echo -n "Starting"
    sudo start-stop-daemon --start --background --pidfile ${APPLICATION_PATH}/RUNNING_PID -d ${APPLICATION_PATH} --exec target/start -- -Dhttp.port=9000
    RETVAL=$?

    if [ $RETVAL -eq 0 ]; then
        echo " - Success"
    else
        echo " - Failure"
    fi
    echo
}
stop() {
    echo -n "Stopping"
    sudo start-stop-daemon --stop --pidfile ${APPLICATION_PATH}/RUNNING_PID

    RETVAL=$?

    if [ $RETVAL -eq 0 ]; then
        echo " - Success"
    else
        echo " - Failure"
    fi
    echo
}

  case "$1" in
    start)
      start
  ;;
    stop)
      stop
  ;;
    restart)
      stop
      start
  ;;
    *)
      echo "Usage: play-server {start|stop|restart}"
      exit 1
  ;;
esac
exit $RETVAL



## END INIT



#make it executable 
chmod +x /etc/init.g/play.myapp

# test it
/etc/init.d/play.myapp start

# now make it start on boot
update-rc.d play.myapp defaults


# finally lets get some proxy action
# edit nginx.conf adding servers for each of your projects...
vim /etc/nginx/nginx.conf


######### SAMPLE SECTION TO ADD in http{ ...
# add 1 sever per domain. Also note that you can have wildcards *
 server {
        listen       80;
        server_name  *.mysite.com;
               
        location / {
           proxy_pass http://localhost:9000/;
           proxy_set_header Host $http_host;
        }
# 404 ect...
}

# now restart nginx
/etc/init.d/nginx restart


Lets assume you had a number of sites on one server. You could have a number of play applications running on various ports. Then in nginx.conf you would define a sever rule for each site. Also MAKE sure you pass through the Host header or you will run into issues. (ex: secure_socaiul sending redirect urls as "localhost:9000"

Thursday, October 24, 2013

Openfire Plugin Dev / Customization

Openfire Plugin Dev / Customization

Now that I have openfire up and running... the next thing I want to do is to customize it to my needs. The normal way to extend openfire is by way of plugin. This allows you to enjoy future upgrades of openfire while not blowing away your source code additions. Admittedly it took a little more digging that I would have liked to find the right getting started guide.

Im my case I decided to go with using eclipse (Scala IDE). Originally I had hopes of writing my plugins in scala.. but ran into a number of problems there, so in the interest of time decided to continue forward with eclipse java. The first guide you should look at to get setup in eclipse is the following doc. Once you have that setup you may want to look at this one as well regarding building all the plugins.

One of the other things that you will notice is the ability to customize the data storage. The architecture here was really focused on JDBC and a SQL back-end... In my case I am using a graph database store so none of this helps me. Looking a little further into it and seems that replacing the JDBC provider with a custom implementation would not take to long ref here. However in the interest of time I have put it on the list of TODO: and forged ahead. There is however a method that will get us partly there. I will outline the goals and methods for achieving them in the following paragraphs

Goals

The idea here is that I would like to replace the "user" and "roster" aka "buddy" list of openfire with my own graphdb full of FOAF like relations. The next thing is I don't want to be slowed down at this stage in dev... so it is ok to leave some "hacks" in the interest of time and then circle back.

Method

In browsing the Plugins I noticed the "userservice". Here is a bit of text right out of the README

The User Service Plugin provides the ability to add,edit,delete users and manage their rosters by sending an http request to the server. It is intended to be used by applications automating the user administration process. This plugin's functionality is useful for applications that need to administer users outside of the Openfire admin console. An example of such an application might be a live sports reporting application that uses XMPP as its transport, and creates/deletes users according to the receipt, or non receipt, of a subscription fee. 


Perfect... this solves our quick and dirty way of getting our users into the system. Next we just need to deal with the roster... so that we get the proper "presence" requests for users coming on and offline.

Again a bit of digging lead me to the creation of a Custom Roster Item Provider. Here is the documentation that describes how to implement.

With not a ton of code, I was able to write a simple Roster Provider that calls my Scala Play webservice. The webservice of course is a REST Json service wrapper for all the good things stored in the graphdb :)

Few last points

In order to get things to work correctly I had to return roster items using the constructor that takes a domain (for example: jid@mydomain.com). But when creating that user inside open fire we only want to pass in the "jid" portion for the user name. Failing this the two ends will not be linked how you would expect them to be.