Get content from Akka ResponseEntity in Scala - scala

I do a GET HTTP call to a rest service which returns a json. I would like to parse the json to a scala object but here I got stuck. I am using the Akka api and I can't manage to retrieve the content from the Akka's ResponseEntity
Here is my sbt file:
name := "ScalaHttp"
version := "1.0"
scalaVersion := "2.11.8"
libraryDependencies ++={
val akkaV = "2.4.5"
Seq(
"com.typesafe.akka" %% "akka-http-core" % akkaV,
"com.typesafe.play" %% "play-json" % "2.4.0-M3"
)
}
And here is the app
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpRequest
import akka.stream.ActorMaterializer
import scala.concurrent.ExecutionContext.Implicits.global
object Sender {
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
Http().singleRequest(HttpRequest(uri = "http://declinators.com/declinator?noun=komunikacja")) foreach {
y => println(y.entity)
println(y.entity.getContentType())
println(y.entity.contentType)
}
}
}
This prints:
HttpEntity.Strict(application/json,{"nominative":"komunikacja","genitive":"komunikacji","dative":"komunikacji","accusative":"komunikację","instrumental":"komunikacją","locative":"komunikacji","vocative":"komunikacjo"})
application/json
application/json
Here come the questions:
1. Why ResponseEntity supplies getContentType() and contentType()? They return the same thing.
2. Getting the contentyType is easy, you have two ways for doing it, but how can I get the content itself, so I can play (i.e. parse it using play) with the json!

You can use entity.data.toString for Binary content type, or the following code piece
data.decodeString(nb.charset.value)
Please follow the HttpEntity.Strict.toString implementation here for detail:
https://github.com/akka/akka/blob/master/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpEntity.scala#L316

Related

Error while calling an API using akka in scala

TO start with I am very new to Scala and also I don't have any Java experience also. I am trying to call an API using simple Scala code and running into the errors. Code looks like:
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import scala.concurrent.Future
import scala.util.{ Failure, Success }
object Client {
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = "https://akka.io"))
responseFuture
.onComplete {
case Success(res) => println(res)
case Failure(_) => sys.error("something wrong")
}
}
}
Basically, I have just copied the code from Akka documentation and trying to run it.
I get the following errors:
not found: value ActorSystem
implicit val system = ActorSystem()
not found: value ActorMaterializer
implicit val materializer = ActorMaterializer()
Cannot find an implicit ExecutionContext. You might pass
an (implicit ec: ExecutionContext) parameter to your method.
Also 'ActorMaterializer()' now seems to be deprecated. Is this the reason for errors?
Thanks in advance :)
You may need to add akka-streams as a dependency in your build tool.
ActorMaterializer.apply should just be a warning, unrelated to your other error. Might be worth opening an issue on github asking for updated docs for this snippet though.
This should get you going.
In your build.sbt
scalaVersion := "2.13.8"
val akkaVersion = "2.6.19"
val akkaHTTPVersion = "10.2.9"
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-stream" % akkaVersion,
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-http" % akkaHTTPVersion
)
The code below is from the latest Doc, with the added line to consume the response entity as described in the Doc.
object HttpClientSingleRequest extends App {
implicit val system: ActorSystem = ActorSystem()
import system.dispatcher
val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = "https://akka.io"))
responseFuture
.onComplete {
case Success(res) =>
// Even if we don’t care about the response entity, we must consume it
res.entity.discardBytes()
println(s"Success: ${res.status}")
case Failure(ex) => sys.error(s"Something wrong: ${ex.getMessage}")
}
}

How to use a standalone WSClient in a Scala application

I would like to use ws in a standalone application. Trying this code, copied from https://gist.github.com/cdimascio/46b2b7d2986636c1189c :
import com.ning.http.client.AsyncHttpClientConfig
import play.api.libs.ws.ning._
import play.api.libs.ws._
// provide an execution context
import scala.concurrent.ExecutionContext.Implicits.global
object WSStandaloneTest {
def main(args: Array[String]) {
// set up the client
val config = new NingAsyncHttpClientConfigBuilder(DefaultWSClientConfig()).build
val builder = new AsyncHttpClientConfig.Builder(config)
val client = new NingWSClient(builder.build)
// execute a GET request
val response = client.url("http://www.example.com").get
// print the response body
response.foreach(r => {
println(r.body)
// not the best place to close the client,
// but it ensures we dont close the threads before the response arrives
// Good enough for the gist :-D
client.close()
})
}
}
Results in the following error:
[error] object ning is not a member of package play.api.libs.ws
[error] import play.api.libs.ws.ning._
In my build.sbt I have this:
libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.1"
libraryDependencies += "com.typesafe.play" %% "play-ws" % "2.6.1"
What am I doing wrong?
NingWSClient is deprecated in Play! 2.5.x.
In 2.6.x
The ning package has been replaced by the ahc package, and the Ning* classes replaced by AHC*.
There is a migration guide available in the official doc.
So you can choose to downgrade to 2.5.x and use ning or update the code.

unable to use WS.url() in Play app tutorial

