Scala/Spray/Akka unable to leverage mapRequest - scala

I am new to Scala/Spray/AKKA so please excuse this dumb requests.
I have the following Directive and it is being called as the first
logger line ("inside") is showing up in logs.
However, anything inside mapRequest{} is skipped over. The logging line ("headers:") isn't showing up
private def directiveToGetHeaders(input: String) : Directive0 = {
logger.info("inside")
mapRequest { request =>
val headList: List[HttpHeader] = request.headers
logger.info("headers: " + headList.size)
request
}
}
I am not sure what I did wrong. My goal is to pull out all the HTTP headers. Any tip/pointer much appreciated. Thanks
-v

You can use extractRequest directive for getting headers.
private def directiveToGetHeaders(input: String) : Directive0 = {
logger.info("inside")
extractRequest { request =>
val headList: Seq[HttpHeader] = request.headers
logger.info("headers: " + headList.size)
complete(HttpResponse())
}
}

Related

Akka Http route test with formFields causing UnsupportedRequestContentTypeRejection

I have a GET request with parameters and a formField.
It works when I use a client like Insomnia/Postman to send the req.
But the route test below fails with the error:
UnsupportedRequestContentTypeRejection(Set(application/x-www-form-urlencoded, multipart/form-data))
(Rejection created by unmarshallers. Signals that the request was rejected because the requests content-type is unsupported.)
I have tried everything I can think of to fix it but it still returns the same error.
It is the formField that causes the problem, for some reason when called by the test it doesnt like the headers.
Is it something to do with withEntity ?
Code:
path("myurl" ) {
get {
extractRequest { request =>
parameters('var1.as[String], 'var2.as[String], 'var3.as[String], 'var4.as[String]) { (var1, var2, var3, var4) =>
formField('sessionid.as[String]) { (sessionid) =>
complete {
request.headers.foreach(a => println("h=" + a))
}
}
}
}
}
}
Test:
// TESTED WITH THIS - Fails with UnsupportedRequestContentTypeRejection(Set(application/x-www-form-urlencoded, multipart/form-data))
class GETTest extends FreeSpec with Matchers with ScalatestRouteTest {
val get = HttpRequest(HttpMethods.GET, uri = "/myurl?var1=456&var2=123&var3=789&var4=987")
.withEntity("sessionid:1234567890")
.withHeaders(
scala.collection.immutable.Seq(
RawHeader("Content-Type", "application/x-www-form-urlencoded"), // same problem if I comment out these 2 Content-Type lines
RawHeader("Content-Type", "multipart/form-data"),
RawHeader("Accept", "Application/JSON")
)
)
get ~> route ~> check {
status should equal(StatusCodes.OK)
}
The exception is thrown before the formField line.
Full exception:
ScalaTestFailureLocation: akka.http.scaladsl.testkit.RouteTest$$anonfun$check$1 at (RouteTest.scala:57)
org.scalatest.exceptions.TestFailedException: Request was rejected with rejection UnsupportedRequestContentTypeRejection(Set(application/x-www-form-urlencoded, multipart/form-data))
at akka.http.scaladsl.testkit.TestFrameworkInterface$Scalatest$class.failTest(TestFrameworkInterface.scala:24)
}
You could either use:
val get = HttpRequest(HttpMethods.GET, uri = "/myurl?var1=456&var2=123&var3=789&var4=987", entity = FormData("sessionid" -> "1234567.890").toEntity)
or
val get = Get("/myurl?var1=456&var2=123&var3=789&var4=987", FormData("sessionid" -> "1234567.890"))

akka-http HttpEntity.toStrict timed out while still waiting for outstanding data

