Akka-Http client: How to get binary data from an http response? - scala

I call an API to get a zip file response. The API responds correctly but I am unable to get the byte array from response because the future that should complete on getting the ByteString never completes:
val authorization = akka.http.javadsl.model.headers.Authorization.basic("xxxxx", "xxxxxx")
val query = Map("fed" -> "xxxx", "trd" -> "yyy", "id" -> "zzz")
val request = HttpRequest(HttpMethods.GET, Uri("https://xxxx.yyyy.com/ggg/ttt.php").withQuery(Query(params = query))).addHeader(authorization)
val responseFut = http.singleRequest(request)
responseFut1.map(response => {
println("*******************************")
println(response)
response.status match {
case akka.http.javadsl.model.StatusCodes.OK => {
println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + response._3)
val entityFut = response.entity.toStrict(60.seconds)
val byteStringFut = entityFut.flatMap(entity => {
entity.dataBytes.runFold(ByteString.empty)(_ ++ _)
})
println("#############################")
try {
byteStringFut.map(x => {
//this never prints =======================================problem
println("----------------------------" + x.toArray[Byte])
})
}catch{
case e: Exception => println("Error: " + e)
}
}
case _ => {}
}
})
If I print out the response this is what it looks like:
*******************************
HttpResponse(200 OK,List(Date: Fri, 08 Sep 2017 20:58:43 GMT, Server: Apache/2.4.18 (Ubuntu), Content-Disposition: attachment; filename="xxxxx.zip", Pragma: public, Cache-Contr
ol: public, must-revalidate, Content-Transfer-Encoding: binary),HttpEntity.Chunked(application/x-zip),HttpProtocol(HTTP/1.1))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^HttpEntity.Chunked(application/x-zip)
#############################
So response is coming back fine, but I still cannot get the binary data for the zip file.
We use akka-http in other places to call APIs that return json response and this approach seems to work fine there.
Why doesnt it work here? What am I doing wrong?
Any advice is appreciated.
Thanks.
Update
Adding byteStringFut.failed.foreach(println(_)) shows this exception: akka.http.scaladsl.model.EntityStreamException: HTTP chunk size exceeds the configured limit of 1048576 bytes

It looks like something went wrong and an exception was thrown in the async computation. You can check the exception by inspecting Future in the following way:
byteStringFut.failed.foreach(println)

Related

Response entity was not subscribed after 100 seconds. Make sure to read the response entity body or call `discardBytes()` on it

