adapted EchoServer for netty. Upgraded to unflitered 0.6.1 webid
authorHenry Story <henry.story@bblfish.net>
Wed, 04 Apr 2012 18:23:20 +0200
branchwebid
changeset 185 b11844b0eea8
parent 184 213512a4f137
child 186 20b5f23f324f
adapted EchoServer for netty. Upgraded to unflitered 0.6.1
project/build.scala
src/main/scala/EchoPlan.scala
src/main/scala/ReadWriteWeb.scala
src/main/scala/ReadWriteWebMain.scala
src/main/scala/netty/ReadWriteWebNetty.scala
src/main/scala/plan.scala
--- a/project/build.scala	Tue Apr 03 21:48:11 2012 +0200
+++ b/project/build.scala	Wed Apr 04 18:23:20 2012 +0200
@@ -10,7 +10,7 @@
   val dispatch_version = "0.8.6"
   val dispatch_http = "net.databinder" %% "dispatch-http" % dispatch_version 
   val dispatch_nio = "net.databinder" %% "dispatch-nio" % dispatch_version 
-  val unfiltered_version = "0.5.3"
+  val unfiltered_version = "0.6.1"
   val unfiltered_filter = "net.databinder" %% "unfiltered-filter" % unfiltered_version 
   val unfiltered_jetty = "net.databinder" %% "unfiltered-jetty" % unfiltered_version 
   val unfiltered_netty = "net.databinder" %% "unfiltered-netty" % unfiltered_version 
--- a/src/main/scala/EchoPlan.scala	Tue Apr 03 21:48:11 2012 +0200
+++ b/src/main/scala/EchoPlan.scala	Wed Apr 04 18:23:20 2012 +0200
@@ -23,31 +23,59 @@
 
 package org.w3.readwriteweb
 
-import unfiltered.request.Path
-import unfiltered.response.{ResponseString, PlainTextContent, ContentType, Ok}
 import io.BufferedSource
+import unfiltered.Cycle
+import unfiltered.netty.ReceivedMessage
+import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
+import unfiltered.request.{HttpRequest, Path}
+import org.jboss.netty.handler.codec.http.HttpResponse
+import unfiltered.response.{Ok, PlainTextContent, ResponseString}
 
 
 /**
+ * Useful Echo Server, for debugging
+ *
  * @author hjs
  * @created: 19/10/2011
  */
