diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2018-08-13 03:54:50 +0800 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2018-08-13 03:54:50 +0800 |
commit | 2fc9fd22084bb4a89a72be525c18fc409303ada5 (patch) | |
tree | 511734c255237e99ba0b5b302232073572faaa38 /cask/src/cask/main | |
parent | 790deda0f38e36c7378ff05a9c234a56e14a5d6b (diff) | |
download | cask-2fc9fd22084bb4a89a72be525c18fc409303ada5.tar.gz cask-2fc9fd22084bb4a89a72be525c18fc409303ada5.tar.bz2 cask-2fc9fd22084bb4a89a72be525c18fc409303ada5.zip |
Basic websocket support works
Diffstat (limited to 'cask/src/cask/main')
-rw-r--r-- | cask/src/cask/main/Decorators.scala | 13 | ||||
-rw-r--r-- | cask/src/cask/main/Main.scala | 100 | ||||
-rw-r--r-- | cask/src/cask/main/Routes.scala | 10 |
3 files changed, 84 insertions, 39 deletions
diff --git a/cask/src/cask/main/Decorators.scala b/cask/src/cask/main/Decorators.scala index 73d8c19..239cab4 100644 --- a/cask/src/cask/main/Decorators.scala +++ b/cask/src/cask/main/Decorators.scala @@ -2,13 +2,15 @@ package cask.main import cask.internal.Router import cask.internal.Router.ArgReader -import cask.model.{Response, ParamContext} +import cask.model.{ParamContext, Response} + +trait Endpoint extends BaseEndpoint with HttpDecorator /** * Used to annotate a single Cask endpoint function; similar to a [[Decorator]] * but with additional metadata and capabilities. */ -trait Endpoint extends BaseDecorator{ +trait BaseEndpoint extends BaseDecorator{ /** * What is the path that this particular endpoint matches? */ @@ -45,10 +47,13 @@ trait BaseDecorator{ type InputParser[T] <: ArgReader[Input, T, ParamContext] type Output type Delegate = Map[String, Input] => Router.Result[Output] - type Returned = Router.Result[Response] + type Returned <: Router.Result[Any] def wrapFunction(ctx: ParamContext, delegate: Delegate): Returned def getParamParser[T](implicit p: InputParser[T]) = p } +trait HttpDecorator extends BaseDecorator{ + type Returned = Router.Result[Response] +} /** * A decorator allows you to annotate a function to wrap it, via @@ -61,7 +66,7 @@ trait BaseDecorator{ * to `wrapFunction`, which takes a `Map` representing any additional argument * lists (if any). */ -trait Decorator extends BaseDecorator { +trait Decorator extends HttpDecorator { type Input = Any type Output = Response diff --git a/cask/src/cask/main/Main.scala b/cask/src/cask/main/Main.scala index 87c66c4..94d0e14 100644 --- a/cask/src/cask/main/Main.scala +++ b/cask/src/cask/main/Main.scala @@ -1,5 +1,6 @@ package cask.main +import cask.endpoints.WebsocketResult import cask.model._ import cask.internal.Router.EntryPoint import cask.internal.{DispatchTrie, Router, Util} @@ -27,7 +28,7 @@ abstract class BaseMain{ } yield (routes, route) - lazy val routeTries = Seq("get", "put", "post") + lazy val routeTries = Seq("get", "put", "post", "websocket") .map { method => method -> DispatchTrie.construct[(Routes, Routes.EndpointMetadata[_])](0, for ((route, metadata) <- routeList if metadata.endpoint.methods.contains(method)) @@ -52,42 +53,81 @@ abstract class BaseMain{ ) } + def genericWebsocketHandler(exchange0: HttpServerExchange) = + hello(exchange0, "websocket", ParamContext(exchange0, _), exchange0.getRequestPath).foreach{ r => + r.asInstanceOf[WebsocketResult] match{ + case l: WebsocketResult.Listener => + io.undertow.Handlers.websocket(l.value).handleRequest(exchange0) + case r: WebsocketResult.Response => + writeResponseHandler(r).handleRequest(exchange0) + } + } - def defaultHandler = new BlockingHandler( + def defaultHandler = new HttpHandler() { def handleRequest(exchange: HttpServerExchange): Unit = { - routeTries(exchange.getRequestMethod.toString.toLowerCase()).lookup(Util.splitPath(exchange.getRequestPath).toList, Map()) match{ - case None => writeResponse(exchange, handleNotFound()) - case Some(((routes, metadata), extBindings, remaining)) => - val ctx = ParamContext(exchange, remaining) - def rec(remaining: List[Decorator], - bindings: List[Map[String, Any]]): Router.Result[Response] = try { - remaining match { - case head :: rest => - head.wrapFunction(ctx, args => rec(rest, args :: bindings)) - - case Nil => - metadata.endpoint.wrapFunction(ctx, epBindings => - metadata.entryPoint - .asInstanceOf[EntryPoint[cask.main.Routes, cask.model.ParamContext]] - .invoke(routes, ctx, (epBindings ++ extBindings.mapValues(metadata.endpoint.wrapPathSegment)) :: bindings.reverse) - .asInstanceOf[Router.Result[Nothing]] - ) - - } - // Make sure we wrap any exceptions that bubble up from decorator - // bodies, so outer decorators do not need to worry about their - // delegate throwing on them - }catch{case e: Throwable => Router.Result.Error.Exception(e) } - - rec((metadata.decorators ++ routes.decorators ++ mainDecorators).toList, Nil)match{ - case Router.Result.Success(response: Response) => writeResponse(exchange, response) - case e: Router.Result.Error => writeResponse(exchange, handleEndpointError(exchange, routes, metadata, e)) - } + if (exchange.getRequestHeaders.getFirst("Upgrade") == "websocket") { + + genericWebsocketHandler(exchange) + } else { + defaultHttpHandler.handleRequest(exchange) } } } + + def writeResponseHandler(r: WebsocketResult.Response) = new BlockingHandler( + new HttpHandler { + def handleRequest(exchange: HttpServerExchange): Unit = { + writeResponse(exchange, r.value) + } + } ) + def defaultHttpHandler = new BlockingHandler( + new HttpHandler() { + def handleRequest(exchange: HttpServerExchange) = { + hello(exchange, exchange.getRequestMethod.toString.toLowerCase(), ParamContext(exchange, _), exchange.getRequestPath).foreach{ r => + writeResponse(exchange, r.asInstanceOf[Response]) + } + } + } + ) + + def hello(exchange0: HttpServerExchange, effectiveMethod: String, ctx0: Seq[String] => ParamContext, path: String) = { + routeTries(effectiveMethod).lookup(Util.splitPath(path).toList, Map()) match{ + case None => + writeResponse(exchange0, handleNotFound()) + None + case Some(((routes, metadata), extBindings, remaining)) => + val ctx = ParamContext(exchange0, remaining) + val ctx1 = ctx0(remaining) + def rec(remaining: List[Decorator], + bindings: List[Map[String, Any]]): Router.Result[Any] = try { + remaining match { + case head :: rest => + head.wrapFunction(ctx, args => rec(rest, args :: bindings).asInstanceOf[Router.Result[head.Output]]) + + case Nil => + metadata.endpoint.wrapFunction(ctx, epBindings => + metadata.entryPoint + .asInstanceOf[EntryPoint[cask.main.Routes, cask.model.ParamContext]] + .invoke(routes, ctx1, (epBindings ++ extBindings.mapValues(metadata.endpoint.wrapPathSegment)) :: bindings.reverse) + .asInstanceOf[Router.Result[Nothing]] + ) + } + // Make sure we wrap any exceptions that bubble up from decorator + // bodies, so outer decorators do not need to worry about their + // delegate throwing on them + }catch{case e: Throwable => Router.Result.Error.Exception(e) } + + rec((metadata.decorators ++ routes.decorators ++ mainDecorators).toList, Nil)match{ + case Router.Result.Success(res) => Some(res) + case e: Router.Result.Error => + writeResponse(exchange0, handleEndpointError(exchange0, routes, metadata, e)) + None + } + } + + } def handleEndpointError(exchange: HttpServerExchange, routes: Routes, diff --git a/cask/src/cask/main/Routes.scala b/cask/src/cask/main/Routes.scala index 7b47731..aaec832 100644 --- a/cask/src/cask/main/Routes.scala +++ b/cask/src/cask/main/Routes.scala @@ -8,8 +8,8 @@ import language.experimental.macros object Routes{ case class EndpointMetadata[T](decorators: Seq[Decorator], - endpoint: Endpoint, - entryPoint: EntryPoint[T, ParamContext]) + endpoint: BaseEndpoint, + entryPoint: EntryPoint[T, _]) case class RoutesEndpointsMetadata[T](value: EndpointMetadata[T]*) object RoutesEndpointsMetadata{ implicit def initialize[T] = macro initializeImpl[T] @@ -22,12 +22,12 @@ object Routes{ val annotations = m.annotations.filter(_.tree.tpe <:< c.weakTypeOf[BaseDecorator]).reverse if annotations.nonEmpty } yield { - if(!(annotations.head.tree.tpe <:< weakTypeOf[Endpoint])) c.abort( + if(!(annotations.head.tree.tpe <:< weakTypeOf[BaseEndpoint])) c.abort( annotations.head.tree.pos, s"Last annotation applied to a function must be an instance of Endpoint, " + s"not ${annotations.head.tree.tpe}" ) - val allEndpoints = annotations.filter(_.tree.tpe <:< weakTypeOf[Endpoint]) + val allEndpoints = annotations.filter(_.tree.tpe <:< weakTypeOf[BaseEndpoint]) if(allEndpoints.length > 1) c.abort( annotations.head.tree.pos, s"You can only apply one Endpoint annotation to a function, not " + @@ -43,7 +43,7 @@ object Routes{ m.asInstanceOf[MethodSymbol], weakTypeOf[T], q"${annotObjectSyms.head}.convertToResultType", - c.weakTypeOf[ParamContext], + tq"cask.ParamContext", annotObjectSyms.map(annotObjectSym => q"$annotObjectSym.getParamParser"), annotObjectSyms.map(annotObjectSym => tq"$annotObjectSym.Input") |