summaryrefslogtreecommitdiff
path: root/cask/src/cask/main
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-08-13 03:54:50 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-08-13 03:54:50 +0800
commit2fc9fd22084bb4a89a72be525c18fc409303ada5 (patch)
tree511734c255237e99ba0b5b302232073572faaa38 /cask/src/cask/main
parent790deda0f38e36c7378ff05a9c234a56e14a5d6b (diff)
downloadcask-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.scala13
-rw-r--r--cask/src/cask/main/Main.scala100
-rw-r--r--cask/src/cask/main/Routes.scala10
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")