Let's look at some output (classic timing code kludged into Part 1's solution - you don't need to see it):
Pinging 'http://www.bar.net' Pinged 'http://www.bar.net' - got result: 'HTTP/1.1 200 OK' in 440ms Pinging 'http://www.baz.com' Pinged 'http://www.baz.com' - got result: 'HTTP/1.1 200 OK' in 240ms Pinging 'http://www.zomg.com' Pinged 'http://www.zomg.com' - got result: 'HTTP/1.1 200 OK' in 230ms Pinging 'http://fe.zomg.com' Pinged 'http://fe.zomg.com' - got result: 'HTTP/1.1 200 OK' in 242ms Entire operation took 1155msRealistically, we should be constrained only by the slowest element (just like school, right?) and so our "Entire operation" time should be something like 450ms, give or take. Let's fix this up.
I'll be using Play's WS features to achieve this, which means that as a bonus, I get to drop my dependency on Apache HTTP Client. Nothing against it, but less code (even somebody else's) is always better code. Smaller code search-space, smaller deployment artifact, win!
With a lot of help from the Play Async doco and the Akka Futures explanation, I came up with the following changes to the previous single-threaded solution:
trait Pingable extends Addressable { def ping : Future[(String, String, Long)] = { println("Pinging '" + address + "'") val startTime = Platform.currentTime WS.url( address ).get().map { response => val endTime = Platform.currentTime val time = endTime - startTime println("Pinged '" + address + "' - got result: '" + response.status + "' in " + time + "ms") (address, response.statusText, time) } } } def sendPing = Action { val pingTargets = configuration.getStrings("targets") val startTime = Platform.currentTime val futurePingResults : List[Future[(String, String, Long)]] = pingTargets.map( _ ping) Async { val results = Future.sequence(futurePingResults) results.map { tuples => val endTime = Platform.currentTime val time = endTime - startTime println("Entire operation took " + time + "ms") Ok(html.ping(tuples)) } } }Things to note:
- ping() now returns a Future Tuple3, which will eventually hold the address, status and ping response time
- This is the result of calling map on the WS's get() which is already returning a Future - we're essentially just massaging the actual return type to the one we want
- The pingTargets.map() call is unchanged, only its return type (stated explicitly for clarity) has altered
- The Async block tells Play that we'll be dealing with Futures from here on
- And, perhaps least obviously, but most importantly of all, the Future.sequence has the very important task of translating a List of Future triples into a Future List of triples, giving us just one thing to wait for instead of many
Pinging 'http://www.foo.net' Pinging 'http://www.bar.net' Pinging 'http://www.baz.com' Pinging 'http://www.zomg.com' Pinging 'http://fe.zomg.com' Pinged 'http://www.foo.net' - got result: '200' in 226ms Pinged 'http://www.bar.net' - got result: '200' in 427ms Pinged 'http://www.zomg.com' - got result: '200' in 435ms Pinged 'http://www.baz.com' - got result: '200' in 441ms Pinged 'http://fe.zomg.com' - got result: '200' in 458ms Entire operation took 463ms
Aaaand, strut :-)