From 0f0f945abd365c9f6f83b3d315cea30eacb6ed51 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 9 Oct 2019 10:44:14 +0800 Subject: Add test to verify `compress` decorator is properly passing through irrelevant results --- build.sc | 5 ++- cask/src/cask/decorators/compress.scala | 5 ++- cask/src/cask/router/Result.scala | 6 +-- .../1 - Cask: a Scala HTTP micro-framework.md | 6 +++ example/websockets4/app/src/Websockets4.scala | 22 ++++++++++ .../websockets4/app/test/src/ExampleTests.scala | 51 ++++++++++++++++++++++ example/websockets4/build.sc | 17 ++++++++ 7 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 example/websockets4/app/src/Websockets4.scala create mode 100644 example/websockets4/app/test/src/ExampleTests.scala create mode 100644 example/websockets4/build.sc diff --git a/build.sc b/build.sc index f6ce6ae..dc7f051 100644 --- a/build.sc +++ b/build.sc @@ -23,6 +23,7 @@ import $file.example.variableRoutes.build import $file.example.websockets.build import $file.example.websockets2.build import $file.example.websockets3.build +import $file.example.websockets4.build trait CaskModule extends ScalaModule with PublishModule{ def scalaVersion = "2.13.0" @@ -122,7 +123,8 @@ object example extends Module{ object variableRoutes extends $file.example.variableRoutes.build.AppModule with LocalModule object websockets extends $file.example.websockets.build.AppModule with LocalModule object websockets2 extends $file.example.websockets2.build.AppModule with LocalModule - object websockets3 extends $file.example.websockets2.build.AppModule with LocalModule + object websockets3 extends $file.example.websockets3.build.AppModule with LocalModule + object websockets4 extends $file.example.websockets4.build.AppModule with LocalModule } def publishVersion = T.input($file.ci.version.publishVersion) @@ -167,6 +169,7 @@ def uploadToGithub(authKey: String) = T.command{ $file.example.websockets.build.millSourcePath, $file.example.websockets2.build.millSourcePath, $file.example.websockets3.build.millSourcePath, + $file.example.websockets4.build.millSourcePath, ) for(example <- examples){ val f = T.ctx().dest diff --git a/cask/src/cask/decorators/compress.scala b/cask/src/cask/decorators/compress.scala index 61d76f6..17931fd 100644 --- a/cask/src/cask/decorators/compress.scala +++ b/cask/src/cask/decorators/compress.scala @@ -11,7 +11,8 @@ class compress extends cask.RawDecorator{ .toSeq .flatMap(_.asScala) .flatMap(_.split(", ")) - delegate(Map()).transform{ case v: cask.Response[_] => + val r = delegate(Map()) + val finalResult = r.transform{ case v: cask.Response.Raw => val (newData, newHeaders) = if (acceptEncodings.exists(_.toLowerCase == "gzip")) { new Response.Data { def write(out: OutputStream): Unit = { @@ -37,6 +38,6 @@ class compress extends cask.RawDecorator{ v.cookies ) } - + finalResult } } diff --git a/cask/src/cask/router/Result.scala b/cask/src/cask/router/Result.scala index e38ee28..3c2f957 100644 --- a/cask/src/cask/router/Result.scala +++ b/cask/src/cask/router/Result.scala @@ -9,7 +9,7 @@ package cask.router */ sealed trait Result[+T]{ def map[V](f: T => V): Result[V] - def transform[V](f: PartialFunction[T, V]): Result[V] + def transform[V](f: PartialFunction[Any, V]): Result[V] } object Result{ @@ -19,7 +19,7 @@ object Result{ */ case class Success[T](value: T) extends Result[T]{ def map[V](f: T => V) = Success(f(value)) - def transform[V](f: PartialFunction[T, V]) = f.lift(value) match { + def transform[V](f: PartialFunction[Any, V]) = f.lift(value) match { case None => Success(value).asInstanceOf[Result[V]] case Some(res) => Success(res) } @@ -30,7 +30,7 @@ object Result{ */ sealed trait Error extends Result[Nothing]{ def map[V](f: Nothing => V) = this - def transform[V](f: PartialFunction[Nothing, V]) = this + def transform[V](f: PartialFunction[Any, V]) = this } 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 aa878c6..8cf80ec 100644 --- a/docs/pages/1 - Cask: a Scala HTTP micro-framework.md +++ b/docs/pages/1 - Cask: a Scala HTTP micro-framework.md @@ -378,6 +378,12 @@ 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. +If you are separating your `cask.Routes` from your `cask.Main`, you need to +inject in a `cask.Logger` to handle errors reported when handling websocket +requests: + +$$$websockets3 + ## TodoMVC Api Server diff --git a/example/websockets4/app/src/Websockets4.scala b/example/websockets4/app/src/Websockets4.scala new file mode 100644 index 0000000..f275746 --- /dev/null +++ b/example/websockets4/app/src/Websockets4.scala @@ -0,0 +1,22 @@ +package app + +case class Websockets4()(implicit val log: cask.Logger) extends cask.Routes{ + @cask.decorators.compress // make sure compress decorator passes non-requests through correctly + @cask.websocket("/connect/:userName") + def showUserProfile(userName: String): cask.WebsocketResult = { + if (userName != "haoyi") cask.Response("", statusCode = 403) + else cask.WsHandler { channel => + cask.WsActor { + case cask.Ws.Text("") => channel.send(cask.Ws.Close()) + case cask.Ws.Text(data) => + channel.send(cask.Ws.Text(userName + " " + data)) + } + } + } + + initialize() +} + +object Websockets4Main extends cask.Main{ + val allRoutes = Seq(Websockets4()) +} diff --git a/example/websockets4/app/test/src/ExampleTests.scala b/example/websockets4/app/test/src/ExampleTests.scala new file mode 100644 index 0000000..cd33bb4 --- /dev/null +++ b/example/websockets4/app/test/src/ExampleTests.scala @@ -0,0 +1,51 @@ +package app + +import java.util.concurrent.atomic.AtomicInteger + +import org.asynchttpclient.ws.{WebSocket, WebSocketListener, WebSocketUpgradeHandler} +import utest._ +import concurrent.ExecutionContext.Implicits.global +import cask.Logger.Console.globalLogger +object ExampleTests extends TestSuite{ + + + def withServer[T](example: cask.main.Main)(f: String => T): T = { + val server = io.undertow.Undertow.builder + .addHttpListener(8080, "localhost") + .setHandler(example.defaultHandler) + .build + server.start() + val res = + try f("http://localhost:8080") + finally server.stop() + res + } + + val tests = Tests{ + test("Websockets") - withServer(Websockets4Main){ host => + @volatile var out = List.empty[String] + // 4. open websocket + val ws = cask.WsClient.connect("ws://localhost:8080/connect/haoyi"){ + case cask.Ws.Text(s) => out = s :: out + } + + try { + // 5. send messages + ws.send(cask.Ws.Text("hello")) + ws.send(cask.Ws.Text("world")) + ws.send(cask.Ws.Text("")) + Thread.sleep(100) + out ==> List("haoyi world", "haoyi hello") + + var error: String = "" + val ws2 = cask.WsClient.connect("ws://localhost:8080/connect/nobody") { + case cask.Ws.Text(s) => out = s :: out + case cask.Ws.Error(t) => error += t.toString + case cask.Ws.Close(code, reason) => error += reason + } + + assert(error.contains("403")) + }finally ws.close() + } + } +} diff --git a/example/websockets4/build.sc b/example/websockets4/build.sc new file mode 100644 index 0000000..197e285 --- /dev/null +++ b/example/websockets4/build.sc @@ -0,0 +1,17 @@ +import mill._, scalalib._ + + +trait AppModule extends ScalaModule{ + def scalaVersion = "2.13.0" + def ivyDeps = Agg[Dep]( + ) + object test extends Tests{ + def testFrameworks = Seq("utest.runner.Framework") + + def ivyDeps = Agg( + ivy"com.lihaoyi::utest::0.7.1", + ivy"com.lihaoyi::requests::0.2.0", + ivy"org.asynchttpclient:async-http-client:2.5.2" + ) + } +} \ No newline at end of file -- cgit v1.2.3