I encounter a problem randomly without SSL(sometimes it works without issue), and each time with SSL, and I don't understand why.
It's a time out during HttpEntity.toStrict.
extractRequest{payload =>
val futureRequestEntity = payload.entity.toStrict(5.seconds).map(_.data.utf8String)
val requestEntity = Await.result(futureRequestEntity, 10.seconds)
... }
I tried to set longer waiting times, but doesn't solve the issue.
java.util.concurrent.TimeoutException: HttpEntity.toStrict timed out after 5 seconds while still waiting for outstanding data
at akka.http.impl.util.ToStrict$$anon$1.onTimer(package.scala:138)
I need to use extractRequest for my path because I extract the entity and the header further in the code.
Route code :
options {
corsHandler.corsHandler(complete(StatusCodes.OK))
} ~ post {
path("recommandation" / Segment / Segment / "suggestion") {(docType,docId) =>
extractRequest{payload =>
writeLog("info",s"/recommandation : Post request for $docType suggestion : $docId ")
val coBranding = payload.headers.filter(x => x.is("cobrandingcontext")).map(x => x.value()).head
val futureRequestEntity = payload.entity.toStrict(5.seconds).map(_.data.utf8String)
val requestEntity = Await.result(futureRequestEntity, 10.seconds)
val parsedPayload = suggestionEngine.payloadParser.suggestionEnginePayloadParser(requestEntity,
coBranding,docType, docId)
if (parsedPayload.isDefined){
val suggestionResult = suggestionEngine.suggestionWorker.suggestionPipeline(parsedPayload.get)
val suggestionResponse = suggestionEngine.responseHandler.responseBuilder(suggestionResult).get
complete(200,List(`Content-Type`(`application/json`)),suggestionResponse)
} else {
writeLog("error","/recommandation : undefined payload")
complete(StatusCodes.BadRequest,List(`Content-Type`(`application/json`)),"Undefined payload")
}
}
}
}
Request example :
curl -i https://mydns.com:443/recommandation/products/59ad73be20a35d3fa47c80c8/suggestion -H
'cobrandingcontext: branding' -H 'Content-Type: application/json;charset=UTF-8' -X POST -d '{"collection":"products",
"query":{"category_parent":"category-ex","category_child":"category-child-ex","dimensions.width":{"$lt":70,"$gt":0},
"dimensions":{"$lt":40,"$gt":-40},"status":true,"structured":true,"visibilities":"architects-3d-btoc"},"configuration":{"type":"nearest-neighbors","metric":"cosine","features":["styles"]}}'
Thank's in advance
I think the explanation is that the use of Await will block a dispatcher thread which can lead to thread starvation.
The solution I found is to nest directives :
options {
corsHandler.corsHandler(complete(StatusCodes.OK))
} ~ post {
path("/"){
entity(as[String]){payload =>
headerValueByName("context"){ contextValue => {
...// work with payload and contextValue
}
}
}
}
}

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

WSClient - Too Many Open Files

I'm working with Play Framework 2.4 on CentOS 6 and my application is throwing this exception:
java.net.SocketException: Too many open files
I've searched a lot of topics on Stack Overflow and tried the solutions:
Increase the number of open files to 65535;
Change hard and soft limits on /etc/security/limits.conf;
Change the value of fs.file-max on /etc/sysctl.conf;
Reduced the timeout on file /proc/sys/net/ipv4/tcp_fin_timeout;
The error keeps happening. On another sites, i've found people that are facing the same problem because they weren't calling the method close() from WSClient but in my case, i'm working with dependency injection:
#Singleton
class RabbitService #Inject()(ws:WSClient) {
def myFunction() {
ws.url(“url”).withHeaders(
"Content-type" -> "application/json",
"Authorization" -> ("Bearer " + authorization))
.post(message)
.map(r => {
r.status match {
case 201 => Logger.debug("It Rocks")
case _ => Logger.error(s"It sucks")
}
})
}
}
If i change my implementation to await the result, it work's like a charm but the performance is very poor - and i would like to use map function instead wait the result:
#Singleton
class RabbitService #Inject()(ws:WSClient) {
def myFunction() {
val response = ws.url("url")
.withHeaders(
"Content-type" -> "application/json",
"Authorization" -> ("Bearer " + authorization))
.post(message)
Try(Await.result(response, 1 seconds)) match {
case Success(r) =>
if(r.status == 201) {
Logger.debug(s"It rocks")
} else {
Logger.error(s"It sucks")
}
case Failure(e) => Logger.error(e.getMessage, e)
}
}
}
Anyone have an idea how can i fix this error? I've tried everything but without success.
If anyone is facing the same problem, you need to configure WSClient on application.conf - need to set maxConnectionsTotal and maxConnectionsPerHost.
This is how I solved this problem.
https://www.playframework.com/documentation/2.5.x/ScalaWS

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?
}