diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 0000000000..26cad92ce0 --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1,11 @@ +--- +id: index +title: "ZIO HTTP Examples" +sidebar_label: Examples +--- + +This section aims to demonstrate the usage of key concepts and ideas in the ZIO HTTP library with examples. + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md index 4ba3609e84..b48aded922 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -22,7 +22,7 @@ ZIO HTTP provides built-in support for JSON serialization and deserialization us ### How Can I Handle CORS Requests in ZIO HTTP? -ZIO has several middlewares including `CORS` that can be used to handle cross-origin resource sharing requests. Check out the [Middleware](./reference/middleware.md) section in the documentation for more details. +ZIO has several middlewares including `CORS` that can be used to handle cross-origin resource sharing requests. Check out the [Middleware](./reference/aop/middleware) section in the documentation for more details. ### How Does ZIO HTTP Handle Errors? diff --git a/docs/reference/index.md b/docs/reference/index.md index 1c2cf1b77f..5c9047c726 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -1,246 +1,11 @@ --- id: index -title: Introduction to ZIO HTTP +title: "ZIO HTTP Reference" +sidebar_label: Reference --- -ZIO HTTP offers an expressive API for creating HTTP applications. It uses a domain-specific language (DSL) to define routes and handlers. Both server and client are designed in terms of **HTTP as a function**, so they are functions from `Request` to `Response`. +This section offers a detailed reference for the essential concepts and ideas in the ZIO HTTP library. -## Core Concepts +import DocCardList from '@theme/DocCardList'; -ZIO HTTP has powerful functional domains that help in creating, modifying, and composing apps easily. Let's take a look at the core domain: - -- `Routes` - A collection of `Route`s. If the error type of the routes is `Response`, then they can be served. -- `Route` - A single route that can be matched against an HTTP `Request` and produce a `Response`. It comprises a `RoutePattern` and a `Handler`: - 1. `RoutePattern` - A pattern that can be matched against an HTTP `Request`. It is a combination of `Method` and `PathCodec` which can be used to match the `Method` and `Path` of the `Request`. - 2. `Handler` - A function that can convert a `Request` into a `Response`. - -Let's see each of these concepts inside a simple example: - -```scala mdoc:silent -import zio._ -import zio.http._ - -object ExampleServer extends ZIOAppDefault { - - // A route that matches GET requests to /greet - // It doesn't require any service from the ZIO environment - // so the first type parameter is Any - // All its errors are handled so the second type parameter is Nothing - val greetRoute: Route[Any, Nothing] = - // The whole Method.GET / "greet" is a RoutePattern - Method.GET / "greet" -> - // The handler is a function that takes a Request and returns a Response - handler { (req: Request) => - val name = req.queryParamToOrElse("name", "World") - Response.text(s"Hello $name!") - } - - // A route that matches POST requests to /echo - // It doesn't require any service from the ZIO environment - // It is an unhandled route so the second type parameter is something other than Nothing - val echoRoute: Route[Any, Throwable] = - Method.POST / "echo" -> handler { (req: Request) => - req.body.asString.map(Response.text(_)) - } - - // The Routes that don't require any service from the ZIO environment, - // so the first type parameter is Any. - // All the errors are handled by turning them into a Response. - val routes: Routes[Any, Response] = - // List of all the routes - Routes(greetRoute, echoRoute) - // Handle all unhandled errors - .handleError(e => Response.internalServerError(e.getMessage)) - - // Serving the routes using the default server layer on port 8080 - def run = Server.serve(routes).provide(Server.default) -} -``` - -### 1.Routes - -The `Routes` is a collection of `Route` values. It can be created using its default constructor: - -```scala mdoc:invisible -import zio.http._ - -val greetRoute: Route[Any, Nothing] = Route.notFound -val echoRoute: Route[Any, Throwable] = Route.notFound -``` - -```scala mdoc:silent -import zio.http._ - -val app: Routes[Any, Response] = - Routes(greetRoute, echoRoute) - .handleError(e => Response.internalServerError(e.getMessage)) -``` - -The `Handler` and `Route` can be transformed to `Routes` by the `.toRoutes` method. To serve the routes, all errors should be handled by converting them into a `Response` using for example the `.handleError` method. - -For handling routes, ZIO HTTP has a [`Routes`](routing/routes.md) value, which allows us to aggregate a collection of individual routes. Behind the scenes, ZIO HTTP builds an efficient prefix-tree whenever needed to optimize dispatch. - -### 2. Route - -Each `Route` is a combination of a [`RoutePattern`](routing/route_pattern.md) and a [`Handler`](handler.md). The `RoutePattern` is a combination of a `Method` and a [`PathCodec`](routing/path_codec.md) that can be used to match the method and path of the request. The `Handler` is a function that can convert a `Request` into a `Response`. - -The `PathCodec` can be parameterized to extract values from the path. In such cases, the `Handler` should be a function that accepts the extracted values besides the `Request`: - -```scala mdoc:silent:nest -import zio.http._ - -val routes = Routes( - Method.GET / "user" / int("id") -> - handler { (id: Int, req: Request) => - Response.text(s"Requested User ID: $id") - } -) -``` - -To learn more about routes, see the [Routes](routing/routes.md) page. - -### 3. Handler - -The `Handler` describes the transformation from an incoming `Request` to an outgoing `Response`: - -```scala mdoc:compile-only -val helloHandler = - handler { (_: Request) => - Response.text("Hello World!") - } -``` - -The `Handler` can be effectful, in which case it should be a function that returns a `ZIO` effect, e.g.: - -```scala mdoc:compile-only -val randomGeneratorHandler = - handler { (_: Request) => - Random.nextIntBounded(100).map(_.toString).map(Response.text(_)) - } -``` - -There are several ways to create a `Handler`, to learn more about handlers, see the [Handlers](reference/handler.md) page. - -## Accessing the Request - -To access the request, just create a handler that accepts the request: - -```scala mdoc:silent:reset -import zio.http._ -import zio._ - -val routes = Routes( - Method.GET / "fruits" / "a" -> handler { (req: Request) => - Response.text("URL:" + req.url.path.toString + " Headers: " + req.headers) - }, - - Method.POST / "fruits" / "a" -> handler { (req: Request) => - req.body.asString.map(Response.text(_)) - } -) -``` - -To learn more about the request, see the [Request](request.md) page. - -## Accessing Services from The Environment - -ZIO HTTP is built on top of ZIO, which means that we can access services from the environment in our handlers. For example, we can access a `Ref[Int]` service to create a simple counter: - -```scala mdoc:compile-only -import zio._ -import zio.http._ - -object CounterExample extends ZIOAppDefault { - val routes: Routes[Ref[Int], Response] = - Routes( - Method.GET / "count" / int("n") -> - handler { (n: Int, _: Request) => - for { - ref <- ZIO.service[Ref[Int]] - res <- ref.updateAndGet(_ + n) - } yield Response.text(s"Counter: $res") - }, - ) - - def run = Server.serve(routes).provide(Server.default, ZLayer.fromZIO(Ref.make(0))) -} -``` - -Finally, we should provide the required services to the server using the `provide` method. In the above example, we provided the `Ref[Int]` service using the `ZLayer.fromZIO` method. - -## WebSocket Connection - -To handle WebSocket connections, we can use `Handler.webSocket` to create a socket app. To create a socket app, we need to create a socket that accepts `WebSocketChannel` and produces `ZIO`. 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, which sends `WebsSocketTextFrame` "BAR" on receiving `WebsSocketTextFrame` "FOO": - -```scala mdoc:silent:reset -import zio.http._ -import zio.stream._ -import zio._ - -val socket = - Handler.webSocket { channel => - channel.receiveAll { - case ChannelEvent.Read(WebSocketFrame.Text("FOO")) => - channel.send(ChannelEvent.Read(WebSocketFrame.text("BAR"))) - case _ => - ZIO.unit - } - } - -val routes = - Routes( - Method.GET / "greet" / string("name") -> handler { (name: String, req: Request) => - Response.text(s"Greetings {$name}!") - }, - Method.GET / "ws" -> handler(socket.toResponse) - ) -``` - -We have a more detailed explanation of the WebSocket connection on the [Socket](socket/socket.md) page. - -## Server - -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 our server. The server can be configured according to the leak detection level, request size, address etc. - -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 mdoc:silent:reset -import zio.http._ -import zio._ - -object HelloWorld extends ZIOAppDefault { - val routes = Handler.ok.toRoutes - - override def run = - Server.serve(routes).provide(Server.defaultWithPort(8090)) -} -``` - -Finally, we provided the default server with the port `8090` to the app. To learn more about the server, see the [Server](server.md) page. - -## Client - -Besides creating HTTP apps, ZIO HTTP also provides a way to create HTTP clients. The client can be used to send requests to the server and receive responses: - -```scala mdoc:compile-only -import zio._ -import zio.http._ - -object ClientExample extends ZIOAppDefault { - - val app = - for { - client <- ZIO.serviceWith[Client](_.host("localhost").port(8090)) - response <- client.request(Request.get("/")) - _ <- ZIO.debug("Response Status: " + response.status) - } yield () - - def run = app.provide(Client.default, Scope.default) -} -``` - -In the above example, we obtained the `Client` service from the environment and sent a `GET` request to the server. Finally, to run the client app, we provided the default `Client` and `Scope` services to the app. For more information about the client, see the [Client](client.md) page. + \ No newline at end of file diff --git a/docs/reference/overview.md b/docs/reference/overview.md new file mode 100644 index 0000000000..69aef59001 --- /dev/null +++ b/docs/reference/overview.md @@ -0,0 +1,246 @@ +--- +id: overview +title: Overview +--- + +ZIO HTTP offers an expressive API for creating HTTP applications. It uses a domain-specific language (DSL) to define routes and handlers. Both server and client are designed in terms of **HTTP as a function**, so they are functions from `Request` to `Response`. + +## Core Concepts + +ZIO HTTP has powerful functional domains that help in creating, modifying, and composing apps easily. Let's take a look at the core domain: + +- `Routes` - A collection of `Route`s. If the error type of the routes is `Response`, then they can be served. +- `Route` - A single route that can be matched against an HTTP `Request` and produce a `Response`. It comprises a `RoutePattern` and a `Handler`: + 1. `RoutePattern` - A pattern that can be matched against an HTTP `Request`. It is a combination of `Method` and `PathCodec` which can be used to match the `Method` and `Path` of the `Request`. + 2. `Handler` - A function that can convert a `Request` into a `Response`. + +Let's see each of these concepts inside a simple example: + +```scala mdoc:silent +import zio._ +import zio.http._ + +object ExampleServer extends ZIOAppDefault { + + // A route that matches GET requests to /greet + // It doesn't require any service from the ZIO environment + // so the first type parameter is Any + // All its errors are handled so the second type parameter is Nothing + val greetRoute: Route[Any, Nothing] = + // The whole Method.GET / "greet" is a RoutePattern + Method.GET / "greet" -> + // The handler is a function that takes a Request and returns a Response + handler { (req: Request) => + val name = req.queryParamToOrElse("name", "World") + Response.text(s"Hello $name!") + } + + // A route that matches POST requests to /echo + // It doesn't require any service from the ZIO environment + // It is an unhandled route so the second type parameter is something other than Nothing + val echoRoute: Route[Any, Throwable] = + Method.POST / "echo" -> handler { (req: Request) => + req.body.asString.map(Response.text(_)) + } + + // The Routes that don't require any service from the ZIO environment, + // so the first type parameter is Any. + // All the errors are handled by turning them into a Response. + val routes: Routes[Any, Response] = + // List of all the routes + Routes(greetRoute, echoRoute) + // Handle all unhandled errors + .handleError(e => Response.internalServerError(e.getMessage)) + + // Serving the routes using the default server layer on port 8080 + def run = Server.serve(routes).provide(Server.default) +} +``` + +### 1.Routes + +The `Routes` is a collection of `Route` values. It can be created using its default constructor: + +```scala mdoc:invisible +import zio.http._ + +val greetRoute: Route[Any, Nothing] = Route.notFound +val echoRoute: Route[Any, Throwable] = Route.notFound +``` + +```scala mdoc:silent +import zio.http._ + +val app: Routes[Any, Response] = + Routes(greetRoute, echoRoute) + .handleError(e => Response.internalServerError(e.getMessage)) +``` + +The `Handler` and `Route` can be transformed to `Routes` by the `.toRoutes` method. To serve the routes, all errors should be handled by converting them into a `Response` using for example the `.handleError` method. + +For handling routes, ZIO HTTP has a [`Routes`](routing/routes.md) value, which allows us to aggregate a collection of individual routes. Behind the scenes, ZIO HTTP builds an efficient prefix-tree whenever needed to optimize dispatch. + +### 2. Route + +Each `Route` is a combination of a [`RoutePattern`](routing/route_pattern.md) and a [`Handler`](handler.md). The `RoutePattern` is a combination of a `Method` and a [`PathCodec`](routing/path_codec.md) that can be used to match the method and path of the request. The `Handler` is a function that can convert a `Request` into a `Response`. + +The `PathCodec` can be parameterized to extract values from the path. In such cases, the `Handler` should be a function that accepts the extracted values besides the `Request`: + +```scala mdoc:silent:nest +import zio.http._ + +val routes = Routes( + Method.GET / "user" / int("id") -> + handler { (id: Int, req: Request) => + Response.text(s"Requested User ID: $id") + } +) +``` + +To learn more about routes, see the [Routes](routing/routes.md) page. + +### 3. Handler + +The `Handler` describes the transformation from an incoming `Request` to an outgoing `Response`: + +```scala mdoc:compile-only +val helloHandler = + handler { (_: Request) => + Response.text("Hello World!") + } +``` + +The `Handler` can be effectful, in which case it should be a function that returns a `ZIO` effect, e.g.: + +```scala mdoc:compile-only +val randomGeneratorHandler = + handler { (_: Request) => + Random.nextIntBounded(100).map(_.toString).map(Response.text(_)) + } +``` + +There are several ways to create a `Handler`, to learn more about handlers, see the [Handlers](reference/handler.md) page. + +## Accessing the Request + +To access the request, just create a handler that accepts the request: + +```scala mdoc:silent:reset +import zio.http._ +import zio._ + +val routes = Routes( + Method.GET / "fruits" / "a" -> handler { (req: Request) => + Response.text("URL:" + req.url.path.toString + " Headers: " + req.headers) + }, + + Method.POST / "fruits" / "a" -> handler { (req: Request) => + req.body.asString.map(Response.text(_)) + } +) +``` + +To learn more about the request, see the [Request](request.md) page. + +## Accessing Services from The Environment + +ZIO HTTP is built on top of ZIO, which means that we can access services from the environment in our handlers. For example, we can access a `Ref[Int]` service to create a simple counter: + +```scala mdoc:compile-only +import zio._ +import zio.http._ + +object CounterExample extends ZIOAppDefault { + val routes: Routes[Ref[Int], Response] = + Routes( + Method.GET / "count" / int("n") -> + handler { (n: Int, _: Request) => + for { + ref <- ZIO.service[Ref[Int]] + res <- ref.updateAndGet(_ + n) + } yield Response.text(s"Counter: $res") + }, + ) + + def run = Server.serve(routes).provide(Server.default, ZLayer.fromZIO(Ref.make(0))) +} +``` + +Finally, we should provide the required services to the server using the `provide` method. In the above example, we provided the `Ref[Int]` service using the `ZLayer.fromZIO` method. + +## WebSocket Connection + +To handle WebSocket connections, we can use `Handler.webSocket` to create a socket app. To create a socket app, we need to create a socket that accepts `WebSocketChannel` and produces `ZIO`. 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, which sends `WebsSocketTextFrame` "BAR" on receiving `WebsSocketTextFrame` "FOO": + +```scala mdoc:silent:reset +import zio.http._ +import zio.stream._ +import zio._ + +val socket = + Handler.webSocket { channel => + channel.receiveAll { + case ChannelEvent.Read(WebSocketFrame.Text("FOO")) => + channel.send(ChannelEvent.Read(WebSocketFrame.text("BAR"))) + case _ => + ZIO.unit + } + } + +val routes = + Routes( + Method.GET / "greet" / string("name") -> handler { (name: String, req: Request) => + Response.text(s"Greetings {$name}!") + }, + Method.GET / "ws" -> handler(socket.toResponse) + ) +``` + +We have a more detailed explanation of the WebSocket connection on the [Socket](socket/socket.md) page. + +## Server + +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 our server. The server can be configured according to the leak detection level, request size, address etc. + +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 mdoc:silent:reset +import zio.http._ +import zio._ + +object HelloWorld extends ZIOAppDefault { + val routes = Handler.ok.toRoutes + + override def run = + Server.serve(routes).provide(Server.defaultWithPort(8090)) +} +``` + +Finally, we provided the default server with the port `8090` to the app. To learn more about the server, see the [Server](server.md) page. + +## Client + +Besides creating HTTP apps, ZIO HTTP also provides a way to create HTTP clients. The client can be used to send requests to the server and receive responses: + +```scala mdoc:compile-only +import zio._ +import zio.http._ + +object ClientExample extends ZIOAppDefault { + + val app = + for { + client <- ZIO.serviceWith[Client](_.host("localhost").port(8090)) + response <- client.request(Request.get("/")) + _ <- ZIO.debug("Response Status: " + response.status) + } yield () + + def run = app.provide(Client.default, Scope.default) +} +``` + +In the above example, we obtained the `Client` service from the environment and sent a `GET` request to the server. Finally, to run the client app, we provided the default `Client` and `Scope` services to the app. For more information about the client, see the [Client](client.md) page. \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js index c3d357b674..3fd14961e5 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -4,102 +4,133 @@ const sidebars = { type: "category", label: "ZIO HTTP", collapsed: true, + + // main documentation index link: { type: "doc", id: "index" }, items: [ - "installation", + "installation", + + // Reference section { type: "category", collapsed: true, link: { type: "doc", id: "reference/index" }, label: "Reference", items: [ - "reference/server", - "reference/client", + "reference/overview", + "reference/server", + "reference/client", + "reference/endpoint", + + // Routing subsection { type: "category", label: "Routing", items: [ "reference/routing/routes", - "reference/routing/route_pattern", + "reference/routing/route_pattern", "reference/routing/path_codec", ], }, - "reference/handler", + + "reference/handler", + + // HTTP Messages subsection { type: "category", label: "HTTP Messages", items: [ "reference/request", "reference/response/response", + "reference/response/status", + + // Headers subsection { type: "category", label: "Headers", items: [ - "reference/headers/headers", - "reference/headers/session/cookies", - "reference/headers/session/flash", + "reference/headers/headers", + "reference/headers/session/cookies", + "reference/headers/session/flash", ], }, + + // Message Body subsection { type: "category", label: "Message Body", items: [ - "reference/body/body", + "reference/body/body", "reference/body/form", "reference/body/binary_codecs", - "reference/body/template", + "reference/body/template", ], }, - "reference/response/status", + ], }, - "reference/endpoint", + + + // Aspects subsection { type: "category", label: "Aspects", items: [ - "reference/aop/protocol-stack", - "reference/aop/middleware", - "reference/aop/handler_aspect", + "reference/aop/protocol-stack", + "reference/aop/middleware", + "reference/aop/handler_aspect", ], }, + + // WebSocket subsection { type: "category", label: "WebSocket", items: [ - "reference/socket/socket", - "reference/socket/websocketframe", + "reference/socket/socket", + "reference/socket/websocketframe", ], }, ], }, - "testing-http-apps", - "faq", + + { + // Subcategory: Tutorials + type: "category", + label: "Tutorials", + items: [ + "tutorials/testing-http-apps", + ], + }, + + // Examples section { type: "category", label: "Examples", - link: { type: "doc", id: "index" }, + link: { type: "doc", id: "examples/index" }, items: [ - "examples/hello-world", - "examples/http-client-server", - "examples/https-client-server", + "examples/hello-world", + "examples/http-client-server", + "examples/https-client-server", "examples/serving-static-files", - "examples/html-templating", - "examples/websocket", - "examples/streaming", - "examples/endpoint", - "examples/middleware-cors-handling", - "examples/authentication", + "examples/html-templating", + "examples/websocket", + "examples/streaming", + "examples/endpoint", + "examples/middleware-cors-handling", + "examples/authentication", "examples/graceful-shutdown", - "examples/cli", - "examples/concrete-entity", - "examples/multipart-form-data", + "examples/cli", + "examples/concrete-entity", + "examples/multipart-form-data", "examples/server-sent-events-in-endpoints", ], }, + + "faq", ], }, ], }; -module.exports = sidebars; +module.exports = sidebars; \ No newline at end of file diff --git a/docs/testing-http-apps.md b/docs/tutorials/testing-http-apps.md similarity index 100% rename from docs/testing-http-apps.md rename to docs/tutorials/testing-http-apps.md