Sonson123
Sonson123

Reputation: 11447

Call multiple webservices from play 2

I am a play2.0-Scala-beginner and have to call several Webservices to generate a HTML page.

After reading the The Play WS API page and a very interesting article from Sadek Drobi I am still unsure what's the best way to accomplish this.

The article shows some code snippets which I don't fully understand as a Play beginner.

Figure 2 on page 4:

val response: Either[Response,Response] =
  WS.url("http://someservice.com/post/123/comments").focusOnOk

val responseOrUndesired: Either[Result,Response] = response.left.map {
  case Status(4,0,4) => NotFound
  case Status(4,0,3) => NotAuthorized
  case _ => InternalServerError
}

val comments: Either[Result,List[Comment]] = 
responseOrUndesired.right.map(r => r.json.as[List[Comment]])

// in the controller
comment.fold(identity, cs => Ok(html.showComments(cs)))

What does the last line with the fold do? Should comment be comments? Haven't I group the last statement in an Async block?

Figure 4 shows how to combine several IO calls with a single for-expression:

for {
  profile <- profilePromise
  events <- attachedEventsPromise
  articles <- topArticlesPromise
} yield Json.obj(
  "profile" -> profile,
  "events" -> events,
  "articles" -> articles )

}

// in the controller
def showInfo(...) = Action { rq =>
  Async {
    actorInfo(...).map(info => Ok(info))
  }
}

How can I use this snippet? (I am a bit confused by the extra-} after the for-expression.) Should I write something like this?

var actorInfo = for {                // Model
  profile <- profilePromise
  events <- attachedEventsPromise
  articles <- topArticlesPromise
} yield Json.obj(
  "profile" -> profile,
  "events" -> events,
  "articles" -> articles )

def showInfo = Action { rq =>         // Controller
  Async {
    actorInfo.map(info => Ok(info))
  }
}

What's the best way to combine the snippets from figure 2 and 4 (error handling + composition of IO non-blocking calls)? (f.ex. I want to produce a Error 404 status code if any of the called webservice produce an Error 404).

Maybe someone knows a complete example of calling webservices in the play framework (cannot find an example in the play Sample applications or anywhere else).

Upvotes: 2

Views: 1667

Answers (1)

thatsmydoing
thatsmydoing

Reputation: 863

I have to say that the article is wrong in the example you show in Figure 2. The method focusOnOk does not exist in Play 2.0. I assume the author of the article used a pre-release version of Play 2 then.

Regarding comment, yes it should be comments. The fold in the statement is operating on an Either. It takes 2 functions as parameters. The first is a function to apply if it is a left value. The second is a function to apply if it is a right value. A more detailed explanation can be found here: http://daily-scala.blogspot.com/2009/11/either.html

So what the line does is. If I have a left value (which meant I got an undesired response), apply the built-in identity function which just gives you back the value. If it has a right value (which means I got an OK response), make a new result that shows the comments somehow.

Regarding Async, it's not actually asynchronous. focusOnOk is a blocking function (a remnant from the old Java days of Play 1.x). But remember, that's not valid Play 2 code.

As for Figure 4, the trailing } is actually because it's a partial alternative of what's in Figure 3. Instead of the numerous promise flatMaps. You can do a for comprehension instead. Also, I think it should be userInfo(...).map instead of actorInfo(...).map.

The Play documentation you linked to actually already shows you a full example.

def feedTitle(feedUrl: String) = Action {
  Async {
    WS.url(feedUrl).get().map { response =>
      Ok("Feed title: " + (response.json \ "title").as[String])
    }
  }  
}

will get whatever is at feedUrl, and you map it to do something with the response which has a status field you can check to see if it was a 404 or something else.

To that end, the Figure 3 and 4 of your linked article should give you a starting point. So you'd have something like,

def getInfo(...) : Promise[String] = {
  val profilePromise = WS.url(...).get()
  val attachedEventsPromise = WS.url(...).get()
  val topArticlesPromise = WS.url(...).get()

  for {
    profile <- profilePromise
    events <- attachedEventsPromise
    articles <- topArticlesPromise
  } yield {
    // or return whatever you want
    // remember to change String to something else in the return type
    profile.name 
  }
}

def showInfo(...) = Action { rq =>
  Async { 
    getInfo(...).map { info =>
      // convert your info to a Result
      Ok(info)
    }
  }
}

Upvotes: 2

Related Questions