1 package gr.grnet.aquarium.rest.actor
4 * Copyright 2011 GRNET S.A. All rights reserved.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
10 * 1. Redistributions of source code must retain the above
11 * copyright notice, this list of conditions and the following
14 * 2. Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials
17 * provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
20 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
32 * The views and conclusions contained in the software and
33 * documentation are those of the authors and should not be
34 * interpreted as representing official policies, either expressed
35 * or implied, of GRNET S.A.
38 import cc.spray.can.HttpMethods.{GET, POST}
40 import gr.grnet.aquarium.util.Loggable
41 import net.liftweb.json.JsonAST.JValue
42 import net.liftweb.json.{JsonAST, Printer}
43 import gr.grnet.aquarium.MasterConf
44 import akka.actor.{ActorRef, Actor}
45 import gr.grnet.aquarium.processor.actor.{RESTResponse, RESTRequest}
46 import gr.grnet.aquarium.actor.{RESTRole, AquariumActor, DispatcherRole}
49 * Spray-based REST service. This is the outer-world's interface to Aquarium functionality.
51 * @author Christos KK Loverdos <loverdos@gmail.com>.
53 class RESTActor(_id: String) extends AquariumActor with Loggable {
54 def this() = this("spray-root-service")
58 private def jsonResponse200(body: JValue, pretty: Boolean = false): HttpResponse = {
59 val stringBody = Printer.pretty(JsonAST.render(body))
60 stringResponse200(stringBody, "application/json")
63 private def stringResponse(status: Int, stringBody: String, contentType: String = "application/json"): HttpResponse = {
66 HttpHeader("Content-type", "%s;charset=utf-8".format(contentType)) :: Nil,
67 stringBody.getBytes("UTF-8")
71 private def stringResponse200(stringBody: String, contentType: String = "application/json"): HttpResponse = {
72 stringResponse(200, stringBody, contentType)
75 protected def receive = {
76 case RequestContext(HttpRequest(GET, "/ping", _, _, _), _, responder) ⇒
77 responder.complete(stringResponse200("{pong: %s}".format(System.currentTimeMillis())))
79 case RequestContext(HttpRequest(GET, "/stats", _, _, _), _, responder) ⇒ {
80 (serverActor ? GetStats).mapTo[Stats].onComplete {
82 future.value.get match {
83 case Right(stats) => responder.complete {
85 "Uptime : " + (stats.uptime / 1000.0) + " sec\n" +
86 "Requests dispatched : " + stats.requestsDispatched + '\n' +
87 "Requests timed out : " + stats.requestsTimedOut + '\n' +
88 "Requests open : " + stats.requestsOpen + '\n' +
89 "Open connections : " + stats.connectionsOpen + '\n'
92 case Left(ex) => responder.complete(stringResponse(500, "Couldn't get server stats due to " + ex, "text/plain"))
97 case RequestContext(HttpRequest(post@POST, "/events", headers, body, protocol), _, responder) ⇒
99 val masterConf = MasterConf.MasterConf
100 val actorProvider = masterConf.actorProvider
101 val dispatcher = actorProvider.actorForRole(DispatcherRole)
102 val headersMap = headers map { h => (h.name, h.value) } toMap
103 val futureResponse = dispatcher ask RESTRequest("POST", "/events", headersMap, body)
105 futureResponse onComplete { fr ⇒
108 // TODO: Will this ever happen??
109 case Some(Left(throwable)) ⇒
110 // TODO: Log something here and give back some more detailed info
111 responder.complete(stringResponse(500, "Internal Server Error", "text/plain"))
112 case Some(Right(actualResponse)) ⇒
113 actualResponse match {
114 case RESTResponse(status, headers, body) ⇒
118 headers map { case (k, v) => HttpHeader(k, v)} toList,
122 case unknownResponse ⇒
123 // TODO: Log something here and give back some more detailed info
124 responder.complete(stringResponse(500, "Internal Server Error", "text/plain"))
129 case RequestContext(HttpRequest(_, _, _, _, _), _, responder) ⇒
130 responder.complete(stringResponse(404, "Unknown resource!", "text/plain"))
132 case Timeout(method, uri, _, _, _, complete) ⇒ complete {
133 HttpResponse(status = 500).withBody("The " + method + " request to '" + uri + "' has timed out...")
137 ////////////// helpers //////////////
139 val defaultHeaders = List(HttpHeader("Content-Type", "text/plain"))
141 lazy val serverActor = Actor.registry.actorsFor("spray-can-server").head