summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-07-21 23:45:34 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-07-21 23:45:45 +0800
commit1cfe997b4d8874738c2ed384dc221420b42f551b (patch)
treee090e458bbb1a5ebd66433c67224ac2be20fea4f
parent3f61791c57b450de84a6599e2338b1afcf172a05 (diff)
downloadcask-1cfe997b4d8874738c2ed384dc221420b42f551b.tar.gz
cask-1cfe997b4d8874738c2ed384dc221420b42f551b.tar.bz2
cask-1cfe997b4d8874738c2ed384dc221420b42f551b.zip
Static file serving now works
-rw-r--r--cask/src/cask/Annotations.scala31
-rw-r--r--cask/src/cask/DispatchTrie.scala44
-rw-r--r--cask/src/cask/Main.scala8
-rw-r--r--cask/src/cask/ParamReader.scala31
-rw-r--r--cask/src/cask/Routes.scala4
-rw-r--r--cask/test/src/test/cask/CaskTest.scala48
-rw-r--r--cask/test/src/test/cask/VariableRoutes.scala8
7 files changed, 94 insertions, 80 deletions
diff --git a/cask/src/cask/Annotations.scala b/cask/src/cask/Annotations.scala
index fa0e25e..17e6b0a 100644
--- a/cask/src/cask/Annotations.scala
+++ b/cask/src/cask/Annotations.scala
@@ -6,20 +6,23 @@ import collection.JavaConverters._
trait EndpointAnnotation[R]{
val path: String
+ def subpath: Boolean = false
def wrapMethodOutput(t: R): Any
def parseMethodInput[T](implicit p: ParamReader[T]) = p
def handle(exchange: HttpServerExchange,
+ remaining: Seq[String],
bindings: Map[String, String],
routes: Routes,
- entryPoint: EntryPoint[Routes, HttpServerExchange]): Router.Result[Response]
+ entryPoint: EntryPoint[Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response]
}
trait RouteBase extends EndpointAnnotation[Response]{
def wrapMethodOutput(t: Response) = t
def handle(exchange: HttpServerExchange,
+ remaining: Seq[String],
bindings: Map[String, String],
routes: Routes,
- entryPoint: EntryPoint[Routes, HttpServerExchange]): Router.Result[Response] = {
+ entryPoint: EntryPoint[Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response] = {
val allBindings =
bindings.toSeq ++
exchange.getQueryParameters
@@ -27,39 +30,37 @@ trait RouteBase extends EndpointAnnotation[Response]{
.toSeq
.flatMap{case (k, vs) => vs.asScala.map((k, _))}
- entryPoint.invoke(routes, exchange, allBindings.map{case (k, v) => (k, Some(v))})
+ entryPoint.invoke(routes, (exchange, remaining), allBindings.map{case (k, v) => (k, Some(v))})
.asInstanceOf[Router.Result[Response]]
}
}
-class get(val path: String) extends RouteBase
-class post(val path: String) extends RouteBase
-class put(val path: String) extends RouteBase
-class route(val path: String, val methods: Seq[String]) extends RouteBase
+class get(val path: String, override val subpath: Boolean = false) extends RouteBase
+class post(val path: String, override val subpath: Boolean = false) extends RouteBase
+class put(val path: String, override val subpath: Boolean = false) extends RouteBase
+class route(val path: String, val methods: Seq[String], override val subpath: Boolean = false) extends RouteBase
class static(val path: String) extends EndpointAnnotation[String] {
+ override def subpath = true
def wrapOutput(t: String) = t
def wrapMethodOutput(t: String) = t
def handle(exchange: HttpServerExchange,
+ remaining: Seq[String],
bindings: Map[String, String],
routes: Routes,
- entryPoint: EntryPoint[Routes, HttpServerExchange]): Router.Result[Response] = {
- entryPoint.invoke(routes, exchange, Nil).asInstanceOf[Router.Result[String]] match{
+ entryPoint: EntryPoint[Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response] = {
+ entryPoint.invoke(routes, (exchange, remaining), Nil).asInstanceOf[Router.Result[String]] match{
case Router.Result.Success(s) =>
- println("XXX STATIC")
val relPath = java.nio.file.Paths.get(
- s + Util.splitPath(exchange.getRequestPath).drop(Util.splitPath(path).length).mkString("/")
+ s + "/" + remaining.mkString("/")
)
- println("Y")
if (java.nio.file.Files.exists(relPath) && java.nio.file.Files.isRegularFile(relPath)){
Router.Result.Success(Response(java.nio.file.Files.newInputStream(relPath)))
}else{
Router.Result.Success(Response("", 404))
}
- case e: Router.Result.Error =>
- println("XXX")
- e
+ case e: Router.Result.Error => e
}
diff --git a/cask/src/cask/DispatchTrie.scala b/cask/src/cask/DispatchTrie.scala
index 034bb55..d986c8b 100644
--- a/cask/src/cask/DispatchTrie.scala
+++ b/cask/src/cask/DispatchTrie.scala
@@ -1,18 +1,19 @@
package cask
import collection.mutable
object DispatchTrie{
- def construct[T](index: Int, inputs: Seq[(IndexedSeq[String], T)]): DispatchTrie[T] = {
- val continuations = mutable.Map.empty[String, mutable.Buffer[(IndexedSeq[String], T)]]
+ def construct[T](index: Int,
+ inputs: Seq[(IndexedSeq[String], T, Boolean)]): DispatchTrie[T] = {
+ val continuations = mutable.Map.empty[String, mutable.Buffer[(IndexedSeq[String], T, Boolean)]]
- val terminals = mutable.Buffer.empty[(IndexedSeq[String], T)]
+ val terminals = mutable.Buffer.empty[(IndexedSeq[String], T, Boolean)]
- for((path, endPoint) <- inputs) {
+ for((path, endPoint, allowSubpath) <- inputs) {
if (path.length < index) () // do nothing
else if (path.length == index) {
- terminals.append(path -> endPoint)
+ terminals.append((path, endPoint, allowSubpath))
} else if (path.length > index){
val buf = continuations.getOrElseUpdate(path(index), mutable.Buffer.empty)
- buf.append(path -> endPoint)
+ buf.append((path, endPoint, allowSubpath))
}
}
@@ -25,31 +26,37 @@ object DispatchTrie{
} else if(wildcards.size >= 1 && continuations.size > 1) {
throw new Exception(
"Routes overlap with wildcards: " +
- (wildcards ++ continuations).flatMap(_._2).map(_._1.mkString("/"))
+ (wildcards ++ continuations).flatMap(_._2).map(_._1.mkString("/"))
+ )
+ }else if (terminals.headOption.exists(_._3) && continuations.size == 1){
+ throw new Exception(
+ "Routes overlap with subpath capture: " +
+ (wildcards ++ continuations).flatMap(_._2).map(_._1.mkString("/"))
)
}else{
DispatchTrie[T](
- current = terminals.headOption.map(_._2),
+ current = terminals.headOption.map(x => x._2 -> x._3),
children = continuations.map{ case (k, vs) =>
if (!k.startsWith("::")) (k, construct(index + 1, vs))
- else (k, DispatchTrie(Some(vs.head._2), Map()))
+ else (k, DispatchTrie(Some(vs.head._2 -> vs.head._3), Map()))
}.toMap
)
}
}
}
-case class DispatchTrie[T](current: Option[T],
+case class DispatchTrie[T](current: Option[(T, Boolean)],
children: Map[String, DispatchTrie[T]]){
- final def lookup(input: List[String],
- bindings: Map[String, String])
- : Option[(T, Map[String, String])] = {
- input match{
- case Nil => current.map(_ -> bindings)
+ final def lookup(remainingInput: List[String],
+ bindings: Map[String, String])
+ : Option[(T, Map[String, String], Seq[String])] = {
+ remainingInput match{
+ case Nil =>
+ current.map(x => (x._1, bindings, Nil))
+ case head :: rest if current.exists(_._2) =>
+ current.map(x => (x._1, bindings, head :: rest))
case head :: rest =>
- if (children.size == 1 && children.keys.head.startsWith("::")){
- children.values.head.lookup(Nil, bindings + (children.keys.head.drop(2) -> input.mkString("/")))
- }else if (children.size == 1 && children.keys.head.startsWith(":")){
+ if (children.size == 1 && children.keys.head.startsWith(":")){
children.values.head.lookup(rest, bindings + (children.keys.head.drop(1) -> head))
}else{
children.get(head) match{
@@ -60,5 +67,4 @@ case class DispatchTrie[T](current: Option[T],
}
}
-
}
diff --git a/cask/src/cask/Main.scala b/cask/src/cask/Main.scala
index dcbc807..4469a1e 100644
--- a/cask/src/cask/Main.scala
+++ b/cask/src/cask/Main.scala
@@ -27,7 +27,7 @@ abstract class BaseMain{
lazy val routeTrie = DispatchTrie.construct[(Routes, Routes.RouteMetadata[_])](0,
for((route, metadata) <- routeList)
- yield (Util.splitPath(metadata.metadata.path): IndexedSeq[String], (route, metadata))
+ yield (Util.splitPath(metadata.metadata.path): IndexedSeq[String], (route, metadata), metadata.metadata.subpath)
)
def handleError(statusCode: Int): Response = {
@@ -50,10 +50,10 @@ abstract class BaseMain{
def handleRequest(exchange: HttpServerExchange): Unit = {
routeTrie.lookup(Util.splitPath(exchange.getRequestPath).toList, Map()) match{
case None => writeResponse(exchange, handleError(404))
- case Some(((routes, metadata), bindings)) =>
+ case Some(((routes, metadata), bindings, remaining)) =>
val result = metadata.metadata.handle(
- exchange, bindings, routes,
- metadata.entryPoint.asInstanceOf[EntryPoint[Routes, HttpServerExchange]]
+ exchange, remaining, bindings, routes,
+ metadata.entryPoint.asInstanceOf[EntryPoint[Routes, (HttpServerExchange, Seq[String])]]
)
result match{
diff --git a/cask/src/cask/ParamReader.scala b/cask/src/cask/ParamReader.scala
index 8cb7c1f..1ae5bd8 100644
--- a/cask/src/cask/ParamReader.scala
+++ b/cask/src/cask/ParamReader.scala
@@ -3,21 +3,24 @@ package cask
import io.undertow.server.HttpServerExchange
class ParamReader[T](val arity: Int,
- val read0: (HttpServerExchange, Seq[String]) => T)
- extends Router.ArgReader[T, HttpServerExchange]{
- def read(ctx: HttpServerExchange, v: Seq[String]): T = read0(ctx, v)
+ val read0: (HttpServerExchange, Seq[String], Seq[String]) => T)
+ extends Router.ArgReader[T, (HttpServerExchange, Seq[String])]{
+ def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]): T = read0(ctx._1, v, ctx._2)
}
object ParamReader{
- implicit object StringParam extends ParamReader[String](1, (h, x) => x.head)
- implicit object BooleanParam extends ParamReader[Boolean](1, (h, x) => x.head.toBoolean)
- implicit object ByteParam extends ParamReader[Byte](1, (h, x) => x.head.toByte)
- implicit object ShortParam extends ParamReader[Short](1, (h, x) => x.head.toShort)
- implicit object IntParam extends ParamReader[Int](1, (h, x) => x.head.toInt)
- implicit object LongParam extends ParamReader[Long](1, (h, x) => x.head.toLong)
- implicit object DoubleParam extends ParamReader[Double](1, (h, x) => x.head.toDouble)
- implicit object FloatParam extends ParamReader[Float](1, (h, x) => x.head.toFloat)
+ implicit object StringParam extends ParamReader[String](1, (h, x, r) => x.head)
+ implicit object BooleanParam extends ParamReader[Boolean](1, (h, x, r) => x.head.toBoolean)
+ implicit object ByteParam extends ParamReader[Byte](1, (h, x, r) => x.head.toByte)
+ implicit object ShortParam extends ParamReader[Short](1, (h, x, r) => x.head.toShort)
+ implicit object IntParam extends ParamReader[Int](1, (h, x, r) => x.head.toInt)
+ implicit object LongParam extends ParamReader[Long](1, (h, x, r) => x.head.toLong)
+ implicit object DoubleParam extends ParamReader[Double](1, (h, x, r) => x.head.toDouble)
+ implicit object FloatParam extends ParamReader[Float](1, (h, x, r) => x.head.toFloat)
implicit def SeqParam[T: ParamReader] =
- new ParamReader[Seq[T]](1, (h, s) => s.map(x => implicitly[ParamReader[T]].read(h, Seq(x))))
+ new ParamReader[Seq[T]](1, (h, s, r) => s.map(x => implicitly[ParamReader[T]].read((h, r), Seq(x))))
- implicit object HttpExchangeParam extends ParamReader[HttpServerExchange](0, (h, x) => h)
-} \ No newline at end of file
+ implicit object HttpExchangeParam extends ParamReader[HttpServerExchange](0, (h, x, r) => h)
+ implicit object SubpathParam extends ParamReader[Subpath](0, (h, x, r) => new Subpath(r))
+}
+
+class Subpath(val value: Seq[String]) \ No newline at end of file
diff --git a/cask/src/cask/Routes.scala b/cask/src/cask/Routes.scala
index 3ab5906..0145c91 100644
--- a/cask/src/cask/Routes.scala
+++ b/cask/src/cask/Routes.scala
@@ -32,7 +32,7 @@ object Response{
object Routes{
case class RouteMetadata[T](metadata: EndpointAnnotation[_],
- entryPoint: EntryPoint[T, HttpServerExchange])
+ entryPoint: EntryPoint[T, (HttpServerExchange, Seq[String])])
case class Metadata[T](value: RouteMetadata[T]*)
object Metadata{
implicit def initialize[T] = macro initializeImpl[T]
@@ -50,7 +50,7 @@ object Routes{
m.asInstanceOf[MethodSymbol],
weakTypeOf[T],
(t: router.c.universe.Tree) => q"$annotObjectSym.wrapMethodOutput($t)",
- c.weakTypeOf[io.undertow.server.HttpServerExchange],
+ c.weakTypeOf[(io.undertow.server.HttpServerExchange, Seq[String])],
q"$annotObjectSym.parseMethodInput"
)
diff --git a/cask/test/src/test/cask/CaskTest.scala b/cask/test/src/test/cask/CaskTest.scala
index 7bbb8de..394e53c 100644
--- a/cask/test/src/test/cask/CaskTest.scala
+++ b/cask/test/src/test/cask/CaskTest.scala
@@ -7,11 +7,11 @@ object CaskTest extends TestSuite {
'hello - {
val x = DispatchTrie.construct(0,
- Seq(Vector("hello") -> 1)
+ Seq((Vector("hello"), 1, false))
)
assert(
- x.lookup(List("hello"), Map()) == Some((1, Map())),
+ x.lookup(List("hello"), Map()) == Some((1, Map(), Nil)),
x.lookup(List("hello", "world"), Map()) == None,
x.lookup(List("world"), Map()) == None
)
@@ -19,13 +19,13 @@ object CaskTest extends TestSuite {
'nested - {
val x = DispatchTrie.construct(0,
Seq(
- Vector("hello", "world") -> 1,
- Vector("hello", "cow") -> 2
+ (Vector("hello", "world"), 1, false),
+ (Vector("hello", "cow"), 2, false)
)
)
assert(
- x.lookup(List("hello", "world"), Map()) == Some((1, Map())),
- x.lookup(List("hello", "cow"), Map()) == Some((2, Map())),
+ x.lookup(List("hello", "world"), Map()) == Some((1, Map(), Nil)),
+ x.lookup(List("hello", "cow"), Map()) == Some((2, Map(), Nil)),
x.lookup(List("hello"), Map()) == None,
x.lookup(List("hello", "moo"), Map()) == None,
x.lookup(List("hello", "world", "moo"), Map()) == None
@@ -33,11 +33,11 @@ object CaskTest extends TestSuite {
}
'bindings - {
val x = DispatchTrie.construct(0,
- Seq(Vector(":hello", ":world") -> 1)
+ Seq((Vector(":hello", ":world"), 1, false))
)
assert(
- x.lookup(List("hello", "world"), Map()) == Some((1, Map("hello" -> "hello", "world" -> "world"))),
- x.lookup(List("world", "hello"), Map()) == Some((1, Map("hello" -> "world", "world" -> "hello"))),
+ x.lookup(List("hello", "world"), Map()) == Some((1, Map("hello" -> "hello", "world" -> "world"), Nil)),
+ x.lookup(List("world", "hello"), Map()) == Some((1, Map("hello" -> "world", "world" -> "hello"), Nil)),
x.lookup(List("hello", "world", "cow"), Map()) == None,
x.lookup(List("hello"), Map()) == None
@@ -46,12 +46,14 @@ object CaskTest extends TestSuite {
'path - {
val x = DispatchTrie.construct(0,
- Seq(Vector("hello", "::world") -> 1)
+ Seq((Vector("hello"), 1, true))
)
+
assert(
- x.lookup(List("hello", "world"), Map()) == Some((1,Map("world" -> "world"))),
- x.lookup(List("hello", "world", "cow"), Map()) == Some((1,Map("world" -> "world/cow"))),
- x.lookup(List("hello"), Map()) == None
+ x.lookup(List("hello", "world"), Map()) == Some((1,Map(), Seq("world"))),
+ x.lookup(List("hello", "world", "cow"), Map()) == Some((1,Map(), Seq("world", "cow"))),
+ x.lookup(List("hello"), Map()) == Some((1,Map(), Seq())),
+ x.lookup(List(), Map()) == None
)
}
@@ -59,40 +61,40 @@ object CaskTest extends TestSuite {
intercept[Exception]{
DispatchTrie.construct(0,
Seq(
- Vector("hello", ":world") -> 1,
- Vector("hello", "world") -> 2
+ (Vector("hello", ":world"), 1, false),
+ (Vector("hello", "world"), 2, false)
)
)
}
intercept[Exception]{
DispatchTrie.construct(0,
Seq(
- Vector("hello", ":world") -> 1,
- Vector("hello", "world", "omg") -> 2
+ (Vector("hello", ":world"), 1, false),
+ (Vector("hello", "world", "omg"), 2, false)
)
)
}
intercept[Exception]{
DispatchTrie.construct(0,
Seq(
- Vector("hello", "::world") -> 1,
- Vector("hello", "cow", "omg") -> 2
+ (Vector("hello"), 1, true),
+ (Vector("hello", "cow", "omg"), 2, false)
)
)
}
intercept[Exception]{
DispatchTrie.construct(0,
Seq(
- Vector("hello", ":world") -> 1,
- Vector("hello", ":cow") -> 2
+ (Vector("hello", ":world"), 1, false),
+ (Vector("hello", ":cow"), 2, false)
)
)
}
intercept[Exception]{
DispatchTrie.construct(0,
Seq(
- Vector("hello", "world") -> 1,
- Vector("hello", "world") -> 2
+ (Vector("hello", "world"), 1, false),
+ (Vector("hello", "world"), 2, false)
)
)
}
diff --git a/cask/test/src/test/cask/VariableRoutes.scala b/cask/test/src/test/cask/VariableRoutes.scala
index e7415c6..1dda4e6 100644
--- a/cask/test/src/test/cask/VariableRoutes.scala
+++ b/cask/test/src/test/cask/VariableRoutes.scala
@@ -1,5 +1,7 @@
package test.cask
+import cask.Subpath
+
object VariableRoutes extends cask.MainRoutes{
@cask.get("/user/:userName")
def showUserProfile(userName: String) = {
@@ -11,9 +13,9 @@ object VariableRoutes extends cask.MainRoutes{
s"Post $postId $query"
}
- @cask.get("/path/::subPath")
- def showSubpath(subPath: String) = {
- s"Subpath $subPath"
+ @cask.get("/path", subpath = true)
+ def showSubpath(subPath: Subpath) = {
+ s"Subpath ${subPath.value}"
}
initialize()