From 470206b724efc927cc0d14ee166e2ef60ca5e90d Mon Sep 17 00:00:00 2001 From: Shruti Verma <62893271+ShrutiVerma97@users.noreply.github.com> Date: Wed, 26 Jan 2022 21:46:40 +0530 Subject: [PATCH 01/21] fix: example links (#906) --- docs/website/docs/v1.x/getting-started.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/website/docs/v1.x/getting-started.md b/docs/website/docs/v1.x/getting-started.md index 7e1806035d..a1b321fafd 100644 --- a/docs/website/docs/v1.x/getting-started.md +++ b/docs/website/docs/v1.x/getting-started.md @@ -154,10 +154,10 @@ object HelloWorld extends App { ## Examples -- [Simple Server](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/HelloWorld.scala) -- [Advanced Server](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/HelloWorldAdvanced.scala) -- [WebSocket Server](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/SocketEchoServer.scala) -- [Streaming Response](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/StreamingResponse.scala) -- [Simple Client](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/SimpleClient.scala) -- [File Streaming](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/FileStreaming.scala) -- [Authentication](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/Authentication.scala) +- [Simple Server](https://dream11.github.io/zio-http/docs/v1.x/examples/zio-http-basic-examples/hello-world) +- [Advanced Server](https://dream11.github.io/zio-http/docs/v1.x/examples/advanced-examples/hello-world-advanced) +- [WebSocket Server](https://dream11.github.io/zio-http/docs/v1.x/examples/zio-http-basic-examples/web-socket) +- [Streaming Response](https://dream11.github.io/zio-http/docs/v1.x/examples/advanced-examples/stream-response) +- [Simple Client](https://dream11.github.io/zio-http/docs/v1.x/examples/zio-http-basic-examples/simple-client) +- [File Streaming](https://dream11.github.io/zio-http/docs/v1.x/examples/advanced-examples/stream-file) +- [Authentication](https://dream11.github.io/zio-http/docs/v1.x/examples/advanced-examples/authentication) From 23011a91a318aa0d726ddd72b034eb43456d8679 Mon Sep 17 00:00:00 2001 From: Amit Kumar Singh Date: Wed, 26 Jan 2022 22:06:27 +0530 Subject: [PATCH 02/21] Update getting-started.md (#907) --- docs/website/docs/v1.x/getting-started.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/website/docs/v1.x/getting-started.md b/docs/website/docs/v1.x/getting-started.md index a1b321fafd..a1b2c93542 100644 --- a/docs/website/docs/v1.x/getting-started.md +++ b/docs/website/docs/v1.x/getting-started.md @@ -28,7 +28,7 @@ An app can be made using any of the available constructors on `zhttp.Http`. For handling routes, Http Domain has a `collect` method that, accepts different requests and produces responses. Pattern matching on the route is supported by the framework The example below shows how to create routes: -```scala, +```scala import zhttp.http._ val app = Http.collect[Request] { @@ -37,7 +37,7 @@ val app = Http.collect[Request] { } ``` You can create typed routes as well. The below example shows how to accept count as `Int` only. - ```scala, + ```scala import zhttp.http._ val app = Http.collect[Request] { @@ -47,7 +47,7 @@ You can create typed routes as well. The below example shows how to accept count ### Composition -HTTP app can be composed using the `++` operator. The way it works is if none of the routes matches in `a` or there is an error `a`, the control is passed to the `b` app. +HTTP app can be composed using the `++` operator. The way it works is if none of the routes matches in `a`, the control is passed to the `b` app. ```scala import zhttp.http._ From d58857c67116281e1860a1e4a760b85e6b0ab470 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Wed, 26 Jan 2022 22:26:22 +0530 Subject: [PATCH 03/21] refactor: rename asString to encode in Scheme (#905) From f75030c432080d28f8214e589139976cab94d8fa Mon Sep 17 00:00:00 2001 From: Shubham Girdhar Date: Wed, 26 Jan 2022 23:43:34 +0530 Subject: [PATCH 04/21] Doc: setup (#886) * doc: setup * resolve: PR comments --- docs/website/docs/v1.x/index.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/website/docs/v1.x/index.md b/docs/website/docs/v1.x/index.md index 5819dcca26..1523440356 100644 --- a/docs/website/docs/v1.x/index.md +++ b/docs/website/docs/v1.x/index.md @@ -4,4 +4,23 @@ sidebar_label: "Setup" --- # Setup -Work in progress \ No newline at end of file + +In this guide, you'll learn how to get started with a new zio-http project. + +Before we dive in, make sure that you have the following on your computer: + +* JDK 1.8 or higher +* sbt (scalaVersion >= 2.12) + +## As a dependency + +To use zio-http, add the following dependencies in your project: + +```scala +val ZHTTPVersion = "1.0.0.0-RC23" + +libraryDependencies ++= Seq( + "io.d11" %% "zhttp" % ZHTTPVersion, + "io.d11" %% "zhttp-test" % ZHTTPVersion % Test +) +``` From 5e714e0a1c199544483334ca43cff36d2e70afac Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Thu, 27 Jan 2022 05:51:18 +0100 Subject: [PATCH 05/21] Update netty-incubator-transport-native-io_uring to 0.0.12.Final (#908) --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 01979e7170..6e5999d2d0 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -3,7 +3,7 @@ import sbt._ object Dependencies { val JwtCoreVersion = "9.0.3" val NettyVersion = "4.1.73.Final" - val NettyIncubatorVersion = "0.0.11.Final" + val NettyIncubatorVersion = "0.0.12.Final" val ScalaCompactCollectionVersion = "2.6.0" val ZioVersion = "1.0.13" val SttpVersion = "3.3.18" From b656936ba635b010ebac4530e289d1c847e3f64c Mon Sep 17 00:00:00 2001 From: sumawa Date: Thu, 27 Jan 2022 10:21:29 +0530 Subject: [PATCH 06/21] Documentation for Server (#885) * removed config.md and make configurations part of Server page * added server configurations * markdwon appearing fine in docusaurus generated page * added one missing configuration * Server documentation changed according to PR comments * change note to tip Co-authored-by: Sumant Awasthi Co-authored-by: amitsingh --- docs/website/docs/v1.x/dsl/server/config.md | 3 - docs/website/docs/v1.x/dsl/server/index.md | 109 ++++++++++++++++++++ 2 files changed, 109 insertions(+), 3 deletions(-) delete mode 100644 docs/website/docs/v1.x/dsl/server/config.md create mode 100644 docs/website/docs/v1.x/dsl/server/index.md diff --git a/docs/website/docs/v1.x/dsl/server/config.md b/docs/website/docs/v1.x/dsl/server/config.md deleted file mode 100644 index 50b2454a71..0000000000 --- a/docs/website/docs/v1.x/dsl/server/config.md +++ /dev/null @@ -1,3 +0,0 @@ -# Config - -Work in progress \ No newline at end of file diff --git a/docs/website/docs/v1.x/dsl/server/index.md b/docs/website/docs/v1.x/dsl/server/index.md new file mode 100644 index 0000000000..eff8c9feff --- /dev/null +++ b/docs/website/docs/v1.x/dsl/server/index.md @@ -0,0 +1,109 @@ +# ZIO HTTP Server + +This section describes, ZIO HTTP Server and different configurations you can provide while creating the Server + +## Start a ZIO HTTP Server with default configurations +```scala + override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = + Server.start(8090, app.silent).exitCode +``` +## Start a ZIO HTTP Server with custom configurations. +1. Imports required by the customised server. + ```scala + import zhttp.http._ + import zhttp.service.server.ServerChannelFactory + import zhttp.service.{EventLoopGroup, Server} + import zio._ + import scala.util.Try + ``` +2. The Server can be built incrementally with a `++` each returning a new Server overriding any default configuration. (More properties are given in the [Server Configurations](#server-configurations) section below.) + ```scala + private val server = + Server.port(PORT) ++ // Setup port + Server.maxRequestSize(8 * 1024) ++ // handle max request size of 8 KB (default 4 KB) + Server.app(fooBar ++ app) // Setup the Http app + ``` +3. And then use ```Server.make``` to get a "managed" instance use it to run a server forever + ```scala + override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = { + server.make + .use(start => + console.putStrLn(s"Server started on port ${start.port}") + *> ZIO.never, + ).provideCustomLayer(ServerChannelFactory.auto ++ EventLoopGroup.auto(2)) + .exitCode + ``` + **Tip :** `ServerChannelFactory.auto ++ EventLoopGroup.auto(num Threads)` is supplied as an external dependency to choose netty transport type. One can leave it as `auto` to let the application handle it for you. + Also in `EventLoopGroup.auto(numThreads)` you can choose number of threads based on number of available processors. + +### Binding Server to a socket address +One can bind server to Inet address in multiple ways, either by providing a port number or +- If no port is provided, the default port is 8080 +- If specified port is 0, it will use a dynamically selected port. + +
+A complete example + +- Example below shows how the server can be started in forever mode to serve HTTP requests: + +```scala +import zhttp.http._ +import zhttp.service._ +import zhttp.service.server.ServerChannelFactory +import zio._ + +import scala.util.Try + +object HelloWorldAdvanced extends App { + // Set a port + private val PORT = 8090 + + private val fooBar: HttpApp[Any, Nothing] = Http.collect[Request] { + case Method.GET -> !! / "foo" => Response.text("bar") + case Method.GET -> !! / "bar" => Response.text("foo") + } + + private val app = Http.collectM[Request] { + case Method.GET -> !! / "random" => random.nextString(10).map(Response.text) + case Method.GET -> !! / "utc" => clock.currentDateTime.map(s => Response.text(s.toString)) + } + + private val server = + Server.port(PORT) ++ // Setup port + Server.paranoidLeakDetection ++ // Paranoid leak detection (affects performance) + Server.app(fooBar +++ app) // Setup the Http app + + override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = { + // Configure thread count using CLI + val nThreads: Int = args.headOption.flatMap(x => Try(x.toInt).toOption).getOrElse(0) + + // Create a new server + server.make + .use(_ => + // Waiting for the server to start + console.putStrLn(s"Server started on port $PORT") + + // Ensures the server doesn't die after printing + *> ZIO.never, + ) + .provideCustomLayer(ServerChannelFactory.auto ++ EventLoopGroup.auto(nThreads)) + .exitCode + } +} + ``` +
+ +## Server Configurations + +| **Configuration** | **Purpose and usage** | +| ----------- | ----------- | +| `Server.app(httpApp)` | Mount routes. Refer to complete example above | +| `Server.maxRequestSize(8 * 1024)` | handle max request size of 8 KB (default 4 KB) | +| `Server.port(portNum)` or `Server.bind(portNum)` | Bind server to the port, refer to examples above | +| `Server.ssl(sslOptions)` | Creates a new server with ssl options. [HttpsHelloWorld](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/example/HttpsHelloWorld.scala) | +| `Server.acceptContinue` | Sends a [100 CONTINUE](https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3) | +| `Server.disableFlowControl` | Refer [Netty FlowControlHandler](https://netty.io/4.1/api/io/netty/handler/flow/FlowControlHandler.html) | +| `Server.disableLeakDetection` | Disable any leak detection Refer netty's [ResourceLeakDetector](https://netty.io/4.0/api/io/netty/util/ResourceLeakDetector.Level.html) | +| `Server.simpleLeakDetection` | Simplistic leak detection comes with small over head. Refer netty's [ResourceLeakDetector](https://netty.io/4.0/api/io/netty/util/ResourceLeakDetector.Level.html) | +| `Server.paranoidLeakDetection` | Comes with highest possible overhead (for testing purposes only). Refer netty's [ResourceLeakDetector](https://netty.io/4.0/api/io/netty/util/ResourceLeakDetector.Level.html) | +| `Server.consolidateFlush` | Flushing content is done in batches. Can potentially improve performance. | From 594d8cbb7b69e249a75ba00cbdc92ce3b75781d0 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Thu, 27 Jan 2022 10:27:58 +0530 Subject: [PATCH 07/21] Performance: Improve performance of `collectM` (#882) * Use ZIO response * fix: improve cancellation performance * refactor: use sticky server * refactor: reduce allocations * revert example --- build.sbt | 2 +- .../scala/zhttp/service/HttpRuntime.scala | 58 +++++++++++-------- .../src/main/scala/zhttp/service/Server.scala | 2 +- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/build.sbt b/build.sbt index 36490ac34b..ebaa132997 100644 --- a/build.sbt +++ b/build.sbt @@ -124,6 +124,6 @@ lazy val zhttpTest = (project in file("zio-http-test")) lazy val example = (project in file("./example")) .settings(stdSettings("example")) .settings(publishSetting(false)) - .settings(runSettings("example.FileStreaming")) + .settings(runSettings("example.Main")) .settings(libraryDependencies ++= Seq(`jwt-core`)) .dependsOn(zhttp) diff --git a/zio-http/src/main/scala/zhttp/service/HttpRuntime.scala b/zio-http/src/main/scala/zhttp/service/HttpRuntime.scala index 05b67735e2..2d3e0888ab 100644 --- a/zio-http/src/main/scala/zhttp/service/HttpRuntime.scala +++ b/zio-http/src/main/scala/zhttp/service/HttpRuntime.scala @@ -1,9 +1,9 @@ package zhttp.service import io.netty.channel.{ChannelHandlerContext, EventLoopGroup => JEventLoopGroup} -import io.netty.util.concurrent.{EventExecutor, Future} +import io.netty.util.concurrent.{EventExecutor, Future, GenericFutureListener} +import zio._ import zio.internal.Executor -import zio.{Exit, Runtime, URIO, ZIO} import scala.collection.mutable import scala.concurrent.{ExecutionContext => JExecutionContext} @@ -14,16 +14,23 @@ import scala.jdk.CollectionConverters._ * cancel the execution when the channel closes. */ final class HttpRuntime[+R](strategy: HttpRuntime.Strategy[R]) { - def unsafeRun(ctx: ChannelHandlerContext)(program: ZIO[R, Throwable, Any]): Unit = { + val rtm = strategy.getRuntime(ctx) + + // Close the connection if the program fails + // When connection closes, interrupt the program + rtm .unsafeRunAsync(for { fiber <- program.fork - _ <- ZIO.effect { - ctx.channel().closeFuture.addListener((_: Future[_ <: Void]) => rtm.unsafeRunAsync_(fiber.interrupt): Unit) + close <- UIO { + val close = closeListener(rtm, fiber) + ctx.channel().closeFuture.addListener(close) + close } _ <- fiber.join + _ <- UIO(ctx.channel().closeFuture().removeListener(close)) } yield ()) { case Exit.Success(_) => () case Exit.Failure(cause) => @@ -31,18 +38,39 @@ final class HttpRuntime[+R](strategy: HttpRuntime.Strategy[R]) { case None => () case Some(_) => System.err.println(cause.prettyPrint) } - ctx.close() + if (ctx.channel().isOpen) ctx.close() } } + + private def closeListener(rtm: Runtime[Any], fiber: Fiber.Runtime[_, _]): GenericFutureListener[Future[_ >: Void]] = + (_: Future[_ >: Void]) => rtm.unsafeRunAsync_(fiber.interrupt): Unit } object HttpRuntime { + def dedicated[R](group: JEventLoopGroup): URIO[R, HttpRuntime[R]] = + Strategy.dedicated(group).map(runtime => new HttpRuntime[R](runtime)) + + def default[R]: URIO[R, HttpRuntime[R]] = + Strategy.default().map(runtime => new HttpRuntime[R](runtime)) + + def sticky[R](group: JEventLoopGroup): URIO[R, HttpRuntime[R]] = + Strategy.sticky(group).map(runtime => new HttpRuntime[R](runtime)) + sealed trait Strategy[R] { def getRuntime(ctx: ChannelHandlerContext): Runtime[R] } object Strategy { + def dedicated[R](group: JEventLoopGroup): ZIO[R, Nothing, Strategy[R]] = + ZIO.runtime[R].map(runtime => Dedicated(runtime, group)) + + def default[R](): ZIO[R, Nothing, Strategy[R]] = + ZIO.runtime[R].map(runtime => Default(runtime)) + + def sticky[R](group: JEventLoopGroup): ZIO[R, Nothing, Strategy[R]] = + ZIO.runtime[R].map(runtime => Group(runtime, group)) + case class Default[R](runtime: Runtime[R]) extends Strategy[R] { override def getRuntime(ctx: ChannelHandlerContext): Runtime[R] = runtime } @@ -73,23 +101,5 @@ object HttpRuntime { override def getRuntime(ctx: ChannelHandlerContext): Runtime[R] = localRuntime.getOrElse(ctx.executor(), runtime) } - - def sticky[R](group: JEventLoopGroup): ZIO[R, Nothing, Strategy[R]] = - ZIO.runtime[R].map(runtime => Group(runtime, group)) - - def default[R](): ZIO[R, Nothing, Strategy[R]] = - ZIO.runtime[R].map(runtime => Default(runtime)) - - def dedicated[R](group: JEventLoopGroup): ZIO[R, Nothing, Strategy[R]] = - ZIO.runtime[R].map(runtime => Dedicated(runtime, group)) } - - def sticky[R](group: JEventLoopGroup): URIO[R, HttpRuntime[R]] = - Strategy.sticky(group).map(runtime => new HttpRuntime[R](runtime)) - - def dedicated[R](group: JEventLoopGroup): URIO[R, HttpRuntime[R]] = - Strategy.dedicated(group).map(runtime => new HttpRuntime[R](runtime)) - - def default[R]: URIO[R, HttpRuntime[R]] = - Strategy.default().map(runtime => new HttpRuntime[R](runtime)) } diff --git a/zio-http/src/main/scala/zhttp/service/Server.scala b/zio-http/src/main/scala/zhttp/service/Server.scala index cc2137a754..711f957734 100644 --- a/zio-http/src/main/scala/zhttp/service/Server.scala +++ b/zio-http/src/main/scala/zhttp/service/Server.scala @@ -215,7 +215,7 @@ object Server { for { channelFactory <- ZManaged.access[ServerChannelFactory](_.get) eventLoopGroup <- ZManaged.access[EventLoopGroup](_.get) - zExec <- HttpRuntime.default[R].toManaged_ + zExec <- HttpRuntime.sticky[R](eventLoopGroup).toManaged_ reqHandler = settings.app.compile(zExec, settings) respHandler = ServerResponseHandler(zExec, settings, ServerTimeGenerator.make) init = ServerChannelInitializer(zExec, settings, reqHandler, respHandler) From b54e9351392fe2d631548b95a6f80ce46ba9e3ab Mon Sep 17 00:00:00 2001 From: Shruti Verma <62893271+ShrutiVerma97@users.noreply.github.com> Date: Thu, 27 Jan 2022 10:58:11 +0530 Subject: [PATCH 08/21] Doc: Added <> operator in getting started (#910) * fix: ++ operator doc * fix: composition --- docs/website/docs/v1.x/getting-started.md | 24 +++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/website/docs/v1.x/getting-started.md b/docs/website/docs/v1.x/getting-started.md index a1b2c93542..2d6e72ab8d 100644 --- a/docs/website/docs/v1.x/getting-started.md +++ b/docs/website/docs/v1.x/getting-started.md @@ -47,19 +47,31 @@ You can create typed routes as well. The below example shows how to accept count ### Composition -HTTP app can be composed using the `++` operator. The way it works is if none of the routes matches in `a`, the control is passed to the `b` app. +Apps can be composed using operators in `Http`: + +- Using the `++` operator. The way it works is, if none of the routes match in `a`, then the control is passed on to the `b` app. + +```scala + import zhttp.http._ + + val a = Http.collect[Request] { case Method.GET -> !! / "a" => Response.ok } + val b = Http.collect[Request] { case Method.GET -> !! / "b" => Response.ok } + + val app = a ++ b + ``` + + +- Using the `<>` operator. The way it works is, if `a` fails, then the control is passed on to the `b` app. ```scala import zhttp.http._ -val a = Http.collect[Request] { case Method.GET -> !! / "a" => Response.ok } -val b = Http.collect[Request] { case Method.GET -> !! / "b" => Response.ok } +val a = Http.fail(new Error("SERVER_ERROR")) +val b = Http.text("OK") -val app = a ++ b +val app = a <> b ``` -Apps can be composed using the `++` operator. The way it works is, if none of the routes match in `a` , then the control is passed on to the `b` app. - ### ZIO Integration For creating effectful apps, you can use `collectZIO` and wrap `Response` using `wrapZIO` to produce ZIO effect value. From 8e73e9ee7e87034d553e179ab127b87c76cfb5e2 Mon Sep 17 00:00:00 2001 From: James Beem Date: Thu, 27 Jan 2022 04:49:25 -0500 Subject: [PATCH 09/21] feature: Add `collectManaged` to Http (#909) * Add collectManaged * comment typo --- zio-http/src/main/scala/zhttp/http/Http.scala | 15 +++++++++++++++ zio-http/src/test/scala/zhttp/http/HttpSpec.scala | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/zio-http/src/main/scala/zhttp/http/Http.scala b/zio-http/src/main/scala/zhttp/http/Http.scala index da986539c8..566ec45fa7 100644 --- a/zio-http/src/main/scala/zhttp/http/Http.scala +++ b/zio-http/src/main/scala/zhttp/http/Http.scala @@ -102,6 +102,11 @@ sealed trait Http[-R, +E, -A, +B] extends (A => ZIO[R, Option[E], B]) { self => ): Http[R1, E1, A1, C] = self >>> Http.collectZIO(pf) + final def collectManaged[R1 <: R, E1 >: E, A1 <: A, B1 >: B, C]( + pf: PartialFunction[B1, ZManaged[R1, E1, C]], + ): Http[R1, E1, A1, C] = + self >>> Http.collectManaged(pf) + /** * Named alias for `<<<` */ @@ -465,6 +470,11 @@ object Http { */ def collectZIO[A]: Http.PartialCollectZIO[A] = Http.PartialCollectZIO(()) + /** + * Creates an Http app which accepts a request and produces response from a managed resource + */ + def collectManaged[A]: Http.PartialCollectManaged[A] = Http.PartialCollectManaged(()) + /** * Combines multiple Http apps into one */ @@ -619,6 +629,11 @@ object Http { Http.collect[A] { case a if pf.isDefinedAt(a) => Http.fromZIO(pf(a)) }.flatten } + final case class PartialCollectManaged[A](unit: Unit) extends AnyVal { + def apply[R, E, B](pf: PartialFunction[A, ZManaged[R, E, B]]): Http[R, E, A, B] = + Http.collect[A] { case a if pf.isDefinedAt(a) => Http.fromZIO(pf(a).useNow) }.flatten + } + final case class PartialCollect[A](unit: Unit) extends AnyVal { def apply[B](pf: PartialFunction[A, B]): Http[Any, Nothing, A, B] = Collect(pf) } diff --git a/zio-http/src/test/scala/zhttp/http/HttpSpec.scala b/zio-http/src/test/scala/zhttp/http/HttpSpec.scala index 944656f5f0..7fcaa2fd48 100644 --- a/zio-http/src/test/scala/zhttp/http/HttpSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/HttpSpec.scala @@ -114,6 +114,11 @@ object HttpSpec extends DefaultRunnableSpec with HExitAssertion { val actual = a.execute(1) assert(actual)(isEffect) } + + test("should resolve managed") { + val a = Http.collectManaged[Int] { case 1 => ZManaged.succeed("A") } + val actual = a.execute(1) + assert(actual)(isEffect) + } + test("should resolve second effect") { val a = Http.empty.flatten val b = Http.succeed("B") From 0a3d486edc9bb83c1fab637c0d9c8c51f5ccc5de Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Thu, 27 Jan 2022 19:08:40 +0530 Subject: [PATCH 10/21] feature: add `echo` operator to `Socket` (#900) --- .../src/main/scala/zhttp/socket/Socket.scala | 6 ++ .../test/scala/zhttp/socket/SocketSpec.scala | 70 +++++++++++-------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/zio-http/src/main/scala/zhttp/socket/Socket.scala b/zio-http/src/main/scala/zhttp/socket/Socket.scala index 54190c52eb..1833aa031d 100644 --- a/zio-http/src/main/scala/zhttp/socket/Socket.scala +++ b/zio-http/src/main/scala/zhttp/socket/Socket.scala @@ -57,8 +57,14 @@ sealed trait Socket[-R, +E, -A, +B] { self => } object Socket { + def collect[A]: PartialCollect[A] = new PartialCollect[A](()) + /** + * Simply echos the incoming message back + */ + def echo[A]: Socket[Any, Nothing, A, A] = Socket.collect[A] { case a => ZStream.succeed(a) } + def end: ZStream[Any, Nothing, Nothing] = ZStream.halt(Cause.empty) def fromFunction[A]: PartialFromFunction[A] = new PartialFromFunction[A](()) diff --git a/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala b/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala index 6313830056..04109fa95c 100644 --- a/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala +++ b/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala @@ -1,17 +1,19 @@ package zhttp.socket import zio._ +import zio.duration.durationInt import zio.stream.ZStream import zio.test.Assertion._ +import zio.test.TestAspect.timeout import zio.test._ object SocketSpec extends DefaultRunnableSpec { def spec = suite("SocketSpec") { - OperationsSpec - } + operationsSpec + } @@ timeout(5 seconds) - def OperationsSpec = suite("Operations Spec") { + def operationsSpec = suite("OperationsSpec") { testM("fromStream provide") { val text = "Cat ipsum dolor sit amet" val environment = ZStream.environment[String] @@ -23,38 +25,44 @@ object SocketSpec extends DefaultRunnableSpec { assertM(socket.runCollect) { equalTo(Chunk(text)) } - } + testM("fromFunction provide") { - val environmentFunction = (_: Any) => ZStream.environment[WebSocketFrame] - val socket = Socket - .fromFunction(environmentFunction) - .provide(WebSocketFrame.text("Foo")) - .execute(WebSocketFrame.text("Bar")) + } + + testM("fromFunction provide") { + val environmentFunction = (_: Any) => ZStream.environment[WebSocketFrame] + val socket = Socket + .fromFunction(environmentFunction) + .provide(WebSocketFrame.text("Foo")) + .execute(WebSocketFrame.text("Bar")) - assertM(socket.runCollect) { - equalTo(Chunk(WebSocketFrame.text("Foo"))) - } - } + testM("collect provide") { - val environment = ZStream.environment[WebSocketFrame] - val socket = Socket - .collect[WebSocketFrame] { case WebSocketFrame.Pong => - environment + assertM(socket.runCollect) { + equalTo(Chunk(WebSocketFrame.text("Foo"))) } - .provide(WebSocketFrame.ping) - .execute(WebSocketFrame.pong) + } + + testM("collect provide") { + val environment = ZStream.environment[WebSocketFrame] + val socket = Socket + .collect[WebSocketFrame] { case WebSocketFrame.Pong => + environment + } + .provide(WebSocketFrame.ping) + .execute(WebSocketFrame.pong) - assertM(socket.runCollect) { - equalTo(Chunk(WebSocketFrame.ping)) - } - } + testM("ordered provide") { - val socket = Socket.collect[Int] { case _ => - ZStream.environment[Int] - } + assertM(socket.runCollect) { + equalTo(Chunk(WebSocketFrame.ping)) + } + } + + testM("ordered provide") { + val socket = Socket.collect[Int] { case _ => + ZStream.environment[Int] + } - val socketA: Socket[Int, Nothing, Int, Int] = socket.provide(12) - val socketB: Socket[Int, Nothing, Int, Int] = socketA.provide(1) - val socketC: Socket[Any, Nothing, Int, Int] = socketB.provide(42) + val socketA: Socket[Int, Nothing, Int, Int] = socket.provide(12) + val socketB: Socket[Int, Nothing, Int, Int] = socketA.provide(1) + val socketC: Socket[Any, Nothing, Int, Int] = socketB.provide(42) - assertM(socketC.execute(1000).runCollect)(equalTo(Chunk(12))) - } + assertM(socketC.execute(1000).runCollect)(equalTo(Chunk(12))) + } + + testM("echo") { + assertM(Socket.echo(1).runCollect)(equalTo(Chunk(1))) + } } } From cfa6c3e7b2666777eb334585a4d6a2f3a346f2b5 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Thu, 27 Jan 2022 19:36:06 +0530 Subject: [PATCH 11/21] feature: add `Socket.empty` (#901) * feature: add `Socket.empty` * test: add unit test case for Socket#empty Co-authored-by: Shubham Girdhar --- zio-http/src/main/scala/zhttp/socket/Socket.scala | 8 ++++++++ zio-http/src/test/scala/zhttp/socket/SocketSpec.scala | 3 +++ 2 files changed, 11 insertions(+) diff --git a/zio-http/src/main/scala/zhttp/socket/Socket.scala b/zio-http/src/main/scala/zhttp/socket/Socket.scala index 1833aa031d..2bdefef9a7 100644 --- a/zio-http/src/main/scala/zhttp/socket/Socket.scala +++ b/zio-http/src/main/scala/zhttp/socket/Socket.scala @@ -21,6 +21,7 @@ sealed trait Socket[-R, +E, -A, +B] { self => case FMerge(sa, sb) => sa(a) merge sb(a) case Succeed(a) => ZStream.succeed(a) case Provide(s, r) => s(a).asInstanceOf[ZStream[R, E, B]].provide(r.asInstanceOf[R]) + case Empty => ZStream.empty } def contramap[Z](za: Z => A): Socket[R, E, Z, B] = Socket.FCMap(self, za) @@ -65,6 +66,11 @@ object Socket { */ def echo[A]: Socket[Any, Nothing, A, A] = Socket.collect[A] { case a => ZStream.succeed(a) } + /** + * Creates a socket that doesn't do anything. + */ + def empty: Socket[Any, Nothing, Any, Nothing] = Socket.Empty + def end: ZStream[Any, Nothing, Nothing] = ZStream.halt(Cause.empty) def fromFunction[A]: PartialFromFunction[A] = new PartialFromFunction[A](()) @@ -108,4 +114,6 @@ object Socket { private final case class Provide[R, E, A, B](s: Socket[R, E, A, B], r: R) extends Socket[Any, E, A, B] private case object End extends Socket[Any, Nothing, Any, Nothing] + + private case object Empty extends Socket[Any, Nothing, Any, Nothing] } diff --git a/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala b/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala index 04109fa95c..68adc4f7eb 100644 --- a/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala +++ b/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala @@ -63,6 +63,9 @@ object SocketSpec extends DefaultRunnableSpec { } + testM("echo") { assertM(Socket.echo(1).runCollect)(equalTo(Chunk(1))) + } + + testM("empty") { + assertM(Socket.empty(()).runCollect)(isEmpty) } } } From 36f8c83155d6f59a693c036932ce4cf661e7799e Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Thu, 27 Jan 2022 20:27:08 +0530 Subject: [PATCH 12/21] feature: add `toHttp` to Socket.scala (#902) * feature: add `toHttp` to Socket.scala * test: add unit test for `toApp` to Socket.scala Co-authored-by: Shubham Girdhar --- .../src/main/scala/zhttp/socket/Socket.scala | 7 ++++++- .../test/scala/zhttp/socket/SocketSpec.scala | 17 ++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/zio-http/src/main/scala/zhttp/socket/Socket.scala b/zio-http/src/main/scala/zhttp/socket/Socket.scala index 2bdefef9a7..0cf086c7af 100644 --- a/zio-http/src/main/scala/zhttp/socket/Socket.scala +++ b/zio-http/src/main/scala/zhttp/socket/Socket.scala @@ -1,6 +1,6 @@ package zhttp.socket -import zhttp.http.Response +import zhttp.http.{Http, Response} import zio.stream.ZStream import zio.{Cause, NeedsEnv, ZIO} @@ -44,6 +44,11 @@ sealed trait Socket[-R, +E, -A, +B] { self => */ def provide(r: R)(implicit env: NeedsEnv[R]): Socket[Any, E, A, B] = Provide(self, r) + /** + * Converts the Socket into an Http + */ + def toHttp(implicit ev: IsWebSocket[R, E, A, B]): Http[R, E, Any, Response] = Http.fromZIO(toResponse) + /** * Creates a response from the socket. */ diff --git a/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala b/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala index 68adc4f7eb..b0889fadf2 100644 --- a/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala +++ b/zio-http/src/test/scala/zhttp/socket/SocketSpec.scala @@ -1,5 +1,6 @@ package zhttp.socket +import zhttp.http.Status import zio._ import zio.duration.durationInt import zio.stream.ZStream @@ -22,9 +23,7 @@ object SocketSpec extends DefaultRunnableSpec { .provide(text) .execute("") - assertM(socket.runCollect) { - equalTo(Chunk(text)) - } + assertM(socket.runCollect)(equalTo(Chunk(text))) } + testM("fromFunction provide") { val environmentFunction = (_: Any) => ZStream.environment[WebSocketFrame] @@ -33,9 +32,7 @@ object SocketSpec extends DefaultRunnableSpec { .provide(WebSocketFrame.text("Foo")) .execute(WebSocketFrame.text("Bar")) - assertM(socket.runCollect) { - equalTo(Chunk(WebSocketFrame.text("Foo"))) - } + assertM(socket.runCollect)(equalTo(Chunk(WebSocketFrame.text("Foo")))) } + testM("collect provide") { val environment = ZStream.environment[WebSocketFrame] @@ -46,9 +43,7 @@ object SocketSpec extends DefaultRunnableSpec { .provide(WebSocketFrame.ping) .execute(WebSocketFrame.pong) - assertM(socket.runCollect) { - equalTo(Chunk(WebSocketFrame.ping)) - } + assertM(socket.runCollect)(equalTo(Chunk(WebSocketFrame.ping))) } + testM("ordered provide") { val socket = Socket.collect[Int] { case _ => @@ -66,6 +61,10 @@ object SocketSpec extends DefaultRunnableSpec { } + testM("empty") { assertM(Socket.empty(()).runCollect)(isEmpty) + } + + testM("toHttp") { + val http = Socket.succeed(WebSocketFrame.ping).toHttp + assertM(http(()).map(_.status))(equalTo(Status.SWITCHING_PROTOCOLS)) } } } From fc8b9b5a155e7c59b548d191265d30e0e6b61d8b Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Thu, 27 Jan 2022 20:38:38 +0530 Subject: [PATCH 13/21] refactor: merge Request for Client and Server (#894) --- .../src/main/scala/zhttp/http/Request.scala | 4 +- .../src/main/scala/zhttp/service/Client.scala | 63 ++++---------- .../zhttp/service/EncodeClientParams.scala | 58 ++++++------- .../zhttp/http/EncodeClientRequestSpec.scala | 83 ------------------ .../scala/zhttp/http/EncodeRequestSpec.scala | 85 +++++++++++++++++++ .../zhttp/http/GetBodyAsStringSpec.scala | 30 +++---- .../test/scala/zhttp/internal/HttpGen.scala | 7 +- .../zhttp/internal/HttpRunnableSpec.scala | 11 +-- 8 files changed, 152 insertions(+), 189 deletions(-) delete mode 100644 zio-http/src/test/scala/zhttp/http/EncodeClientRequestSpec.scala create mode 100644 zio-http/src/test/scala/zhttp/http/EncodeRequestSpec.scala diff --git a/zio-http/src/main/scala/zhttp/http/Request.scala b/zio-http/src/main/scala/zhttp/http/Request.scala index a930ea5ed5..e78be8918b 100644 --- a/zio-http/src/main/scala/zhttp/http/Request.scala +++ b/zio-http/src/main/scala/zhttp/http/Request.scala @@ -95,8 +95,8 @@ object Request { method: Method = Method.GET, url: URL = URL.root, headers: Headers = Headers.empty, - remoteAddress: Option[InetAddress] = None, data: HttpData = HttpData.Empty, + remoteAddress: Option[InetAddress] = None, ): Request = { val m = method val u = url @@ -121,7 +121,7 @@ object Request { remoteAddress: Option[InetAddress], content: HttpData = HttpData.empty, ): UIO[Request] = - UIO(Request(method, url, headers, remoteAddress, content)) + UIO(Request(method, url, headers, content, remoteAddress)) /** * Lift request to TypedRequest with option to extract params diff --git a/zio-http/src/main/scala/zhttp/service/Client.scala b/zio-http/src/main/scala/zhttp/service/Client.scala index 853eab2be2..40beb22f0f 100644 --- a/zio-http/src/main/scala/zhttp/service/Client.scala +++ b/zio-http/src/main/scala/zhttp/service/Client.scala @@ -2,42 +2,38 @@ package zhttp.service import io.netty.bootstrap.Bootstrap import io.netty.buffer.{ByteBuf, ByteBufUtil} -import io.netty.channel.{ - Channel, - ChannelFactory => JChannelFactory, - ChannelHandlerContext, - EventLoopGroup => JEventLoopGroup, -} -import io.netty.handler.codec.http.HttpVersion +import io.netty.channel.{Channel, ChannelFactory => JChannelFactory, EventLoopGroup => JEventLoopGroup} +import io.netty.handler.codec.http.{FullHttpRequest, HttpVersion} import zhttp.http.URL.Location import zhttp.http._ import zhttp.http.headers.HeaderExtension import zhttp.service -import zhttp.service.Client.{ClientRequest, ClientResponse} +import zhttp.service.Client.ClientResponse import zhttp.service.client.ClientSSLHandler.ClientSSLOptions import zhttp.service.client.{ClientChannelInitializer, ClientInboundHandler} import zio.{Chunk, Promise, Task, ZIO} -import java.net.{InetAddress, InetSocketAddress} +import java.net.InetSocketAddress final case class Client(rtm: HttpRuntime[Any], cf: JChannelFactory[Channel], el: JEventLoopGroup) extends HttpMessageCodec { def request( - request: Client.ClientRequest, + request: Request, sslOption: ClientSSLOptions = ClientSSLOptions.DefaultSSL, ): Task[Client.ClientResponse] = for { promise <- Promise.make[Throwable, Client.ClientResponse] - _ <- Task(asyncRequest(request, promise, sslOption)).catchAll(cause => promise.fail(cause)) + jReq <- encodeClientParams(HttpVersion.HTTP_1_1, request) + _ <- Task(asyncRequest(request, jReq, promise, sslOption)).catchAll(cause => promise.fail(cause)) res <- promise.await } yield res private def asyncRequest( - req: ClientRequest, + req: Request, + jReq: FullHttpRequest, promise: Promise[Throwable, ClientResponse], sslOption: ClientSSLOptions, ): Unit = { - val jReq = encodeClientParams(HttpVersion.HTTP_1_1, req) try { val hand = ClientInboundHandler(rtm, jReq, promise) val host = req.url.host @@ -111,14 +107,14 @@ object Client { method: Method, url: URL, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = - request(ClientRequest(method, url)) + request(Request(method, url)) def request( method: Method, url: URL, sslOptions: ClientSSLOptions, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = - request(ClientRequest(method, url), sslOptions) + request(Request(method, url), sslOptions) def request( method: Method, @@ -126,7 +122,7 @@ object Client { headers: Headers, sslOptions: ClientSSLOptions, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = - request(ClientRequest(method, url, headers), sslOptions) + request(Request(method, url, headers), sslOptions) def request( method: Method, @@ -134,48 +130,19 @@ object Client { headers: Headers, content: HttpData, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = - request(ClientRequest(method, url, headers, content)) + request(Request(method, url, headers, content, None)) def request( - req: ClientRequest, + req: Request, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = make.flatMap(_.request(req)) def request( - req: ClientRequest, + req: Request, sslOptions: ClientSSLOptions, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = make.flatMap(_.request(req, sslOptions)) - final case class ClientRequest( - method: Method, - url: URL, - getHeaders: Headers = Headers.empty, - data: HttpData = HttpData.empty, - private val channelContext: ChannelHandlerContext = null, - ) extends HeaderExtension[ClientRequest] { self => - - def getBodyAsString: Option[String] = data match { - case HttpData.Text(text, _) => Some(text) - case HttpData.BinaryChunk(data) => Some(new String(data.toArray, HTTP_CHARSET)) - case HttpData.BinaryByteBuf(data) => Some(data.toString(HTTP_CHARSET)) - case _ => Option.empty - } - - def remoteAddress: Option[InetAddress] = { - if (channelContext != null && channelContext.channel().remoteAddress().isInstanceOf[InetSocketAddress]) - Some(channelContext.channel().remoteAddress().asInstanceOf[InetSocketAddress].getAddress) - else - None - } - - /** - * Updates the headers using the provided function - */ - override def updateHeaders(update: Headers => Headers): ClientRequest = - self.copy(getHeaders = update(self.getHeaders)) - } - final case class ClientResponse(status: Status, headers: Headers, private[zhttp] val buffer: ByteBuf) extends HeaderExtension[ClientResponse] { self => diff --git a/zio-http/src/main/scala/zhttp/service/EncodeClientParams.scala b/zio-http/src/main/scala/zhttp/service/EncodeClientParams.scala index f9de469a08..ca5d4b4807 100644 --- a/zio-http/src/main/scala/zhttp/service/EncodeClientParams.scala +++ b/zio-http/src/main/scala/zhttp/service/EncodeClientParams.scala @@ -1,41 +1,37 @@ package zhttp.service -import io.netty.buffer.Unpooled import io.netty.handler.codec.http.{DefaultFullHttpRequest, FullHttpRequest, HttpHeaderNames, HttpVersion} -import zhttp.http.HTTP_CHARSET +import zhttp.http.Request +import zio.Task trait EncodeClientParams { /** * Converts client params to JFullHttpRequest */ - def encodeClientParams(jVersion: HttpVersion, req: Client.ClientRequest): FullHttpRequest = { - val method = req.method.asHttpMethod - val url = req.url - - // As per the spec, the path should contain only the relative path. - // Host and port information should be in the headers. - val path = url.relative.encode - - val content = req.getBodyAsString match { - case Some(text) => Unpooled.copiedBuffer(text, HTTP_CHARSET) - case None => Unpooled.EMPTY_BUFFER - } - - val encodedReqHeaders = req.getHeaders.encode - - val headers = url.host match { - case Some(value) => encodedReqHeaders.set(HttpHeaderNames.HOST, value) - case None => encodedReqHeaders - } - - val writerIndex = content.writerIndex() - if (writerIndex != 0) { - headers.set(HttpHeaderNames.CONTENT_LENGTH, writerIndex.toString()) - } - // TODO: we should also add a default user-agent req header as some APIs might reject requests without it. - val jReq = new DefaultFullHttpRequest(jVersion, method, path, content) - jReq.headers().set(headers) - - jReq + def encodeClientParams(jVersion: HttpVersion, req: Request): Task[FullHttpRequest] = req.getBodyAsByteBuf.map { + content => + val method = req.method.asHttpMethod + val url = req.url + + // As per the spec, the path should contain only the relative path. + // Host and port information should be in the headers. + val path = url.relative.encode + + val encodedReqHeaders = req.getHeaders.encode + + val headers = url.host match { + case Some(value) => encodedReqHeaders.set(HttpHeaderNames.HOST, value) + case None => encodedReqHeaders + } + + val writerIndex = content.writerIndex() + if (writerIndex != 0) { + headers.set(HttpHeaderNames.CONTENT_LENGTH, writerIndex.toString()) + } + // TODO: we should also add a default user-agent req header as some APIs might reject requests without it. + val jReq = new DefaultFullHttpRequest(jVersion, method, path, content) + jReq.headers().set(headers) + + jReq } } diff --git a/zio-http/src/test/scala/zhttp/http/EncodeClientRequestSpec.scala b/zio-http/src/test/scala/zhttp/http/EncodeClientRequestSpec.scala deleted file mode 100644 index b69a33246d..0000000000 --- a/zio-http/src/test/scala/zhttp/http/EncodeClientRequestSpec.scala +++ /dev/null @@ -1,83 +0,0 @@ -package zhttp.http - -import io.netty.handler.codec.http.{HttpHeaderNames, HttpVersion} -import zhttp.internal.HttpGen -import zhttp.service.{Client, EncodeClientParams} -import zio.random.Random -import zio.test.Assertion._ -import zio.test._ - -object EncodeClientRequestSpec extends DefaultRunnableSpec with EncodeClientParams { - - val anyClientParam: Gen[Random with Sized, Client.ClientRequest] = HttpGen.clientRequest( - HttpGen.httpData( - Gen.listOf(Gen.alphaNumericString), - ), - ) - - val clientParamWithAbsoluteUrl = HttpGen.clientRequest( - dataGen = HttpGen.httpData( - Gen.listOf(Gen.alphaNumericString), - ), - urlGen = HttpGen.genAbsoluteURL, - ) - - def clientParamWithFiniteData(size: Int): Gen[Random with Sized, Client.ClientRequest] = HttpGen.clientRequest( - for { - content <- Gen.alphaNumericStringBounded(size, size) - data <- Gen.fromIterable(List(HttpData.fromString(content))) - } yield data, - ) - - def spec = suite("EncodeClientParams") { - testM("method") { - check(anyClientParam) { params => - val req = encodeClientParams(HttpVersion.HTTP_1_1, params) - assert(req.method())(equalTo(params.method.asHttpMethod)) - } - } + - testM("method on HttpData.File") { - check(HttpGen.clientParamsForFileHttpData()) { params => - val req = encodeClientParams(HttpVersion.HTTP_1_1, params) - assert(req.method())(equalTo(params.method.asHttpMethod)) - } - } + - suite("uri") { - testM("uri") { - check(anyClientParam) { params => - val req = encodeClientParams(HttpVersion.HTTP_1_1, params) - assert(req.uri())(equalTo(params.url.relative.encode)) - } - } + - testM("uri on HttpData.File") { - check(HttpGen.clientParamsForFileHttpData()) { params => - val req = encodeClientParams(HttpVersion.HTTP_1_1, params) - assert(req.uri())(equalTo(params.url.relative.encode)) - } - } - } + - testM("content-length") { - check(clientParamWithFiniteData(5)) { params => - val req = encodeClientParams(HttpVersion.HTTP_1_1, params) - assert(req.headers().getInt(HttpHeaderNames.CONTENT_LENGTH).toLong)(equalTo(5L)) - } - } + - testM("host header") { - check(anyClientParam) { params => - val req = encodeClientParams(HttpVersion.HTTP_1_1, params) - val hostHeader = HttpHeaderNames.HOST - assert(Option(req.headers().get(hostHeader)))(equalTo(params.url.host)) - } - } + - testM("host header when absolute url") { - check(clientParamWithAbsoluteUrl) { params => - val req = encodeClientParams(HttpVersion.HTTP_1_1, params) - val reqHeaders = req.headers() - val hostHeader = HttpHeaderNames.HOST - - assert(reqHeaders.getAll(hostHeader).size)(equalTo(1)) && - assert(Option(reqHeaders.get(hostHeader)))(equalTo(params.url.host)) - } - } - } -} diff --git a/zio-http/src/test/scala/zhttp/http/EncodeRequestSpec.scala b/zio-http/src/test/scala/zhttp/http/EncodeRequestSpec.scala new file mode 100644 index 0000000000..0dcf1c3b5e --- /dev/null +++ b/zio-http/src/test/scala/zhttp/http/EncodeRequestSpec.scala @@ -0,0 +1,85 @@ +package zhttp.http + +import io.netty.handler.codec.http.{HttpHeaderNames, HttpVersion} +import zhttp.internal.HttpGen +import zhttp.service.EncodeClientParams +import zio.random.Random +import zio.test.Assertion._ +import zio.test._ + +object EncodeRequestSpec extends DefaultRunnableSpec with EncodeClientParams { + + val anyClientParam: Gen[Random with Sized, Request] = HttpGen.clientRequest( + HttpGen.httpData( + Gen.listOf(Gen.alphaNumericString), + ), + ) + + val clientParamWithAbsoluteUrl = HttpGen.clientRequest( + dataGen = HttpGen.httpData( + Gen.listOf(Gen.alphaNumericString), + ), + urlGen = HttpGen.genAbsoluteURL, + ) + + def clientParamWithFiniteData(size: Int): Gen[Random with Sized, Request] = HttpGen.clientRequest( + for { + content <- Gen.alphaNumericStringBounded(size, size) + data <- Gen.fromIterable(List(HttpData.fromString(content))) + } yield data, + ) + + def spec = suite("EncodeClientParams") { + testM("method") { + checkM(anyClientParam) { params => + val method = encodeClientParams(HttpVersion.HTTP_1_1, params).map(_.method()) + assertM(method)(equalTo(params.method.asHttpMethod)) + } + } + + testM("method on HttpData.File") { + checkM(HttpGen.clientParamsForFileHttpData()) { params => + val method = encodeClientParams(HttpVersion.HTTP_1_1, params).map(_.method()) + assertM(method)(equalTo(params.method.asHttpMethod)) + } + } + + suite("uri") { + testM("uri") { + checkM(anyClientParam) { params => + val uri = encodeClientParams(HttpVersion.HTTP_1_1, params).map(_.uri()) + assertM(uri)(equalTo(params.url.relative.encode)) + } + } + + testM("uri on HttpData.File") { + checkM(HttpGen.clientParamsForFileHttpData()) { params => + val uri = encodeClientParams(HttpVersion.HTTP_1_1, params).map(_.uri()) + assertM(uri)(equalTo(params.url.relative.encode)) + } + } + } + + testM("content-length") { + checkM(clientParamWithFiniteData(5)) { params => + val len = encodeClientParams(HttpVersion.HTTP_1_1, params).map( + _.headers().getInt(HttpHeaderNames.CONTENT_LENGTH).toLong, + ) + assertM(len)(equalTo(5L)) + } + } + + testM("host header") { + checkM(anyClientParam) { params => + val hostHeader = HttpHeaderNames.HOST + val headers = encodeClientParams(HttpVersion.HTTP_1_1, params).map(h => Option(h.headers().get(hostHeader))) + assertM(headers)(equalTo(params.url.host)) + } + } + + testM("host header when absolute url") { + checkM(clientParamWithAbsoluteUrl) { params => + val hostHeader = HttpHeaderNames.HOST + for { + reqHeaders <- encodeClientParams(HttpVersion.HTTP_1_1, params).map(_.headers()) + } yield assert(reqHeaders.getAll(hostHeader).size)(equalTo(1)) && assert(Option(reqHeaders.get(hostHeader)))( + equalTo(params.url.host), + ) + } + } + } +} diff --git a/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala b/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala index aad22542d1..bd3da3cc7e 100644 --- a/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala @@ -1,7 +1,6 @@ package zhttp.http import io.netty.handler.codec.http.HttpHeaderNames -import zhttp.service.Client import zio.Chunk import zio.test.Assertion._ import zio.test._ @@ -16,27 +15,26 @@ object GetBodyAsStringSpec extends DefaultRunnableSpec { val charsetGen: Gen[Any, Charset] = Gen.fromIterable(List(UTF_8, UTF_16, UTF_16BE, UTF_16LE, US_ASCII, ISO_8859_1)) - check(charsetGen) { charset => - val encoded = Client - .ClientRequest( - Method.GET, - URL(Path("/")), - getHeaders = Headers(HttpHeaderNames.CONTENT_TYPE.toString, s"text/html; charset=$charset"), - data = HttpData.BinaryChunk(Chunk.fromArray("abc".getBytes())), - ) - .getBodyAsString - val actual = Option(new String(Chunk.fromArray("abc".getBytes(charset)).toArray, charset)) + checkM(charsetGen) { charset => + val encoded = Request( + Method.GET, + URL(Path("/")), + headers = Headers(HttpHeaderNames.CONTENT_TYPE.toString, s"text/html; charset=$charset"), + data = HttpData.BinaryChunk(Chunk.fromArray("abc".getBytes(charset))), + ).getBodyAsString - assert(actual)(equalTo(encoded)) + val expected = new String(Chunk.fromArray("abc".getBytes(charset)).toArray, charset) + + assertM(encoded)(equalTo(expected)) } } + - test("should map bytes to default utf-8 if no charset given") { + testM("should map bytes to default utf-8 if no charset given") { val data = Chunk.fromArray("abc".getBytes()) val content = HttpData.BinaryChunk(data) - val request = Client.ClientRequest(Method.GET, URL(Path("/")), data = content) + val request = Request(Method.GET, URL(Path("/")), data = content) val encoded = request.getBodyAsString - val actual = Option(new String(data.toArray, HTTP_CHARSET)) - assert(actual)(equalTo(encoded)) + val actual = new String(data.toArray, HTTP_CHARSET) + assertM(encoded)(equalTo(actual)) }, ) } diff --git a/zio-http/src/test/scala/zhttp/internal/HttpGen.scala b/zio-http/src/test/scala/zhttp/internal/HttpGen.scala index 996deb4d4a..5b06448608 100644 --- a/zio-http/src/test/scala/zhttp/internal/HttpGen.scala +++ b/zio-http/src/test/scala/zhttp/internal/HttpGen.scala @@ -3,7 +3,6 @@ package zhttp.internal import io.netty.buffer.Unpooled import zhttp.http.URL.Location import zhttp.http._ -import zhttp.service.Client.ClientRequest import zio.random.Random import zio.stream.ZStream import zio.test.{Gen, Sized} @@ -23,7 +22,7 @@ object HttpGen { url <- urlGen headers <- Gen.listOf(headerGen).map(Headers(_)) data <- dataGen - } yield ClientRequest(method, url, headers, data) + } yield Request(method, url, headers, data, None) def clientParamsForFileHttpData() = { for { @@ -31,7 +30,7 @@ object HttpGen { method <- HttpGen.method url <- HttpGen.url headers <- Gen.listOf(HttpGen.header).map(Headers(_)) - } yield ClientRequest(method, url, headers, HttpData.fromFile(file)) + } yield Request(method, url, headers, HttpData.fromFile(file), None) } def cookies: Gen[Random with Sized, Cookie] = for { @@ -119,7 +118,7 @@ object HttpGen { url <- HttpGen.url headers <- Gen.listOf(HttpGen.header).map(Headers(_)) data <- HttpGen.httpData(Gen.listOf(Gen.alphaNumericString)) - } yield Request(method, url, headers, None, data) + } yield Request(method, url, headers, data, None) def response[R](gContent: Gen[R, List[String]]): Gen[Random with Sized with R, Response] = { for { diff --git a/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala b/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala index 1642799a76..5718e794c4 100644 --- a/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala +++ b/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala @@ -21,7 +21,7 @@ import zio.{Has, Task, ZIO, ZManaged} */ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => - implicit class RunnableClientHttpSyntax[R, A](app: Http[R, Throwable, Client.ClientRequest, A]) { + implicit class RunnableClientHttpSyntax[R, A](app: Http[R, Throwable, Request, A]) { /** * Runs the deployed Http app by making a real http request to it. The method allows us to configure individual @@ -34,11 +34,12 @@ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => headers: Headers = Headers.empty, ): ZIO[R, Throwable, A] = app( - Client.ClientRequest( + Request( method, URL(path, Location.Absolute(Scheme.HTTP, "localhost", 0)), headers, HttpData.fromString(content), + None, ), ).catchAll { case Some(value) => ZIO.fail(value) @@ -58,7 +59,7 @@ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => for { port <- Http.fromZIO(DynamicServer.getPort) id <- Http.fromZIO(DynamicServer.deploy(app)) - response <- Http.fromFunctionZIO[Client.ClientRequest] { params => + response <- Http.fromFunctionZIO[Request] { params => Client.request( params .addHeader(DynamicServer.APP_ID, id) @@ -74,7 +75,7 @@ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => def deployWebSocket: HttpTestClient[SttpClient, client3.Response[Either[String, WebSocket[Task]]]] = for { id <- Http.fromZIO(DynamicServer.deploy(app)) res <- - Http.fromFunctionZIO[Client.ClientRequest](params => + Http.fromFunctionZIO[Request](params => for { port <- DynamicServer.getPort url = s"ws://localhost:$port${params.url.path.asString}" @@ -117,7 +118,7 @@ object HttpRunnableSpec { Http[ R with EventLoopGroup with ChannelFactory with DynamicServer with ServerChannelFactory, Throwable, - Client.ClientRequest, + Request, A, ] } From 91db176da235dc301be0e4884ac77a78c01bed59 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Fri, 28 Jan 2022 12:01:26 +0530 Subject: [PATCH 14/21] Revert "refactor: merge Request for Client and Server (#894)" (#915) This reverts commit fc8b9b5a155e7c59b548d191265d30e0e6b61d8b. --- .../src/main/scala/zhttp/http/Request.scala | 4 +- .../src/main/scala/zhttp/service/Client.scala | 63 ++++++++++---- .../zhttp/service/EncodeClientParams.scala | 58 +++++++------ .../zhttp/http/EncodeClientRequestSpec.scala | 83 ++++++++++++++++++ .../scala/zhttp/http/EncodeRequestSpec.scala | 85 ------------------- .../zhttp/http/GetBodyAsStringSpec.scala | 30 ++++--- .../test/scala/zhttp/internal/HttpGen.scala | 7 +- .../zhttp/internal/HttpRunnableSpec.scala | 11 ++- 8 files changed, 189 insertions(+), 152 deletions(-) create mode 100644 zio-http/src/test/scala/zhttp/http/EncodeClientRequestSpec.scala delete mode 100644 zio-http/src/test/scala/zhttp/http/EncodeRequestSpec.scala diff --git a/zio-http/src/main/scala/zhttp/http/Request.scala b/zio-http/src/main/scala/zhttp/http/Request.scala index e78be8918b..a930ea5ed5 100644 --- a/zio-http/src/main/scala/zhttp/http/Request.scala +++ b/zio-http/src/main/scala/zhttp/http/Request.scala @@ -95,8 +95,8 @@ object Request { method: Method = Method.GET, url: URL = URL.root, headers: Headers = Headers.empty, - data: HttpData = HttpData.Empty, remoteAddress: Option[InetAddress] = None, + data: HttpData = HttpData.Empty, ): Request = { val m = method val u = url @@ -121,7 +121,7 @@ object Request { remoteAddress: Option[InetAddress], content: HttpData = HttpData.empty, ): UIO[Request] = - UIO(Request(method, url, headers, content, remoteAddress)) + UIO(Request(method, url, headers, remoteAddress, content)) /** * Lift request to TypedRequest with option to extract params diff --git a/zio-http/src/main/scala/zhttp/service/Client.scala b/zio-http/src/main/scala/zhttp/service/Client.scala index 40beb22f0f..853eab2be2 100644 --- a/zio-http/src/main/scala/zhttp/service/Client.scala +++ b/zio-http/src/main/scala/zhttp/service/Client.scala @@ -2,38 +2,42 @@ package zhttp.service import io.netty.bootstrap.Bootstrap import io.netty.buffer.{ByteBuf, ByteBufUtil} -import io.netty.channel.{Channel, ChannelFactory => JChannelFactory, EventLoopGroup => JEventLoopGroup} -import io.netty.handler.codec.http.{FullHttpRequest, HttpVersion} +import io.netty.channel.{ + Channel, + ChannelFactory => JChannelFactory, + ChannelHandlerContext, + EventLoopGroup => JEventLoopGroup, +} +import io.netty.handler.codec.http.HttpVersion import zhttp.http.URL.Location import zhttp.http._ import zhttp.http.headers.HeaderExtension import zhttp.service -import zhttp.service.Client.ClientResponse +import zhttp.service.Client.{ClientRequest, ClientResponse} import zhttp.service.client.ClientSSLHandler.ClientSSLOptions import zhttp.service.client.{ClientChannelInitializer, ClientInboundHandler} import zio.{Chunk, Promise, Task, ZIO} -import java.net.InetSocketAddress +import java.net.{InetAddress, InetSocketAddress} final case class Client(rtm: HttpRuntime[Any], cf: JChannelFactory[Channel], el: JEventLoopGroup) extends HttpMessageCodec { def request( - request: Request, + request: Client.ClientRequest, sslOption: ClientSSLOptions = ClientSSLOptions.DefaultSSL, ): Task[Client.ClientResponse] = for { promise <- Promise.make[Throwable, Client.ClientResponse] - jReq <- encodeClientParams(HttpVersion.HTTP_1_1, request) - _ <- Task(asyncRequest(request, jReq, promise, sslOption)).catchAll(cause => promise.fail(cause)) + _ <- Task(asyncRequest(request, promise, sslOption)).catchAll(cause => promise.fail(cause)) res <- promise.await } yield res private def asyncRequest( - req: Request, - jReq: FullHttpRequest, + req: ClientRequest, promise: Promise[Throwable, ClientResponse], sslOption: ClientSSLOptions, ): Unit = { + val jReq = encodeClientParams(HttpVersion.HTTP_1_1, req) try { val hand = ClientInboundHandler(rtm, jReq, promise) val host = req.url.host @@ -107,14 +111,14 @@ object Client { method: Method, url: URL, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = - request(Request(method, url)) + request(ClientRequest(method, url)) def request( method: Method, url: URL, sslOptions: ClientSSLOptions, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = - request(Request(method, url), sslOptions) + request(ClientRequest(method, url), sslOptions) def request( method: Method, @@ -122,7 +126,7 @@ object Client { headers: Headers, sslOptions: ClientSSLOptions, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = - request(Request(method, url, headers), sslOptions) + request(ClientRequest(method, url, headers), sslOptions) def request( method: Method, @@ -130,19 +134,48 @@ object Client { headers: Headers, content: HttpData, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = - request(Request(method, url, headers, content, None)) + request(ClientRequest(method, url, headers, content)) def request( - req: Request, + req: ClientRequest, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = make.flatMap(_.request(req)) def request( - req: Request, + req: ClientRequest, sslOptions: ClientSSLOptions, ): ZIO[EventLoopGroup with ChannelFactory, Throwable, ClientResponse] = make.flatMap(_.request(req, sslOptions)) + final case class ClientRequest( + method: Method, + url: URL, + getHeaders: Headers = Headers.empty, + data: HttpData = HttpData.empty, + private val channelContext: ChannelHandlerContext = null, + ) extends HeaderExtension[ClientRequest] { self => + + def getBodyAsString: Option[String] = data match { + case HttpData.Text(text, _) => Some(text) + case HttpData.BinaryChunk(data) => Some(new String(data.toArray, HTTP_CHARSET)) + case HttpData.BinaryByteBuf(data) => Some(data.toString(HTTP_CHARSET)) + case _ => Option.empty + } + + def remoteAddress: Option[InetAddress] = { + if (channelContext != null && channelContext.channel().remoteAddress().isInstanceOf[InetSocketAddress]) + Some(channelContext.channel().remoteAddress().asInstanceOf[InetSocketAddress].getAddress) + else + None + } + + /** + * Updates the headers using the provided function + */ + override def updateHeaders(update: Headers => Headers): ClientRequest = + self.copy(getHeaders = update(self.getHeaders)) + } + final case class ClientResponse(status: Status, headers: Headers, private[zhttp] val buffer: ByteBuf) extends HeaderExtension[ClientResponse] { self => diff --git a/zio-http/src/main/scala/zhttp/service/EncodeClientParams.scala b/zio-http/src/main/scala/zhttp/service/EncodeClientParams.scala index ca5d4b4807..f9de469a08 100644 --- a/zio-http/src/main/scala/zhttp/service/EncodeClientParams.scala +++ b/zio-http/src/main/scala/zhttp/service/EncodeClientParams.scala @@ -1,37 +1,41 @@ package zhttp.service +import io.netty.buffer.Unpooled import io.netty.handler.codec.http.{DefaultFullHttpRequest, FullHttpRequest, HttpHeaderNames, HttpVersion} -import zhttp.http.Request -import zio.Task +import zhttp.http.HTTP_CHARSET trait EncodeClientParams { /** * Converts client params to JFullHttpRequest */ - def encodeClientParams(jVersion: HttpVersion, req: Request): Task[FullHttpRequest] = req.getBodyAsByteBuf.map { - content => - val method = req.method.asHttpMethod - val url = req.url - - // As per the spec, the path should contain only the relative path. - // Host and port information should be in the headers. - val path = url.relative.encode - - val encodedReqHeaders = req.getHeaders.encode - - val headers = url.host match { - case Some(value) => encodedReqHeaders.set(HttpHeaderNames.HOST, value) - case None => encodedReqHeaders - } - - val writerIndex = content.writerIndex() - if (writerIndex != 0) { - headers.set(HttpHeaderNames.CONTENT_LENGTH, writerIndex.toString()) - } - // TODO: we should also add a default user-agent req header as some APIs might reject requests without it. - val jReq = new DefaultFullHttpRequest(jVersion, method, path, content) - jReq.headers().set(headers) - - jReq + def encodeClientParams(jVersion: HttpVersion, req: Client.ClientRequest): FullHttpRequest = { + val method = req.method.asHttpMethod + val url = req.url + + // As per the spec, the path should contain only the relative path. + // Host and port information should be in the headers. + val path = url.relative.encode + + val content = req.getBodyAsString match { + case Some(text) => Unpooled.copiedBuffer(text, HTTP_CHARSET) + case None => Unpooled.EMPTY_BUFFER + } + + val encodedReqHeaders = req.getHeaders.encode + + val headers = url.host match { + case Some(value) => encodedReqHeaders.set(HttpHeaderNames.HOST, value) + case None => encodedReqHeaders + } + + val writerIndex = content.writerIndex() + if (writerIndex != 0) { + headers.set(HttpHeaderNames.CONTENT_LENGTH, writerIndex.toString()) + } + // TODO: we should also add a default user-agent req header as some APIs might reject requests without it. + val jReq = new DefaultFullHttpRequest(jVersion, method, path, content) + jReq.headers().set(headers) + + jReq } } diff --git a/zio-http/src/test/scala/zhttp/http/EncodeClientRequestSpec.scala b/zio-http/src/test/scala/zhttp/http/EncodeClientRequestSpec.scala new file mode 100644 index 0000000000..b69a33246d --- /dev/null +++ b/zio-http/src/test/scala/zhttp/http/EncodeClientRequestSpec.scala @@ -0,0 +1,83 @@ +package zhttp.http + +import io.netty.handler.codec.http.{HttpHeaderNames, HttpVersion} +import zhttp.internal.HttpGen +import zhttp.service.{Client, EncodeClientParams} +import zio.random.Random +import zio.test.Assertion._ +import zio.test._ + +object EncodeClientRequestSpec extends DefaultRunnableSpec with EncodeClientParams { + + val anyClientParam: Gen[Random with Sized, Client.ClientRequest] = HttpGen.clientRequest( + HttpGen.httpData( + Gen.listOf(Gen.alphaNumericString), + ), + ) + + val clientParamWithAbsoluteUrl = HttpGen.clientRequest( + dataGen = HttpGen.httpData( + Gen.listOf(Gen.alphaNumericString), + ), + urlGen = HttpGen.genAbsoluteURL, + ) + + def clientParamWithFiniteData(size: Int): Gen[Random with Sized, Client.ClientRequest] = HttpGen.clientRequest( + for { + content <- Gen.alphaNumericStringBounded(size, size) + data <- Gen.fromIterable(List(HttpData.fromString(content))) + } yield data, + ) + + def spec = suite("EncodeClientParams") { + testM("method") { + check(anyClientParam) { params => + val req = encodeClientParams(HttpVersion.HTTP_1_1, params) + assert(req.method())(equalTo(params.method.asHttpMethod)) + } + } + + testM("method on HttpData.File") { + check(HttpGen.clientParamsForFileHttpData()) { params => + val req = encodeClientParams(HttpVersion.HTTP_1_1, params) + assert(req.method())(equalTo(params.method.asHttpMethod)) + } + } + + suite("uri") { + testM("uri") { + check(anyClientParam) { params => + val req = encodeClientParams(HttpVersion.HTTP_1_1, params) + assert(req.uri())(equalTo(params.url.relative.encode)) + } + } + + testM("uri on HttpData.File") { + check(HttpGen.clientParamsForFileHttpData()) { params => + val req = encodeClientParams(HttpVersion.HTTP_1_1, params) + assert(req.uri())(equalTo(params.url.relative.encode)) + } + } + } + + testM("content-length") { + check(clientParamWithFiniteData(5)) { params => + val req = encodeClientParams(HttpVersion.HTTP_1_1, params) + assert(req.headers().getInt(HttpHeaderNames.CONTENT_LENGTH).toLong)(equalTo(5L)) + } + } + + testM("host header") { + check(anyClientParam) { params => + val req = encodeClientParams(HttpVersion.HTTP_1_1, params) + val hostHeader = HttpHeaderNames.HOST + assert(Option(req.headers().get(hostHeader)))(equalTo(params.url.host)) + } + } + + testM("host header when absolute url") { + check(clientParamWithAbsoluteUrl) { params => + val req = encodeClientParams(HttpVersion.HTTP_1_1, params) + val reqHeaders = req.headers() + val hostHeader = HttpHeaderNames.HOST + + assert(reqHeaders.getAll(hostHeader).size)(equalTo(1)) && + assert(Option(reqHeaders.get(hostHeader)))(equalTo(params.url.host)) + } + } + } +} diff --git a/zio-http/src/test/scala/zhttp/http/EncodeRequestSpec.scala b/zio-http/src/test/scala/zhttp/http/EncodeRequestSpec.scala deleted file mode 100644 index 0dcf1c3b5e..0000000000 --- a/zio-http/src/test/scala/zhttp/http/EncodeRequestSpec.scala +++ /dev/null @@ -1,85 +0,0 @@ -package zhttp.http - -import io.netty.handler.codec.http.{HttpHeaderNames, HttpVersion} -import zhttp.internal.HttpGen -import zhttp.service.EncodeClientParams -import zio.random.Random -import zio.test.Assertion._ -import zio.test._ - -object EncodeRequestSpec extends DefaultRunnableSpec with EncodeClientParams { - - val anyClientParam: Gen[Random with Sized, Request] = HttpGen.clientRequest( - HttpGen.httpData( - Gen.listOf(Gen.alphaNumericString), - ), - ) - - val clientParamWithAbsoluteUrl = HttpGen.clientRequest( - dataGen = HttpGen.httpData( - Gen.listOf(Gen.alphaNumericString), - ), - urlGen = HttpGen.genAbsoluteURL, - ) - - def clientParamWithFiniteData(size: Int): Gen[Random with Sized, Request] = HttpGen.clientRequest( - for { - content <- Gen.alphaNumericStringBounded(size, size) - data <- Gen.fromIterable(List(HttpData.fromString(content))) - } yield data, - ) - - def spec = suite("EncodeClientParams") { - testM("method") { - checkM(anyClientParam) { params => - val method = encodeClientParams(HttpVersion.HTTP_1_1, params).map(_.method()) - assertM(method)(equalTo(params.method.asHttpMethod)) - } - } + - testM("method on HttpData.File") { - checkM(HttpGen.clientParamsForFileHttpData()) { params => - val method = encodeClientParams(HttpVersion.HTTP_1_1, params).map(_.method()) - assertM(method)(equalTo(params.method.asHttpMethod)) - } - } + - suite("uri") { - testM("uri") { - checkM(anyClientParam) { params => - val uri = encodeClientParams(HttpVersion.HTTP_1_1, params).map(_.uri()) - assertM(uri)(equalTo(params.url.relative.encode)) - } - } + - testM("uri on HttpData.File") { - checkM(HttpGen.clientParamsForFileHttpData()) { params => - val uri = encodeClientParams(HttpVersion.HTTP_1_1, params).map(_.uri()) - assertM(uri)(equalTo(params.url.relative.encode)) - } - } - } + - testM("content-length") { - checkM(clientParamWithFiniteData(5)) { params => - val len = encodeClientParams(HttpVersion.HTTP_1_1, params).map( - _.headers().getInt(HttpHeaderNames.CONTENT_LENGTH).toLong, - ) - assertM(len)(equalTo(5L)) - } - } + - testM("host header") { - checkM(anyClientParam) { params => - val hostHeader = HttpHeaderNames.HOST - val headers = encodeClientParams(HttpVersion.HTTP_1_1, params).map(h => Option(h.headers().get(hostHeader))) - assertM(headers)(equalTo(params.url.host)) - } - } + - testM("host header when absolute url") { - checkM(clientParamWithAbsoluteUrl) { params => - val hostHeader = HttpHeaderNames.HOST - for { - reqHeaders <- encodeClientParams(HttpVersion.HTTP_1_1, params).map(_.headers()) - } yield assert(reqHeaders.getAll(hostHeader).size)(equalTo(1)) && assert(Option(reqHeaders.get(hostHeader)))( - equalTo(params.url.host), - ) - } - } - } -} diff --git a/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala b/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala index bd3da3cc7e..aad22542d1 100644 --- a/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala @@ -1,6 +1,7 @@ package zhttp.http import io.netty.handler.codec.http.HttpHeaderNames +import zhttp.service.Client import zio.Chunk import zio.test.Assertion._ import zio.test._ @@ -15,26 +16,27 @@ object GetBodyAsStringSpec extends DefaultRunnableSpec { val charsetGen: Gen[Any, Charset] = Gen.fromIterable(List(UTF_8, UTF_16, UTF_16BE, UTF_16LE, US_ASCII, ISO_8859_1)) - checkM(charsetGen) { charset => - val encoded = Request( - Method.GET, - URL(Path("/")), - headers = Headers(HttpHeaderNames.CONTENT_TYPE.toString, s"text/html; charset=$charset"), - data = HttpData.BinaryChunk(Chunk.fromArray("abc".getBytes(charset))), - ).getBodyAsString + check(charsetGen) { charset => + val encoded = Client + .ClientRequest( + Method.GET, + URL(Path("/")), + getHeaders = Headers(HttpHeaderNames.CONTENT_TYPE.toString, s"text/html; charset=$charset"), + data = HttpData.BinaryChunk(Chunk.fromArray("abc".getBytes())), + ) + .getBodyAsString + val actual = Option(new String(Chunk.fromArray("abc".getBytes(charset)).toArray, charset)) - val expected = new String(Chunk.fromArray("abc".getBytes(charset)).toArray, charset) - - assertM(encoded)(equalTo(expected)) + assert(actual)(equalTo(encoded)) } } + - testM("should map bytes to default utf-8 if no charset given") { + test("should map bytes to default utf-8 if no charset given") { val data = Chunk.fromArray("abc".getBytes()) val content = HttpData.BinaryChunk(data) - val request = Request(Method.GET, URL(Path("/")), data = content) + val request = Client.ClientRequest(Method.GET, URL(Path("/")), data = content) val encoded = request.getBodyAsString - val actual = new String(data.toArray, HTTP_CHARSET) - assertM(encoded)(equalTo(actual)) + val actual = Option(new String(data.toArray, HTTP_CHARSET)) + assert(actual)(equalTo(encoded)) }, ) } diff --git a/zio-http/src/test/scala/zhttp/internal/HttpGen.scala b/zio-http/src/test/scala/zhttp/internal/HttpGen.scala index 5b06448608..996deb4d4a 100644 --- a/zio-http/src/test/scala/zhttp/internal/HttpGen.scala +++ b/zio-http/src/test/scala/zhttp/internal/HttpGen.scala @@ -3,6 +3,7 @@ package zhttp.internal import io.netty.buffer.Unpooled import zhttp.http.URL.Location import zhttp.http._ +import zhttp.service.Client.ClientRequest import zio.random.Random import zio.stream.ZStream import zio.test.{Gen, Sized} @@ -22,7 +23,7 @@ object HttpGen { url <- urlGen headers <- Gen.listOf(headerGen).map(Headers(_)) data <- dataGen - } yield Request(method, url, headers, data, None) + } yield ClientRequest(method, url, headers, data) def clientParamsForFileHttpData() = { for { @@ -30,7 +31,7 @@ object HttpGen { method <- HttpGen.method url <- HttpGen.url headers <- Gen.listOf(HttpGen.header).map(Headers(_)) - } yield Request(method, url, headers, HttpData.fromFile(file), None) + } yield ClientRequest(method, url, headers, HttpData.fromFile(file)) } def cookies: Gen[Random with Sized, Cookie] = for { @@ -118,7 +119,7 @@ object HttpGen { url <- HttpGen.url headers <- Gen.listOf(HttpGen.header).map(Headers(_)) data <- HttpGen.httpData(Gen.listOf(Gen.alphaNumericString)) - } yield Request(method, url, headers, data, None) + } yield Request(method, url, headers, None, data) def response[R](gContent: Gen[R, List[String]]): Gen[Random with Sized with R, Response] = { for { diff --git a/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala b/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala index 5718e794c4..1642799a76 100644 --- a/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala +++ b/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala @@ -21,7 +21,7 @@ import zio.{Has, Task, ZIO, ZManaged} */ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => - implicit class RunnableClientHttpSyntax[R, A](app: Http[R, Throwable, Request, A]) { + implicit class RunnableClientHttpSyntax[R, A](app: Http[R, Throwable, Client.ClientRequest, A]) { /** * Runs the deployed Http app by making a real http request to it. The method allows us to configure individual @@ -34,12 +34,11 @@ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => headers: Headers = Headers.empty, ): ZIO[R, Throwable, A] = app( - Request( + Client.ClientRequest( method, URL(path, Location.Absolute(Scheme.HTTP, "localhost", 0)), headers, HttpData.fromString(content), - None, ), ).catchAll { case Some(value) => ZIO.fail(value) @@ -59,7 +58,7 @@ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => for { port <- Http.fromZIO(DynamicServer.getPort) id <- Http.fromZIO(DynamicServer.deploy(app)) - response <- Http.fromFunctionZIO[Request] { params => + response <- Http.fromFunctionZIO[Client.ClientRequest] { params => Client.request( params .addHeader(DynamicServer.APP_ID, id) @@ -75,7 +74,7 @@ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => def deployWebSocket: HttpTestClient[SttpClient, client3.Response[Either[String, WebSocket[Task]]]] = for { id <- Http.fromZIO(DynamicServer.deploy(app)) res <- - Http.fromFunctionZIO[Request](params => + Http.fromFunctionZIO[Client.ClientRequest](params => for { port <- DynamicServer.getPort url = s"ws://localhost:$port${params.url.path.asString}" @@ -118,7 +117,7 @@ object HttpRunnableSpec { Http[ R with EventLoopGroup with ChannelFactory with DynamicServer with ServerChannelFactory, Throwable, - Request, + Client.ClientRequest, A, ] } From d88c0c4b4462cf89d17ddc44e4246bca9ad26be2 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Fri, 28 Jan 2022 17:58:08 +0530 Subject: [PATCH 15/21] Feature: add `toHttp` to Response (#903) * feature: add `wrapHttp` to Response * test: add unit test for wrapHttp in Response.scala * refactor: operator name changed to `toHttp` Co-authored-by: Shubham Girdhar --- .../src/main/scala/zhttp/http/Response.scala | 5 ++++ ...seHelpersSpec.scala => ResponseSpec.scala} | 26 ++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) rename zio-http/src/test/scala/zhttp/http/{ResponseHelpersSpec.scala => ResponseSpec.scala} (74%) diff --git a/zio-http/src/main/scala/zhttp/http/Response.scala b/zio-http/src/main/scala/zhttp/http/Response.scala index 738aced8c3..94cde589d6 100644 --- a/zio-http/src/main/scala/zhttp/http/Response.scala +++ b/zio-http/src/main/scala/zhttp/http/Response.scala @@ -61,6 +61,11 @@ final case class Response private ( */ def withServerTime: Response = self.copy(attribute = self.attribute.withServerTime) + /** + * Wraps the current response as a Http + */ + def toHttp: Http[Any, Nothing, Any, Response] = Http.succeed(self) + /** * Wraps the current response into a ZIO */ diff --git a/zio-http/src/test/scala/zhttp/http/ResponseHelpersSpec.scala b/zio-http/src/test/scala/zhttp/http/ResponseSpec.scala similarity index 74% rename from zio-http/src/test/scala/zhttp/http/ResponseHelpersSpec.scala rename to zio-http/src/test/scala/zhttp/http/ResponseSpec.scala index d66b219727..4ae3c56ac7 100644 --- a/zio-http/src/test/scala/zhttp/http/ResponseHelpersSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/ResponseSpec.scala @@ -1,11 +1,12 @@ package zhttp.http +import zio.test.Assertion._ import zio.test._ -object ResponseHelpersSpec extends DefaultRunnableSpec { - val redirectSpec = { - val location = "www.google.com" - suite("redirectSpec")( +object ResponseSpec extends DefaultRunnableSpec { + def spec = suite("Response")( + suite("redirect") { + val location = "www.google.com" test("Temporary redirect should produce a response with a TEMPORARY_REDIRECT") { val x = Response.redirect(location) assertTrue(x.status == Status.TEMPORARY_REDIRECT) && @@ -22,14 +23,19 @@ object ResponseHelpersSpec extends DefaultRunnableSpec { test("Permanent redirect should produce a response with a location") { val x = Response.redirect(location, true) assertTrue(x.getHeaderValue(HeaderNames.location).contains(location)) - } + + } + } + + suite("json")( test("Json should set content type to ApplicationJson") { val x = Response.json("""{"message": "Hello"}""") assertTrue(x.getHeaderValue(HeaderNames.contentType).contains(HeaderValues.applicationJson.toString)) }, - ) - } - - def spec = - suite("ResponseHelpers")(redirectSpec) + ) + + suite("toHttp")( + testM("should convert response to Http") { + val http = Response.ok.toHttp + assertM(http(()))(equalTo(Response.ok)) + }, + ), + ) } From 4e2d48c2b636e1a497ee2b423c679a204ec429be Mon Sep 17 00:00:00 2001 From: Amit Kumar Singh Date: Tue, 1 Feb 2022 14:32:24 +0530 Subject: [PATCH 16/21] Update scalafmt-core to 3.4.0 (#920) --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 19a16b06f9..43acb10301 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.3.3 +version = 3.4.0 maxColumn = 120 align.preset = more From 7d5a80ff3f16ecdbfa18a131bf99ab7640192b0d Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Tue, 1 Feb 2022 10:04:43 +0100 Subject: [PATCH 17/21] Update sbt to 1.6.2 (#931) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 3161d2146c..c8fcab543a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.6.1 +sbt.version=1.6.2 From d8b0143768d7901e1cce37a686beeed3e483effb Mon Sep 17 00:00:00 2001 From: Shruti Verma <62893271+ShrutiVerma97@users.noreply.github.com> Date: Tue, 1 Feb 2022 16:33:20 +0530 Subject: [PATCH 18/21] Remove outdated benchmark.md (#940) * removed benchmark file * removed benchmark hyperlink from readme --- BENCHMARKS.md | 232 -------------------------------------------------- README.md | 1 - 2 files changed, 233 deletions(-) delete mode 100644 BENCHMARKS.md diff --git a/BENCHMARKS.md b/BENCHMARKS.md deleted file mode 100644 index 8bf4f1a29b..0000000000 --- a/BENCHMARKS.md +++ /dev/null @@ -1,232 +0,0 @@ -# Table of Contents - -- [Table of Contents](#table-of-contents) -- [Methodology](#methodology) -- [Benchmarks](#benchmarks) - - [ZIO Http](#zio-http) - - [Vert.x](#vertx) - - [Http4s](#http4s) - - [Play](#play) - - [Finagle](#finagle) - -# Methodology - -1. For more realistic benchmarks the client and the server were deployed on different machines, with the following configuration — - - 1. EC2(C5.4xLarge) 16 vCPUs 32 GB RAM as **server**. - 1. EC2(C5.4xLarge) 16 vCPUs 32 GB RAM as **client** with [wrk] setup. - -1. After the servers were started they were warmed up using wrk until the results start stabilizing. - -[wrk]: https://github.com/wg/wrk - -# Benchmarks - -## [ZIO Http](https://github.com/dream11/zio-http) - -[source code](https://github.com/dream11/zio-http/tree/master/example/src/main/scala/HelloWorldAdvanced.scala) - -**Plain Text** - -```dtd -./wrk -t12 -c1000 --latency --timeout=10s --duration=10s http://10.10.105.8:8090/text -Running 10s test @ http://10.10.109.3:8090 - 12 threads and 1000 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 1.37ms 844.59us 206.84ms 97.72% - Req/Sec 60.42k 2.20k 74.51k 70.22% - Latency Distribution - 50% 1.28ms - 75% 1.48ms - 90% 1.72ms - 99% 2.55ms - 7267713 requests in 10.10s, 346.55MB read -Requests/sec: 719576.04 -Transfer/sec: 34.31MB -``` - -**JSON** - -```dtd -./wrk -t12 -c1000 --latency --timeout=10s --duration=10s http://10.10.105.8:8090/json -Running 10s test @ http://10.10.109.3:8090/json - 12 threads and 1000 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 1.40ms 421.62us 32.84ms 90.14% - Req/Sec 58.73k 2.81k 68.19k 68.51% - Latency Distribution - 50% 1.32ms - 75% 1.53ms - 90% 1.80ms - 99% 2.49ms - 7070158 requests in 10.10s, 660.78MB read -Requests/sec: 700073.31 -``` - -## [Vert.x](https://vertx.io/) - -[source code](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Scala/vertx-web-scala) - -**Plain Text** - -```dtd -./wrk -t12 -c1000 --latency --timeout=10s --duration=10s http://10.10.109.3:8080/plaintext -Running 10s test @ http://10.10.109.3:8080/plaintext - 12 threads and 1000 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 4.86ms 20.51ms 455.47ms 96.42% - Req/Sec 59.89k 17.38k 82.30k 82.17% - Latency Distribution - 50% 1.12ms - 75% 1.46ms - 90% 4.20ms - 99% 103.73ms - 7150937 requests in 10.10s, 0.87GB read -Requests/sec: 707991.69 -Transfer/sec: 87.78MB -``` - -**JSON** - -```dtd -./wrk -t12 -c1000 --latency --timeout=10s --duration=10s http://10.10.109.3:8080/json -Running 10s test @ http://10.10.109.3:8080/json - 12 threads and 1000 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 5.98ms 20.68ms 331.64ms 94.99% - Req/Sec 55.01k 19.93k 93.01k 78.66% - Latency Distribution - 50% 1.18ms - 75% 1.69ms - 90% 9.91ms - 99% 114.11ms - 6513121 requests in 10.10s, 0.92GB read -Requests/sec: 644854.27 -Transfer/sec: 92.86MB -``` - -## [Http4s](https://github.com/http4s/http4s) - -[source code](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Scala/http4s) - -**Plain Text** - -```dtd -./wrk -t12 -c1000 --latency --timeout=10s --duration=10s http://10.10.109.3:8080/plaintext -Running 10s test @ http://10.10.109.3:8080/plaintext - 12 threads and 1000 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 78.36ms 455.43ms 6.74s 97.14% - Req/Sec 11.76k 3.34k 47.87k 79.03% - Latency Distribution - 50% 5.35ms - 75% 9.36ms - 90% 45.89ms - 99% 2.46s - 1406517 requests in 10.08s, 202.55MB read -Requests/sec: 139573.98 -Transfer/sec: 20.10MB -``` - -**JSON** - -```dtd -./wrk -t12 -c1000 --latency --timeout=10s --duration=10s http://10.10.109.3:8080/json -Running 10s test @ http://10.10.109.3:8080/json - 12 threads and 1000 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 87.89ms 500.30ms 6.71s 97.02% - Req/Sec 11.43k 3.52k 36.27k 74.58% - Latency Distribution - 50% 5.37ms - 75% 9.45ms - 90% 47.89ms - 99% 2.78s - 1369098 requests in 10.10s, 203.69MB read -Requests/sec: 135565.22 -Transfer/sec: 20.17MB -``` - -## [Play](https://www.playframework.com/documentation/2.8.x/ScalaHome) - -[source code](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Scala/play2-scala) - -**Plain text** - -```dtd -./wrk -t12 -c1000 --latency --timeout=10s --duration=10s http://10.10.109.3:9000/plaintext -Running 10s test @ http://10.10.109.3:9000/plaintext - 12 threads and 1000 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 54.78ms 320.71ms 6.72s 96.49% - Req/Sec 22.46k 8.60k 81.67k 84.98% - Latency Distribution - 50% 3.31ms - 75% 3.84ms - 90% 4.60ms - 99% 1.59s - 2664591 requests in 10.10s, 292.23MB read -Requests/sec: 263819.25 -Transfer/sec: 28.93MB -``` - -**JSON** - -```dtd -./wrk -t12 -c1000 --latency --timeout=10s --duration=10s http://10.10.109.3:9000/json -Running 10s test @ http://10.10.109.3:9000/json - 12 threads and 1000 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 124.11ms 568.53ms 6.68s 95.06% - Req/Sec 22.21k 7.34k 60.60k 84.84% - Latency Distribution - 50% 3.39ms - 75% 3.99ms - 90% 8.18ms - 99% 3.17s - 2638210 requests in 10.10s, 339.66MB read -Requests/sec: 261223.68 -Transfer/sec: 33.63MB -``` - -## [Finagle](https://twitter.github.io/finagle/) - -[source code](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Scala/finagle) - -**Plain Text** - -```dtd -./wrk -t12 -c1000 --latency --timeout=10s --duration=10s http://10.10.110.217:8080/plaintext -Running 10s test @ http://10.10.110.217:8080/plaintext - 12 threads and 1000 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 1.74ms 712.71us 22.53ms 83.65% - Req/Sec 48.17k 2.11k 69.16k 90.63% - Latency Distribution - 50% 1.54ms - 75% 1.96ms - 90% 2.65ms - 99% 4.22ms - 5779092 requests in 10.10s, 721.99MB read -Requests/sec: 572231.69 -Transfer/sec: 71.49MB -``` - -**JSON** - -```dtd -./wrk -t12 -c1000 --latency --timeout=10s --duration=10s http://10.10.110.217:8080/json -Running 10s test @ http://10.10.110.217:8080/json - 12 threads and 1000 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 1.77ms 811.08us 32.39ms 88.24% - Req/Sec 47.65k 2.36k 59.13k 86.19% - Latency Distribution - 50% 1.54ms - 75% 2.03ms - 90% 2.57ms - 99% 4.39ms - 5731384 requests in 10.10s, 825.35MB read -Requests/sec: 567496.97 -Transfer/sec: 81.72MB -``` diff --git a/README.md b/README.md index 095a08891c..b388eaf246 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ Check out the full documentation here: [Documentation] - [ZIO Http](#zio-http) - [Getting Started](#getting-started) - [Installation](#installation) -- [Benchmarks](#benchmarks) - [Documentation](https://dream11.github.io/zio-http/) # Getting Started From 120450b51d3030038f44ca6a65d8e8fb8d770039 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Tue, 1 Feb 2022 16:34:13 +0530 Subject: [PATCH 19/21] refactor: rename `getHeaders` to `headers` in `ClientRequest` (#928) * refactor: rename `getHeaders` to `headers` in `ClientRequest` * fix: compiler errors * fix: compiler errors --- zio-http/src/main/scala/zhttp/service/Client.scala | 6 ++++-- .../src/test/scala/zhttp/http/GetBodyAsStringSpec.scala | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/zio-http/src/main/scala/zhttp/service/Client.scala b/zio-http/src/main/scala/zhttp/service/Client.scala index 853eab2be2..1b2e258140 100644 --- a/zio-http/src/main/scala/zhttp/service/Client.scala +++ b/zio-http/src/main/scala/zhttp/service/Client.scala @@ -150,7 +150,7 @@ object Client { final case class ClientRequest( method: Method, url: URL, - getHeaders: Headers = Headers.empty, + headers: Headers = Headers.empty, data: HttpData = HttpData.empty, private val channelContext: ChannelHandlerContext = null, ) extends HeaderExtension[ClientRequest] { self => @@ -162,6 +162,8 @@ object Client { case _ => Option.empty } + def getHeaders: Headers = headers + def remoteAddress: Option[InetAddress] = { if (channelContext != null && channelContext.channel().remoteAddress().isInstanceOf[InetSocketAddress]) Some(channelContext.channel().remoteAddress().asInstanceOf[InetSocketAddress].getAddress) @@ -173,7 +175,7 @@ object Client { * Updates the headers using the provided function */ override def updateHeaders(update: Headers => Headers): ClientRequest = - self.copy(getHeaders = update(self.getHeaders)) + self.copy(headers = update(self.getHeaders)) } final case class ClientResponse(status: Status, headers: Headers, private[zhttp] val buffer: ByteBuf) diff --git a/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala b/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala index aad22542d1..5e2c97030f 100644 --- a/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/GetBodyAsStringSpec.scala @@ -21,7 +21,7 @@ object GetBodyAsStringSpec extends DefaultRunnableSpec { .ClientRequest( Method.GET, URL(Path("/")), - getHeaders = Headers(HttpHeaderNames.CONTENT_TYPE.toString, s"text/html; charset=$charset"), + headers = Headers(HttpHeaderNames.CONTENT_TYPE.toString, s"text/html; charset=$charset"), data = HttpData.BinaryChunk(Chunk.fromArray("abc".getBytes())), ) .getBodyAsString From aec73823146b58d7b81158c2f9c152c250e98768 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Tue, 1 Feb 2022 16:35:01 +0530 Subject: [PATCH 20/21] Refactor: rename `asString` to `encode` in `Path` (#927) * refactor: rename `asString` to `encode` in Path * fix: compiler errors --- zio-http/src/main/scala/zhttp/http/Cookie.scala | 2 +- zio-http/src/main/scala/zhttp/http/HttpError.scala | 2 +- zio-http/src/main/scala/zhttp/http/PathModule.scala | 10 +++++----- zio-http/src/main/scala/zhttp/http/URL.scala | 2 +- zio-http/src/test/scala/zhttp/http/PathSpec.scala | 8 ++++---- .../test/scala/zhttp/internal/HttpRunnableSpec.scala | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/zio-http/src/main/scala/zhttp/http/Cookie.scala b/zio-http/src/main/scala/zhttp/http/Cookie.scala index 024b434838..bc30b097a1 100644 --- a/zio-http/src/main/scala/zhttp/http/Cookie.scala +++ b/zio-http/src/main/scala/zhttp/http/Cookie.scala @@ -132,7 +132,7 @@ final case class Cookie( expires.map(e => s"Expires=$e"), maxAge.map(a => s"Max-Age=${a.toString}"), domain.map(d => s"Domain=$d"), - path.map(p => s"Path=${p.asString}"), + path.map(p => s"Path=${p.encode}"), if (isSecure) Some("Secure") else None, if (isHttpOnly) Some("HttpOnly") else None, sameSite.map(s => s"SameSite=${s.asString}"), diff --git a/zio-http/src/main/scala/zhttp/http/HttpError.scala b/zio-http/src/main/scala/zhttp/http/HttpError.scala index 71999c77c4..1a71d50a08 100644 --- a/zio-http/src/main/scala/zhttp/http/HttpError.scala +++ b/zio-http/src/main/scala/zhttp/http/HttpError.scala @@ -24,7 +24,7 @@ object HttpError { final case class Forbidden(msg: String = "Forbidden") extends HttpError(Status.FORBIDDEN, msg) final case class NotFound(path: Path) - extends HttpError(Status.NOT_FOUND, s"""The requested URI "${path.asString}" was not found on this server\n""") + extends HttpError(Status.NOT_FOUND, s"""The requested URI "${path.encode}" was not found on this server\n""") final case class MethodNotAllowed(msg: String = "Method Not Allowed") extends HttpError(Status.METHOD_NOT_ALLOWED, msg) diff --git a/zio-http/src/main/scala/zhttp/http/PathModule.scala b/zio-http/src/main/scala/zhttp/http/PathModule.scala index da8375562d..5dae4e1b5b 100644 --- a/zio-http/src/main/scala/zhttp/http/PathModule.scala +++ b/zio-http/src/main/scala/zhttp/http/PathModule.scala @@ -8,13 +8,17 @@ private[zhttp] trait PathModule { module => val Root = !! sealed trait Path { self => + final override def toString: String = this.encode + final def /(name: String): Path = Path(self.toList :+ name) final def /:(name: String): Path = append(name) final def append(name: String): Path = if (name.isEmpty) self else Path.Cons(name, self) - final def asString: String = { + final def drop(n: Int): Path = Path(self.toList.drop(n)) + + final def encode: String = { def loop(self: Path): String = { self match { case Path.End => "" @@ -25,8 +29,6 @@ private[zhttp] trait PathModule { module => if (result.isEmpty) "/" else result } - final def drop(n: Int): Path = Path(self.toList.drop(n)) - final def initial: Path = self match { case Path.End => self case Path.Cons(_, path) => path @@ -58,8 +60,6 @@ private[zhttp] trait PathModule { module => final def take(n: Int): Path = Path(self.toList.take(n)) def toList: List[String] - - final override def toString: String = this.asString } object Path { diff --git a/zio-http/src/main/scala/zhttp/http/URL.scala b/zio-http/src/main/scala/zhttp/http/URL.scala index 43f4c8d2c5..1befd10f50 100644 --- a/zio-http/src/main/scala/zhttp/http/URL.scala +++ b/zio-http/src/main/scala/zhttp/http/URL.scala @@ -85,7 +85,7 @@ object URL { def asString(url: URL): String = { def path: String = { - val encoder = new QueryStringEncoder(s"${url.path.asString}${url.fragment.fold("")(f => "#" + f.raw)}") + val encoder = new QueryStringEncoder(s"${url.path.encode}${url.fragment.fold("")(f => "#" + f.raw)}") url.queryParams.foreach { case (key, values) => if (key != "") values.foreach { value => encoder.addParam(key, value) } } diff --git a/zio-http/src/test/scala/zhttp/http/PathSpec.scala b/zio-http/src/test/scala/zhttp/http/PathSpec.scala index 3457e1804f..0549505738 100644 --- a/zio-http/src/test/scala/zhttp/http/PathSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/PathSpec.scala @@ -32,15 +32,15 @@ object PathSpec extends DefaultRunnableSpec with HExitAssertion { ) + suite("asString")( test("a, b, c") { - val path = Path("a", "b", "c").asString + val path = Path("a", "b", "c").encode assert(path)(equalTo("/a/b/c")) } + test("Path()") { - val path = Path().asString + val path = Path().encode assert(path)(equalTo("/")) } + test("!!") { - val path = !!.asString + val path = !!.encode assert(path)(equalTo("/")) }, ) + @@ -69,7 +69,7 @@ object PathSpec extends DefaultRunnableSpec with HExitAssertion { } + suite("default")( test("extract path 'name' /: name") { - val path = collect { case "name" /: name => name.asString } + val path = collect { case "name" /: name => name.encode } assert(path(Path("name", "a", "b", "c")))(isSome(equalTo("/a/b/c"))) } + test("extract paths 'name' /: a /: b /: 'c' /: !!") { diff --git a/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala b/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala index 1642799a76..4438f517c1 100644 --- a/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala +++ b/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala @@ -77,7 +77,7 @@ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => Http.fromFunctionZIO[Client.ClientRequest](params => for { port <- DynamicServer.getPort - url = s"ws://localhost:$port${params.url.path.asString}" + url = s"ws://localhost:$port${params.url.path.encode}" headerConv = params.addHeader(DynamicServer.APP_ID, id).getHeaders.toList.map(h => SHeader(h._1, h._2)) res <- send(basicRequest.get(uri"$url").copy(headers = headerConv).response(asWebSocketUnsafe)) } yield res, From 21cdee6fe03b653d5fa716885b9f65220a80f332 Mon Sep 17 00:00:00 2001 From: Amit Kumar Singh Date: Tue, 1 Feb 2022 16:38:40 +0530 Subject: [PATCH 21/21] Disable flow Control (#854) --- example/src/main/scala/example/PlainTextBenchmarkServer.scala | 3 ++- zio-http/src/main/scala/zhttp/service/Server.scala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/example/src/main/scala/example/PlainTextBenchmarkServer.scala b/example/src/main/scala/example/PlainTextBenchmarkServer.scala index 33af49d087..7ebf1ecbef 100644 --- a/example/src/main/scala/example/PlainTextBenchmarkServer.scala +++ b/example/src/main/scala/example/PlainTextBenchmarkServer.scala @@ -36,6 +36,7 @@ object Main extends App { Server.error(_ => UIO.unit) ++ Server.keepAlive ++ Server.disableLeakDetection ++ - Server.consolidateFlush + Server.consolidateFlush ++ + Server.disableFlowControl } diff --git a/zio-http/src/main/scala/zhttp/service/Server.scala b/zio-http/src/main/scala/zhttp/service/Server.scala index 711f957734..1b21f0d6b2 100644 --- a/zio-http/src/main/scala/zhttp/service/Server.scala +++ b/zio-http/src/main/scala/zhttp/service/Server.scala @@ -129,7 +129,7 @@ object Server { acceptContinue: Boolean = false, keepAlive: Boolean = false, consolidateFlush: Boolean = false, - flowControl: Boolean = false, + flowControl: Boolean = true, ) /**