Play2, scala: how to use controller result from another controller? - scala

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

Related

How can I serialize Sangria responses with json4s and Akka HTTP?

I'm working through a slight variation of Sangria's Getting Started, using Akka HTTP. I'm attempting to use json4s-jackson as the serializaltion lib, but am running in to some trouble getting the response I want.
Specifically, the serialized response I get is the JSON version of the (StatusCode, Node) tuple:
{
"_1": {
"allowsEntity": true,
"defaultMessage": "OK",
"intValue": 200,
"reason": "OK"
},
"_2": {
"data": {
"foo": {
"id": "1",
"name": "Foo"
}
}
}
}
The data portion is correct, but obviously I just want that and not the first element of the serialized tuple.
I'm using akka-http-json4s, so my trait with route looks like:
case class GraphQlData(query: String, operation: Option[String])
trait FooController {
import de.heikoseeberger.akkahttpjson4s.Json4sSupport._
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
val fooRoutes = post {
entity(as[GraphQlData]) { data =>
QueryParser.parse(data.query) match {
// query parsed successfully, time to execute it!
case Success(queryAst) =>
complete {
Executor
.execute(
SchemaDefinition.FooSchema,
queryAst,
new FooService,
operationName = data.operation
)
.map(OK -> _)
.recover {
case error: QueryAnalysisError => BadRequest -> error.resolveError
case error: ErrorWithResolver => InternalServerError -> error.resolveError
}
}
// can't parse GraphQL query, return error
case Failure(error) =>
complete(BadRequest -> error.getMessage)
}
}
}
implicit def executionContext: ExecutionContext
}
For the life of me I can't figure out what's wrong. I've been looking at sangria-akka-http-example but it seems to be exactly the same, with the exception of using spray-json instead of json4s.
Ideas? Thanks!
Ah, figured it out. I neglected to add
import sangria.marshalling.json4s.jackson._
to the trait defining the route. Adding it does the trick.
Just wanted to provide a quick update to this answer for the full GraphQLRequest. Now the variables are included in the request.
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s._
import org.json4s.JsonAST.JObject
import sangria.marshalling.json4s.jackson._
case class GQLRequest(query: String, operationName: Option[String], variables: JObject)
trait SomeJsonSupport extends Json4sSupport {
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
}
trait GraphQLResource extends SomeJsonSupport{
implicit val timeout:Timeout
implicit val system:ActorSystem
import system.dispatcher
def graphqlRoute: Route =
(post & path("graphql")) {
entity(as[GQLRequest]) { requestJson =>
println(s"This is the requestJson = $requestJson")
graphQLEndpoint(requestJson)
}
} ~
get {
println(s"This is working")
getFromResource("graphiql.html")
}
def graphQLEndpoint(requestJson: GQLRequest): Route = {
val route = QueryParser.parse(requestJson.query) match {
case Success(query) =>
println(s"This is the query $query")
val vars = requestJson.variables match {
case jObj:JObject => jObj
case _ => JObject(List.empty)
}
val futureJValue = Executor.execute(clientSchema,
query,
NclhGqlRequest(this),
operationName = requestJson.operationName,
variables = vars)
val futureTupleStatusCodeJValue = futureJValue.map(OK -> _).recover {
case error: QueryAnalysisError => BadRequest -> error.resolveError
case error: ErrorWithResolver => InternalServerError -> error.resolveError
}
complete(futureTupleStatusCodeJValue)
case Failure(error) =>
complete(BadRequest, error.getMessage)
}
route
}

Appending to given formdata for http post

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"))
}
}

Akka-http logrequest not logging the request body

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)

Choosing between two Action.async blocks, why can't I specify Action.async in the caller?

On the first line below, I want to know if there's anything I can or should put to the left of the = that will future-proof against bad editing. I had one Action.async, but now I want to have two choices (run a single MongoDB query or run multiple MongoDB queries) to arrive at the answer to the web query q. My code compiles as-is, but I think I should have to put something before the = to make the code more type-safe.
def q(arg: String) =
if (wantMultipleQueriesByDataSource)
runMultipleQueriesByDataSource(arg)
else
runSingleQuery(arg)
def runSingleQuery(arg: String) = Action.async {
if (oDb.isDefined) {
val collection: MongoCollection[Document] = oDb.get.getCollection(collectionName)
val fut = getMapData(collection, arg)
fut.map { docs: Seq[Document] =>
val docsSources = List(DocsSource(docs, "*"))
val pickedDocs = pickDocs(args, docsSources)
Ok(buildQueryAnswer(pickedDocs))
} recover {
case e => BadRequest("FAIL(rect): " + e.getMessage)
}
}
else Future.successful(Ok(buildQueryAnswer(Nil)))
}
def runMultipleQueriesByDataSource(arg: String) = Action.async {
if (oDb.isDefined) {
val collection: MongoCollection[Document] = oDb.get.getCollection(collectionName)
val dataSources = List("apples", "bananas", "cherries")
val futureCollections = dataSources map { getMapDataByDataSource(collection, _, arg) }
Future.sequence(futureCollections) map {
case docsGroupedByDataSource: Seq[Seq[Document]] =>
val docsSources = (docsGroupedByDataSource zip dataSources) map {x => DocsSource(x._1, x._2)}
val pickedDocs = pickDocs(args, docsSources)
Ok(buildQueryAnswer(pickedDocs))
case _ => Ok(buildQueryAnswer(Nil))
} recover {
case e => BadRequest("FAIL(rect): " + e.getMessage)
}
}
else Future.successful(Ok(buildQueryAnswer(Nil)))
}
You don't need to because Scala has type inference in this case. But if you want, you can change the method signature to the following:
def q(arg: String): Action[AnyContent]
I'm assuming here that you are using Playframework.
Edit: In fact, in this case, Action.async return an Action[AnyContent] and it receives a block that returns a Future[Result].

TypeError Expected: Result, Actual: Future[SimpleResult]

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