I have ServerA which exposes an API method for a client, which looks like this:
def methodExposed()= Action.async(json) { req =>
val reqAsModel = request.body.extractOpt[ClientRequestModel]
reqAsModel match {
case Some(clientRequest) =>
myApiService
.doSomething(clientRequest.someList)
.map(res => ???)
case None =>
Future.successful(BadRequest("could not extract request"))
}
}
So, I have a case class for the client request and if I cannot extract it from the request body, then I return a BadRequest with the message and otherwise I call an internal apiService to perform some action with this request.
doSomething performs an API call to ServerB that can return 3 possible responses:
200 status
400 status with body that I need to extract to a case class
500 status
doSomething looks like this:
def doSomething(list: List[String]) = {
wSClient.url(url).withHeaders(("Content-Type", "application/json")).post(write(list)).map { response =>
response.status match {
case Status.BAD_REQUEST =>
parse(response.body).extract[ServiceBResponse]
case Status.INTERNAL_SERVER_ERROR =>
val ex = new RuntimeException(s"ServiceB Failed with status: ${response.status} body: ${response.body}")
throw ex
}
}
}
Now I have two issues:
Since the 200 returns with no body and 400 has a body, I don't know what should be the return type of doSomething
How should I handle this in the controller and return the response to the client properly in methodExposed?
I would do something like this:
case class ServiceBResponse(status: Int, body: Option[String] = None)
And then, doSomething would be like:
def doSomething(list: List[String]) = {
wSClient.url(url).withHeaders(("Content-Type", "application/json")).post(write(list)).map { response =>
response.status match {
case Status.OK =>
ServiceBResponse(response.status)
case Status.BAD_REQUEST =>
ServiceBResponse(response.status, Option(response.body))
case Status.INTERNAL_SERVER_ERROR =>
val message = s"ServiceB Failed with status: ${response.status} body: ${response.body}"
ServiceBResponse(response.status, Option(message))
}
}
}
Finally, inside the controller:
def methodExposed() = Action.async(json) { req =>
val reqAsModel = request.body.extractOpt[ClientRequestModel]
reqAsModel match {
case Some(clientRequest) =>
myApiService
.doSomething(clientRequest.someList)
.map(serviceBResponse => Status(serviceBResponse.status)(serviceBResponse.getOrElse("")))
case None =>
Future.successful(BadRequest("could not extract request"))
}
}
Another alternative is directly use WSResponse:
def doSomething(list: List[String]) = {
wSClient
.url(url)
.withHeaders(("Content-Type", "application/json"))
.post(write(list))
}
And the controller:
def methodExposed() = Action.async(json) { req =>
val reqAsModel = request.body.extractOpt[ClientRequestModel]
reqAsModel match {
case Some(clientRequest) =>
myApiService
.doSomething(clientRequest.someList)
.map(wsResponse => Status(wsResponse.status)(wsResponse.body))
case None =>
Future.successful(BadRequest("could not extract request"))
}
}
If 400 is a common expected error, I think the type Future[Either[Your400CaseClass, Unit]] makes sense. In terms of how methodExposed returns the result to the client depends on your business logic:
Is the underlying 400 something the client should be informed of? If methodExposed should return a 500 to the client when doSomething encounters a 400
Otherwise you can propagate the error to the client. Depending on the business logic, you may or may not want to transform your case class into another form, and potentially with a different http code.
You should throw an exception (using Future.failed) if doSomething returns 500 (or more generally, any unexpected http code).
(Lastly, I hope you're not using 500 to communicate a 'normal' error like validation / authentication error. 5xx code should only be used for exceptional and unrecoverable errors. Most http clients I know will throw an exception immediately when a 5xx is encountered, which means the user won't get the chance to handle it)
Related
Because I do some "complex" operations, I think my Actor became asyncronous. The main problem I think is that I use Await.result inside the method which return responses.
actor:
def process(subscribers: Set[ActorRef]): Receive = {
case Join(ref) => context become process(subscribers + ref)
case Leave(ref) => context become process(subscribers - ref)
case Push(request) =>
val filteredSubscribers = (subscribers - sender())
.filter(s => exists(s, request)) // just some actor filters
filteredSubscribers.foreach { subscriber =>
// here I have a Map with each actor requests
val actorOptions = getActorOptions(subscriber)
subscriber ? getResponse(actorOptions, request)
}
}
The problem is inside getResponse (I think).
getResponse(actorOptions: JsValue, request: SocketRequest): JsValue = {
(actorOptions \ "dashboardId").asOpt[Int] match {
case Some(id) => {
val response = widgetsService.getByDashboadId(id) map { widgets =>
val widgetsResponse: List[Future[String]] = widgets.map(w => {
widgetsService.getDataById(w.id) map {
data => s"""{ "widgetId": ${w.id}, "data": $data }"""
}
})
var responses: List[String] = List.empty
widgetsResponse.foreach(f => {
f.onComplete {
case Success(value) => responses = value :: responses
case Failure(e) => println(s"Something happened: ${e.getMessage}")
}
})
// first time when I use Await.result
// used to populate the responses list with data from all futures
Await.result(Future.sequence(widgetsResponse), Duration.Inf)
Json.parse(s"""{
"dashboardId": $id,
"widgets": [${response.mkString(", ")}]
}""".stripMargin)
}
// second time when I use Await.result
// used to return a JsValue instead of a Future[JsValue]
Await.result(response, Duration.Inf)
}
case None => buildDefaultJson // return default json value, unimportant for this example
}
}
Due of that, In frontend, if I have 2 sockets clients, the response for the second will be send only after first.
I found that I can obtain a "fake" increase of performance if I embrance the getResponse in a future inside of my Actor.
filteredSubscribers.foreach { subscriber =>
val actorOptions = getActorOptions(subscriber)
Future(subscriber ? getResponse(actorOptions, request))
}
So, for both subscribers the action will be started in same time, but when the first will reach the Await.result, the second will be locked until first is done.
I need to avoid using Await.result there, but I don't know how to get the results of a list of futures, without using for-comprehension (because is a dynamically list) for first time where I use it.
Because Akka ask operator (?) return a Future[Any], I tried that my getResponse method to return directly a JsValue to be mapped then in Future[JsValue]. If I remove the second Await.result and my method will return Future[JsValue], then the actor will return a Future[Future[JsValue]] which I don't think is too right.
After some more researches and solutions found on so, my code become:
Future.sequence(widgetsResponse) map { responses =>
Json.parse(
s"""
|{
|"dashboardId": $id,
|"tableSourceId": $tableSourceId,
|"widgets": [ ${responses.mkString(", ")}]
|}""".stripMargin
)
}
getResponse returns a Future[JsValue] now, removing both Await.result cases, and actor case become:
filteredSubscribers.foreach { subscriber =>
val actorOptions = getActorOptions(subscriber)
getResponse(actorOptions, request) map { data =>
subscriber ? data
}
}
I don't know why, still have a synchronous behavior. Damn, this can be due of my subscribers type: Set[ActorRef]? I tried to use parallel foreach and this looks like solving my problem:
filteredSubscribers.par.foreach { subscriber =>
val actorOptions = getActorOptions(subscriber)
getResponse(actorOptions, request) map { data =>
subscriber ? data
}
}
in my scala playframework application I want to return the via Post submitted and then with slick stored object back to frontend as json
I tried this:
def createClient = Action.async { implicit request =>
request.body.asJson.map(_.validate[ClientModel] match {
case JsSuccess(client) =>
clientDTO.createClient(client).map { clients =>
Ok(Json.toJson(clients))
}
})
}
but I get this error:
what could be my problem?
NEW ERROR
Try with something along these lines:
def createClient = Action.async { implicit request =>
request.body.asJson match {
case None => // do something that returns a Future[Result] ~ such as NotFound or
case Some(js) =>
js.validate[ClientModel] match {
case client: JsSuccess[ClientModel] =>
clientDTO.createClient(client).map { clients =>
Ok(Json.toJson(clients))
}
case e: JsError => // do something that returns a Future[Result] ~ such as InternalServerError
}
}
}
the .async, as the name suggests require a Future type.
You have 2 options:
Remove the .async, this will make your def synchronous (deprecated)
Leave the .async but return a Future result
def createClient = Action.async { implicit request =>
request.body.asJson.map(_.validate[ClientModel] match {
case JsSuccess(client) =>
clientDTO.createClient(client).map { clients =>
Future(Ok(Json.toJson(clients)))
}
})
}
But you still need to add the case of validation error:
case JsSuccess(client) =>
{
clientDTO.createClient(client).map
{
clients => Future(Ok(Json.toJson(clients)))
}
}
case _ => Future(BadRequest(""))
This should work and, in all the cases apart JsSuccess, the function will return a future BadRequest response.
A better solution is to change the _ with JsError:
case e: JsError =>
{
Println(e)
Future(BadRequest(.....))
}
This will also print the error.
You can read more here: https://www.playframework.com/documentation/2.6.x/ScalaJson
(Using validation chapter)
More about future in scala: https://docs.scala-lang.org/overviews/core/futures.html
In Scala I have a call to service in controller which is returning me Future[WSResponse]. I want to make sure service is returning valid result so send Ok(..) otherwise send BadRequest(...). I don't think I can use map. Any other suggestion?
def someWork = Action.async(parse.xml) { request =>
val result:Future[WSResponse] = someService.processData(request.body.toString())
//Need to send back Ok or BadRequest Message
}
EDIT
Solution from #alextsc is working fine. Now moving to test my existing test is failing. It is getting 400 instead of 200.
test("should post something") {
val requestBody = <value>{UUID.randomUUID}</value>
val mockResponse = mock[WSResponse]
val expectedResponse: Future[WSResponse] = Future.successful(mockResponse)
val request = FakeRequest(Helpers.POST, "/posthere").withXmlBody(requestBody)
when(mockResponse.body).thenReturn("SOME_RESPONSE")
when(someService.processData(any[String])).thenReturn(expectedResponse)
val response: Future[Result] = call(controller.someWork , request)
whenReady(response) { response =>
assert(response.header.status == 200)
}
}
You're on the right track and yes, you can use map.
Since you're using Action.async already and your service returns a future as it stands all you need to do is map that future to a Future[Result] so Play can handle it:
def someWork = Action.async(parse.xml) { request =>
someService.processData(request.body.toString()).map {
// Assuming status 200 (OK) is a valid result for you.
case resp : WSResponse if resp.getStatus == 200 => Ok(...)
case _ => BadRequest(...)
}
}
(I note that your service returns WSResponse (from the play ws java library) and not play.api.libs.ws.Response (the scala version of it), hence getStatus and not just status)
I have a controller that exposes a method as a route. In this method, I call a long running computation that returns a Future[SomeType].
I now have the following:
def compute(id: String) = Action.async { request =>
val result: Future[SomeType] = compute(id)
result.map(value => Ok(transform(value, id)))
}
So far this is just the happy path. What if compute(id) results in a Failure? How to handle that? I could wrap the whole thing in a Try block, but is there a better alternative? Any suggestions?
We usually use the following pattern:
def compute(id: String) = Action.async { request =>
val result: Future[SomeType] = compute(id)
result.map(value => Ok(transform(value, id)))
.recover { case ex =>
Logger.error("Something went wrong", ex)
InternalServerError
}
}
This way the HTTP response code will be 500 INTERNAL SERVER ERROR, so the caller will be informed. You may also want to add validation on the parameters of the request and return a 400 BAD REQUEST etc.
I have the following piece of code that makes a request to a HTTP service to fetch a resource. It could be that the service is not available or the resource is not available. So I need to handle such error scenarios. The server actually throws an exception that I have to handle in my Scala client. Here is what I have done so far:
def getData(resource: String, requestParam: Option[Seq[(String, String)]]): Try[Elem] = {
Try {
val wc = WebClient.create(address(scheme, host, port) + "/" + resource, un, pw, null)
// Load the trust store to the HTTPConduit
if ("https" == scheme) {
val conduit = WebClient.getConfig(wc).getConduit.asInstanceOf[HTTPConduit]
loadTrustStore(conduit)
}
requestParam match {
case Some(reqParams) => reqParams.foreach((param: (String, String)) => {
wc.query(param._1, param._2)
})
case None => wc
}
XML.loadString("""<?xml version="1.0" encoding="UTF-8"?>""" + wc.get(classOf[String]))
}
}
Now, the caller of this method looks like this:
def prepareDomainObject(): MyDomainObject = {
getData(resource, params) match {
case Success(elem) => getDomainFromElem(elem)
case Failure(_) => ?? What do I do here? I actually want to get the message in the Throwable and return another type. How do I get around this?
}
How do I handle my match condition such that I return a different type in case of an error?
You have two options. Either have prepareDomainObject return a Try as well:
def prepareDomainObject(): Try[MyDomainObject] = {
getData(resource, params) map getDomainFromElem
}
or have it throw an exception on error:
def prepareDomainObject(): MyDomainObject = {
getDomainFromElem(getData(resource, params).get)
}