Browse files

Initial commit of sample

  • Loading branch information...
1 parent 67f32e8 commit c919383cf2a86f6048c5c6398632d49bc5388442 Jennifer Hickey committed May 8, 2012
View
2 .gitignore
@@ -0,0 +1,2 @@
+dist
+target
View
14 README.md
@@ -1,2 +1,14 @@
spray-can-server
-================
+================
+The spray-can simple-http-server example from [Spray](http://github.com/spray/spray). Spray is a suite of scala libraries for building and consuming RESTful web services on top of Akka. This example is deployed to Cloud Foundry as a standalone application.
+
+### Deploying to Cloud Foundry
+
+To deploy the application to Cloud Foundry, simply build the dist and push it to Cloud Foundry using the provided manifest.yml file. You may need to modify the manifest to use a unique URL.
+
+```bash
+sbt clean compile package-dist
+vmc push
+Would you like to deploy from the current directory? [Yn]:
+Pushing application 'simple-http-server'...
+```
View
25 build.sbt
@@ -0,0 +1,25 @@
+import com.twitter.sbt._
+
+seq(StandardProject.newSettings: _*)
+
+organization := "cc.spray"
+
+name := "simple-http-server"
+
+packageDistZipName := "simple-http-server.zip"
+
+version := "0.1.0-SNAPSHOT"
+
+scalaVersion := "2.9.1"
+
+resolvers ++= Seq(
+ "Typesafe repo" at "http://repo.typesafe.com/typesafe/releases/",
+ "spray repo" at "http://repo.spray.cc/"
+)
+
+
+libraryDependencies ++= Seq(
+ "cc.spray" % "spray-server" % "1.0-M1",
+ "cc.spray" % "spray-can" % "1.0-M1",
+ "com.typesafe.akka" % "akka-actor" % "2.0"
+)
View
15 manifest.yml
@@ -0,0 +1,15 @@
+---
+applications:
+ dist/simple-http-server/simple-http-server.zip:
+ name: simple-http-server
+ framework:
+ name: standalone
+ info:
+ mem: 64M
+ description: Standalone Application
+ exec:
+ runtime: java
+ command: java $JAVA_OPTS -jar simple-http-server_2.9.1-0.1.0-SNAPSHOT.jar
+ url: simple-http-server.${target-base}
+ mem: 512M
+ instances: 1
View
1 project/build.properties
@@ -0,0 +1 @@
+sbt.version=0.11.2
View
3 project/plugins.sbt
@@ -0,0 +1,3 @@
+addSbtPlugin("com.twitter" %% "sbt-package-dist" % "1.0.0")
+
+resolvers += "twitter-repo" at "http://maven.twttr.com"
View
15 src/main/resources/application.conf
@@ -0,0 +1,15 @@
+akka {
+ loglevel = INFO
+ #event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
+}
+
+spray {
+ # check the reference.conf in the spray-can project main/resources for all defined settings
+ can.server {
+ idle-timeout = 100 s
+ request-timeout = 100 s
+ }
+
+ # check the reference.conf in the spray-io project main/resources for all defined settings
+ io.confirm-sends = off # for maximum performance
+}
View
26 src/main/resources/logback.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+ <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+ <target>System.out</target>
+ <encoder>
+ <pattern>%date{MM/dd HH:mm:ss.SSS} %-5level[%.15thread] %logger{1} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <!-- <appender name="FILE" class="ch.qos.logback.core.FileAppender">
+ <file>akka.log</file>
+ <append>false</append>
+ <encoder>
+ <pattern>%date{MM/dd HH:mm:ss} %-5level[%thread] %logger{1} - %msg%n</pattern>
+ </encoder>
+ </appender> -->
+
+ <logger name="akka" level="INFO" />
+ <logger name="cc.spray.io.IoWorker" level="INFO" />
+
+ <root level="INFO">
+ <appender-ref ref="CONSOLE"/>
+ </root>
+
+</configuration>
View
52 src/main/scala/cc/spray/can/example/Main.scala
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011 Mathias Doenitz
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cc.spray.can.example
+
+import cc.spray.io.pipelines.MessageHandlerDispatch
+import cc.spray.io.IoWorker
+import cc.spray.can.server.HttpServer
+import akka.actor._
+
+object Main extends App {
+ // we need an ActorSystem to host our application in
+ val system = ActorSystem("SimpleHttpServer")
+
+ // the handler actor replies to incoming HttpRequests
+ val handler = system.actorOf(Props[TestService])
+
+ // every spray-can HttpServer (and HttpClient) needs an IoWorker for low-level network IO
+ // (but several servers and/or clients can share one)
+ val ioWorker = new IoWorker(system).start()
+
+ // create and start the spray-can HttpServer, telling it that we want requests to be
+ // handled by our singleton handler
+ val server = system.actorOf(
+ props = Props(new HttpServer(ioWorker, MessageHandlerDispatch.SingletonHandler(handler))),
+ name = "http-server"
+ )
+
+ // a running HttpServer can be bound, unbound and rebound
+ // initially to need to tell it where to bind to
+ println("jen" + System.getenv("VCAP_APP_HOST"))
+ server ! HttpServer.Bind(Option(System.getenv("VCAP_APP_HOST")).getOrElse("localhost"), Option(System.getenv("VCAP_APP_PORT")).getOrElse("8080").toInt)
+
+ // finally we drop the main thread but hook the shutdown of
+ // our IoWorker into the shutdown of the applications ActorSystem
+ system.registerOnTermination {
+ ioWorker.stop()
+ }
+}
View
153 src/main/scala/cc/spray/can/example/TestService.scala
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2011 Mathias Doenitz
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cc.spray.can.example
+
+import cc.spray.can.model._
+import cc.spray.can.server.HttpServer
+import cc.spray.util.DateTime
+import cc.spray.io.ConnectionClosedReason
+import akka.util.duration._
+import akka.actor._
+import akka.pattern.ask
+
+class TestService extends Actor with ActorLogging {
+ import HttpMethods._
+
+ protected def receive = {
+
+ case HttpRequest(GET, "/", _, _, _) =>
+ sender ! index
+
+ case HttpRequest(GET, "/ping", _, _, _) =>
+ sender ! response("PONG!")
+
+ case HttpRequest(GET, "/stream", _, _, _) =>
+ val peer = sender // since the Props creator is executed asyncly we need to save the sender ref
+ context.actorOf(Props(new Streamer(peer, 100)))
+
+ case HttpRequest(GET, "/stats", _, _, _) =>
+ val client = sender
+ context.actorFor("../http-server").ask(HttpServer.GetStats)(1.second).onSuccess {
+ case x: HttpServer.Stats => client ! statsPresentation(x)
+ }
+
+ case HttpRequest(GET, "/crash", _, _, _) =>
+ sender ! response("About to throw an exception in the request handling actor, " +
+ "which will trigger an actor restart as defined by the default supervisor strategy")
+ throw new RuntimeException("BOOM!")
+
+ case HttpRequest(GET, "/timeout", _, _, _) =>
+ log.info("Dropping request, triggering a timeout")
+
+ case HttpRequest(GET, "/timeout/timeout", _, _, _) =>
+ log.info("Dropping request, triggering a timeout")
+
+ case HttpRequest(GET, "/stop", _, _, _) =>
+ sender ! response("Shutting down in 1 second ...")
+ context.system.scheduler.scheduleOnce(1.second, new Runnable { def run() { context.system.shutdown() } })
+
+ case _: HttpRequest => sender ! response("Unknown resource!", 404)
+
+ case HttpServer.RequestTimeout(HttpRequest(_, "/timeout/timeout", _, _, _)) =>
+ log.info("Dropping RequestTimeout message")
+
+ case HttpServer.RequestTimeout(request) =>
+ sender ! HttpResponse(status = 500).withBody {
+ "The " + request.method + " request to '" + request.uri + "' has timed out..."
+ }
+
+ case x: HttpServer.Closed =>
+ context.children.foreach(_ ! CancelStream(sender, x.reason))
+
+ case _: HttpServer.SendCompleted =>
+ // ignore send confirmations (they might even have been turned off in the application.conf)
+ }
+
+ ////////////// helpers //////////////
+
+ val defaultHeaders = List(HttpHeader("Content-Type", "text/plain"))
+
+ def response(msg: String, status: Int = 200) =
+ HttpResponse(status, defaultHeaders, msg.getBytes("ISO-8859-1"))
+
+ lazy val index = HttpResponse(
+ headers = List(HttpHeader("Content-Type", "text/html")),
+ body =
+ <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="/stats">/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.getBytes("ISO-8859-1")
+ )
+
+ def statsPresentation(s: HttpServer.Stats) = HttpResponse(
+ headers = List(HttpHeader("Content-Type", "text/html")),
+ body =
+ <html>
+ <body>
+ <h1>HttpServer Stats</h1>
+ <table>
+ <tr><td>uptime:</td><td>{s.uptime.printHMS}</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.getBytes("ISO-8859-1")
+ )
+
+ case class CancelStream(peer: ActorRef, reason: ConnectionClosedReason)
+
+ class Streamer(peer: ActorRef, var count: Int) extends Actor with ActorLogging {
+ log.debug("Starting streaming response ...")
+ peer ! ChunkedResponseStart(HttpResponse(headers = defaultHeaders).withBody(" " * 2048))
+ val chunkGenerator = context.system.scheduler.schedule(100.millis, 100.millis, self, 'Tick)
+
+ protected def receive = {
+ case 'Tick if count > 0 =>
+ log.info("Sending response chunk ...")
+ peer ! MessageChunk(DateTime.now.toIsoDateTimeString + ", ")
+ count -= 1
+ case 'Tick =>
+ log.info("Finalizing response stream ...")
+ chunkGenerator.cancel()
+ peer ! MessageChunk("\nStopped...")
+ peer ! ChunkedMessageEnd()
+ context.stop(self)
+ case CancelStream(ref, reason) => if (ref == peer) {
+ log.info("Canceling response stream due to {} ...", reason)
+ chunkGenerator.cancel()
+ context.stop(self)
+ }
+ }
+ }
+}

0 comments on commit c919383

Please sign in to comment.