From a873d77fbeb4590dceeb893ab067beb9058db241 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Mon, 13 Aug 2018 15:00:14 +0800 Subject: Tweak websocket tests, example and docs --- cask/src/cask/endpoints/FormEndpoint.scala | 2 +- cask/src/cask/endpoints/JsonEndpoint.scala | 2 +- cask/src/cask/endpoints/StaticEndpoints.scala | 7 +++---- cask/src/cask/main/Main.scala | 19 +++++++++---------- ci/test.sh | 2 +- docs/pages/1 - Cask: a Scala HTTP micro-framework .md | 11 ++++++++--- example/endpoints/app/src/Endpoints.scala | 6 +----- example/websockets/app/src/Websockets.scala | 8 ++++---- example/websockets/app/test/src/ExampleTests.scala | 2 +- 9 files changed, 29 insertions(+), 30 deletions(-) diff --git a/cask/src/cask/endpoints/FormEndpoint.scala b/cask/src/cask/endpoints/FormEndpoint.scala index eb882fa..a952a2a 100644 --- a/cask/src/cask/endpoints/FormEndpoint.scala +++ b/cask/src/cask/endpoints/FormEndpoint.scala @@ -43,7 +43,7 @@ object FormReader{ def read(ctx: ParamContext, label: String, input: Seq[FormEntry]) = input.map(_.asInstanceOf[FormFile]) } } -class postForm(val path: String, override val subpath: Boolean = false) extends Endpoint with HttpDecorator{ +class postForm(val path: String, override val subpath: Boolean = false) extends Endpoint { type Output = Response val methods = Seq("post") diff --git a/cask/src/cask/endpoints/JsonEndpoint.scala b/cask/src/cask/endpoints/JsonEndpoint.scala index f3b0cae..f91b888 100644 --- a/cask/src/cask/endpoints/JsonEndpoint.scala +++ b/cask/src/cask/endpoints/JsonEndpoint.scala @@ -26,7 +26,7 @@ object JsReader{ } } } -class postJson(val path: String, override val subpath: Boolean = false) extends Endpoint with HttpDecorator{ +class postJson(val path: String, override val subpath: Boolean = false) extends Endpoint{ type Output = Response val methods = Seq("post") type Input = ujson.Js.Value diff --git a/cask/src/cask/endpoints/StaticEndpoints.scala b/cask/src/cask/endpoints/StaticEndpoints.scala index a9b3193..726af21 100644 --- a/cask/src/cask/endpoints/StaticEndpoints.scala +++ b/cask/src/cask/endpoints/StaticEndpoints.scala @@ -1,10 +1,9 @@ package cask.endpoints -import cask.internal.Router -import cask.main.{Endpoint, HttpDecorator} -import cask.model.{ParamContext, Response} +import cask.main.Endpoint +import cask.model.ParamContext -class static(val path: String) extends Endpoint with HttpDecorator{ +class static(val path: String) extends Endpoint{ type Output = String val methods = Seq("get") type Input = Seq[String] diff --git a/cask/src/cask/main/Main.scala b/cask/src/cask/main/Main.scala index 94d0e14..1b69597 100644 --- a/cask/src/cask/main/Main.scala +++ b/cask/src/cask/main/Main.scala @@ -53,8 +53,8 @@ abstract class BaseMain{ ) } - def genericWebsocketHandler(exchange0: HttpServerExchange) = - hello(exchange0, "websocket", ParamContext(exchange0, _), exchange0.getRequestPath).foreach{ r => + def websocketEndpointHandler(exchange0: HttpServerExchange) = + invokeEndpointFunction(exchange0, "websocket", exchange0.getRequestPath).foreach{ r => r.asInstanceOf[WebsocketResult] match{ case l: WebsocketResult.Listener => io.undertow.Handlers.websocket(l.value).handleRequest(exchange0) @@ -67,10 +67,9 @@ abstract class BaseMain{ new HttpHandler() { def handleRequest(exchange: HttpServerExchange): Unit = { if (exchange.getRequestHeaders.getFirst("Upgrade") == "websocket") { - - genericWebsocketHandler(exchange) + websocketEndpointHandler(exchange) } else { - defaultHttpHandler.handleRequest(exchange) + httpEndpointHandler.handleRequest(exchange) } } } @@ -82,24 +81,24 @@ abstract class BaseMain{ } } ) - def defaultHttpHandler = new BlockingHandler( + + def httpEndpointHandler = new BlockingHandler( new HttpHandler() { def handleRequest(exchange: HttpServerExchange) = { - hello(exchange, exchange.getRequestMethod.toString.toLowerCase(), ParamContext(exchange, _), exchange.getRequestPath).foreach{ r => + invokeEndpointFunction(exchange, exchange.getRequestMethod.toString.toLowerCase(), exchange.getRequestPath).foreach{ r => writeResponse(exchange, r.asInstanceOf[Response]) } } } ) - def hello(exchange0: HttpServerExchange, effectiveMethod: String, ctx0: Seq[String] => ParamContext, path: String) = { + def invokeEndpointFunction(exchange0: HttpServerExchange, effectiveMethod: String, 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 { @@ -110,7 +109,7 @@ abstract class BaseMain{ 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) + .invoke(routes, ctx, (epBindings ++ extBindings.mapValues(metadata.endpoint.wrapPathSegment)) :: bindings.reverse) .asInstanceOf[Router.Result[Nothing]] ) } diff --git a/ci/test.sh b/ci/test.sh index a493514..3c955b9 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -2,4 +2,4 @@ set -eux -mill -i __.testLocal \ No newline at end of file +mill -i --disable-ticker __.testLocal \ No newline at end of file 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 131d06b..df79f09 100644 --- a/docs/pages/1 - Cask: a Scala HTTP micro-framework .md +++ b/docs/pages/1 - Cask: a Scala HTTP micro-framework .md @@ -319,6 +319,13 @@ Returning a `cask.Response` immediately closes the websocket connection, and is useful if you want to e.g. return a 404 or 403 due to the initial request being invalid. +Cask intentionally provides a relatively low-level websocket interface. It +leaves it up to you to manage open channels, react to incoming messages, or +pro-actively send them out, mostly using the underlying Undertow webserver +interface. While Cask does not model streams, backpressure, iteratees, or +provide any higher level API, it should not be difficult to take the Cask API +and build whatever higher-level abstractions you prefer to use. + ### TodoMVC Api Server @@ -396,9 +403,7 @@ need some low-level functionality not exposed by the Cask API, you can override `defaultHandler` to make use of Undertow's own [handler API](http://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html#built-in-handlers) for customizing your webserver. This allows for things that Cask itself doesn't -internally support: asynchronous requests & response, -[Websockets](http://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html#websockets), -etc. +internally support. ### def port: Int = 8080, def host: String = "localhost" diff --git a/example/endpoints/app/src/Endpoints.scala b/example/endpoints/app/src/Endpoints.scala index 9eadf39..5450029 100644 --- a/example/endpoints/app/src/Endpoints.scala +++ b/example/endpoints/app/src/Endpoints.scala @@ -1,10 +1,6 @@ package app -import cask.main.HttpDecorator -import cask.model.ParamContext - - -class custom(val path: String, val methods: Seq[String]) extends cask.Endpoint with HttpDecorator{ +class custom(val path: String, val methods: Seq[String]) extends cask.Endpoint{ type Output = Int def wrapFunction(ctx: cask.ParamContext, delegate: Delegate): Returned = { delegate(Map()).map{num => diff --git a/example/websockets/app/src/Websockets.scala b/example/websockets/app/src/Websockets.scala index 277b06f..a6ceb73 100644 --- a/example/websockets/app/src/Websockets.scala +++ b/example/websockets/app/src/Websockets.scala @@ -13,14 +13,14 @@ object Websockets extends cask.MainRoutes{ channel.getReceiveSetter.set( new AbstractReceiveListener() { override def onFullTextMessage(channel: WebSocketChannel, message: BufferedTextMessage) = { - val data = message.getData - if (data == "") channel.close() - else WebSockets.sendTextBlocking(userName + " " + data, channel) + message.getData match{ + case "" => channel.close() + case data => WebSockets.sendTextBlocking(userName + " " + data, channel) + } } } ) channel.resumeReceives() - } } } diff --git a/example/websockets/app/test/src/ExampleTests.scala b/example/websockets/app/test/src/ExampleTests.scala index 403fde0..969750c 100644 --- a/example/websockets/app/test/src/ExampleTests.scala +++ b/example/websockets/app/test/src/ExampleTests.scala @@ -105,7 +105,7 @@ object ExampleTests extends TestSuite{ w.sendTextFrame("") Thread.sleep(1) } - Thread.sleep(1500) + Thread.sleep(2000) out.length ==> 2000 }finally{ -- cgit v1.2.3