Skip to content

Commit

Permalink
Merge branch 'main' into refeactor/combine-request-responses
Browse files Browse the repository at this point in the history
# Conflicts:
#	zio-http/src/main/scala/zhttp/service/EncodeClientParams.scala
#	zio-http/src/test/scala/zhttp/http/EncodeClientRequestSpec.scala
  • Loading branch information
tusharmath committed Jan 27, 2022
2 parents 55089b8 + 0a3d486 commit 50b18a7
Show file tree
Hide file tree
Showing 19 changed files with 310 additions and 99 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
3 changes: 0 additions & 3 deletions docs/website/docs/v1.x/dsl/server/config.md

This file was deleted.

109 changes: 109 additions & 0 deletions docs/website/docs/v1.x/dsl/server/index.md
Original file line number Diff line number Diff line change
@@ -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.

<details>
<summary><b>A complete example </b></summary>

- 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
}
}
```
</details>

## 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. |
90 changes: 66 additions & 24 deletions docs/website/docs/v1.x/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,30 @@ sidebar_position: 2

# Getting Started

**ZIO HTTP** is a powerful library that is used to build highly performant HTTP-based services and clients using functional scala and ZIO and uses [Netty](https://netty.io/) as its core.
ZIO HTTP has powerful functional domains which help in creating, modifying, composing apps easily. Let's start with the HTTP domain.
The first step when using ZIO HTTP is creating an HTTP app.

## Http

`Http` is a domain that models HTTP apps using ZIO and works over any request and response types. `Http` Domain provides different constructors to create HTTP apps, `Http.text`, `Http.html`, `Http.fromFile`, `Http.fromData`, `Http.fromStream`, `Http.fromEffect`.

### Creating a "_Hello World_" app

Creating an HTTP app using ZIO Http is as simple as given below, this app will always respond with "Hello World!"

```scala
import zhttp.http._

val app = Http.text("Hello World!")
```

An application can be made using any of the available operators on `zhttp.Http`. In the above program for any Http request, the response is always `"Hello World!"`.
An app can be made using any of the available constructors on `zhttp.Http`.

### Routing

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
import zhttp.http._

Expand All @@ -26,34 +36,56 @@ val app = Http.collect[Request] {
case Method.GET -> !! / "fruits" / "b" => Response.text("Banana")
}
```

Pattern matching on route is supported by the framework
You can create typed routes as well. The below example shows how to accept count as `Int` only.
```scala
import zhttp.http._

val app = Http.collect[Request] {
case Method.GET -> !! / "Apple" / int(count) => Response.text(s"Apple: $count")
}
```

### Composition

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
```

Apps can be composed using the `<>` operator. The way it works is, if none of the routes match in `a` , or a `NotFound` error is thrown from `a`, and 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.

```scala
val app = Http.collectZIO[Request] {
case Method.GET -> !! / "hello" => Response.text("Hello World").wrapZIO
}
```

`Http.collectZIO` allow routes to return a ZIO effect value.

### Accessing the Request

To access the request use `@` as it binds a matched pattern to a variable and can be used while creating a response.

```scala
import zhttp.http._

Expand All @@ -67,29 +99,36 @@ val app = Http.collectZIO[Request] {

### Testing

zhttp provides a `zhttp-test` package for use in unit tests. You can utilize it as follows:
Since `Http` is a function of the form `A => ZIO[R, Option[E], B]` to test it you can simply call an `Http` like a function.

```scala
import zio.test._
import zhttp.test._
import zhttp.http._

object Spec extends DefaultRunnableSpec {

def spec = suite("http")(
testM("should be ok") {
val app = Http.ok
val req = Request()
assertM(app(req))(equalTo(Response.ok)) // an apply method is added via `zhttp.test` package
assertM(app(req))(equalTo(Response.ok))
}
)
}
```
When we call the `app` with the `request` it calls the apply method of `Http` via `zhttp.test` package

## Socket

`Socket` is functional domain in ZIO HTTP. It provides constructors to create socket apps.
A socket app is an app that handles WebSocket connections.

### Creating a socket app

Socket app can be created by using `Socket` constructors. To create a socket app, you need to create a socket that accepts `WebSocketFrame` and produces `ZStream` of `WebSocketFrame`.
Finally, we need to convert socketApp to `Response` using `toResponse`, so that we can run it like any other HTTP app.
The below example shows a simple socket app, we are using `collect` which returns a stream with WebsSocketTextFrame "BAR" on receiving WebsSocketTextFrame "FOO".

```scala
import zhttp.socket._

Expand All @@ -105,7 +144,12 @@ private val socket = Socket.collect[WebSocketFrame] { case WebSocketFrame.Text("

## Server

### Starting an Http App
As we have seen how to create HTTP apps, the only thing left is to run an HTTP server and serve requests.
ZIO HTTP provides a way to set configurations for your server. The server can be configured according to the leak detection level, request size, address etc.

### Starting an HTTP App

To launch our app, we need to start the server on a port. The below example shows a simple HTTP app that responds with empty content and a `200` status code, deployed on port `8090` using `Server.start`.

```scala
import zhttp.http._
Expand All @@ -120,14 +164,12 @@ object HelloWorld extends App {
}
```

A simple Http app that responds with empty content and a `200` status code is deployed on port `8090` using `Server.start`.

## 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)
21 changes: 20 additions & 1 deletion docs/website/docs/v1.x/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,23 @@ sidebar_label: "Setup"
---

# Setup
Work in progress

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
)
```
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
15 changes: 15 additions & 0 deletions zio-http/src/main/scala/zhttp/http/Http.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<<<`
*/
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion zio-http/src/main/scala/zhttp/http/Scheme.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package zhttp.http

import io.netty.handler.codec.http.HttpScheme
sealed trait Scheme { self =>
def asString: String = Scheme.asString(self)
def encode: String = Scheme.asString(self)
}
object Scheme {
def asString(self: Scheme): String = self match {
Expand Down
6 changes: 3 additions & 3 deletions zio-http/src/main/scala/zhttp/http/URL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final case class URL(
case _ => self.copy(kind = URL.Location.Relative)
}

def asString: String = URL.asString(self)
def encode: String = URL.asString(self)
}
object URL {
sealed trait Location
Expand Down Expand Up @@ -95,8 +95,8 @@ object URL {
url.kind match {
case Location.Relative => path
case Location.Absolute(scheme, host, port) =>
if (port == 80 || port == 443) s"${scheme.asString}://$host$path"
else s"${scheme.asString}://$host:$port$path"
if (port == 80 || port == 443) s"${scheme.encode}://$host$path"
else s"${scheme.encode}://$host:$port$path"
}
}

Expand Down
Loading

2 comments on commit 50b18a7

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance Benchmark:

Concurrency: 256
Requests/sec: 927671.06

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance Benchmark:

Concurrency: 256
Requests/sec: 911794.17

Please sign in to comment.