diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2018-07-25 16:17:50 +0800 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2018-07-25 16:30:07 +0800 |
commit | c4476471f4ff4b38c518b5478996bc178a129e6b (patch) | |
tree | 63078ea3da9b866bd1edab7a6a4db2a9c9f13ab1 | |
parent | 603dfa8946f8c78580568613cd268ad05c6c38f6 (diff) | |
download | cask-c4476471f4ff4b38c518b5478996bc178a129e6b.tar.gz cask-c4476471f4ff4b38c518b5478996bc178a129e6b.tar.bz2 cask-c4476471f4ff4b38c518b5478996bc178a129e6b.zip |
Split up `cask` package into subpackages
21 files changed, 564 insertions, 478 deletions
diff --git a/cask/src/cask/Endpoints.scala b/cask/src/cask/Endpoints.scala deleted file mode 100644 index b0bc207..0000000 --- a/cask/src/cask/Endpoints.scala +++ /dev/null @@ -1,71 +0,0 @@ -package cask - -import cask.Router.EntryPoint -import io.undertow.server.HttpServerExchange - -import collection.JavaConverters._ - -trait Endpoint[R]{ - type InputType - val path: String - def subpath: Boolean = false - def wrapMethodOutput(t: R): Any - def handle(exchange: HttpServerExchange, - remaining: Seq[String], - bindings: Map[String, String], - routes: Routes, - entryPoint: EntryPoint[InputType, Routes, (HttpServerExchange, Seq[String])]): Router.Result[BaseResponse] -} -trait WebEndpoint extends Endpoint[Response]{ - type InputType = Seq[String] - def wrapMethodOutput(t: Response) = t - def parseMethodInput[T](implicit p: QueryParamReader[T]) = p - def handle(exchange: HttpServerExchange, - remaining: Seq[String], - bindings: Map[String, String], - routes: Routes, - entryPoint: EntryPoint[Seq[String], Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response] = { - val allBindings = - bindings.map{case (k, v) => (k, Seq(v))} ++ - exchange.getQueryParameters - .asScala - .toSeq - .map{case (k, vs) => (k, vs.asScala.toSeq)} - - entryPoint.invoke(routes, (exchange, remaining), allBindings) - .asInstanceOf[Router.Result[Response]] - } -} -class get(val path: String, override val subpath: Boolean = false) extends WebEndpoint -class post(val path: String, override val subpath: Boolean = false) extends WebEndpoint -class put(val path: String, override val subpath: Boolean = false) extends WebEndpoint -class route(val path: String, val methods: Seq[String], override val subpath: Boolean = false) extends WebEndpoint -class static(val path: String) extends Endpoint[String] { - type InputType = Seq[String] - override def subpath = true - def wrapOutput(t: String) = t - def parseMethodInput[T](implicit p: QueryParamReader[T]) = p - def wrapMethodOutput(t: String) = t - - def handle(exchange: HttpServerExchange, - remaining: Seq[String], - bindings: Map[String, String], - routes: Routes, - entryPoint: EntryPoint[Seq[String], Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response] = { - entryPoint.invoke(routes, (exchange, remaining), Map()).asInstanceOf[Router.Result[String]] match{ - case Router.Result.Success(s) => - val relPath = java.nio.file.Paths.get( - s + "/" + remaining.mkString("/") - ) - 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 => e - - } - - } -}
\ No newline at end of file diff --git a/cask/src/cask/Status.scala b/cask/src/cask/Status.scala deleted file mode 100644 index f3f5434..0000000 --- a/cask/src/cask/Status.scala +++ /dev/null @@ -1,286 +0,0 @@ -package cask - -sealed trait Status { - val code: Int - val reason: String -} - -object Status { - val codesToStatus: Map[Int, Status] = Map( - 100 -> Continue, - 101 -> SwitchingProtocols, - 200 -> OK, - 201 -> Created, - 202 -> Accepted, - 203 -> NonAuthoritativeInformation, - 204 -> NoContent, - 205 -> ResetContent, - 206 -> PartialContent, - 300 -> MultipleChoices, - 301 -> MovedPermanently, - 302 -> Found, - 303 -> SeeOther, - 304 -> NotModified, - 305 -> UseProxy, - 307 -> TemporaryRedirect, - 308 -> PermanentRedirect, - 400 -> BadRequest, - 401 -> Unauthorized, - 402 -> PaymentRequired, - 403 -> Forbidden, - 404 -> NotFound, - 405 -> MethodNotAllowed, - 406 -> NotAcceptable, - 407 -> ProxyAuthenticationRequired, - 408 -> RequestTimeout, - 409 -> Conflict, - 410 -> Gone, - 411 -> LengthRequired, - 412 -> PreconditionFailed, - 413 -> RequestEntityTooLarge, - 414 -> RequestURITooLong, - 415 -> UnsupportedMediaType, - 416 -> RequestedRangeNotSatisfiable, - 417 -> ExpectationFailed, - 418 -> Teapot, - 420 -> EnhanceYourCalm, - 429 -> TooManyRequests, - 451 -> UnavailableForLegalReasons, - 500 -> InternalServerError, - 501 -> NotImplemented, - 502 -> BadGateway, - 503 -> ServiceUnavailable, - 504 -> GatewayTimeout, - 505 -> HTTPVersionNotSupported - ) - - val statusToCodes: Map[String, Int] = - codesToStatus.map { case (code, status) => status.reason -> code } -} - -case class Unknown(code: Int, reason: String) extends Status - -case object Continue extends Status { - val code = 100 - val reason: String = "Continue" -} - -case object SwitchingProtocols extends Status { - val code = 101 - val reason: String = "Switching Protocols" -} - -case object OK extends Status { - val code = 200 - val reason: String = "OK" -} - -case object Created extends Status { - val code = 201 - val reason: String = "Created" -} - -case object Accepted extends Status { - val code = 202 - val reason: String = "Accepted" -} - -case object NonAuthoritativeInformation extends Status { - val code = 203 - val reason: String = "Non-Authoritative Information" -} - -case object NoContent extends Status { - val code = 204 - val reason: String = "No Content" -} - -case object ResetContent extends Status { - val code = 205 - val reason: String = "Reset Content" -} - -case object PartialContent extends Status { - val code = 206 - val reason: String = "Partial Content" -} - -case object MultipleChoices extends Status { - val code = 300 - val reason: String = "Multiple Choices" -} - -case object MovedPermanently extends Status { - val code = 301 - val reason: String = "Moved Permanently" -} - -case object Found extends Status { - val code = 302 - val reason: String = "Found" -} - -case object SeeOther extends Status { - val code = 303 - val reason: String = "See Other" -} - -case object NotModified extends Status { - val code = 304 - val reason: String = "Not Modified" -} - -case object UseProxy extends Status { - val code = 305 - val reason: String = "Use Proxy" -} - -case object TemporaryRedirect extends Status { - val code = 307 - val reason: String = "Temporary Redirect" -} - -case object PermanentRedirect extends Status { - val code = 308 - val reason: String = "Permanent Redirect" -} - -case object BadRequest extends Status { - val code = 400 - val reason: String = "Bad Request" -} - -case object Unauthorized extends Status { - val code = 401 - val reason: String = "Unauthorized" -} - -case object PaymentRequired extends Status { - val code = 402 - val reason: String = "Payment Required" -} - -case object Forbidden extends Status { - val code = 403 - val reason: String = "Forbidden" -} - -case object NotFound extends Status { - val code = 404 - val reason: String = "Not Found" -} - -case object MethodNotAllowed extends Status { - val code = 405 - val reason: String = "Method Not Allowed" -} - -case object NotAcceptable extends Status { - val code = 406 - val reason: String = "Not Acceptable" -} - -case object ProxyAuthenticationRequired extends Status { - val code = 407 - val reason: String = "Proxy Authentication Required" -} - -case object RequestTimeout extends Status { - val code = 408 - val reason: String = "Request Time-out" -} - -case object Conflict extends Status { - val code = 409 - val reason: String = "Conflict" -} - -case object Gone extends Status { - val code = 410 - val reason: String = "Gone" -} - -case object LengthRequired extends Status { - val code = 411 - val reason: String = "Length Required" -} - -case object PreconditionFailed extends Status { - val code = 412 - val reason: String = "Precondition Failed" -} - -case object RequestEntityTooLarge extends Status { - val code = 413 - val reason: String = "Request Entity Too Large" -} - -case object RequestURITooLong extends Status { - val code = 414 - val reason: String = "Request-URI Too Large" -} - -case object UnsupportedMediaType extends Status { - val code = 415 - val reason: String = "Unsupported Media Type" -} - -case object RequestedRangeNotSatisfiable extends Status { - val code = 416 - val reason: String = "Requested range not satisfiable" -} - -case object ExpectationFailed extends Status { - val code = 417 - val reason: String = "Expectation Failed" -} - -case object Teapot extends Status { - val code = 418 - val reason: String = "I'm a teapot" -} - -case object EnhanceYourCalm extends Status { - val code = 420 - val reason: String = "Enhance Your Calm" -} - -case object TooManyRequests extends Status { - val code = 429 - val reason: String = "Too Many Requests" -} - -case object UnavailableForLegalReasons extends Status { - val code = 451 - val reason: String = "Unavailable For Legal Reasons" -} - -case object InternalServerError extends Status { - val code = 500 - val reason: String = "Internal Server Error" -} - -case object NotImplemented extends Status { - val code = 501 - val reason: String = "Not Implemented" -} - -case object BadGateway extends Status { - val code = 502 - val reason: String = "Bad Gateway" -} - -case object ServiceUnavailable extends Status { - val code = 503 - val reason: String = "Service Unavailable" -} - -case object GatewayTimeout extends Status { - val code = 504 - val reason: String = "Gateway Time-out" -} - -case object HTTPVersionNotSupported extends Status { - val code = 505 - val reason: String = "HTTP Version not supported" -} diff --git a/cask/src/cask/endpoints/Endpoint.scala b/cask/src/cask/endpoints/Endpoint.scala new file mode 100644 index 0000000..5d69faa --- /dev/null +++ b/cask/src/cask/endpoints/Endpoint.scala @@ -0,0 +1,19 @@ +package cask.endpoints + +import cask.internal.Router +import cask.internal.Router.EntryPoint +import cask.main.Routes +import cask.model.BaseResponse +import io.undertow.server.HttpServerExchange + +trait Endpoint[R]{ + type InputType + val path: String + def subpath: Boolean = false + def wrapMethodOutput(t: R): Any + def handle(exchange: HttpServerExchange, + remaining: Seq[String], + bindings: Map[String, String], + routes: Routes, + entryPoint: EntryPoint[InputType, Routes, (HttpServerExchange, Seq[String])]): Router.Result[BaseResponse] +}
\ No newline at end of file diff --git a/cask/src/cask/FormEndpoint.scala b/cask/src/cask/endpoints/FormEndpoint.scala index 0d6f2f2..29265b9 100644 --- a/cask/src/cask/FormEndpoint.scala +++ b/cask/src/cask/endpoints/FormEndpoint.scala @@ -1,8 +1,12 @@ -package cask +package cask.endpoints -import cask.Router.EntryPoint +import cask.internal.Router.EntryPoint +import cask.internal.Router +import cask.main.Routes +import cask.model.{FormValue, Response} import io.undertow.server.HttpServerExchange import io.undertow.server.handlers.form.FormParserFactory + import collection.JavaConverters._ sealed trait FormReader[T] extends Router.ArgReader[Seq[FormValue], T, (HttpServerExchange, Seq[String])] diff --git a/cask/src/cask/JsonEndpoint.scala b/cask/src/cask/endpoints/JsonEndpoint.scala index 58fd596..ca66f67 100644 --- a/cask/src/cask/JsonEndpoint.scala +++ b/cask/src/cask/endpoints/JsonEndpoint.scala @@ -1,6 +1,9 @@ -package cask +package cask.endpoints -import cask.Router.EntryPoint +import cask.internal.Router +import cask.internal.Router.EntryPoint +import cask.main.Routes +import cask.model.Response import io.undertow.server.HttpServerExchange diff --git a/cask/src/cask/endpoints/ParamReader.scala b/cask/src/cask/endpoints/ParamReader.scala new file mode 100644 index 0000000..a9d9a7f --- /dev/null +++ b/cask/src/cask/endpoints/ParamReader.scala @@ -0,0 +1,27 @@ +package cask.endpoints + +import cask.Cookie +import cask.internal.Router +import io.undertow.server.HttpServerExchange +import io.undertow.server.handlers.form.{FormData, FormParserFactory} + +abstract class ParamReader[T] + extends Router.ArgReader[Seq[String], T, (HttpServerExchange, Seq[String])]{ + def arity: Int + def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]): T +} +object ParamReader{ + class NilParam[T](f: (HttpServerExchange, Seq[String]) => T) extends ParamReader[T]{ + def arity = 0 + def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]): T = f(ctx._1, ctx._2) + } + implicit object HttpExchangeParam extends NilParam[HttpServerExchange]((server, remaining) => server) + implicit object SubpathParam extends NilParam[cask.model.Subpath]((server, remaining) => new cask.model.Subpath(remaining)) + implicit object CookieParam extends NilParam[cask.model.Cookies]((server, remaining) => { + import collection.JavaConverters._ + new cask.model.Cookies(server.getRequestCookies.asScala.toMap.map{case (k, v) => (k, Cookie.fromUndertow(v))}) + }) + implicit object FormDataParam extends NilParam[FormData]((server, remaining) => + FormParserFactory.builder().build().createParser(server).parseBlocking() + ) +} diff --git a/cask/src/cask/endpoints/StaticEndpoints.scala b/cask/src/cask/endpoints/StaticEndpoints.scala new file mode 100644 index 0000000..ed964b0 --- /dev/null +++ b/cask/src/cask/endpoints/StaticEndpoints.scala @@ -0,0 +1,30 @@ +package cask.endpoints + +import cask.internal.Router +import cask.internal.Router.EntryPoint +import cask.main.Routes +import cask.model.BaseResponse +import io.undertow.server.HttpServerExchange + +class static(val path: String) extends Endpoint[String] { + type InputType = Seq[String] + override def subpath = true + def wrapOutput(t: String) = t + def parseMethodInput[T](implicit p: QueryParamReader[T]) = p + def wrapMethodOutput(t: String) = t + + def handle(exchange: HttpServerExchange, + remaining: Seq[String], + bindings: Map[String, String], + routes: Routes, + entryPoint: EntryPoint[Seq[String], Routes, (HttpServerExchange, Seq[String])]): Router.Result[BaseResponse] = { + entryPoint.invoke(routes, (exchange, remaining), Map()).asInstanceOf[Router.Result[String]] match{ + case Router.Result.Success(s) => + Router.Result.Success(cask.model.Static(s + "/" + remaining.mkString("/"))) + + case e: Router.Result.Error => e + + } + + } +} diff --git a/cask/src/cask/QueryParamReader.scala b/cask/src/cask/endpoints/WebEndpoints.scala index 1d34b14..af2ed0a 100644 --- a/cask/src/cask/QueryParamReader.scala +++ b/cask/src/cask/endpoints/WebEndpoints.scala @@ -1,29 +1,39 @@ -package cask +package cask.endpoints +import cask.internal.Router +import cask.internal.Router.EntryPoint +import cask.main.Routes +import cask.model.BaseResponse import io.undertow.server.HttpServerExchange -import io.undertow.server.handlers.form.{FormData, FormParserFactory} +import collection.JavaConverters._ -abstract class ParamReader[T] - extends Router.ArgReader[Seq[String], T, (HttpServerExchange, Seq[String])]{ - def arity: Int - def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]): T -} -object ParamReader{ - class NilParam[T](f: (HttpServerExchange, Seq[String]) => T) extends ParamReader[T]{ - def arity = 0 - def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]): T = f(ctx._1, ctx._2) + +trait WebEndpoint extends Endpoint[BaseResponse]{ + type InputType = Seq[String] + def wrapMethodOutput(t: BaseResponse) = t + def parseMethodInput[T](implicit p: QueryParamReader[T]) = p + def handle(exchange: HttpServerExchange, + remaining: Seq[String], + bindings: Map[String, String], + routes: Routes, + entryPoint: EntryPoint[Seq[String], Routes, (HttpServerExchange, Seq[String])]): Router.Result[BaseResponse] = { + val allBindings = + bindings.map{case (k, v) => (k, Seq(v))} ++ + exchange.getQueryParameters + .asScala + .toSeq + .map{case (k, vs) => (k, vs.asScala.toArray.toSeq)} + + entryPoint.invoke(routes, (exchange, remaining), allBindings) + .asInstanceOf[Router.Result[BaseResponse]] } - implicit object HttpExchangeParam extends NilParam[HttpServerExchange]((server, remaining) => server) - implicit object SubpathParam extends NilParam[Subpath]((server, remaining) => new Subpath(remaining)) - implicit object CookieParam extends NilParam[Cookies]((server, remaining) => { - import collection.JavaConverters._ - new Cookies(server.getRequestCookies.asScala.toMap.map{case (k, v) => (k, Cookie.fromUndertow(v))}) - }) - implicit object FormDataParam extends NilParam[FormData]((server, remaining) => - FormParserFactory.builder().build().createParser(server).parseBlocking() - ) } +class get(val path: String, override val subpath: Boolean = false) extends WebEndpoint +class post(val path: String, override val subpath: Boolean = false) extends WebEndpoint +class put(val path: String, override val subpath: Boolean = false) extends WebEndpoint +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, (HttpServerExchange, Seq[String])]{ def arity: Int @@ -58,6 +68,3 @@ object QueryParamReader{ } } - -class Subpath(val value: Seq[String]) -class Cookies(val value: Map[String, Cookie])
\ No newline at end of file diff --git a/cask/src/cask/DispatchTrie.scala b/cask/src/cask/internal/DispatchTrie.scala index d986c8b..57d8d9d 100644 --- a/cask/src/cask/DispatchTrie.scala +++ b/cask/src/cask/internal/DispatchTrie.scala @@ -1,4 +1,4 @@ -package cask +package cask.internal import collection.mutable object DispatchTrie{ def construct[T](index: Int, @@ -21,7 +21,7 @@ object DispatchTrie{ if (terminals.length > 1){ throw new Exception( "More than one endpoint has the same path: " + - terminals.map(_._1.map(_.mkString("/"))).mkString(", ") + terminals.map(_._1.map(_.mkString("/"))).mkString(", ") ) } else if(wildcards.size >= 1 && continuations.size > 1) { throw new Exception( @@ -49,7 +49,7 @@ case class DispatchTrie[T](current: Option[(T, Boolean)], children: Map[String, DispatchTrie[T]]){ final def lookup(remainingInput: List[String], bindings: Map[String, String]) - : Option[(T, Map[String, String], Seq[String])] = { + : Option[(T, Map[String, String], Seq[String])] = { remainingInput match{ case Nil => current.map(x => (x._1, bindings, Nil)) diff --git a/cask/src/cask/Router.scala b/cask/src/cask/internal/Router.scala index 90d5245..f6a4405 100644 --- a/cask/src/cask/Router.scala +++ b/cask/src/cask/internal/Router.scala @@ -1,4 +1,4 @@ -package cask +package cask.internal import io.undertow.server.HttpServerExchange @@ -67,10 +67,10 @@ object Router{ } def read[I, C] - (dict: Map[String, I], - default: => Option[Any], - arg: ArgSig[I, _, _, C], - thunk: I => Any): FailMaybe = { + (dict: Map[String, I], + default: => Option[Any], + arg: ArgSig[I, _, _, C], + thunk: I => Any): FailMaybe = { arg.reads.arity match{ case 0 => tryEither( @@ -160,10 +160,10 @@ object Router{ } def makeReadCall[I, C] - (dict: Map[String, I], - ctx: C, - default: => Option[Any], - arg: ArgSig[I, _, _, C]) = { + (dict: Map[String, I], + ctx: C, + default: => Option[Any], + arg: ArgSig[I, _, _, C]) = { read[I, C](dict, default, arg, arg.reads.read(ctx, _)) } @@ -267,7 +267,7 @@ class Router[C <: Context](val c: C) { } val argSig = q""" - cask.Router.ArgSig[$annotDeserializeType, $curCls, $docUnwrappedType, $ctx]( + cask.internal.Router.ArgSig[$annotDeserializeType, $curCls, $docUnwrappedType, $ctx]( ${arg.name.toString}, ${docUnwrappedType.toString + (if(vararg) "*" else "")}, $docTree, @@ -278,7 +278,7 @@ class Router[C <: Context](val c: C) { val reader = if(vararg) c.abort(meth.pos, "Varargs are not supported in cask routes") else q""" - cask.Router.makeReadCall( + cask.internal.Router.makeReadCall( $argListSymbol, ctx, $default, @@ -305,23 +305,23 @@ class Router[C <: Context](val c: C) { if (meth.paramLists.isEmpty) q"$baseArgSym.${meth.name.toTermName}" else q"$baseArgSym.${meth.name.toTermName}(..$argNameCasts)" val res = q""" - cask.Router.EntryPoint[$annotDeserializeType, $curCls, $ctx]( + cask.internal.Router.EntryPoint[$annotDeserializeType, $curCls, $ctx]( ${meth.name.toString}, scala.Seq(..$argSigs), ${methodDoc match{ - case None => q"scala.None" - case Some(s) => q"scala.Some($s)" - }}, + case None => q"scala.None" + case Some(s) => q"scala.Some($s)" + }}, ${varargs.contains(true)}, ($baseArgSym: $curCls, ctx: $ctx, $argListSymbol: Map[String, $annotDeserializeType]) => - cask.Router.validate(Seq(..$readArgs)) match{ - case cask.Router.Result.Success(Seq(..$argNames)) => - cask.Router.Result.Success( + cask.internal.Router.validate(Seq(..$readArgs)) match{ + case cask.internal.Router.Result.Success(Seq(..$argNames)) => + cask.internal.Router.Result.Success( ${wrapOutput(methCall)} ) - case x: cask.Router.Result.Error => x + case x: cask.internal.Router.Result.Error => x } - ).asInstanceOf[cask.Router.EntryPoint[Any, $curCls, $ctx]] + ).asInstanceOf[cask.internal.Router.EntryPoint[Any, $curCls, $ctx]] """ c.internal.transform(res){(t, a) => diff --git a/cask/src/cask/Util.scala b/cask/src/cask/internal/Util.scala index 8614639..84c8d52 100644 --- a/cask/src/cask/Util.scala +++ b/cask/src/cask/internal/Util.scala @@ -1,4 +1,4 @@ -package cask +package cask.internal object Util { def splitPath(p: String) = diff --git a/cask/src/cask/Main.scala b/cask/src/cask/main/Main.scala index 188c24c..77bac94 100644 --- a/cask/src/cask/Main.scala +++ b/cask/src/cask/main/Main.scala @@ -1,13 +1,14 @@ -package cask -import cask.Router.EntryPoint -import java.io.OutputStream -import java.nio.ByteBuffer +package cask.main -import cask.Routes.RoutesEndpointsMetadata +import cask.model.{BaseResponse, Response, Status} +import cask.Cookie +import cask.internal.Router.EntryPoint +import cask.internal.{DispatchTrie, Router, Util} import io.undertow.Undertow -import io.undertow.server.handlers.BlockingHandler import io.undertow.server.{HttpHandler, HttpServerExchange} -import io.undertow.util.{Headers, HttpString} +import io.undertow.server.handlers.BlockingHandler +import io.undertow.util.HttpString + class MainRoutes extends BaseMain with Routes{ def allRoutes = Seq(this) @@ -27,7 +28,7 @@ abstract class BaseMain{ lazy val routeTrie = DispatchTrie.construct[(Routes, Routes.EndpointMetadata[_])](0, for((route, metadata) <- routeList) - yield (Util.splitPath(metadata.metadata.path): IndexedSeq[String], (route, metadata), metadata.metadata.subpath) + yield (Util.splitPath(metadata.metadata.path): IndexedSeq[String], (route, metadata), metadata.metadata.subpath) ) def handleError(statusCode: Int): Response = { @@ -54,7 +55,7 @@ abstract class BaseMain{ case Some(((routes, metadata), bindings, remaining)) => val result = metadata.metadata.handle( exchange, remaining, bindings, routes, - metadata.entryPoint.asInstanceOf[EntryPoint[metadata.metadata.InputType, cask.Routes, (HttpServerExchange, Seq[String])]] + metadata.entryPoint.asInstanceOf[EntryPoint[metadata.metadata.InputType, cask.main.Routes, (HttpServerExchange, Seq[String])]] ) result match{ diff --git a/cask/src/cask/Routes.scala b/cask/src/cask/main/Routes.scala index e539312..fdd39bd 100644 --- a/cask/src/cask/Routes.scala +++ b/cask/src/cask/main/Routes.scala @@ -1,51 +1,11 @@ -package cask -import language.experimental.macros -import java.io.{InputStream, OutputStream, OutputStreamWriter, StringWriter} +package cask.main -import cask.Router.EntryPoint +import cask.endpoints.Endpoint +import cask.internal.Router.EntryPoint import io.undertow.server.HttpServerExchange import scala.reflect.macros.blackbox.Context - -case class Request(cookies: Map[String, Cookie], - data: InputStream, - queryParams: Map[String, Seq[String]], - headers: Map[String, Seq[String]]) - -case class Response(data: Response.Data, - statusCode: Int = 200, - headers: Seq[(String, String)] = Nil, - cookies: Seq[Cookie] = Nil) extends BaseResponse - -trait BaseResponse{ - def data: Response.Data - def statusCode: Int - def headers: Seq[(String, String)] - def cookies: Seq[Cookie] -} -object Response{ - implicit def dataResponse[T](t: T)(implicit c: T => Data) = Response(t) - trait Data{ - def write(out: OutputStream): Unit - } - object Data{ - implicit class StringData(s: String) extends Data{ - def write(out: OutputStream) = out.write(s.getBytes) - } - implicit class BytesData(b: Array[Byte]) extends Data{ - def write(out: OutputStream) = out.write(b) - } - implicit class StreamData(b: InputStream) extends Data{ - def write(out: OutputStream) = b.transferTo(out) - } - implicit def JsonResponse[T: upickle.default.Writer](t: T) = new Data{ - def write(out: OutputStream) = implicitly[upickle.default.Writer[T]].write( - new ujson.BaseRenderer(new OutputStreamWriter(out)), - t - ) - } - } -} +import language.experimental.macros object Routes{ case class EndpointMetadata[T](metadata: Endpoint[_], @@ -55,7 +15,7 @@ object Routes{ implicit def initialize[T] = macro initializeImpl[T] implicit def initializeImpl[T: c.WeakTypeTag](c: Context): c.Expr[RoutesEndpointsMetadata[T]] = { import c.universe._ - val router = new cask.Router[c.type](c) + val router = new cask.internal.Router[c.type](c) val routeParts = for{ m <- c.weakTypeOf[T].members @@ -75,14 +35,14 @@ object Routes{ q"""{ val $annotObjectSym = $annotObject - cask.Routes.EndpointMetadata( + cask.main.Routes.EndpointMetadata( $annotObjectSym, $route ) }""" } - c.Expr[RoutesEndpointsMetadata[T]](q"""cask.Routes.RoutesEndpointsMetadata(..$routeParts)""") + c.Expr[RoutesEndpointsMetadata[T]](q"""cask.main.Routes.RoutesEndpointsMetadata(..$routeParts)""") } } } diff --git a/cask/src/cask/Cookie.scala b/cask/src/cask/model/Cookie.scala index 8d1d239..119ea66 100644 --- a/cask/src/cask/Cookie.scala +++ b/cask/src/cask/model/Cookie.scala @@ -1,4 +1,4 @@ -package cask +package cask.model import io.undertow.server.handlers.CookieImpl diff --git a/cask/src/cask/FormValue.scala b/cask/src/cask/model/FormValue.scala index 60a2d39..01c75a4 100644 --- a/cask/src/cask/FormValue.scala +++ b/cask/src/cask/model/FormValue.scala @@ -1,4 +1,4 @@ -package cask +package cask.model object FormValue{ def fromUndertow(from: io.undertow.server.handlers.form.FormData.FormValue) = { diff --git a/cask/src/cask/model/Params.scala b/cask/src/cask/model/Params.scala new file mode 100644 index 0000000..f64fd87 --- /dev/null +++ b/cask/src/cask/model/Params.scala @@ -0,0 +1,4 @@ +package cask.model + +class Subpath(val value: Seq[String]) +class Cookies(val value: Map[String, Cookie])
\ No newline at end of file diff --git a/cask/src/cask/model/Response.scala b/cask/src/cask/model/Response.scala new file mode 100644 index 0000000..b3bb22b --- /dev/null +++ b/cask/src/cask/model/Response.scala @@ -0,0 +1,81 @@ +package cask.model + +import java.io.{InputStream, OutputStream, OutputStreamWriter} + + +trait BaseResponse{ + def data: BaseResponse.Data + def statusCode: Int + def headers: Seq[(String, String)] + def cookies: Seq[Cookie] +} +object BaseResponse{ + implicit def dataResponse[T](t: T)(implicit c: T => Data) = Response(t) + trait Data{ + def write(out: OutputStream): Unit + } + object Data{ + implicit class StringData(s: String) extends Data{ + def write(out: OutputStream) = out.write(s.getBytes) + } + implicit class BytesData(b: Array[Byte]) extends Data{ + def write(out: OutputStream) = out.write(b) + } + implicit class StreamData(b: InputStream) extends Data{ + def write(out: OutputStream) = b.transferTo(out) + } + implicit def JsonResponse[T: upickle.default.Writer](t: T) = new Data{ + def write(out: OutputStream) = implicitly[upickle.default.Writer[T]].write( + new ujson.BaseRenderer(new OutputStreamWriter(out)), + t + ) + } + } +} +case class Redirect(url: String) extends BaseResponse{ + override def data = "" + + override def statusCode = 301 + + override def headers = Seq("Location" -> url) + + override def cookies = Nil +} +case class Abort(code: Int) extends BaseResponse { + override def data = "" + + override def statusCode = code + + override def headers = Nil + + override def cookies = Nil +} + +case class Static(path: String) extends BaseResponse { + val relPath = java.nio.file.Paths.get(path) + val (data0: BaseResponse.Data, statusCode0) = + if (java.nio.file.Files.exists(relPath) && java.nio.file.Files.isRegularFile(relPath)){ + (java.nio.file.Files.newInputStream(relPath), 200) + }else{ + ("", 404) + } + override def data = data0 + + override def statusCode = statusCode0 + + override def headers = Nil + + override def cookies = Nil +} + + +case class Request(cookies: Map[String, Cookie], + data: InputStream, + queryParams: Map[String, Seq[String]], + headers: Map[String, Seq[String]]) + +case class Response(data: BaseResponse.Data, + statusCode: Int = 200, + headers: Seq[(String, String)] = Nil, + cookies: Seq[Cookie] = Nil) extends BaseResponse + diff --git a/cask/src/cask/model/Status.scala b/cask/src/cask/model/Status.scala new file mode 100644 index 0000000..26bb60f --- /dev/null +++ b/cask/src/cask/model/Status.scala @@ -0,0 +1,288 @@ +package cask.model + +sealed trait Status { + val code: Int + val reason: String +} + +object Status { + val codesToStatus: Map[Int, Status] = Map( + 100 -> Continue, + 101 -> SwitchingProtocols, + 200 -> OK, + 201 -> Created, + 202 -> Accepted, + 203 -> NonAuthoritativeInformation, + 204 -> NoContent, + 205 -> ResetContent, + 206 -> PartialContent, + 300 -> MultipleChoices, + 301 -> MovedPermanently, + 302 -> Found, + 303 -> SeeOther, + 304 -> NotModified, + 305 -> UseProxy, + 307 -> TemporaryRedirect, + 308 -> PermanentRedirect, + 400 -> BadRequest, + 401 -> Unauthorized, + 402 -> PaymentRequired, + 403 -> Forbidden, + 404 -> NotFound, + 405 -> MethodNotAllowed, + 406 -> NotAcceptable, + 407 -> ProxyAuthenticationRequired, + 408 -> RequestTimeout, + 409 -> Conflict, + 410 -> Gone, + 411 -> LengthRequired, + 412 -> PreconditionFailed, + 413 -> RequestEntityTooLarge, + 414 -> RequestURITooLong, + 415 -> UnsupportedMediaType, + 416 -> RequestedRangeNotSatisfiable, + 417 -> ExpectationFailed, + 418 -> Teapot, + 420 -> EnhanceYourCalm, + 429 -> TooManyRequests, + 451 -> UnavailableForLegalReasons, + 500 -> InternalServerError, + 501 -> NotImplemented, + 502 -> BadGateway, + 503 -> ServiceUnavailable, + 504 -> GatewayTimeout, + 505 -> HTTPVersionNotSupported + ) + + val statusToCodes: Map[String, Int] = + codesToStatus.map { case (code, status) => status.reason -> code } + + + case class Unknown(code: Int, reason: String) extends Status + + case object Continue extends Status { + val code = 100 + val reason: String = "Continue" + } + + case object SwitchingProtocols extends Status { + val code = 101 + val reason: String = "Switching Protocols" + } + + case object OK extends Status { + val code = 200 + val reason: String = "OK" + } + + case object Created extends Status { + val code = 201 + val reason: String = "Created" + } + + case object Accepted extends Status { + val code = 202 + val reason: String = "Accepted" + } + + case object NonAuthoritativeInformation extends Status { + val code = 203 + val reason: String = "Non-Authoritative Information" + } + + case object NoContent extends Status { + val code = 204 + val reason: String = "No Content" + } + + case object ResetContent extends Status { + val code = 205 + val reason: String = "Reset Content" + } + + case object PartialContent extends Status { + val code = 206 + val reason: String = "Partial Content" + } + + case object MultipleChoices extends Status { + val code = 300 + val reason: String = "Multiple Choices" + } + + case object MovedPermanently extends Status { + val code = 301 + val reason: String = "Moved Permanently" + } + + case object Found extends Status { + val code = 302 + val reason: String = "Found" + } + + case object SeeOther extends Status { + val code = 303 + val reason: String = "See Other" + } + + case object NotModified extends Status { + val code = 304 + val reason: String = "Not Modified" + } + + case object UseProxy extends Status { + val code = 305 + val reason: String = "Use Proxy" + } + + case object TemporaryRedirect extends Status { + val code = 307 + val reason: String = "Temporary Redirect" + } + + case object PermanentRedirect extends Status { + val code = 308 + val reason: String = "Permanent Redirect" + } + + case object BadRequest extends Status { + val code = 400 + val reason: String = "Bad Request" + } + + case object Unauthorized extends Status { + val code = 401 + val reason: String = "Unauthorized" + } + + case object PaymentRequired extends Status { + val code = 402 + val reason: String = "Payment Required" + } + + case object Forbidden extends Status { + val code = 403 + val reason: String = "Forbidden" + } + + case object NotFound extends Status { + val code = 404 + val reason: String = "Not Found" + } + + case object MethodNotAllowed extends Status { + val code = 405 + val reason: String = "Method Not Allowed" + } + + case object NotAcceptable extends Status { + val code = 406 + val reason: String = "Not Acceptable" + } + + case object ProxyAuthenticationRequired extends Status { + val code = 407 + val reason: String = "Proxy Authentication Required" + } + + case object RequestTimeout extends Status { + val code = 408 + val reason: String = "Request Time-out" + } + + case object Conflict extends Status { + val code = 409 + val reason: String = "Conflict" + } + + case object Gone extends Status { + val code = 410 + val reason: String = "Gone" + } + + case object LengthRequired extends Status { + val code = 411 + val reason: String = "Length Required" + } + + case object PreconditionFailed extends Status { + val code = 412 + val reason: String = "Precondition Failed" + } + + case object RequestEntityTooLarge extends Status { + val code = 413 + val reason: String = "Request Entity Too Large" + } + + case object RequestURITooLong extends Status { + val code = 414 + val reason: String = "Request-URI Too Large" + } + + case object UnsupportedMediaType extends Status { + val code = 415 + val reason: String = "Unsupported Media Type" + } + + case object RequestedRangeNotSatisfiable extends Status { + val code = 416 + val reason: String = "Requested range not satisfiable" + } + + case object ExpectationFailed extends Status { + val code = 417 + val reason: String = "Expectation Failed" + } + + case object Teapot extends Status { + val code = 418 + val reason: String = "I'm a teapot" + } + + case object EnhanceYourCalm extends Status { + val code = 420 + val reason: String = "Enhance Your Calm" + } + + case object TooManyRequests extends Status { + val code = 429 + val reason: String = "Too Many Requests" + } + + case object UnavailableForLegalReasons extends Status { + val code = 451 + val reason: String = "Unavailable For Legal Reasons" + } + + case object InternalServerError extends Status { + val code = 500 + val reason: String = "Internal Server Error" + } + + case object NotImplemented extends Status { + val code = 501 + val reason: String = "Not Implemented" + } + + case object BadGateway extends Status { + val code = 502 + val reason: String = "Bad Gateway" + } + + case object ServiceUnavailable extends Status { + val code = 503 + val reason: String = "Service Unavailable" + } + + case object GatewayTimeout extends Status { + val code = 504 + val reason: String = "Gateway Time-out" + } + + case object HTTPVersionNotSupported extends Status { + val code = 505 + val reason: String = "HTTP Version not supported" + } + +} diff --git a/cask/src/cask/package.scala b/cask/src/cask/package.scala index f46dc8d..fddb1e0 100644 --- a/cask/src/cask/package.scala +++ b/cask/src/cask/package.scala @@ -1,11 +1,30 @@ package object cask { - def redirect(url: String) = Response( - "", - 301, - headers = Seq("Location" -> url) - ) - def abort(code: Int) = Response( - "", - code - ) + // 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 + type FormValue = model.FormValue + val FormValue = model.FormValue + type Cookie = model.Cookie + val Cookie = model.Cookie + type Cookies = model.Cookies + type Subpath = model.Subpath + + // endpoints + type get = endpoints.get + type post = endpoints.post + type put = endpoints.put + type route = endpoints.route + type static = endpoints.static + type postJson = endpoints.postJson + type postForm = endpoints.postForm + + // main + type MainRoutes = main.MainRoutes + type Routes = main.Routes + type Main = main.Main + } diff --git a/cask/test/src/test/cask/DispatchTrieTests.scala b/cask/test/src/test/cask/DispatchTrieTests.scala index fdd5f27..068d7d0 100644 --- a/cask/test/src/test/cask/DispatchTrieTests.scala +++ b/cask/test/src/test/cask/DispatchTrieTests.scala @@ -1,5 +1,5 @@ package test.cask -import cask.DispatchTrie +import cask.internal.DispatchTrie import utest._ object DispatchTrieTests extends TestSuite { diff --git a/cask/test/src/test/cask/RedirectAbort.scala b/cask/test/src/test/cask/RedirectAbort.scala index 6a557d6..f2aa811 100644 --- a/cask/test/src/test/cask/RedirectAbort.scala +++ b/cask/test/src/test/cask/RedirectAbort.scala @@ -3,12 +3,12 @@ package test.cask object RedirectAbort extends cask.MainRoutes{ @cask.get("/") def index() = { - cask.redirect("/login") + cask.Redirect("/login") } @cask.get("/login") def login() = { - cask.abort(401) + cask.Abort(401) } initialize() |