Play Framework (1) – WebSocket

Even though Play uses async method to communicate with front-end and back-end. But sometimes, we still need to call third-party restful api to do something else. Especially, third-party restful api takes too long time to return data. In this case, it is wasteful to wait there and always ask whether it is ok or not. Good way to handle it is to make use of third-party callback and web socket to deal with this case.

So it is very clear that we have three sides: one is third-party api, the other is our back-end, another is our front-end.

What we need to do is that front-end builds a WebSocket with back-end. And then back-end requests data from third-party api. When third-party api returns data,  back-end sends data back to front-end.

  1. Build a web socket between front-end and back-end.
  2. front-end sends data to back-end
  3. back-end calls third-party api
  4. third-party api returns data back to back-end
  5. back-end returns data back to front-end by web socket

So, there are two routes, one is for web socket, another is for callback. The connection between the two is to store actorRef to keep the bridge. You also needs to ask callback to append special token back in order to figure out this actor.

  • Javascript code to send web socket to build connection between front-end and back-end.
  • var myWebSocket = new WebSocket("ws://<your_domain_name>/<your_ws_route>/");
    myWebSocket.onopen = function() { myWebSocket.send(<your_data>); }; 
    myWebSocket.onmessage = function(event) { alert(event.data); // to render data }; 
    myWebSocket.onclose = function() { console.log("wsconnect close."); };
  • Nginx configuration to build connection. Please note: Here if you don’t configure “

    proxy_read_timeout” attribute, your websocket default connection time is 60 seconds and then it is closed automatically. So here normally I set it as 1d.

  • location /daoApp/websocket/create/ {
      proxy_pass  http://<your_domain_name>;
      proxy_http_version 1.1;
      proxy_set_header Host $host;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      proxy_read_timeout 1d;
    }
  • Play routes to accept web socket request and another one is to accept callback request
  • POST    /<your_callback_route>/ controllers.WebSocketApp.apiCallback
    GET     /<your_ws_route>/       controllers.WebSocketApp.create
  • And next I have one global variable to store actorRef for each connect between front-end and back-end. For each web socket request, I will store unique key combining the actorRef and trigger call to third-party api. For each callback request, I will to find its mapping actorRef to send data back to front-end.
  • object WebSocketApp extends Controller {
      // remember to build a map to store your actorRef
      // here we use ConcurrentHashMap which is to avoid memory leak in multiple cores device
      var channels = new ConcurrentHashMap[String, ChannelMsg]().asScala
      def apiCallback = Action {
        request =>
          val requestBody = request.body.asFormUrlEncoded
          requestBody match {
            case Some(s) =>
              if (s.contains("parentID")) {
                val parentID = s("parentID").head
                val channelInfo = channels.get(parentID)
                channelInfo match {
                  case Some(info) =>
                    val yourParsedData = <deal_with_data_from_api>
                    actorInfo match {
                      case Some(actor) =>
                        actor ! <your_parse_data>
                        channels.remove(parentID)
                      case None =>
                    }
                  case None =>
                }
              }
            case None =>
          }
          Ok
      }
    
      def create = WebSocket.acceptWithActor[String, String] { request => out =>
        Props(new DAOWSCreateActor(out, request))
      }
    }
    class DAOWSCreateActor(out: ActorRef, request: RequestHeader) extends Actor {
      override def receive = {
        case topicName: String =>
          val queryTokenInfo = <your_send_request_to_third_party>
          // remember to involve your actorRef to channelMsg which will be used to parse actor 
          valchannelMsg = <construct_your_channel_msg>
          // remember to involve your key to callback on post which will be used to find actor back to send data back to front-end
          WebSocketApp.channels.put(<your_key>, channelMsg)
        case _ =>
      }
    }
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s