Spray(2)spray-can
穆德海
2023-12-01
Spray(2)spray-can
Prepare the Sample codes
>git clone https://github.com/spray/spray.git
>cd spray
And we can run both the 2 examples like this:
>sbt "project simple-http-client" run
>sbt "project simple-http-server" run
Everything runs fine, I will import the project into eclipse
>sbt update
>sbt eclipse
And I import these projects to my eclipse
simple-http-server
spray-can
spray-http
spray-io
spray-util
Go through the Online Documents to understand spray-can
Dependencies
spray-can depends on spray-io, spray-http, spray-util, akka-actor.
Create a learning project under easy sub system
>mkdir easyspray
>cd easyspray
>vi build.sbt
name := "easyspray"
organization := "com.sillycat"
version := "1.0"
scalaVersion := "2.10.0-RC1"
scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8")
>sbt update
>sbt eclipse
Import my sample project into eclipse.
Installation
I already have a sbt project named easyspray, and I will install spray-can in that project.
make the resolvers in sbt as follow:
resolvers ++= Seq(
"sonatype releases" at "https://oss.sonatype.org/content/repositories/releases/",
"sonatype snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/",
"typesafe repo" at "http://repo.typesafe.com/typesafe/releases/",
"spray repo" at "http://repo.spray.io/"
)
Add the sbt maven dependencies like this>
libraryDependencies ++= Seq(
"io.spray" % "spray-io" % "1.1-M4",
"io.spray" % "spray-http" % "1.1-M4",
"io.spray" % "spray-can" % "1.1-M4",
"io.spray" % "spray-util" % "1.1-M4",
"com.typesafe.akka" %% "akka-actor" % "2.1.0-RC1" cross CrossVersion.full,
"com.typesafe" % "config" % "1.0.0"
)
The most important configuration file is
reference.conf configure the server side configuration and database connection configuration.
HttpServer
Connection management
Message parsing and header separation
Timeout management
Response ordering
Basic Architecture
The spray-can HttpServer is implemented as an Akka actor, which talks to an underlying IOBridge and spawns new child actors for every new connection.
Just receive the request, and design in which case it will go, sender it to the handler when response the html.
def receive = {
case HttpRequest(GET, "/", _, _, _) =>
sender ! index
}
lazy val index = HttpResponse(
entity = HttpBody(`text/html`,
<html>
<body>
<h1>Say hello to <i>spray-can</i>!</h1>
<p>Defined resources:</p>
<ul>
<li><a href="/ping">/ping</a></li>
<li><a href="/stream">/stream</a></li>
<li><a href="/server-stats">/server-stats</a></li>
<li><a href="/io-stats">/io-stats</a></li>
<li><a href="/crash">/crash</a></li>
<li><a href="/timeout">/timeout</a></li>
<li><a href="/timeout/timeout">/timeout/timeout</a></li>
<li><a href="/stop">/stop</a></li>
</ul>
</body>
</html>.toString
)
)
Starting and Stopping
The HttpServer is a regular actor, it starts like this
// the handler actor replies to incoming HttpRequests
val handler = system.actorOf(Props[DemoService])
// create a new HttpServer using our handler and tell it where to bind to
newHttpServer(handler) ! Bind(interface = "localhost", port = 8080)
It stops like this:
context.system.scheduler.scheduleOnce(1 second span, new Runnable { def run() { context.system.shutdown() } })
I think probably this command statement will shutdown the actor context.system.shutdown(), other codes are just try to schedule that.
Message Protocol
A running HttpServer actor understands the following command messages.
Bind
Start listening for incoming connections on a particular port.
Unbind
Revert a previous Bind.
GetStats
ClearStats
…snip…
SprayCanHttpServerApp trait
I found that I am using the latest sample codes, so I need to change my version from 1.1-M4 to 1.1-M7. So the latest version make SprayCanHttpServerApp trait work.
package com.sillycat.easyspray.external
import spray.can.server.SprayCanHttpServerApp
import akka.actor.Props
object Server extends App with SprayCanHttpServerApp {
val handler = system.actorOf(Props[EasyRouter])
newHttpServer(handler) ! Bind(interface = "localhost", port = 8080)
}
package com.sillycat.easyspray.external
import akka.actor.Actor
import spray.http.HttpMethods
import spray.http.HttpRequest
import spray.http.HttpResponse
import spray.util.SprayActorLogging
import spray.http.HttpBody
import spray.http.MediaTypes._
class EasyRouter extends Actor with SprayActorLogging {
def receive = {
case HttpRequest(HttpMethods.GET, "/", _, _, _) =>
sender ! index
}
lazy val index = HttpResponse(
entity = HttpBody(`text/html`,
<html]]>
<body]]>
<h1]]>Welcome Page</h1]]>
</body]]>
</html]]>.toString))
}
HttpClient
…snip…
HttpDialog
…snip…
Examples
Example for stop the server and get status from the server:
package com.sillycat.easyspray.external
import scala.concurrent.duration._
import akka.pattern.ask
import akka.util.Timeout
import akka.actor._
import spray.io.{IOBridge, IOExtension}
import spray.can.server.HttpServer
import spray.util._
import spray.http._
import HttpMethods._
import MediaTypes._
class EasyRouter extends Actor with SprayActorLogging {
implicit val timeout: Timeout = Duration(1, "sec")
def receive = {
case HttpRequest(HttpMethods.GET, "/", _, _, _) =>
sender ! index
case HttpRequest(HttpMethods.GET, "/stop", _, _, _) =>
sender ! HttpResponse(entity = "Shutting down in 1 second ...")
context.system.scheduler.scheduleOnce(1 second span, new Runnable { def run() { context.system.shutdown() } })
case HttpRequest(GET, "/server-stats", _, _, _) =>
val client = sender
(context.actorFor("../http-server") ? HttpServer.GetStats).onSuccess {
case x: HttpServer.Stats => client ! statsPresentation(x)
}
case HttpRequest(GET, "/io-stats", _, _, _) =>
val client = sender
(IOExtension(context.system).ioBridge() ? IOBridge.GetStats).onSuccess {
case IOBridge.StatsMap(map) => client ! statsPresentation(map)
}
}
lazy val index = HttpResponse(
entity = HttpBody(`text/html`,
<html>
<body>
<h1>Welcome Page</h1>
<ul>
<li><a href="/stop">Stop The Server</a></li>
<li><a href="/server-stats">Server Status</a></li>
<li><a href="/io-stats">IO Status</a></li>
</ul>
</body>
</html>.toString))
def statsPresentation(s: HttpServer.Stats) = HttpResponse(
entity = HttpBody(`text/html`,
<html>
<body>
<h1>HttpServer Stats</h1>
<table>
<tr><td>uptime:</td><td]]>{s.uptime.formatHMS}</td></tr>
<tr><td>totalRequests:</td><td>{s.totalRequests}</td></tr>
<tr><td>openRequests:</td><td>{s.openRequests}</td></tr>
<tr><td>maxOpenRequests:</td><td>{s.maxOpenRequests}</td></tr>
<tr><td>totalConnections:</td><td>{s.totalConnections}</td></tr>
<tr><td>openConnections:</td><td>{s.openConnections}</td></tr>
<tr><td>maxOpenConnections:</td><td>{s.maxOpenConnections}</td></tr>
<tr><td>requestTimeouts:</td><td>{s.requestTimeouts}</td></tr>
<tr><td>idleTimeouts:</td><td>{s.idleTimeouts}</td></tr>
</table>
</body>
</html>.toString
)
)
def statsPresentation(map: Map[ActorRef, IOBridge.Stats]) = HttpResponse(
entity = HttpBody(`text/html`,
<html>
<body]]>
<h1]]>IOBridge Stats</h1]]>
<table]]>
{
def extractData(t: (ActorRef, IOBridge.Stats)) = t._1.path.elements.last :: t._2.productIterator.toList
val data = map.toSeq.map(extractData).sortBy(_.head.toString).transpose
valheaders = Seq("IOBridge", "uptime", "bytesRead", "bytesWritten", "connectionsOpened", "commandsExecuted")
headers.zip(data).map { case (header, items) =>
<tr><td]]>{header}:</td]]>{items.map(x => <td]]>{x}</td]]>)}</tr]]>
}
}
</table]]>
</body]]>
</html]]>.toString
)
)
}
References:
http://spray.io/documentation/spray-routing/
http://spray.io/documentation/spray-can/#spray-can
http://sillycat.iteye.com/blog/1744082
http://sillycat.iteye.com/blog/1750374
http://www.scala-sbt.org/release/docs/Getting-Started/index.html
http://spray.io/project-info/maven-repository/