+trait EchoPlan[Req,Res] {
+    import collection.JavaConversions._
 
-class EchoPlan {
-  import collection.JavaConversions._
+  /**
+   * unfiltered is missing a method to get the header names from the request, so this method is required
+   * @param req
+   * @return
+   */
+  def headers(req: HttpRequest[Req]): Iterator[String]
 
-  lazy val plan = unfiltered.filter.Planify({
+  def intent : Cycle.Intent[Req, Res] = {
     case req@Path(path) if path startsWith "/test/http/echo" => {
       Ok ~> PlainTextContent ~> {
-        val headers = req.underlying.getHeaderNames()
-        val result = for (name <- headers ;
-             val nameStr = name.asInstanceOf[String]
+        val header = for (headerName <- headers(req);
+                          headerValue <- req.headers(headerName)
         ) yield {
-          nameStr + ": " + req.underlying.getHeader(nameStr)+"\r\n"
+          headerName + ": " + headerValue +"\r\n"
         }
-        ResponseString(result.mkString+ "\r\n" + new BufferedSource(req.inputStream).mkString)
+        ResponseString(header.mkString+ "\r\n" + new BufferedSource(req.inputStream).mkString)
       }
     }
+  }
+}
 
-  })
-}
\ No newline at end of file
+/**
+ * this is a trait so that it can be mixed in with different threading policies
+ */
+trait NettyEchoPlan extends EchoPlan[ReceivedMessage,HttpResponse] {
+  import scala.collection.JavaConverters.collectionAsScalaIterableConverter
+
+  def headers(req: HttpRequest[ReceivedMessage]) = req.underlying.request.getHeaderNames.asScala.toIterator
+}
+
+
+object JettyEchoPlan extends EchoPlan[HttpServletRequest,HttpServletResponse] {
+  import scala.collection.JavaConverters.enumerationAsScalaIteratorConverter
+  import java.util.Enumeration
+  def headers(req: HttpRequest[HttpServletRequest]) = Option(req.underlying.getHeaderNames).
+    map(enum=> enumerationAsScalaIteratorConverter[String](enum.asInstanceOf[Enumeration[String]]).asScala).
+    get
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/ReadWriteWeb.scala	Wed Apr 04 18:23:20 2012 +0200
@@ -0,0 +1,174 @@
+package org.w3.readwriteweb
+
+import auth.{AuthZ, NullAuthZ}
+import org.w3.readwriteweb.util._
+
+import scala.io.Source
+import java.net.URL
+
+import org.slf4j.{Logger, LoggerFactory}
+
+import com.hp.hpl.jena.query.{Query, QueryExecution, QueryExecutionFactory}
+import com.hp.hpl.jena.update.UpdateAction
+import Query.{QueryTypeSelect => SELECT,
+              QueryTypeAsk => ASK,
+              QueryTypeConstruct => CONSTRUCT,
+              QueryTypeDescribe => DESCRIBE}
+
+import scalaz.{Resource => _}
+import unfiltered.request._
+import unfiltered.Cycle
+import unfiltered.response._
+
+import com.hp.hpl.jena.rdf.model.Model
+
+//object ReadWriteWeb {
+//
+//  val defaultHandler: PartialFunction[Throwable, HttpResponse[_]] = {
+//    case t => InternalServerError ~> ResponseString(t.getStackTraceString)
+//  }
+//
+//}
+
+/**
+ * The ReadWriteWeb intent.
+ * It is independent of jetty or netty
+ */
+trait ReadWriteWeb[Req, Res] {
+  val rm: ResourceManager
+  implicit def manif: Manifest[Req]
+  implicit val authz: AuthZ[Req, Res] = new NullAuthZ[Req, Res]
+  // a few type short cuts to make it easier to reason with the code here
+  // one may want to generalize this code so that it does not depend so strongly on servlets.
+//  type Request = HttpRequest[Req]
+//  type Response = ResponseFunction[Res]
+
+  val logger: Logger = LoggerFactory.getLogger(this.getClass)
+
+  /**
+   * The partial function that if triggered sends to the readwrite web code.
+   * It wraps the ReadWriteWeb function with the AuthZ passed in the argument
+   * ( Note that we don't want to protect this intent, since that would be to apply the security to all other applications,
+   * many of which may want different authorization implementations )
+   */
+  def intent : Cycle.Intent[Req, Res] = {
+      case req @ Path(path) if path startsWith rm.basePath => authz.protect(rwwIntent)(manif)(req)
+  }
+
+  /**
+   * The core ReadWrite web function
+   * ( This is not a partial function and so is not a Plan.Intent )
+   *
+   *  This code makes use of ScalaZ Validation. For example of how to use it, see
+   *  http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html
+   *  
+   *  the Resource abstraction returns Validation[Throwable, ?something]
+   *  we use the for monadic constructs (although it's *not* a monad).
+   *  Everything construct are mapped to Validation[ResponseFunction, ResponseFuntion],
+   *  the left value always denoting the failure. Hence, the rest of the for-construct
+   *  is not evaluated, but let the reader of the code understand clearly what's happening.
+   *  
+   *  This mapping is made possible with the failMap method. I couldn't find an equivalent
+   *  in the ScalaZ API so I made my own through an implicit.
+   *  
+   *  At last, Validation[ResponseFunction, ResponseFuntion] is exposed as a ResponseFunction
+   *  through another implicit conversion. It saves us the call to the Validation.fold() method
+   */
+  def rwwIntent  =  (req: HttpRequest[Req]) => {
+
+          val Authoritative(uri: URL, representation: Representation) = req
+          val r: Resource = rm.resource(uri)
+          val res: ResponseFunction[Res] = req match {
+            case GET(_) if representation == HTMLRepr => {
+              val source = Source.fromFile("src/main/resources/skin.html")("UTF-8")
+              val body = source.getLines.mkString("\n")
+              Ok ~> ViaSPARQL ~> ContentType("text/html") ~> ResponseString(body)
+            }
+            case GET(_) | HEAD(_) =>
+              for {
+                model <- r.get() failMap { x => NotFound }
+                lang = representation match {
+                  case RDFRepr(l) => l
+                  case _ => Lang.default
+                }
+              } yield {
+                val res = req match {
+                  case GET(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
+                  case HEAD(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType)
+                }
+                res ~> ContentLocation( uri.toString ) // without this netty (perhaps jetty too?) sends very weird headers, breaking tests
+              }
+            case PUT(_) & RequestLang(lang) if representation == DirectoryRepr => {
+              for {
+                bodyModel <- modelFromInputStream(Body.stream(req), uri, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
+                _ <- r.createDirectory(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
+              } yield Created
+            }
+            case PUT(_) & RequestLang(lang) =>
+              for {
+                bodyModel <- modelFromInputStream(Body.stream(req), uri, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
+                _ <- r.save(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
+              } yield Created
+            case PUT(_) =>
+              BadRequest ~> ResponseString("Content-Type MUST be one of: " + Lang.supportedAsString)
+            case POST(_) & RequestContentType(ct) if Post.supportContentTypes contains ct => {
+              Post.parse(Body.stream(req), uri, ct) match {
+                case PostUnknown => {
+                  logger.info("Couldn't parse the request")
+                  BadRequest ~> ResponseString("You MUST provide valid content for given Content-Type: " + ct)
+                }
+                case PostUpdate(update) => {
+                  logger.info("SPARQL UPDATE:\n" + update.toString())
+                  for {
+                    model <- r.get() failMap { t => NotFound }
+                    // TODO: we should handle an error here
+                    _ = UpdateAction.execute(update, model)
+                    _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
+                  } yield Ok
+                }
+                case PostRDF(diffModel) => {
+                  logger.info("RDF content:\n" + diffModel.toString())
+                  for {
+                    model <- r.get() failMap { t => NotFound }
+                    // TODO: we should handle an error here
+                    _ = model.add(diffModel)
+                    _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
+                  } yield Ok
+                }
+                case PostQuery(query) => {
+                  logger.info("SPARQL Query:\n" + query.toString())
+                  lazy val lang = RequestLang(req) getOrElse Lang.default
+                  for {
+                    model <- r.get() failMap { t => NotFound }
+                  } yield {
+                    val qe: QueryExecution = QueryExecutionFactory.create(query, model)
+                    query.getQueryType match {
+                      case SELECT =>
+                        Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execSelect())
+                      case ASK =>
+                        Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execAsk())
+                      case CONSTRUCT => {
+                        val result: Model = qe.execConstruct()
+                        Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
+                      }
+                      case DESCRIBE => {
+                        val result: Model = qe.execDescribe()
+                        Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
+                      }
+                    }
+                  }
+                }
+              }
+            }
+            case POST(_) =>
+              BadRequest ~> ResponseString("Content-Type MUST be one of: " + Post.supportedAsString)
+            case _ => MethodNotAllowed ~> Allow("GET", "PUT", "POST")
+          }
+          res
+        }
+      
+    
+    
+  
+
+}
--- a/src/main/scala/ReadWriteWebMain.scala	Tue Apr 03 21:48:11 2012 +0200
+++ b/src/main/scala/ReadWriteWebMain.scala	Wed Apr 04 18:23:20 2012 +0200
@@ -125,9 +125,10 @@
     service.filter(new FilterLogger(logger)).
       context("/public"){ ctx:ContextBuilder =>
         ctx.resources(ClasspathUtils.fromClasspath("public/").toURI.toURL)
-    }.filter(Planify(rww.intent)).
+    }.filter(Planify(JettyEchoPlan.intent)).
+      filter(Planify(rww.intent)).
       filter(Planify(x509v.intent)).
-      filter(new EchoPlan().plan).run()
+      run()
     
   }
 
--- a/src/main/scala/netty/ReadWriteWebNetty.scala	Tue Apr 03 21:48:11 2012 +0200
+++ b/src/main/scala/netty/ReadWriteWebNetty.scala	Wed Apr 04 18:23:20 2012 +0200
@@ -66,6 +66,8 @@
           override val authz = new RDFAuthZ[ReceivedMessage,HttpResponse](filesystem)
      }
 
+     val echo =  new cycle.Plan  with cycle.ThreadPool with ServerErrorResponse with NettyEchoPlan
+
      //this is incomplete: we should be able to start both ports.... not sure how to do this yet.
      val service = httpsPort.value match {
        case Some(port) => new KeyAuth_Https(port)
@@ -75,6 +77,7 @@
 
      // configures and launches a Netty server
      service.plan(publicStatic).
+       plan( echo ).
        plan( x509v ).
        plan( webidp ).
        plan( rww ).run()
--- a/src/main/scala/plan.scala	Tue Apr 03 21:48:11 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,174 +0,0 @@
-package org.w3.readwriteweb
-
-import auth.{AuthZ, NullAuthZ}
-import org.w3.readwriteweb.util._
-
-import scala.io.Source
-import java.net.URL
-
-import org.slf4j.{Logger, LoggerFactory}
-
-import com.hp.hpl.jena.query.{Query, QueryExecution, QueryExecutionFactory}
-import com.hp.hpl.jena.update.UpdateAction
-import Query.{QueryTypeSelect => SELECT,
-              QueryTypeAsk => ASK,
-              QueryTypeConstruct => CONSTRUCT,
-              QueryTypeDescribe => DESCRIBE}
-
-import scalaz.{Resource => _}
-import unfiltered.request._
-import unfiltered.Cycle
-import unfiltered.response._
-
-import com.hp.hpl.jena.rdf.model.Model
-
-//object ReadWriteWeb {
-//
-//  val defaultHandler: PartialFunction[Throwable, HttpResponse[_]] = {
-//    case t => InternalServerError ~> ResponseString(t.getStackTraceString)
-//  }
-//
-//}
-
-/**
- * The ReadWriteWeb intent.
- * It is independent of jetty or netty
- */
-trait ReadWriteWeb[Req, Res] {
-  val rm: ResourceManager
-  implicit def manif: Manifest[Req]
-  implicit val authz: AuthZ[Req, Res] = new NullAuthZ[Req, Res]
-  // a few type short cuts to make it easier to reason with the code here
-  // one may want to generalize this code so that it does not depend so strongly on servlets.
-//  type Request = HttpRequest[Req]
-//  type Response = ResponseFunction[Res]
-
-  val logger: Logger = LoggerFactory.getLogger(this.getClass)
-
-  /**
-   * The partial function that if triggered sends to the readwrite web code.
-   * It wraps the ReadWriteWeb function with the AuthZ passed in the argument
-   * ( Note that we don't want to protect this intent, since that would be to apply the security to all other applications,
-   * many of which may want different authorization implementations )
-   */
-  def intent : Cycle.Intent[Req, Res] = {
-      case req @ Path(path) if path startsWith rm.basePath => authz.protect(rwwIntent)(manif)(req)
-  }
-
-  /**
-   * The core ReadWrite web function
-   * ( This is not a partial function and so is not a Plan.Intent )
-   *
-   *  This code makes use of ScalaZ Validation. For example of how to use it, see
-   *  http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html
-   *  
-   *  the Resource abstraction returns Validation[Throwable, ?something]
-   *  we use the for monadic constructs (although it's *not* a monad).
-   *  Everything construct are mapped to Validation[ResponseFunction, ResponseFuntion],
-   *  the left value always denoting the failure. Hence, the rest of the for-construct
-   *  is not evaluated, but let the reader of the code understand clearly what's happening.
-   *  
-   *  This mapping is made possible with the failMap method. I couldn't find an equivalent
-   *  in the ScalaZ API so I made my own through an implicit.
-   *  
-   *  At last, Validation[ResponseFunction, ResponseFuntion] is exposed as a ResponseFunction
-   *  through another implicit conversion. It saves us the call to the Validation.fold() method
-   */
-  def rwwIntent  =  (req: HttpRequest[Req]) => {
-
-          val Authoritative(uri: URL, representation: Representation) = req
-          val r: Resource = rm.resource(uri)
-          val res: ResponseFunction[Res] = req match {
-            case GET(_) if representation == HTMLRepr => {
-              val source = Source.fromFile("src/main/resources/skin.html")("UTF-8")
-              val body = source.getLines.mkString("\n")
-              Ok ~> ViaSPARQL ~> ContentType("text/html") ~> ResponseString(body)
-            }
-            case GET(_) | HEAD(_) =>
-              for {
-                model <- r.get() failMap { x => NotFound }
-                lang = representation match {
-                  case RDFRepr(l) => l
-                  case _ => Lang.default
-                }
-              } yield {
-                val res = req match {
-                  case GET(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
-                  case HEAD(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType)
-                }
-                res ~> ContentLocation( uri.toString ) // without this netty (perhaps jetty too?) sends very weird headers, breaking tests
-              }
-            case PUT(_) & RequestLang(lang) if representation == DirectoryRepr => {
-              for {
-                bodyModel <- modelFromInputStream(Body.stream(req), uri, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
-                _ <- r.createDirectory(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
-              } yield Created
-            }
-            case PUT(_) & RequestLang(lang) =>
-              for {
-                bodyModel <- modelFromInputStream(Body.stream(req), uri, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
-                _ <- r.save(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
-              } yield Created
-            case PUT(_) =>
-              BadRequest ~> ResponseString("Content-Type MUST be one of: " + Lang.supportedAsString)
-            case POST(_) & RequestContentType(ct) if Post.supportContentTypes contains ct => {
-              Post.parse(Body.stream(req), uri, ct) match {
-                case PostUnknown => {
-                  logger.info("Couldn't parse the request")
-                  BadRequest ~> ResponseString("You MUST provide valid content for given Content-Type: " + ct)
-                }
-                case PostUpdate(update) => {
-                  logger.info("SPARQL UPDATE:\n" + update.toString())
-                  for {
-                    model <- r.get() failMap { t => NotFound }
-                    // TODO: we should handle an error here
-                    _ = UpdateAction.execute(update, model)
-                    _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
-                  } yield Ok
-                }
-                case PostRDF(diffModel) => {
-                  logger.info("RDF content:\n" + diffModel.toString())
-                  for {
-                    model <- r.get() failMap { t => NotFound }
-                    // TODO: we should handle an error here
-                    _ = model.add(diffModel)
-                    _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
-                  } yield Ok
-                }
-                case PostQuery(query) => {
-                  logger.info("SPARQL Query:\n" + query.toString())
-                  lazy val lang = RequestLang(req) getOrElse Lang.default
-                  for {
-                    model <- r.get() failMap { t => NotFound }
-                  } yield {
-                    val qe: QueryExecution = QueryExecutionFactory.create(query, model)
-                    query.getQueryType match {
-                      case SELECT =>
-                        Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execSelect())
-                      case ASK =>
-                        Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execAsk())
-                      case CONSTRUCT => {
-                        val result: Model = qe.execConstruct()
-                        Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
-                      }
-                      case DESCRIBE => {
-                        val result: Model = qe.execDescribe()
-                        Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
-                      }
-                    }
-                  }
-                }
-              }
-            }
-            case POST(_) =>
-              BadRequest ~> ResponseString("Content-Type MUST be one of: " + Post.supportedAsString)
-            case _ => MethodNotAllowed ~> Allow("GET", "PUT", "POST")
-          }
-          res
-        }
-      
-    
-    
-  
-
-}