ReadWriteWeb now also runs on netty. Old tests work.
--- a/project/build.scala Sat Oct 22 20:36:17 2011 +0200
+++ b/project/build.scala Sun Oct 23 16:45:49 2011 +0200
@@ -9,6 +9,7 @@
val unfiltered_version = "0.5.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
// val unfiltered_spec = "net.databinder" %% "unfiltered-spec" % "0.4.1" % "test"
val ivyUnfilteredSpec =
<dependencies>
@@ -82,6 +83,7 @@
libraryDependencies += dispatch_http,
libraryDependencies += unfiltered_filter,
libraryDependencies += unfiltered_jetty,
+ libraryDependencies += unfiltered_netty,
// libraryDependencies += slf4jSimple,
libraryDependencies += jena,
libraryDependencies += arq,
--- a/src/main/scala/Authoritative.scala Sat Oct 22 20:36:17 2011 +0200
+++ b/src/main/scala/Authoritative.scala Sun Oct 23 16:45:49 2011 +0200
@@ -24,14 +24,26 @@
package org.w3.readwriteweb
import unfiltered.request._
-import java.net.URL
+import java.security.cert.Certificate
+import javax.servlet.http.HttpServletRequest
+import unfiltered.netty.ReceivedMessage
+import org.eclipse.jetty.util.URIUtil
+import java.net.{MalformedURLException, URL}
object Authoritative {
val r = """^(.*)\.(\w{0,4})$""".r
-
- def unapply(req: HttpRequest[javax.servlet.http.HttpServletRequest]): Option[(URL, Representation)] = {
- val uri = req.underlying.getRequestURL.toString
+
+ // all of this would be unnecessary if req.uri would really return the full URI
+ // we should try to push for that to be done at unfiltered layer
+ def reqURL[T](m: Manifest[T], r: HttpRequest[T]): String = {
+ if (m <:< manifest[HttpServletRequest]) reqUrlServlet(r.asInstanceOf[HttpRequest[HttpServletRequest]])
+ else if (m <:< manifest[ReceivedMessage]) reqUrlNetty(r.asInstanceOf[HttpRequest[ReceivedMessage]])
+ else "" //todo: should perhaps throw an exception here.
+ }
+
+ def unapply[T](req: HttpRequest[T]) (implicit m: Manifest[T]) : Option[(URL, Representation)] = {
+ val uri = reqURL(m, req)
val suffixOpt = uri match {
case r(_, suffix) => Some(suffix)
case _ if uri endsWith "/" => Some("/")
@@ -40,4 +52,30 @@
Some((new URL(uri), Representation(suffixOpt, Accept(req))))
}
+
+ private def reqUrlServlet[T <: HttpServletRequest](req: HttpRequest[T]): String = {
+ req.underlying.getRequestURL.toString
+ }
+
+ private def reqUrlNetty[T <: ReceivedMessage](req: HttpRequest[T]): String = {
+ try {
+ val u = new URL(req.uri)
+ new URL(u.getProtocol,u.getHost,u.getPort,u.getPath).toExternalForm
+ } catch {
+ case e: MalformedURLException => {
+ val url: StringBuffer = new StringBuffer (48)
+ val scheme = if (req.isSecure) "https" else "http"
+ val hostport = {//we assume there was some checking done earlier, and that we can rely on this
+ val host = req.headers ("Host")
+ if (host.hasNext) host.next () else "localhost"
+ }
+ url.append (scheme)
+ url.append ("://")
+ url.append (hostport)
+ url.append(req.uri)
+ url.toString
+ }
+ }
+ }
+
}
--- a/src/main/scala/Filesystem.scala Sat Oct 22 20:36:17 2011 +0200
+++ b/src/main/scala/Filesystem.scala Sun Oct 23 16:45:49 2011 +0200
@@ -20,7 +20,7 @@
def sanityCheck(): Boolean =
baseDirectory.exists && baseDirectory.isDirectory
-
+
def resource(url: URL): Resource = new Resource {
val relativePath: String = url.getPath.replaceAll("^"+basePath.toString+"/?", "")
val fileOnDisk = new File(baseDirectory, relativePath)
--- a/src/main/scala/ReadWriteWebMain.scala Sat Oct 22 20:36:17 2011 +0200
+++ b/src/main/scala/ReadWriteWebMain.scala Sun Oct 23 16:45:49 2011 +0200
@@ -92,7 +92,11 @@
baseURL.value.get,
lang=rdfLanguage.value getOrElse RDFXML)(mode.value getOrElse ResourcesDontExistByDefault)
- val app = new ReadWriteWeb(filesystem, new RDFAuthZ(webCache,filesystem))
+ val rww = new ReadWriteWeb[HttpServletRequest,HttpServletResponse] {
+ val rm = filesystem
+ def manif = manifest[HttpServletRequest]
+ override implicit val authz = new RDFAuthZ[HttpServletRequest,HttpServletResponse](webCache,filesystem)
+ }
//this is incomplete: we should be able to start both ports.... not sure how to do this yet.
val service = httpsPort.value match {
@@ -105,12 +109,14 @@
context("/public"){ ctx:ContextBuilder =>
ctx.resources(ClasspathUtils.fromClasspath("public/").toURI.toURL)
}.
- filter(app.plan).
+ filter(Planify(rww.intent)).
filter(Planify(x509v.intent)).
filter(new EchoPlan().plan).run()
}
+
+
object x509v extends X509view[HttpServletRequest,HttpServletResponse] {
def wc = webCache
def manif = manifest[HttpServletRequest]
--- a/src/main/scala/auth/Authz.scala Sat Oct 22 20:36:17 2011 +0200
+++ b/src/main/scala/auth/Authz.scala Sun Oct 23 16:45:49 2011 +0200
@@ -72,18 +72,18 @@
}
}
-object NullAuthZ extends AuthZ {
- def subject(req: NullAuthZ.Req) = null
+class NullAuthZ[Request,Response] extends AuthZ[Request,Response] {
+ override def subject(req: Req): Option[Subject] = None
- def guard(m: Method, path: String) = null
+ override def guard(m: Method, path: String): Guard = null
override def protect(in: Req=>Res) = in
}
-abstract class AuthZ {
- type Req = HttpRequest[HttpServletRequest]
- type Res = ResponseFunction[HttpServletResponse]
+abstract class AuthZ[Request,Response] {
+ type Req = HttpRequest[Request]
+ type Res = ResponseFunction[Response]
def protect(in: Req=>Res): Req=>Res = {
case req @ HttpMethod(method) & Path(path) if guard(method, path).allow(() => subject(req)) => in(req)
@@ -108,7 +108,8 @@
}
-class RDFAuthZ(val webCache: WebCache, rm: ResourceManager) extends AuthZ {
+class RDFAuthZ[Request,Response](val webCache: WebCache, rm: ResourceManager)
+ (implicit val m: Manifest[Request]) extends AuthZ[Request,Response] {
import AuthZ.x509toSubject
implicit val cache : WebCache = webCache
--- a/src/main/scala/auth/X509view.scala Sat Oct 22 20:36:17 2011 +0200
+++ b/src/main/scala/auth/X509view.scala Sun Oct 23 16:45:49 2011 +0200
@@ -30,17 +30,20 @@
/**
* This plan just described the X509 WebID authentication information.
+ * It works independently of the underlying Cycle.Intent implementations of Request and Response,
+ * so it can work with servlet filters just as well as with netty.
+ *
* This is a simple version. A future version will show EARL output, and so be useful for debugging the endpoint.
*
* @author hjs
* @created: 13/10/2011
*/
-trait X509view[A,B] {
+trait X509view[Request,Response] {
implicit def wc: WebCache
- implicit def manif: Manifest[A]
+ implicit def manif: Manifest[Request]
- def intent: Cycle.Intent[A, B] = {
+ def intent: Cycle.Intent[Request, Response] = {
case req @ Path(path) if path startsWith "/test/auth/x509" =>
Ok ~> ContentType("text/html") ~> Html(
<html><head><title>Authentication Page</title></head>
--- a/src/main/scala/netty/ReadWriteWebNetty.scala Sat Oct 22 20:36:17 2011 +0200
+++ b/src/main/scala/netty/ReadWriteWebNetty.scala Sun Oct 23 16:45:49 2011 +0200
@@ -27,8 +27,8 @@
import scala.Console._
import org.w3.readwriteweb.auth.{X509view, RDFAuthZ}
import org.w3.readwriteweb._
-import unfiltered.netty.{ReceivedMessage, ServerErrorResponse, cycle}
import org.jboss.netty.handler.codec.http.HttpResponse
+import unfiltered.netty.{ServerErrorResponse, ReceivedMessage, cycle}
/**
* ReadWrite Web for Netty server, allowing content renegotiation
@@ -54,8 +54,13 @@
baseURL.value.get,
lang=rdfLanguage.value getOrElse RDFXML)(mode.value getOrElse ResourcesDontExistByDefault)
- val app = new ReadWriteWeb(filesystem, new RDFAuthZ(webCache,filesystem))
-
+// val app = new ReadWriteWeb(filesystem, new RDFAuthZ(webCache,filesystem))
+ val rww = new cycle.Plan with cycle.ThreadPool with ServerErrorResponse with ReadWriteWeb[ReceivedMessage,HttpResponse]{
+ val rm = filesystem
+ def manif = manifest[ReceivedMessage]
+ override val authz = new RDFAuthZ[ReceivedMessage,HttpResponse](webCache,filesystem)
+ }
+
//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)
@@ -63,7 +68,8 @@
}
// configures and launches a Netty server
- service.plan( x509v ).run()
+ service.plan( x509v ).
+ plan( rww ).run()
}
--- a/src/main/scala/plan.scala Sat Oct 22 20:36:17 2011 +0200
+++ b/src/main/scala/plan.scala Sun Oct 23 16:45:49 2011 +0200
@@ -21,6 +21,7 @@
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
import unfiltered.request._
import unfiltered.response._
+import unfiltered.Cycle
//object ReadWriteWeb {
//
@@ -30,34 +31,29 @@
//
//}
-class ReadWriteWeb(rm: ResourceManager, implicit val authz: AuthZ = NullAuthZ) {
+/**
+ * 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 generalise this code so that it does not depend so strongly on servlets.
- type Req = HttpRequest[HttpServletRequest]
- type Res = ResponseFunction[HttpServletResponse]
+// type Request = HttpRequest[Req]
+// type Response = ResponseFunction[Res]
val logger: Logger = LoggerFactory.getLogger(this.getClass)
-
- /**
- * The Servlet Filter for the ReadWriteWeb
- *
- * Planify.apply takes an Intent, which is defined in Cycle by
- * type Intent [-A, -B] = PartialFunction[HttpRequest[A], ResponseFunction[B]]
- * the corresponding syntax is: case ... => ...
- */
- //bblfish: I don't understand why this has to be lazy, but if it is not, it the plan ends up throwing a null
- //pointer exception.
- lazy val plan = unfiltered.filter.Planify(intent)
-
/**
* 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 )
*/
- val intent : Plan.Intent = {
- case req @ Path(path) if path startsWith rm.basePath => authz.protect(readWriteWebIntent)(req)
+ def intent : Cycle.Intent[Req,Res] = {
+ case req @ Path(path) if path startsWith rm.basePath => authz.protect(rwwIntent)(req)
}
/**
@@ -79,11 +75,11 @@
* At last, Validation[ResponseFunction, ResponseFuntion] is exposed as a ResponseFunction
* through another implicit conversion. It saves us the call to the Validation.fold() method
*/
- val readWriteWebIntent = (req: Req) => {
+ def rwwIntent = (req: HttpRequest[Req]) => {
val Authoritative(uri, representation) = req
val r: Resource = rm.resource(uri)
- val res: Res = req match {
+ 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")
--- a/src/test/scala/util/specs.scala Sat Oct 22 20:36:17 2011 +0200
+++ b/src/test/scala/util/specs.scala Sun Oct 23 16:45:49 2011 +0200
@@ -2,6 +2,7 @@
import org.w3.readwriteweb._
+import auth.RDFAuthZ
import org.specs._
import java.net.URL
import unfiltered.response._
@@ -18,12 +19,19 @@
import org.w3.readwriteweb.util._
import org.w3.readwriteweb.utiltest._
+import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
+import unfiltered.filter.Planify
trait ResourceManaged extends Specification with unfiltered.spec.jetty.Served {
def resourceManager: ResourceManager
-
- def setup = { _.filter(new ReadWriteWeb(resourceManager).plan) }
+
+ val rww = new ReadWriteWeb[HttpServletRequest,HttpServletResponse] {
+ val rm = resourceManager
+ def manif = manifest[HttpServletRequest]
+ }
+
+ def setup = { _.filter(Planify(rww.intent)) }
}