Search code examples
javaasynchronousplayframework-2.2continuations

how do I suspend a request in Play Framework?


I'm playing around with the Play Framework (v2.2.2), and I'm trying to figure out how to suspend an HTTP request. I'm trying to create a handshake between users, meaning, I want user A to be able to fire off a request and wait until user B "connects". Once the user B has connected, user A's request should return with some information (the info is irrelevant; let's just say some JSON for now).

In another app I've worked on, I use continuations to essentially suspend and replay an HTTP request, so I have something like this...

@Override
public JsonResponse doGet(HttpServletRequest request, HttpServletResponse response) {

  Continuation reqContinuation = ContinuationSupport.getContinuation(request);
  if (reqContinuation.isInitial()) {
    ...
    reqContinuation.addContinuationListener(new ContinuationListener() {
      public void onTimeout(Continuation c) {...}
      public void onComplete(Continuation c) {...}
    });
    ...
    reqContinuation.suspend();
    return null;
  }
  else {
    // check results and return JsonResponse with data
  }
}

... and at some point, user B will connect and the continuation will be resumed/completed in a different servlet. Now, I'm trying to figure out how to do this in Play. I've set up my route...

GET    /test        controllers.TestApp.test()

... and I have my Action...

public static Promise<Result> test() {

  Promise<JsonResponse> promise = Promise.promise(new Function0<JsonResponse>() {
      public JsonResponse apply() {
        // what do I do now...?
        // I need to wait for user B to connect
      }
  });

  return promise.map(new Function<JsonResponse, Result>() {
      public Result apply(JsonResponse json) {
        return ok(json);
      }
  });
}

I'm having a hard time understanding how to construct my Promise. Essentially, I need to tell user A "hey, you're waiting on user B, so here's a promise that user B will eventually connect to you, or else I'll let you know when you don't have to wait anymore".

How do I suspend the request such that I can return a promise of user B connecting? How do I wait for user B to connect?


Solution

  • You need to create a Promise that can be redeemed later. Strangely, the Play/Java library (F.java) doesn't seem to expose this API, so you have to reach into the Scala Promise class.

    Create a small Scala helper class for yourself, PromiseUtility.scala:

    import scala.concurrent.Promise
    
    object PromiseUtility {
      def newPromise[T]() = Promise[T]()
    }
    

    You can then do something like this in a controller (note, I don't fully understand your use case, so this is just a rough outline of how to use these Promises):

    if (needToWaitForUserB()) {
      // Create an unredeemed Scala Promise
      scala.concurrent.Promise<Json> unredeemed = PromiseUtility.newPromise();
    
      // Store it somewhere so you can access it later, e.g. a ConcurrentMap keyed by userId
      storeUnredeemed(userId, unredeemed);
    
      // Wrap as an F.Promise and, when redeemed later on, convert to a Result
      return F.Promise.wrap(unredeemed.future()).map(new Function<Json, Result>() {
        @Override
        public Result apply(Json json) {
          return ok(json);
        }
      });
    }
    
    // [..]
    // In some other part of the code where user B connects
    
    scala.concurrent.Promise<Json> unredeemed = getUnredeemed(userId);
    unredeemed.success(jsonDataForUserB);