From 4b0cfdf0eeca46cfccbf9fe42af42f383932c427 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sat, 18 Aug 2018 10:16:27 +0800 Subject: 0.1.1 --- build.sc | 2 +- cask/src/cask/decorators/compress.scala | 4 +- cask/src/cask/endpoints/FormEndpoint.scala | 18 ++-- cask/src/cask/endpoints/JsonEndpoint.scala | 10 +-- cask/src/cask/endpoints/ParamReader.scala | 16 ++-- cask/src/cask/endpoints/StaticEndpoints.scala | 10 +-- cask/src/cask/endpoints/WebEndpoints.scala | 16 ++-- cask/src/cask/endpoints/WebSocketEndpoint.scala | 15 ++-- cask/src/cask/internal/DispatchTrie.scala | 13 ++- cask/src/cask/main/Decorators.scala | 10 +-- cask/src/cask/main/Main.scala | 4 +- cask/src/cask/main/Routes.scala | 4 +- cask/src/cask/model/ParamContext.scala | 5 -- cask/src/cask/model/Params.scala | 16 +--- cask/src/cask/model/Response.scala | 98 ++++++++-------------- cask/src/cask/package.scala | 8 +- cask/test/src/test/cask/FailureTests.scala | 5 +- .../1 - Cask: a Scala HTTP micro-framework.md | 6 +- example/compress/build.sc | 2 +- example/compress2/build.sc | 2 +- example/compress3/build.sc | 2 +- example/cookies/build.sc | 2 +- example/decorated/app/src/Decorated.scala | 4 +- example/decorated/build.sc | 2 +- example/decorated2/app/src/Decorated2.scala | 4 +- example/decorated2/build.sc | 2 +- example/endpoints/app/src/Endpoints.scala | 2 +- example/endpoints/build.sc | 2 +- example/formJsonPost/build.sc | 2 +- example/httpMethods/build.sc | 2 +- example/minimalApplication/build.sc | 2 +- example/minimalApplication2/build.sc | 2 +- example/redirectAbort/build.sc | 2 +- example/scalatags/build.sc | 2 +- example/staticFiles/build.sc | 2 +- example/todo/app/src/TodoServer.scala | 2 +- example/todo/build.sc | 2 +- example/todoApi/build.sc | 2 +- example/todoDb/app/src/TodoMvcDb.scala | 2 +- example/todoDb/build.sc | 2 +- .../variableRoutes/app/src/VariableRoutes.scala | 4 +- example/variableRoutes/build.sc | 2 +- example/websockets/build.sc | 2 +- 43 files changed, 139 insertions(+), 177 deletions(-) delete mode 100644 cask/src/cask/model/ParamContext.scala diff --git a/build.sc b/build.sc index 9ea4c7b..b368baa 100644 --- a/build.sc +++ b/build.sc @@ -58,7 +58,7 @@ object cask extends ScalaModule with PublishModule { } object example extends Module{ trait LocalModule extends ScalaModule{ - def ivyDeps = super.ivyDeps().filter(_ != ivy"com.lihaoyi::cask:0.1.0") + def ivyDeps = super.ivyDeps().filter(_ != ivy"com.lihaoyi::cask:0.1.1") override def millSourcePath = super.millSourcePath / "app" def moduleDeps = Seq(cask) diff --git a/cask/src/cask/decorators/compress.scala b/cask/src/cask/decorators/compress.scala index 22bd29e..75e2bed 100644 --- a/cask/src/cask/decorators/compress.scala +++ b/cask/src/cask/decorators/compress.scala @@ -3,11 +3,11 @@ import java.io.{ByteArrayOutputStream, OutputStream} import java.util.zip.{DeflaterOutputStream, GZIPOutputStream} import cask.internal.Router -import cask.model.{ParamContext, Response} +import cask.model.{Request, Response} import collection.JavaConverters._ class compress extends cask.Decorator{ - def wrapFunction(ctx: ParamContext, delegate: Delegate) = { + def wrapFunction(ctx: Request, delegate: Delegate) = { val acceptEncodings = ctx.exchange.getRequestHeaders.get("Accept-Encoding").asScala.flatMap(_.split(", ")) delegate(Map()).map{ v => val (newData, newHeaders) = if (acceptEncodings.exists(_.toLowerCase == "gzip")) { diff --git a/cask/src/cask/endpoints/FormEndpoint.scala b/cask/src/cask/endpoints/FormEndpoint.scala index a952a2a..cc74093 100644 --- a/cask/src/cask/endpoints/FormEndpoint.scala +++ b/cask/src/cask/endpoints/FormEndpoint.scala @@ -7,40 +7,40 @@ import io.undertow.server.handlers.form.FormParserFactory import collection.JavaConverters._ -sealed trait FormReader[T] extends Router.ArgReader[Seq[FormEntry], T, ParamContext] +sealed trait FormReader[T] extends Router.ArgReader[Seq[FormEntry], T, Request] object FormReader{ implicit def paramFormReader[T: QueryParamReader] = new FormReader[T]{ def arity = implicitly[QueryParamReader[T]].arity - def read(ctx: ParamContext, label: String, input: Seq[FormEntry]) = { + def read(ctx: Request, label: String, input: Seq[FormEntry]) = { implicitly[QueryParamReader[T]].read(ctx, label, if (input == null) null else input.map(_.valueOrFileName)) } } implicit def formEntryReader = new FormReader[FormEntry]{ def arity = 1 - def read(ctx: ParamContext, label: String, input: Seq[FormEntry]) = input.head + def read(ctx: Request, label: String, input: Seq[FormEntry]) = input.head } implicit def formEntriesReader = new FormReader[Seq[FormEntry]]{ def arity = 1 - def read(ctx: ParamContext, label: String, input: Seq[FormEntry]) = input + def read(ctx: Request, label: String, input: Seq[FormEntry]) = input } implicit def formValueReader = new FormReader[FormValue]{ def arity = 1 - def read(ctx: ParamContext, label: String, input: Seq[FormEntry]) = input.head.asInstanceOf[FormValue] + def read(ctx: Request, label: String, input: Seq[FormEntry]) = input.head.asInstanceOf[FormValue] } implicit def formValuesReader = new FormReader[Seq[FormValue]]{ def arity = 1 - def read(ctx: ParamContext, label: String, input: Seq[FormEntry]) = input.map(_.asInstanceOf[FormValue]) + def read(ctx: Request, label: String, input: Seq[FormEntry]) = input.map(_.asInstanceOf[FormValue]) } implicit def formFileReader = new FormReader[FormFile]{ def arity = 1 - def read(ctx: ParamContext, label: String, input: Seq[FormEntry]) = input.head.asInstanceOf[FormFile] + def read(ctx: Request, label: String, input: Seq[FormEntry]) = input.head.asInstanceOf[FormFile] } implicit def formFilesReader = new FormReader[Seq[FormFile]]{ def arity = 1 - def read(ctx: ParamContext, label: String, input: Seq[FormEntry]) = input.map(_.asInstanceOf[FormFile]) + def read(ctx: Request, label: String, input: Seq[FormEntry]) = input.map(_.asInstanceOf[FormFile]) } } class postForm(val path: String, override val subpath: Boolean = false) extends Endpoint { @@ -49,7 +49,7 @@ class postForm(val path: String, override val subpath: Boolean = false) extends val methods = Seq("post") type Input = Seq[FormEntry] type InputParser[T] = FormReader[T] - def wrapFunction(ctx: ParamContext, + def wrapFunction(ctx: Request, delegate: Map[String, Input] => Router.Result[Output]): Router.Result[Response] = { try { val formData = FormParserFactory.builder().build().createParser(ctx.exchange).parseBlocking() diff --git a/cask/src/cask/endpoints/JsonEndpoint.scala b/cask/src/cask/endpoints/JsonEndpoint.scala index f91b888..212597c 100644 --- a/cask/src/cask/endpoints/JsonEndpoint.scala +++ b/cask/src/cask/endpoints/JsonEndpoint.scala @@ -5,15 +5,15 @@ import java.io.ByteArrayOutputStream import cask.internal.{Router, Util} import cask.internal.Router.EntryPoint import cask.main.{Endpoint, HttpDecorator, Routes} -import cask.model.{ParamContext, Response} +import cask.model.{Request, Response} -sealed trait JsReader[T] extends Router.ArgReader[ujson.Js.Value, T, cask.model.ParamContext] +sealed trait JsReader[T] extends Router.ArgReader[ujson.Js.Value, T, cask.model.Request] object JsReader{ implicit def defaultJsReader[T: upickle.default.Reader] = new JsReader[T]{ def arity = 1 - def read(ctx: cask.model.ParamContext, label: String, input: ujson.Js.Value): T = { + def read(ctx: cask.model.Request, label: String, input: ujson.Js.Value): T = { implicitly[upickle.default.Reader[T]].apply(input) } } @@ -21,7 +21,7 @@ object JsReader{ implicit def paramReader[T: ParamReader] = new JsReader[T] { override def arity = 0 - override def read(ctx: cask.model.ParamContext, label: String, v: ujson.Js.Value) = { + override def read(ctx: cask.model.Request, label: String, v: ujson.Js.Value) = { implicitly[ParamReader[T]].read(ctx, label, Nil) } } @@ -31,7 +31,7 @@ class postJson(val path: String, override val subpath: Boolean = false) extends val methods = Seq("post") type Input = ujson.Js.Value type InputParser[T] = JsReader[T] - def wrapFunction(ctx: ParamContext, + def wrapFunction(ctx: Request, delegate: Map[String, Input] => Router.Result[Output]): Router.Result[Response] = { val obj = for{ str <- diff --git a/cask/src/cask/endpoints/ParamReader.scala b/cask/src/cask/endpoints/ParamReader.scala index f1ec0bf..e43f482 100644 --- a/cask/src/cask/endpoints/ParamReader.scala +++ b/cask/src/cask/endpoints/ParamReader.scala @@ -1,22 +1,28 @@ package cask.endpoints import cask.internal.Router -import cask.model.ParamContext +import cask.model.{Cookie, Request} import io.undertow.server.HttpServerExchange import io.undertow.server.handlers.form.{FormData, FormParserFactory} -abstract class ParamReader[T] extends Router.ArgReader[Unit, T, cask.model.ParamContext]{ +abstract class ParamReader[T] extends Router.ArgReader[Unit, T, cask.model.Request]{ def arity: Int - def read(ctx: cask.model.ParamContext, label: String, v: Unit): T + def read(ctx: cask.model.Request, label: String, v: Unit): T } object ParamReader{ - class NilParam[T](f: (ParamContext, String) => T) extends ParamReader[T]{ + class NilParam[T](f: (Request, String) => T) extends ParamReader[T]{ def arity = 0 - def read(ctx: cask.model.ParamContext, label: String, v: Unit): T = f(ctx, label) + def read(ctx: cask.model.Request, label: String, v: Unit): T = f(ctx, label) } implicit object HttpExchangeParam extends NilParam[HttpServerExchange]((ctx, label) => ctx.exchange) implicit object FormDataParam extends NilParam[FormData]((ctx, label) => FormParserFactory.builder().build().createParser(ctx.exchange).parseBlocking() ) + + implicit object RequestParam extends NilParam[Request]((ctx, label) => ctx) + + implicit object CookieParam extends NilParam[Cookie]((ctx, label) => + Cookie.fromUndertow(ctx.exchange.getRequestCookies().get(label)) + ) } diff --git a/cask/src/cask/endpoints/StaticEndpoints.scala b/cask/src/cask/endpoints/StaticEndpoints.scala index 1d66b2c..15eae0d 100644 --- a/cask/src/cask/endpoints/StaticEndpoints.scala +++ b/cask/src/cask/endpoints/StaticEndpoints.scala @@ -1,7 +1,7 @@ package cask.endpoints import cask.main.Endpoint -import cask.model.ParamContext +import cask.model.Request class staticFiles(val path: String) extends Endpoint{ type Output = String @@ -9,8 +9,8 @@ class staticFiles(val path: String) extends Endpoint{ type Input = Seq[String] type InputParser[T] = QueryParamReader[T] override def subpath = true - def wrapFunction(ctx: ParamContext, delegate: Delegate): Returned = { - delegate(Map()).map(t => cask.model.StaticFile(t + "/" + ctx.remaining.mkString("/"))) + def wrapFunction(ctx: Request, delegate: Delegate): Returned = { + delegate(Map()).map(t => cask.model.StaticFile(t + "/" + ctx.remainingPathSegments.mkString("/"))) } def wrapPathSegment(s: String): Input = Seq(s) @@ -22,9 +22,9 @@ class staticResources(val path: String, resourceRoot: ClassLoader = getClass.get type Input = Seq[String] type InputParser[T] = QueryParamReader[T] override def subpath = true - def wrapFunction(ctx: ParamContext, delegate: Delegate): Returned = { + def wrapFunction(ctx: Request, delegate: Delegate): Returned = { delegate(Map()).map(t => - cask.model.StaticResource(t + "/" + ctx.remaining.mkString("/"), resourceRoot) + cask.model.StaticResource(t + "/" + ctx.remainingPathSegments.mkString("/"), resourceRoot) ) } diff --git a/cask/src/cask/endpoints/WebEndpoints.scala b/cask/src/cask/endpoints/WebEndpoints.scala index 41c3113..02aeec4 100644 --- a/cask/src/cask/endpoints/WebEndpoints.scala +++ b/cask/src/cask/endpoints/WebEndpoints.scala @@ -2,7 +2,7 @@ package cask.endpoints import cask.internal.Router import cask.main.{Endpoint, HttpDecorator} -import cask.model.{ParamContext, Response} +import cask.model.{Request, Response} import collection.JavaConverters._ @@ -11,7 +11,7 @@ trait WebEndpoint extends Endpoint with HttpDecorator{ type Output = Response type Input = Seq[String] type InputParser[T] = QueryParamReader[T] - def wrapFunction(ctx: ParamContext, + def wrapFunction(ctx: Request, delegate: Map[String, Input] => Router.Result[Output]): Router.Result[Response] = { delegate( ctx.exchange.getQueryParameters @@ -34,14 +34,14 @@ class put(val path: String, override val subpath: Boolean = false) extends WebEn class route(val path: String, val methods: Seq[String], override val subpath: Boolean = false) extends WebEndpoint abstract class QueryParamReader[T] - extends Router.ArgReader[Seq[String], T, cask.model.ParamContext]{ + extends Router.ArgReader[Seq[String], T, cask.model.Request]{ def arity: Int - def read(ctx: cask.model.ParamContext, label: String, v: Seq[String]): T + def read(ctx: cask.model.Request, label: String, v: Seq[String]): T } object QueryParamReader{ class SimpleParam[T](f: String => T) extends QueryParamReader[T]{ def arity = 1 - def read(ctx: cask.model.ParamContext, label: String, v: Seq[String]): T = f(v.head) + def read(ctx: cask.model.Request, label: String, v: Seq[String]): T = f(v.head) } implicit object StringParam extends SimpleParam[String](x => x) @@ -54,20 +54,20 @@ object QueryParamReader{ implicit object FloatParam extends SimpleParam[Float](_.toFloat) implicit def SeqParam[T: QueryParamReader] = new QueryParamReader[Seq[T]]{ def arity = 1 - def read(ctx: cask.model.ParamContext, label: String, v: Seq[String]): Seq[T] = { + def read(ctx: cask.model.Request, label: String, v: Seq[String]): Seq[T] = { v.map(x => implicitly[QueryParamReader[T]].read(ctx, label, Seq(x))) } } implicit def OptionParam[T: QueryParamReader] = new QueryParamReader[Option[T]]{ def arity = 1 - def read(ctx: cask.model.ParamContext, label: String, v: Seq[String]): Option[T] = { + def read(ctx: cask.model.Request, label: String, v: Seq[String]): Option[T] = { v.headOption.map(x => implicitly[QueryParamReader[T]].read(ctx, label, Seq(x))) } } implicit def paramReader[T: ParamReader] = new QueryParamReader[T] { override def arity = 0 - override def read(ctx: cask.model.ParamContext, label: String, v: Seq[String]) = { + override def read(ctx: cask.model.Request, label: String, v: Seq[String]) = { implicitly[ParamReader[T]].read(ctx, label, v) } } diff --git a/cask/src/cask/endpoints/WebSocketEndpoint.scala b/cask/src/cask/endpoints/WebSocketEndpoint.scala index 89c05b9..8c6bc16 100644 --- a/cask/src/cask/endpoints/WebSocketEndpoint.scala +++ b/cask/src/cask/endpoints/WebSocketEndpoint.scala @@ -1,25 +1,22 @@ package cask.endpoints import cask.internal.Router -import cask.model.{ParamContext, Subpath} +import cask.model.Request import io.undertow.server.HttpServerExchange import io.undertow.websockets.WebSocketConnectionCallback -trait WebsocketParam[T] extends Router.ArgReader[Seq[String], T, cask.model.ParamContext] +trait WebsocketParam[T] extends Router.ArgReader[Seq[String], T, cask.model.Request] object WebsocketParam{ - class NilParam[T](f: (ParamContext, String) => T) extends WebsocketParam[T]{ + class NilParam[T](f: (Request, String) => T) extends WebsocketParam[T]{ def arity = 0 - def read(ctx: ParamContext, label: String, v: Seq[String]): T = f(ctx, label) + def read(ctx: Request, label: String, v: Seq[String]): T = f(ctx, label) } implicit object HttpExchangeParam extends NilParam[HttpServerExchange]( (ctx, label) => ctx.exchange ) - implicit object SubpathParam extends NilParam[Subpath]( - (ctx, label) => new Subpath(ctx.remaining) - ) class SimpleParam[T](f: String => T) extends WebsocketParam[T]{ def arity = 1 - def read(ctx: cask.model.ParamContext, label: String, v: Seq[String]): T = f(v.head) + def read(ctx: cask.model.Request, label: String, v: Seq[String]): T = f(v.head) } implicit object StringParam extends SimpleParam[String](x => x) @@ -44,7 +41,7 @@ class websocket(val path: String, override val subpath: Boolean = false) extends type Input = Seq[String] type InputParser[T] = WebsocketParam[T] type Returned = Router.Result[WebsocketResult] - def wrapFunction(ctx: ParamContext, delegate: Delegate): Returned = delegate(Map()) + def wrapFunction(ctx: Request, delegate: Delegate): Returned = delegate(Map()) def wrapPathSegment(s: String): Input = Seq(s) diff --git a/cask/src/cask/internal/DispatchTrie.scala b/cask/src/cask/internal/DispatchTrie.scala index 57d8d9d..952c39b 100644 --- a/cask/src/cask/internal/DispatchTrie.scala +++ b/cask/src/cask/internal/DispatchTrie.scala @@ -36,15 +36,20 @@ object DispatchTrie{ }else{ DispatchTrie[T]( 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 -> vs.head._3), Map())) - }.toMap + children = continuations.map{ case (k, vs) => (k, construct(index + 1, vs))}.toMap ) } } } +/** + * A simple Trie that can be compiled from a list of endpoints, to allow + * endpoint lookup in O(length-of-request-path) time. Lookup returns the + * [[T]] this trie contains, as well as a map of bound wildcards (path + * segments starting with `:`) and any remaining un-used path segments + * (only when `current._2 == true`, indicating this route allows trailing + * segments) + */ case class DispatchTrie[T](current: Option[(T, Boolean)], children: Map[String, DispatchTrie[T]]){ final def lookup(remainingInput: List[String], diff --git a/cask/src/cask/main/Decorators.scala b/cask/src/cask/main/Decorators.scala index 239cab4..0515232 100644 --- a/cask/src/cask/main/Decorators.scala +++ b/cask/src/cask/main/Decorators.scala @@ -2,7 +2,7 @@ package cask.main import cask.internal.Router import cask.internal.Router.ArgReader -import cask.model.{ParamContext, Response} +import cask.model.{Request, Response} trait Endpoint extends BaseEndpoint with HttpDecorator @@ -44,11 +44,11 @@ trait BaseEndpoint extends BaseDecorator{ trait BaseDecorator{ type Input - type InputParser[T] <: ArgReader[Input, T, ParamContext] + type InputParser[T] <: ArgReader[Input, T, Request] type Output type Delegate = Map[String, Input] => Router.Result[Output] type Returned <: Router.Result[Any] - def wrapFunction(ctx: ParamContext, delegate: Delegate): Returned + def wrapFunction(ctx: Request, delegate: Delegate): Returned def getParamParser[T](implicit p: InputParser[T]) = p } trait HttpDecorator extends BaseDecorator{ @@ -73,10 +73,10 @@ trait Decorator extends HttpDecorator { type InputParser[T] = NoOpParser[Input, T] } -class NoOpParser[Input, T] extends ArgReader[Input, T, ParamContext] { +class NoOpParser[Input, T] extends ArgReader[Input, T, Request] { def arity = 1 - def read(ctx: ParamContext, label: String, input: Input) = input.asInstanceOf[T] + def read(ctx: Request, label: String, input: Input) = input.asInstanceOf[T] } object NoOpParser{ implicit def instance[Input, T] = new NoOpParser[Input, T] diff --git a/cask/src/cask/main/Main.scala b/cask/src/cask/main/Main.scala index 70fe206..46e1a65 100644 --- a/cask/src/cask/main/Main.scala +++ b/cask/src/cask/main/Main.scala @@ -74,7 +74,7 @@ abstract class BaseMain{ case None => writeResponse(exchange, handleNotFound()) case Some(((routes, metadata), extBindings, remaining)) => - val ctx = ParamContext(exchange, remaining) + val ctx = Request(exchange, remaining) def rec(remaining: List[Decorator], bindings: List[Map[String, Any]]): Router.Result[Any] = try { remaining match { @@ -84,7 +84,7 @@ abstract class BaseMain{ case Nil => metadata.endpoint.wrapFunction(ctx, epBindings => metadata.entryPoint - .asInstanceOf[EntryPoint[cask.main.Routes, cask.model.ParamContext]] + .asInstanceOf[EntryPoint[cask.main.Routes, cask.model.Request]] .invoke(routes, ctx, (epBindings ++ extBindings.mapValues(metadata.endpoint.wrapPathSegment)) :: bindings.reverse) .asInstanceOf[Router.Result[Nothing]] ) diff --git a/cask/src/cask/main/Routes.scala b/cask/src/cask/main/Routes.scala index aaec832..1a74b87 100644 --- a/cask/src/cask/main/Routes.scala +++ b/cask/src/cask/main/Routes.scala @@ -1,7 +1,7 @@ package cask.main import cask.internal.Router.EntryPoint -import cask.model.ParamContext +import cask.model.Request import scala.reflect.macros.blackbox.Context import language.experimental.macros @@ -43,7 +43,7 @@ object Routes{ m.asInstanceOf[MethodSymbol], weakTypeOf[T], q"${annotObjectSyms.head}.convertToResultType", - tq"cask.ParamContext", + tq"cask.Request", annotObjectSyms.map(annotObjectSym => q"$annotObjectSym.getParamParser"), annotObjectSyms.map(annotObjectSym => tq"$annotObjectSym.Input") diff --git a/cask/src/cask/model/ParamContext.scala b/cask/src/cask/model/ParamContext.scala deleted file mode 100644 index 43da260..0000000 --- a/cask/src/cask/model/ParamContext.scala +++ /dev/null @@ -1,5 +0,0 @@ -package cask.model - -import io.undertow.server.HttpServerExchange - -case class ParamContext(exchange: HttpServerExchange, remaining: Seq[String]) diff --git a/cask/src/cask/model/Params.scala b/cask/src/cask/model/Params.scala index bd10161..270c190 100644 --- a/cask/src/cask/model/Params.scala +++ b/cask/src/cask/model/Params.scala @@ -2,18 +2,11 @@ package cask.model import java.io.{ByteArrayOutputStream, InputStream} -import cask.endpoints.ParamReader.NilParam import cask.internal.Util import io.undertow.server.HttpServerExchange import io.undertow.server.handlers.CookieImpl -import io.undertow.websockets.spi.WebSocketHttpExchange -class Subpath(val value: Seq[String]) -object Subpath{ - implicit object SubpathParam extends NilParam[Subpath]((ctx, label) => new Subpath(ctx.remaining)) -} - -case class Request(exchange: HttpServerExchange){ +case class Request(exchange: HttpServerExchange, remainingPathSegments: Seq[String]){ import collection.JavaConverters._ lazy val cookies: Map[String, Cookie] = { exchange.getRequestCookies.asScala.mapValues(Cookie.fromUndertow).toMap @@ -33,13 +26,8 @@ case class Request(exchange: HttpServerExchange){ .toMap } } -object Request{ - implicit object RequestParam extends NilParam[Request]((ctx, label) => new Request(ctx.exchange)) -} object Cookie{ - implicit object CookieParam extends NilParam[Cookie]((ctx, label) => - Cookie.fromUndertow(ctx.exchange.getRequestCookies().get(label)) - ) + def fromUndertow(from: io.undertow.server.handlers.Cookie): Cookie = { Cookie( from.getName, diff --git a/cask/src/cask/model/Response.scala b/cask/src/cask/model/Response.scala index 9ac5664..59b44c9 100644 --- a/cask/src/cask/model/Response.scala +++ b/cask/src/cask/model/Response.scala @@ -4,22 +4,24 @@ import java.io.{InputStream, OutputStream, OutputStreamWriter} import cask.internal.Util - -trait Response{ - def data: Response.Data - def statusCode: Int - def headers: Seq[(String, String)] - def cookies: Seq[Cookie] -} +/** + * The basic response returned by a HTTP endpoint. + * + * Note that [[data]] by default can take in a wide range of types: strings, + * bytes, uPickle JSON-convertable types or arbitrary input streams. You can + * also construct your own implementations of `Response.Data`. + */ +case class Response( + data: Response.Data, + statusCode: Int, + headers: Seq[(String, String)], + cookies: Seq[Cookie] +) object Response{ def apply(data: Data, statusCode: Int = 200, headers: Seq[(String, String)] = Nil, - cookies: Seq[Cookie] = Nil) = Simple(data, statusCode, headers, cookies) - case class Simple(data: Data, - statusCode: Int = 200, - headers: Seq[(String, String)] = Nil, - cookies: Seq[Cookie] = Nil) extends Response + cookies: Seq[Cookie] = Nil) = new Response(data, statusCode, headers, cookies) implicit def dataResponse[T](t: T)(implicit c: T => Data) = Response(t) trait Data{ @@ -43,59 +45,33 @@ object Response{ } } } -case class Redirect(url: String) extends Response{ - override def data = "" - - override def statusCode = 301 - - override def headers = Seq("Location" -> url) - - override def cookies = Nil +object Redirect{ + def apply(url: String) = Response("", 301, Seq("Location" -> url), Nil) } -case class Abort(code: Int) extends Response { - override def data = "" - - override def statusCode = code - - override def headers = Nil - - override def cookies = Nil +object Abort{ + def apply(code: Int) = Response("", code, Nil, Nil) } - -case class StaticFile(path: String) extends Response { - val relPath = java.nio.file.Paths.get(path) - val (data0, statusCode0) = - if (java.nio.file.Files.exists(relPath) && java.nio.file.Files.isRegularFile(relPath)){ - (java.nio.file.Files.newInputStream(relPath): Response.Data, 200) - }else{ - ("": Response.Data, 404) - } - override def data = data0 - - override def statusCode = statusCode0 - - override def headers = Nil - - override def cookies = Nil +object StaticFile{ + def apply(path: String) = { + val relPath = java.nio.file.Paths.get(path) + val (data0, statusCode0) = + if (java.nio.file.Files.exists(relPath) && java.nio.file.Files.isRegularFile(relPath)){ + (java.nio.file.Files.newInputStream(relPath): Response.Data, 200) + }else{ + ("": Response.Data, 404) + } + Response(data0, statusCode0, Nil, Nil) + } } - -case class StaticResource(path: String, resourceRoot: ClassLoader) extends Response { - val relPath = java.nio.file.Paths.get(path) - val (data0, statusCode0) = resourceRoot.getResourceAsStream(path) match{ - case null => ("": Response.Data, 404) - case res => (res: Response.Data, 200) +object StaticResource{ + def apply(path: String, resourceRoot: ClassLoader) = { + val relPath = java.nio.file.Paths.get(path) + val (data0, statusCode0) = resourceRoot.getResourceAsStream(path) match{ + case null => ("": Response.Data, 404) + case res => (res: Response.Data, 200) + } + Response(data0, statusCode0, Nil, Nil) } - - override def data = data0 - - override def statusCode = statusCode0 - - override def headers = Nil - - override def cookies = Nil } - - - diff --git a/cask/src/cask/package.scala b/cask/src/cask/package.scala index 06d9738..cd1a8e5 100644 --- a/cask/src/cask/package.scala +++ b/cask/src/cask/package.scala @@ -2,10 +2,10 @@ package object cask { // model type Response = model.Response val Response = model.Response - type Abort = model.Abort val Abort = model.Abort - type Redirect = model.Redirect val Redirect = model.Redirect + val StaticFile = model.StaticFile + val StaticResource = model.StaticResource type FormEntry = model.FormEntry val FormEntry = model.FormEntry type FormValue = model.FormValue @@ -14,12 +14,8 @@ package object cask { val FormFile = model.FormFile type Cookie = model.Cookie val Cookie = model.Cookie - type Subpath = model.Subpath - val Subpath = model.Subpath type Request = model.Request val Request = model.Request - type ParamContext = model.ParamContext - val ParamContext = model.ParamContext // endpoints type websocket = endpoints.websocket diff --git a/cask/test/src/test/cask/FailureTests.scala b/cask/test/src/test/cask/FailureTests.scala index fed56e5..d24d52c 100644 --- a/cask/test/src/test/cask/FailureTests.scala +++ b/cask/test/src/test/cask/FailureTests.scala @@ -1,12 +1,11 @@ package test.cask -import cask.internal.Router -import cask.model.{ParamContext, Response} +import cask.model.Request import utest._ object FailureTests extends TestSuite { class myDecorator extends cask.Decorator { - def wrapFunction(ctx: ParamContext, delegate: Delegate): Returned = { + def wrapFunction(ctx: Request, delegate: Delegate): Returned = { delegate(Map("extra" -> 31337)) } } diff --git a/docs/pages/1 - Cask: a Scala HTTP micro-framework.md b/docs/pages/1 - Cask: a Scala HTTP micro-framework.md index 4ed5841..dd98dd2 100644 --- a/docs/pages/1 - Cask: a Scala HTTP micro-framework.md +++ b/docs/pages/1 - Cask: a Scala HTTP micro-framework.md @@ -81,10 +81,10 @@ via the following coordinates: ```scala // Mill -ivy"com.lihaoyi::cask:0.1.0" +ivy"com.lihaoyi::cask:0.1.1" // SBT -"com.lihaoyi" %% "cask" % "0.1.0" +"com.lihaoyi" %% "cask" % "0.1.1" ``` The `./cask` command is just a wrapper around the @@ -233,7 +233,7 @@ You can write extra decorator annotations that stack on top of the existing done by implementing the `cask.Decorator` interface and it's `getRawParams` function. `getRawParams`: -- Receives a `ParamContext`, which basically gives you full access to the +- Receives a `Request`, which basically gives you full access to the underlying undertow HTTP connection so you can pick out whatever data you would like diff --git a/example/compress/build.sc b/example/compress/build.sc index 2166763..a08b2c6 100644 --- a/example/compress/build.sc +++ b/example/compress/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/compress2/build.sc b/example/compress2/build.sc index 2166763..a08b2c6 100644 --- a/example/compress2/build.sc +++ b/example/compress2/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/compress3/build.sc b/example/compress3/build.sc index 2166763..a08b2c6 100644 --- a/example/compress3/build.sc +++ b/example/compress3/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/cookies/build.sc b/example/cookies/build.sc index 2166763..a08b2c6 100644 --- a/example/cookies/build.sc +++ b/example/cookies/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/decorated/app/src/Decorated.scala b/example/decorated/app/src/Decorated.scala index 77f9133..f037c6a 100644 --- a/example/decorated/app/src/Decorated.scala +++ b/example/decorated/app/src/Decorated.scala @@ -4,12 +4,12 @@ object Decorated extends cask.MainRoutes{ override def toString = "[haoyi]" } class loggedIn extends cask.Decorator { - def wrapFunction(ctx: cask.ParamContext, delegate: Delegate): Returned = { + def wrapFunction(ctx: cask.Request, delegate: Delegate): Returned = { delegate(Map("user" -> new User())) } } class withExtra extends cask.Decorator { - def wrapFunction(ctx: cask.ParamContext, delegate: Delegate): Returned = { + def wrapFunction(ctx: cask.Request, delegate: Delegate): Returned = { delegate(Map("extra" -> 31337)) } } diff --git a/example/decorated/build.sc b/example/decorated/build.sc index 2166763..a08b2c6 100644 --- a/example/decorated/build.sc +++ b/example/decorated/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/decorated2/app/src/Decorated2.scala b/example/decorated2/app/src/Decorated2.scala index 014965e..20526b7 100644 --- a/example/decorated2/app/src/Decorated2.scala +++ b/example/decorated2/app/src/Decorated2.scala @@ -4,12 +4,12 @@ object Decorated2 extends cask.MainRoutes{ override def toString = "[haoyi]" } class loggedIn extends cask.Decorator { - def wrapFunction(ctx: cask.ParamContext, delegate: Delegate): Returned = { + def wrapFunction(ctx: cask.Request, delegate: Delegate): Returned = { delegate(Map("user" -> new User())) } } class withExtra extends cask.Decorator { - def wrapFunction(ctx: cask.ParamContext, delegate: Delegate): Returned = { + def wrapFunction(ctx: cask.Request, delegate: Delegate): Returned = { delegate(Map("extra" -> 31337)) } } diff --git a/example/decorated2/build.sc b/example/decorated2/build.sc index 2166763..a08b2c6 100644 --- a/example/decorated2/build.sc +++ b/example/decorated2/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/endpoints/app/src/Endpoints.scala b/example/endpoints/app/src/Endpoints.scala index 5450029..b478c80 100644 --- a/example/endpoints/app/src/Endpoints.scala +++ b/example/endpoints/app/src/Endpoints.scala @@ -2,7 +2,7 @@ package app class custom(val path: String, val methods: Seq[String]) extends cask.Endpoint{ type Output = Int - def wrapFunction(ctx: cask.ParamContext, delegate: Delegate): Returned = { + def wrapFunction(ctx: cask.Request, delegate: Delegate): Returned = { delegate(Map()).map{num => cask.Response("Echo " + num, statusCode = num) } diff --git a/example/endpoints/build.sc b/example/endpoints/build.sc index 2166763..a08b2c6 100644 --- a/example/endpoints/build.sc +++ b/example/endpoints/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/formJsonPost/build.sc b/example/formJsonPost/build.sc index 2166763..a08b2c6 100644 --- a/example/formJsonPost/build.sc +++ b/example/formJsonPost/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/httpMethods/build.sc b/example/httpMethods/build.sc index 2166763..a08b2c6 100644 --- a/example/httpMethods/build.sc +++ b/example/httpMethods/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/minimalApplication/build.sc b/example/minimalApplication/build.sc index 2166763..a08b2c6 100644 --- a/example/minimalApplication/build.sc +++ b/example/minimalApplication/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/minimalApplication2/build.sc b/example/minimalApplication2/build.sc index 2166763..a08b2c6 100644 --- a/example/minimalApplication2/build.sc +++ b/example/minimalApplication2/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/redirectAbort/build.sc b/example/redirectAbort/build.sc index 2166763..a08b2c6 100644 --- a/example/redirectAbort/build.sc +++ b/example/redirectAbort/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/scalatags/build.sc b/example/scalatags/build.sc index d517c6b..86141e0 100644 --- a/example/scalatags/build.sc +++ b/example/scalatags/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ivy"com.lihaoyi::scalatags:0.6.7", ) diff --git a/example/staticFiles/build.sc b/example/staticFiles/build.sc index 00a7ee3..54d1fb5 100644 --- a/example/staticFiles/build.sc +++ b/example/staticFiles/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) def forkWorkingDir = build.millSourcePath diff --git a/example/todo/app/src/TodoServer.scala b/example/todo/app/src/TodoServer.scala index 1a768d1..4adb55c 100644 --- a/example/todo/app/src/TodoServer.scala +++ b/example/todo/app/src/TodoServer.scala @@ -17,7 +17,7 @@ object TodoServer extends cask.MainRoutes{ class transactional extends cask.Decorator{ class TransactionFailed(val value: Router.Result.Error) extends Exception - def wrapFunction(pctx: cask.ParamContext, delegate: Delegate): Returned = { + def wrapFunction(pctx: cask.Request, delegate: Delegate): Returned = { try ctx.transaction( delegate(Map()) match{ case Router.Result.Success(t) => Router.Result.Success(t) diff --git a/example/todo/build.sc b/example/todo/build.sc index ca0716a..0f01a14 100644 --- a/example/todo/build.sc +++ b/example/todo/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ivy"org.xerial:sqlite-jdbc:3.18.0", ivy"io.getquill::quill-jdbc:2.5.4", ivy"com.lihaoyi::scalatags:0.6.7", diff --git a/example/todoApi/build.sc b/example/todoApi/build.sc index 2166763..a08b2c6 100644 --- a/example/todoApi/build.sc +++ b/example/todoApi/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/todoDb/app/src/TodoMvcDb.scala b/example/todoDb/app/src/TodoMvcDb.scala index c566b60..7ce3c50 100644 --- a/example/todoDb/app/src/TodoMvcDb.scala +++ b/example/todoDb/app/src/TodoMvcDb.scala @@ -16,7 +16,7 @@ object TodoMvcDb extends cask.MainRoutes{ class transactional extends cask.Decorator{ class TransactionFailed(val value: Router.Result.Error) extends Exception - def wrapFunction(pctx: cask.ParamContext, delegate: Delegate): Returned = { + def wrapFunction(pctx: cask.Request, delegate: Delegate): Returned = { try ctx.transaction( delegate(Map()) match{ case Router.Result.Success(t) => Router.Result.Success(t) diff --git a/example/todoDb/build.sc b/example/todoDb/build.sc index 046e2e1..5bfc599 100644 --- a/example/todoDb/build.sc +++ b/example/todoDb/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ivy"org.xerial:sqlite-jdbc:3.18.0", ivy"io.getquill::quill-jdbc:2.5.4" ) diff --git a/example/variableRoutes/app/src/VariableRoutes.scala b/example/variableRoutes/app/src/VariableRoutes.scala index 760ab15..a1c8a4f 100644 --- a/example/variableRoutes/app/src/VariableRoutes.scala +++ b/example/variableRoutes/app/src/VariableRoutes.scala @@ -11,8 +11,8 @@ object VariableRoutes extends cask.MainRoutes{ } @cask.get("/path", subpath = true) - def showSubpath(subPath: cask.Subpath) = { - s"Subpath ${subPath.value}" + def showSubpath(request: cask.Request) = { + s"Subpath ${request.remainingPathSegments}" } initialize() diff --git a/example/variableRoutes/build.sc b/example/variableRoutes/build.sc index 2166763..a08b2c6 100644 --- a/example/variableRoutes/build.sc +++ b/example/variableRoutes/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ diff --git a/example/websockets/build.sc b/example/websockets/build.sc index 96991eb..aa9fa17 100644 --- a/example/websockets/build.sc +++ b/example/websockets/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends ScalaModule{ def scalaVersion = "2.12.6" def ivyDeps = Agg( - ivy"com.lihaoyi::cask:0.1.0", + ivy"com.lihaoyi::cask:0.1.1", ) object test extends Tests{ -- cgit v1.2.3