I am doing a tutorial for Play framework with Scala. I am quite early into the tutorial and i am having a problem with ws. In my class WS is not recognized although that says to use WS.url("url-here") and to import play.api.libs._ which i have done both. I have also tried using ws.url("url-here") as well... and here wsis recognized but after that i get a "can't resolve symbol 'url'". Here is my build.sbt:
name := """play_hello"""
organization := "x.x"
version := "1.0-SNAPSHOT"
lazy val root = (project in file(".")).enablePlugins(PlayScala)
scalaVersion := "2.12.3"
libraryDependencies ++= Seq(
"org.scalatestplus.play" %% "scalatestplus-play" % "3.1.2" % Test,
"com.ning" % "async-http-client" % "1.9.29",
guice,
ws
)
And here is the code for my class:
package controllers
import javax.inject.Inject
import com.ning.http.client.oauth.{ConsumerKey, RequestToken}
import play.api.Play.current
import play.api.libs._
import play.api.mvc._
import scala.concurrent.Future
class Application #Inject()(cc: ControllerComponents) extends
AbstractController(cc){
def tweets = Action.async{
credentials.map { case (consumerKey, requestToken) =>
WS.url("http://stream.twitter.com")
Future.successful{
Ok
}
}getOrElse{
Future.successful{
InternalServerError("Twitter credentials are missing!")
}
}
}
def credentials: Option[(ConsumerKey, RequestToken)] = for{
apiKey <- current.configuration.getString("twitter.apiKey")
apiSecret <- current.configuration.getString("twitter.apiSecret")
token <- current.configuration.getString("twitter.token")
tokenSecret <- current.configuration.getString("twitter.tokenSecret")
}yield (
new ConsumerKey(apiKey, apiSecret),
new RequestToken(token, tokenSecret)
)
}
I Figure that most likely this is some type of problem with a dependency conflict. Here is a screenshot of ws related libraries in project structure. I would appreciate any help in finding a solution to this. Thank you.
The solution was to add ws: WSClient to the parameters of Application class constructor. Apperently standalone WS object has been removed in more recent versions of ws library.
class Application #Inject()(cc: ControllerComponents, ws: WSClient) extends AbstractController(cc)
Now i can use:
ws.url("https://stream.twitter.com/1.1/statuses/filter.json")
Also according to the documentation on play website if you for some reason can not use an injected WSClient, then you can create an instance of one and use that.

Could not access type Unmarshaller in value akka.http.javadsl.unmarshalling

I am trying to write a simple Http client using Akka Http Client API. Towards this I have written the following code
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.unmarshalling._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Sink, Source}
import scala.concurrent.duration._
import scala.concurrent.{Await}
import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import spray.json._
final case class Post(postId: Int, id: Int, name: String, email: String, body: String)
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val postFormat = jsonFormat5(Post.apply)
}
class AkkaHttpClient extends App with Directives with JsonSupport {
implicit val system = ActorSystem("my-Actor")
implicit val actorMaterializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val httpClient = Http().outgoingConnection(host="http://jsonplaceholder.typicode.com/")
val flow = Source.single(HttpRequest(uri = Uri("/comments/1")))
.via(httpClient)
.mapAsync(1)(r => Unmarshal(r.entity).to[Post])
.runWith(Sink.head)
val results = Await.result(flow, 15 seconds)
println(results)
}
My build.sbt file looks like
name := "Akka-Http-Client"
version := "1.0"
scalaVersion := "2.11.8"
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http-experimental" % "2.4.9-RC1",
"com.typesafe.akka" %% "akka-http-spray-json-experimental" % "2.4.9-RC1"
)
When I try to compile my code I get these errors
Error:scalac: missing or invalid dependency detected while loading class file 'Unmarshaller.class'.
Could not access type Unmarshaller in value akka.http.javadsl.unmarshalling,
because it (or its dependencies) are missing. Check your build definition for
missing or conflicting dependencies. (Re-run with `-Ylog-classpath` to see the problematic classpath.)
A full rebuild may help if 'Unmarshaller.class' was compiled against an incompatible version of akka.http.javadsl.unmarshalling.
I am having the same problem on 2.4.9-RC1, falling back to 2.4.8 solves the problem.
OR you could use this workaround described here: https://github.com/akka/akka/issues/21105

POST request using spray-client

I want to send XML over HTTP POST request to server using spray-client with some headers set etc. However, only examples that I can find are for JSON requests.
Can somebody provide a simple snippet of code for XML over HTTP POST communication using spray-client?
Thanks!
Here is a small code sample for creating a spray HttpRequest that has an xml NodeSeq based payload. Let me know if you this helps or if you need more code (like submitting the request):
import spray.httpx.RequestBuilding._
import spray.http._
import HttpMethods._
import HttpHeaders._
import MediaTypes._
object SprayXml {
def main(args: Array[String]) {
val xml = <root>foo</root>
val req = Post("/some/url", xml)
}
}
The two dependencies I was using to make this code work are spray-client and spray-httpx.
The relevant pieces from my build.sbt are:
scalaVersion := "2.10.0"
resolvers ++= Seq(
"Scala Tools Repo Releases" at "http://scala-tools.org/repo-releases",
"Typesafe Repo Releases" at "http://repo.typesafe.com/typesafe/releases/",
"spray" at "http://repo.spray.io/"
)
libraryDependencies ++= Seq(
"io.spray" % "spray-httpx" % "1.1-M7",
"io.spray" % "spray-client" % "1.1-M7",
"com.typesafe.akka" %% "akka-actor" % "2.1.0"
)
With a hacky way to be specific about the content type. Note payload can be string or xml literal.
import spray.client.pipelining._
import spray.http._
val pipeline: HttpRequest => Future[HttpResponse] = {
addHeader("My-Header-Key", "myheaderdata") ~>
((_:HttpRequest).mapEntity( _.flatMap( f => HttpEntity(
f.contentType.withMediaType(MediaTypes.`application/xml`),f.data))))
~> sendReceive
}
pipeline(
Post(
"http://www.example.com/myendpoint", <MyXmlTag>MyXmlData</MyXmlTag>
)
)