Handling and manipulating a list of http responses - scala

I'm currently trying to implement API logic to fetch multiple images from a server.
This server accepts an image id and return an HTTP response that contains the image in PNG format as an entity.
Right now, we want to add a new endpoint that accepts a list of images IDs and return a list of all the images:
I have done the following:
def getImagesFromIds(IdsList: List[String]): Future[List[HttpResponse]] = {
Future.sequence {
IdsList.map(
id => getImageById(id)
)
}
}
this function will receive a list of ids and will call the getImageById to fetch all the images, it will return a list of HttpResponse.
And for the route definition, I have done the following:
def getImagesByIdsListRoute: Route = get {
path("by-ids-list") {
entity(as[List[String]]){
upcs =>
complete(getImagesFromIds(upcs))
}
}
}
But I'm getting the following error message:
no implicits found for parameter m: marshalling.toresponsemarshallable[list[httpresponse]]
Does Any one know how we can marshall a list of http responses, or if there is any way to improve this logic to fetch multiple http responses ?

If I understand correctly, you want to download multiple images and return them as a HTTP response.
The problems with your current attempt
The call to the API made via getImageById returns a HttpResponse. You can't be sure what is the result of this API call. If it fails, the response won't contain any image at all.
You are trying to return List[HttpResponse] as your response. How should this response be serialized? Akka doesn't know what you mean by that and tries to find a marshaller which will serialize your object (for example to JSON) but can't find one.
Returning a list of images requires zipping them. You can't return multiple entities in a single HTTP response.
Possible approach
You have to change getImageById so that it checks what is in the HttpResponse and returns the entity bytes.
Example:
response match {
case HttpResponse(StatusCodes.OK, _, entity, _) =>
entity.dataBytes
case resp # HttpResponse(code, _, _, _) =>
// Response failed and we don't care about the response entity
// Details: https://doc.akka.io/docs/akka-http/current/implications-of-streaming-http-entity.html
resp.discardEntityBytes()
// Decide yourself how you want to handle failures
throw new RuntimeException("Request failed, response code: " + code)
}
dataBytes returns a Source so you'll end up with a List of Sources. You have to concatenate them via, for example via concat.
The result stream has to be zipped via Compression.gzip.
Finally, the stream can be put in the complete method of getImagesByIdsListRoute.

Related

Lagom - Add header to response in service call composition

i want to write code which will refresh cookie (via set-cookie http header) in Lagom.
For clarification Cookie is an encoded string (for example AES).
Lets take lagom service call composition for authentication from Implementing services and edit it
def authenticated[Request, Response](
serviceCall: User => ServerServiceCall[Request, Response]
) = ServerServiceCall.composeAsync { requestHeader =>
//Get cookie from header and decode it
val cookie = decodeCookie(requestHeader)
//Get user based on cookie decode function
val userLookup = getUser(cookie)
userLookup.map {
case Some(user) =>
serviceCall(user)
case None => throw Forbidden("User must be authenticated")
}
}
It is possible to manipulate serviceCall(user) response headers?
I tried something like this:
serviceCall( employee ).handleResponseHeader { case (responseHeader, response) =>
responseHeader.withHeader("Set-Cookie",encodeCookie("NewCookieStringExample")) // Add header
response
}
But function handleResponseHeader requires only response[T] as result and header would not be changed because is immutable.
I know that i can pass cookie to serviceCall(user) and in every service call implementation return tuple with ResponseHeader and Response but this will impact all endpoints and will add much more code.
Use of HeaderFilter is possible too but this would decode and encode cookie twice in one request (in header filter and in authenticated service call composition )
Any tips?
You can return the modified Header and the Response as a Tuple:
serviceCall( employee ).handleResponseHeader((responseHeader, response) => {
val modifiedHeader = responseHeader.withHeader("Set-Cookie",encodeCookie("NewCookieStringExample")) // Add header
(modifiedHeader, response)
})
By the way, The provided example does not compile for me. composeAsync wants a Future[ServerServiceCall[...]].

How to read query parameters in akka-http?

I know akka-http libraries marshal and unmarshal to class type while processing request.But now, I need to read request-parameters of GET request. I tried parameter() method and It is returning ParamDefAux type but i need those values as strings types
I check for answer at below questions.
How can I parse out get request parameters in spray-routing?
Query parameters for GET requests using Akka HTTP (formally known as Spray)
but can't do what i need.
Please tell me how can i extract query parameters from request. OR How can I extract required value from ParamDefAux
Request URL
http://host:port/path?key=authType&value=Basic345
Get method definition
val propName = parameter("key")
val propValue = parameter("value")
complete(persistanceMgr.deleteSetting(propName,propValue))
My method declarations
def deleteSetting(name:String,value:String): Future[String] = Future{
code...
}
For a request like http://host:port/path?key=authType&value=Basic345 try
path("path") {
get {
parameters('key.as[String], 'value.as[String]) { (key, value) =>
complete {
someFunction(key,value)
}
}
}
}
Even though being less explicit in the code, you can also extract all the query parameters at once from the context. You can use as follows:
// Previous part of the Akka HTTP routes ...
extract(_.request.uri.query()) { params =>
complete {
someFunction(key,value)
}
}
If you wish extract query parameters as one piece
extract(ctx => ctx.request.uri.queryString(charset = Charset.defaultCharset)) { queryParams =>
//useyourMethod()
}

Play WS API Making nested requests - WSClient

