From the Scala Spray documentation it is not clear how to check if it is not able to bind to a particular port
implicit val system = ActorSystem("mediaiton")
implicit val timeout = Timeout(5, TimeUnit.SECONDS)
val service = system.actorOf(Props[IotRestNB], "mediaiton")
println(s"Going to start the REST NB at $host $port")
IO(Http) ! Http.Bind(service, interface = host, port = port)
Spend a day trying to figure out from different post
import java.util.concurrent.TimeUnit
import akka.actor.{ActorSystem, Props}
import akka.io.IO
import akka.util.Timeout
import nb.rest.IotRestNB
import spray.can.Http
implicit val system = ActorSystem("lwm2m-mediaiton")
implicit val timeout = Timeout(5, TimeUnit.SECONDS)
val service = system.actorOf(Props[IotClassNB], "lwm2m-mediaiton")
println(s"Going to start the REST NB at $host $port")
IO(Http).tell(Http.Bind(service, interface = host, port = port), sender = service)
Now the actor Class - IotClassNB
import java.util.concurrent.Executors
import akka.actor.Actor
import lwm2m.server.BootUp
import org.eclipse.leshan.core.request.ContentFormat
import spray.can.Http._
import spray.http.MediaTypes
import spray.routing.HttpService
import scala.concurrent.{ExecutionContext, Future}
class IotClassNBextends Actor with MediationRoute {
//mixin class
def actorRefFactory = context
def receive = handleConnection orElse runRoute(route)
def handleConnection: Receive = {
case b: Bound =>
println("***REST Server Started***")
Future.successful(b)
case failed: CommandFailed =>
println("***REST Server Could not be Started***")//this is what we want
Future.failed(new
RuntimeException("Binding failed"))
}
}
trait MediationRoute extends HttpService {
// Execution Context for blocking ops
val blockingExecutionContext = {
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(10))
}
val route = {
pathPrefix("v1") {
pathPrefix("mediation") {
respondWithMediaType(MediaTypes.`application/json`) {
pathPrefix("get_connected_clients") {
pathEnd {
get {
complete(
// Future.apply {
get_registered_clients())
// }(blockingExecutionContext))
}
}.....
And here is how to test your Spary server via Spray Client
#Test def test_RestNB(): Unit = {
implicit val system = ActorSystem("test")
import system.dispatcher
val pipeline: HttpRequest => Future[HttpResponse] = sendReceive
implicit val timeout = Timeout(25, TimeUnit.SECONDS)
val server_url = s"http://${host}:${port}/xxx/"
val response: Future[HttpResponse] = pipeline(Get(server_url))
val result = Await.result(response, timeout.duration) //wait for timeout
// println(s"Await Result is ${result.entity.asString}")
response.onComplete {
case Success(result: HttpResponse) =>
logger.info("Result: " + result.entity.asString)
assert(result.entity.asString === xxxxx")
case Failure(error) =>
logger.error(error + "Couldn't get list of items")
case _ =>
assert(false)
}
Related
I have the following code, that does not keep the connection open to the websocket server:
import akka.Done
import akka.actor.{Actor, ActorLogging, Props}
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.{Message, TextMessage, WebSocketRequest}
import akka.stream.{ActorMaterializer, Materializer}
import akka.stream.scaladsl.{Flow, Keep, Sink, Source}
import scala.concurrent._
import scala.util.{Failure, Success}
object WsActor {
def props: Props = Props(new WsActor)
}
final class WsActor extends Actor with ActorLogging {
import com.sweetsoft.WsConnector._
implicit val materializer: Materializer = ActorMaterializer()
implicit val ec: ExecutionContextExecutor = context.system.dispatcher
implicit val actor = context.system
// Future[Done] is the materialized value of Sink.foreach,
// emitted when the stream completes
private val incoming: Sink[Message, Future[Done]] =
Sink.foreach[Message] {
case message: TextMessage.Strict =>
println(message.text)
case _ =>
println("Unknown messages.")
}
//private val outgoing: Source[Message, Promise[Option[Message]]] =
// Source.maybe[Message]
// val flow: Flow[Message, Message, Promise[Option[Message]]] =
// Flow.fromSinkAndSourceMat(incoming, Source.maybe[Message])(Keep.right)
log.info("Websocket actor started.")
override def receive: Receive = {
case Initialized =>
log.info("Initialization to receive messages via stream.")
sender() ! Ack
case Completed =>
log.info("Streams completed.")
sender() ! Ack
case Msg(value) =>
val replyTo = sender()
val flow: Flow[Message, Message, Promise[Option[Message]]] =
Flow.fromSinkAndSourceMat(incoming, Source.single(TextMessage(value)).concatMat(Source.maybe[Message])(Keep.right))(Keep.right)
val (upgradeResponse, _) =
Http().singleWebSocketRequest(WebSocketRequest("ws://127.0.0.1:7000/ws"), flow.mapAsync(4)(msg => Future(msg)))
upgradeResponse.flatMap { upgrade =>
if (upgrade.response.status == StatusCodes.SwitchingProtocols) {
Future.successful(Done)
} else {
throw new RuntimeException(s"Connection failed: ${upgrade.response.status}")
}
}.onComplete {
case Success(_) =>
replyTo ! Ack
log.info("Done")
case Failure(ex) => log.error(ex.getMessage)
}
case Failed(ex) =>
log.info(s"Stream failed with ${ex.getMessage}.")
}
}
So every time, when a message is received, it will close the connection and open a new connection for the next request.
The question is, how can I keep the connection open?
Http().webSocketClientFlow in stead of Http().singleWebSocketRequest
Http().webSocketClientFlow will give you a Flow Flow[Message, Message, Future[WebSocketUpgradeResponse]]
This will not create a new connection every time.
You should declare it in the companion object, so every instance of the class can use the same connection.
First declare your actor system for entire application in a separate package.
object ActorEssentials {
implicit val actorSystem = ActorSystem("test")
}
Then you can declare the following
object WsActor {
import ActorEssentials._
def props: Props = Props[WsActor]
val flow = Http()(actorSystem).webSocketClientFlow(...)
}
I can't figure out how to create cachedHostConnectionPool in akka-http using scala for sending https requests. queueRequest(HttpRequest(uri = "https://example.com") sends a request to http, cachedHostConnectionPool[Promise[HttpResponse]]("https://example.com") throws an error that : isn't expected character.
import scala.util.{ Failure, Success }
import scala.concurrent.{ Future, Promise }
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import akka.stream.{ OverflowStrategy, QueueOfferResult }
implicit val system = ActorSystem()
import system.dispatcher // to get an implicit ExecutionContext into scope
implicit val materializer = ActorMaterializer()
val QueueSize = 10
// This idea came initially from this blog post:
// http://kazuhiro.github.io/scala/akka/akka-http/akka-streams/2016/01/31/connection-pooling-with-akka-http-and-source-queue.html
val poolClientFlow = Http().cachedHostConnectionPool[Promise[HttpResponse]]("example.com")
val queue =
Source.queue[(HttpRequest, Promise[HttpResponse])](QueueSize, OverflowStrategy.dropNew)
.via(poolClientFlow)
.toMat(Sink.foreach({
case ((Success(resp), p)) => p.success(resp)
case ((Failure(e), p)) => p.failure(e)
}))(Keep.left)
.run()
def queueRequest(request: HttpRequest): Future[HttpResponse] = {
val responsePromise = Promise[HttpResponse]()
queue.offer(request -> responsePromise).flatMap {
case QueueOfferResult.Enqueued => responsePromise.future
case QueueOfferResult.Dropped => Future.failed(new RuntimeException("Queue overflowed. Try again later."))
case QueueOfferResult.Failure(ex) => Future.failed(ex)
case QueueOfferResult.QueueClosed => Future.failed(new RuntimeException("Queue was closed (pool shut down) while running the request. Try again later."))
}
}
val responseFuture: Future[HttpResponse] = queueRequest(HttpRequest(uri = "/"))
It seems like scala version supports only plain host names while in java you can provide a protocol too (from their tests):
http.cachedHostConnectionPool("akka.io", materializer());
http.cachedHostConnectionPool("https://akka.io", materializer());
http.cachedHostConnectionPool("https://akka.io:8080", materializer());
Any known workarounds?
You have to use cachedHostConnectionPoolHttps instead:
val poolClientFlow = Http().cachedHostConnectionPoolHttps[Promise[HttpResponse]]("example.com")
I am trying to add support for serializing and deserializing LocalDateTime in akka-http. I'm a little confused by the following error:
Error:(42, 20) could not find implicit value for parameter um:
akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[SomeData]
entity(as[SomeData]) { evt => complete(StatusCodes.Created) }
Error:(42, 20) not enough arguments for method as: (implicit um:
akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[SomeData])akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[SomeData].
Unspecified value parameter um.
entity(as[SomeData]) { evt => complete(StatusCodes.Created) }
What an I missing in the following code? I pasted my code into the minimalist sample from the Akka-http documentation. I am running Akka-http 10.0.6.
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
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import spray.json.{DefaultJsonProtocol, JsString, JsValue, JsonFormat}
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.server.Route
final case class SomeData (dateTime: LocalDateTime)
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val eventDataFormat: JsonFormat[SomeData] = jsonFormat1(SomeData)
implicit val localDateTimeFormat = new JsonFormat[LocalDateTime] {
private val iso_date_time = DateTimeFormatter.ISO_DATE_TIME
def write(x: LocalDateTime) = JsString(iso_date_time.format(x))
def read(value: JsValue) = value match {
case JsString(x) => LocalDateTime.parse(x, iso_date_time)
case x => throw new RuntimeException(s"Unexpected type ${x.getClass.getName} when trying to parse LocalDateTime")
}
}
// implicit val eventDataFormat: JsonFormat[SomeData] = jsonFormat1(SomeData) ** moved here **
}
object Main extends JsonSupport {
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val route:Route =
path("hello") {
post {
entity(as[SomeData]) { evt => complete(StatusCodes.Created) }
}
}
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
}
}
implicit val eventDataFormat: JsonFormat[SomeData] = jsonFormat1(SomeData)
↓
implicit val eventDataFormat = jsonFormat1(SomeData)
I have this crude test example with akka-http client and server.
Server.scala:
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Sink
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
import scala.concurrent.Future
class Server extends Runnable {
def run() = {
implicit val system = ActorSystem("server")
implicit val materializer = ActorMaterializer()
val serverSource = Http().bind(interface = "localhost", port = 8200)
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/stream"), _, _, _) =>
HttpResponse(entity = HttpEntity(MediaTypes.`text/plain`, "test"))
}
val bindingFuture: Future[Http.ServerBinding] = serverSource.to(Sink.foreach { connection =>
connection handleWithSyncHandler requestHandler
}).run()
}
}
Client.scala:
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{Uri, HttpRequest}
import akka.stream.ActorMaterializer
object Client extends App {
implicit val system = ActorSystem("client")
import system.dispatcher
new Thread(new Server).start()
implicit val materializer = ActorMaterializer()
val source = Uri("http://localhost:8200/stream")
val finished = Http().singleRequest(HttpRequest(uri = source)).flatMap { response =>
response.entity.dataBytes.runForeach { chunk =>
println(chunk.utf8String)
}
}
}
At the moment Server just replies with a single "test".
How do I change the HttpResponse in Server to send "test" as chunked (stream) in an endless loop every 1 second?
Found the answer.
Server.scala:
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Source, Sink}
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
import scala.concurrent.Future
import scala.concurrent.duration._
class Server extends Runnable {
def run() = {
implicit val system = ActorSystem("server")
implicit val materializer = ActorMaterializer()
val serverSource = Http().bind(interface = "localhost", port = 8200)
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/stream"), _, _, _) =>
HttpResponse(entity = HttpEntity.Chunked(ContentTypes.`text/plain`, Source(0 seconds, 1 seconds, "test")))
}
val bindingFuture: Future[Http.ServerBinding] = serverSource.to(Sink.foreach { connection =>
connection handleWithSyncHandler requestHandler
}).run()
}
}
I am building a microservice using Scala, akka-http, json4s. Also using case classes for my business bean classes. My case classes have scala Enumerations (I researched on the scala Enums and aware of the limitations, but this perfectly suits my current use cases).
With this background, when I tried to create a service, I am not able to understand where to define the
implicit val formats = DefaultFormats + new EnumNameSerializer(_ProfessionClaType) + new EnumNameSerializer(_LinkRelType)
Following is my rough scala class structure:
import akka.actor.ActorSystem
import akka.event.{Logging, LoggingAdapter}
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshalling.ToResponseMarshallable
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.server.Directives
import akka.stream.ActorMaterializer
import com.typesafe.config.{Config, ConfigFactory}
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import gremlin.scala.ScalaVertex
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex
import org.json4s.ext.EnumNameSerializer
import org.json4s.{DefaultFormats, jackson}
import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future}
trait Service {
implicit val system: ActorSystem
implicit def executor: ExecutionContextExecutor
implicit val materializer: ActorMaterializer
// ?? implicit val formats = DefaultFormats + new EnumNameSerializer(_ProfessionClaType) + new EnumNameSerializer(_LinkRelType)
lazy val client = TitanConnection.cluster.connect();
def config: Config
val logger: LoggingAdapter
def addPerson(p: Person): Future[Either[String, Person]] = {
try {
//Code to add person to database
val resultPerson = Person(propertyMap)
Future.successful(Right(resultPerson))
} catch {
case e :Exception => e.printStackTrace
Future.failed(new Exception("Person can't be created"))
}
}
def fetchPerson(pId: String): Future[Either[Error, Person]] = {
try {
//Code to fetch person object from database
result = results.one() //fetches the first record from results, if it exists
//Following is code for validation of the data
if(result.isNull)
Future.successful(Left(new Error("FAILED","","",s"""There is no person with requested personID:$pId""")))
else {
//Code to retrieve person and return the same as object
Future.successful(Right(resultPerson))
}
} catch {
case e :Exception => e.printStackTrace
Future.successful(Left(new Error("FATAL","","",s"""There is some exception while retrieving person with requested personID:$pId""")))
}
}
/** This is the list of operations possible on the person business entity.
*
* #param ec
* #return
*/
def routes(implicit ec: ExecutionContext) = {
import Directives._
import Json4sSupport._
implicit val serialization = jackson.Serialization // or native.Serialization
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val formats = DefaultFormats + new EnumNameSerializer(_ProfessionClaType) + new EnumNameSerializer(_LinkRelType)
logRequestResult("PersonMicroservice") {
pathPrefix("person") {
(get & path(Segment)) { personId =>
implicit val formats = DefaultFormats + new EnumNameSerializer(_ProfessionClaType) + new EnumNameSerializer(_LinkRelType)
complete {
fetchPerson(personId).map[ToResponseMarshallable] {
case Right(personFormat) => personFormat
case Left(errorMessage) => BadRequest -> errorMessage
}
}
}~
post { entity(as[Person]) { entity =>
implicit val formats = DefaultFormats + new EnumNameSerializer(_ProfessionClaType) + new EnumNameSerializer(_LinkRelType)
complete {
addPerson(entity).map[ToResponseMarshallable] {
case Right(personFormat) => personFormat
case Left(error) => BadRequest -> error
}
}
}
}
}
}
}
}
/** Microservice for "Person" business entity. This microservice shall handle the basic CRUD related operations
* of Person business entity.
*/
object PersonMicroService extends App with Service {
override implicit val system = ActorSystem()
override implicit val executor = system.dispatcher
override implicit val materializer = ActorMaterializer()
override val config = ConfigFactory.load()
override val logger = Logging(system, getClass)
Http().bindAndHandle(routes , config.getString("http.interface"), config.getInt("http.port"))
}
I also have a ScalaTest spec for unit testing the service, and I am also forced to define the formats in each of the test case. Not sure if am doing this right. Hence seeking expert advice.
Following is my test spec:
package in.niftyride.unit
import akka.event.{Logging, LoggingAdapter}
import akka.http.scaladsl.server.Directives
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s.{jackson, DefaultFormats}
import org.json4s.ext.EnumNameSerializer
import akka.http.scaladsl.model.ContentTypes._
import akka.http.scaladsl.model.StatusCodes._
import Json4sSupport._
class PersonEndpointSpec extends UnitServiceSpec{
override def testConfigSource = "akka.loglevel = WARNING"
override def config = testConfig
override lazy val client = TestDatabaseProvider.cluster.connect;
val logger: LoggingAdapter = Logging(system, this.getClass)
System.setProperty(DatabaseUtils.SERVER_HASH_TEXT, DatabaseUtils.RANDOM_HASH)
"Service" should "respond to single id query" in {
implicit val serialization = jackson.Serialization // or native.Serialization
implicit val formats = DefaultFormats + new EnumNameSerializer(_ProfessionClaType) + new EnumNameSerializer(_LinkRelType)
Get(s"/person/${PersonTestData.personId1}") ~> routes ~> check {
status shouldBe OK
contentType shouldBe `application/json`
responseAs[Person] shouldBe PersonTestData.minData1
}
}
it should "be possible to create a person with valid data through POST" in {
implicit val serialization = jackson.Serialization // or native.Serialization
implicit val formats = DefaultFormats + new EnumNameSerializer(_ProfessionClaType) + new EnumNameSerializer(_LinkRelType)
Post(s"/person", PersonTestData.minDataEmptyPersonId1) ~> routes ~> check {
status shouldBe OK
contentType shouldBe `application/json`
responseAs[Person] shouldBe PersonTestData.minData1
}
}
}