2

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?

Hristo
  • 45,559
  • 65
  • 163
  • 230

1 Answers1

2

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);
Yevgeniy Brikman
  • 8,711
  • 6
  • 46
  • 60
  • I'm getting an error with `scala.concurrent.Promise.apply();`... method cannot be resolved. also, should `F.Promise` and `new Function` be from `play.libs.F.java`? – Hristo Apr 13 '14 at 09:48
  • Yea, sorry, accessing `scala.concurrent.Promise.apply()` from Java code is a little trickier: you either use `Promise$.MODULE$`, which is ugly (see: [Trait Companion Object is not visible in Java](http://stackoverflow.com/questions/9436074/scala-trait-companion-object-is-not-visible-in-java)) or you create a tiny Scala wrapper that exposes the `apply` method in a cleaner way, as in the updated example above. – Yevgeniy Brikman Apr 13 '14 at 20:09
  • awesome! I did something similar last night... `def newPromise[T](): Promise[T] = { Promise[T]() }`... and it seemed to work as well – Hristo Apr 13 '14 at 20:20
  • this works perfectly. only thing is, IntelliJ things there's an error: `Incompatible types. Required: scala.concurrent.Promise Found: scala.concurrent.Promise` even if I change it to be `scala.concurrent.Promise unredeemed = PromiseUtility.newPromise();` – Hristo Apr 13 '14 at 20:23
  • You probably don't have Scala setup correctly in IntelliJ then. – Yevgeniy Brikman Apr 13 '14 at 20:54