Return untranslated results from a Play!2 WS call (in Scala) - web-services

I'm writing a play Controller that should call another web service and return its result verbatim -- same response code, same headers, same body. But it seems like the Controller is written such that I have to specify an explicit return code. I've tried getting the ahcResponse, but that doesn't seem to provide an obvious solution.
Here's what I have now:
def route(name: String, command: String) = Action {
Async {
(
WS.url("someurl").get().map {
(
response => Ok(response.body))
})
}
}
However, this always returns an "OK" status, and if it gets an error, it will pull the error HTML into the body as text.
How do I forward the results of a WS call back to my caller?

You could forward the response code and body in the following way:
WS.url(url)
.get
.map(response =>
response.status match {
// in case you want to do something special for ok
// otherwise, pattern matching is not necessary
case OK => Ok(response.body)
case x => new Status(x)(response.body)
})
.recover {
case ex: Throwable =>
InternalServerError("some exception...")
}

Related

Test JSON-returning controller method without MissingViewError

I am testing a Controller method that has only a JSON view. My method runs as expected, but the test method only returns "MissingViewException". Is there a solution to avoiding this exception in the unit test (besides inserting an empty file at View/People/map_leads.ctp)?
PeopleController.php
public function mapLeads($territory_id = null) {
$leads = $this->Person->getPeople([
'territory_id' => $territory_id
]);
$this->set('leads', $leads);
}
AppController.php
public $components = ['RequestHandler'];
routes.php
Router::parseExtensions('json');
PeopleControllerTest.php
public function testMapLeads() {
$id = 40;
$result = $this->testAction('/people/mapLeads/' . $id, array('return' => 'vars'));
}
View/People/json/map_leads.ctp exists and is properly utilized by CakePHP; it is only the test that wants to see View/People/map_leads.ctp.
I checked at CakePHP: calling testAction to a json-returning method causes missing view exception reminding about adding RequestHandler to $components. This does not resolve the exception.
You aren't issuing a JSON request/accessing a JSON endpoint, as neither your request URL does contain the .json extension, nor does your request send an appropriate Accept header (I don't remember whether the latter is possible with the 2.x controller test case class at all).
Use the .json extension and you should be good.
$this->testAction('/people/mapLeads/' . $id . '.json', array('return' => 'vars'));
Write this code inside your action.
$this->autoLayout = false;
$this->autoRender = false;
$this->response->type('application/javascript');

Akka Ask Pattern on future recover

I'm writing a Spray + Akka small server. And a blog post stood out and recommended this ask pattern. The CapserOk class has a signiture of this:
case class CasperOk(company: Option[Company.Company], existOrNot: Boolean)
I'm retrieving this database row and if it doesn't exist or some error occured, I want to send out a response from Spray to tell the client.
However, using this pattern, I don't know where to inject the switch.
I tried to change the code to this:
val response = (secCompanyActor ? jObjectFromCasper(company))
.mapTo[CasperOk]
.map(result => result.succeedOrNot match {
case true => (OK, "transaction successful")
case false => (Conflict, "FileLoc format is wrong or it already exists")
})
.recover{case _ => (InternalServerError, "error occured! We will fix this")}
complete(response)
Spray uses complete() to send out a HTTP response. complete() can either take two objects, or a string/serializable object. I want to use the two object mode (which allows me to manually encode its header), and an ideal situation should look like complete(response._1, response._2)
Is there any way to achieve this with Akka's Future?
You can achieve this via registering a call to complete function on future onComplete method.
response.onComplete {
case (statusCode, message) => complete(statusCode, message)
}

test of angularjs controller: unsatistifed post request

I'm testing an angularjs controller, using also mocks, but it raises the error 'Error: Unsatisfied requests: POST /myurl
My file for test contains a beforeEach method like this
httpBackend.whenPOST('/myurl')
.respond( 200,obj1 );
httpBackend.expectPOST('/myurl')
scope = $rootScope.$new();
MainCtrl = $controller('MyCtrl', {
$scope:scope
});
and my test case is:
it('scope.mymethod should work fine', function(){
httpBackend.flush()
// verify size of array before calling the method
expect(scope.myobjs.length).toEqual(2)
// call the method
scope.saveNewPage(myobj)
// verify size of array after calling the method
expect(scope.myobjs.length).toEqual(3)
})
The method saveNewPage looks like:
function saveNewPage(p){
console.log('Hello')
$http.post('/myurl', {
e:p.e, url:p.url, name:p.name
}).then(function (response) {
otherMethod(new Page(response.data.page))
}, handleError);
}
Note that console.log('Hello') is never executed (in karma console it's never printed).
EDIT:
In the meanwhile I'm studying the doc about httpBackend, I tried to change the position of httpBackend.flush(). Basically, i'm executing a first flush(), to initialize data in the scope, then I execute the method, and then I execute an other flush() for the pending request. Specifically, in this situation the test case look like:
it('scope.saveNewPage should work fine', function(){
var p=new Object(pages[0])
httpBackend.flush()
httpBackend.whenPOST('/myurl',{
url:pages[0].url,
existingPage:new Object(pages[0]),
name:pages[0].name
}).respond(200,{data:pages[0]})
httpBackend.expectPOST('/myurl')
scope.saveNewPage(p)
httpBackend.flush()
expect(scope.pages.length).toBe(3)
})
But now it raises Error: No response defined !, like if I didn't specified the mock for that url
I solved in this way:
I put the calls of whenPOST and expectPOST before calling the method to test
I put httpBackend.flush() after calling the method to test, such that, invoking the method it generates pending request, and by httpBackend.flush() it satisfies the pending requests
I adjusted the parameter of respond method. Basically it didn't need to associate the response to a data key of the response
Assuming the POST is supposed to come from saveNewPage, you will need to call httpBackend.flush() between saveNewPage and the line where you inspect the result. flush only flushes the responses that have already been requested by your code.
it('scope.mymethod should work fine', function(){
expect(scope.myobjs.length).toEqual(2)
scope.saveNewPage(myobj)
expect(scope.myobjs.length).toEqual(2)
httpBackend.flush()
expect(scope.myobjs.length).toEqual(3)
})

Scala Play2 Error on Web Service call

I'm getting an error on compile with the following code.
I'm trying to call a Web Service.
def authenticate(username: String, password: String): String = {
val request: Future[Response] =
WS.url(XXConstants.URL_GetTicket)
.withTimeout(5000)
.post( Map("username" -> Seq(username), "password" -> Seq(password) ) )
request map { response =>
Ok(response.xml.text)
} recover {
case t: TimeoutException =>
RequestTimeout(t.getMessage)
case e =>
ServiceUnavailable(e.getMessage)
}
}
I'm seeing the following compiler error:
type mismatch; found : scala.concurrent.Future[play.api.mvc.SimpleResult[String]] required: String
The value being returned from your authenticate function is val request = ... which is of type Future[Response] but the function expects a String which as the compiler says is a type mismatch error. Changing the return type of the function to Future[Response] or converting request to a String before returning it should fix it.
Like say Brian, you're currently returning a Future[String], when you method said that you want to return a String.
The request return a Future because it's an asynchronous call.
So, you have two alternatives:
Change your method definition to return a Future[String], and manage this future in another method (with .map())
Force the request to get this result immediately, in a synchronous way. It's not a very good deal, but sometimes it's the simplest solution.
import scala.concurrent.Await
import scala.concurrent.duration.Duration
val response: String = Await.result(req, Duration.Inf)

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).
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)
}
}
}