I am building a scala application. Within the application, we are making a call to an external service, and fetching the data.
When I am hitting the endpoint of this external service using the
postman, I am getting the complete data, around 9000 lines of JSON
data, within 9 seconds.
But when I am hitting the same endpoint through my scala application, I am getting a 200 OK response, but getting the below error:
[WARN] [06/09/2022 18:05:45.765] [default-akka.actor.default-dispatcher-9] [default/Pool(shared->http://ad-manager-api-production.ap-south-1.elasticbeanstalk.com:80)] [4 (WaitingForResponseEntitySubscription)] Response entity was not subscribed after 100 seconds. Make sure to read the response entity body or call `discardBytes()` on it. GET /admin/campaigns Empty -> 200 OK Chunked
I read about it and found that we can set response-entity-subscription-timeout property to a higher value. I set it to about 100 seconds, but this does not seem to help.
My Code:
private val sendAndReceive = customSendAndReceive.getOrElse(HttpClientUtils.singleRequest)
.
.
.
def getActiveCampaigns: GetActiveCampaigns = () => {
val request = HttpRequest(
uri = s"$endpoint/admin/campaigns?status=PUBLISHED", // includes both PUBLISHED_READY and PUBLISHED_PAUSED
method = HttpMethods.GET,
headers = heathers
)
sendAndReceive(request).timed(getActiveCampaignsTimer).flatMap {
case HttpResponse(StatusCodes.OK, _, entity, _) =>
Unmarshal(entity).to[List[CampaignListDetailsDto]]
case response#HttpResponse(_, _, _, _) =>
response.discardEntityBytes()
Future.failed(new RuntimeException(s"Ad manager service exception: $response"))
case response =>
log.error(s"Error calling ad manager service: $response")
response.discardEntityBytes()
Future.failed(new RuntimeException(s"Ad manager service exception: $response"))
}
}
.
.
.
def getCampaignSpendData(getActiveCampaigns: GetActiveCampaigns, getCampaignTotalSpend: GetCampaignTotalSpend)(implicit ec: ExecutionContext): GetCampaignsSpendData = () => {
getActiveCampaigns()
.andThen {
case Failure(t) => log.error("Failed to fetch ads from ad manager", t)
}
.flatMap {
campaignList => Future.sequence(campaignList.map(campaign => budgetSpendPercentage(getCampaignTotalSpend)(campaign)))
}
}
Questions
What does this error exactly mean? Is it that it is able to connect to the endpoint but not able to get the complete data from it before the connection is closed/reset?
How can we address this issue?

Play Framework - edit Sec-WebSocket-Protocol in Web socket Response Header

Edit the Web socket header response sent from server to client.
I am creating a websocket server application using playframework. Right now the websocket response from the server is
taken care by Play. Following is the response header,
Request Header:
(UpgradeToWebSocket,),
(Host,localhost:8083),
(Connection,Upgrade),
(Upgrade,websocket),
(Sec-WebSocket-Version,13),
(Accept-Encoding,gzip, deflate, br),
(Accept-Language,en-US,en;q=0.9),
(Sec-WebSocket-Key,ZvfzpVo3EX4DFA4BRcgRIA==)
def chatSystem(): WebSocket = WebSocket.acceptOrResult[String, String] { request =>
Future.successful{
AuthenticationService.doBasicAuthentication(request.headers) match {
case Results.Ok => Right(ActorFlow.actorRef { out => ChatServiceActor.props(out) })
case _ => Left(Unauthorized)
}
}
}
I want to validate Sec-WebSocket-Protocol if it is present in the request header or add the same with value in the server response if it is not present.
I used the following code:
// Defined at http://tools.ietf.org/html/rfc6455#section-4.2.2
val MagicGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
def websocketAcceptForKey(key: String): String = {
val sha1 = MessageDigest.getInstance("sha1")
val salted = key + MagicGuid
val hash = sha1.digest(salted.asciiBytes)
val acceptKey: String = Base64.rfc2045().encodeToString(hash, false)
acceptKey
}
Use it like the following:
val wsKey: Optional[HttpHeader] = request.getHeader("Sec-WebSocket-Key")
val wsAccept = if (wsKey.isPresent) Some(RawHeader("Sec-WebSocket-Accept", websocketAcceptForKey(wsKey.get.value()))) else None

HTTP4S client. How to get the exact request and response body

I am writing a small http4s client
val client = SimpleHttp1Client()
val uri = Uri.fromString(requestUrl).valueOr(throw _)
val task = POST(uri, UrlForm("username" -> userName, "password" -> password)).map{request => println("request: " + request.body)}
try {
val response = client.expect[String](task).unsafePerformSync
println("token: " + response)
response
} catch {
case e: Exception => println(e.getMessage);"BadToken"
}
The output is like
[info] Running com.researchnow.nova.shield.NovaShieldSetup
[info] Emit(Vector(ByteVector(44 bytes, 0x757365726e616d653d616268737269766173746176612670617373776f72643d41726)))
[info] Failed: unexpected HTTP status: 400 Bad Request
[info] token: BadToken
How to convert the binary request body to String? I want to see the body and headers in clear text.
I had a conversation with the http4s team on gitter and found the response. since gitter talk is not returned by google I am putting the answer here
val loggedReq = req.copy(body = request.body.observe(scalaz.stream.io.stdOutBytes))
println(loggedReq)
this prints all the headers. If we do something with the loggedReq then we get the entire body which is posted
loggedReq.as[String].run

How to check for 200 OK response status using Scala and Play Framework

I have a following Actor class that is responsible for sending a JSON message to a URL using POST.
import play.api.libs.ws._
class Worker extends Actor {
val logger: Logger = Logger("superman")
val supermanURL = "http://localhost:9000/superman/send"
def receive = {
case message: JsValue => {
val transactionID = (message \ "transactionID").get
println("Received JSON Object =>" + message)
val responseFromSuperman = WS.url(supermanURL).withHeaders("Content-Type" -> "application/json").post(message)
responseFromSuperman.map(
result => {
//TODO: Make sure to only log if response status is 200 OK
logger.info("""Message="ACK received from Superman" for transactionID=""" + transactionID)}
).recover { case error: Throwable =>
logger.error("""Message="NACK received from Superman" for transactionID=""" + transactionID) + " errorMessage:" + error.getLocalizedMessage()
}
}
}
}
So, if you look into my TODO above, I would like to add a check for a response type 200 OK. The current implementation is not doing that and it logs the message even if I manually send in a BadRequest. I tried checking for result.allHeaders which returns:
Map(Date -> Buffer(Wed, 27 Jan 2016 21:45:31 GMT), Content-Type -> Buffer(text/plain; charset=utf-8), Content-Length -> Buffer(7))
but no information about response status 200 OK
Simply:
import play.api.http.Status
if(result.status == Status.OK) {
// ...
}
Maybe I am missing here something but you have "status" on the response.
So you can do:
WS.url(url).withHeaders("Content-Type" -> "application/json").post(message).map{
case response if ( response.status == OK) => //DO SOMETHING?
}

POST request sometimes hits the wrong endpoint in Akka-http program

I have an Akka-http 2 server to handle HTTP requests.
On either endpoint, I'm converting to case class A or B:
case class caseClassA(data: String, eventID: String)
case class caseClassB(data: String, otherData: JsObject)
I have a routes object like this:
val routes = {
logRequestResult("akka-http-microservice") {
  pathPrefix("some_endpoint")
       (post & entity(as[caseClassA])) {
        Request =>
          val future = someFunction(Request)
            complete {
             future.map(result =>
             result
              )
            }
        } ~
 pathPrefix("other_endpoint") {
      (post & entity(as[caseClassB])) {
Request =>
          val future = otherFunction(Request)
          complete {
            future.map(result =>
             result
            )
          }
        }
      }
}
The problem: Sometimes, when calling from an iOS app using Alamofire, I'm getting an error response for bad data to some_endpoint when making a request for other_endpoint with a caseClassB-convertible JSON object:
[DEBUG] [01/22/2016 16:30:16.350] [ReactiveKafka-akka.actor.default-dispatcher-23] [ActorSystem(ReactiveKafka)] akka-http-microservice: Response for
  Request : HttpRequest(HttpMethod(POST),http://192.168.2.141:9004/other_endpoint,List(Host: 192.168.2.141:9004, Connection: keep-alive, Accept: */*, User-Agent: myapp/myco.myapp (1; OS Version 9.2  (Build 13C75)), Accept-Language: en-US, zh-Hans-US;q=0.9, Accept-Encoding: gzip, compress;q=0.5),HttpEntity.Strict(application/json,ByteString(XXX, XXX, XXX, XXX, ETC <REPLACED FOR READABILITY, THIS WAS GOOD DATA>)),HttpProtocol(HTTP/1.1))
  Response: Complete(HttpResponse(200 OK,List(),HttpEntity.Strict(application/json,ByteString(XXX, XXX, XXX, XXX, ETC <REPLACED FOR READABILITY, THIS WAS GOOD DATA>)),HttpProtocol(HTTP/1.1)))
[DEBUG] [01/22/2016 16:30:21.347] [ReactiveKafka-akka.actor.default-dispatcher-5] [akka://ReactiveKafka/user/$a/flow-17-2-prefixAndTail] Cancelling akka.stream.impl.MultiStreamOutputProcessor$SubstreamOutput#454147bb (after: 5000 ms)
[DEBUG] [01/22/2016 16:30:26.259] [ReactiveKafka-akka.actor.default-dispatcher-7] [ActorSystem(ReactiveKafka)] akka-http-microservice: Response for
  Request : HttpRequest(HttpMethod(POST),http://192.168.2.141:9004/other_endpoint,List(Host: 192.168.2.141:9004, Connection: keep-alive, Accept: */*, User-Agent: myapp/myco.myapp (1; OS Version 9.2  (Build 13C75)), Accept-Language: en-US, zh-Hans-US;q=0.9, Accept-Encoding: gzip, compress;q=0.5),HttpEntity.Default(application/json,319,akka.stream.scaladsl.Source#c3e53bd),HttpProtocol(HTTP/1.1))
  Response: Rejected(List(MalformedRequestContentRejection(Object is missing required member 'eventID',Some(java.util.NoSuchElementException: key not found: eventID)), TransformationRejection(<function1>), MalformedRequestContentRejection(Unexpected end-of-input at input index 0 (line 1, position 1), expected JSON Value:
^
,None), TransformationRejection(<function1>)))
As you can see by missing eventID, this is trying to convert to caseClassA. Both requests in the output above show I'm trying to hit other_endpoint. When I call this endpoint from Postman with the same data, it works 100% of the time.
What gives?
As seen in the comments above, the issue was missing braces ({}) around the code for "some_endpoint". Here's the corrected code block:
val routes = {
logRequestResult("akka-http-microservice") {
  pathPrefix("some_endpoint") {
      (post & entity(as[caseClassA])) {
         Request =>
         val future = someFunction(Request)
         complete {
          future.map(result =>
             result
           )
         }
}
     } ~
  pathPrefix("other_endpoint") {
       (post & entity(as[caseClassB])) {
Request =>
        val future = otherFunction(Request)
         complete {
           future.map(result =>
           result
          )
        }
       }
     }
}