3

I am using akka-http and writing an Unmarhsaller for one of my classes. What I am trying to do is to get the body of the POST request as a String so I can create my object with it :

case class MyClass(geom: String)

implicit def fromRequestUnmarshaller = Unmarshaller[HttpRequest, MyClass]({implicit ec: ExecutionContext =>
    req: HttpRequest => Future(MyClass(req.entity.asInstanceOf[HttpEntity.Strict].data.map(_.toChar).mkString))
})

It seems like a very complicated line of code just to get the body as a String. Plus, I am doing a very ugly asInstanceOf[HttpEntity.Strict] just because I figured the HttpRequest was of this type while debugging.

My question: is there a simpler/cleaner way to achieve my goal ?

Thanks a lot :)

meucaa
  • 1,455
  • 2
  • 13
  • 27

1 Answers1

3

Be Careful

The reason akka implements the entity as a Source[ByteString,_] is because the entity of an HttpRequest can potentially be infinite length. Therefore, you better make sure that your application has enough RAM to handle any request that is likely to be thrown at it...

toStrict

You can use HttpEntity#toStrict:

implicit val materializer : Materializer = ???
implicit val executionContext : ExecutionContext = ???    

val entityFromRequest : (HttpRequest, FiniteDuration) => Future[ByteString] = 
  (_ : HttpRequest)
    .entity 
    .toStrict(_ : FiniteDuration)
    .map(_.data)

Manual Conversion

You can access the "body", i.e. entity, of an HttpRequest as a Source[ByteString, _]:

val getBodySource : HttpRequest => Source[ByteString,_] = 
  _.entity
   .dataBytes

This Source can then be sent to a Sink which collects the ByteString values into a Seq:

val convertSrcToSeq : Source[ByteString,_] => Future[Seq[ByteString]] = 
  _ runWith Sink.seq

The body you are looking for is one continuous String therefore these ByteStrings need to be reduced to a single value:

val reduceSeqToStr : Future[Seq[ByteString]] => Future[ByteString] = 
  _ map (_ reduceOption (_ ++ _) getOrElse ByteString.empty)

These steps can now be composed into a single function:

val getBodyStrFromRequest : HttpRequest => Future[ByteString] = 
  getBodySource andThen convertSrcToSeq andThen reduceSeqToStr
Ramón J Romero y Vigil
  • 17,373
  • 7
  • 77
  • 125
  • Is there a way to make sure the entity is of finite length ? – meucaa Mar 17 '17 at 15:53
  • @meucaa You can't stop the client from sending infinite length. But you can modify `getBodySource` to cap the number of ByteString values you process or make a slightly more complicated `Source[ByteString, _]` that caps the number of characters in the body... – Ramón J Romero y Vigil Mar 17 '17 at 15:55
  • @meucaa You could also check the HttpRequest to see if the body is a strict using pattern matching and returning a 404 response if the entity is anything other than strict. – Ramón J Romero y Vigil Mar 17 '17 at 15:59
  • Ok, thank you for your answer, I can't believe such a common operation is so complicated .. Yeah I could do a clean version of my arbitrary cast using pattern matching. – meucaa Mar 17 '17 at 15:59
  • @meucaa You are welcome. The complication stems from the fact that the http protocol does not place these types of limits on request/response entities. Therefore akka has to provide functionality that conforms to the spec. It can be both benefit & curse... Happy hacking. – Ramón J Romero y Vigil Mar 17 '17 at 16:01