I want to make two HTTP requests(POST) using play2 WSRequest where some information from the first response is send to the second request. I tried to do this in following manner to make sure second request is only triggers after the first one is completed. But I get Type mismatch: cannot convert from F.Promise to F.Promise error
public Promise<Result> executeAPI(String apiName,JsonNode requestBody){
WSRequest pcLoginRequest = ws.url("http://xxxxx.qa.local:8080/mytest/rest/login");
pcLoginRequest.setContentType("application/json");
pcLoginRequest.setHeader("X-x-Password", "xxxxx")
.setHeader("X-x-Username", "xxxxx")
.setHeader("X-x-Content-Type", "application/json");
Promise<Result> myPromise = pcLoginRequest.post("").map(response -> {
ProApiSession.getInstanceOf().setProToeken(response.asJson().get("token").asText());
WSRequest pcrequest = ws.url("http://xxxxx.qa.local:8080/mytest/rest/api/" + apiName);
pcrequest.setContentType("application/json");
pcrequest.setHeader("X-x-Token",ProApiSession.getInstanceOf().getProToeken() )
.setBody(requestBody)
.setHeader("X-x-Content-Type", "application/json");
Promise<Result> myPromise2 = pcLoginRequest.post(requestBody).map(response2 -> {
return Results.ok(response2.asJson());
});
return myPromise;
});
Can someone please suggest how to do nested request using WSRequest in play. (import play.libs.ws.* )
Java 8 type inference errors are bad at the best of times. Since the result of the lambda you're passing to the first map is Promise<Result>, what you're trying to assign to myPromise is Promise<Promise<Result>>. What you actually want to do is replace the map call with flatMap, which is so named because is "flattens" the nested promise to just be a single promise.

Akka HTTP set response header based on result of Future

I'm designing a REST service using Akka-HTTP 2.0-M2 and have come across a situation where I'd like to supply additional headers which are dependent upon the reply of the queried Actor.
Currently, I have the following...
val route = {
path("oncologist") {
get {
parameters('active.as[Boolean].?, 'skip.as[Int].?, 'limit.as[Int].?).as(GetAllOncologists) {
req =>
complete {
(oncologistActor ? req).mapTo[OncologistList]
}
}
}
}
While this is returning without issue. I'd like to move some of the properties of OncologistList into the response header rather than returning them in the body. Namely, I'm returning total record counts and offset and I would like to generate a previous and next URL header value for use by the client. I'm at a loss on how to proceed.
I think you can use the onComplete and respondWithHeaders directives to accomplish what you want. The onComplete directive works with the result of a Future which is exactly what ask (?) will return. Here is an example using a case class like so:
case class Foo(id:Int, name:String)
And a simple route showing onComplete like so:
get{
parameters('active.as[Boolean].?, 'skip.as[Int].?, 'limit.as[Int].?).as(GetAllOncologists) { req =>
val fut = (oncologistActor ? req).mapTo[Foo]
onComplete(fut){
case util.Success(f) =>
val headers = List(
RawHeader("X-MyObject-Id", f.id.toString),
RawHeader("X-MyObject-Name", f.name)
)
respondWithHeaders(headers){
complete(StatusCodes.OK)
}
case util.Failure(ex) =>
complete(StatusCodes.InternalServerError )
}
}
}
So if we get a successful result from the ask on oncologistActor we can then leverage the respondWithHeaders to add some custom headers to the response. Hopefully this is what you were looking for.

Play Framework 2.4 how to use return statement in Action controller

is there a way to return a value inside Action controller.
I have a method in my User model which returns the number of friends of a given user.
def nrOfFriends(current_user: Long): Int = {
DB.withConnection{ implicit connection =>
var nr: Int = SQL("select count(*) from friend where user_id_1=" + current_user + " or user_id_2=" + current_user).as(scalar[Int].single)
nr
}
}
In my controller, I just want to return the value from the model
def freunde() = IsAuthenticated { username => _ =>
User.findByUsername(username).map { user =>
var nr: Int = Friend.nrOfFriends(user.id.get)
Ok(""+nr)
}.getOrElse(Forbidden)
}
But in the way that is written, it will print "empty string " concatenated with the number
If I replace Ok(""+nr) with Ok(nr) I receive the following error:
"Cannot write an instance of Int to HTTP response. Try to define a Writeable[Int]"
I need for my action to return a value so that I can pass the value from the action to header.views.html inside the navbar something like that
#Freund.freunde Friends
if you want your response to just be the value of nr you can simply call nr.toString:
def freunde() = IsAuthenticated { username => _ =>
User.findByUsername(username).map { user =>
var nr: Int = Friend.nrOfFriends(user.id.get)
Ok(nr.toString)
}.getOrElse(Forbidden)
}
The error you're getting makes reference to the fact that Int doesn't have an implicit Writeable[Int] in scope. So play doesn't know how display an Int in an http response.
You can add make Int writeable by putting this in scope:
implicit val intWriteable = play.api.http.Writeable[Int](_.toString.getBytes, None)
Then you would be able to just say:
Ok(nr)
without error.
However, it sounds like you just want the result of nrOfFriends inside an unrelated template. If that's the case, you should be using an Action at all. Instead just call your model function inside the template where you need the data.
#User.nrOfFriends(user.id) Friends
Of course you would need to pass in the user to the template as well.
You didn't post a full sample of all the code involved in what you are trying to accomplish so I think this is the best I can do for now. Perhaps try posting the base template that your <a> is in.
An important point is that Actions are for production an HTTP response, and not just plain data internally to the application.
An action of a controller handles a request and generates a result to be sent to the client. In other words, an action returns a play.api.mvc.Result value, representing the HTTP response to send to the web client. In your example Ok constructs a 200 OK response. The body of the response must be one of the predefined types, including text/plain, json, and html. The number of a friends is an integer and is NOT an acceptable type of the body. Therefore, a simple way to address this problem is to convert it into a text/plain using .toString().
On the other hand, you can define a writer for Int that lets Play know how to convert an integer into a json format.
For more details, please take a look at this https://www.playframework.com/documentation/2.2.x/ScalaActions.