I'm working on a REST service using Akka HTTP (in Scala). I would like a parameter that is passed in to a http get request to be converted to the ZonedDateTime type. The code works fine if I try to use String or Int but fails with a ZonedDateTime type. The code would look like so:
parameters('testparam.as[ZonedDateTime])
Here is the error that I'm seeing:
Error:(23, 35) type mismatch;
found : akka.http.scaladsl.common.NameReceptacle[java.time.ZonedDateTime]
required: akka.http.scaladsl.server.directives.ParameterDirectives.ParamMagnet
parameters('testparam.as[ZonedDateTime]){
If I add more than one parameter to the list I get a different error:
Error:(23, 21) too many arguments for method parameters: (pdm: akka.http.scaladsl.server.directives.ParameterDirectives.ParamMagnet)pdm.Out
parameters('testparam.as[ZonedDateTime], 'testp2){
I found this in the documentation when I was researching the problem http://doc.akka.io/japi/akka-stream-and-http-experimental/2.0/akka/http/scaladsl/server/directives/ParameterDirectives.html and I tried the workaround of adding import akka.http.scaladsl.server.directives.ParameterDirectives.ParamMagnet along with using Scala 2.11 but the problem persisted.
Could someone please explain what I'm doing wrong and why the ZonedDateTime type doesn't work? Thanks in advance!
Here is a code snippet that should reproduce the problem I'm seeing
import java.time.ZonedDateTime
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.io.StdIn
object WebServer {
def main(args: Array[String]) {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val route =
path("hello") {
get {
parameters('testparam.as[ZonedDateTime]){
(testparam) =>
complete(testparam.toString)
}
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
As ZonedDateTime is not natively unmarshalled by Akka-HTTP, you will need to provide a custom unmarshaller to the parameters directive.
This functionality is briefly described in the docs here.
Your unmarshaller can be created from a function by using Unmarshaller.strict, e.g.
val stringToZonedDateTime = Unmarshaller.strict[String, ZonedDateTime](ZonedDateTime.parse)
This example assumes your param is provided in an ISO format. If it's not, you'll need to amend the unmarshalling function.
You can then use the unmarshaller passing it to the parameters directive:
parameters('testparam.as(stringToZonedDateTime)){ testparam =>
complete(testparam.toString)
}
Related
Hello I'm writing scala code to pull the data from API.
Data is paginated, so I'm pulling a data sequentially.
Now, I'm looking a solution to pulling multiple page parallel and stuck to create WSClient programatically instead of Inject.
Anyone have a solution to create WSClient ?
I found a AhcWSClient(), but it required to implicitly import actor system.
When you cannot Inject one as suggested in the other answer, you can create a Standalone WS client using:
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import play.api.libs.ws._
import play.api.libs.ws.ahc.StandaloneAhcWSClient
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val ws = StandaloneAhcWSClient()
No need to reinvent the wheel here. And I'm not sure why you say you can't inject a WSClient. If you can inject a WSClient, then you could do something like this to run the requests in parallel:
class MyClient #Inject() (wsClient: WSClient)(implicit ec: ExecutionContext) {
def getSomething(urls: Vector[String]): Future[Something] = {
val futures = urls.par.map { url =>
wsClient.url(url).get()
}
Future.sequence(futures).map { responses =>
//process responses here. You might want to fold them together
}
}
}
I am using Akka HTTP as a client to do a POST request and parse the answer. I am using Play JSON and I get the following compiler error:
could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.Unmarshaller[akka.http.javadsl.model.ResponseEntity,B]
[ERROR] Unmarshal(response.entity).to[B].recoverWith {
This is the dependency I added to use Play JSON instead of Spray:
"de.heikoseeberger" %% "akka-http-play-json"
My class definition is:
class HttpClient(implicit val system: ActorSystem, val materializer: Materializer) extends PlayJsonSupport {
and the method definition is:
private def parseResponse[B](response: HttpResponse)(implicit reads: Reads[B]): Future[B] = {
if (response.status().isSuccess) {
Unmarshal(response.entity).to[B].recoverWith {
....
In the imports I have:
import play.api.libs.json._
import scala.concurrent.ExecutionContext.Implicits.global
import de.heikoseeberger.akkahttpplayjson.PlayJsonSupport._
It seems to me that I have the required implicits in scope. The Marshal part has a similar logic (but with Writes instead of Reads) and compiles fine. What am I missing?
Check your other imports. Based on the error message, it appears that you're using akka.http.javadsl.model.HttpResponse instead of akka.http.scaladsl.model.HttpResponse; PlayJsonSupport only supports the Scala DSL:
private def parseResponse[B](response: HttpResponse)(implicit reads: Reads[B]): Future[B] = ???
// ^ this should be akka.http.scaladsl.model.HttpResponse
In other words, use
import akka.http.scaladsl.model._
instead of
import akka.http.javadsl.model._
Let's say you have 100 models, and for all of them you want the client to be able to do GET/POST/PUT/DELETE.
I read this article and a few others, but even with the promising title it seems that a lot of code would have to be duplicated for each model. Boilerplate, as some would call it.
Ideally I want to be able to say something like
case class AwesomeCaseClass(primeNumber : Long)
case class EvenMoreAwesomeCaseClass(favoritePrimeNumber : Long)
and then specify either all commands that should be accepted for each class (or maybe things like UPSERT).
I cannot find anyone who has done this, can anyone provide a link or a pointer in the right direction?
When Googling I also came across this, but for Akka HTTP in particular there seemed to only be support for API documentation.
Here is my attempt so far:
package route
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpResponse
import akka.http.scaladsl.server.Route
import akka.stream.ActorMaterializer
import scala.reflect._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller
/**
* Created by flyrev on 30.05.16.
*/
case class Test(name: String)
class REST[T](implicit um: FromRequestUnmarshaller[T]) {
def generateRoute(klazz: Class[_]) : Route = {
path("test" / klazz.getSimpleName) {
get {
complete(HttpResponse(entity = "OK", status = 200))
} ~ post {
entity(as[T]) {
// Insert into DB here
(zomg) => complete(HttpResponse(entity = "Would have POSTed that stuff", status = 200))
}
}
} ~ get {
complete(HttpResponse(entity="Not found", status=404))
}
}
}
object GenerateRouteFromClass extends App {
def runtimeClass[T: ClassTag] = classTag[T].runtimeClass
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import argonaut._
import CodecJson.derive
implicit val um = derive[Test]
Http().bindAndHandle(new REST[Test].generateRoute(runtimeClass[Test]), "localhost", 8080)
}
However, this gives:
Error:(45, 24) could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[route.Test]
Http().bindAndHandle(new REST[Test].generateRoute(runtimeClass[Test]), "localhost", 8080)
^
Error:(45, 24) not enough arguments for constructor REST: (implicit um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[route.Test])route.REST[route.Test].
Unspecified value parameter um.
Http().bindAndHandle(new REST[Test].generateRoute(runtimeClass[Test]), "localhost", 8080)
^
I am looking into the akka-http documentation, and find there how to serve html content with the low-level server api using HttpResponses. However, I can not find any good examples on how to serve html-content, which should be correctly represented in the browser. The only things that I find and can get working is when it serve String content as below. I found an example that:
imports akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
but I can not see that scaladsl contains marshallers (it contains marshalling)
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.http.scaladsl.server.Directives._
import akka.actor.ActorSystem
object HttpAkka extends App{
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher
val route = get {
pathEndOrSingleSlash {
complete("<h1>Hello</h1>")
}
}
Http().bindAndHandle(route, "localhost", 8080)
}
found a relevant question here Akka-http: Accept and Content-type handling
I did not fully understand the answer in that link, but tried the following (i eventually got this to work..):
complete {
HttpResponse(entity=HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say Hello</h1>"))
}
as well as:
complete {
respondWithHeader (RawHeader("Content-Type", "text/html(UFT-8"))
"<h1> Say hello</h1>"
}
The best way to handle this probably is to use ScalaXmlSupport to build a NodeSeq marshaller that will properly set the content type as text/html. To do that, you first need to add a new dependency as ScalaXmlSupport is not included by default. Assuming you are using sbt, then the dependency to add is as follows:
"com.typesafe.akka" %% "akka-http-xml-experimental" % "2.4.2"
Then, you can setup a route like so to return a Scala NodeSeq that will be flagged as text/html when akka sets the content type:
implicit val system = ActorSystem()
import system.dispatcher
implicit val mater = ActorMaterializer()
implicit val htmlMarshaller = ScalaXmlSupport.nodeSeqMarshaller(MediaTypes.`text/html` )
val route = {
(get & path("foo")){
val resp =
<html>
<body>
<h1>This is a test</h1>
</body>
</html>
complete(resp)
}
}
Http().bindAndHandle(route, "localhost", 8080
The trick to getting text/html vs text/xml is to use the nodeSeqMarshaller method on ScalaXmlSupport, passing in MediaTypes.text/html as the media type. If you just import ScalaXmlSupport._ then the default marshaller that will be in scope will set the content type to text/xml.
MyService.scala:33: could not find implicit value for parameter eh: spray.routing.ExceptionHandler
I have run into a "missing implicit" compilation error using Akka, in spray.io code that makes an http call to a separate back-end server, as part of responding to an http get. The code needs to import quite a lot of Spray and Akka libraries, so it's a bit hard figuring whether there may be some library conflicts causing this, and I'd rather figure how to logically trace this sort of problem for this and other cases.
The missing implicit is encountered on calling runRoute(myRoute)
Here's the code:
import spray.routing._
import akka.actor.Actor
import akka.actor.ActorSystem
import spray.http._
import MediaTypes._
import akka.io.IO
import spray.httpx.RequestBuilding._
import scala.concurrent.Future
import spray.can.Http
import spray.http._
import akka.util.Timeout
import HttpMethods._
import akka.pattern.ask
import akka.event.Logging
import scala.concurrent.duration._
// we don't implement our route structure directly in the service actor because
// we want to be able to test it independently, without having to spin up an actor
class MyServiceActor extends Actor with MyService with akka.actor.ActorLogging {
log.info("Starting")
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(myRoute)
}
// this trait defines our service behavior independently from the service actor
trait MyService extends HttpService {
implicit val system: ActorSystem = ActorSystem()
implicit val timeout: Timeout = Timeout(15.seconds)
import system.dispatcher // implicit execution context
//val logger = context.actorSelection("/user/logger")
val logger = actorRefFactory.actorSelection("../logger")
val myRoute =
{
def forward(): String = {
logger ! Log("forwarding to backend")
val response: Future[HttpResponse] =
(IO(Http) ? Get("http:3080//localhost/backend")).mapTo[HttpResponse]
"<html><body><h1>api response after backend processing</h1></body></html>"
}
path("") {
get {
respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here
complete(forward)
}
}
}
}
}
I am wondering what's the best way to solve this, hopefully providing insight into how to solve similar problems with implicits being missing, as they are somehow inherently not straightforward to track down.
EDIT: when trying to directly pass implicits as in #christian's answer below, I get:
MyService.scala:35: ambiguous implicit values:
both value context in trait Actor of type => akka.actor.ActorContext
and value system in trait MyService of type => akka.actor.ActorSystem
match expected type akka.actor.ActorRefFactory
RoutingSettings.default, LoggingContext.fromActorRefFactory)
^
Not quite sure why being specific as in #christian's answer leaves room for ambiguity for the compiler...
I ran into the same "could not find implicit value for parameter eh: spray.routing.ExceptionHandler" error earlier today. I tried #Christian's approach but saw a few "implicit values for xxx" creeping up. After scouting the error message a little I found adding implicit val system = context.system to the actor that runRoute solved the problem.
runRoute expects a few implicits. You are missing an import:
import spray.routing.RejectionHandler.Default
Update:
I think we also did have some problems with runRoute because we are supplying the implicit parameters explicitly:
runRoute(route)(ExceptionHandler.default, RejectionHandler.Default, context,
RoutingSettings.default, LoggingContext.fromActorRefFactory)
Update2:
To fix the last error, remove the creation of the ActorSystem (in MyService you get the actor system from MyServiceActor - therefore you have to use a self type annotation). This compiles:
import akka.actor.Actor
import akka.io.IO
import spray.httpx.RequestBuilding._
import spray.http.MediaTypes._
import spray.routing.{RoutingSettings, RejectionHandler, ExceptionHandler, HttpService}
import spray.util.LoggingContext
import scala.concurrent.Future
import spray.can.Http
import spray.http._
import akka.util.Timeout
import HttpMethods._
import akka.pattern.ask
import akka.event.Logging
import scala.concurrent.duration._
// we don't implement our route structure directly in the service actor because
// we want to be able to test it independently, without having to spin up an actor
class MyServiceActor extends Actor with MyService with akka.actor.ActorLogging {
log.info("Starting")
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
implicit def actorRefFactory = context
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(myRoute)(ExceptionHandler.default, RejectionHandler.Default, context,
RoutingSettings.default, LoggingContext.fromActorRefFactory)
}
// this trait defines our service behavior independently from the service actor
trait MyService extends HttpService { this: MyServiceActor =>
implicit val timeout: Timeout = Timeout(15.seconds)
implicit val system = context.system
//val logger = context.actorSelection("/user/logger")
val logger = actorRefFactory.actorSelection("../logger")
val myRoute =
{
def forward(): String = {
//logger ! Log("forwarding to backend")
val response: Future[HttpResponse] =
(IO(Http) ? Get("http:3080//localhost/backend")).mapTo[HttpResponse]
"<html><body><h1>api response after backend processing</h1></body></html>"
}
path("") {
get {
respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here
complete(forward)
}
}
}
}
}