I am calling the following helper function from an action to make a syncronous HTTP-post with a special key-value pair appended to the URL-parameters in formdata:
def synchronousPost(url: String, formdata: Map[String, Seq[String]], timeout: Duration=Duration.create(30, TimeUnit.SECONDS)): String = {
import ExecutionContext.Implicits.global
val params = formdata.map { case (k, v) => "%s=%s".format(k, URLEncoder.encode(v.head, "UTF-8")) }.mkString("&")
val future: Future[WSResponse] = ws.url(url).
withHttpHeaders(("Content-Type", "application/x-www-form-urlencoded")).
post(params)
try {
Await.result(future, timeout)
future.value.get.get.body
} catch {
case ex: Exception =>
Logger.error(ex.toString)
ex.toString
}
}
It is called like this:
def act = Action { request =>
request.body.asFormUrlEncoded match {
case Some(formdata) =>
synchronousPost(url, formdata + ("handshake" -> List("ok"))) match {
Actually it is copy-pasted from some old gist and I am quite sure it can be rewritten in a cleaner way.
How can the line val params = ... be rewritten in a cleaner way? It seems to be low level.
The original formdata comes in from request.body.asFormUrlEncoded and I simply need to append the handshake parameter to the formdata-map and send the original request back to the sender to do the handshake.
Since formdata is a Map[String, Seq[String]], a data type for which a default WSBodyWritable is provided, you can simply use it as the request body directly:
val future: Future[WSResponse] = ws.url(url)
.withHttpHeaders(("Content-Type", "application/x-www-form-urlencoded"))
.post(formdata)
Incidentally, it's considered bad form to use Await when it's easy to make Play controllers return a Future[Result] using Action.async, e.g:
def asyncPost(url: String, formdata: Map[String, Seq[String]]): Future[String] = {
ws.url(url)
.withHttpHeaders(("Content-Type", "application/x-www-form-urlencoded"))
.post(formdata)
.map(_.body)
}
def action = Action.async { request =>
request.body.asFormUrlEncoded match {
case Some(formdata) =>
asyncPost(url, formdata + ("handshake" -> List("ok"))).map { body =>
Ok("Here is the body: " + body)
} recover {
case e =>
Logger.error(e)
InternalServerError(e.getMessage)
}
case None => Future.successful(BadRequest("No form data given"))
}
}
Related
I am using akka-http and trying to log a request on a specific path using logrequest :
path(Segment / "account") { id =>
logRequest("users/account", Logging.InfoLevel) {
post {
entity(as[Account]) { account => ???
complete(HttpResponse(StatusCodes.NoContent))
}
}
}
however on my log I see something like
HttpRequest(HttpMethod(POST),https://localhost:9009/api/users/123/account,List(Host: localhost:9009, User-Agent: akka-http/10.0.6, Timeout-Access: <function1>),HttpEntity.Chunked(application/json),HttpProtocol(HTTP/1.1))
what I am looking for is the exact request including the body (json) as it was sent by the requestor.
The "HttpEntity.Chunked(application/json)" segment of the log is the output of HttpEntity.Chunked#toString. To get the entire request body, which is implemented as a stream, you need to call HttpEntity#toStrict to convert the Chunked request entity into a Strict request entity. You can make this call in a custom route:
def logRequestEntity(route: Route, level: LogLevel)
(implicit m: Materializer, ex: ExecutionContext) = {
def requestEntityLoggingFunction(loggingAdapter: LoggingAdapter)(req: HttpRequest): Unit = {
val timeout = 900.millis
val bodyAsBytes: Future[ByteString] = req.entity.toStrict(timeout).map(_.data)
val bodyAsString: Future[String] = bodyAsBytes.map(_.utf8String)
bodyAsString.onComplete {
case Success(body) =>
val logMsg = s"$req\nRequest body: $body"
loggingAdapter.log(level, logMsg)
case Failure(t) =>
val logMsg = s"Failed to get the body for: $req"
loggingAdapter.error(t, logMsg)
}
}
DebuggingDirectives.logRequest(LoggingMagnet(requestEntityLoggingFunction(_)))(route)
}
To use the above, pass your route to it:
val loggedRoute = logRequestEntity(route, Logging.InfoLevel)
Given the following EssentialAction...
object MyController extends Controller {
...
def HasToken(action: Token => EssentialAction) = EssentialAction { request =>
...
// this doesn't compile
val body = request.body match {
case json: JsValue => json.toString
case _ => ""
}
// calculate hash with body content here
...
}
// here is an authenticated action
def getUser(userId: Strign) = HasToken { token =>
Action(parse.json) { request =>
request.body.validate[User] match {
...
}
}
}
}
... how do I get the body of the request without parsing it?
I don't want and I don't need to parse the request body in HasToken since the body is going to be parsed in action getUser. I just need the raw content of the body to calculate a hash.
The code in HasToken doesn't compile because request is of type RequestHeader whereas I need a Request, which defines body.
Will this work for you ?
object MyController extends Controller {
// Your HasToken Action
def Authenticate(action: Token => EssentialAction) = EssentialAction { requestHeader =>
// ... execute logic to verify authenticity using requestHeader
}
// Your action to validate tampering of request body and validity of JSON
def Validate[A](action: Token => Request[A]) = Action(parse.json) { request =>
val body = request.body
body match {
case json: JsValue => json.toString
case _ => ""
}
// calculate hash with body content here
body.validate[User] match {
// ...
}
}
def getUser(userId: Strign) = Authenticate { token =>
Validate { user =>
//.... Continue
}
}
}
Authentication only uses RequestHeader
Validation uses Request body. (Bonus: Body is only parsed once)
EDIT:
Question #1: I don't want to validate the body in Validate... since I need a generic validation mechanism that could be used everywhere regardless of the content type (e.g. user, message, etc.).
How about adding another type param (so that it is made generic):
def Validate[A, B](action: Token => Request[A])(implicit reads: Reads[B]) = Action(parse.json) { request =>
// ...
}
Question #2: Furthermore, if the token validation fails, the body don't have to be processed (that's important in case of file upload, which has to be performed if and only if the validation succeeded). That's way, in my opinion, the best option would be to read the raw content of the body in Validate.
This can be easily achieved:
def Validate[A, B](action: Token => Request[A])(implicit reads: Reads[B]) = Action(parse.json) { request =>
val body = request.body
body match {
case json: JsValue => json.toString
case _ => ""
}
// calculate hash with body content here and figure out if the body is tampered
if (bodyIsNotTampered) {
body.validate[B] match {
// ...
}
} else {
// log and return Future.successful(BadRequest)
}
}
EDIT 3: Full solution:
import play.api.libs.json.{Json, JsValue, Format}
object CompilationUtils {
class Token
case class User(name: String)
implicit val UserFormat = Json.format[User]
def authenticate = new Token // authentication logic
def isTampered(body: JsValue) = {
val bodyAsStr: String = Json.stringify(body)
// calculate hash with body content here
false
}
}
object MyController extends Controller {
import CompilationUtils._
// Your HasToken Action
def Authenticate(action: Token => EssentialAction) = EssentialAction { requestHeader =>
action(authenticate)(requestHeader) // your execute logic to verify authenticity using requestHeader
}
// Your action to validate tampering of request body and validity of JSON
def Validate[A, B](request: Request[A])(implicit formatA: Format[A], formatB: Format[B]): Either[Result, B] = {
val body = request.body
val bodyAsJsValue = Json.toJson(body)
if (!isTampered(bodyAsJsValue)) {
bodyAsJsValue.validate[B].fold(
valid = res => Right(res),
invalid = err => Left(BadRequest(err.toString))
)
} else {
Left(BadRequest) // Request Tampered
}
}
def getUser(userId: String) = Authenticate { token =>
Action(parse.json) { request =>
Validate(request).fold(
badReq => badReq,
user =>
// continue...
Ok("")
)
}
}
}
I have an application, that used play 2.3.8, scala and (if it matter) play-auth.
Have a controller, with method:
def foo(id: Long) = StackAction(AuthorityKey -> Everybody) { implicit request =>
//code forming json
Ok(json)
}
How can I get that json from another controller?
I try something, but without success:
def bar(id : Long) = StackAction(AuthorityKey -> Everybody){ implicit request =>
val futureResponse = AnotherController.foo(id).apply(request)
val result = Await.result(futureResponse, Timeout(5, TimeUnit.SECONDS).duration)
Logger.debug("_______________________" + result.body) //dont't know how to convert that to json
//handle json there
Ok(newResult)
}
How to do that right?
Try this
def bar(id : Long) = StackAction(AuthorityKey -> Everybody){ implicit request =>
val futureResponse: Future[JsValue] =
AnotherController.foo(id).apply(request).flatMap{ res =>
res.body |>>> Iteratee.consume[Array[Byte]]()
}.map(bytes => Json.parse(new String(bytes,"utf-8")))
val json = Await.result(futureResponse, Timeout(5, TimeUnit.SECONDS).duration)
Logger.debug("_______________________" + json) //dont't know how to convert that to json
//handle json there
Ok(json)
}
I am working on a Scala service in Play to act as a proxy for another service. The problem I am having is that IntelliJ is giving me a type error saying that I should be returning a Future[SimpleResult] instead of a Result Object. Here is what I have:
def getProxy(proxyUrl: String) = Action { request =>
val urlSplit = proxyUrl.split('/')
urlSplit(0)
WS.url(Play.current.configuration.getString("Services.url.um") + proxyUrl).get().map { response =>
val contentType = response.header("Content-Type").getOrElse("text/json")
Ok(response.body)
}
}
How do I fix this so I can return a Result object?
Since Play WS.get() returns a Future[Response], which you're mapping to Future[Result], you need to use Action.async instead of Action.apply:
def getProxy(proxyUrl: String) = Action.async { request =>
val urlSplit = proxyUrl.split('/')
urlSplit(0)
WS.url(Play.current.configuration.getString("Services.url.um") + proxyUrl).get().map { response =>
val contentType = response.header("Content-Type").getOrElse("text/json")
Ok(response.body)
}
}
The below code does streaming back to client, in, what I gather is a more idiomatic way than using Java's IO Streams. It, however, has an issue: connection is kept open after stream is done.
def getImage() = Action { request =>
val imageUrl = "http://hereandthere.com/someimageurl.png"
Ok.stream({ content: Iteratee[Array[Byte], Unit] =>
WS.url(imageUrl).withHeaders("Accept"->"image/png").get { response => content }
return
}).withHeaders("Content-Type"->"image/png")
}
this is intended for streaming large (>1 mb) files from internal API to requester.
The question is, why does it keep the connection open? Is there something it expects from upstream server? I tested the upstream server using curl, and the connection does close - it just doesn't close when passed through this proxy.
The reason that the stream doesn't finish is because an EOF isn't sent to the iteratee that comes back from WS.get() call. Without this explicit EOF, the connection stays open - as it's in chunked mode, and potentially a long-running, comet-like connection.
Here's the fixed code:
Ok.stream({ content: Iteratee[Array[Byte], Unit] =>
WS.url(imageUrl)
.withHeaders("Accept"->"image/png")
.get { response => content }
.onRedeem { ii =>
ii.feed(Input.EOF)
}
}).withHeaders("Content-Type"->"image/png")
Here is a modified version for play 2.1.0. See https://groups.google.com/forum/#!msg/play-framework/HwoRR-nipCc/gUKs9NexCx4J
Thanks Anatoly G for sharing.
def proxy = Action {
val url = "..."
Async {
val iterateePromise = Promise[Iteratee[Array[Byte], Unit]]
val resultPromise = Promise[ChunkedResult[Array[Byte]]]
WS.url(url).get { responseHeaders =>
resultPromise.success {
new Status(responseHeaders.status).stream({ content: Iteratee[Array[Byte], Unit] =>
iterateePromise.success(content)
}).withHeaders(
"Content-Type" -> responseHeaders.headers.getOrElse("Content-Type", Seq("application/octet-stream")).head,
"Connection" -> "Close")
}
Iteratee.flatten(iterateePromise.future)
}.onComplete {
case Success(ii) => ii.feed(Input.EOF)
case Failure(t) => resultPromise.failure(t)
}
resultPromise.future
}
}
Update for play 2.2.x:
def proxy = Action.async {
val url = "http://localhost:9000"
def enumerator(chunks: Iteratee[Array[Byte], Unit] => _) = {
new Enumerator[Array[Byte]] {
def apply[C](i: Iteratee[Array[Byte], C]): Future[Iteratee[Array[Byte], C]] = {
val doneIteratee = Promise[Iteratee[Array[Byte], C]]()
chunks(i.map {
done =>
doneIteratee.success(Done[Array[Byte], C](done)).asInstanceOf[Unit]
})
doneIteratee.future
}
}
}
val iterateePromise = Promise[Iteratee[Array[Byte], Unit]]()
val resultPromise = Promise[SimpleResult]()
WS.url(url).get {
responseHeaders =>
resultPromise.success(new Status(responseHeaders.status).chunked(
enumerator({
content: Iteratee[Array[Byte], Unit] => iterateePromise.success(content)
}
)).withHeaders(
"Content-Type" -> responseHeaders.headers.getOrElse("Content-Type", Seq("application/octet-stream")).head,
"Connection" -> "Close"))
Iteratee.flatten(iterateePromise.future)
}.onComplete {
case Success(ii) => ii.feed(Input.EOF)
case Failure(t) => throw t
}
resultPromise.future
}
if anyone has a better solution, it interests me greatly!