diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2018-07-21 23:45:34 +0800 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2018-07-21 23:45:45 +0800 |
commit | 1cfe997b4d8874738c2ed384dc221420b42f551b (patch) | |
tree | e090e458bbb1a5ebd66433c67224ac2be20fea4f /cask | |
parent | 3f61791c57b450de84a6599e2338b1afcf172a05 (diff) | |
download | cask-1cfe997b4d8874738c2ed384dc221420b42f551b.tar.gz cask-1cfe997b4d8874738c2ed384dc221420b42f551b.tar.bz2 cask-1cfe997b4d8874738c2ed384dc221420b42f551b.zip |
Static file serving now works
Diffstat (limited to 'cask')
-rw-r--r-- | cask/src/cask/Annotations.scala | 31 | ||||
-rw-r--r-- | cask/src/cask/DispatchTrie.scala | 44 | ||||
-rw-r--r-- | cask/src/cask/Main.scala | 8 | ||||
-rw-r--r-- | cask/src/cask/ParamReader.scala | 31 | ||||
-rw-r--r-- | cask/src/cask/Routes.scala | 4 | ||||
-rw-r--r-- | cask/test/src/test/cask/CaskTest.scala | 48 | ||||
-rw-r--r-- | cask/test/src/test/cask/VariableRoutes.scala | 8 |
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() |