Making a web service request with a timeout from a Play Framework Controller

My original "Day 1 with Play web services" code is shown a few paragraphs below, but on Day 2, the following code looks like an easier solution to the problem, courtesy of this web page:

/**
 * Get the weather forecast from the Yahoo Weather service.
 * Times out if a response is not received in five seconds.
 */
def getWeather = Action {
  import play.api.libs.ws.{WS, Response}
  import java.util.concurrent.TimeoutException
  val promiseOfString: Promise[Response] = WS.url("http://weather.yahooapis.com/forecastrss?p=80020&u=f").get()
  try {
    Ok(promiseOfString.await(5000).get.body)
  } catch {
    case e: TimeoutException => InternalServerError("Request timed out.")
  }
}

In this code, if the timeout is exceeded, the await method will throw a TimeoutException, and the InternalServerError message will be displayed in the browser. If the web service call is successfully completed in less than 5000 ms, the response body is shown in the browser with the Ok method.

Of course in the real world you'll probably want to do more work with the content you retrieve, but for the purpose of creating a simple example to get you started with Play Framework web service GET requests that support a timeout, this should do the trick.

Feel free to read the rest of this blog post to learn about the more complicated approach I started with, otherwise just take this code and run with it.

My Day 1 Play Framework "web services with a timeout" code

I'm not going to take too much time to describe this today, in part because I'm not convinced that this is 100% right, but ... if you want to make a web service request with a timeout from a Scala Play Framework Controller object, this code seems to work:

def getWeather = Action {
  import play.api.libs.concurrent.Akka
  import play.api.libs.ws.{WS, Response}
  import play.api.Play.current
  val promiseOfString = Akka.future {
    WS.url("http://weather.yahooapis.com/forecastrss?p=80020&u=f").get().map { response =>
      response.body
    }
  }
  Async {
    promiseOfString.orTimeout("Oops", 5000).map { eitherStringOrTimeout =>
      eitherStringOrTimeout.fold(
        content => Ok(content.value.get),
        timeout => InternalServerError("Timeout Exceeded!")
      )    
    }  
  }
}

Just put this method is in a Controller object named Application, then add this line to your conf/routes file:

GET  /getWeather    controllers.Application.getWeather

Next, access this web service in your browser at http://localhost:9000/getWeather. If the data is retrieved in less than five seconds, you'll see it displayed in your browser; if the timeout is exceeded, you'll see the "Timeout Exceeded" error message instead.

If you're familiar with the Play Framework, hopefully this makes sense. If you're really familiar with the Play Framework and you think I'm having an LSD flashback, please leave a note in the Comments section on what I've done wrong, and I'll get it corrected. (I just started working with Play web services today, so I'm at a point where I know this works, but I don't know if it's right.)

For more information on what the heck I'm doing (or trying to do), see these Play Framework URLs: