From 97162e0c39d07447ba390a5a9dc5b2697c91fdd9 Mon Sep 17 00:00:00 2001 From: daveads Date: Mon, 5 Jun 2023 19:53:06 +0100 Subject: [PATCH 01/71] sidebarsjs setup --- docs/dsl/body.md | 77 ---- docs/dsl/cookies.md | 139 ------- docs/dsl/headers.md | 233 ----------- docs/dsl/html.md | 78 ---- docs/dsl/http.md | 364 ------------------ docs/dsl/middleware.md | 275 ------------- docs/dsl/request.md | 70 ---- docs/dsl/response.md | 83 ---- docs/dsl/server.md | 63 --- docs/dsl/socket/socket.md | 56 --- docs/dsl/socket/websocketframe.md | 106 ----- docs/examples/advanced/authentication.md | 66 ---- docs/examples/advanced/concrete-entity.md | 36 -- .../middleware-basic-authentication.md | 27 -- .../advanced/middleware-cors-handling.md | 38 -- docs/examples/advanced/server.md | 48 --- docs/examples/advanced/streaming-file.md | 39 -- docs/examples/advanced/streaming-response.md | 37 -- docs/examples/advanced/websocket-server.md | 58 --- docs/examples/basic/http-client.md | 25 -- docs/examples/basic/http-server.md | 23 -- docs/examples/basic/https-client.md | 42 -- docs/examples/basic/https-server.md | 43 --- docs/examples/basic/websocket.md | 40 -- docs/faq.md | 0 docs/getting-started.md | 209 ---------- docs/index.md | 46 +-- docs/performance.md | 0 docs/quickstart.md | 0 docs/setup.md | 70 ---- docs/sidebar.js | 80 ++++ docs/sidebars.js | 76 ---- 32 files changed, 82 insertions(+), 2465 deletions(-) delete mode 100644 docs/dsl/body.md delete mode 100644 docs/dsl/cookies.md delete mode 100644 docs/dsl/headers.md delete mode 100644 docs/dsl/html.md delete mode 100644 docs/dsl/http.md delete mode 100644 docs/dsl/middleware.md delete mode 100644 docs/dsl/request.md delete mode 100644 docs/dsl/response.md delete mode 100644 docs/dsl/server.md delete mode 100644 docs/dsl/socket/socket.md delete mode 100644 docs/dsl/socket/websocketframe.md delete mode 100644 docs/examples/advanced/authentication.md delete mode 100644 docs/examples/advanced/concrete-entity.md delete mode 100644 docs/examples/advanced/middleware-basic-authentication.md delete mode 100644 docs/examples/advanced/middleware-cors-handling.md delete mode 100644 docs/examples/advanced/server.md delete mode 100644 docs/examples/advanced/streaming-file.md delete mode 100644 docs/examples/advanced/streaming-response.md delete mode 100644 docs/examples/advanced/websocket-server.md delete mode 100644 docs/examples/basic/http-client.md delete mode 100644 docs/examples/basic/http-server.md delete mode 100644 docs/examples/basic/https-client.md delete mode 100644 docs/examples/basic/https-server.md delete mode 100644 docs/examples/basic/websocket.md create mode 100644 docs/faq.md delete mode 100644 docs/getting-started.md create mode 100644 docs/performance.md create mode 100644 docs/quickstart.md create mode 100644 docs/sidebar.js delete mode 100644 docs/sidebars.js diff --git a/docs/dsl/body.md b/docs/dsl/body.md deleted file mode 100644 index dedd45e103..0000000000 --- a/docs/dsl/body.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -id: body -title: Body ---- - -`Body` is a domain to model content for `Request`, `Response` and `ClientRequest`. ZIO HTTP uses Netty at it's core and Netty handles content as `ByteBuf`. `Body` helps you decode and encode this content into simpler, easier to use data types while creating a Request or Response. - -## Server-side usage of `Body` - -On the server-side, `ZIO-HTTP` models content in `Request` and `Response` as `Body` with `Body.Empty` as the default value. To add content while creating a `Response` you can use the `Response` constructor: - -```scala mdoc:silent - import zio._ - import zio.http._ - import zio.stream._ - - val res: Response = Response( body = Body.fromString("Some String")) -``` - -To add content while creating a `Request` for unit tests, you can use the `Request` constructor: - -```scala mdoc:silent - val req: Request = Request.post(Body.fromString("Some String"), URL(Root / "save")) -``` - -## Client-side usage of `Body` - -On the client-side, `ZIO-HTTP` models content in `ClientRequest` as `Body` with `Body.Empty` as the default value. - -To add content while making a request using ZIO HTTP you can use the `Client.request` method: - -```scala mdoc:silent - val actual: ZIO[Client, Throwable, Response] = - Client.request("https://localhost:8073/success", content = Body.fromString("Some string")) -``` - -## Creating a Body - -### Creating a Body from a `String` - -To create an `Body` that encodes a String you can use `Body.fromString`: - -```scala mdoc:silent - val textHttpData: Body = Body.fromString("any string", Charsets.Http) -``` - -### Creating a Body from `Chunk of Bytes` - -To create an `Body` that encodes chunk of bytes you can use `Body.fromChunk`: - -```scala mdoc:silent - val chunkHttpData: Body = Body.fromChunk(Chunk.fromArray("Some Sting".getBytes(Charsets.Http))) -``` - -### Creating a Body from a `Stream` - -To create an `Body` that encodes a Stream you can use `Body.fromStream`. - -- Using a Stream of Bytes - -```scala mdoc:silent - val streamHttpData1: Body = Body.fromStream(ZStream.fromChunk(Chunk.fromArray("Some String".getBytes(Charsets.Http)))) -``` - -- Using a Stream of String - -```scala mdoc:silent - val streamHttpData2: Body = Body.fromStream(ZStream("a", "b", "c"), Charsets.Http) -``` - -### Creating a Body from a `File` - -To create an `Body` that encodes a File you can use `Body.fromFile`: - -```scala mdoc:silent:crash - val fileHttpData: Body = Body.fromFile(new java.io.File(getClass.getResource("/fileName.txt").getPath)) -``` diff --git a/docs/dsl/cookies.md b/docs/dsl/cookies.md deleted file mode 100644 index 1f09a91896..0000000000 --- a/docs/dsl/cookies.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -id: cookies -title: Cookies ---- - -**ZIO HTTP** has special support for Cookie headers using the `Cookie` Domain to add and invalidate cookies. Adding a -cookie will generate the correct [Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) -headers - -## Create a Request Cookie - -A request cookie consists of `name` and `content` and can be created with `Cookie.Request`: - -```scala mdoc -import zio._ -import zio.http._ - -val cookie: Cookie = Cookie.Request("id", "abc") -``` - -### Updating a request cookie - -- `withContent` updates the content of cookie - -```scala mdoc -cookie.withContent("def") -``` - -- `withName` updates the name of cookie - -```scala mdoc -cookie.withName("id2") -``` - -## Create a Response Cookie - -A Response `Cookie` can be created with -params `name`, `content`, `expires`, `domain`, `path`, `isSecure`, `isHttpOnly`, `maxAge`, `sameSite` and `secret` -according to HTTP [Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) - -The below snippet creates a response cookie from the above request cookie: - -```scala mdoc -val responseCookie = cookie.toResponse -``` - -### Update a Response Cookie - -`Cookie.Response` is a case class so it can be updated by its `copy` method: - -- `maxAge` updates the max-age of the cookie - -```scala mdoc -responseCookie.copy(maxAge = Some(5.days)) -``` - -- `domain` updates the host to which the cookie will be sent - -```scala mdoc -responseCookie.copy(domain = Some("example.com")) -``` - -- `path` updates the path of the cookie - -```scala mdoc -responseCookie.copy(path = Some(Root / "cookie")) -``` - -- `isSecure` enables cookie only on https server - -```scala mdoc -responseCookie.copy(isSecure = true) -``` - -- `isHttpOnly` forbids JavaScript from accessing the cookie - -```scala mdoc -responseCookie.copy(isHttpOnly = true) -``` - -- `sameSite` updates whether or not a cookie is sent with cross-origin requests - -```scala mdoc -responseCookie.copy(sameSite = Some(Cookie.SameSite.Strict)) -``` - -## Sign a Cookie - -The cookies can be signed with a signature: - -- Using `sign` - To sign a cookie, you can use `sign` - -```scala mdoc -val cookie2 = Cookie.Response("key", "hello", maxAge = Some(5.days)) -val app = Http.collect[Request] { case Method.GET -> Root / "cookie" => - Response.ok.addCookie(cookie2.sign("secret")) -} -``` - -- Using `signCookies` middleware - -To sign all the cookies in your `HttpApp`, you can use `signCookies` middleware: - -```scala mdoc -import RequestHandlerMiddlewares.signCookies - -private val app2 = Http.collect[Request] { - case Method.GET -> Root / "cookie" => Response.ok.addCookie(cookie2) - case Method.GET -> Root / "secure-cookie" => Response.ok.addCookie(cookie2.copy(isSecure = true)) -} - -// Run it like any simple app -def run(args: List[String]): ZIO[Any, Throwable, Nothing] = - Server.serve(app2 @@ signCookies("secret")) - .provide(Server.default) -``` - -## Adding Cookie in Response - -The cookies can be added in `Response` headers: - -```scala mdoc -val res = Response.ok.addCookie(responseCookie) -``` - -It updates the response header `Set-Cookie` as ```Set-Cookie: =``` - -## Getting Cookie from Request - -In HTTP requests, cookies are stored in the `cookie` header. `cookiesDecoded` can be used to get all the cookies in the -request: - -```scala mdoc - private val app3 = Http.collect[Request] { - case req@Method.GET -> Root / "cookie" => - Response.text(req.header(Header.Cookie).map(_.value.toChunk).getOrElse(Chunk.empty).mkString("")) -} -``` diff --git a/docs/dsl/headers.md b/docs/dsl/headers.md deleted file mode 100644 index caa8d51bfb..0000000000 --- a/docs/dsl/headers.md +++ /dev/null @@ -1,233 +0,0 @@ ---- -id: headers -title: Headers ---- - -**ZIO HTTP** provides support for all HTTP headers (as defined -in [RFC2616](https://datatracker.ietf.org/doc/html/rfc2616) ) along with custom headers. - -## Server-side - -### Attaching Headers to `Response` - -On the server-side, `ZIO-HTTP` is adding a collection of pre-defined headers to the response, according to the HTTP -specification, additionally, users may add other headers, including custom headers. - -There are multiple ways to attach headers to a response: - -Using `addHeaders` helper on response: -- - -```scala mdoc -import zio._ -import zio.http._ - -Response.ok.addHeader(Header.ContentLength(0L)) -``` - -Through `Response` constructors: - -```scala mdoc -Response( - status = Status.Ok, - // Setting response header - headers = Headers(Header.ContentLength(0L)), - body = Body.empty -) -``` - -Using `Middlewares`: - -```scala mdoc -import RequestHandlerMiddlewares.addHeader - -Handler.ok @@ addHeader(Header.ContentLength(0L)) -``` - -### Reading Headers from `Request` - -On the Server-side you can read Request headers as given below - -```scala mdoc -Http.collect[Request] { - case req@Method.GET -> Root / "streamOrNot" => Response.text(req.headers.map(_.toString).mkString("\n")) -} -``` - -
-Detailed examples - -Example below shows how the Headers could be added to a response by using `Response` constructors and how a custom -header is added to `Response` through `addHeader`: - -```scala mdoc:silent -import zio._ -import zio.http._ -import zio.stream._ - -object SimpleResponseDispatcher extends ZIOAppDefault { - override def run = - // Starting the server (for more advanced startup configuration checkout `HelloWorldAdvanced`) - Server.serve(app).provide(Server.default) - - // Create a message as a Chunk[Byte] - val message = Chunk.fromArray("Hello world !\r\n".getBytes(Charsets.Http)) - // Use `Http.collect` to match on route - val app: App[Any] = - Http.collect[Request] { - // Simple (non-stream) based route - case Method.GET -> Root / "health" => Response.ok - - // From Request(req), the headers are accessible. - case req@Method.GET -> Root / "streamOrNot" => - // Checking if client is able to handle streaming response - val acceptsStreaming: Boolean = req.header(Header.Accept).exists(_.mimeTypes.contains(Header.Accept.MediaTypeWithQFactor(MediaType.application.`octet-stream`, None))) - if (acceptsStreaming) - Response( - status = Status.Ok, - // Setting response header - headers = Headers(Header.ContentLength(message.length.toLong)), // adding CONTENT-LENGTH header - body = Body.fromStream(ZStream.fromChunk(message)), // Encoding content using a ZStream - ) - else { - // Adding a custom header to Response - Response(status = Status.Accepted, body = Body.fromChunk(message)).addHeader("X-MY-HEADER", "test") - } - } -} - -``` - -The following example shows how Headers could be added to `Response` in a `RequestHandlerMiddleware` implementation: - -```scala mdoc:silent - -/** - * Creates an authentication middleware that only allows authenticated requests to be passed on to the app. - */ -final def customAuth( - verify: Headers => Boolean, - responseHeaders: Headers = Headers.empty, - responseStatus: Status = Status.Unauthorized, - ): RequestHandlerMiddleware[Nothing, Any, Nothing, Any] = - new RequestHandlerMiddleware.Simple[Any, Nothing] { - override def apply[R1 <: Any, Err1 >: Nothing]( - handler: Handler[R1, Err1, Request, Response], - )(implicit trace: Trace): Handler[R1, Err1, Request, Response] = - Handler.fromFunctionHandler[Request] { request => - if (verify(request.headers)) handler - else Handler.status(responseStatus).addHeaders(responseHeaders) - } - } - -``` - -More examples: - -- [BasicAuth](https://github.com/zio/zio-http/blob/main/example/src/main/scala/BasicAuth.scala) -- [Authentication](https://github.com/zio/zio-http/blob/main/example/src/main/scala/Authentication.scala) - -
- -## Client-side - -### Adding headers to `Request` - -ZIO-HTTP provides a simple way to add headers to a client `Request`. - -```scala mdoc:silent -val headers = Headers(Header.Host("sports.api.decathlon.com"), Header.Accept(MediaType.application.json)) -Client.request("http://sports.api.decathlon.com/test", headers = headers) -``` - -### Reading headers from `Response` - -```scala mdoc:silent -Client.request("http://sports.api.decathlon.com/test").map(_.headers) -``` - -
-Detailed examples - -- The sample below shows how a header could be added to a client request: - -```scala mdoc:silent -import zio._ -import zio.http._ - -object SimpleClientJson extends ZIOAppDefault { - val url = "http://sports.api.decathlon.com/groups/water-aerobics" - // Construct headers - val headers = Headers(Header.Host("sports.api.decathlon.com"), Header.Accept(MediaType.application.json)) - - val program = for { - // Pass headers to request - res <- Client.request(url, headers = headers) - // List all response headers - _ <- Console.printLine(res.headers.toList.mkString("\n")) - data <- - // Check if response contains a specified header with a specified value. - if (res.header(Header.ContentType).exists(_.mediaType == MediaType.application.json)) - res.body.asString - else - res.body.asString - _ <- Console.printLine(data) - } yield () - - override def run = - program.provide(Client.default) - -} -``` - -
- -## Headers DSL - -Headers DSL provides plenty of powerful operators that can be used to add, remove, modify and verify headers. Headers -APIs could be used on client, server, and middleware. - -`zio.http.Headers` - represents an immutable collection of headers -`zio.http.Header` - a collection of all the standard headers - -`Headers` have following type of helpers - -Constructors - Provides a list of helpful methods that can create `Headers`. - -```scala mdoc -// create a simple Accept header: -Headers(Header.Accept(MediaType.application.json)) - -// create a basic authentication header: -Headers(Header.Authorization.Basic("username", "password")) -``` - -Getters - Provides a list of operators that parse and extract data from the `Headers`. - -```scala mdoc - -// retrieving the value of Accept header value: -val acceptHeader: Headers = Headers(Header.Accept(MediaType.application.json)) -val acceptHeaderValue: Option[CharSequence] = acceptHeader.header(Header.Accept).map(_.renderedValue) - - -// retrieving a bearer token from Authorization header: -val authorizationHeader: Headers = Headers(Header.Authorization.Bearer("test")) -val authorizationHeaderValue: Option[String] = acceptHeader.header(Header.Authorization).map(_.renderedValue) -``` - -Modifiers - Provides a list of operators that modify the current `Headers`. Once modified, a new instance of the same -type is returned. - -```scala mdoc -// add Accept header: -Headers.empty.addHeader(Header.Accept(MediaType.application.json)) -``` - -Checks - Provides a list of operators that checks if the `Headers` meet the give constraints. - -```scala mdoc -val contentTypeHeader: Headers = Headers(Header.ContentType(MediaType.application.json)) -val isHeaderPresent: Boolean = contentTypeHeader.hasHeader(Header.ContentType) -val isJsonContentType: Boolean = contentTypeHeader.hasJsonContentType -``` diff --git a/docs/dsl/html.md b/docs/dsl/html.md deleted file mode 100644 index cca03e5173..0000000000 --- a/docs/dsl/html.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -id: html -title: Html ---- - -The package `zio.http.html._` contains lightweight helpers for generating statically typed, safe html similiar in spirit to `scalatags`. - -## Html and DOM - -### Html from string - -One possible way is to create an instance of `Html` directly from a `String` value, with the obvious drawback of not having checks -from the compiler: - -```scala mdoc:silent -import zio.http.html._ - -val divHtml1: Html = Html.fromString("""""") -``` - -### Html from constructors - -In order to improve on type safety one could use `Html` with `Dom` constructor functions, with the drawback that the resulting -code is much more verbose: - -```scala mdoc:silent -import zio.http.html._ - -val divHtml2: Html = Html.fromDomElement( - Dom.element( - "div", - Dom.attr("class", "container1 container2"), - Dom.element( - "a", - Dom.attr("href", "http://zio.dev"), - Dom.text("ZIO Homepage") - ) - ) -) -``` - -Please note that both values `divHtml1` and `divHtml2` produce identical html output. - -### Html from Tag API - -Practically one would very likely not use one of the above mentioned versions but instead use the `Tag API`. That API lets one use not only html -elements like `div` or `a` but also html attributes like `hrefAttr` or `styleAttr` as scala functions. By convention values of html attributes -are suffixed `attr` to easily distinguish those from html elements: - -```scala mdoc:silent -import zio.http.html._ - -val divHtml3: Html = div( - classAttr := "container1" :: "container2" :: Nil, - a(hrefAttr := "http://zio.dev", "ZIO Homepage") -) -``` - -Also `divHtml3` produces identical html output as `divHtml1` and `divHtml2`. - -Html elements like `div` or `a` are represented as values of `PartialElement` which have an `apply` method for nesting html elements, -html attributes and text values. Html attributes are represented as values of `PartialAttribute` which provides an operator `:=` for "assigning" -attribute values. Besides `:=` attributes also have an `apply` method that provides an alternative syntax e.g. instead -of `a(hrefAttr := "http://zio.dev", "ZIO Homepage")` one can use `a(hrefAttr("http://zio.dev"), "ZIO Homepage")`. - -### Html composition - -One can compose values of `Html` sequentially using the operator `++` to produce a larger `Html`: - -```scala mdoc:silent -import zio.http.html._ - -val fullHtml: Html = divHtml1 ++ divHtml2 ++ divHtml3 -``` - -## Html response - -One can create a successful http response in routing code from a given value of `Html` with `Response.html`. diff --git a/docs/dsl/http.md b/docs/dsl/http.md deleted file mode 100644 index b218944b1b..0000000000 --- a/docs/dsl/http.md +++ /dev/null @@ -1,364 +0,0 @@ ---- -id: http -title: Http ---- - -`Http` is a functional domain that models HTTP applications. It’s polymorphic on input and output type. - -A `Http[-R, +E, -A, +B]` models a function from `A` to `ZIO[R, Option[E], Handler[R, E, A, B]]`. -When a value of type `A` is evaluated against an `Http[R,E,A,B]`, it can either succeed with a `Handler`, fail with -a `Some[E]` or if `A` is not defined in the application, fail with `None`. - -`Handler[-R, +E, -A, +B]` models a function that takes an `A` and returns a `B`, possibly failing with an `E` and using -a ZIO effect. A handler can always succeed with a value (or fail) no matter what its input is. - -`Http` on the other hand can decide to not handle a particular input, so it adds input based _routing_ on top of -the `Handler` type. - -Both `Handler` and `Http` provides several operators and constructors to model the application as per your use case. - -## Creating an HTTP Application - -### HTTP application that always succeeds - -To create an HTTP application that always returns the same response and never fails, you can use the `succeed` -constructor. - -```scala mdoc:silent -import zio._ -import zio.http._ - -val app1: Http[Any, Nothing, Any, Int] = Handler.succeed(1).toHttp -``` - -### HTTP application that always fails - -To create an HTTP application that always fails with the given error, you can use the `fail` constructor. - -```scala mdoc:silent -val app2: Http[Any, Error, Any, Nothing] = Handler.fail(new Error("Error_Message")).toHttp -``` - -HTTP applications can also be created from total and partial functions. These are some constructors to create HTTP -applications from total as well as partial functions. - -### HTTP application from a partial function - -`Http.collect` can create an `Http[Any, Nothing, A, B]` from a `PartialFunction[A, B]`. In case the input is not defined -for the partial function, the application will return a `None` type error. - -```scala mdoc:silent -val app3: Http[Any, Nothing, String, String] = - Http.collect[String] { - case "case 1" => "response 1" - case "case 2" => "response 2" - } -``` - -`Http.collectZIO` can be used to create a `Http[R, E, A, B]` from a partial function that returns a ZIO effect, -i.e `PartialFunction[A, ZIO[R, E, B]`. This constructor is used when the output is effectful. - -```scala mdoc:silent -val app4: Http[Any, Nothing, String, String] = - Http.collectZIO[String] { - case "case 1" => ZIO.succeed("response 1") - } -``` - -### HTTP application from a total function - -`Handler.fromFunction` can create an `Http[Any, Nothing, A, B]` from a function `f: A=>B`. This can be converted to -a `Http` which always routes to that given `Handler` by using `toHttp`: - -```scala mdoc:silent -val app5: Http[Any, Nothing, Int, Int] = - Handler.fromFunction[Int](i => i + 1).toHttp -``` - -`Handler.fromFunctionZIO` can create a `Http[R, E, A, B]` from a function that returns a ZIO effect, -i.e `f: A => ZIO[R, E, B]`. - -```scala mdoc:silent -val app6: Http[Any, Nothing, Int, Int] = - Handler.fromFunctionZIO[Int](i => ZIO.succeed(i + 1)).toHttp -``` - -## Transforming Http Applications - -Http operators are used to transform one or more HTTP applications to create a new HTTP application. Http domain -provides plenty of such powerful operators. - -### Transforming the output - -To transform the output of the HTTP application, you can use `map` operator . It takes a function `f: B=>C` to convert -a `Http[R,E,A,B]`to `Http[R,E,A,C]`. - -```scala mdoc:silent -val app7: Http[Any, Nothing, Any, String] = Handler.succeed("text").toHttp -val app8: Http[Any, Nothing, Any, Int] = app7.map(s => s.length()) -``` - -To transform the output of the HTTP application effectfully, you can use `mapZIO` operator. It takes a -function `B => ZIO[R1, E1, C]` to convert a `Http[R,E,A,B]` to `Http[R,E,A,C]`. - -```scala mdoc:silent -val app9: Http[Any, Nothing, Any, Int] = app7.mapZIO(s => ZIO.succeed(s.length())) -``` - -### Transforming the input - -To transform the input of a `Handler`, you can use `contramap` operator. -Before passing the input on to the HTTP application, `contramap` applies a function `xa: X => A` on it. - -```scala mdoc:silent -val handler1: Handler[Any, Nothing, String, String] = Handler.fromFunction[String](s => s + ' ' + s) -val app10: Http[Any, Nothing, Int, String] = handler1.contramap[Int](_.toString).toHttp -``` - -To transform the input of the handler effectfully, you can use `contramapZIO` operator. Before passing the -input on to the HTTP application, `contramapZIO` applies a function `xa: X => ZIO[R1, E1, A]` on it. - -```scala mdoc:silent -val app11: Http[Any, Any, Int, String] = handler1.contramapZIO[Any, Any, Int](a => ZIO.succeed(a.toString)).toHttp -``` - -### Chaining handlers - -To chain two handlers applications, you can use `flatMap` operator.It creates a new `Handler[R1, E1, A1, C1]` from the -output -of a `Handler[R,E,A,B]`, using a function `f: B => Handler[R1, E1, A1, C1]`. `>>=` is an alias for flatMap. - -```scala mdoc:silent -val handler2: Handler[Any, Nothing, Any, String] = Handler.succeed("text1") -val handler3: Handler[Any, Nothing, Any, String] = handler2 >>= (s => Handler.succeed(s + " text2")) -``` - -### Folding a handler - -`foldHandler` lets you handle the success and failure values of a handler. It takes in two functions, one for -failure and one for success, and one more handler. - -- If the handler fails with `E` the first function will be executed with `E`, -- If the application succeeds with `B`, the second function will be executed with `B` and - -```scala mdoc:silent -val handler4: Handler[Any, String, String, String] = Handler.fromFunctionHandler[String] { - case "case" => Handler.fail("1") - case _ => Handler.succeed("2") -} -val handler5: Handler[Any, Nothing, String, String] = handler4.foldHandler(e => Handler.succeed(e), s => Handler.succeed(s)) -``` - -## Error Handling - -These are several ways in which error handling can be done in both the `Http` and the `Handler` domains. - -### Catch all errors - -To catch all errors in case of failure of an HTTP application, you can use `catchAllZIO` operator. It pipes the error to -a -function `f: E => Http[R1, E1, A1, B1]`. - -```scala mdoc:silent -val app12: Http[Any, Throwable, Any, Nothing] = Handler.fail(new Throwable("Error_Message")).toHttp -val app13: Http[Any, Nothing, Any, Option[Throwable]] = app12.catchAllZIO(e => ZIO.succeed(Option(e))) -``` - -### Mapping the error - -To transform the failure of an HTTP application, you can use `mapError` operator. It pipes the error to a -function `ee: E => E1`. - -```scala mdoc:silent -val app14: Http[Any, Throwable, Any, Nothing] = Handler.fail(new Throwable("Error_Message")).toHttp -val app15: Http[Any, Option[Throwable], Any, Nothing] = app14.mapError(e => Option(e)) -``` - -## Composition of HTTP applications - -HTTP applications can be composed using several special operators. - -### Using `++` - -`++` is an alias for `defaultWith`. While using `++`, if the first HTTP application returns `None` the second HTTP -application will be evaluated, ignoring the result from the first. If the first HTTP application is failing with -a `Some[E]` the second HTTP application won't be evaluated. - -```scala mdoc:silent -val app16: Http[Any, Nothing, String, String] = Http.collect[String] { - case "case 1" => "response 1" - case "case 2" => "response 2" -} -val app17: Http[Any, Nothing, String, String] = Http.collect[String] { - case "case 3" => "response 3" - case "case 4" => "response 4" -} -val app18: Http[Any, Nothing, String, String] = app16 ++ app17 -``` - -### Using `<>` - -`<>` is an alias for `orElse`. While using `<>`, if the first handler fails with `Some[E]`, the second handler will be -evaluated, ignoring the result from the first. This operator is not available on the `Http` level, to keep the rules of -applying middlewares simple. - -```scala mdoc:silent -val handler6: Handler[Any, Nothing, Any, Int] = Handler.fail(1) <> Handler.succeed(2) -``` - -### Using `>>>` - -`>>>` is an alias for `andThen`. It runs the first HTTP application and pipes the output into the other handler. -The right side must be a `Handler`, it cannot perform further routing. - -```scala mdoc:silent -val app19: Http[Any, Nothing, Int, Int] = Handler.fromFunction[Int](a => a + 1).toHttp -val handler7: Handler[Any, Nothing, Int, Unit] = Handler.fromFunctionZIO[Int](b => ZIO.debug(b * 2)) -val app20: Http[Any, Nothing, Int, Unit] = app19 >>> handler7 -``` - -### Using `<<<` - -`<<<` is the alias for `compose`. Compose is similar to andThen, but it is only available on the `Handler` level. -It runs the second handler and pipes the output to the first handler. - -```scala mdoc:silent -val handler8: Handler[Any, Nothing, Int, Int] = Handler.fromFunction[Int](a => a + 1) -val handler9: Handler[Any, Nothing, Int, Int] = Handler.fromFunction[Int](b => b * 2) -val handler10: Handler[Any, Nothing, Int, Int] = handler8 <<< handler9 -``` - -## Providing environments - -There are many operators to provide the HTTP application with its required environment, they work the same as the ones -on `ZIO`. - -## Attaching Middleware - -Middlewares are essentially transformations that one can apply to any `Http` or a `Handler` to produce a new one. To -attach middleware -to the HTTP application, you can use `middleware` operator. `@@` is an alias for `middleware`. - -`RequestHandlerMiddleware` applies to `Handler`s converting a HTTP `Request` to `Response`. You can apply -a `RequestHandlerMiddleware` to both `Handler` and `Http`. -When applying it to a `Http`, it is equivalent to applying it to all handlers the `Http` can route to. -`HttpAppMiddleware` applies only to `Http`s and they are capable of change the routing behavior. - -## Unit testing - -Since an HTTP application `Http[R, E, A, B]` is a function from `A` to `ZIO[R, Option[E], B]`, we can write unit tests -just like we do for normal functions. - -The below snippet tests an app that takes `Int` as input and responds by adding 1 to the input. - -```scala mdoc:silent -import zio.test.Assertion.equalTo -import zio.test.{test, _} - -object Spec extends ZIOSpecDefault { - - def spec = suite("http")( - test("1 + 1 = 2") { - val app: Http[Any, Nothing, Int, Int] = Handler.fromFunction[Int](_ + 1).toHttp - assertZIO(app.runZIO(1))(equalTo(2)) - } - ) -} -``` - -# What is App? - -`App[-R]` is a type alias for `Http[R, Response, Request, Response]`. -ZIO HTTP server runs `App[E]` only. It is an application that takes a `Request` as an input, and it either produces -a successful `Response` or in case of failure it produces also a `Response`, representing the failure message to be sent -back. - -## Special Constructors for Http and Handler - -These are some special constructors for `Http` and `Handler`: - -### Handler.ok - -Creates a `Handler` that always responds with a 200 status code. - -```scala mdoc:silent -Handler.ok -``` - -### Handler.text - -Creates a `Handler` that always responds with the same plain text. - -```scala mdoc:silent -Handler.text("Text Response") -``` - -### Handler.status - -Creates a `Handler` that always responds with the same status code and empty data. - -```scala mdoc:silent -Handler.status(Status.Ok) -``` - -### Handler.error - -Creates a `Handler` that always fails with the given `HttpError`. - -```scala mdoc:silent -Handler.error(HttpError.Forbidden()) -``` - -### Handler.response - -Creates an `Handler` that always responds with the same `Response`. - -```scala mdoc:silent -Handler.response(Response.ok) -``` - -## Special operators on Handler - -These are some special operators for `Handler`s. - -### withMethod - -Overwrites the method in the incoming request to the `Handler` - -```scala mdoc:silent -val handler11 = Handler.fromFunction((request: Request) => Response.text(request.method.toString)) -handler11.withMethod(Method.POST) -``` - -### patch - -Patches the response produced by the request handler using a `Patch`. - -```scala mdoc:silent -val handler12 = Handler.response(Response.text("Hello World!")) -val handler13 = handler12.patch(Response.Patch.withStatus(Status.Accepted)) -``` - -## Converting an `Http` to `App` - -If you want to run an `Http[R, E, A, B]` app on the ZIO HTTP server you need to convert it to `App[R]` using -operators like `map`, `contramap`, etc. - -Custom errors can be converted to `Response` using `mapError` or you can use `withDefaultErrorHandling` to convert -all custom errors into internal server error responses. - -If a `Http` can never fail (has `Nothing` as its error type), there is no need to use `withDefaultErrorHandling` -or `mapError`. - -## Running an App - -ZIO HTTP server needs an `App[R]` for running. We can use `Server.serve()` method to bootstrap the server with -an `App[R]`: - -```scala mdoc:silent -object HelloWorld extends ZIOAppDefault { - val app: App[Any] = Handler.ok.toHttp - - override def run = Server.serve(app).provide(Server.default) -} -``` diff --git a/docs/dsl/middleware.md b/docs/dsl/middleware.md deleted file mode 100644 index 9791e18704..0000000000 --- a/docs/dsl/middleware.md +++ /dev/null @@ -1,275 +0,0 @@ ---- -id: middleware -title: Middleware ---- - -Before introducing middleware, let us understand why they are needed. - -Consider the following example where we have two endpoints within HttpApp -* GET a single user by id -* GET all users - -```scala -private val app = Http.collectZIO[Request] { - case Method.GET -> Root / "users" / id => - // core business logic - dbService.lookupUsersById(id).map(Response.json(_.json)) - case Method.GET -> Root / "users" => - // core business logic - dbService.paginatedUsers(pageNum).map(Response.json(_.json)) -} -``` - -#### The polluted code violates the principle of "Separation of concerns" - -As our application grows, we want to code the following aspects like -* Basic Auth -* Request logging -* Response logging -* Timeout and retry - -For both of our example endpoints, our core business logic gets buried under boilerplate like this - -```scala -(for { - // validate user - _ <- MyAuthService.doAuth(request) - // log request - _ <- logRequest(request) - // core business logic - user <- dbService.lookupUsersById(id).map(Response.json(_.json)) - resp <- Response.json(user.toJson) - // log response - _ <- logResponse(resp) -} yield resp) - .timeout(2.seconds) - .retryN(5) -``` -Imagine repeating this for all our endpoints!!! - -So there are two problems with this approach -* We are dangerously coupling our business logic with cross-cutting concerns (like applying timeouts) -* Also, addressing these concerns will require updating code for every single route in the system. For 100 routes we will need to repeat 100 timeouts!!! -* For example, any change related to a concern like the logging mechanism from logback to log4j2 may cause changing signature of `log(..)` function in 100 places. -* On the other hand, this also makes testing core business logic more cumbersome. - - -This can lead to a lot of boilerplate clogging our neatly written endpoints affecting readability, thereby leading to increased maintenance costs. - -## Need for middlewares and handling "aspects" - -If we refer to Wikipedia for the definition of an "[Aspect](https://en.wikipedia.org/wiki/Aspect_(computer_programming))" we can glean the following points. - -* An aspect of a program is a feature linked to many other parts of the program (**_most common example, logging_**)., -* But it is not related to the program's primary function (**_core business logic_**) -* An aspect crosscuts the program's core concerns (**_for example logging code intertwined with core business logic_**), -* Therefore, it can violate the principle of "separation of concerns" which tries to encapsulate unrelated functions. (**_Code duplication and maintenance nightmare_**) - -Or in short, aspect is a common concern required throughout the application, and its implementation could lead to repeated boilerplate code and in violation of the principle of separation of concerns. - -There is a paradigm in the programming world called [aspect-oriented programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming) that aims for modular handling of these common concerns in an application. - -Some examples of common "aspects" required throughout the application -- logging, -- timeouts (preventing long-running code) -- retries (or handling flakiness for example while accessing third party APIs) -- authenticating a user before using the REST resource (basic, or custom ones like OAuth / single sign-on, etc). - -This is where middleware comes to the rescue. -Using middlewares we can compose out-of-the-box middlewares (or our custom middlewares) to address the above-mentioned concerns using ++ and @@ operators as shown below. - -#### Cleaned up code using middleware to address cross-cutting concerns like auth, request/response logging, etc. -Observe, how we can address multiple cross-cutting concerns using neatly composed middlewares, in a single place. - -```scala mdoc:silent -import zio._ -import zio.http._ - -// compose basic auth, request/response logging, timeouts middlewares -val composedMiddlewares = RequestHandlerMiddlewares.basicAuth("user","pw") ++ - RequestHandlerMiddlewares.debug ++ - RequestHandlerMiddlewares.timeout(5.seconds) -``` - -And then we can attach our composed bundle of middlewares to an Http using `@@` - -```scala -val app = Http.collectZIO[Request] { - case Method.GET -> Root / "users" / id => - // core business logic - dbService.lookupUsersById(id).map(Response.json(_.json)) - case Method.GET -> Root / "users" => - // core business logic - dbService.paginatedUsers(pageNum).map(Response.json(_.json)) -} @@ composedMiddlewares // attach composedMiddlewares to the app using @@ -``` - -Observe how we gained the following benefits by using middlewares -* **Readability**: de-cluttering business logic. -* **Modularity**: we can manage aspects independently without making changes in 100 places. For example, - * replacing the logging mechanism from logback to log4j2 will require a change in one place, the logging middleware. - * replacing the authentication mechanism from OAuth to single sign-on will require changing the auth middleware -* **Testability**: we can test our aspects independently. - -## Middleware in zio-http - -A middleware helps in addressing common crosscutting concerns without duplicating boilerplate code. - -#### Attaching middleware to Http - -`@@` operator is used to attach a middleware to an Http. Example below shows a middleware attached to an HttpApp - -```scala mdoc:silent -val app = Http.collect[Request] { - case Method.GET -> Root / name => Response.text(s"Hello $name") -} -val appWithMiddleware = app @@ RequestHandlerMiddlewares.debug -``` - -Logically the code above translates to `Middleware.debug(app)` - -#### A simple middleware example - -Let us consider a simple example using out-of-the-box middleware called ```addHeader``` -We will write a middleware that will attach a custom header to the response. - -We create a middleware that appends an additional header to the response indicating whether it is a Dev/Prod/Staging environment. - -```scala mdoc:silent:reset -import zio._ -import zio.http._ - -lazy val patchEnv = RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") -``` - -A test `App` with attached middleware: - -```scala mdoc:silent -val app = Http.collect[Request] { - case Method.GET -> Root / name => Response.text(s"Hello $name") -} -val appWithMiddleware = app @@ patchEnv -``` - -Start the server: - -```scala mdoc:silent -Server.serve(appWithMiddleware).provide(Server.default) -``` - -Fire a curl request, and we see an additional header added to the response indicating the "Dev" environment: - -``` -curl -i http://localhost:8080/Bob - -HTTP/1.1 200 OK -content-type: text/plain -X-Environment: Dev -content-length: 12 - -Hello Bob -``` - -## Combining middlewares - -Middlewares can be combined using several special operators like `++`, `<>` and `>>>` - -### Using `++` combinator - -`>>>` and `++` are aliases for `andThen`. It combines two middlewares. - -For example, if we have three middlewares f1, f2, f3 - -f1 ++ f2 ++ f3 applies on an `http`, from left to right with f1 first followed by others, like this -```scala - f3(f2(f1(http))) -``` -#### A simple example using `++` combinator - -Start with imports: - -```scala mdoc:silent:reset -import zio.http._ -import zio.http.RequestHandlerMiddlewares.basicAuth -import zio._ -``` - -A user app with single endpoint that welcomes a user: - -```scala mdoc:silent -val userApp = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => - Response.text(s"Welcome to the ZIO party! ${name}") -} -``` - -A basicAuth middleware with hardcoded user pw and another patches response with environment value: - -```scala mdoc:silent -val basicAuthMW = basicAuth("admin", "admin") -val patchEnv = RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") -// apply combined middlewares to the userApp -val appWithMiddleware = userApp @@ (basicAuthMW ++ patchEnv) -``` - -Start the server: - -```scala mdoc:silent -Server.serve(appWithMiddleware).provide(Server.default) -``` - -Fire a curl request with an incorrect user/password combination: - -``` -curl -i --user admin:wrong http://localhost:8080/user/admin/greet - -HTTP/1.1 401 Unauthorized -www-authenticate: Basic -X-Environment: Dev -content-length: 0 -``` - -We notice in the response that first basicAuth middleware responded `HTTP/1.1 401 Unauthorized` and then patch middleware attached a `X-Environment: Dev` header. - -## Conditional application of middlewares - -- `when` applies middleware only if the condition function evaluates to true --`whenZIO` applies middleware only if the condition function(with effect) evaluates - - -## A complete example of a middleware - -
-Detailed example showing "debug" and "addHeader" middlewares - -```scala mdoc:silent:reset -import zio.http._ -import zio._ - -import java.io.IOException -import java.util.concurrent.TimeUnit - -object Example extends ZIOAppDefault { - val app: App[Any] = - Http.collectZIO[Request] { - // this will return result instantly - case Method.GET -> Root / "text" => ZIO.succeed(Response.text("Hello World!")) - // this will return result after 5 seconds, so with 3 seconds timeout it will fail - case Method.GET -> Root / "long-running" => ZIO.succeed(Response.text("Hello World!")).delay(5.seconds) - } - - val middlewares = - RequestHandlerMiddlewares.debug ++ // print debug info about request and response - RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") // add static header - - override def run = - Server.serve(app @@ middlewares).provide(Server.default) -} -``` - -
- -### A few "Out of the box" middlewares -- [Basic Auth](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/middleware_basic_auth) -- [CORS](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/middleware_cors) -- [CSRF](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/middleware_csrf) - diff --git a/docs/dsl/request.md b/docs/dsl/request.md deleted file mode 100644 index ef9c4c3986..0000000000 --- a/docs/dsl/request.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -id: request -title: Request ---- - -**ZIO HTTP** `Request` is designed in the simplest way possible to decode HTTP Request into a ZIO HTTP request. - It supports all HTTP request methods (as defined in [RFC2616](https://datatracker.ietf.org/doc/html/rfc2616) ) and headers along with custom methods and headers. - -## Creating a Request - -`Request` can be created with `method`, `url`, `headers`, `remoteAddress` and `data`. -Creating requests using `Request` is useful while writing unit tests. - -The below snippet creates a request with default params, `headers` as `Headers.empty`, `data` as `Body.Empty`, `remoteAddress` as `None` -```scala mdoc -import zio.http._ -import zio._ - -Request.default(Method.GET, URL(Root)) -``` - -## Matching and Extracting Requests - -`Request` can be extracted into an HTTP Method and Path via `->`. On the left side is the `Method`, and on the right side, the `Path`. - -```scala -Method.GET -> Root / "text" -``` - -### Method - -`Method` represents HTTP methods like POST, GET, PUT, PATCH, and DELETE. You can create existing HTTP methods such as `Method.GET`, `Method.POST` etc or create a custom one. - -### Path - `Path` can be created using - - `Root` which represents the root - - `/` which represents the path delimiter and starts the extraction from the left-hand side of the expression - - `/:` which represents the path delimiter and starts the extraction from the right-hand side of the expression and can match paths partially - -The below snippet creates an `HttpApp` that accepts an input of type `Request` and output of type `Response` with two paths. -According to the request path, it will respond with the corresponding response: -- if the request has path `/name` it will match the first route. -- if the request has path `/name/joe/wilson` it will match the second route as `/:` matches the path partially as well. - -```scala mdoc:silent - val app: HttpApp[Any, Nothing] = Http.collect[Request] { - case Method.GET -> Root / a => Response.text(s"$a") - case Method.GET -> "" /: "name" /: a => Response.text(s"$a") - } -``` - -## Accessing the Request - -- `body` to access the content of request as a `Body` -- `headers` to get all the headers in the Request -- `method` to access request method -- `url` to access request URL -- `remoteAddress` to access request's remote address if available -- `version` to access the HTTP version - -## Creating and reading a Request with query params - -Query params can be added in the request using `url` in `Request`, `URL` stores query params as `Map[String, List[String]]`. - -The below snippet creates a request with query params: `?q=a&q=b&q=c` -```scala mdoc -Request.get(url = URL(Root, queryParams = QueryParams("q" -> Chunk("a","b","c")))) -``` - -`url.queryParams` can be used to read query params from the request diff --git a/docs/dsl/response.md b/docs/dsl/response.md deleted file mode 100644 index e1d46efae6..0000000000 --- a/docs/dsl/response.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -id: response -title: Response ---- - -**ZIO HTTP** `Response` is designed to encode HTTP Response. -It supports all HTTP status codes and headers along with custom methods and headers (as defined in [RFC2616](https://datatracker.ietf.org/doc/html/rfc2616) ) - -## Creating a Response - -`Response` can be created with `status`, `headers` and `data`. - -The below snippet creates a response with default params, `status` as `Status.OK`, `headers` as `Headers.empty` and `data` as `Body.Empty`. -```scala mdoc -import zio.http._ -import zio._ - -Response() -``` -### Empty Response - -`ok` creates an empty response with status code 200 - -```scala mdoc -Response.ok -``` - -`status` creates an empty response with provided status code. - -```scala mdoc -Response.status(Status.Continue) -``` - -### Specialized Response Constructors - -`text` creates a response with data as text, content-type header set to text/plain and status code 200 - -```scala mdoc -Response.text("hey") -``` - -`json` creates a response with data as json, content-type header set to application/json and status code 200 - -```scala mdoc -Response.json("""{"greetings": "Hello World!"}""") -``` - -`html` creates a response with data as html, content-type header set to text/html and status code 200 -```scala mdoc -import zio.http.html._ - -Response.html(Html.fromString("html text")) -``` - -### Specialized Response Operators - -`withStatus` to update the `status` of `Response` - -```scal mdoca -Response.text("Hello World!").withStatus(Status.NOT_FOUND) -``` - -`updateHeaders` to update the `headers` of `Response` - -```scala mdoc -Response.ok.updateHeaders(_ => Headers("key", "value")) -``` - -### Response from HttpError - -`fromHttpError` creates a response with provided `HttpError` - -```scala mdoc - Response.fromHttpError(HttpError.BadRequest()) -``` - -## Adding Cookie to Response - -`addCookie` adds cookies in the headers of the response. -```scala mdoc -val cookie = Cookie.Response("key", "value") -Response.ok.addCookie(cookie) -``` \ No newline at end of file diff --git a/docs/dsl/server.md b/docs/dsl/server.md deleted file mode 100644 index 466af18984..0000000000 --- a/docs/dsl/server.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -id: server -title: 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 mdoc:silent -import zio.http._ -import zio._ - -def app: App[Any] = ??? -``` - -```scala mdoc:silent:crash -Server.serve(app).provide(Server.default) -``` - -A quick shortcut to only customize the port is `Server.defaultWithPort`: - -```scala mdoc:silent:crash -Server.serve(app).provide(Server.defaultWithPort(8081)) -``` - -Or to customize more properties of the _default configuration_: - -```scala mdoc:silent:crash -Server.serve(app).provide( - Server.defaultWith( - _.port(8081).enableRequestStreaming - ) -) -``` - -## Start a ZIO HTTP Server with custom configurations. - -The `live` layer expects a `Server.Config` holding the custom configuration for the server. - -```scala mdoc:silent:crash -Server - .serve(app) - .provide( - ZLayer.succeed(Server.Config.default.port(8081)), - Server.live - ) -``` - -The `configured` layer loads the server configuration using the application's _ZIO configuration provider_, which -is using the environment by default but can be attached to a different backends using -the [ZIO Config library](https://zio.github.io/zio-config/). - -```scala mdoc:silent:crash -Server - .serve(app) - .provide( - Server.configured() - ) -``` - -In order to customize Netty-specific properties, the `customized` layer can be used, providing not only `Server.Config` -but also `NettyConfig`. \ No newline at end of file diff --git a/docs/dsl/socket/socket.md b/docs/dsl/socket/socket.md deleted file mode 100644 index 9b7ea2f25d..0000000000 --- a/docs/dsl/socket/socket.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -id: socket -title: "Socket" ---- - -Websocket support can be added to your Http application using the same `Http` domain, something like this — - -```scala mdoc:silent -import zio.http._ -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 http = Http.collectZIO[Request] { - case Method.GET -> Root / "subscriptions" => socket.toResponse -} -``` - -The WebSocket API leverages the already powerful `Http` domain to write web socket apps. The difference is that instead -of collecting `Request` we collect `Channel` or more specifically `WebSocketChannel`. And, instead of -returning -a `Response` we return `Unit`, because we use the channel to write content directly. - -## Channel - -Essentially, whenever there is a connection created between a server and client a channel is created on both sides. The -channel is a low level api that allows us to send and receive arbitrary messages. - -When we upgrade a Http connection to WebSocket, we create a specialized channel that only allows websocket frames to be -sent and received. The access to channel is available through the `Channel` api. - -## ChannelEvents - -A `ChannelEvent` is an immutable, type-safe representation of an event that's happened on a channel, and it looks like -this: - -```scala -sealed trait ChannelEvent[A] -``` - -It is the **Event** that was triggered. The type param `A` on the ChannelEvent represents the kind of message the event contains. - -The type `WebSocketChannelEvent` is a type alias to `ChannelEvent[WebsocketFrame]`. Meaning an event that contains `WebSocketFrame` typed messages. - -## Using `Http` - -We can use `Http.collect` to select the events that we care about for our use case, like in the above example we are -only interested in the `ChannelRead` event. There are other life cycle events such as `ChannelRegistered` -and `ChannelUnregistered` that one might want to hook onto for some other use cases. diff --git a/docs/dsl/socket/websocketframe.md b/docs/dsl/socket/websocketframe.md deleted file mode 100644 index 9c6fe10ff5..0000000000 --- a/docs/dsl/socket/websocketframe.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -id: websocketframe -title: "WebSocketFrame" ---- - -In the [WebSocket](https://datatracker.ietf.org/doc/html/rfc6455) protocol, communication happens using frames. ZIO -HTTP's [WebSocketFrame](https://github.com/zio/zio-http/blob/main/zio-http/src/main/scala/zio/socket/WebSocketFrame.scala) -is the representation of those frames. The domain defines the following type of frames: - -* Text -* Binary -* Continuation -* Close -* Ping -* Pong - -## Text - -To create a Text frame that models textual data in the WebSocket protocol, you can use the `text` constructor. - -```scala mdoc:silent -import zio.http._ - -WebSocketFrame.text("Hello from ZIO-HTTP") -``` - -## Binary - -To create a Binary frame that models raw binary data, you can use the `binary` constructor. - -```scala mdoc:silent -import zio.Chunk -import java.nio.charset.StandardCharsets - -WebSocketFrame.binary(Chunk.fromArray("Hello from ZIO-HTTP".getBytes(StandardCharsets.UTF_16))) -``` - -## Continuation - -To create a Continuation frame to model a continuation fragment of the previous message, you can use the `continuation` -constructor. - -```scala mdoc:silent -WebSocketFrame.continuation(Chunk.fromArray("Hello from ZIO-HTTP".getBytes(StandardCharsets.UTF_16))) -``` - -## Close - -To create a Close frame for a situation where the connection needs to be closed, you can use the `close` constructor. -The constructor requires two arguments: - -* Status -* Optional reason. - -### Constructing Close with just status - -```scala mdoc:silent -WebSocketFrame.close(1000) -``` - -### Constructing Close with status and a reason - -```scala mdoc:silent -WebSocketFrame.close(1000, Some("Normal Closure")) -``` - -More information on status codes can be found -in [Section 7.4](https://datatracker.ietf.org/doc/html/rfc6455#section-7.4) of IETF's Data Tracker. - -## Ping - -Ping models heartbeat in the WebSocket protocol. The server or the client can at any time, after a successful handshake, -send a ping frame. - -```scala mdoc:silent -WebSocketFrame.ping -``` - -## Pong - -Pong models the second half of the heartbeat in the WebSocket protocol. Upon receiving [ping](#ping), a pong needs to be -sent back. - -```scala mdoc:silent -WebSocketFrame.ping -``` - -### Pattern Matching on WebSocketFrame - -ZIO HTTP envisions the WebSocketFrame as a [Sum](https://en.wikipedia.org/wiki/Tagged_union) type, which allows -exhaustive pattern matching to be performed on it. - -You can do pattern matching on the WebSocketFrame type in the following way: - -```scala -val frame: WebSocketFrame = ... - -frame match { - case WebSocketFrame.Binary(bytes) => ??? - case WebSocketFrame.Text(text) => ??? - case WebSocketFrame.Close(status, reason) => ??? - case WebSocketFrame.Ping => ??? - case WebSocketFrame.Pong => ??? - case WebSocketFrame.Continuation(buffer) => ??? -} -``` diff --git a/docs/examples/advanced/authentication.md b/docs/examples/advanced/authentication.md deleted file mode 100644 index 33831b1e1e..0000000000 --- a/docs/examples/advanced/authentication.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -id: authentication-server -title: "Authentication Server Example" -sidebar_label: "Authentication Server" ---- - -```scala mdoc:silent - -import java.time.Clock - -import zio._ - -import zio.http.HttpAppMiddleware.bearerAuth -import zio.http._ - -import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim} - -object AuthenticationServer extends ZIOAppDefault { - - /** - * This is an example to demonstrate barer Authentication middleware. The - * Server has 2 routes. The first one is for login,Upon a successful login, it - * will return a jwt token for accessing protected routes. The second route is - * a protected route that is accessible only if the request has a valid jwt - * token. AuthenticationClient example can be used to makes requests to this - * server. - */ - - // Secret Authentication key - val SECRET_KEY = "secretKey" - - implicit val clock: Clock = Clock.systemUTC - - // Helper to encode the JWT token - def jwtEncode(username: String): String = { - val json = s"""{"user": "${username}"}""" - val claim = JwtClaim { - json - }.issuedNow.expiresIn(300) - Jwt.encode(claim, SECRET_KEY, JwtAlgorithm.HS512) - } - - // Helper to decode the JWT token - def jwtDecode(token: String): Option[JwtClaim] = { - Jwt.decode(token, SECRET_KEY, Seq(JwtAlgorithm.HS512)).toOption - } - - // Http app that is accessible only via a jwt token - def user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => - Response.text(s"Welcome to the ZIO party! ${name}") - } @@ bearerAuth(jwtDecode(_).isDefined) - - // App that let's the user login - // Login is successful only if the password is the reverse of the username - def login: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "login" / username / password => - if (password.reverse.hashCode == username.hashCode) Response.text(jwtEncode(username)) - else Response.text("Invalid username or password.").withStatus(Status.Unauthorized) - } - - // Composing all the HttpApps together - val app: HttpApp[Any, Nothing] = login ++ user - - // Run it like any simple app - override val run = Server.serve(app).provide(Server.default) -} -``` \ No newline at end of file diff --git a/docs/examples/advanced/concrete-entity.md b/docs/examples/advanced/concrete-entity.md deleted file mode 100644 index 71c9f5f3b6..0000000000 --- a/docs/examples/advanced/concrete-entity.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -id: concrete-entity -title: "Concrete Entity Example" -sidebar_label: "Concrete Entity" ---- - -```scala mdoc:silent -import zio._ - -import zio.http._ - -/** - * Example to build app on concrete entity - */ -object ConcreteEntity extends ZIOAppDefault { - // Request - case class CreateUser(name: String) - - // Response - case class UserCreated(id: Long) - - val user: Handler[Any, Nothing, CreateUser, UserCreated] = - Handler.fromFunction[CreateUser] { case CreateUser(_) => - UserCreated(2) - } - - val app: RequestHandler[Any, Nothing] = - user - .contramap[Request](req => CreateUser(req.path.encode)) // Http[Any, Nothing, Request, UserCreated] - .map(userCreated => Response.text(userCreated.id.toString)) // Http[Any, Nothing, Request, Response] - - // Run it like any simple app - val run = - Server.serve(app.toHttp.withDefaultErrorResponse).provide(Server.default) -} -``` \ No newline at end of file diff --git a/docs/examples/advanced/middleware-basic-authentication.md b/docs/examples/advanced/middleware-basic-authentication.md deleted file mode 100644 index 674ef70726..0000000000 --- a/docs/examples/advanced/middleware-basic-authentication.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -id: middleware-basic-authentication -title: "Middleware Basic Authentication Example" -sidebar_label: "Middleware Basic Authentication" ---- - -```scala mdoc:silent -import zio._ - -import zio.http.HttpAppMiddleware.basicAuth -import zio.http._ - -object BasicAuth extends ZIOAppDefault { - - // Http app that requires a JWT claim - val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => - Response.text(s"Welcome to the ZIO party! ${name}") - } - - // Composing all the HttpApps together - val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") - - // Run it like any simple app - val run = Server.serve(app).provide(Server.default) -} - -``` \ No newline at end of file diff --git a/docs/examples/advanced/middleware-cors-handling.md b/docs/examples/advanced/middleware-cors-handling.md deleted file mode 100644 index 2887b76f22..0000000000 --- a/docs/examples/advanced/middleware-cors-handling.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -id: middleware-cors-handling -title: "Middleware CORS Handling Example" -sidebar_label: "Middleware CORS Handling" ---- - -```scala mdoc:silent -import zio._ - -import zio.http.Header.{AccessControlAllowMethods, AccessControlAllowOrigin, Origin} -import zio.http.HttpAppMiddleware.cors -import zio.http._ -import zio.http.internal.middlewares.Cors.CorsConfig - -object HelloWorldWithCORS extends ZIOAppDefault { - - // Create CORS configuration - val config: CorsConfig = - CorsConfig( - allowedOrigin = { - case origin@Origin.Value(_, host, _) if host == "dev" => Some(AccessControlAllowOrigin.Specific(origin)) - case _ => None - }, - allowedMethods = AccessControlAllowMethods(Method.PUT, Method.DELETE), - ) - - // Create HTTP route with CORS enabled - val app: HttpApp[Any, Nothing] = - Http.collect[Request] { - case Method.GET -> Root / "text" => Response.text("Hello World!") - case Method.GET -> Root / "json" => Response.json("""{"greetings": "Hello World!"}""") - } @@ cors(config) - - // Run it like any simple app - val run = - Server.serve(app).provide(Server.default) -} -``` \ No newline at end of file diff --git a/docs/examples/advanced/server.md b/docs/examples/advanced/server.md deleted file mode 100644 index 50bbfab690..0000000000 --- a/docs/examples/advanced/server.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -id: server -title: "Advanced Server Example" -sidebar_label: "Server" ---- - -```scala mdoc:silent -import scala.util.Try - -import zio._ - -import zio.http._ -import zio.http.netty.NettyConfig -import zio.http.netty.NettyConfig.LeakDetectionLevel - -object HelloWorldAdvanced extends ZIOAppDefault { - // Set a port - private val PORT = 0 - - private val fooBar: HttpApp[Any, Nothing] = Http.collect[Request] { - case Method.GET -> Root / "foo" => Response.text("bar") - case Method.GET -> Root / "bar" => Response.text("foo") - } - - private val app = Http.collectZIO[Request] { - case Method.GET -> Root / "random" => Random.nextString(10).map(Response.text(_)) - case Method.GET -> Root / "utc" => Clock.currentDateTime.map(s => Response.text(s.toString)) - } - - val run = ZIOAppArgs.getArgs.flatMap { args => - // Configure thread count using CLI - val nThreads: Int = args.headOption.flatMap(x => Try(x.toInt).toOption).getOrElse(0) - - val config = Server.Config.default - .port(PORT) - val nettyConfig = NettyConfig.default - .leakDetection(LeakDetectionLevel.PARANOID) - .maxThreads(nThreads) - val configLayer = ZLayer.succeed(config) - val nettyConfigLayer = ZLayer.succeed(nettyConfig) - - (Server.install(fooBar ++ app).flatMap { port => - Console.printLine(s"Started server on port: $port") - } *> ZIO.never) - .provide(configLayer, nettyConfigLayer, Server.customized) - } -} -``` \ No newline at end of file diff --git a/docs/examples/advanced/streaming-file.md b/docs/examples/advanced/streaming-file.md deleted file mode 100644 index 2ef6d2bddf..0000000000 --- a/docs/examples/advanced/streaming-file.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -id: streaming-file -title: "Streaming File Example" -sidebar_label: "Streaming File" ---- - -```scala mdoc -import java.io.File -import java.nio.file.Paths - -import zio._ - -import zio.stream.ZStream - -import zio.http._ - -object FileStreaming extends ZIOAppDefault { - - // Create HTTP route - val app = Http.collectHttp[Request] { - case Method.GET -> Root / "health" => Handler.ok.toHttp - - // Read the file as ZStream - // Uses the blocking version of ZStream.fromFile - case Method.GET -> Root / "blocking" => Handler.fromStream(ZStream.fromPath(Paths.get("README.md"))).toHttp - - // Uses netty's capability to write file content to the Channel - // Content-type response headers are automatically identified and added - // Adds content-length header and does not use Chunked transfer encoding - case Method.GET -> Root / "video" => Http.fromFile(new File("src/main/resources/TestVideoFile.mp4")) - case Method.GET -> Root / "text" => Http.fromFile(new File("src/main/resources/TestFile.txt")) - } - - // Run it like any simple app - val run = - Server.serve(app.withDefaultErrorResponse).provide(Server.default) -} - -``` \ No newline at end of file diff --git a/docs/examples/advanced/streaming-response.md b/docs/examples/advanced/streaming-response.md deleted file mode 100644 index f6bafc66ce..0000000000 --- a/docs/examples/advanced/streaming-response.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -id: streaming-response -title: "Streaming Response Example" -sidebar_label: "Streaming Response" ---- - -```scala mdoc -import zio.{http, _} -import zio.stream.ZStream -import zio.http._ - -/** - * Example to encode content using a ZStream - */ -object StreamingResponse extends ZIOAppDefault { - // Starting the server (for more advanced startup configuration checkout `HelloWorldAdvanced`) - def run = Server.serve(app).provide(Server.default) - - // Create a message as a Chunk[Byte] - def message = Chunk.fromArray("Hello world !\r\n".getBytes(Charsets.Http)) - // Use `Http.collect` to match on route - def app: HttpApp[Any, Nothing] = Http.collect[Request] { - - // Simple (non-stream) based route - case Method.GET -> Root / "health" => Response.ok - - // ZStream powered response - case Method.GET -> Root / "stream" => - http.Response( - status = Status.Ok, - headers = Headers(Header.ContentLength(message.length.toLong)), - body = Body.fromStream(ZStream.fromChunk(message)), // Encoding content using a ZStream - ) - } -} - -``` \ No newline at end of file diff --git a/docs/examples/advanced/websocket-server.md b/docs/examples/advanced/websocket-server.md deleted file mode 100644 index 13ab328821..0000000000 --- a/docs/examples/advanced/websocket-server.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -id: websocket-server -title: "WebSocket Server Example" -sidebar_label: "WebSocket Server" ---- - -```scala mdoc:silent -import zio._ - -import zio.http.ChannelEvent.{ExceptionCaught, Read, UserEvent, UserEventTriggered} -import zio.http._ - -object WebSocketAdvanced extends ZIOAppDefault { - - val socketApp: SocketApp[Any] = - Handler.webSocket { channel => - channel.receiveAll { - case Read(WebSocketFrame.Text("end")) => - channel.shutdown - - // Send a "bar" if the server sends a "foo" - case Read(WebSocketFrame.Text("foo")) => - channel.send(Read(WebSocketFrame.text("bar"))) - - // Send a "foo" if the server sends a "bar" - case Read(WebSocketFrame.Text("bar")) => - channel.send(Read(WebSocketFrame.text("foo"))) - - // Echo the same message 10 times if it's not "foo" or "bar" - case Read(WebSocketFrame.Text(text)) => - channel.send(Read(WebSocketFrame.text(text))).repeatN(10) - - // Send a "greeting" message to the server once the connection is established - case UserEventTriggered(UserEvent.HandshakeComplete) => - channel.send(Read(WebSocketFrame.text("Greetings!"))) - - // Log when the channel is getting closed - case Read(WebSocketFrame.Close(status, reason)) => - Console.printLine("Closing channel with status: " + status + " and reason: " + reason) - - // Print the exception if it's not a normal close - case ExceptionCaught(cause) => - Console.printLine(s"Channel error!: ${cause.getMessage}") - - case _ => - ZIO.unit - } - } - - val app: Http[Any, Nothing, Request, Response] = - Http.collectZIO[Request] { - case Method.GET -> Root / "greet" / name => ZIO.succeed(Response.text(s"Greetings ${name}!")) - case Method.GET -> Root / "subscriptions" => socketApp.toResponse - } - - override val run = Server.serve(app).provide(Server.default) -} -``` \ No newline at end of file diff --git a/docs/examples/basic/http-client.md b/docs/examples/basic/http-client.md deleted file mode 100644 index 0e6d99acef..0000000000 --- a/docs/examples/basic/http-client.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: http-client -title: Http Client Example -sidebar_label: Http Client ---- - -```scala mdoc:silent -import zio._ - -import zio.http.Client - -object SimpleClient extends ZIOAppDefault { - val url = "http://sports.api.decathlon.com/groups/water-aerobics" - - val program = for { - res <- Client.request(url) - data <- res.body.asString - _ <- Console.printLine(data) - } yield () - - override val run = program.provide(Client.default) - -} - -``` \ No newline at end of file diff --git a/docs/examples/basic/http-server.md b/docs/examples/basic/http-server.md deleted file mode 100644 index 394d03e1cd..0000000000 --- a/docs/examples/basic/http-server.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -id: http-server -title: Http Server Example -sidebar_label: Http Server ---- - -```scala mdoc:silent -import zio._ -import zio.http._ - -object HelloWorld extends ZIOAppDefault { - - // Create HTTP route - val app: HttpApp[Any, Nothing] = Http.collect[Request] { - case Method.GET -> Root / "text" => Response.text("Hello World!") - case Method.GET -> Root / "json" => Response.json("""{"greetings": "Hello World!"}""") - } - - // Run it like any simple app - override val run = Server.serve(app).provide(Server.default) -} - -``` \ No newline at end of file diff --git a/docs/examples/basic/https-client.md b/docs/examples/basic/https-client.md deleted file mode 100644 index 4367b53c2a..0000000000 --- a/docs/examples/basic/https-client.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -id: https-client -title: Https Client Example -sidebar_label: Https Client ---- - -```scala mdoc:silent -import zio._ - -import zio.http._ -import zio.http.netty.NettyConfig -import zio.http.netty.client.NettyClientDriver - -object HttpsClient extends ZIOAppDefault { - val url = "https://sports.api.decathlon.com/groups/water-aerobics" - val headers = Headers(Header.Host("sports.api.decathlon.com")) - - val sslConfig = ClientSSLConfig.FromTrustStoreResource( - trustStorePath = "truststore.jks", - trustStorePassword = "changeit", - ) - - val clientConfig = ZClient.Config.default.ssl(sslConfig) - - val program = for { - res <- Client.request(url, headers = headers) - data <- res.body.asString - _ <- Console.printLine(data) - } yield () - - val run = - program.provide( - ZLayer.succeed(clientConfig), - Client.customized, - NettyClientDriver.live, - DnsResolver.default, - ZLayer.succeed(NettyConfig.default), - ) - -} - -``` diff --git a/docs/examples/basic/https-server.md b/docs/examples/basic/https-server.md deleted file mode 100644 index 277e7bc672..0000000000 --- a/docs/examples/basic/https-server.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -id: https-server -title: Https Server Example -sidebar_label: Https Server ---- - -```scala mdoc:silent -import zio._ -import zio.http._ - -object HttpsHelloWorld extends ZIOAppDefault { - // Create HTTP route - val app: HttpApp[Any, Nothing] = Http.collect[Request] { - case Method.GET -> Root / "text" => Response.text("Hello World!") - case Method.GET -> Root / "json" => Response.json("""{"greetings": "Hello World!"}""") - } - - /** - * In this example an inbuilt API using keystore is used. For testing this - * example using curl, setup the certificate named "server.crt" from resources - * for the OS. Alternatively you can create the keystore and certificate using - * the following link - * https://medium.com/@maanadev/netty-with-https-tls-9bf699e07f01 - */ - - val sslConfig = SSLConfig.fromResource( - behaviour = SSLConfig.HttpBehaviour.Accept, - certPath = "server.crt", - keyPath = "server.key", - ) - - private val config = Server.Config.default - .port(8090) - .ssl(sslConfig) - - private val configLayer = ZLayer.succeed(config) - - override val run = - Server.serve(app).provide(configLayer, Server.live) - -} - -``` \ No newline at end of file diff --git a/docs/examples/basic/websocket.md b/docs/examples/basic/websocket.md deleted file mode 100644 index aeeb1cbb3e..0000000000 --- a/docs/examples/basic/websocket.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: websocket -title: WebSocket Example -sidebar_label: WebSocket ---- - -```scala mdoc:silent -import zio._ - -import zio.http.ChannelEvent.Read -import zio.http._ - -object WebSocketEcho extends ZIOAppDefault { - private val socketApp: SocketApp[Any] = - Handler.webSocket { channel => - channel.receiveAll { - case Read(WebSocketFrame.Text("FOO")) => - channel.send(Read(WebSocketFrame.text("BAR"))) - - case Read(WebSocketFrame.Text("BAR")) => - channel.send(Read(WebSocketFrame.text("FOO"))) - - case Read(WebSocketFrame.Text(text)) => - channel.send(Read(WebSocketFrame.text(text))).repeatN(10) - - case _ => - ZIO.unit - } - } - - private val app: Http[Any, Nothing, Request, Response] = - Http.collectZIO[Request] { - case Method.GET -> Root / "greet" / name => ZIO.succeed(Response.text(s"Greetings {$name}!")) - case Method.GET -> Root / "subscriptions" => socketApp.toResponse - } - - override val run = Server.serve(app).provide(Server.default) -} - -``` diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index 06da6c1445..0000000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -id: getting-started -title: 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 and Handler - -`Handler` describes the transformation from an incoming `Request` to an outgoing `Response` type. The `Http` type on top -if this -provides input-dependent routing to different `Handler` values. There are some default handler constructors such -as `Handler.text`, `Handler.html`, `Handler.fromFile`, `Handler.fromData`, `Handler.fromStream`, `Handler.fromEffect`. - -A `Handler` can always be transformed to a `Http` value using the `.toHttp` method. - -### 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 mdoc:silent -import zio.http._ - -val app = Handler.text("Hello World!").toHttp -``` - -An application can be made using any of the available operators on `zio.Http`. In the above program for any Http -request, the response is always `"Hello World!"`. - -### 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 mdoc:silent:reset -import zio.http._ - -val app = Http.collect[Request] { - case Method.GET -> Root / "fruits" / "a" => Response.text("Apple") - case Method.GET -> Root / "fruits" / "b" => Response.text("Banana") -} -``` - -You can create typed routes as well. The below example shows how to accept count as `Int` only: - - ```scala mdoc:silent:reset -import zio.http._ - -val app = Http.collect[Request] { - case Method.GET -> Root / "Apple" / int(count) => Response.text(s"Apple: $count") -} - ``` - -Pattern matching on route is supported by the framework - -### 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 mdoc:silent:reset -import zio.http._ - -val a = Http.collect[Request] { case Method.GET -> Root / "a" => Response.ok } -val b = Http.collect[Request] { case Method.GET -> Root / "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 mdoc:silent:reset -import zio.http._ - -val a = Handler.fail(new Error("SERVER_ERROR")) -val b = Handler.text("OK") - -val app = (a <> b).toHttp -``` - -### ZIO Integration - -For creating effectful apps, you can use `collectZIO` and wrap `Response` with `ZIO` to produce ZIO effect value. - -```scala mdoc:silent:reset -import zio.http._ -import zio._ - -val app = Http.collectZIO[Request] { - case Method.GET -> Root / "hello" => ZIO.succeed(Response.text("Hello World")) -} -``` - -### 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 mdoc:silent:reset -import zio.http._ -import zio._ - -val app = Http.collectZIO[Request] { - case req@Method.GET -> Root / "fruits" / "a" => - ZIO.succeed(Response.text("URL:" + req.url.path.toString + " Headers: " + req.headers)) - case req@Method.POST -> Root / "fruits" / "a" => - req.body.asString.map(Response.text(_)) -} -``` - -### Testing - -You can run `Http` as a function of `A => ZIO[R, Option[E], B]` to test it by using the `runZIO` method. - -```scala mdoc:silent:reset -import zio.test._ -import zio.test.Assertion.equalTo -import zio.http._ - -object Spec extends ZIOSpecDefault { - - def spec = suite("http")( - test("should be ok") { - val app = Handler.ok.toHttp - val req = Request.get(URL(Root)) - assertZIO(app.runZIO(req))(equalTo(Response.ok)) - } - ) -} -``` - -When we call the `app` with the `request` it calls the apply method of `Http` via `zio.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 `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, we are using `collectZIO` which sends WebsSocketTextFrame " -BAR" on receiving WebsSocketTextFrame "FOO". - -```scala mdoc:silent:reset -import zio.http._ -import zio.stream._ -import zio._ - -private val socket = - Handler.webSocket { channel => - channel.receiveAll { - case ChannelEvent.Read(WebSocketFrame.Text("FOO")) => - channel.send(ChannelEvent.Read(WebSocketFrame.text("BAR"))) - case _ => - ZIO.unit - } - } - -private val app = - Http.collectZIO[Request] { - case Method.GET -> Root / "greet" / name => ZIO.succeed(Response.text(s"Greetings {$name}!")) - case Method.GET -> Root / "ws" => socket.toResponse - } -``` - -## 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 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 mdoc:silent:reset -import zio.http._ -import zio._ - -object HelloWorld extends ZIOAppDefault { - val app = Handler.ok.toHttp - - override def run = - Server.serve(app).provide(Server.defaultWithPort(8090)) -} -``` - -## Examples - -- [HTTP Server](https://zio.github.io/zio-http/docs/v1.x/examples/zio-http-basic-examples/http_server) -- [Advanced Server](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/advanced_server) -- [WebSocket Server](https://zio.github.io/zio-http/docs/v1.x/examples/zio-http-basic-examples/web-socket) -- [Streaming Response](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/stream-response) -- [HTTP Client](https://zio.github.io/zio-http/docs/v1.x/examples/zio-http-basic-examples/http_client) -- [File Streaming](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/stream-file) -- [Authentication](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/authentication) diff --git a/docs/index.md b/docs/index.md index 9ab557c126..9430a91480 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,51 +4,9 @@ title: "Introduction to ZIO Http" sidebar_label: "ZIO Http" --- -ZIO Http is a scala library for building http apps. It is powered by ZIO and [netty](https://netty.io/) and aims at being the defacto solution for writing, highly scalable and performant web applications using idiomatic scala. +ZIO-HTTP is a library for building asynchronous and concurrent HTTP services in Scala, it's designed to provide a high-performance, purely functional HTTP server and client implementation that integrates seamlessly with the ZIO ecosystem. + @PROJECT_BADGES@ ## Installation - -Setup via `build.sbt`: - -```scala -libraryDependencies += "dev.zio" %% "zio-http" % "@VERSION@" -``` - -**NOTES ON VERSIONING:** - -- Older library versions `1.x` or `2.x` with organization `io.d11` of ZIO Http are derived from Dream11, the organization that donated ZIO Http to the ZIO organization in 2022. -- Newer library versions, starting in 2023 and resulting from the ZIO organization (`dev.zio`) started with `0.0.x`, reaching `1.0.0` release candidates in April of 2023 - -## Getting Started - -A simple Http server can be built using a few lines of code. - -```scala mdoc:silent -import zio._ -import zio.http._ - -object HelloWorld extends ZIOAppDefault { - - val app: App[Any] = - Http.collect[Request] { - case Method.GET -> Root / "text" => Response.text("Hello World!") - } - - override val run = - Server.serve(app).provide(Server.default) -} -``` - -## Steps to run an example - -1. Edit the [RunSettings](https://github.com/zio/zio-http/blob/main/project/BuildHelper.scala#L107) - modify `className` to the example you'd like to run. -2. From sbt shell, run `~example/reStart`. You should see `Server started on port: 8080`. -3. Send curl request for defined `http Routes`, for eg : `curl -i "http://localhost:8080/text"` for `example.HelloWorld`. - -## Watch Mode - -You can use the [sbt-revolver] plugin to start the server and run it in watch mode using `~ reStart` command on the SBT console. - -[sbt-revolver]: https://github.com/spray/sbt-revolver diff --git a/docs/performance.md b/docs/performance.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/setup.md b/docs/setup.md index f570b0d47e..e69de29bb2 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -1,70 +0,0 @@ ---- -id: setup -title: "Setup" ---- - -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 -libraryDependencies += "dev.zio" %% "zio-http" % "@VERSION@" -``` - -## Using Dream11's g8 template - -Run the following command on your terminal to set up a ZIO-HTTP project using the provided g8 template: - -```shell -sbt new dream11/zio-http.g8 -``` - -### Includes - -* [sbt-native-packager](https://github.com/sbt/sbt-native-packager) -* [scalafmt](https://github.com/scalameta/scalafmt) -* [scalafix](https://github.com/scalacenter/scalafix) - * Included rule(s): - * [scalafix-organize-imports](https://github.com/liancheng/scalafix-organize-imports) -* [sbt-revolver](https://github.com/spray/sbt-revolver) - -## Efficient development process - -The dependencies in the Dream11 g8 template were added to enable an efficient development process. - -### sbt-revolver "hot-reload" changes - -Sbt-revolver can watch application resources for change and automatically re-compile and then re-start the application under development. This provides a fast development-turnaround, the closest you can get to real hot-reloading. - -Start your application from _sbt_ with the following command - -```shell -~reStart -``` - -Pressing enter will stop watching for changes, but not stop the application. Use the following command to stop the application (shutdown hooks will not be executed). - -``` -reStop -``` - -In case you already have an _sbt_ server running, i.e. to provide your IDE with BSP information, use _sbtn_ instead of _sbt_ to run `~reStart`, this let's both _sbt_ sessions share one server. - -### scalafmt automatically format source code - -scalafmt will automatically format all source code and assert that all team members use consistent formatting. - -### scalafix refactoring and linting - -scalafix will mainly be used as a linting tool during everyday development, for example by removing unused dependencies or reporting errors for disabled features. Additionally it can simplify upgrades of Scala versions and dependencies, by executing predefined migration paths. - -### sbt-native-packager - -sbt-native-packager can package the application in the most popular formats, for example Docker images, rpm packages or graalVM native images. diff --git a/docs/sidebar.js b/docs/sidebar.js new file mode 100644 index 0000000000..150354d6a5 --- /dev/null +++ b/docs/sidebar.js @@ -0,0 +1,80 @@ +const sidebars = { + sidebar: [ + { + + type: "category", + label: "ZIO Http", + collapsed: false, + link: { type: "doc", id: "index" }, + + items: [ + "setup", + "quickstart", + { + type: "category", + label: "Concepts", + collapsed: false, + link: { type: "doc", id: "index" }, + items: [ + "concepts/routing", + "concepts/request-handling", + "concepts/server", + "concepts/client", + "concepts/middleware", + "concepts/endpoint" + ] + }, + { + type: "category", + label: "Tutorials", + collapsed: false, + link: { type: "doc", id: "index" }, + items: [ + "tutorials/your-first-zio-http-app", + "tutorials/deploying-a-zio-http-app", + "tutorials/testing-your-zio-http-app" + ] + }, + { + type: "category", + label: "How-to-guides", + collapsed: false, + link: { type: "doc", id: "index" }, + items: [ + "how-to-guides/endpoint", + "how-to-guides/middleware", + ] + }, + { + type: "category", + label: "Reference", + collapsed: false, + link: { type: "doc", id: "index" }, + items: [ + "reference/api-docs", + "reference/server-backend", + "reference/websockets", + "reference/json-handling", + "reference/metrics", + "reference/request-logging" + ] + }, + { + type: "category", + label: "Performance", + collapsed: false, + link: { type: "doc", id: "index" }, + items: ["performance"] + }, + { + type: "category", + label: "FAQ", + collapsed: false, + link: { type: "doc", id: "index" }, + items: ["faq"] + } + ] + + } + ] +}; \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js deleted file mode 100644 index 99c3aaa0f5..0000000000 --- a/docs/sidebars.js +++ /dev/null @@ -1,76 +0,0 @@ -const sidebars = { - sidebar: [ - { - type: "category", - label: "ZIO Http", - collapsed: false, - link: { type: "doc", id: "index" }, - items: [ - "setup", - "getting-started", - { - type: "category", - label: "DSL", - link: { type: "doc", id: "index" }, - items: [ - "dsl/server", - "dsl/http", - "dsl/request", - "dsl/response", - "dsl/body", - "dsl/headers", - "dsl/cookies", - "dsl/middleware", - { - type: "category", - label: "DSL", - collapsed: false, - items: [ - "dsl/socket/socket", - "dsl/socket/websocketframe" - ] - }, - "dsl/html" - ] - }, - { - type: "category", - label: "Examples", - collapsed: false, - link: { type: "doc", id: "index" }, - items: [ - { - type: "category", - label: "Basic Examples", - collapsed: false, - items: [ - "examples/basic/http-client", - "examples/basic/https-client", - "examples/basic/http-server", - "examples/basic/https-server", - "examples/basic/websocket", - ] - }, - { - type: "category", - label: "Advanced Examples", - collapsed: false, - items: [ - "examples/advanced/authentication-server", - "examples/advanced/concrete-entity", - "examples/advanced/middleware-basic-authentication", - "examples/advanced/middleware-cors-handling", - "examples/advanced/server", - "examples/advanced/streaming-file", - "examples/advanced/streaming-response", - "examples/advanced/websocket-server" - ] - } - ] - } - ] - } - ] -}; - -module.exports = sidebars; From 0c5b2a9c6b3cd6ea328ef6148cacdb6e211ff41c Mon Sep 17 00:00:00 2001 From: daveads Date: Mon, 5 Jun 2023 22:54:15 +0100 Subject: [PATCH 02/71] ref --- docs/Reference/ref.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/Reference/ref.md diff --git a/docs/Reference/ref.md b/docs/Reference/ref.md new file mode 100644 index 0000000000..e69de29bb2 From cec549ded36c3c976e44381d795710e2f17bc957 Mon Sep 17 00:00:00 2001 From: daveads Date: Mon, 5 Jun 2023 23:02:15 +0100 Subject: [PATCH 03/71] default files --- docs/concepts/concepts.md | 0 docs/how-to-guides/howto.md | 0 docs/tutorials/tutorials.md | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/concepts/concepts.md create mode 100644 docs/how-to-guides/howto.md create mode 100644 docs/tutorials/tutorials.md diff --git a/docs/concepts/concepts.md b/docs/concepts/concepts.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/how-to-guides/howto.md b/docs/how-to-guides/howto.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/tutorials/tutorials.md b/docs/tutorials/tutorials.md new file mode 100644 index 0000000000..e69de29bb2 From 3cb613d34fb83c8542c5bf98eae9d38a10b83c11 Mon Sep 17 00:00:00 2001 From: felicien Date: Mon, 5 Jun 2023 23:39:52 +0000 Subject: [PATCH 04/71] # Tutorial: Your First ZIO HTTP App --- docs/tutorials/tutorials.md | 90 +++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/docs/tutorials/tutorials.md b/docs/tutorials/tutorials.md index e69de29bb2..d9280b7bd2 100644 --- a/docs/tutorials/tutorials.md +++ b/docs/tutorials/tutorials.md @@ -0,0 +1,90 @@ +# Tutorial: Your First ZIO HTTP App + +In this tutorial, you will learn how to build your first ZIO HTTP application. We will start by setting up a basic ZIO project and then create an HTTP server using the ZIO HTTP module. By the end of this tutorial, you will have a working ZIO HTTP application. + +## Prerequisites + +Before we begin, make sure you have the following installed: + +- JDK 11 or greater +- SBT (Scala Build Tool) + +## Setup + +Let's start by creating a new SBT project and adding the necessary dependencies. + +1. Create a new directory for your project. +2. Inside the project directory, create a new file named `build.sbt`. +3. Open the `build.sbt` file and add the following lines: + +```scala +scalaVersion := "2.13.8" + +libraryDependencies ++= Seq( + "dev.zio" %% "zio" % "2.0.14", + "dev.zio" %% "zio-streams" % "2.0.14", + "dev.zio" %% "zio-interop-cats" % "2.5.1.0", + "dev.zio" %% "zio-logging" % "0.5.12", + "dev.zio" %% "zio-logging-slf4j" % "0.5.12", + "org.http4s" %% "http4s-blaze-server" % "1.0.0-M23", + "org.http4s" %% "http4s-dsl" % "1.0.0-M23" +) +``` +4.Save the file. +Building the HTTP Server +Create a new file named Main.scala in the project's root directory. + +Open Main.scala and add the following code: + +```import zio._ +import zio.console._ +import org.http4s._ +import org.http4s.dsl.Http4sDsl +import org.http4s.blaze.server.BlazeServerBuilder +import scala.concurrent.ExecutionContext.global + +object Main extends App { + + val dsl = Http4sDsl[Task] + import dsl._ + + val appLogic: HttpRoutes[Task] = HttpRoutes.of[Task] { + case GET -> Root / "hello" => + Ok("Hello, ZIO HTTP!") + } + + val server: ZIO[ZEnv, Throwable, Unit] = + ZIO.runtime[ZEnv].flatMap { implicit rts => + BlazeServerBuilder[Task](global) + .bindHttp(8080, "0.0.0.0") + .withHttpApp(appLogic.orNotFound) + .serve + .compile + .drain + } + + def run(args: List[String]): URIO[ZEnv, ExitCode] = + server.provideCustomLayer(Console.live).exitCode +} +``` +.Save the file. + +#Running the HTTP Server +To run the HTTP server, open your terminal and navigate to the project's root directory. + +Run the following command: + +``` +sbt run + +``` +Wait for the server to start. You should see a message indicating that the server is running on http://0.0.0.0:8080. + +#Testing the HTTP Server +To test the HTTP server, open your web browser or use a tool like cURL or Postman. + +Open your browser and visit http://localhost:8080/hello. + +You should see the response "Hello, ZIO HTTP!". + +Congratulations! You have successfully built. \ No newline at end of file From 97b213b69cb666d6c9a1e81bb139b4b435885b42 Mon Sep 17 00:00:00 2001 From: daveads Date: Tue, 6 Jun 2023 00:49:17 +0100 Subject: [PATCH 05/71] file edit --- docs/tutorials/deploying-a-zio-http-app.md | 0 docs/tutorials/testing-your-zio-http-app.md | 0 docs/tutorials/{tutorials.md => your-first-zio-http-app.md} | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/tutorials/deploying-a-zio-http-app.md create mode 100644 docs/tutorials/testing-your-zio-http-app.md rename docs/tutorials/{tutorials.md => your-first-zio-http-app.md} (100%) diff --git a/docs/tutorials/deploying-a-zio-http-app.md b/docs/tutorials/deploying-a-zio-http-app.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/tutorials/testing-your-zio-http-app.md b/docs/tutorials/testing-your-zio-http-app.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/tutorials/tutorials.md b/docs/tutorials/your-first-zio-http-app.md similarity index 100% rename from docs/tutorials/tutorials.md rename to docs/tutorials/your-first-zio-http-app.md From 1b5d05dd7c02bedb622590cd75bb0e3733ef29bb Mon Sep 17 00:00:00 2001 From: felicien Date: Tue, 6 Jun 2023 13:40:23 +0000 Subject: [PATCH 06/71] add more detail --- docs/tutorials/your-first-zio-http-app.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/tutorials/your-first-zio-http-app.md b/docs/tutorials/your-first-zio-http-app.md index d9280b7bd2..ccbffe6e9d 100644 --- a/docs/tutorials/your-first-zio-http-app.md +++ b/docs/tutorials/your-first-zio-http-app.md @@ -1,21 +1,22 @@ # Tutorial: Your First ZIO HTTP App -In this tutorial, you will learn how to build your first ZIO HTTP application. We will start by setting up a basic ZIO project and then create an HTTP server using the ZIO HTTP module. By the end of this tutorial, you will have a working ZIO HTTP application. +ZIO is a modern, high-performance library for asynchronous programming in Scala. It provides a powerful and expressive API for building scalable and reliable applications. ZIO also has a strong focus on type safety and resource management, which makes it a popular choice for building server-side applications. -## Prerequisites +In this tutorial, we will learn how to build a simple HTTP server using ZIO and the ZIO HTTP module. By the end of this tutorial, you will have a basic understanding of how to build HTTP servers using ZIO and the ZIO HTTP module. -Before we begin, make sure you have the following installed: +## Introduction -- JDK 11 or greater -- SBT (Scala Build Tool) +ZIO is a modern, high-performance library for asynchronous programming in Scala. It provides a powerful and expressive API for building scalable and reliable applications. ZIO also has a strong focus on type safety and resource management, which makes it a popular choice for building server-side applications. -## Setup +In this tutorial, we will learn how to build a simple HTTP server using ZIO and the ZIO HTTP module. By the end of this tutorial, you will have a basic understanding of how to build HTTP servers using ZIO and the ZIO HTTP module. -Let's start by creating a new SBT project and adding the necessary dependencies. +## Setting up the Project -1. Create a new directory for your project. -2. Inside the project directory, create a new file named `build.sbt`. -3. Open the `build.sbt` file and add the following lines: +To get started, we need to set up a new SBT project. Here are the steps to do that: + +1. Open your preferred text editor and create a new directory for your project. +2. Open a terminal window and navigate to the project directory. +3. Run the following command to create a new SBT project: ```scala scalaVersion := "2.13.8" From 464780bdc932d4307b3d357de985472d9384ddff Mon Sep 17 00:00:00 2001 From: felicien Date: Tue, 6 Jun 2023 21:31:40 +0000 Subject: [PATCH 07/71] # Tutorial: How to Deploy a ZIO Application --- docs/tutorials/deploying-a-zio-http-app.md | 93 ++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/docs/tutorials/deploying-a-zio-http-app.md b/docs/tutorials/deploying-a-zio-http-app.md index e69de29bb2..a6022b234f 100644 --- a/docs/tutorials/deploying-a-zio-http-app.md +++ b/docs/tutorials/deploying-a-zio-http-app.md @@ -0,0 +1,93 @@ +# Tutorial: How to Deploy a ZIO Application Using Docker? + +## Introduction +Docker is a tool that allows us to package, ship, and run our applications in an isolated environment called a container. Using Docker, we can simplify the deployment process by isolating our applications in their own container and abstracting them from the host environment. + +In this tutorial, we are going to learn how to build a Docker image for our ZIO application and then how to deploy it. Instead of writing the Dockerfile from scratch, we will use the sbt-native-packager to build our Docker image. + +## Running The Examples +In this quickstart, we developed a web service containing 4 different HTTP Applications. Now in this article, we want to dockerize this web application. + +To access the code examples, you can clone the ZIO Quickstarts project: + +$ git clone git@github.com:zio/zio-quickstarts.git +$ cd zio-quickstarts/zio-quickstart-restful-webservice-dockerize + +## Prerequisites +Before we can dockerize our web service, we need to download and install Docker. So we assume that the reader has already installed Docker. + +## Adding SBT Native Packager Plugin +The sbt-native-packager is an sbt plugin that enables us an easy way to package the application as a docker image and deploy that as a docker container. + +First, we need to add the plugin to our `project/plugins.sbt` file: + +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.9") + +Now it's time to enable the JavaAppPackaging and DockerPlugin plugins. So we need to add the following lines in the `build.sbt` file: + +enablePlugins(JavaAppPackaging) +enablePlugins(DockerPlugin) + +## Building The Docker Image +The DockerPlugin plugin of sbt-native-packager is responsible for configuring and building the docker image. We can run the following command to build the docker image: + +$ sbt docker:publishLocal + +After the docker image is built, we can run the `docker images` command to see the list of images that are currently available in the local docker registry: + +$ docker images +REPOSITORY TAG IMAGE ID CREATED SIZE +zio-quickstart-restful-webservice 0.1.0 c9ae81ee8fa6 17 hours ago 558MB + +Note that, to see the generated Dockerfile we can use the `docker:stage` command: + +$ sbt docker:stage + +The Dockerfile will be generated in the `target/docker/stage` directory. + +## Deploying The Docker Image +Now we can create a new container from this image by using the `docker run` command: + +$ docker run -p 80:800 zio-quickstart-restful-webservice:0.1.0 + +Using the `-p` flag, we can specify the port that the container will listen to. As the web service is running on port 8080, we bind this port to the host port 80. Therefore, we can access the web service from the host machine through the port 80: + +$ curl -i "http://localhost/greet?name=Jane&name=John" +HTTP/1.1 200 OK +content-type: text/plain +content-length: 20 + +Hello Jane and John! + +## Configuring The Docker Image +By default, the sbt-native-packager plugin will build the docker image using some predefined settings. So without any configuration we can use the `sbt docker:publish` or `sbt docker:publishLocal` commands to build and publish the docker image to the remote or local docker registry. + +However, it is possible to configure the docker image, and it has lots of options to configure. We can find the list of available options in the sbt-native-packager documentation. + +## Exposing Container Ports +For example, when we build a docker image, we can specify which ports the container will listen to, by using the EXPOSE instruction in the Dockerfile. In the similar way, we can expose the ports using sbt-native-packager, by using the `dockerExposedPorts` setting in the `build.sbt` file: + +dockerExposedPorts := Seq(8080) + +Now, when we build the docker image and create a container from it, the new container has the port 8080 exposed. So when we run the `docker ps` command, we can see that the new container has the port 8080 exposed under the PORTS column: + +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +29982b053379 zio-quickstart-restful-webservice:0.1.0 "/opt/docker/bin/zio…" 3 seconds ago Up 2 seconds 8080/tcp bold_liskov + +## Publishing The Docker Image to a Remote Registry +In a CI/CD pipeline, we might want to publish the docker image to a remote registry other than the local registry. We can do this by configuring the `dockerUsername` and `dockerRepository` settings in the `build.sbt` file: + +dockerUsername := sys.props.get("docker.username") +dockerRepository := sys.props.get("docker.registry") + +Now, we can use the following command to publish the docker image to the remote registry: + +$ export DOCKER_USERNAME= // e.g: johndoe +$ export DOCKER_REGISTRY= // e.g: docker.io +$ sbt -Ddocker.username=$NAMESPACE -Ddocker.registry=$DOCKER_REGISTRY docker:publish + +## Conclusion +In this tutorial, we learned how to build a docker image using sbt-native-packager, and how to deploy the docker image to the local or remote Docker registry. + +All the source code associated with this article is available on the ZIO Quickstart on Github. From 921f748483d1784b4082afe98de1234698e9dc56 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 7 Jun 2023 11:47:18 +0100 Subject: [PATCH 08/71] Intro to zio-http --- docs/index.md | 162 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index 9430a91480..0117d9247d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,9 +4,165 @@ title: "Introduction to ZIO Http" sidebar_label: "ZIO Http" --- -ZIO-HTTP is a library for building asynchronous and concurrent HTTP services in Scala, it's designed to provide a high-performance, purely functional HTTP server and client implementation that integrates seamlessly with the ZIO ecosystem. +ZIO HTTP is an powerful library that empowers developers to build highly performant HTTP-based services and clients using functional Scala and ZIO, with Netty as its core. This library provides powerful functional domains that make it easy to create, modify, and compose applications. Let's start by exploring the HTTP domain, which involves creating an HTTP app when using ZIO HTTP. Here's an example of a simple echo server using ZIO-HTTP: +```scala +import zio.{App, ExitCode, URIO} +import zio.http.{Http, Method, Request, Response} +import zio.http.server.{Server, ServerRoute} +import zio.stream.ZStream -@PROJECT_BADGES@ +object EchoServer extends App { -## Installation + val app: Http[Any, Nothing, Request, Response] = Http.collect[Request] { + case Method.GET -> Root / "echo" / body => + for { + bodyText <- ZStream.fromEffect(body.text).runHead + response <- ZStream.fromEffect(Response.text(bodyText)).runHead + } yield response + }.catchAll(e => Response.status(400).build) + + override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = + Server.start(8080, ServerRoute.const(app)).exitCode +} +``` + +ZIO-HTTP provides a core library, `zhttp-http`, which includes the base HTTP implementation and server/client capabilities based on ZIO. Additional functionality like serverless support, templating, and websockets are available through separate add-on modules. ZIO-HTTP applications can be easily integrated into different deployment platforms, such as server-based, serverless, or compiled to native binaries. + +The principles of ZIO-HTTP are: + +- Application as a Function: HTTP services in ZIO-HTTP are composed of simple functions. The `HttpApp` type represents a function from an `HttpRequest` to a `ZIO` effect that produces an `HttpResponse`. +- Immutability: Entities in ZIO-HTTP are immutable by default, promoting functional programming principles. +- Symmetric: The same `HttpApp` interface is used for both defining HTTP services and making HTTP requests. This enables easy testing and integration of services without requiring an HTTP container. +- Minimal Dependencies: The core `zhttp-http` module has minimal dependencies, and additional add-on modules only include dependencies required for specific functionality. +- Testability: ZIO-HTTP supports easy in-memory and port-based testing of individual endpoints, applications, websockets/SSE, and complete suites of microservices. +- Portability: ZIO-HTTP applications are portable across different deployment platforms, making them versatile and adaptable. + +By leveraging the power of ZIO and the simplicity of functional programming, ZIO-HTTP provides a robust and flexible toolkit for building scalable and composable HTTP services in Scala. + +## Quickstart + +Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart]() guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. + +## Module feature overview + +Core: + +- Lightweight and performant HTTP handler and message objects +- Powerful routing system with support for path-based and parameterized routes +- Typesafe HTTP message construction and deconstruction +- Extensible filters for common HTTP functionalities such as caching, compression, and request/response logging +- Support for cookie handling +- Servlet implementation for integration with Servlet containers +- Built-in support for launching applications with an embedded server backend + +Client: + +- Robust and flexible HTTP client with support for synchronous and asynchronous operations +- Adapters for popular HTTP client libraries such as Apache HttpClient, OkHttp, and Jetty HttpClient +- Websocket client with blocking and non-blocking modes +- GraphQL client integration for consuming GraphQL APIs + +Server: + +- Lightweight server backend spin-up for various platforms including Apache, Jetty, Netty, and SunHttp +- Support for SSE (Server-Sent Events) and Websocket communication +- Easy customization of underlying server backend +- Native-friendly for compilation with GraalVM and Quarkus + +Serverless: + +- Function-based support for building serverless HTTP and event-driven applications +- Adapters for AWS Lambda, Google Cloud Functions, Azure Functions, and other serverless platforms +- Custom AWS Lambda runtime for improved performance and reduced startup time + +Contract: + +- Typesafe HTTP contract definition with support for path parameters, query parameters, headers, and request/response bodies +- Automatic validation of incoming requests based on contract definition +- Self-documenting routes with built-in support for OpenAPI (Swagger) descriptions + +Templating: + +- Pluggable templating system support for popular template engines such as Dust, Freemarker, Handlebars, and Thymeleaf +- Caching and hot-reload template support for efficient rendering + +Message Formats: + +- First-class support for various message formats such as JSON, XML, YAML, and CSV +- Seamless integration with popular libraries like Jackson, Gson, and Moshi for automatic marshalling and unmarshalling + +Resilience: + +- Integration with Resilience4J for implementing resilience patterns such as circuit breakers, retries, rate-limiting, and bulkheading + +Metrics: + +- Support for integrating zio-http applications with Micrometer for monitoring and metrics collection + +Security: + +- OAuth support for implementing authorization flows with popular providers like Auth0, Google, Facebook, and more +- Digest authentication support for secure client-server communication + +Cloud Native: + +- Tooling and utilities for operating zio-http applications in cloud environments such as Kubernetes and CloudFoundry +- Support for 12-factor configuration, dual-port servers, and health checks + +Testing: + +- Approval testing extensions for testing zio-http Request and Response messages +- Chaos testing API for injecting failure modes and evaluating application behavior under different failure conditions +- Matchers for popular testing frameworks like Hamkrest, Kotest, and Strikt + +Service Virtualization: + +- Record and replay HTTP contracts to simulate virtualized services using Servirtium Markdown format +- Includes Servirtium MiTM (Man-in-the-Middle) server for capturing and replaying HTTP interactions + +WebDriver: + +- Lightweight implementation of Selenium WebDriver for testing zio-http applications + +These features provide a comprehensive set of tools and capabilities for building scalable, performant, and secure HTTP applications with zio-http. + +## Example + +This brief illustration is intended to showcase the ease and capabilities of zio-http. Additionally, refer to the quickstart guide for a minimalistic starting point that demonstrates serving and consuming HTTP services with dynamic routing. + +To install, add these dependencies to your `build.sbt`: + +```scala +package example + +import zio._ +import zio.http.HttpAppMiddleware.basicAuth +import zio.http._ + +object BasicAuth extends ZIOAppDefault { + + // Define an HTTP application that requires a JWT claim + val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => + Response.text(s"Welcome to the ZIO party! ${name}") + } + + // Compose all the HttpApps together + val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") + + // Run the application like any simple app + val run = Server.serve(app).provide(Server.default) +} +``` + +# Explaination of the code above + +- The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. + +- The code imports the necessary dependencies from ZIO and ZIO HTTP. + +- The user value represents an HTTP application that requires a JWT claim. It uses the Http.collect combinator to pattern match on GET requests with a specific path pattern (Root / "user" / name / "greet") and responds with a greeting message that includes the extracted name. + +- The app value is created by composing the user HTTP application with the basicAuth middleware. The basicAuth function takes a username and password as arguments and returns a middleware that performs basic authentication. It applies basic authentication with the username "admin" and password "admin" to the user application. + +- Finally, the server is run using the Server.serve method. The app is provided as the HTTP application, and Server.default is provided as the server configuration. The server configuration contains default settings for the server, such as the port to listen on. The run value represents the execution of the server. It starts the ZIO runtime and executes the server, making it ready to receive and respond to HTTP requests. From e2b21c695ee53d34edb043698fe7d14880dab9be Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 7 Jun 2023 15:37:28 +0100 Subject: [PATCH 09/71] quickstart --- docs/quickstart.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/docs/quickstart.md b/docs/quickstart.md index e69de29bb2..f7cf194310 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -0,0 +1,57 @@ +## Quickstart + +Excited to begin your journey? This page provides an excellent overview of zio-http, making it a great starting point. + +An example of a minimal application using the zio-http would typically exhibit the following structure: + +**Save this in a file** `build.sbt` + +```scala +scalaVersion := "2.13.6" + +libraryDependencies ++= Seq( + "dev.zio" %% "zio-http" % "3.0.0-RC2" +) +``` + +```scala +// save this in a file "QuickStart.scale" +package example + +import zio._ + +import zio.http._ + +object QuickStart extends ZIOAppDefault { + + val app: HttpApp[Any, Nothing] = Http.collect[Request] { + case Method.GET -> Root / "hello" => Response.text("Hello, World!") + case Method.GET -> Root / "greet" / name => Response.text(s"Hello, $name!") + } + +// Run it like any simple app + override val run = Server.serve(app).provide(Server.default) +} +``` + +**Code Explainaiton:** + +so what's this code doing ? + +- The `package example` statement indicates that the code belongs to the example package. + +- The `import` statements bring in the required classes and methods from the `zio and zio.http` libraries. + +- The QuickStart object represents the entry point of the application. It extends the `ZIOAppDefault trait`, which simplifies the creation of a ZIO application. + +- The app value is of type `HttpApp[Any, Nothing]`, which represents an HTTP application that handles requests. It is created using the `Http.collect method`, which allows pattern matching on the incoming requests. In this case, there are two cases specified using partial functions: + +- The first case matches a GET request with the path `"/hello"`. It responds with a Response.text containing the message `"Hello, World!"`. + +- The second case matches a GET request with a path in the format `"/greet/{name}"`. It extracts the name parameter from the path and responds with a Response.text containing the message `"Hello, {name}!"`. + +- The run method is overridden from the `ZIOAppDefault` trait. This method serves as the entry point of the application. It starts the HTTP server by using the Server.serve method, which creates a server that serves the app defined earlier. The provide method is used to provide the default Server environment to the server. + +- The `Server.default` value represents a default configuration for the HTTP server. It uses the default settings such as the port number. + +- When the application is executed, the run method is called. It starts the HTTP server, which listens for incoming requests and responds accordingly. From 7a4e1c40e26df888b81bac1516081bba1db2d108 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 7 Jun 2023 15:44:08 +0100 Subject: [PATCH 10/71] setup --- docs/setup.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/docs/setup.md b/docs/setup.md index e69de29bb2..5e8b3f5f1f 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -0,0 +1,73 @@ +--- +id: setup +title: "Getting Started: Setting Up a ZIO-HTTP Project" +--- + +This guide will walk you through the process of setting up a new ZIO-HTTP project. Before we begin, please ensure that you have the following installed on your computer: + +- JDK 1.8 or higher +- sbt (scalaVersion >= 2.12) + +## Adding ZIO-HTTP as a Dependency + +To use ZIO-HTTP in your project, add the following dependency to your build file: + +```scala +libraryDependencies += "dev.zio" %% "zio-http" % "@VERSION@" +``` + +Replace `@VERSION@` with the desired version of ZIO-HTTP. + +## Using Dream11's g8 Template + +You can quickly set up a ZIO-HTTP project using Dream11's g8 template. Run the following command in your terminal: + +```shell +sbt new dream11/zio-http.g8 +``` + +This command will generate a project structure based on the template. + +The template includes the following features: + +- [sbt-native-packager](https://github.com/sbt/sbt-native-packager): Enables packaging the application in various formats such as Docker images, RPM packages, or GraalVM native images. +- [scalafmt](https://github.com/scalameta/scalafmt): Automatically formats the source code for consistent formatting across the team. +- [scalafix](https://github.com/scalacenter/scalafix): Provides refactoring and linting capabilities, ensuring code quality and simplifying upgrades. + - Includes the [scalafix-organize-imports](https://github.com/liancheng/scalafix-organize-imports) rule for organizing imports. +- [sbt-revolver](https://github.com/spray/sbt-revolver): Enables hot-reloading of changes during development. + +## Efficient Development Process + +The provided dependencies in the Dream11 g8 template enable an efficient development process. + +### sbt-revolver for "Hot-Reload" Changes + +Sbt-revolver can watch application resources for changes and automatically recompile and restart the application during development. This allows for a fast development turnaround. + +To start your application from sbt with automatic hot-reloading, use the following command: + +```shell +~reStart +``` + +Pressing enter will stop watching for changes, but the application will continue running. Use the following command to stop the application without executing shutdown hooks: + +```shell +reStop +``` + +If you already have an sbt server running, such as for IDE integration, use `sbtn` instead of `sbt` to run `~reStart`. This allows multiple sbt sessions to share the server. + +### Automatic Source Code Formatting with Scalafmt + +Scalafmt will automatically format the source code, ensuring consistent formatting across team members. + +### Scalafix for Refactoring and Linting + +Scalafix serves as a linting tool during everyday development, detecting issues such as unused dependencies or disabled features. It can also simplify Scala version and dependency upgrades by executing predefined migration paths. + +### sbt-native-packager for Packaging Applications + +The sbt-native-packager plugin allows packaging the application in popular formats such as Docker images, RPM packages, or GraalVM native images. + +By following these steps and leveraging the provided tools, you can efficiently set up and develop your ZIO-HTTP project. \ No newline at end of file From 51b5634fbd9cc6af0e639106f891b0c8f1ff607d Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 7 Jun 2023 16:15:41 +0100 Subject: [PATCH 11/71] edit --- docs/quickstart.md | 5 +++++ docs/setup.md | 4 ++-- docs/tutorials/deploying-a-zio-http-app.md | 6 ++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index f7cf194310..27f4b77428 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,3 +1,8 @@ +--- +id: quickstart +title: quickstart +--- + ## Quickstart Excited to begin your journey? This page provides an excellent overview of zio-http, making it a great starting point. diff --git a/docs/setup.md b/docs/setup.md index 5e8b3f5f1f..950ffddf93 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -1,6 +1,6 @@ --- id: setup -title: "Getting Started: Setting Up a ZIO-HTTP Project" +title: "Setup" --- This guide will walk you through the process of setting up a new ZIO-HTTP project. Before we begin, please ensure that you have the following installed on your computer: @@ -33,7 +33,7 @@ The template includes the following features: - [sbt-native-packager](https://github.com/sbt/sbt-native-packager): Enables packaging the application in various formats such as Docker images, RPM packages, or GraalVM native images. - [scalafmt](https://github.com/scalameta/scalafmt): Automatically formats the source code for consistent formatting across the team. - [scalafix](https://github.com/scalacenter/scalafix): Provides refactoring and linting capabilities, ensuring code quality and simplifying upgrades. - - Includes the [scalafix-organize-imports](https://github.com/liancheng/scalafix-organize-imports) rule for organizing imports. + - Includes the [scalafix-organize-imports](https://github.com/liancheng/scalafix-organize-imports) rule for organizing imports. - [sbt-revolver](https://github.com/spray/sbt-revolver): Enables hot-reloading of changes during development. ## Efficient Development Process diff --git a/docs/tutorials/deploying-a-zio-http-app.md b/docs/tutorials/deploying-a-zio-http-app.md index a6022b234f..ff7fcfe00b 100644 --- a/docs/tutorials/deploying-a-zio-http-app.md +++ b/docs/tutorials/deploying-a-zio-http-app.md @@ -1,3 +1,9 @@ +--- +id: deploying-a-zio-http-app +title: Deploying Zio-http Application +sidebar_label: Deploying Zio-http +--- + # Tutorial: How to Deploy a ZIO Application Using Docker? ## Introduction From fd5fa7c3c9f555dc394ff13055d84a6016b6c15c Mon Sep 17 00:00:00 2001 From: felicien Date: Wed, 7 Jun 2023 21:56:12 +0000 Subject: [PATCH 12/71] Introduction to Zio test --- docs/tutorials/testing-your-zio-http-app.md | 42 +++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/docs/tutorials/testing-your-zio-http-app.md b/docs/tutorials/testing-your-zio-http-app.md index e69de29bb2..ad2bcc679c 100644 --- a/docs/tutorials/testing-your-zio-http-app.md +++ b/docs/tutorials/testing-your-zio-http-app.md @@ -0,0 +1,42 @@ +## Introduction to ZIO Test + +ZIO Test is a zero-dependency testing library that simplifies the testing of effectual programs. It seamlessly integrates with ZIO, making it natural to test both effectual and pure programs. + +## Motivation + +Testing ordinary values and data types is straightforward using traditional Scala assertions: + +```scala +assert(1 + 2 == 2 + 1) +assert("Hi" == "H" + "i") + +case class Point(x: Long, y: Long) +assert(Point(5L, 10L) == Point.apply(5L, 10L)) +``` + +However, when it comes to functional effects like ZIO, testing them becomes challenging. Functional effects describe a series of computations, and we cannot assert two effects using ordinary Scala assertions without executing them. Simply comparing two effects (assert(expectedEffect == actualEffect)) does not guarantee that they behave similarly or produce the same result. To test ZIO effects, we must unsafeRun them and assert their results. + +For example, let's consider a random generator effect, and we want to ensure that the output is greater than zero. We would need to unsafeRun the effect and assert the result: + +scala +Copy code +``` +val random = Unsafe.unsafe { implicit unsafe => + Runtime.default.unsafe.run(Random.nextIntBounded(10)).getOrThrowFiberFailure() +} +``` +assert(random >= 0) +Testing effectful programs becomes complex due to the usage of multiple unsafeRun methods, and ensuring non-flaky tests is not straightforward. Running unsafeRun multiple times for thorough testing can be challenging. To address these issues, a testing framework is needed that treats effects as first-class values. This was the primary motivation behind creating the ZIO Test library. + +#Design of ZIO Test +ZIO Test was designed with the concept of making tests first-class objects. This means that tests (and other related concepts, like assertions) become ordinary values that can be passed around, transformed, and composed. + +This approach offers greater flexibility compared to some other testing frameworks where tests and additional logic had to be put into callbacks or specialized structures. + +Furthermore, this design choice aligns well with other ZIO concepts such as Scopes. Scopes define the scope of execution for a group of tests and allow for proper resource management. With traditional testing frameworks, managing resources during test suite execution using BeforeAll and AfterAll callbacks could create mismatches. However, with ZIO Test's first-class tests, Scopes can be easily integrated and used within a scoped block of code. + +Another significant advantage of treating tests as values is that they are also effects. This eliminates the common challenge of testing asynchronous values found in other frameworks. In traditional frameworks, efforts are made to "run" effects and potentially wrap them in Scala's Future to handle asynchronicity. ZIO Test, on the other hand, expects tests to be ZIO objects directly, avoiding the need for indirect transformations between different wrapping objects. + +Since tests are ordinary ZIO values, ZIO Test eliminates the need to rely on testing frameworks for features like retries, timeouts, and resource management. These problems can be solved using the rich set of functions provided by ZIO itself. + +In summary, ZIO Test's design philosophy of treating tests as first-class objects not only simplifies testing effectual programs but also offers flexibility, seamless integration with other ZIO concepts, and eliminates the challenges of testing asynchronous values. \ No newline at end of file From 5de8bdf4dcc578051775479f82771edbe43add5f Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 7 Jun 2023 23:54:07 +0100 Subject: [PATCH 13/71] id, title --- docs/concepts/client.md | 4 ++++ docs/concepts/concepts.md | 0 docs/concepts/endpoint.md | 4 ++++ docs/concepts/middleware.md | 4 ++++ docs/concepts/request-handling.md | 4 ++++ docs/concepts/routing.md | 4 ++++ docs/concepts/server.md | 4 ++++ docs/tutorials/deploying-a-zio-http-app.md | 1 - docs/tutorials/testing-your-zio-http-app.md | 5 +++++ docs/tutorials/your-first-zio-http-app.md | 5 +++++ 10 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 docs/concepts/client.md delete mode 100644 docs/concepts/concepts.md create mode 100644 docs/concepts/endpoint.md create mode 100644 docs/concepts/middleware.md create mode 100644 docs/concepts/request-handling.md create mode 100644 docs/concepts/routing.md create mode 100644 docs/concepts/server.md diff --git a/docs/concepts/client.md b/docs/concepts/client.md new file mode 100644 index 0000000000..70bbc9d285 --- /dev/null +++ b/docs/concepts/client.md @@ -0,0 +1,4 @@ +--- +id: client +title: Client +--- \ No newline at end of file diff --git a/docs/concepts/concepts.md b/docs/concepts/concepts.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/concepts/endpoint.md b/docs/concepts/endpoint.md new file mode 100644 index 0000000000..0365309828 --- /dev/null +++ b/docs/concepts/endpoint.md @@ -0,0 +1,4 @@ +--- +id: endpoint +title: Endpoint +--- \ No newline at end of file diff --git a/docs/concepts/middleware.md b/docs/concepts/middleware.md new file mode 100644 index 0000000000..69ac778e60 --- /dev/null +++ b/docs/concepts/middleware.md @@ -0,0 +1,4 @@ +--- +id: middleware +title: Middleware +--- \ No newline at end of file diff --git a/docs/concepts/request-handling.md b/docs/concepts/request-handling.md new file mode 100644 index 0000000000..faab717557 --- /dev/null +++ b/docs/concepts/request-handling.md @@ -0,0 +1,4 @@ +--- +id: request-handling +title: Request Handling +--- \ No newline at end of file diff --git a/docs/concepts/routing.md b/docs/concepts/routing.md new file mode 100644 index 0000000000..41bcb341cf --- /dev/null +++ b/docs/concepts/routing.md @@ -0,0 +1,4 @@ +--- +id: routing +title: Routing +--- \ No newline at end of file diff --git a/docs/concepts/server.md b/docs/concepts/server.md new file mode 100644 index 0000000000..e2dc3bce16 --- /dev/null +++ b/docs/concepts/server.md @@ -0,0 +1,4 @@ +--- +id: server +title: Server +--- \ No newline at end of file diff --git a/docs/tutorials/deploying-a-zio-http-app.md b/docs/tutorials/deploying-a-zio-http-app.md index ff7fcfe00b..46db6dc3fa 100644 --- a/docs/tutorials/deploying-a-zio-http-app.md +++ b/docs/tutorials/deploying-a-zio-http-app.md @@ -1,7 +1,6 @@ --- id: deploying-a-zio-http-app title: Deploying Zio-http Application -sidebar_label: Deploying Zio-http --- # Tutorial: How to Deploy a ZIO Application Using Docker? diff --git a/docs/tutorials/testing-your-zio-http-app.md b/docs/tutorials/testing-your-zio-http-app.md index ad2bcc679c..56c5a8abca 100644 --- a/docs/tutorials/testing-your-zio-http-app.md +++ b/docs/tutorials/testing-your-zio-http-app.md @@ -1,3 +1,8 @@ +--- +id: testing-your-zio-http-app +title: Testing zio http app +--- + ## Introduction to ZIO Test ZIO Test is a zero-dependency testing library that simplifies the testing of effectual programs. It seamlessly integrates with ZIO, making it natural to test both effectual and pure programs. diff --git a/docs/tutorials/your-first-zio-http-app.md b/docs/tutorials/your-first-zio-http-app.md index ccbffe6e9d..721903cfc6 100644 --- a/docs/tutorials/your-first-zio-http-app.md +++ b/docs/tutorials/your-first-zio-http-app.md @@ -1,3 +1,8 @@ +--- +id: your-first-zio-http-app +title: Your First Zio http app +--- + # Tutorial: Your First ZIO HTTP App ZIO is a modern, high-performance library for asynchronous programming in Scala. It provides a powerful and expressive API for building scalable and reliable applications. ZIO also has a strong focus on type safety and resource management, which makes it a popular choice for building server-side applications. From 81e36e4a550b6385ebe29a177179e5bee8d745eb Mon Sep 17 00:00:00 2001 From: daveads Date: Thu, 8 Jun 2023 02:43:52 +0100 Subject: [PATCH 14/71] index code snippet --- docs/index.md | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/docs/index.md b/docs/index.md index 0117d9247d..ffcc5d452e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,23 +7,30 @@ sidebar_label: "ZIO Http" ZIO HTTP is an powerful library that empowers developers to build highly performant HTTP-based services and clients using functional Scala and ZIO, with Netty as its core. This library provides powerful functional domains that make it easy to create, modify, and compose applications. Let's start by exploring the HTTP domain, which involves creating an HTTP app when using ZIO HTTP. Here's an example of a simple echo server using ZIO-HTTP: ```scala -import zio.{App, ExitCode, URIO} -import zio.http.{Http, Method, Request, Response} -import zio.http.server.{Server, ServerRoute} -import zio.stream.ZStream - -object EchoServer extends App { - - val app: Http[Any, Nothing, Request, Response] = Http.collect[Request] { - case Method.GET -> Root / "echo" / body => - for { - bodyText <- ZStream.fromEffect(body.text).runHead - response <- ZStream.fromEffect(Response.text(bodyText)).runHead - } yield response - }.catchAll(e => Response.status(400).build) - - override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = - Server.start(8080, ServerRoute.const(app)).exitCode +package example + +import zio._ + +import zio.http._ + +object RequestStreaming extends ZIOAppDefault { + + // Create HTTP route which echos back the request body + val app = Http.collect[Request] { case req @ Method.POST -> Root / "echo" => + // Returns a stream of bytes from the request + // The stream supports back-pressure + val stream = req.body.asStream + + // Creating HttpData from the stream + // This works for file of any size + val data = Body.fromStream(stream) + + Response(body = data) + } + + // Run it like any simple app + val run: UIO[ExitCode] = + Server.serve(app).provide(Server.default).exitCode } ``` @@ -155,14 +162,14 @@ object BasicAuth extends ZIOAppDefault { } ``` -# Explaination of the code above +# Explanation of the code above - The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. - + - The code imports the necessary dependencies from ZIO and ZIO HTTP. - + - The user value represents an HTTP application that requires a JWT claim. It uses the Http.collect combinator to pattern match on GET requests with a specific path pattern (Root / "user" / name / "greet") and responds with a greeting message that includes the extracted name. - + - The app value is created by composing the user HTTP application with the basicAuth middleware. The basicAuth function takes a username and password as arguments and returns a middleware that performs basic authentication. It applies basic authentication with the username "admin" and password "admin" to the user application. - Finally, the server is run using the Server.serve method. The app is provided as the HTTP application, and Server.default is provided as the server configuration. The server configuration contains default settings for the server, such as the port to listen on. The run value represents the execution of the server. It starts the ZIO runtime and executes the server, making it ready to receive and respond to HTTP requests. From 836122424dbace04bc154767c47eb10debe7736a Mon Sep 17 00:00:00 2001 From: daveads Date: Thu, 8 Jun 2023 02:44:09 +0100 Subject: [PATCH 15/71] concepts --- docs/concepts/client.md | 66 +++++++++++++++++- docs/concepts/endpoint.md | 110 +++++++++++++++++++++++++++++- docs/concepts/middleware.md | 86 ++++++++++++++++++++++- docs/concepts/request-handling.md | 74 +++++++++++++++++++- docs/concepts/routing.md | 103 +++++++++++++++++++++++++++- docs/concepts/server.md | 59 +++++++++++++++- 6 files changed, 492 insertions(+), 6 deletions(-) diff --git a/docs/concepts/client.md b/docs/concepts/client.md index 70bbc9d285..aa69929f1a 100644 --- a/docs/concepts/client.md +++ b/docs/concepts/client.md @@ -1,4 +1,68 @@ --- id: client title: Client ---- \ No newline at end of file +--- + +# Client + +The client concept in ZIO-HTTP represents the ability to interact with remote HTTP servers by sending requests and handling the received responses. It allows you to build robust and scalable HTTP clients using ZIO's functional programming capabilities. + +Key components and concepts involved in the client concept in ZIO-HTTP: + +- Client Creation: To create an HTTP client, you can use the `Client.request` method. This method takes a URL, additional headers, and other parameters, and returns a ZIO effect representing the client's interaction with the server. + +- Request Building: ZIO-HTTP provides convenient functions to construct HTTP requests, including specifying the HTTP method (GET, POST, etc.), URL path, request headers, and request body. + +- Response Handling: Once a request is sent, the client receives a response from the server. ZIO-HTTP allows you to handle the response using various combinators and methods. For example, you can extract the response body as a string, parse it as JSON, or process it as a stream of bytes. + +- Client Configuration: ZIO-HTTP allows you to configure the behavior of the client through a `ZClient.Config` object. This configuration includes options such as request timeouts, follow redirects, SSL settings, and more. + +- Dependency Injection: ZIO's dependency injection capabilities enable you to provide the necessary dependencies to the client. This includes the client configuration, the client implementation, and other dependencies required for networking, DNS resolution, and other underlying functionality. + +- Error Handling: ZIO-HTTP provides comprehensive error handling mechanisms. Errors that may occur during the client interaction, such as network failures, timeouts, or invalid responses, are represented as typed errors in the ZIO effect. You can handle these errors using combinators like `catchAll`, `orElse`, or `fold`. + +By leveraging the client concept in ZIO-HTTP, you can build type-safe, composable, and concurrent HTTP clients that seamlessly integrate with the ZIO ecosystem. It enables you to write pure and testable code while benefiting from ZIO's powerful features like error handling, concurrency, and resource management. + +here is an example: + +```scala +package example + +import zio._ + +import zio.http.Header.AcceptEncoding +import zio.http.netty.NettyConfig +import zio.http.{Client, DnsResolver, Headers, ZClient} + +object ClientWithDecompression extends ZIOAppDefault { + val url = "http://sports.api.decathlon.com/groups/water-aerobics" + + val program = for { + res <- Client.request(url, headers = Headers(AcceptEncoding(AcceptEncoding.GZip(), AcceptEncoding.Deflate()))) + data <- res.body.asString + _ <- Console.printLine(data) + } yield () + + val config = ZClient.Config.default.requestDecompression(true) + override val run = + program.provide( + ZLayer.succeed(config), + Client.live, + ZLayer.succeed(NettyConfig.default), + DnsResolver.default, + ) + +} +``` + +**Code break down** + +- Client Creation: The code creates an HTTP client using the `Client.request` method. It sends an HTTP GET request to the specified URL (`url` variable) and provides additional headers (Accept-Encoding) for the request. + +- Request and Response Handling: The code retrieves the response from the client using `res.body.asString`, which reads the response body as a string. It then proceeds to print the response data using `Console.printLine`. + +- Client Configuration: The code includes a `ZClient.Config` object that configures the client. In this case, `requestDecompression` is set to `true`, enabling automatic decompression of the response body if the server provides compressed data (e.g., gzip or deflate). + +- Client Execution: The `program` is executed using the `run` method, which provides the necessary dependencies to the client. The dependencies include the client configuration, the live `Client` implementation, the `NettyConfig` for the underlying networking library, and the default `DnsResolver`. + +Overall, this code demonstrates how to create an HTTP client using ZIO-HTTP. It configures the client, sends an HTTP request, handles the response, and prints the response data. The client concept in ZIO-HTTP enables you to interact with remote servers, send requests, and process the received responses in a purely functional manner using ZIO's effectful programming model. \ No newline at end of file diff --git a/docs/concepts/endpoint.md b/docs/concepts/endpoint.md index 0365309828..3bfc4951bf 100644 --- a/docs/concepts/endpoint.md +++ b/docs/concepts/endpoint.md @@ -1,4 +1,112 @@ --- id: endpoint title: Endpoint ---- \ No newline at end of file +--- + +## Endpoint + +Endpoints in ZIO-HTTP represent individual API operations or routes that the server can handle. They define the structure and behavior of the API endpoints in a type-safe manner. Let's break down the key aspects: + +- Endpoint Definition: + - Endpoints are defined using the `Endpoint` object's combinators, such as `get`, `post`, `path`, `query`, and more. + + - Combinators allow you to specify the HTTP method, URL path, query parameters, request/response bodies, and other details of the endpoint. + +- Middleware: + - Middleware can be applied to endpoints using the `@@` operator to add additional behavior or processing to the endpoint. + + - Middleware can handle authentication, validation, error handling, logging, or any custom logic needed for the endpoint. + +- Endpoint Implementation: + - Endpoints are implemented using the `implement` method, which takes a function specifying the logic to handle the request and generate the response. + + - Inside the implementation function, you can use ZIO effects to perform computations, interact with dependencies, and produce the desired response. + +- Endpoint Composition: + - Endpoints can be composed together using operators like `++`, allowing you to build a collection of endpoints that make up your API. + + - Composition enables structuring the API by grouping related endpoints or creating reusable components. + +- Converting to App: + - To serve the defined endpoints, they need to be converted to an HTTP application (`HttpApp`). + + - This conversion is done using the `toApp` method, which prepares the endpoints to be served as an HTTP application. + + - Any required middleware can be applied during this conversion to the final app. + +- Server: + - The server is responsible for starting the HTTP server and making the app available to handle incoming requests. + + - The `Server.serve` method is used to start the server by providing the HTTP application and any necessary configurations. + +- Client Interaction: + - ZIO-HTTP also provides facilities to interact with endpoints as a client. + + - An `EndpointExecutor` can be created to execute the defined endpoints on the client-side, providing input values and handling the response. + +Overall, endpoints in ZIO-HTTP define the structure, behavior, and implementation of individual API operations. They enable type-safe routing, request/response handling, and composition of API routes. Endpoints can be converted into an HTTP application and served by a server, or executed on the client-side using an `EndpointExecutor`. + +Here is an Example: + +```scala +package example + +import zio._ + +import zio.http.Header.Authorization +import zio.http._ +import zio.http.codec.HttpCodec +import zio.http.endpoint._ + +object EndpointExamples extends ZIOAppDefault { + import HttpCodec._ + + val auth = EndpointMiddleware.auth + + // MiddlewareSpec can be added at the service level as well + val getUser = + Endpoint.get("users" / int("userId")).out[Int] @@ auth + + val getUserRoute = + getUser.implement { id => + ZIO.succeed(id) + } + + val getUserPosts = + Endpoint + .get("users" / int("userId") / "posts" / int("postId")) + .query(query("name")) + .out[List[String]] @@ auth + + val getUserPostsRoute = + getUserPosts.implement[Any] { case (id1: Int, id2: Int, query: String) => + ZIO.succeed(List(s"API2 RESULT parsed: users/$id1/posts/$id2?name=$query")) + } + + val routes = getUserRoute ++ getUserPostsRoute + + val app = routes.toApp(auth.implement(_ => ZIO.unit)(_ => ZIO.unit)) + + val request = Request.get(url = URL.decode("/users/1").toOption.get) + + val run = Server.serve(app).provide(Server.default) + + object ClientExample { + def example(client: Client) = { + val locator = + EndpointLocator.fromURL(URL.decode("http://localhost:8080").toOption.get) + + val executor: EndpointExecutor[Authorization] = + EndpointExecutor(client, locator, ZIO.succeed(Authorization.Basic("user", "pass"))) + + val x1 = getUser(42) + val x2 = getUserPosts(42, 200, "adam") + + val result1: UIO[Int] = executor(x1) + val result2: UIO[List[String]] = executor(x2) + + result1.zip(result2).debug + } + } +} +``` \ No newline at end of file diff --git a/docs/concepts/middleware.md b/docs/concepts/middleware.md index 69ac778e60..98c3b7854b 100644 --- a/docs/concepts/middleware.md +++ b/docs/concepts/middleware.md @@ -1,4 +1,88 @@ --- id: middleware title: Middleware ---- \ No newline at end of file +--- + +## Middleware + +Middleware in ZIO-HTTP is a powerful mechanism that allows you to intercept and modify HTTP requests and responses. It provides a way to add additional functionality to an HTTP application in a modular and reusable manner. + +Middleware functions are applied to an HTTP application to transform its behavior. Each middleware function takes an existing HTTP application as input and returns a new HTTP application with modified behavior. The composition of multiple middleware functions creates a pipeline through which requests and responses flow, allowing each middleware to perform specific actions. + +Some common use cases for middleware include: + +- Logging: Middleware can log request and response details, such as headers, paths, and payloads, for debugging or auditing purposes. + +- Authentication and Authorization: Middleware can enforce authentication and authorization rules by inspecting request headers or tokens and validating user permissions. + +- Error handling: Middleware can catch and handle errors that occur during request processing, allowing for centralized error handling and consistent error responses. + +- Request preprocessing: Middleware can modify or enrich incoming requests before they are processed by the application. For example, parsing request parameters or validating input data. + +- Response post-processing: Middleware can transform or enhance outgoing responses before they are sent back to the client. This includes adding headers, compressing data, or transforming response formats. + +- Caching: Middleware can implement caching mechanisms to improve performance by storing and serving cached responses for certain requests. + +- Rate limiting: Middleware can restrict the number of requests allowed from a client within a specific time frame to prevent abuse or ensure fair usage. + +By composing multiple middleware functions together, you can build complex request processing pipelines tailored to your application's specific needs. Middleware promotes separation of concerns and code reusability by encapsulating different aspects of request handling in a modular way. + +ZIO-HTTP provides a rich set of built-in middleware functions, and you can also create custom middleware by implementing the `HttpApp` or `HttpFilter` interfaces. This flexibility allows you to customize and extend the behavior of your HTTP applications in a declarative and composable manner. + +here is a simple example of middleware: + +```scala +package example + +import java.util.concurrent.TimeUnit + +import zio._ + +import zio.http._ + +object HelloWorldWithMiddlewares extends ZIOAppDefault { + + val app: HttpApp[Any, Nothing] = Http.collectZIO[Request] { + // this will return result instantly + case Method.GET -> Root / "text" => ZIO.succeed(Response.text("Hello World!")) + // this will return result after 5 seconds, so with 3 seconds timeout it will fail + case Method.GET -> Root / "long-running" => ZIO.succeed(Response.text("Hello World!")).delay(5 seconds) + } + + val serverTime: RequestHandlerMiddleware[Nothing, Any, Nothing, Any] = HttpAppMiddleware.patchZIO(_ => + for { + currentMilliseconds <- Clock.currentTime(TimeUnit.MILLISECONDS) + withHeader = Response.Patch.addHeader("X-Time", currentMilliseconds.toString) + } yield withHeader, + ) + val middlewares = + // print debug info about request and response + HttpAppMiddleware.debug ++ + // close connection if request takes more than 3 seconds + HttpAppMiddleware.timeout(3 seconds) ++ + // add static header + HttpAppMiddleware.addHeader("X-Environment", "Dev") ++ + // add dynamic header + serverTime + + // Run it like any simple app + val run = Server.serve((app @@ middlewares).withDefaultErrorResponse).provide(Server.default) +} +``` + +**Break down of the code**: +In the code above, the `middlewares` value is a composition of multiple middleware functions using the `++` operator. Each middleware function adds a specific behavior to the HTTP application (`app`) by modifying requests or responses. + +The middleware functions used in the example are: + +- `HttpAppMiddleware.debug`: This middleware logs debug information about the incoming requests and outgoing responses. + +- `HttpAppMiddleware.timeout(3 seconds)`: This middleware closes the connection if the request takes more than 3 seconds, enforcing a timeout. + +- `HttpAppMiddleware.addHeade("X-Environment", "Dev")`: This middleware adds a static header "X-Environment: Dev" to every response. + +- `serverTime`: This middleware is a custom middleware that adds a dynamic header "X-Time" with the current timestamp to every response. + +The composition of these middleware functions using the `++` operator creates a new HTTP application (`app @@ middlewares`) that incorporates all the defined middleware behaviors. The resulting application is then served by the server (`Server.serve`) with the addition of a default error response configuration. + +By using middleware, you can modify the behavior of your HTTP application at various stages of the request-response cycle, such as request preprocessing, response post-processing, error handling, authentication, and more. diff --git a/docs/concepts/request-handling.md b/docs/concepts/request-handling.md index faab717557..93a8c56ac8 100644 --- a/docs/concepts/request-handling.md +++ b/docs/concepts/request-handling.md @@ -1,4 +1,76 @@ --- id: request-handling title: Request Handling ---- \ No newline at end of file +--- + +## Request Handling + +In ZIO-HTTP, request handling is the process of receiving an HTTP request, processing it, and generating an appropriate HTTP response. ZIO-HTTP provides a functional and composable approach to request handling using the HttpApp type. + +The HttpApp type represents an HTTP application, which is essentially a function that takes an incoming Request and returns a Response. It is defined as type `HttpApp[R, E] = Request => ZIO[R, E, Response[E]]`, where R represents the required environment, E represents the potential error type, and Response[E] represents the response type. + +Here's an overview of the request handling process in ZIO-HTTP: + +- Defining an HttpApp: To handle requests, you define one or more HttpApp values. Each HttpApp is responsible for processing a specific type of request or a specific path pattern. + +- Request Matching: The Http.collect or Http.collectM combinator is used to pattern match on the incoming Request. It allows you to define matching criteria based on HTTP method, path, headers, etc. + +- Request Processing: Once a matching rule is found, you can perform any required processing on the request. This can include extracting information from the request, validating inputs, invoking other services, or performing any necessary business logic. The processing typically results in the creation of a Response. + +- Generating the Response: The processing logic within the HttpApp should construct an appropriate Response based on the request and any additional computation. The Response type encapsulates the HTTP status, headers, and body. + +- Error Handling: During the request handling process, errors may occur. ZIO-HTTP allows you to handle and propagate errors in a type-safe manner using ZIO's ZIO[R, E, A] data type. You can use various combinators and operators provided by ZIO to handle errors, perform error recovery, or propagate errors to higher layers. + + +- Composing HttpApp Instances: ZIO-HTTP provides combinators to compose multiple HttpApp instances together. This allows you to build complex routing logic by combining multiple handlers for different paths or methods. Combinators like @@, ||, &&, and orElse can be used to combine, match, and route requests to the appropriate HttpApp instances. + +- Server Configuration: Once you have defined your HttpApp or a composition of HttpApp instances, you can configure the server settings, such as the port to listen on, TLS settings, or other server-specific options. + +- Running the Server: To start the server and begin handling requests, you use the Server.serve method, providing your HttpApp as the main application to be served. You can also provide additional server-specific configurations if needed. + +Here's an example of request handling: + +```scala +package example + +import zio._ + +import zio.http._ + +object RequestStreaming extends ZIOAppDefault { + + // Create HTTP route which echos back the request body + val app = Http.collect[Request] { case req @ Method.POST -> Root / "echo" => + // Returns a stream of bytes from the request + // The stream supports back-pressure + val stream = req.body.asStream + + // Creating HttpData from the stream + // This works for file of any size + val data = Body.fromStream(stream) + + Response(body = data) + } + + // Run it like any simple app + val run: UIO[ExitCode] = + Server.serve(app).provide(Server.default).exitCode +} +``` + +**Explainaition**: + +- `app` Definition: The `app` value represents an HttpApp that handles incoming requests. It is defined using the `Http.collect` combinator, which pattern matches on the incoming Request. In this case, it matches a POST request to the `/echo` endpoint. + +- Request Matching: The `Http.collect` combinator pattern matches on the request using a partial function. It checks if the request's method is `POST` and if the request path is `/echo`. + +- Request Processing: Once a matching rule is found, the code inside the `Http.collect` block is executed. In this case, it retrieves the request body as a stream of bytes using `req.body.asStream`. + +- Creating `HttpData`: The code then creates an HttpData instance from the request stream using `Body.fromStream(stream)`. `HttpData` represents the body of an HTTP response and can be created from various sources, such as streams, byte arrays, or strings. + +- Generating the Response: Finally, the code constructs a `Response` with the created `HttpData` as the body. The response will echo back the received request body. + +- Running the Server: The `run` value is responsible for starting the server and handling incoming requests. It uses the `Server.serve` method to serve the app as the main application. The server is provided with the default server configuration using `Server.default`. The `exitCode` method is used to provide an appropriate exit code for the application. + + +Overall, the concept of request handling in ZIO-HTTP revolves around defining HttpApp instances, matching incoming requests, processing them, generating responses, and composing multiple HttpApp instances to build a complete HTTP server application. The functional and composable nature of ZIO allows for a flexible and modular approach to building robust and scalable HTTP services. diff --git a/docs/concepts/routing.md b/docs/concepts/routing.md index 41bcb341cf..84a8b54256 100644 --- a/docs/concepts/routing.md +++ b/docs/concepts/routing.md @@ -1,4 +1,105 @@ --- id: routing title: Routing ---- \ No newline at end of file +--- + +# Routing + +ZIO-HTTP provides a powerful and expressive routing system for defining HTTP routes and handling requests. It leverages the capabilities of the ZIO functional programming library to provide a purely functional and composable approach to routing. + +In ZIO-HTTP, routing is based on the concept of "endpoints." An endpoint represents a specific combination of an HTTP method, URL path pattern, and input/output types. It defines how to handle incoming requests that match the specified method and path. + +The core abstraction for defining endpoints in ZIO-HTTP is the Endpoint type, which is built using a DSL (Domain Specific Language) provided by the library. The DSL allows you to define endpoints and combine them together to create more complex routing configurations. + +Here's an example of how you can define an endpoint using ZIO-HTTP: + +```scala +package example + +import zio._ + +import zio.http.Header.Authorization +import zio.http._ +import zio.http.codec.HttpCodec +import zio.http.endpoint._ + +object EndpointExamples extends ZIOAppDefault { + import HttpCodec._ + + val auth = EndpointMiddleware.auth + + // MiddlewareSpec can be added at the service level as well + val getUser = + Endpoint.get("users" / int("userId")).out[Int] @@ auth + + val getUserRoute = + getUser.implement { id => + ZIO.succeed(id) + } + + val getUserPosts = + Endpoint + .get("users" / int("userId") / "posts" / int("postId")) + .query(query("name")) + .out[List[String]] @@ auth + + val getUserPostsRoute = + getUserPosts.implement[Any] { case (id1: Int, id2: Int, query: String) => + ZIO.succeed(List(s"API2 RESULT parsed: users/$id1/posts/$id2?name=$query")) + } + + val routes = getUserRoute ++ getUserPostsRoute + + val app = routes.toApp(auth.implement(_ => ZIO.unit)(_ => ZIO.unit)) + + val request = Request.get(url = URL.decode("/users/1").toOption.get) + + val run = Server.serve(app).provide(Server.default) + + object ClientExample { + def example(client: Client) = { + val locator = + EndpointLocator.fromURL(URL.decode("http://localhost:8080").toOption.get) + + val executor: EndpointExecutor[Authorization] = + EndpointExecutor(client, locator, ZIO.succeed(Authorization.Basic("user", "pass"))) + + val x1 = getUser(42) + val x2 = getUserPosts(42, 200, "adam") + + val result1: UIO[Int] = executor(x1) + val result2: UIO[List[String]] = executor(x2) + + result1.zip(result2).debug + } + } +} +``` + +In the given example above, the concepts of routing in ZIO-HTTP are demonstrated using the ZIO and zio-http libraries. Let's go through the code to understand these concepts: + +- `Endpoint`: An Endpoint represents a specific HTTP endpoint that your application can handle. It consists of a combination of path segments, query parameters, request and response codecs, and middleware. Endpoints define the structure of the HTTP request and response. + +- `EndpointMiddleware`: EndpointMiddleware is a helper object that provides middleware functions for endpoints. Middleware allows you to add additional functionality to your endpoints, such as authentication, logging, error handling, etc. + +- `getUser`: This is an example of an endpoint defined using the Endpoint object. It represents a GET request to the `"/users/{userId}"` path, where `"{userId}"` is a path parameter of type Int. The `@@` operator is used to add middleware (auth in this case) to the endpoint. + +- `getUserRoute`: This defines the implementation of the getUser endpoint. The implementation is a ZIO effect that takes an Int as input and returns a successful ZIO effect with the same Int value. This implementation can be any business logic you want to execute when the endpoint is called. + +- `getUserPosts`: This is another example of an endpoint representing a GET request to the `"/users/{userId}/posts/{postId}"` path. It also includes a query parameter named `"name"`. The response type is `List[String]`. The `@@` operator is used to add the auth middleware to this endpoint as well. + +- `getUserPostsRoute`: This defines the implementation of the getUserPosts endpoint. The implementation takes three input parameters: `id1: Int, id2: Int, and query: String`. It returns a successful ZIO effect that constructs a list of strings based on the input parameters. + +- `routes`: This combines the getUserRoute and getUserPostsRoute endpoints into a single Routes object. The `++` operator is used to concatenate the two routes. + +- `app`: This converts the routes object into a ZIO HttpApp by using the toApp method. The toApp method takes an additional auth.implement function, which provides the implementation for the authentication middleware. In this case, it's a simple implementation that returns ZIO.unit. + +- `request`: This creates an example HTTP request to the `"/users/1"` path using the Request.get method. + +- `run`: This sets up a server using the Server.serve method, passing in the app and a default server configuration. + +- `ClientExample`: This is an example client code that demonstrates how to interact with the server using the defined endpoints. It uses an EndpointLocator to map endpoint paths to URLs and an EndpointExecutor to execute the endpoints on the server. + +- `example`: This method takes a Client as input and demonstrates how to execute the getUser and getUserPosts endpoints using the EndpointExecutor. It sets up the executor with a basic authentication header and executes the endpoints, resulting in two ZIO effects: `result1 (type UIO[Int])` and `result2 (type UIO[List[String]])`. + +The example code shows how to define and implement endpoints using ZIO-HTTP, how to set up a server to handle those endpoints, and how to execute those endpoints using a client. It also demonstrates the use of middleware for authentication (auth), path parameters, query parameters, and response types. diff --git a/docs/concepts/server.md b/docs/concepts/server.md index e2dc3bce16..675d6bcedf 100644 --- a/docs/concepts/server.md +++ b/docs/concepts/server.md @@ -1,4 +1,61 @@ --- id: server title: Server ---- \ No newline at end of file +--- + +## Server + +The concept of a server in ZIO-HTTP revolves around handling incoming HTTP requests and producing corresponding HTTP responses. The server is responsible for listening on a specific port, accepting incoming connections, and routing requests to appropriate handlers based on their HTTP method and path. + +ZIO-HTTP provides a simple and composable DSL for defining HTTP servers using the `Http` type. The `Http` type represents an HTTP route or endpoint that can handle incoming requests and produce responses. Servers in ZIO-HTTP are created by defining an Http route and then using the `Server.serve` method to start the server and bind it to a specific port. + +Here are the key components involved in the server concept in ZIO-HTTP: + +- HTTP Route: A route is defined using the `Http.collect` method, which takes a partial function mapping requests to their corresponding responses. The partial function matches on the HTTP method and path of the request and returns a `Response` for each matched request. + +- Server Configuration: The server configuration includes information such as the host, port, SSL settings, and other options. In ZIO-HTTP, the server configuration is provided through the `ServerConfig` type, which can be customized as needed. + +- Starting the Server: The `Server.serve` method is used to start the server by providing it with the HTTP route and the server configuration. This method creates a ZIO effect that represents the running server. The server will listen for incoming connections and route the requests to the appropriate handlers defined in the route. + +- Server Environment: ZIO-HTTP leverages the ZIO library, which uses an environment-based approach for dependency injection. The server environment consists of the necessary services and resources required for the server to operate, such as the event loop, clock, console, and any other dependencies needed by the defined routes. + +By combining these components, ZIO-HTTP allows you to define and run servers that handle incoming HTTP requests and produce HTTP responses. The server concept in ZIO-HTTP emphasizes composability, type-safety, and a functional programming approach, making it easier to build robust and scalable HTTP servers in a purely functional manner. + +Here is an example: + +```scala +package example + +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter.ISO_LOCAL_TIME + +import zio.{ExitCode, Schedule, URIO, ZIOAppDefault, durationInt} + +import zio.stream.ZStream + +import zio.http._ + +object SSEServer extends ZIOAppDefault { + + val stream: ZStream[Any, Nothing, ServerSentEvent] = + ZStream.repeatWithSchedule(ServerSentEvent(ISO_LOCAL_TIME.format(LocalDateTime.now)), Schedule.spaced(1.second)) + + val app: Http[Any, Nothing, Request, Response] = Http.collect[Request] { case Method.GET -> Root / "sse" => + Response.fromServerSentEvents(stream) + } + + val run: URIO[Any, ExitCode] = { + Server.serve(app.withDefaultErrorResponse).provide(Server.default).exitCode + } +} +``` + +**Explaination:** + +- `stream`: It defines a ZStream that produces `ServerSentEvent` values. In this example, it repeats the current time as an SSE every 1 second using ZIO's scheduling capabilities. + +- `app`: It creates an `Http` route using `Http.collect`. The route matches a GET request to the path "/sse". When a request matches this route, it responds with an SSE stream created from the `stream` defined earlier using Response.`fromServerSentEvents`. + +- `run`: It starts the HTTP server by invoking `Server.serve` with the `app` as the HTTP application and `Server.default` as the server configuration. It then uses ZIO's provide method to provide the necessary environment for the server to run. Finally, it obtains the `ExitCode` from the server execution. + +Overall, this code demonstrates how to define a server using ZIO-HTTP's `Http` DSL, create an HTTP route for an SSE endpoint, and start the server to listen for incoming connections and serve the SSE stream to clients. \ No newline at end of file From 5f84d61ea8b5baaf70b023e0c024f0ba9b8b732c Mon Sep 17 00:00:00 2001 From: felicien Date: Thu, 8 Jun 2023 11:04:56 +0000 Subject: [PATCH 16/71] correct syntax --- docs/tutorials/testing-your-zio-http-app.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/testing-your-zio-http-app.md b/docs/tutorials/testing-your-zio-http-app.md index ad2bcc679c..dd57b45b55 100644 --- a/docs/tutorials/testing-your-zio-http-app.md +++ b/docs/tutorials/testing-your-zio-http-app.md @@ -20,15 +20,18 @@ For example, let's consider a random generator effect, and we want to ensure tha scala Copy code + ``` val random = Unsafe.unsafe { implicit unsafe => Runtime.default.unsafe.run(Random.nextIntBounded(10)).getOrThrowFiberFailure() } ``` + assert(random >= 0) Testing effectful programs becomes complex due to the usage of multiple unsafeRun methods, and ensuring non-flaky tests is not straightforward. Running unsafeRun multiple times for thorough testing can be challenging. To address these issues, a testing framework is needed that treats effects as first-class values. This was the primary motivation behind creating the ZIO Test library. -#Design of ZIO Test +# Design of ZIO Test + ZIO Test was designed with the concept of making tests first-class objects. This means that tests (and other related concepts, like assertions) become ordinary values that can be passed around, transformed, and composed. This approach offers greater flexibility compared to some other testing frameworks where tests and additional logic had to be put into callbacks or specialized structures. @@ -39,4 +42,4 @@ Another significant advantage of treating tests as values is that they are also Since tests are ordinary ZIO values, ZIO Test eliminates the need to rely on testing frameworks for features like retries, timeouts, and resource management. These problems can be solved using the rich set of functions provided by ZIO itself. -In summary, ZIO Test's design philosophy of treating tests as first-class objects not only simplifies testing effectual programs but also offers flexibility, seamless integration with other ZIO concepts, and eliminates the challenges of testing asynchronous values. \ No newline at end of file +In summary, ZIO Test's design philosophy of treating tests as first-class objects not only simplifies testing effectual programs but also offers flexibility, seamless integration with other ZIO concepts, and eliminates the challenges of testing asynchronous values. From 270f18199c47a56ba51cb4a07328f643e3fd4986 Mon Sep 17 00:00:00 2001 From: felicien Date: Thu, 8 Jun 2023 11:05:11 +0000 Subject: [PATCH 17/71] correct syntax --- docs/tutorials/deploying-a-zio-http-app.md | 14 ++++++++++++-- docs/tutorials/your-first-zio-http-app.md | 15 ++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/tutorials/deploying-a-zio-http-app.md b/docs/tutorials/deploying-a-zio-http-app.md index ff7fcfe00b..f6c3cf9fc1 100644 --- a/docs/tutorials/deploying-a-zio-http-app.md +++ b/docs/tutorials/deploying-a-zio-http-app.md @@ -7,22 +7,26 @@ sidebar_label: Deploying Zio-http # Tutorial: How to Deploy a ZIO Application Using Docker? ## Introduction + Docker is a tool that allows us to package, ship, and run our applications in an isolated environment called a container. Using Docker, we can simplify the deployment process by isolating our applications in their own container and abstracting them from the host environment. In this tutorial, we are going to learn how to build a Docker image for our ZIO application and then how to deploy it. Instead of writing the Dockerfile from scratch, we will use the sbt-native-packager to build our Docker image. ## Running The Examples + In this quickstart, we developed a web service containing 4 different HTTP Applications. Now in this article, we want to dockerize this web application. To access the code examples, you can clone the ZIO Quickstarts project: -$ git clone git@github.com:zio/zio-quickstarts.git +$ git clone :zio/zio-quickstarts.git $ cd zio-quickstarts/zio-quickstart-restful-webservice-dockerize ## Prerequisites + Before we can dockerize our web service, we need to download and install Docker. So we assume that the reader has already installed Docker. ## Adding SBT Native Packager Plugin + The sbt-native-packager is an sbt plugin that enables us an easy way to package the application as a docker image and deploy that as a docker container. First, we need to add the plugin to our `project/plugins.sbt` file: @@ -35,6 +39,7 @@ enablePlugins(JavaAppPackaging) enablePlugins(DockerPlugin) ## Building The Docker Image + The DockerPlugin plugin of sbt-native-packager is responsible for configuring and building the docker image. We can run the following command to build the docker image: $ sbt docker:publishLocal @@ -52,13 +57,14 @@ $ sbt docker:stage The Dockerfile will be generated in the `target/docker/stage` directory. ## Deploying The Docker Image + Now we can create a new container from this image by using the `docker run` command: $ docker run -p 80:800 zio-quickstart-restful-webservice:0.1.0 Using the `-p` flag, we can specify the port that the container will listen to. As the web service is running on port 8080, we bind this port to the host port 80. Therefore, we can access the web service from the host machine through the port 80: -$ curl -i "http://localhost/greet?name=Jane&name=John" +$ curl -i "" HTTP/1.1 200 OK content-type: text/plain content-length: 20 @@ -66,11 +72,13 @@ content-length: 20 Hello Jane and John! ## Configuring The Docker Image + By default, the sbt-native-packager plugin will build the docker image using some predefined settings. So without any configuration we can use the `sbt docker:publish` or `sbt docker:publishLocal` commands to build and publish the docker image to the remote or local docker registry. However, it is possible to configure the docker image, and it has lots of options to configure. We can find the list of available options in the sbt-native-packager documentation. ## Exposing Container Ports + For example, when we build a docker image, we can specify which ports the container will listen to, by using the EXPOSE instruction in the Dockerfile. In the similar way, we can expose the ports using sbt-native-packager, by using the `dockerExposedPorts` setting in the `build.sbt` file: dockerExposedPorts := Seq(8080) @@ -82,6 +90,7 @@ CONTAINER ID IMAGE COMMAND 29982b053379 zio-quickstart-restful-webservice:0.1.0 "/opt/docker/bin/zio…" 3 seconds ago Up 2 seconds 8080/tcp bold_liskov ## Publishing The Docker Image to a Remote Registry + In a CI/CD pipeline, we might want to publish the docker image to a remote registry other than the local registry. We can do this by configuring the `dockerUsername` and `dockerRepository` settings in the `build.sbt` file: dockerUsername := sys.props.get("docker.username") @@ -94,6 +103,7 @@ $ export DOCKER_REGISTRY= // e.g: docker.io $ sbt -Ddocker.username=$NAMESPACE -Ddocker.registry=$DOCKER_REGISTRY docker:publish ## Conclusion + In this tutorial, we learned how to build a docker image using sbt-native-packager, and how to deploy the docker image to the local or remote Docker registry. All the source code associated with this article is available on the ZIO Quickstart on Github. diff --git a/docs/tutorials/your-first-zio-http-app.md b/docs/tutorials/your-first-zio-http-app.md index ccbffe6e9d..b09e390472 100644 --- a/docs/tutorials/your-first-zio-http-app.md +++ b/docs/tutorials/your-first-zio-http-app.md @@ -31,6 +31,7 @@ libraryDependencies ++= Seq( "org.http4s" %% "http4s-dsl" % "1.0.0-M23" ) ``` + 4.Save the file. Building the HTTP Server Create a new file named Main.scala in the project's root directory. @@ -68,9 +69,11 @@ object Main extends App { server.provideCustomLayer(Console.live).exitCode } ``` + .Save the file. -#Running the HTTP Server +# Running the HTTP Server + To run the HTTP server, open your terminal and navigate to the project's root directory. Run the following command: @@ -79,13 +82,15 @@ Run the following command: sbt run ``` -Wait for the server to start. You should see a message indicating that the server is running on http://0.0.0.0:8080. -#Testing the HTTP Server +Wait for the server to start. You should see a message indicating that the server is running on . + +# Testing the HTTP Server + To test the HTTP server, open your web browser or use a tool like cURL or Postman. -Open your browser and visit http://localhost:8080/hello. +Open your browser and visit . You should see the response "Hello, ZIO HTTP!". -Congratulations! You have successfully built. \ No newline at end of file +Congratulations! You have successfully built. From 120aae1410cd097d54498937ea01e5d8b63ea912 Mon Sep 17 00:00:00 2001 From: felicien Date: Thu, 8 Jun 2023 12:22:22 +0000 Subject: [PATCH 18/71] Middleware Tutorial --- docs/how-to-guides/Middleware.md | 59 ++++++++++++++++++++++++++++++++ docs/how-to-guides/endpoint.md | 0 2 files changed, 59 insertions(+) create mode 100644 docs/how-to-guides/Middleware.md create mode 100644 docs/how-to-guides/endpoint.md diff --git a/docs/how-to-guides/Middleware.md b/docs/how-to-guides/Middleware.md new file mode 100644 index 0000000000..0d8643f9f5 --- /dev/null +++ b/docs/how-to-guides/Middleware.md @@ -0,0 +1,59 @@ +# Middleware Tutorial + +Middleware is a powerful tool in managing the complexity of HTTP applications and addressing common concerns. It allows for clean and modular code organization, making it easier to maintain and evolve the application over time. + +## What is middleware? + +Middleware is a layer of software that sits between the application and the HTTP server. It intercepts all requests and responses, and can be used to perform a variety of tasks, such as: + +* Authentication +* Authorization +* Logging +* Error handling +* Rate limiting +* Caching + +## Why are middlewares needed? + +Middleware is needed to handle common cross-cutting concerns that are not specific to a particular route or endpoint. For example, authentication and authorization are concerns that need to be applied to all requests, regardless of the route being accessed. + +If we were to implement authentication and authorization directly in our application code, it would quickly become cluttered and difficult to maintain. Middleware allows us to keep our application code clean and focused, while the concerns are managed independently. + +## How do middlewares work? + +Middlewares are typically implemented as functions that take an `HttpRequest` and return an `HttpResponse`. The middleware function can then perform any number of tasks, such as: + +* Inspecting the request headers +* Parsing the request body +* Calling an external service +* Logging the request +* Throwing an error + +If the middleware function returns a non-`HttpResponse`, the request will be aborted and the middleware will not be called again. + +## How to use middlewares in ZIO-HTTP + +ZIO-HTTP provides a number of built-in middlewares, as well as a simple API for creating custom middlewares. To use a middleware, simply attach it to your `HttpApp` using the `@@` operator. + +For example, the following code attaches the `BasicAuthMiddleware` to the `HttpApp`: + +```scala +val app = HttpApp.collectZIO[Request] { + case Method.GET -> Root / "users" / id => + // Core business logic + dbService.lookupUsersById(id).map(Response.json(_.json)) + case Method.GET -> Root / "users" => + // Core business logic + dbService.paginatedUsers(pageNum).map(Response.json(_.json)) +} @@ BasicAuthMiddleware(credentials) +``` + +The `BasicAuthMiddleware` middleware will inspect the `Authorization` header of each request and verify that it contains a valid username and password. If the credentials are valid, the middleware will continue the request processing chain. If the credentials are invalid, the middleware will return a `Unauthorized` response. + +## Conclusion + +Middleware is a powerful tool that can be used to manage the complexity of HTTP applications and address common concerns. By using middleware, we can keep our application code clean and focused, while the concerns are managed independently. + +Using the ZIO-HTTP library, you can easily attach, combine, and create your own middlewares to address your application's specific needs. + +That concludes our tutorial on middleware. Happy coding! \ No newline at end of file diff --git a/docs/how-to-guides/endpoint.md b/docs/how-to-guides/endpoint.md new file mode 100644 index 0000000000..e69de29bb2 From c5598b0bb88773ab960e169ac438d8b5690dc039 Mon Sep 17 00:00:00 2001 From: felicien Date: Thu, 8 Jun 2023 12:29:18 +0000 Subject: [PATCH 19/71] edit --- docs/how-to-guides/Middleware.md | 2 +- docs/how-to-guides/howto.md | 0 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 docs/how-to-guides/howto.md diff --git a/docs/how-to-guides/Middleware.md b/docs/how-to-guides/Middleware.md index 0d8643f9f5..a37c38fe59 100644 --- a/docs/how-to-guides/Middleware.md +++ b/docs/how-to-guides/Middleware.md @@ -56,4 +56,4 @@ Middleware is a powerful tool that can be used to manage the complexity of HTTP Using the ZIO-HTTP library, you can easily attach, combine, and create your own middlewares to address your application's specific needs. -That concludes our tutorial on middleware. Happy coding! \ No newline at end of file +That concludes our tutorial on middleware. Happy coding! diff --git a/docs/how-to-guides/howto.md b/docs/how-to-guides/howto.md deleted file mode 100644 index e69de29bb2..0000000000 From dbbe00df974a7815cb0a7c57f8fe150bc2ada7b1 Mon Sep 17 00:00:00 2001 From: felicien Date: Fri, 9 Jun 2023 15:33:00 +0000 Subject: [PATCH 20/71] correct syntax --- docs/tutorials/deploying-a-zio-http-app.md | 57 +++++++++++++++------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/docs/tutorials/deploying-a-zio-http-app.md b/docs/tutorials/deploying-a-zio-http-app.md index a5bedff756..82af460991 100644 --- a/docs/tutorials/deploying-a-zio-http-app.md +++ b/docs/tutorials/deploying-a-zio-http-app.md @@ -1,108 +1,129 @@ ---- -id: deploying-a-zio-http-app -title: Deploying Zio-http Application ---- +# Deploying Zio-http Application -# Tutorial: How to Deploy a ZIO Application Using Docker? +## Tutorial: How to Deploy a ZIO Application Using Docker? -## Introduction +### Introduction Docker is a tool that allows us to package, ship, and run our applications in an isolated environment called a container. Using Docker, we can simplify the deployment process by isolating our applications in their own container and abstracting them from the host environment. In this tutorial, we are going to learn how to build a Docker image for our ZIO application and then how to deploy it. Instead of writing the Dockerfile from scratch, we will use the sbt-native-packager to build our Docker image. -## Running The Examples +### Running The Examples In this quickstart, we developed a web service containing 4 different HTTP Applications. Now in this article, we want to dockerize this web application. To access the code examples, you can clone the ZIO Quickstarts project: -$ git clone :zio/zio-quickstarts.git +``` +$ git clone git@github.com:zio/zio-quickstarts.git $ cd zio-quickstarts/zio-quickstart-restful-webservice-dockerize +``` -## Prerequisites +### Prerequisites Before we can dockerize our web service, we need to download and install Docker. So we assume that the reader has already installed Docker. -## Adding SBT Native Packager Plugin +### Adding SBT Native Packager Plugin The sbt-native-packager is an sbt plugin that enables us an easy way to package the application as a docker image and deploy that as a docker container. First, we need to add the plugin to our `project/plugins.sbt` file: +```scala addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.9") +``` Now it's time to enable the JavaAppPackaging and DockerPlugin plugins. So we need to add the following lines in the `build.sbt` file: +```scala enablePlugins(JavaAppPackaging) enablePlugins(DockerPlugin) +``` -## Building The Docker Image +### Building The Docker Image The DockerPlugin plugin of sbt-native-packager is responsible for configuring and building the docker image. We can run the following command to build the docker image: +``` $ sbt docker:publishLocal +``` After the docker image is built, we can run the `docker images` command to see the list of images that are currently available in the local docker registry: +``` $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE zio-quickstart-restful-webservice 0.1.0 c9ae81ee8fa6 17 hours ago 558MB +``` Note that, to see the generated Dockerfile we can use the `docker:stage` command: +``` $ sbt docker:stage +``` The Dockerfile will be generated in the `target/docker/stage` directory. -## Deploying The Docker Image +### Deploying The Docker Image Now we can create a new container from this image by using the `docker run` command: +``` $ docker run -p 80:800 zio-quickstart-restful-webservice:0.1.0 +``` Using the `-p` flag, we can specify the port that the container will listen to. As the web service is running on port 8080, we bind this port to the host port 80. Therefore, we can access the web service from the host machine through the port 80: -$ curl -i "" +``` +$ curl -i "http://localhost/greet?name=Jane&name=John" HTTP/1.1 200 OK content-type: text/plain content-length: 20 Hello Jane and John! +``` -## Configuring The Docker Image +### Configuring The Docker Image By default, the sbt-native-packager plugin will build the docker image using some predefined settings. So without any configuration we can use the `sbt docker:publish` or `sbt docker:publishLocal` commands to build and publish the docker image to the remote or local docker registry. However, it is possible to configure the docker image, and it has lots of options to configure. We can find the list of available options in the sbt-native-packager documentation. -## Exposing Container Ports +### Exposing Container Ports For example, when we build a docker image, we can specify which ports the container will listen to, by using the EXPOSE instruction in the Dockerfile. In the similar way, we can expose the ports using sbt-native-packager, by using the `dockerExposedPorts` setting in the `build.sbt` file: +```scala dockerExposedPorts := Seq(8080) +``` Now, when we build the docker image and create a container from it, the new container has the port 8080 exposed. So when we run the `docker ps` command, we can see that the new container has the port 8080 exposed under the PORTS column: +``` $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 29982b053379 zio-quickstart-restful-webservice:0.1.0 "/opt/docker/bin/zio…" 3 seconds ago Up 2 seconds 8080/tcp bold_liskov +``` -## Publishing The Docker Image to a Remote Registry +### Publishing The Docker Image to a Remote Registry In a CI/CD pipeline, we might want to publish the docker image to a remote registry other than the local registry. We can do this by configuring the `dockerUsername` and `dockerRepository` settings in the `build.sbt` file: +```scala dockerUsername := sys.props.get("docker.username") dockerRepository := sys.props.get("docker.registry") +``` Now, we can use the following command to publish the docker image to the remote registry: +``` $ export DOCKER_USERNAME= // e.g: johndoe $ export DOCKER_REGISTRY= // e.g: docker.io $ sbt -Ddocker.username=$NAMESPACE -Ddocker.registry=$DOCKER_REGISTRY docker:publish +``` -## Conclusion +### Conclusion In this tutorial, we learned how to build a docker image using sbt-native-packager, and how to deploy the docker image to the local or remote Docker registry. -All the source code associated with this article is available on the ZIO Quickstart on Github. +All the source code associated with this article is available on the ZIO Quickstart on Github. \ No newline at end of file From 2edd0574723b3d8e221951aa90b8baf9b074cf98 Mon Sep 17 00:00:00 2001 From: felicien Date: Fri, 9 Jun 2023 16:59:17 +0000 Subject: [PATCH 21/71] api docs --- docs/Reference/api-docs.md | 223 +++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 docs/Reference/api-docs.md diff --git a/docs/Reference/api-docs.md b/docs/Reference/api-docs.md new file mode 100644 index 0000000000..759a034add --- /dev/null +++ b/docs/Reference/api-docs.md @@ -0,0 +1,223 @@ + +# ZMX Metric Reference + +All metrics in ZMX are defined as aspects that can be applied to effects without changing the signature of the effect it is applied to. + +Metric aspects are further qualified by a type parameter `A` that must be compatible with the output type of the effect. This means that a `MetricAspect[Any]` can be applied to any effect, while a `MetricAspect[Double]` can only be applied to effects producing a `Double` value. + +Each metric understands a certain data type it can observe to manipulate its state. Counters, Gauges, Histograms, and Summaries all understand `Double` values, while a Set understands `String` values. + +In cases where the output type of an effect is not compatible with the type required to manipulate the metric, the API defines a `xxxxWith` method to construct a `MetricAspect[A]` with a mapper function from `A` to the type required by the metric. + +The API functions in this document are implemented in the `MetricAspect` object. An aspect can be applied to an effect with the `@@` operator. + +Once an application is instrumented with ZMX aspects, it can be configured with a client implementation that is responsible for providing the captured metrics to an appropriate backend. Currently, ZMX supports clients for StatsD and Prometheus out of the box. + +## Counter + +A counter in ZMX is simply a named variable that increases over time. + +### API + +Create a counter that is incremented by 1 every time it is executed successfully. This can be applied to any effect. + +```scala +def count(name: String, tags: Label*): MetricAspect[Any] +``` + +Create a counter that counts the number of failed executions of the effect it is applied to. This can be applied to any effect. + +```scala +def countErrors(name: String, tags: Label*): MetricAspect[Any] +``` + +Create a counter that can be applied to effects producing a `Double` value. The counter will be increased by the value the effect produces. + +```scala +def countValue(name: String, tags: Label*): MetricAspect[Double] +``` + +Create a counter that can be applied to effects producing a value of type `A`. Given the effect produces `v: A`, the counter will be increased by `f(v)`. + +```scala +def countValueWith[A](name: String, tags: Label*)(f: A => Double): MetricAspect[A] +``` + +### Examples + +Create a counter named `countAll` that is incremented by 1 every time it is invoked. + +```scala +val aspCountAll = MetricAspect.count("countAll") +``` + +Now the counter can be applied to any effect. Note that the same aspect can be applied to more than one effect. In the example, we count the sum of executions of both effects in the `for` comprehension. + +```scala +val countAll = for { + _ <- ZIO.unit @@ aspCountAll + _ <- ZIO.unit @@ aspCountAll +} yield () +``` + +Create a counter named `countBytes` that can be applied to effects producing a `Double` value. + +```scala +val aspCountBytes = MetricAspect.countValue("countBytes") +``` + +Now we can apply it to effects producing a `Double`. In a real application, the value might be the number of bytes read from a stream or something similar. + +```scala +val countBytes = nextDoubleBetween(0.0d, 100.0d) @@ aspCountBytes +``` + +## Gauges + +A gauge in ZMX is a named + + variable of type `Double` that can change over time. It can either be set to an absolute value or relative to the current value. + +### API + +Create a gauge that can be set to absolute values. It can be applied to effects yielding a `Double` value. + +```scala +def setGauge(name: String, tags: Label*): MetricAspect[Double] +``` + +Create a gauge that can be set to absolute values. It can be applied to effects producing a value of type `A`. Given the effect produces `v: A`, the gauge will be set to `f(v)` upon successful execution of the effect. + +```scala +def setGaugeWith[A](name: String, tags: Label*)(f: A => Double): MetricAspect[A] +``` + +Create a gauge that can be set relative to its previous value. It can be applied to effects yielding a `Double` value. + +```scala +def adjustGauge(name: String, tags: Label*): MetricAspect[Double] +``` + +Create a gauge that can be set relative to its previous value. It can be applied to effects producing a value of type `A`. Given the effect produces `v: A`, the gauge will be modified by `_ + f(v)` upon successful execution of the effect. + +```scala +def adjustGaugeWith[A](name: String, tags: Label*)(f: A => Double): MetricAspect[A] +``` + +### Examples + +Create a gauge that can be set to absolute values. It can be applied to effects yielding a `Double` value. + +```scala +val aspGaugeAbs = MetricAspect.setGauge("setGauge") +``` + +Create a gauge that can be set relative to its current value. It can be applied to effects yielding a `Double` value. + +```scala +val aspGaugeRel = MetricAspect.adjustGauge("adjustGauge") +``` + +Now we can apply these effects to effects having an output type `Double`. Note that we can instrument an effect with any number of aspects if the type constraints are satisfied. + +```scala +val gaugeSomething = for { + _ <- nextDoubleBetween(0.0d, 100.0d) @@ aspGaugeAbs @@ aspCountAll + _ <- nextDoubleBetween(-50d, 50d) @@ aspGaugeRel @@ aspCountAll +} yield () +``` + +## Histograms + +A histogram observes `Double` values and counts the observed values in buckets. Each bucket is defined by an upper boundary, and the count for a bucket with the upper boundary `b` increases by 1 if an observed value `v` is less than or equal to `b`. + +As a consequence, all buckets that have a boundary `b1` with `b1 > b` will increase by 1 after observing `v`. + +A histogram also keeps track of the overall count of observed values and the sum of all observed values. + +By definition, the last bucket is always defined as `Double.MaxValue`, so that the count of observed values in the last bucket is always equal to the overall count of observed values within the histogram. + +To define a histogram aspect, the API requires specifying the boundaries for the histogram when creating the aspect. + +The mental model for a ZMX histogram is inspired by Prometheus. + +### API + +Create a histogram that can be applied to effects producing `Double` values. The values will be counted as outlined above. + +```scala +def observeHistogram(name: String, boundaries: Chunk[Double], tags: Label*): MetricAspect[Double] +``` + +Create a histogram that can be applied to effects producing values `v` of type `A`. The values `f(v)` will be counted as outlined above. + +```scala +def observeHistogramWith[A + +](name: String, boundaries: Chunk[Double], tags: Label*)(f: A => Double): MetricAspect[A] +``` + +### Examples + +Create a histogram with 12 buckets: 0..100 in steps of 10 and `Double.MaxValue`. It can be applied to effects yielding a `Double` value. + +```scala +val aspHistogram = + MetricAspect.observeHistogram("histogram", DoubleHistogramBuckets.linear(0.0d, 10.0d, 11).boundaries) +``` + +Now we can apply the histogram to effects producing `Double`: + +```scala +val histogram = nextDoubleBetween(0.0d, 120.0d) @@ aspHistogram +``` + +## Summaries + +Similar to a histogram, a summary also observes `Double` values. While a histogram directly modifies the bucket counters and does not keep the individual samples, the summary keeps the observed samples in its internal state. To avoid the set of samples growing uncontrolled, the summary needs to be configured with a maximum age `t` and a maximum size `n`. To calculate the statistics, at most `n` samples will be used, all of which are not older than `t`. + +Essentially, the set of samples is a sliding window over the last observed samples matching the conditions above. + +A summary is used to calculate a set of quantiles over the current set of samples. A quantile is defined by a `Double` value `q` with `0 <= q <= 1` and resolves to a `Double` as well. + +The value of a given quantile `q` is the maximum value `v` out of the current sample buffer with size `n` where at most `q * n` values out of the sample buffer are less than or equal to `v`. + +Typical quantiles for observation are 0.5 (the median) and 0.95. Quantiles are very good for monitoring Service Level Agreements. + +The ZMX API also allows summaries to be configured with an error margin `e`. The error margin is applied to the count of values, so that a quantile `q` for a set of size `s` resolves to value `v` if the number `n` of values less than or equal to `v` is `(1 - e)q * s <= n <= (1+e)q`. + +### API + +A metric aspect that adds a value to a summary each time the effect it is applied to succeeds. This aspect can be applied to effects producing a `Double` value. + +```scala +def observeSummary( + name: String, + maxAge: Duration, + maxSize: Int, + error: Double, + quantiles: Chunk[Double], + tags: Label* +): MetricAspect[Double] +``` + +A metric aspect that adds a value to a summary each time the effect it is applied to succeeds, using the specified function to transform the value returned by the effect to the value to add to the summary. + +```scala +def observeSummaryWith[A]( + name: String, + maxAge: Duration, + maxSize: Int, + error: Double, + quantiles: Chunk[Double], + tags: Label* +)(f: A => Double): MetricAspect[A] +``` + +### Examples + +Create a summary that can hold 100 samples. The maximum age of the samples is 1 day, and the error margin is 3%. The summary should report the 10%, 50%, and 90% quantiles. It can be applied to effects yielding an `Int`. + +```scala +val aspSummary = + MetricAspect.observeSummaryWith[Int]("mySummary", 1.day, 100, 0.03d, Chunk(0.1, \ No newline at end of file From ae476fc8b5cf40bd8b2ff3f7bcb8d813396bf915 Mon Sep 17 00:00:00 2001 From: felicien Date: Fri, 9 Jun 2023 17:43:00 +0000 Subject: [PATCH 22/71] server-backend doc --- docs/Reference/server-backend.md | 114 +++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 docs/Reference/server-backend.md diff --git a/docs/Reference/server-backend.md b/docs/Reference/server-backend.md new file mode 100644 index 0000000000..bffd50d6ee --- /dev/null +++ b/docs/Reference/server-backend.md @@ -0,0 +1,114 @@ + +# ZIO HTTP Server Configurations + +This section describes how to start a ZIO HTTP server using default and custom configurations. + +## Start a ZIO HTTP Server with Default Configurations + +Here's an example of starting a ZIO HTTP server with default configurations: + +```scala +import zio.http._ +import zio._ + +// Define your app using suitable middleware and routes +def app: HttpApp[Any, Nothing] = ??? + +// Create a server instance with default configurations +val server = Server.serve(app).provide(Server.default) +``` + +The `Server.default` method returns a `ZLayer[Any, Throwable, Server.Server]` layer that provides the default environment for the server instance. The `provide` method attaches this default environment to the server instance. + +### Customizing the Port + +If you want to customize the port of the server, use the `Server.defaultWithPort(port)` method. Here's an example: + +```scala +// Create a server instance with customized port configuration +val server = Server.serve(app).provide(Server.defaultWithPort(8081)) +``` + +In this case, the default server environment is extended with an additional setting to customize the port configuration. + +### Customizing More Properties of the Default Configuration + +To customize more properties of the default server configuration, use the `Server.defaultWith(callback)` method, where `callback` is a callback function that applies multiple settings to the underlying configuration object. Here's an example: + +```scala +// Create a server instance with more customized configurations +val server = Server.serve(app).provide( + Server.defaultWith(_.port(8081).enableRequestStreaming) +) +``` + +In this case, the default server environment is extended with a callback function that sets the port configuration to 8081 and enables request streaming. + +## Start a ZIO HTTP Server with Custom Configurations + +To start a server with custom configurations, you need to provide a `Server.Config` object that holds the custom configuration, and attach it to the `Server.live` layer. Here's an example: + +```scala +// Define your app using suitable middleware and routes +def app: HttpApp[Any, Nothing] = ??? + +// Create a server instance with custom configurations +val server = Server.serve(app).provide( + ZLayer.succeed(Server.Config.default.port(8081)), + Server.live +) +``` + +In this case, we're passing a `Server.Config` object with a customized port configuration to the `ZLayer.succeed` method along with the `Server.live` layer to create a custom environment for the server instance. + +### Loading Configurations from ZIO Configuration Provider + +Alternatively, you can use the `Server.configured()` method to load the server configuration from the application's ZIO configuration provider. Here's an example: + +```scala +// Define your app using suitable middleware and routes +def app: HttpApp[Any, Nothing] = ??? + +// Create a server instance with loaded configurations +val server = Server.serve(app).provide(Server.configured()) +``` + +In this case, the `Server.configured()` method loads the server configuration using the application's ZIO configuration provider, which is using the environment by default but can be attached to different backends using the ZIO Config library. + +## Customizing Netty-Specific Properties + +Finally, to customize Netty-specific properties, use a customized layer that contains both the `Server.Config` and `NettyConfig` objects. Here's an example: + +```scala +import zio.duration._ +import zio.nio.core.ChannelOptions +import zio.{Has, ZIO, ZLayer} +import zio.netty.eventloop.EventLoopGroup +import zio.netty.{EventLoopGroupProvider, ServerChannelFactory} +import zio.scheduler.Scheduler + +// Define your app using suitable middleware and routes +def app: HttpApp[Any, Nothing] = ??? + +// Define a customized layer that contains both Server.Config and NettyConfig objects +val customLayer: ZLayer[Any, Throwable, Has[NettyConfig] with Has[Server.Config]] = + (EventLoopGroup.auto(threads = 1) ++ EventLoopGroupProvider.auto ++ Scheduler.live).map { + case (eventLoopGroup, eventLoopGroupProvider, scheduler) => + NettyConfig( + channelFactory = ServerChannelFactory(), + channelOptions = ChannelOptions.empty, + eventLoopGroup = eventLoopGroup, + eventLoopGroupProvider = eventLoopGroupProvider, + maxInitialLineLength = 4096, + maxHeaderSize = 8192, + maxChunkSize = 8192, + idleTimeout = 60.seconds, + scheduler = scheduler + ) ++ Server.Config.default.port(8081) + } + +// Create a server instance with customized Netty configurations +val server = Server.serve(app).provideCustomLayer(customLayer) +``` + +In this example, we're defining a customized layer that includes the `NettyConfig` and `Server.Config` objects. The `NettyConfig` object specifies various Netty-specific properties such as channel factory, channel options, event loop group, timeouts, etc. The `Server.Config` object specifies the port configuration. Finally, we're using the `provideCustomLayer` method to attach the custom environment to the server instance. \ No newline at end of file From 58fd157b42b09ed4520e3bfa5133226e5e6a371f Mon Sep 17 00:00:00 2001 From: felicien Date: Fri, 9 Jun 2023 18:02:11 +0000 Subject: [PATCH 23/71] socket --- docs/Reference/websockets.md | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs/Reference/websockets.md diff --git a/docs/Reference/websockets.md b/docs/Reference/websockets.md new file mode 100644 index 0000000000..9d97315c28 --- /dev/null +++ b/docs/Reference/websockets.md @@ -0,0 +1,45 @@ +**Adding WebSocket Support to your ZIO HTTP Application** + +WebSocket support can be seamlessly integrated into your ZIO HTTP application using the same HTTP domain. Here's an example of how you can add WebSocket functionality: + +```scala +import zio.http._ +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 http = Http.collectZIO[Request] { + case Method.GET -> Root / "subscriptions" => socket.toResponse +} +``` + +In this example, we define a `socket` by using the `Handler.webSocket` method. This method takes a function that receives a `WebSocketChannel` and returns a `ZIO` effect. Inside this function, we can define the behavior of the WebSocket channel. + +The `channel.receiveAll` method is used to handle incoming messages from the WebSocket client. In this case, we check if the received message is `"foo"` and respond with a message `"bar"`. Any other message is ignored. + +The `http` value represents your ZIO HTTP application. We use the `Http.collectZIO` combinator to handle incoming HTTP requests. In this example, we match the request pattern `Method.GET -> Root / "subscriptions"` and return the `socket.toResponse`, which converts the WebSocket channel to an HTTP response. + +**Channel and ChannelEvents** + +A channel is created on both the server and client sides whenever a connection is established between them. It provides a low-level API to send and receive arbitrary messages. + +When a HTTP connection is upgraded to WebSocket, a specialized channel is created that only allows WebSocket frames to be sent and received. The WebSocketChannel is a subtype of the generic Channel API and provides specific methods for WebSocket communication. + +ChannelEvents represent immutable, type-safe events that occur on a channel. These events are wrapped in the `ChannelEvent` type. For WebSocket channels, the type alias `WebSocketChannelEvent` is used, which represents events containing WebSocketFrame messages. + +**Using Http.collect** + +The `Http.collect` combinator allows you to select the events you are interested in for your specific use case. In the example, we use `Http.collectZIO` to handle ZIO effects. We match the desired events, such as `ChannelEvent.Read`, to perform custom logic based on the received WebSocket frames. + +Other lifecycle events, such as `ChannelRegistered` and `ChannelUnregistered`, can also be hooked into for different use cases. + +By leveraging the ZIO HTTP domain, you can build WebSocket applications using the same powerful abstractions provided for regular HTTP handling. + +Please note that the example provided focuses on integrating WebSocket functionality into your ZIO HTTP application. Make sure to adapt it to your specific requirements and implement the necessary logic for handling WebSocket events and messages. From 39e3db50f3bfe7d5f14f04d3a5648918db2f109d Mon Sep 17 00:00:00 2001 From: felicien Date: Fri, 9 Jun 2023 18:10:36 +0000 Subject: [PATCH 24/71] json Handling --- docs/Reference/json-handling.md | 91 +++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 docs/Reference/json-handling.md diff --git a/docs/Reference/json-handling.md b/docs/Reference/json-handling.md new file mode 100644 index 0000000000..b151c30810 --- /dev/null +++ b/docs/Reference/json-handling.md @@ -0,0 +1,91 @@ +**JSON Handling in ZIO HTTP** + +ZIO HTTP provides built-in support for handling JSON data in your applications. This allows you to easily parse incoming JSON requests, serialize data into JSON responses, and work with JSON data structures in a type-safe manner. Here's an overview of how JSON handling is typically done in ZIO HTTP. + +**Parsing JSON Requests** + +To parse incoming JSON requests, you can use the `zio-json` library, which integrates seamlessly with ZIO HTTP. `zio-json` provides a type-safe API for working with JSON data. Here's an example of how you can parse a JSON request body: + +```scala +import zio.http._ +import zio.json._ +import zio._ + +case class MyRequest(name: String, age: Int) + +val httpApp: HttpApp[Any, Throwable] = Http.collectM { + case request @ Method.POST -> Root / "api" / "endpoint" => + request + .asJsonDecode[MyRequest] + .flatMap { myRequest => + // Handle the parsed JSON request + // myRequest will be an instance of MyRequest + // ... + // Return an HTTP response + ZIO.succeed(Response.text("Request handled successfully")) + } +} +``` + +In this example, we define a case class `MyRequest` that represents the expected structure of the JSON request body. We use the `.asJsonDecode` method to parse the request body into an instance of `MyRequest`. If the parsing is successful, we can access the parsed request and perform further processing. + +**Serializing JSON Responses** + +To serialize data into JSON responses, you can use the `.asJsonEncode` method provided by the `zio-json` library. Here's an example of how you can serialize a response object into JSON: + +```scala +import zio.http._ +import zio.json._ +import zio._ + +case class MyResponse(message: String) + +val httpApp: HttpApp[Any, Throwable] = Http.collect { + case Method.GET -> Root / "api" / "endpoint" => + val myResponse = MyResponse("Hello, World!") + Response.jsonString(myResponse.asJson.spaces2) +} +``` + +In this example, we define a case class `MyResponse` that represents the data we want to serialize into JSON. We use the `.asJson` method to convert the response object into a JSON value, and then use `Response.jsonString` to create an HTTP response with the JSON payload. + +**Working with JSON Data Structures** + +`zio-json` provides a type-safe API for working with JSON data structures. You can use case classes or ADTs (Algebraic Data Types) to represent JSON objects and their fields. By deriving the necessary JSON codecs using annotations or manually defining them, you can ensure type safety during JSON parsing and serialization. + +Here's an example of how you can define a case class with nested objects and use it for JSON handling: + +```scala +import zio.http._ +import zio.json._ +import zio._ + +case class Address(city: String, country: String) +case class Person(name: String, age: Int, address: Address) + +object Person { + implicit val addressCodec: JsonCodec[Address] = DeriveJsonCodec.gen[Address] + implicit val personCodec: JsonCodec[Person] = DeriveJsonCodec.gen[Person] +} + +val httpApp: HttpApp[Any, Throwable] = Http.collectM { + case request @ Method.POST -> Root / "api" / "endpoint" => + request + .asJsonDecode[Person] + .flatMap { person => + // Handle the parsed JSON request + // person will be an instance of Person with nested Address object + // ... + // Return an HTTP response + + + ZIO.succeed(Response.text("Request handled successfully")) + } +} +``` + +In this example, we define case classes `Address` and `Person` to represent a nested JSON structure. We derive the necessary JSON codecs using `DeriveJsonCodec.gen`. This allows ZIO HTTP to automatically handle the parsing and serialization of these objects. + +**Summary** + +ZIO HTTP provides seamless integration with `zio-json`, allowing you to easily handle JSON requests and responses in a type-safe manner. By using case classes or ADTs and deriving the necessary JSON codecs, you can ensure proper parsing and serialization of JSON data structures. \ No newline at end of file From 93b275f9b81cb137a6c838b48afd753fd5977ff0 Mon Sep 17 00:00:00 2001 From: felicien Date: Fri, 9 Jun 2023 18:21:21 +0000 Subject: [PATCH 25/71] metrics --- docs/Reference/metrics.md | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 docs/Reference/metrics.md diff --git a/docs/Reference/metrics.md b/docs/Reference/metrics.md new file mode 100644 index 0000000000..ac891d4ba7 --- /dev/null +++ b/docs/Reference/metrics.md @@ -0,0 +1,63 @@ +**Metrics in ZIO HTTP** + +Metrics play a crucial role in monitoring and understanding the performance and behavior of your HTTP applications. ZIO HTTP provides support for integrating metrics into your applications, allowing you to collect and analyze various metrics related to request processing, response times, error rates, and more. Here's an overview of how you can incorporate metrics into your ZIO HTTP applications. + +**1. Metric Types** + +ZIO HTTP supports different types of metrics that you can track in your applications: + +- **Counter**: A counter is a simple metric that keeps track of the number of occurrences of a particular event. For example, you can use a counter to count the number of incoming requests or the number of successful responses. + +- **Timer**: A timer measures the duration of a specific operation or process. You can use timers to measure the processing time of requests or specific parts of your application logic. + +- **Gauge**: A gauge provides a way to track a specific value or metric at a particular point in time. It can be used to monitor things like the number of active connections or the current memory usage of your application. + +- **Histogram**: A histogram captures the statistical distribution of values over a period of time. It can be useful for tracking response times or request sizes. + +**2. Metrics Collection** + +ZIO HTTP integrates with popular metrics libraries, such as Micrometer, which provides a unified way to collect and export metrics to various monitoring systems (e.g., Prometheus, Graphite, etc.). To collect metrics in your ZIO HTTP application, you can create a `Metrics` object and instrument your routes or middleware with the desired metrics. + +Here's an example of using Micrometer with ZIO HTTP to collect request count and response time metrics: + +```scala +import zio.http._ +import zio.metrics._ +import zio._ +import zio.clock.Clock + +val httpApp: HttpApp[Clock with Metrics, Throwable] = Http.collectM { + case Method.GET -> Root / "api" / "endpoint" => + for { + startTime <- clock.nanoTime + _ <- metrics.incrementCounter("requests") + response <- ZIO.succeed(Response.text("Hello, World!")) + endTime <- clock.nanoTime + elapsedTime = (endTime - startTime) / 1000000 // Calculate elapsed time in milliseconds + _ <- metrics.recordTimer("responseTime", elapsedTime) + } yield response +} +``` + +In this example, we create a `Metrics` object by mixing the `Clock` and `Metrics` capabilities into the environment. Within the HTTP route, we increment the "requests" counter to track the number of incoming requests and record the elapsed time in the "responseTime" timer to measure the response processing time. + +**3. Exporting Metrics** + +Once you have collected the metrics, you can export them to your preferred monitoring system. Micrometer provides integrations with various monitoring systems, allowing you to configure the export of metrics. + +For example, to export the metrics to Prometheus, you can include the Prometheus Micrometer library in your project and configure it to scrape the metrics: + +```scala +import io.micrometer.prometheus.PrometheusMeterRegistry + +val registry = new PrometheusMeterRegistry() +Metrics.export(registry) +``` + +In this example, we create a `PrometheusMeterRegistry` and configure the `Metrics` object to export the collected metrics to this registry. You can then expose an endpoint in your application to expose the Prometheus metrics endpoint, which can be scraped by Prometheus for monitoring and visualization. + +**Summary** + +By integrating metrics into your ZIO HTTP applications, you can gain insights into the performance, behavior, and health of your HTTP services. ZIO HTTP provides support for different metric types, allowing you to track request counts, response times, and more. Integration with libraries like Micrometer enables + + you to export metrics to various monitoring systems for analysis and visualization. \ No newline at end of file From 239a41e4687753df4183e989d3ce26014f32356e Mon Sep 17 00:00:00 2001 From: felicien Date: Mon, 12 Jun 2023 16:00:36 +0000 Subject: [PATCH 26/71] upgrade your first zio app --- docs/tutorials/your-first-zio-http-app.md | 237 ++++++++++++++++------ 1 file changed, 170 insertions(+), 67 deletions(-) diff --git a/docs/tutorials/your-first-zio-http-app.md b/docs/tutorials/your-first-zio-http-app.md index 2522357c25..3fcd776ef2 100644 --- a/docs/tutorials/your-first-zio-http-app.md +++ b/docs/tutorials/your-first-zio-http-app.md @@ -3,99 +3,202 @@ id: your-first-zio-http-app title: Your First Zio http app --- -# Tutorial: Your First ZIO HTTP App +# your first ZIO HTTP -ZIO is a modern, high-performance library for asynchronous programming in Scala. It provides a powerful and expressive API for building scalable and reliable applications. ZIO also has a strong focus on type safety and resource management, which makes it a popular choice for building server-side applications. +This tutorial will guide you through the basics of ZIO HTTP, a powerful library for building highly performant HTTP-based services and clients using functional Scala and ZIO. ZIO HTTP uses Netty as its core and provides functional domains for creating, modifying, and composing HTTP apps easily. -In this tutorial, we will learn how to build a simple HTTP server using ZIO and the ZIO HTTP module. By the end of this tutorial, you will have a basic understanding of how to build HTTP servers using ZIO and the ZIO HTTP module. +## Prerequisites -## Introduction +Before you begin, make sure you have the following: -ZIO is a modern, high-performance library for asynchronous programming in Scala. It provides a powerful and expressive API for building scalable and reliable applications. ZIO also has a strong focus on type safety and resource management, which makes it a popular choice for building server-side applications. +- Basic knowledge of Scala programming language +- Familiarity with functional programming concepts +- SBT or another build tool for Scala projects -In this tutorial, we will learn how to build a simple HTTP server using ZIO and the ZIO HTTP module. By the end of this tutorial, you will have a basic understanding of how to build HTTP servers using ZIO and the ZIO HTTP module. +## Installation -## Setting up the Project +To use ZIO HTTP in your Scala project, you need to add the necessary dependencies to your build configuration. Add the following lines to your `build.sbt` file: -To get started, we need to set up a new SBT project. Here are the steps to do that: +```scala +libraryDependencies ++= Seq( + "dev.zio" %% "zio" % "", + "dev.zio" %% "zio-http" % "" +) +``` + +Replace `` with the desired version of ZIO and ZIO HTTP. You can find the latest versions on the [ZIO GitHub page](https://github.com/zio/zio) and [ZIO HTTP GitHub page](https://github.com/zio/zio-http). -1. Open your preferred text editor and create a new directory for your project. -2. Open a terminal window and navigate to the project directory. -3. Run the following command to create a new SBT project: +After updating the build configuration, refresh your project dependencies. + +## Creating an HTTP App + +The first step when using ZIO HTTP is to create an HTTP app. The `Http` domain provides various constructors for creating HTTP apps based on different request and response types. Here's an example of creating a "Hello World" app: ```scala -scalaVersion := "2.13.8" +import zio.http._ -libraryDependencies ++= Seq( - "dev.zio" %% "zio" % "2.0.14", - "dev.zio" %% "zio-streams" % "2.0.14", - "dev.zio" %% "zio-interop-cats" % "2.5.1.0", - "dev.zio" %% "zio-logging" % "0.5.12", - "dev.zio" %% "zio-logging-slf4j" % "0.5.12", - "org.http4s" %% "http4s-blaze-server" % "1.0.0-M23", - "org.http4s" %% "http4s-dsl" % "1.0.0-M23" -) +val app = Http.text("Hello World!") +``` + +In the above example, we use the `Http.text` constructor to create an HTTP app that always responds with the text "Hello World!". + +You can explore other constructors like `Http.html`, `Http.fromFile`, `Http.fromData`, `Http.fromStream`, and `Http.fromEffect` to handle different types of responses. + +## Routing + +ZIO HTTP provides the `collect` method in the `Http` domain for handling routes. It allows you to define different patterns for requests and produce corresponding responses. Here's an example of creating routes: + +```scala +import zio.http._ + +val app = Http.collect[Request] { + case Method.GET -> !! / "fruits" / "a" => Response.text("Apple") + case Method.GET -> !! / "fruits" / "b" => Response.text("Banana") +} +``` + +In the above example, we define two routes: one for `/fruits/a` and another for `/fruits/b`. For a GET request to `/fruits/a`, the response will be "Apple", and for a GET request to `/fruits/b`, the response will be "Banana". + +You can also create typed routes by extracting values from the URL path. Here's an example that accepts the count as an `Int`: + +```scala +import zio.http._ + +val app = Http.collect[Request] { + case Method.GET -> !! / "Apple" / int(count) => Response.text(s"Apple: $count") +} +``` + +In the above example, the route will match URLs like `/Apple/10`, where `10` will be extracted as an `Int` and used in the response. + +## Composition + +ZIO HTTP allows you to compose multiple apps using operators in the `Http` domain. Two commonly used operators are `++` and `<>`. + +Using the `++` operator, you can combine multiple apps, and if none of the routes match in the first app, the control is passed to the next app. Here's an example: + +```scala +import zio.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 +``` + +In the above example, the `app` combines two apps `a` and `b`. If a GET request matches `/a`, the response will be provided by app `a`, and if it matches `/b`, the response will be provided by app `b`. + +Using the `<>` operator, you can handle failure scenarios. If the first app fails, the control is passed to the next app. Here's an example: + +```scala +import zio.http._ + +val a = Http.fail(new Error("SERVER_ERROR")) +val b = Http.text("OK") + +val app = a <> b ``` -4.Save the file. -Building the HTTP Server -Create a new file named Main.scala in the project's root directory. - -Open Main.scala and add the following code: - -```import zio._ -import zio.console._ -import org.http4s._ -import org.http4s.dsl.Http4sDsl -import org.http4s.blaze.server.BlazeServerBuilder -import scala.concurrent.ExecutionContext.global - -object Main extends App { - - val dsl = Http4sDsl[Task] - import dsl._ - - val appLogic: HttpRoutes[Task] = HttpRoutes.of[Task] { - case GET -> Root / "hello" => - Ok("Hello, ZIO HTTP!") - } - - val server: ZIO[ZEnv, Throwable, Unit] = - ZIO.runtime[ZEnv].flatMap { implicit rts => - BlazeServerBuilder[Task](global) - .bindHttp(8080, "0.0.0.0") - .withHttpApp(appLogic.orNotFound) - .serve - .compile - .drain - } - - def run(args: List[String]): URIO[ZEnv, ExitCode] = - server.provideCustomLayer(Console.live).exitCode +In the above example, the `app` first tries app `a`. If app `a` fails, it falls back to app `b`, which responds with "OK". + +## ZIO Integration + +ZIO HTTP seamlessly integrates with ZIO, allowing you to create effectful apps. You can use the `collectZIO` method and wrap the `Response` with `UIO` to produce a ZIO effect value. Here's an example: + +```scala +import zio.http._ + +val app = Http.collectZIO[Request] { + case Method.GET -> !! / "hello" => UIO(Response.text("Hello World")) } ``` -.Save the file. +In the above example, the `app` handles a GET request to `/hello` and responds with "Hello World" wrapped in a ZIO effect. + +## Accessing the Request + +To access the request details, you can use the `@` symbol to bind the matched pattern to a variable. This allows you to use the request details while creating a response. Here's an example: + +```scala +import zio.http._ + +val app = Http.collectZIO[Request] { + case req @ Method.GET -> !! / "fruits" / "a" => + UIO(Response.text("URL:" + req.url.path.asString + " Headers: " + req.getHeaders)) + case req @ Method.POST -> !! / "fruits" / "a" => + req.bodyAsString.map(Response.text(_)) +} +``` -# Running the HTTP Server +In the above example, the `app` handles GET and POST requests to `/fruits/a`. For the GET request, it constructs a response with the URL and headers, and for the POST request, it uses the request body in the response. -To run the HTTP server, open your terminal and navigate to the project's root directory. +## Testing -Run the following command: +You can test your HTTP apps using the ZIO Test framework. Since the `Http` domain is a function, you can call it like any other function and assert the expected responses. Here's an example: +```scala +import zio.test._ +import zio.http._ + +object Spec extends DefaultRunnableSpec { + + def spec = suite("http")( + test("should be ok") { + val app = Http.ok + val req = Request() + assertZIO(app(req))(equalTo(Response.ok)) + } + ) +} ``` -sbt run +In the above example, the test checks if the `app` responds with an `Ok` response when given a request. + +## Socket + +ZIO HTTP also provides a functional domain called `Socket` for handling WebSocket connections. You can create socket apps using `Socket` constructors. Here's an example of a simple socket app: + +```scala +import zio.socket._ + +private val socket = Socket.collect[WebSocketFrame] { + case WebSocketFrame.Text("FOO") => + ZStream.succeed(WebSocketFrame.text("BAR")) +} + +private val app = Http.collectZIO[Request] { + case Method.GET -> !! / "greet" / name => UIO(Response + +.text(s"Greetings {$name}!")) + case Method.GET -> !! / "ws" => socket.toResponse +} ``` -Wait for the server to start. You should see a message indicating that the server is running on . +In the above example, the `socket` collects WebSocket frames and responds with "BAR" when it receives "FOO" as a `WebSocketTextFrame`. The `app` also handles a GET request to `/greet/:name` and responds with a greeting message. + +## Server -# Testing the HTTP Server +To serve HTTP requests, you need to start an HTTP server. ZIO HTTP provides a way to configure your server according to your needs. You can specify the server configuration, such as the leak detection level, request size limits, and address. Here's an example of starting an HTTP server: + +```scala +import zio.http._ +import zio.http.Server +import zio._ + +object HelloWorld extends App { + val app = Http.ok + + override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = + Server.start(8090, app).exitCode +} +``` -To test the HTTP server, open your web browser or use a tool like cURL or Postman. +In the above example, the `app` is a simple HTTP app that responds with an empty content and a 200 status code. The server is started on port 8090 using `Server.start`. -Open your browser and visit . +## Conclusion -You should see the response "Hello, ZIO HTTP!". +In this tutorial, you learned the basics of getting started with ZIO HTTP. You learned how to create HTTP apps, handle routing, compose apps, integrate with ZIO, access request details, test your apps, work with sockets, and start an HTTP server. With ZIO HTTP, you can build highly performant and functional HTTP-based services and clients in Scala. -Congratulations! You have successfully built. +Feel free to explore the ZIO HTTP documentation and experiment with different features to build your own applications. Happy coding! \ No newline at end of file From b53a3c234563eaef41b51a58799302276c9ecbc6 Mon Sep 17 00:00:00 2001 From: felicien Date: Mon, 12 Jun 2023 22:06:20 +0000 Subject: [PATCH 27/71] update zio frist app --- docs/tutorials/your-first-zio-http-app.md | 218 +++++----------------- 1 file changed, 45 insertions(+), 173 deletions(-) diff --git a/docs/tutorials/your-first-zio-http-app.md b/docs/tutorials/your-first-zio-http-app.md index 3fcd776ef2..42979119d7 100644 --- a/docs/tutorials/your-first-zio-http-app.md +++ b/docs/tutorials/your-first-zio-http-app.md @@ -1,204 +1,76 @@ ---- -id: your-first-zio-http-app -title: Your First Zio http app ---- +# ZIO Quickstart: Hello World -# your first ZIO HTTP +The ZIO Quickstart Hello World is a simple example that demonstrates the basics of writing a ZIO application. It showcases how to interact with the console, read input from the user, and perform simple operations using ZIO's effect system. -This tutorial will guide you through the basics of ZIO HTTP, a powerful library for building highly performant HTTP-based services and clients using functional Scala and ZIO. ZIO HTTP uses Netty as its core and provides functional domains for creating, modifying, and composing HTTP apps easily. +## Running The Example -## Prerequisites +To run the example, follow these steps: -Before you begin, make sure you have the following: +1. Open the console and clone the ZIO Quickstarts project using Git. You can also download the project directly. + ``` + git clone git@github.com:zio/zio-quickstarts.git + ``` -- Basic knowledge of Scala programming language -- Familiarity with functional programming concepts -- SBT or another build tool for Scala projects +2. Change the directory to the `zio-quickstarts/zio-quickstart-hello-world` folder. + ``` + cd zio-quickstarts/zio-quickstart-hello-world + ``` -## Installation +3. Once you are inside the project directory, execute the following command to run the application: + ``` + sbt run + ``` -To use ZIO HTTP in your Scala project, you need to add the necessary dependencies to your build configuration. Add the following lines to your `build.sbt` file: +## Testing The Quickstart -```scala -libraryDependencies ++= Seq( - "dev.zio" %% "zio" % "", - "dev.zio" %% "zio-http" % "" -) -``` - -Replace `` with the desired version of ZIO and ZIO HTTP. You can find the latest versions on the [ZIO GitHub page](https://github.com/zio/zio) and [ZIO HTTP GitHub page](https://github.com/zio/zio-http). - -After updating the build configuration, refresh your project dependencies. - -## Creating an HTTP App - -The first step when using ZIO HTTP is to create an HTTP app. The `Http` domain provides various constructors for creating HTTP apps based on different request and response types. Here's an example of creating a "Hello World" app: - -```scala -import zio.http._ - -val app = Http.text("Hello World!") -``` - -In the above example, we use the `Http.text` constructor to create an HTTP app that always responds with the text "Hello World!". - -You can explore other constructors like `Http.html`, `Http.fromFile`, `Http.fromData`, `Http.fromStream`, and `Http.fromEffect` to handle different types of responses. - -## Routing - -ZIO HTTP provides the `collect` method in the `Http` domain for handling routes. It allows you to define different patterns for requests and produce corresponding responses. Here's an example of creating routes: - -```scala -import zio.http._ - -val app = Http.collect[Request] { - case Method.GET -> !! / "fruits" / "a" => Response.text("Apple") - case Method.GET -> !! / "fruits" / "b" => Response.text("Banana") -} -``` - -In the above example, we define two routes: one for `/fruits/a` and another for `/fruits/b`. For a GET request to `/fruits/a`, the response will be "Apple", and for a GET request to `/fruits/b`, the response will be "Banana". - -You can also create typed routes by extracting values from the URL path. Here's an example that accepts the count as an `Int`: - -```scala -import zio.http._ - -val app = Http.collect[Request] { - case Method.GET -> !! / "Apple" / int(count) => Response.text(s"Apple: $count") -} -``` - -In the above example, the route will match URLs like `/Apple/10`, where `10` will be extracted as an `Int` and used in the response. - -## Composition - -ZIO HTTP allows you to compose multiple apps using operators in the `Http` domain. Two commonly used operators are `++` and `<>`. - -Using the `++` operator, you can combine multiple apps, and if none of the routes match in the first app, the control is passed to the next app. Here's an example: - -```scala -import zio.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 -``` - -In the above example, the `app` combines two apps `a` and `b`. If a GET request matches `/a`, the response will be provided by app `a`, and if it matches `/b`, the response will be provided by app `b`. - -Using the `<>` operator, you can handle failure scenarios. If the first app fails, the control is passed to the next app. Here's an example: - -```scala -import zio.http._ - -val a = Http.fail(new Error("SERVER_ERROR")) -val b = Http.text("OK") - -val app = a <> b -``` - -In the above example, the `app` first tries app `a`. If app `a` fails, it falls back to app `b`, which responds with "OK". - -## ZIO Integration - -ZIO HTTP seamlessly integrates with ZIO, allowing you to create effectful apps. You can use the `collectZIO` method and wrap the `Response` with `UIO` to produce a ZIO effect value. Here's an example: +The `sbt run` command searches for the executable class defined in the project, which in this case is `zio.dev.quickstart.MainApp`. The code for this class is as follows: ```scala -import zio.http._ - -val app = Http.collectZIO[Request] { - case Method.GET -> !! / "hello" => UIO(Response.text("Hello World")) -} -``` - -In the above example, the `app` handles a GET request to `/hello` and responds with "Hello World" wrapped in a ZIO effect. - -## Accessing the Request - -To access the request details, you can use the `@` symbol to bind the matched pattern to a variable. This allows you to use the request details while creating a response. Here's an example: - -```scala -import zio.http._ - -val app = Http.collectZIO[Request] { - case req @ Method.GET -> !! / "fruits" / "a" => - UIO(Response.text("URL:" + req.url.path.asString + " Headers: " + req.getHeaders)) - case req @ Method.POST -> !! / "fruits" / "a" => - req.bodyAsString.map(Response.text(_)) -} -``` - -In the above example, the `app` handles GET and POST requests to `/fruits/a`. For the GET request, it constructs a response with the URL and headers, and for the POST request, it uses the request body in the response. - -## Testing - -You can test your HTTP apps using the ZIO Test framework. Since the `Http` domain is a function, you can call it like any other function and assert the expected responses. Here's an example: - -```scala -import zio.test._ -import zio.http._ - -object Spec extends DefaultRunnableSpec { +import zio._ - def spec = suite("http")( - test("should be ok") { - val app = Http.ok - val req = Request() - assertZIO(app(req))(equalTo(Response.ok)) - } - ) +object MainApp extends ZIOAppDefault { + def run = Console.printLine("Hello, World!") } ``` -In the above example, the test checks if the `app` responds with an `Ok` response when given a request. +This code uses ZIO's `Console.printLine` effect to print "Hello, World!" to the console. -## Socket - -ZIO HTTP also provides a functional domain called `Socket` for handling WebSocket connections. You can create socket apps using `Socket` constructors. Here's an example of a simple socket app: +To enhance the quickstart and ask for the user's name, modify the code as shown below: ```scala -import zio.socket._ - -private val socket = Socket.collect[WebSocketFrame] { - case WebSocketFrame.Text("FOO") => - ZStream.succeed(WebSocketFrame.text("BAR")) -} - -private val app = Http.collectZIO[Request] { - case Method.GET -> !! / "greet" / name => UIO(Response +import zio._ -.text(s"Greetings {$name}!")) - case Method.GET -> !! / "ws" => socket.toResponse +object MainApp extends ZIOAppDefault { + def run = + for { + _ <- Console.print("Please enter your name: ") + name <- Console.readLine + _ <- Console.printLine(s"Hello, $name!") + } yield () } ``` -In the above example, the `socket` collects WebSocket frames and responds with "BAR" when it receives "FOO" as a `WebSocketTextFrame`. The `app` also handles a GET request to `/greet/:name` and responds with a greeting message. +In this updated example, we use a for-comprehension to compose ZIO effects. It prompts the user to enter their name, reads the input using `Console.readLine`, and prints a customized greeting using `Console.printLine`. -## Server - -To serve HTTP requests, you need to start an HTTP server. ZIO HTTP provides a way to configure your server according to your needs. You can specify the server configuration, such as the leak detection level, request size limits, and address. Here's an example of starting an HTTP server: +Alternatively, you can rewrite the code using explicit `flatMap` operations: ```scala -import zio.http._ -import zio.http.Server import zio._ -object HelloWorld extends App { - val app = Http.ok - - override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = - Server.start(8090, app).exitCode +object MainApp extends ZIOAppDefault { + def run = + Console.print("Please enter your name: ") + .flatMap { _ => + Console.readLine + .flatMap { name => + Console.printLine(s"Hello, $name!") + } + } } ``` -In the above example, the `app` is a simple HTTP app that responds with an empty content and a 200 status code. The server is started on port 8090 using `Server.start`. - -## Conclusion +Both versions of the code achieve the same result. -In this tutorial, you learned the basics of getting started with ZIO HTTP. You learned how to create HTTP apps, handle routing, compose apps, integrate with ZIO, access request details, test your apps, work with sockets, and start an HTTP server. With ZIO HTTP, you can build highly performant and functional HTTP-based services and clients in Scala. +By running the application with the modified code, you will be prompted to enter your name, and the program will respond with a personalized greeting. -Feel free to explore the ZIO HTTP documentation and experiment with different features to build your own applications. Happy coding! \ No newline at end of file +Feel free to experiment and modify the code to explore more features and capabilities of ZIO. \ No newline at end of file From 56cceadbe47632c11876f8b23f9f85f2867900f4 Mon Sep 17 00:00:00 2001 From: daveads Date: Tue, 13 Jun 2023 11:32:46 +0100 Subject: [PATCH 28/71] rm --- docs/Reference/ref.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/Reference/ref.md diff --git a/docs/Reference/ref.md b/docs/Reference/ref.md deleted file mode 100644 index e69de29bb2..0000000000 From c5b58d500261df9c865a292666589ded441daafe Mon Sep 17 00:00:00 2001 From: daveads Date: Tue, 13 Jun 2023 16:50:47 +0100 Subject: [PATCH 29/71] performance --- docs/performance.md | 60 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/docs/performance.md b/docs/performance.md index e69de29bb2..24fd2aff5e 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -0,0 +1,60 @@ +--- +id: performance +title: Performance +--- + +## Performance + + +The ZIO-HTTP server offers a streamlined layer that sits atop the underlying server APIs, resulting in minimal overhead compared to directly utilizing the raw server APIs. This abstraction layer allows for handling HTTP requests and responses in a purely functional and composable manner. + + +ZIO-HTTP leverages the powerful concurrency and composition capabilities of the ZIO library to provide high-performance, asynchronous handling of HTTP requests. It utilizes non-blocking I/O and lightweight fibers for concurrency, allowing efficient resource utilization and minimizing overhead. + + +## Web Frameworks Benchmark + +Results of zio-http on a well-regarded [Web Frameworks Benchmark](https://web-frameworks-benchmark.netlify.app/) project, which evaluates frameworks through a series of practical tests. + +ZIO-HTTP's performance is not just theoretical; it has been tested and validated against realistic benchmarks. `ZIO-HTTP` has demonstrated its ability to handle various real-world scenarios with exceptional speed and efficiency. + +The full implementation of the benchmark can be found [here](https://github.com/the-benchmarker/web-frameworks/tree/master/scala/zio-http). + + +### Technical Details + +ZIO-HTTP was benchmarked using wrk (threads: 8, timeout: 8, duration: 15 seconds) with 64, 256, and 512 concurrency. +Hardware used for the benchmark: + +* CPU: 8 Cores (AMD FX-8320E Eight-Core Processor) +* RAM: 16 GB +* OS: Linux + + + +### Benchmark Result + + Zio-http + +| Benchmark | Resut | +| :------- | -------: | +| Requests/Second (64) | 103 569 | +| Requests/second (256) | 117 193 | +| Requests/second (512) | 114 772 | +| P50 latency (64) | 0.49 ms | +| P50 latency (256) | 1.84 ms | +| P50 latency (512) | 4.42 ms | +| Average Latency (ms) (64) | 1.17 ms | +| Average Latency (ms) (256) | 2.30 ms | +| Average Latency (ms) (512) | 4.76 ms | +| Minimum Latency (ms) (64) | 0.05 ms | +| Minimum Latency (ms) (256) | 0.05 ms | +| Minimum Latency (ms) (512) | 0.06 ms | +| Maximum Latency (ms) (64) | 104.91 ms | +| Maximum Latency (ms) (256) | 41.14 ms | +| Maximum Latency (ms) (512) | 57.66 ms | + + + + + ZIO-HTTP's remarkable benchmark performance can be attributed to its concurrency model, lightweight execution, asynchronous I/O, functional design principles, composable middleware, integration with the ZIO ecosystem, and validation through realistic benchmarking. These factors combined make ZIO-HTTP an excellent choice for building high-performance and scalable web applications. From 5a2bd675625caaa1bf23df308d4c1a0e4118425e Mon Sep 17 00:00:00 2001 From: daveads Date: Tue, 13 Jun 2023 23:40:26 +0100 Subject: [PATCH 30/71] zio http how to guide --- docs/how-to-guides/Middleware.md | 59 ---------- docs/how-to-guides/advance-http-sever.md | 53 +++++++++ docs/how-to-guides/authentication.md | 74 +++++++++++++ docs/how-to-guides/endpoint.md | 71 ++++++++++++ docs/how-to-guides/http-client.md | 42 +++++++ docs/how-to-guides/http-sever.md | 43 ++++++++ docs/how-to-guides/https-client.md | 46 ++++++++ docs/how-to-guides/https-server.md | 46 ++++++++ .../middleware-basic-authentication.md | 31 ++++++ .../how-to-guides/middleware-cors-handling.md | 39 +++++++ docs/how-to-guides/streaming-file.md | 40 +++++++ docs/how-to-guides/streaming-response.md | 37 +++++++ docs/how-to-guides/websocket.md | 103 ++++++++++++++++++ 13 files changed, 625 insertions(+), 59 deletions(-) delete mode 100644 docs/how-to-guides/Middleware.md create mode 100644 docs/how-to-guides/advance-http-sever.md create mode 100644 docs/how-to-guides/authentication.md create mode 100644 docs/how-to-guides/http-client.md create mode 100644 docs/how-to-guides/http-sever.md create mode 100644 docs/how-to-guides/https-client.md create mode 100644 docs/how-to-guides/https-server.md create mode 100644 docs/how-to-guides/middleware-basic-authentication.md create mode 100644 docs/how-to-guides/middleware-cors-handling.md create mode 100644 docs/how-to-guides/streaming-file.md create mode 100644 docs/how-to-guides/streaming-response.md create mode 100644 docs/how-to-guides/websocket.md diff --git a/docs/how-to-guides/Middleware.md b/docs/how-to-guides/Middleware.md deleted file mode 100644 index a37c38fe59..0000000000 --- a/docs/how-to-guides/Middleware.md +++ /dev/null @@ -1,59 +0,0 @@ -# Middleware Tutorial - -Middleware is a powerful tool in managing the complexity of HTTP applications and addressing common concerns. It allows for clean and modular code organization, making it easier to maintain and evolve the application over time. - -## What is middleware? - -Middleware is a layer of software that sits between the application and the HTTP server. It intercepts all requests and responses, and can be used to perform a variety of tasks, such as: - -* Authentication -* Authorization -* Logging -* Error handling -* Rate limiting -* Caching - -## Why are middlewares needed? - -Middleware is needed to handle common cross-cutting concerns that are not specific to a particular route or endpoint. For example, authentication and authorization are concerns that need to be applied to all requests, regardless of the route being accessed. - -If we were to implement authentication and authorization directly in our application code, it would quickly become cluttered and difficult to maintain. Middleware allows us to keep our application code clean and focused, while the concerns are managed independently. - -## How do middlewares work? - -Middlewares are typically implemented as functions that take an `HttpRequest` and return an `HttpResponse`. The middleware function can then perform any number of tasks, such as: - -* Inspecting the request headers -* Parsing the request body -* Calling an external service -* Logging the request -* Throwing an error - -If the middleware function returns a non-`HttpResponse`, the request will be aborted and the middleware will not be called again. - -## How to use middlewares in ZIO-HTTP - -ZIO-HTTP provides a number of built-in middlewares, as well as a simple API for creating custom middlewares. To use a middleware, simply attach it to your `HttpApp` using the `@@` operator. - -For example, the following code attaches the `BasicAuthMiddleware` to the `HttpApp`: - -```scala -val app = HttpApp.collectZIO[Request] { - case Method.GET -> Root / "users" / id => - // Core business logic - dbService.lookupUsersById(id).map(Response.json(_.json)) - case Method.GET -> Root / "users" => - // Core business logic - dbService.paginatedUsers(pageNum).map(Response.json(_.json)) -} @@ BasicAuthMiddleware(credentials) -``` - -The `BasicAuthMiddleware` middleware will inspect the `Authorization` header of each request and verify that it contains a valid username and password. If the credentials are valid, the middleware will continue the request processing chain. If the credentials are invalid, the middleware will return a `Unauthorized` response. - -## Conclusion - -Middleware is a powerful tool that can be used to manage the complexity of HTTP applications and address common concerns. By using middleware, we can keep our application code clean and focused, while the concerns are managed independently. - -Using the ZIO-HTTP library, you can easily attach, combine, and create your own middlewares to address your application's specific needs. - -That concludes our tutorial on middleware. Happy coding! diff --git a/docs/how-to-guides/advance-http-sever.md b/docs/how-to-guides/advance-http-sever.md new file mode 100644 index 0000000000..d091f1ec83 --- /dev/null +++ b/docs/how-to-guides/advance-http-sever.md @@ -0,0 +1,53 @@ +--- +id: advance-http-sever +title: Advance Http server Example +--- + +# Advanced http sever + +This demonstrates an advanced example of creating an HTTP server + +## code + +```scala +import scala.util.Try + +import zio._ + +import zio.http._ +import zio.http.netty.NettyConfig +import zio.http.netty.NettyConfig.LeakDetectionLevel + +object HelloWorldAdvanced extends ZIOAppDefault { + // Set a port + private val PORT = 0 + + private val fooBar: HttpApp[Any, Nothing] = Http.collect[Request] { + case Method.GET -> Root / "foo" => Response.text("bar") + case Method.GET -> Root / "bar" => Response.text("foo") + } + + private val app = Http.collectZIO[Request] { + case Method.GET -> Root / "random" => Random.nextString(10).map(Response.text(_)) + case Method.GET -> Root / "utc" => Clock.currentDateTime.map(s => Response.text(s.toString)) + } + + val run = ZIOAppArgs.getArgs.flatMap { args => + // Configure thread count using CLI + val nThreads: Int = args.headOption.flatMap(x => Try(x.toInt).toOption).getOrElse(0) + + val config = Server.Config.default + .port(PORT) + val nettyConfig = NettyConfig.default + .leakDetection(LeakDetectionLevel.PARANOID) + .maxThreads(nThreads) + val configLayer = ZLayer.succeed(config) + val nettyConfigLayer = ZLayer.succeed(nettyConfig) + + (Server.install(fooBar ++ app).flatMap { port => + Console.printLine(s"Started server on port: $port") + } *> ZIO.never) + .provide(configLayer, nettyConfigLayer, Server.customized) + } +} +``` \ No newline at end of file diff --git a/docs/how-to-guides/authentication.md b/docs/how-to-guides/authentication.md new file mode 100644 index 0000000000..16eeb089f0 --- /dev/null +++ b/docs/how-to-guides/authentication.md @@ -0,0 +1,74 @@ +--- +id: authentication +title: "Authentication Server Example" +--- + + +This code shows how to how to implement a server with bearer authentication middle ware in `zio-http` + + + +```scala + +import java.time.Clock + +import zio._ + +import zio.http.HttpAppMiddleware.bearerAuth +import zio.http._ + +import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim} + +object AuthenticationServer extends ZIOAppDefault { + + /** + * This is an example to demonstrate barer Authentication middleware. The + * Server has 2 routes. The first one is for login,Upon a successful login, it + * will return a jwt token for accessing protected routes. The second route is + * a protected route that is accessible only if the request has a valid jwt + * token. AuthenticationClient example can be used to makes requests to this + * server. + */ + + // Secret Authentication key + val SECRET_KEY = "secretKey" + + implicit val clock: Clock = Clock.systemUTC + + // Helper to encode the JWT token + def jwtEncode(username: String): String = { + val json = s"""{"user": "${username}"}""" + val claim = JwtClaim { + json + }.issuedNow.expiresIn(300) + Jwt.encode(claim, SECRET_KEY, JwtAlgorithm.HS512) + } + + // Helper to decode the JWT token + def jwtDecode(token: String): Option[JwtClaim] = { + Jwt.decode(token, SECRET_KEY, Seq(JwtAlgorithm.HS512)).toOption + } + + // Http app that is accessible only via a jwt token + def user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => + Response.text(s"Welcome to the ZIO party! ${name}") + } @@ bearerAuth(jwtDecode(_).isDefined) + + // App that let's the user login + // Login is successful only if the password is the reverse of the username + def login: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "login" / username / password => + if (password.reverse.hashCode == username.hashCode) Response.text(jwtEncode(username)) + else Response.text("Invalid username or password.").withStatus(Status.Unauthorized) + } + + // Composing all the HttpApps together + val app: HttpApp[Any, Nothing] = login ++ user + + // Run it like any simple app + override val run = Server.serve(app).provide(Server.default) +} +``` + +### Explanation + +It showcases two routes: one for user login and another for accessing protected routes using a JWT (JSON Web Token) authentication mechanism. The protected route is accessible only if the request contains a valid JWT token. diff --git a/docs/how-to-guides/endpoint.md b/docs/how-to-guides/endpoint.md index e69de29bb2..b3d2081591 100644 --- a/docs/how-to-guides/endpoint.md +++ b/docs/how-to-guides/endpoint.md @@ -0,0 +1,71 @@ +--- +id: endpoint +title: RESTful API Endpoints with ZIO HTTP Example +--- + +This code demonstrates the usage of ZIO HTTP's endpoint module to define and implement RESTful endpoints. + +## Code + +```scala +package example + +import zio._ + +import zio.http.Header.Authorization +import zio.http._ +import zio.http.codec.HttpCodec +import zio.http.endpoint._ + +object EndpointExamples extends ZIOAppDefault { + import HttpCodec._ + + val auth = EndpointMiddleware.auth + + // MiddlewareSpec can be added at the service level as well + val getUser = + Endpoint.get("users" / int("userId")).out[Int] @@ auth + + val getUserRoute = + getUser.implement { id => + ZIO.succeed(id) + } + + val getUserPosts = + Endpoint + .get("users" / int("userId") / "posts" / int("postId")) + .query(query("name")) + .out[List[String]] @@ auth + + val getUserPostsRoute = + getUserPosts.implement[Any] { case (id1: Int, id2: Int, query: String) => + ZIO.succeed(List(s"API2 RESULT parsed: users/$id1/posts/$id2?name=$query")) + } + + val routes = getUserRoute ++ getUserPostsRoute + + val app = routes.toApp(auth.implement(_ => ZIO.unit)(_ => ZIO.unit)) + + val request = Request.get(url = URL.decode("/users/1").toOption.get) + + val run = Server.serve(app).provide(Server.default) + + object ClientExample { + def example(client: Client) = { + val locator = + EndpointLocator.fromURL(URL.decode("http://localhost:8080").toOption.get) + + val executor: EndpointExecutor[Authorization] = + EndpointExecutor(client, locator, ZIO.succeed(Authorization.Basic("user", "pass"))) + + val x1 = getUser(42) + val x2 = getUserPosts(42, 200, "adam") + + val result1: UIO[Int] = executor(x1) + val result2: UIO[List[String]] = executor(x2) + + result1.zip(result2).debug + } + } +} +``` \ No newline at end of file diff --git a/docs/how-to-guides/http-client.md b/docs/how-to-guides/http-client.md new file mode 100644 index 0000000000..16be9e0340 --- /dev/null +++ b/docs/how-to-guides/http-client.md @@ -0,0 +1,42 @@ +--- +id: http-client +title: HTTP Client +--- + + +This example provided demonstrates how to perform an HTTP client request using the zio-http library in Scala with ZIO. + + +## `build.sbt` Setup + +```scala +scalaVersion := "2.13.6" + +libraryDependencies ++= Seq( + "dev.zio" %% "zio-http" % "3.0.0-RC2" +) +``` + + +## code + +```scala +import zio._ +import zio.http.Client + +object SimpleClient extends ZIOAppDefault { + val url = "http://sports.api.decathlon.com/groups/water-aerobics" + + val program = for { + res <- Client.request(url) + data <- res.body.asString + _ <- Console.printLine(data).catchAll(e => ZIO.logError(e.getMessage)) + } yield () + + override val run = program.provide(Client.default) + +} + +``` + + diff --git a/docs/how-to-guides/http-sever.md b/docs/how-to-guides/http-sever.md new file mode 100644 index 0000000000..41c662fe7f --- /dev/null +++ b/docs/how-to-guides/http-sever.md @@ -0,0 +1,43 @@ +--- +id: http-sever +title: HTTP server +--- + + +# Simple http sever + +This example demonstrates the creation of a simple HTTP server in zio-http with ZIO. + + +## `build.sbt` Setup + +```scala +scalaVersion := "2.13.6" + +libraryDependencies ++= Seq( + "dev.zio" %% "zio-http" % "3.0.0-RC2" +) +``` + + +## code + +```scala +import zio._ +import zio.http._ + +object HelloWorld extends ZIOAppDefault { + + // Create HTTP route + val app: HttpApp[Any, Nothing] = Http.collect[Request] { + case Method.GET -> Root / "text" => Response.text("Hello World!") + case Method.GET -> Root / "json" => Response.json("""{"greetings": "Hello World!"}""") + } + + // Run it like any simple app + override val run = Server.serve(app).provide(Server.default) +} + +``` +
+
\ No newline at end of file diff --git a/docs/how-to-guides/https-client.md b/docs/how-to-guides/https-client.md new file mode 100644 index 0000000000..a4a0cc50d2 --- /dev/null +++ b/docs/how-to-guides/https-client.md @@ -0,0 +1,46 @@ +--- +id: https-client +title: HTTPS Client +--- + +This code demonstrate a simple HTTPS client that send an HTTP GET request to a specific URL and retrieve the response. + + +## code + +```scala +import zio._ + +import zio.http._ +import zio.http.netty.NettyConfig +import zio.http.netty.client.NettyClientDriver + +object HttpsClient extends ZIOAppDefault { + val url = "https://sports.api.decathlon.com/groups/water-aerobics" + val headers = Headers(Header.Host("sports.api.decathlon.com")) + + val sslConfig = ClientSSLConfig.FromTrustStoreResource( + trustStorePath = "truststore.jks", + trustStorePassword = "changeit", + ) + + val clientConfig = ZClient.Config.default.ssl(sslConfig) + + val program = for { + res <- Client.request(url, headers = headers) + data <- res.body.asString + _ <- Console.printLine(data) + } yield () + + val run = + program.provide( + ZLayer.succeed(clientConfig), + Client.customized, + NettyClientDriver.live, + DnsResolver.default, + ZLayer.succeed(NettyConfig.default), + ) + +} + +``` \ No newline at end of file diff --git a/docs/how-to-guides/https-server.md b/docs/how-to-guides/https-server.md new file mode 100644 index 0000000000..b6e99421d6 --- /dev/null +++ b/docs/how-to-guides/https-server.md @@ -0,0 +1,46 @@ +--- +id: https-sever +title: Https server +--- + +# Http sever with Https example +This example demonstrates how to use ZIO to create an HTTP server with HTTPS support and configure SSL using a keystore + + +```scala +import zio._ +import zio.http._ + +object HttpsHelloWorld extends ZIOAppDefault { + // Create HTTP route + val app: HttpApp[Any, Nothing] = Http.collect[Request] { + case Method.GET -> Root / "text" => Response.text("Hello World!") + case Method.GET -> Root / "json" => Response.json("""{"greetings": "Hello World!"}""") + } + + /** + * In this example an inbuilt API using keystore is used. For testing this + * example using curl, setup the certificate named "server.crt" from resources + * for the OS. Alternatively you can create the keystore and certificate using + * the following link + * https://medium.com/@maanadev/netty-with-https-tls-9bf699e07f01 + */ + + val sslConfig = SSLConfig.fromResource( + behaviour = SSLConfig.HttpBehaviour.Accept, + certPath = "server.crt", + keyPath = "server.key", + ) + + private val config = Server.Config.default + .port(8090) + .ssl(sslConfig) + + private val configLayer = ZLayer.succeed(config) + + override val run = + Server.serve(app).provide(configLayer, Server.live) + +} + +``` \ No newline at end of file diff --git a/docs/how-to-guides/middleware-basic-authentication.md b/docs/how-to-guides/middleware-basic-authentication.md new file mode 100644 index 0000000000..ba482c3cdb --- /dev/null +++ b/docs/how-to-guides/middleware-basic-authentication.md @@ -0,0 +1,31 @@ +--- +id: middleware-basic-authentication +title: "Middleware Basic Authentication Example" +--- + +This code demonstrates how to configure an HTTP server with basic authentication. Basic authentication is a method of enforcing access control to an HTTP server by requiring clients to provide valid credentials. + + +## Code + +```scala mdoc:silent +import zio._ + +import zio.http.HttpAppMiddleware.basicAuth +import zio.http._ + +object BasicAuth extends ZIOAppDefault { + + // Http app that requires a JWT claim + val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => + Response.text(s"Welcome to the ZIO party! ${name}") + } + + // Composing all the HttpApps together + val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") + + // Run it like any simple app + val run = Server.serve(app).provide(Server.default) +} + +``` \ No newline at end of file diff --git a/docs/how-to-guides/middleware-cors-handling.md b/docs/how-to-guides/middleware-cors-handling.md new file mode 100644 index 0000000000..d534d02fbb --- /dev/null +++ b/docs/how-to-guides/middleware-cors-handling.md @@ -0,0 +1,39 @@ +--- +id: middleware-cors-handling +title: "Middleware CORS Handling Example" +--- + +This code provides a practical example of setting up an HTTP server with Cross-Origin Resource Sharing (CORS) enabled. CORS is a mechanism that allows web browsers to safely access resources from different origins. + +```scala mdoc:silent +import zio._ + +import zio.http.Header.{AccessControlAllowMethods, AccessControlAllowOrigin, Origin} +import zio.http.HttpAppMiddleware.cors +import zio.http._ +import zio.http.internal.middlewares.Cors.CorsConfig + +object HelloWorldWithCORS extends ZIOAppDefault { + + // Create CORS configuration + val config: CorsConfig = + CorsConfig( + allowedOrigin = { + case origin@Origin.Value(_, host, _) if host == "dev" => Some(AccessControlAllowOrigin.Specific(origin)) + case _ => None + }, + allowedMethods = AccessControlAllowMethods(Method.PUT, Method.DELETE), + ) + + // Create HTTP route with CORS enabled + val app: HttpApp[Any, Nothing] = + Http.collect[Request] { + case Method.GET -> Root / "text" => Response.text("Hello World!") + case Method.GET -> Root / "json" => Response.json("""{"greetings": "Hello World!"}""") + } @@ cors(config) + + // Run it like any simple app + val run = + Server.serve(app).provide(Server.default) +} +``` \ No newline at end of file diff --git a/docs/how-to-guides/streaming-file.md b/docs/how-to-guides/streaming-file.md new file mode 100644 index 0000000000..a0db8c538f --- /dev/null +++ b/docs/how-to-guides/streaming-file.md @@ -0,0 +1,40 @@ +--- +id: streaming-file +title: "Streaming File Example" +--- + +This code showcases the utilization of ZIO HTTP to enable file streaming in an HTTP server. + +```scala mdoc +import java.io.File +import java.nio.file.Paths + +import zio._ + +import zio.stream.ZStream + +import zio.http._ + +object FileStreaming extends ZIOAppDefault { + + // Create HTTP route + val app = Http.collectHttp[Request] { + case Method.GET -> Root / "health" => Handler.ok.toHttp + + // Read the file as ZStream + // Uses the blocking version of ZStream.fromFile + case Method.GET -> Root / "blocking" => Handler.fromStream(ZStream.fromPath(Paths.get("README.md"))).toHttp + + // Uses netty's capability to write file content to the Channel + // Content-type response headers are automatically identified and added + // Adds content-length header and does not use Chunked transfer encoding + case Method.GET -> Root / "video" => Http.fromFile(new File("src/main/resources/TestVideoFile.mp4")) + case Method.GET -> Root / "text" => Http.fromFile(new File("src/main/resources/TestFile.txt")) + } + + // Run it like any simple app + val run = + Server.serve(app.withDefaultErrorResponse).provide(Server.default) +} + +``` \ No newline at end of file diff --git a/docs/how-to-guides/streaming-response.md b/docs/how-to-guides/streaming-response.md new file mode 100644 index 0000000000..7bc0c00b0e --- /dev/null +++ b/docs/how-to-guides/streaming-response.md @@ -0,0 +1,37 @@ +--- +id: streaming-response +title: "Streaming Response Example" +--- + +The code demonstrate a simple http server that can be use to stream data to a client and streaming responses. + +```scala mdoc +import zio.{http, _} +import zio.stream.ZStream +import zio.http._ + +/** + * Example to encode content using a ZStream + */ +object StreamingResponse extends ZIOAppDefault { + // Starting the server (for more advanced startup configuration checkout `HelloWorldAdvanced`) + def run = Server.serve(app).provide(Server.default) + + // Create a message as a Chunk[Byte] + def message = Chunk.fromArray("Hello world !\r\n".getBytes(Charsets.Http)) + // Use `Http.collect` to match on route + def app: HttpApp[Any, Nothing] = Http.collect[Request] { + + // Simple (non-stream) based route + case Method.GET -> Root / "health" => Response.ok + + // ZStream powered response + case Method.GET -> Root / "stream" => + http.Response( + status = Status.Ok, + headers = Headers(Header.ContentLength(message.length.toLong)), + body = Body.fromStream(ZStream.fromChunk(message)), // Encoding content using a ZStream + ) + } +} +``` \ No newline at end of file diff --git a/docs/how-to-guides/websocket.md b/docs/how-to-guides/websocket.md new file mode 100644 index 0000000000..030f50622f --- /dev/null +++ b/docs/how-to-guides/websocket.md @@ -0,0 +1,103 @@ +--- +id: websocket +title: Websocket +--- + + +This code sets up a WebSocket echo server and creates an HTTP server that handles WebSocket connections and echoes back received messages. + +
+ +```scala +import zio._ + +import zio.http.ChannelEvent.Read +import zio.http._ + +object WebSocketEcho extends ZIOAppDefault { + private val socketApp: SocketApp[Any] = + Handler.webSocket { channel => + channel.receiveAll { + case Read(WebSocketFrame.Text("FOO")) => + channel.send(Read(WebSocketFrame.text("BAR"))) + + case Read(WebSocketFrame.Text("BAR")) => + channel.send(Read(WebSocketFrame.text("FOO"))) + + case Read(WebSocketFrame.Text(text)) => + channel.send(Read(WebSocketFrame.text(text))).repeatN(10) + + case _ => + ZIO.unit + } + } + + private val app: Http[Any, Nothing, Request, Response] = + Http.collectZIO[Request] { + case Method.GET -> Root / "greet" / name => ZIO.succeed(Response.text(s"Greetings {$name}!")) + case Method.GET -> Root / "subscriptions" => socketApp.toResponse + } + + override val run = Server.serve(app).provide(Server.default) +} + +``` + +
+
+ +## Advanced WebSocket server + +```scala + +import zio._ + +import zio.http.ChannelEvent.{ExceptionCaught, Read, UserEvent, UserEventTriggered} +import zio.http._ + +object WebSocketAdvanced extends ZIOAppDefault { + + val socketApp: SocketApp[Any] = + Handler.webSocket { channel => + channel.receiveAll { + case Read(WebSocketFrame.Text("end")) => + channel.shutdown + + // Send a "bar" if the server sends a "foo" + case Read(WebSocketFrame.Text("foo")) => + channel.send(Read(WebSocketFrame.text("bar"))) + + // Send a "foo" if the server sends a "bar" + case Read(WebSocketFrame.Text("bar")) => + channel.send(Read(WebSocketFrame.text("foo"))) + + // Echo the same message 10 times if it's not "foo" or "bar" + case Read(WebSocketFrame.Text(text)) => + channel.send(Read(WebSocketFrame.text(text))).repeatN(10) + + // Send a "greeting" message to the server once the connection is established + case UserEventTriggered(UserEvent.HandshakeComplete) => + channel.send(Read(WebSocketFrame.text("Greetings!"))) + + // Log when the channel is getting closed + case Read(WebSocketFrame.Close(status, reason)) => + Console.printLine("Closing channel with status: " + status + " and reason: " + reason) + + // Print the exception if it's not a normal close + case ExceptionCaught(cause) => + Console.printLine(s"Channel error!: ${cause.getMessage}") + + case _ => + ZIO.unit + } + } + + val app: Http[Any, Nothing, Request, Response] = + Http.collectZIO[Request] { + case Method.GET -> Root / "greet" / name => ZIO.succeed(Response.text(s"Greetings ${name}!")) + case Method.GET -> Root / "subscriptions" => socketApp.toResponse + } + + override val run = Server.serve(app).provide(Server.default) +} +``` \ No newline at end of file From bf704ed9500beb4169882083a96b2cc2de4d26fb Mon Sep 17 00:00:00 2001 From: daveads Date: Tue, 13 Jun 2023 23:40:56 +0100 Subject: [PATCH 31/71] sidebar --- docs/sidebar.js | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/docs/sidebar.js b/docs/sidebar.js index 150354d6a5..09a1e5f5e5 100644 --- a/docs/sidebar.js +++ b/docs/sidebar.js @@ -10,6 +10,8 @@ const sidebars = { items: [ "setup", "quickstart", + "performance", + "faq", { type: "category", label: "Concepts", @@ -41,6 +43,16 @@ const sidebars = { collapsed: false, link: { type: "doc", id: "index" }, items: [ + "how-to-guides/advance-http-sever", + "how-to-guides/http-client", + "how-to-guides/http-sever", + "how-to-guides/https-server", + "how-to-guides/https-client", + "how-to-guides/middleware-basic-authentication", + "how-to-guides/middleware-cors-handling", + "how-to-guides/streaming-file", + "how-to-guides/streaming-response", + "how-to-guides/websocket", "how-to-guides/endpoint", "how-to-guides/middleware", ] @@ -58,23 +70,8 @@ const sidebars = { "reference/metrics", "reference/request-logging" ] - }, - { - type: "category", - label: "Performance", - collapsed: false, - link: { type: "doc", id: "index" }, - items: ["performance"] - }, - { - type: "category", - label: "FAQ", - collapsed: false, - link: { type: "doc", id: "index" }, - items: ["faq"] } - ] - + ] } ] }; \ No newline at end of file From f9d53fc6fe459cbd03571824c57d0f1181263783 Mon Sep 17 00:00:00 2001 From: daveads Date: Tue, 13 Jun 2023 23:41:41 +0100 Subject: [PATCH 32/71] id title --- docs/faq.md | 7 ++ docs/tutorials/deeper-dive-into-middleware.md | 69 +++++++++++++++++++ docs/tutorials/deploying-a-zio-http-app.md | 5 ++ docs/tutorials/your-first-zio-http-app.md | 5 ++ 4 files changed, 86 insertions(+) create mode 100644 docs/tutorials/deeper-dive-into-middleware.md diff --git a/docs/faq.md b/docs/faq.md index e69de29bb2..fbe14c7663 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -0,0 +1,7 @@ +--- +id: faq +title: "common zio-http asked questions" +--- + +Explore the most commonly asked questions about zio-http and find detailed answers in this informative resource. + diff --git a/docs/tutorials/deeper-dive-into-middleware.md b/docs/tutorials/deeper-dive-into-middleware.md new file mode 100644 index 0000000000..271414c178 --- /dev/null +++ b/docs/tutorials/deeper-dive-into-middleware.md @@ -0,0 +1,69 @@ +--- +id: deeper-dive-into-middleware +title: Middleware +--- + +# Middleware Tutorial + +Middleware is a powerful tool in managing the complexity of HTTP applications and addressing common concerns. It allows for clean and modular code organization, making it easier to maintain and evolve the application over time. + +## What is middleware? + +Middleware is a layer of software that sits between the application and the HTTP server. It intercepts all requests and responses, and can be used to perform a variety of tasks, such as: + +* Authentication +* Authorization +* Logging +* Error handling +* Rate limiting +* Caching + +## Why are middlewares needed? + +Middleware is needed to handle common cross-cutting concerns that are not specific to a particular route or endpoint. For example, authentication and authorization are concerns that need to be applied to all requests, regardless of the route being accessed. + +If we were to implement authentication and authorization directly in our application code, it would quickly become cluttered and difficult to maintain. Middleware allows us to keep our application code clean and focused, while the concerns are managed independently. + +## How do middlewares work? + +Middlewares are typically implemented as functions that take an `HttpRequest` and return an `HttpResponse`. The middleware function can then perform any number of tasks, such as: + +* Inspecting the request headers +* Parsing the request body +* Calling an external service +* Logging the request +* Throwing an error + +If the middleware function returns a non-`HttpResponse`, the request will be aborted and the middleware will not be called again. + +## How to use middlewares in ZIO-HTTP + +ZIO-HTTP provides a number of built-in middlewares, as well as a simple API for creating custom middlewares. To use a middleware, simply attach it to your `HttpApp` using the `@@` operator. + +For example, the following code attaches the `BasicAuthMiddleware` to the `HttpApp`: + +```scala +val app = HttpApp.collectZIO[Request] { + case Method.GET -> Root / "users" / id => + // Core business logic + dbService.lookupUsersById(id).map(Response.json(_.json)) + case Method.GET -> Root / "users" => + // Core business logic + dbService.paginatedUsers(pageNum).map(Response.json(_.json)) +} @@ BasicAuthMiddleware(credentials) +``` + +The `BasicAuthMiddleware` middleware will inspect the `Authorization` header of each request and verify that it contains a valid username and password. If the credentials are valid, the middleware will continue the request processing chain. If the credentials are invalid, the middleware will return a `Unauthorized` response. + + +## Code + + + +## Conclusion + +Middleware is a powerful tool that can be used to manage the complexity of HTTP applications and address common concerns. By using middleware, we can keep our application code clean and focused, while the concerns are managed independently. + +Using the ZIO-HTTP library, you can easily attach, combine, and create your own middlewares to address your application's specific needs. + +That concludes our tutorial on middleware. Happy coding! diff --git a/docs/tutorials/deploying-a-zio-http-app.md b/docs/tutorials/deploying-a-zio-http-app.md index 82af460991..bac4fc8159 100644 --- a/docs/tutorials/deploying-a-zio-http-app.md +++ b/docs/tutorials/deploying-a-zio-http-app.md @@ -1,3 +1,8 @@ +--- +id: deploying-zio-http-application +title: Deploying Zio-http Application +--- + # Deploying Zio-http Application ## Tutorial: How to Deploy a ZIO Application Using Docker? diff --git a/docs/tutorials/your-first-zio-http-app.md b/docs/tutorials/your-first-zio-http-app.md index 42979119d7..b962608bf9 100644 --- a/docs/tutorials/your-first-zio-http-app.md +++ b/docs/tutorials/your-first-zio-http-app.md @@ -1,3 +1,8 @@ +--- +id: your-first-zio-http-app +title: Your first zio http app +--- + # ZIO Quickstart: Hello World The ZIO Quickstart Hello World is a simple example that demonstrates the basics of writing a ZIO application. It showcases how to interact with the console, read input from the user, and perform simple operations using ZIO's effect system. From c44140f505e28d4775c1116c450d9540ddaded3e Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 14 Jun 2023 09:49:55 +0100 Subject: [PATCH 33/71] concrete-entity --- docs/how-to-guides/concrete-entity.md | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 docs/how-to-guides/concrete-entity.md diff --git a/docs/how-to-guides/concrete-entity.md b/docs/how-to-guides/concrete-entity.md new file mode 100644 index 0000000000..6d1c41e1b1 --- /dev/null +++ b/docs/how-to-guides/concrete-entity.md @@ -0,0 +1,40 @@ +--- +id: concrete-entity +title: "Concrete Entity Example" +--- + +`Concrete entities` refer to specific data models or classes that represent the request and response payloads in an HTTP application. This code is an example demonstrating how to build an application using concrete entities in ZIO-HTTP. + + +## Code + +```scala +import zio._ + +import zio.http._ + +/** + * Example to build app on concrete entity + */ +object ConcreteEntity extends ZIOAppDefault { + // Request + case class CreateUser(name: String) + + // Response + case class UserCreated(id: Long) + + val user: Handler[Any, Nothing, CreateUser, UserCreated] = + Handler.fromFunction[CreateUser] { case CreateUser(_) => + UserCreated(2) + } + + val app: RequestHandler[Any, Nothing] = + user + .contramap[Request](req => CreateUser(req.path.encode)) // Http[Any, Nothing, Request, UserCreated] + .map(userCreated => Response.text(userCreated.id.toString)) // Http[Any, Nothing, Request, Response] + + // Run it like any simple app + val run = + Server.serve(app.toHttp.withDefaultErrorResponse).provide(Server.default) +} +``` \ No newline at end of file From dd57f1e93d9e3d873d27c87ec444d0751b4ecec2 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 14 Jun 2023 09:50:40 +0100 Subject: [PATCH 34/71] example from original doc --- docs/concepts/middleware.md | 271 ++++++++++++++++++++++++++++++++++-- 1 file changed, 257 insertions(+), 14 deletions(-) diff --git a/docs/concepts/middleware.md b/docs/concepts/middleware.md index 98c3b7854b..072990da85 100644 --- a/docs/concepts/middleware.md +++ b/docs/concepts/middleware.md @@ -3,33 +3,276 @@ id: middleware title: Middleware --- -## Middleware -Middleware in ZIO-HTTP is a powerful mechanism that allows you to intercept and modify HTTP requests and responses. It provides a way to add additional functionality to an HTTP application in a modular and reusable manner. +Before introducing middleware, let us understand why they are needed. -Middleware functions are applied to an HTTP application to transform its behavior. Each middleware function takes an existing HTTP application as input and returns a new HTTP application with modified behavior. The composition of multiple middleware functions creates a pipeline through which requests and responses flow, allowing each middleware to perform specific actions. +Consider the following example where we have two endpoints within HttpApp +* GET a single user by id +* GET all users -Some common use cases for middleware include: +```scala +private val app = Http.collectZIO[Request] { + case Method.GET -> Root / "users" / id => + // core business logic + dbService.lookupUsersById(id).map(Response.json(_.json)) + case Method.GET -> Root / "users" => + // core business logic + dbService.paginatedUsers(pageNum).map(Response.json(_.json)) +} +``` + +#### The polluted code violates the principle of "Separation of concerns" + +As our application grows, we want to code the following aspects like +* Basic Auth +* Request logging +* Response logging +* Timeout and retry + +For both of our example endpoints, our core business logic gets buried under boilerplate like this + +```scala +(for { + // validate user + _ <- MyAuthService.doAuth(request) + // log request + _ <- logRequest(request) + // core business logic + user <- dbService.lookupUsersById(id).map(Response.json(_.json)) + resp <- Response.json(user.toJson) + // log response + _ <- logResponse(resp) +} yield resp) + .timeout(2.seconds) + .retryN(5) +``` +Imagine repeating this for all our endpoints!!! + +So there are two problems with this approach +* We are dangerously coupling our business logic with cross-cutting concerns (like applying timeouts) +* Also, addressing these concerns will require updating code for every single route in the system. For 100 routes we will need to repeat 100 timeouts!!! +* For example, any change related to a concern like the logging mechanism from logback to log4j2 may cause changing signature of `log(..)` function in 100 places. +* On the other hand, this also makes testing core business logic more cumbersome. + + +This can lead to a lot of boilerplate clogging our neatly written endpoints affecting readability, thereby leading to increased maintenance costs. + +## Need for middlewares and handling "aspects" -- Logging: Middleware can log request and response details, such as headers, paths, and payloads, for debugging or auditing purposes. +If we refer to Wikipedia for the definition of an "[Aspect](https://en.wikipedia.org/wiki/Aspect_(computer_programming))" we can glean the following points. -- Authentication and Authorization: Middleware can enforce authentication and authorization rules by inspecting request headers or tokens and validating user permissions. +* An aspect of a program is a feature linked to many other parts of the program (**_most common example, logging_**)., +* But it is not related to the program's primary function (**_core business logic_**) +* An aspect crosscuts the program's core concerns (**_for example logging code intertwined with core business logic_**), +* Therefore, it can violate the principle of "separation of concerns" which tries to encapsulate unrelated functions. (**_Code duplication and maintenance nightmare_**) -- Error handling: Middleware can catch and handle errors that occur during request processing, allowing for centralized error handling and consistent error responses. +Or in short, aspect is a common concern required throughout the application, and its implementation could lead to repeated boilerplate code and in violation of the principle of separation of concerns. -- Request preprocessing: Middleware can modify or enrich incoming requests before they are processed by the application. For example, parsing request parameters or validating input data. +There is a paradigm in the programming world called [aspect-oriented programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming) that aims for modular handling of these common concerns in an application. + +Some examples of common "aspects" required throughout the application +- logging, +- timeouts (preventing long-running code) +- retries (or handling flakiness for example while accessing third party APIs) +- authenticating a user before using the REST resource (basic, or custom ones like OAuth / single sign-on, etc). + +This is where middleware comes to the rescue. +Using middlewares we can compose out-of-the-box middlewares (or our custom middlewares) to address the above-mentioned concerns using ++ and @@ operators as shown below. + +#### Cleaned up code using middleware to address cross-cutting concerns like auth, request/response logging, etc. +Observe, how we can address multiple cross-cutting concerns using neatly composed middlewares, in a single place. + +```scala mdoc:silent +import zio._ +import zio.http._ + +// compose basic auth, request/response logging, timeouts middlewares +val composedMiddlewares = RequestHandlerMiddlewares.basicAuth("user","pw") ++ + RequestHandlerMiddlewares.debug ++ + RequestHandlerMiddlewares.timeout(5.seconds) +``` + +And then we can attach our composed bundle of middlewares to an Http using `@@` + +```scala +val app = Http.collectZIO[Request] { + case Method.GET -> Root / "users" / id => + // core business logic + dbService.lookupUsersById(id).map(Response.json(_.json)) + case Method.GET -> Root / "users" => + // core business logic + dbService.paginatedUsers(pageNum).map(Response.json(_.json)) +} @@ composedMiddlewares // attach composedMiddlewares to the app using @@ +``` -- Response post-processing: Middleware can transform or enhance outgoing responses before they are sent back to the client. This includes adding headers, compressing data, or transforming response formats. +Observe how we gained the following benefits by using middlewares +* **Readability**: de-cluttering business logic. +* **Modularity**: we can manage aspects independently without making changes in 100 places. For example, + * replacing the logging mechanism from logback to log4j2 will require a change in one place, the logging middleware. + * replacing the authentication mechanism from OAuth to single sign-on will require changing the auth middleware +* **Testability**: we can test our aspects independently. -- Caching: Middleware can implement caching mechanisms to improve performance by storing and serving cached responses for certain requests. +## Middleware in zio-http -- Rate limiting: Middleware can restrict the number of requests allowed from a client within a specific time frame to prevent abuse or ensure fair usage. +A middleware helps in addressing common crosscutting concerns without duplicating boilerplate code. + +#### Attaching middleware to Http + +`@@` operator is used to attach a middleware to an Http. Example below shows a middleware attached to an HttpApp + +```scala mdoc:silent +val app = Http.collect[Request] { + case Method.GET -> Root / name => Response.text(s"Hello $name") +} +val appWithMiddleware = app @@ RequestHandlerMiddlewares.debug +``` + +Logically the code above translates to `Middleware.debug(app)` + +#### A simple middleware example + +Let us consider a simple example using out-of-the-box middleware called ```addHeader``` +We will write a middleware that will attach a custom header to the response. + +We create a middleware that appends an additional header to the response indicating whether it is a Dev/Prod/Staging environment. + +```scala mdoc:silent:reset +import zio._ +import zio.http._ + +lazy val patchEnv = RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") +``` + +A test `App` with attached middleware: + +```scala mdoc:silent +val app = Http.collect[Request] { + case Method.GET -> Root / name => Response.text(s"Hello $name") +} +val appWithMiddleware = app @@ patchEnv +``` + +Start the server: + +```scala mdoc:silent +Server.serve(appWithMiddleware).provide(Server.default) +``` + +Fire a curl request, and we see an additional header added to the response indicating the "Dev" environment: + +``` +curl -i http://localhost:8080/Bob + +HTTP/1.1 200 OK +content-type: text/plain +X-Environment: Dev +content-length: 12 + +Hello Bob +``` + +## Combining middlewares + +Middlewares can be combined using several special operators like `++`, `<>` and `>>>` + +### Using `++` combinator + +`>>>` and `++` are aliases for `andThen`. It combines two middlewares. + +For example, if we have three middlewares f1, f2, f3 + +f1 ++ f2 ++ f3 applies on an `http`, from left to right with f1 first followed by others, like this +```scala + f3(f2(f1(http))) +``` +#### A simple example using `++` combinator + +Start with imports: + +```scala mdoc:silent:reset +import zio.http._ +import zio.http.RequestHandlerMiddlewares.basicAuth +import zio._ +``` + +A user app with single endpoint that welcomes a user: + +```scala mdoc:silent +val userApp = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => + Response.text(s"Welcome to the ZIO party! ${name}") +} +``` + +A basicAuth middleware with hardcoded user pw and another patches response with environment value: + +```scala mdoc:silent +val basicAuthMW = basicAuth("admin", "admin") +val patchEnv = RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") +// apply combined middlewares to the userApp +val appWithMiddleware = userApp @@ (basicAuthMW ++ patchEnv) +``` + +Start the server: + +```scala mdoc:silent +Server.serve(appWithMiddleware).provide(Server.default) +``` + +Fire a curl request with an incorrect user/password combination: + +``` +curl -i --user admin:wrong http://localhost:8080/user/admin/greet + +HTTP/1.1 401 Unauthorized +www-authenticate: Basic +X-Environment: Dev +content-length: 0 +``` + +We notice in the response that first basicAuth middleware responded `HTTP/1.1 401 Unauthorized` and then patch middleware attached a `X-Environment: Dev` header. + +## Conditional application of middlewares + +- `when` applies middleware only if the condition function evaluates to true +-`whenZIO` applies middleware only if the condition function(with effect) evaluates + + +## A complete example of a middleware + +
+Detailed example showing "debug" and "addHeader" middlewares + +```scala mdoc:silent:reset +import zio.http._ +import zio._ + +import java.io.IOException +import java.util.concurrent.TimeUnit + +object Example extends ZIOAppDefault { + val app: App[Any] = + Http.collectZIO[Request] { + // this will return result instantly + case Method.GET -> Root / "text" => ZIO.succeed(Response.text("Hello World!")) + // this will return result after 5 seconds, so with 3 seconds timeout it will fail + case Method.GET -> Root / "long-running" => ZIO.succeed(Response.text("Hello World!")).delay(5.seconds) + } + + val middlewares = + RequestHandlerMiddlewares.debug ++ // print debug info about request and response + RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") // add static header + + override def run = + Server.serve(app @@ middlewares).provide(Server.default) +} +``` -By composing multiple middleware functions together, you can build complex request processing pipelines tailored to your application's specific needs. Middleware promotes separation of concerns and code reusability by encapsulating different aspects of request handling in a modular way. +
-ZIO-HTTP provides a rich set of built-in middleware functions, and you can also create custom middleware by implementing the `HttpApp` or `HttpFilter` interfaces. This flexibility allows you to customize and extend the behavior of your HTTP applications in a declarative and composable manner. +
+
-here is a simple example of middleware: +Here is another example of a simple middleware and code break down : ```scala package example From 5bebcd18d5abd000cb54044050447badf2107ed5 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 14 Jun 2023 09:51:23 +0100 Subject: [PATCH 35/71] faq --- docs/faq.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ docs/sidebar.js | 3 +- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index fbe14c7663..0e441b0d4e 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -5,3 +5,78 @@ title: "common zio-http asked questions" Explore the most commonly asked questions about zio-http and find detailed answers in this informative resource. + +**Q. What is ZIO-HTTP ?** + +ZIO Http is a functional Scala library utilized for constructing high-performance HTTP services and clients. It leverages the power of ZIO's concurrency library and the Netty network library. With ZIO-HTTP, you can create efficient and expressive HTTP applications through its comprehensive high-level API. + +
+ + +**Q. Is zio-http a library or a framework?** + +ZIO-HTTP is primarily a library rather than a framework. It provides a set of tools, components, and abstractions that you can utilize to build HTTP-based services and clients in a functional programming style using Scala and the ZIO concurrency library. It offers a high-level API for constructing HTTP applications, but it does not impose a rigid framework structure or dictate the overall architecture of your application. Instead, it focuses on providing the necessary building blocks and utilities for working with HTTP protocols in a composable and expressive manner. + +
+ +**Q. Does ZIO-HTTP provide support for an asynchronous programming model?** + +Yes, ZIO-HTTP supports an asynchronous model for handling HTTP requests and responses. It is built on top of the ZIO concurrency library, which provides powerful asynchronous and concurrent programming capabilities. + +ZIO's concurrency model is designed to handle high scalability and performance requirements. It utilizes lightweight fibers for efficient concurrency management, allowing you to handle a large number of concurrent requests without incurring significant overhead. By leveraging the asynchronous nature of ZIO, you can write non-blocking and highly performant code, which is essential for building webscale applications. + +With ZIO-HTTP, you can take advantage of these asynchronous features and design your applications to handle high loads, making it well-suited for webscale scenarios. Checkout the [benachmark results](https://web-frameworks-benchmark.netlify.app/compare?f=zio-http) To assess how ZIO-HTTP compares to other JVM-based web libraries in relation to their synchronous and asynchronous capabilities. + + +
+ +**Q. Does ZIO-HTTP support middleware for request/response modification?** + +Yes, ZIO-HTTP does support middleware for request/response modification. Middleware in ZIO-HTTP allows you to intercept and modify requests and responses as they flow through the application's request/response pipeline. + +You can define custom middleware functions that can perform operations such as request/response transformation, authentication, logging, error handling, and more. Middleware functions can be composed and applied to specific routes or globally to the entire application. + + +
+Example + +```scala mdoc:silent:reset +package example + +import java.util.concurrent.TimeUnit + +import zio._ + +import zio.http._ + +object HelloWorldWithMiddlewares extends ZIOAppDefault { + + val app: HttpApp[Any, Nothing] = Http.collectZIO[Request] { + // this will return result instantly + case Method.GET -> Root / "text" => ZIO.succeed(Response.text("Hello World!")) + // this will return result after 5 seconds, so with 3 seconds timeout it will fail + case Method.GET -> Root / "long-running" => ZIO.succeed(Response.text("Hello World!")).delay(5 seconds) + } + + val serverTime: RequestHandlerMiddleware[Nothing, Any, Nothing, Any] = HttpAppMiddleware.patchZIO(_ => + for { + currentMilliseconds <- Clock.currentTime(TimeUnit.MILLISECONDS) + withHeader = Response.Patch.addHeader("X-Time", currentMilliseconds.toString) + } yield withHeader, + ) + val middlewares = + // print debug info about request and response + HttpAppMiddleware.debug ++ + // close connection if request takes more than 3 seconds + HttpAppMiddleware.timeout(3 seconds) ++ + // add static header + HttpAppMiddleware.addHeader("X-Environment", "Dev") ++ + // add dynamic header + serverTime + + // Run it like any simple app + val run = Server.serve((app @@ middlewares).withDefaultErrorResponse).provide(Server.default) +} +``` +
+ diff --git a/docs/sidebar.js b/docs/sidebar.js index 09a1e5f5e5..ceeb04844d 100644 --- a/docs/sidebar.js +++ b/docs/sidebar.js @@ -34,7 +34,7 @@ const sidebars = { items: [ "tutorials/your-first-zio-http-app", "tutorials/deploying-a-zio-http-app", - "tutorials/testing-your-zio-http-app" + "tutorials/testing-your-zio-http-app", ] }, { @@ -55,6 +55,7 @@ const sidebars = { "how-to-guides/websocket", "how-to-guides/endpoint", "how-to-guides/middleware", + "how-to-guides/concrete-entity", ] }, { From 1a3d477b4ed9f4a2320dc27d8ae15721bce52af5 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 14 Jun 2023 20:28:47 +0100 Subject: [PATCH 36/71] dsl --- docs/Reference/dsl/body.md | 77 +++++ docs/Reference/dsl/cookies.md | 139 ++++++++ docs/Reference/dsl/headers.md | 233 +++++++++++++ docs/Reference/dsl/html.md | 78 +++++ docs/Reference/dsl/http.md | 364 ++++++++++++++++++++ docs/Reference/dsl/middleware.md | 275 +++++++++++++++ docs/Reference/dsl/request.md | 70 ++++ docs/Reference/dsl/response.md | 83 +++++ docs/Reference/dsl/server.md | 63 ++++ docs/Reference/dsl/socket/socket.md | 56 +++ docs/Reference/dsl/socket/websocketframe.md | 106 ++++++ 11 files changed, 1544 insertions(+) create mode 100644 docs/Reference/dsl/body.md create mode 100644 docs/Reference/dsl/cookies.md create mode 100644 docs/Reference/dsl/headers.md create mode 100644 docs/Reference/dsl/html.md create mode 100644 docs/Reference/dsl/http.md create mode 100644 docs/Reference/dsl/middleware.md create mode 100644 docs/Reference/dsl/request.md create mode 100644 docs/Reference/dsl/response.md create mode 100644 docs/Reference/dsl/server.md create mode 100644 docs/Reference/dsl/socket/socket.md create mode 100644 docs/Reference/dsl/socket/websocketframe.md diff --git a/docs/Reference/dsl/body.md b/docs/Reference/dsl/body.md new file mode 100644 index 0000000000..dedd45e103 --- /dev/null +++ b/docs/Reference/dsl/body.md @@ -0,0 +1,77 @@ +--- +id: body +title: Body +--- + +`Body` is a domain to model content for `Request`, `Response` and `ClientRequest`. ZIO HTTP uses Netty at it's core and Netty handles content as `ByteBuf`. `Body` helps you decode and encode this content into simpler, easier to use data types while creating a Request or Response. + +## Server-side usage of `Body` + +On the server-side, `ZIO-HTTP` models content in `Request` and `Response` as `Body` with `Body.Empty` as the default value. To add content while creating a `Response` you can use the `Response` constructor: + +```scala mdoc:silent + import zio._ + import zio.http._ + import zio.stream._ + + val res: Response = Response( body = Body.fromString("Some String")) +``` + +To add content while creating a `Request` for unit tests, you can use the `Request` constructor: + +```scala mdoc:silent + val req: Request = Request.post(Body.fromString("Some String"), URL(Root / "save")) +``` + +## Client-side usage of `Body` + +On the client-side, `ZIO-HTTP` models content in `ClientRequest` as `Body` with `Body.Empty` as the default value. + +To add content while making a request using ZIO HTTP you can use the `Client.request` method: + +```scala mdoc:silent + val actual: ZIO[Client, Throwable, Response] = + Client.request("https://localhost:8073/success", content = Body.fromString("Some string")) +``` + +## Creating a Body + +### Creating a Body from a `String` + +To create an `Body` that encodes a String you can use `Body.fromString`: + +```scala mdoc:silent + val textHttpData: Body = Body.fromString("any string", Charsets.Http) +``` + +### Creating a Body from `Chunk of Bytes` + +To create an `Body` that encodes chunk of bytes you can use `Body.fromChunk`: + +```scala mdoc:silent + val chunkHttpData: Body = Body.fromChunk(Chunk.fromArray("Some Sting".getBytes(Charsets.Http))) +``` + +### Creating a Body from a `Stream` + +To create an `Body` that encodes a Stream you can use `Body.fromStream`. + +- Using a Stream of Bytes + +```scala mdoc:silent + val streamHttpData1: Body = Body.fromStream(ZStream.fromChunk(Chunk.fromArray("Some String".getBytes(Charsets.Http)))) +``` + +- Using a Stream of String + +```scala mdoc:silent + val streamHttpData2: Body = Body.fromStream(ZStream("a", "b", "c"), Charsets.Http) +``` + +### Creating a Body from a `File` + +To create an `Body` that encodes a File you can use `Body.fromFile`: + +```scala mdoc:silent:crash + val fileHttpData: Body = Body.fromFile(new java.io.File(getClass.getResource("/fileName.txt").getPath)) +``` diff --git a/docs/Reference/dsl/cookies.md b/docs/Reference/dsl/cookies.md new file mode 100644 index 0000000000..1f09a91896 --- /dev/null +++ b/docs/Reference/dsl/cookies.md @@ -0,0 +1,139 @@ +--- +id: cookies +title: Cookies +--- + +**ZIO HTTP** has special support for Cookie headers using the `Cookie` Domain to add and invalidate cookies. Adding a +cookie will generate the correct [Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) +headers + +## Create a Request Cookie + +A request cookie consists of `name` and `content` and can be created with `Cookie.Request`: + +```scala mdoc +import zio._ +import zio.http._ + +val cookie: Cookie = Cookie.Request("id", "abc") +``` + +### Updating a request cookie + +- `withContent` updates the content of cookie + +```scala mdoc +cookie.withContent("def") +``` + +- `withName` updates the name of cookie + +```scala mdoc +cookie.withName("id2") +``` + +## Create a Response Cookie + +A Response `Cookie` can be created with +params `name`, `content`, `expires`, `domain`, `path`, `isSecure`, `isHttpOnly`, `maxAge`, `sameSite` and `secret` +according to HTTP [Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) + +The below snippet creates a response cookie from the above request cookie: + +```scala mdoc +val responseCookie = cookie.toResponse +``` + +### Update a Response Cookie + +`Cookie.Response` is a case class so it can be updated by its `copy` method: + +- `maxAge` updates the max-age of the cookie + +```scala mdoc +responseCookie.copy(maxAge = Some(5.days)) +``` + +- `domain` updates the host to which the cookie will be sent + +```scala mdoc +responseCookie.copy(domain = Some("example.com")) +``` + +- `path` updates the path of the cookie + +```scala mdoc +responseCookie.copy(path = Some(Root / "cookie")) +``` + +- `isSecure` enables cookie only on https server + +```scala mdoc +responseCookie.copy(isSecure = true) +``` + +- `isHttpOnly` forbids JavaScript from accessing the cookie + +```scala mdoc +responseCookie.copy(isHttpOnly = true) +``` + +- `sameSite` updates whether or not a cookie is sent with cross-origin requests + +```scala mdoc +responseCookie.copy(sameSite = Some(Cookie.SameSite.Strict)) +``` + +## Sign a Cookie + +The cookies can be signed with a signature: + +- Using `sign` + To sign a cookie, you can use `sign` + +```scala mdoc +val cookie2 = Cookie.Response("key", "hello", maxAge = Some(5.days)) +val app = Http.collect[Request] { case Method.GET -> Root / "cookie" => + Response.ok.addCookie(cookie2.sign("secret")) +} +``` + +- Using `signCookies` middleware + +To sign all the cookies in your `HttpApp`, you can use `signCookies` middleware: + +```scala mdoc +import RequestHandlerMiddlewares.signCookies + +private val app2 = Http.collect[Request] { + case Method.GET -> Root / "cookie" => Response.ok.addCookie(cookie2) + case Method.GET -> Root / "secure-cookie" => Response.ok.addCookie(cookie2.copy(isSecure = true)) +} + +// Run it like any simple app +def run(args: List[String]): ZIO[Any, Throwable, Nothing] = + Server.serve(app2 @@ signCookies("secret")) + .provide(Server.default) +``` + +## Adding Cookie in Response + +The cookies can be added in `Response` headers: + +```scala mdoc +val res = Response.ok.addCookie(responseCookie) +``` + +It updates the response header `Set-Cookie` as ```Set-Cookie: =``` + +## Getting Cookie from Request + +In HTTP requests, cookies are stored in the `cookie` header. `cookiesDecoded` can be used to get all the cookies in the +request: + +```scala mdoc + private val app3 = Http.collect[Request] { + case req@Method.GET -> Root / "cookie" => + Response.text(req.header(Header.Cookie).map(_.value.toChunk).getOrElse(Chunk.empty).mkString("")) +} +``` diff --git a/docs/Reference/dsl/headers.md b/docs/Reference/dsl/headers.md new file mode 100644 index 0000000000..caa8d51bfb --- /dev/null +++ b/docs/Reference/dsl/headers.md @@ -0,0 +1,233 @@ +--- +id: headers +title: Headers +--- + +**ZIO HTTP** provides support for all HTTP headers (as defined +in [RFC2616](https://datatracker.ietf.org/doc/html/rfc2616) ) along with custom headers. + +## Server-side + +### Attaching Headers to `Response` + +On the server-side, `ZIO-HTTP` is adding a collection of pre-defined headers to the response, according to the HTTP +specification, additionally, users may add other headers, including custom headers. + +There are multiple ways to attach headers to a response: + +Using `addHeaders` helper on response: +- + +```scala mdoc +import zio._ +import zio.http._ + +Response.ok.addHeader(Header.ContentLength(0L)) +``` + +Through `Response` constructors: + +```scala mdoc +Response( + status = Status.Ok, + // Setting response header + headers = Headers(Header.ContentLength(0L)), + body = Body.empty +) +``` + +Using `Middlewares`: + +```scala mdoc +import RequestHandlerMiddlewares.addHeader + +Handler.ok @@ addHeader(Header.ContentLength(0L)) +``` + +### Reading Headers from `Request` + +On the Server-side you can read Request headers as given below + +```scala mdoc +Http.collect[Request] { + case req@Method.GET -> Root / "streamOrNot" => Response.text(req.headers.map(_.toString).mkString("\n")) +} +``` + +
+Detailed examples + +Example below shows how the Headers could be added to a response by using `Response` constructors and how a custom +header is added to `Response` through `addHeader`: + +```scala mdoc:silent +import zio._ +import zio.http._ +import zio.stream._ + +object SimpleResponseDispatcher extends ZIOAppDefault { + override def run = + // Starting the server (for more advanced startup configuration checkout `HelloWorldAdvanced`) + Server.serve(app).provide(Server.default) + + // Create a message as a Chunk[Byte] + val message = Chunk.fromArray("Hello world !\r\n".getBytes(Charsets.Http)) + // Use `Http.collect` to match on route + val app: App[Any] = + Http.collect[Request] { + // Simple (non-stream) based route + case Method.GET -> Root / "health" => Response.ok + + // From Request(req), the headers are accessible. + case req@Method.GET -> Root / "streamOrNot" => + // Checking if client is able to handle streaming response + val acceptsStreaming: Boolean = req.header(Header.Accept).exists(_.mimeTypes.contains(Header.Accept.MediaTypeWithQFactor(MediaType.application.`octet-stream`, None))) + if (acceptsStreaming) + Response( + status = Status.Ok, + // Setting response header + headers = Headers(Header.ContentLength(message.length.toLong)), // adding CONTENT-LENGTH header + body = Body.fromStream(ZStream.fromChunk(message)), // Encoding content using a ZStream + ) + else { + // Adding a custom header to Response + Response(status = Status.Accepted, body = Body.fromChunk(message)).addHeader("X-MY-HEADER", "test") + } + } +} + +``` + +The following example shows how Headers could be added to `Response` in a `RequestHandlerMiddleware` implementation: + +```scala mdoc:silent + +/** + * Creates an authentication middleware that only allows authenticated requests to be passed on to the app. + */ +final def customAuth( + verify: Headers => Boolean, + responseHeaders: Headers = Headers.empty, + responseStatus: Status = Status.Unauthorized, + ): RequestHandlerMiddleware[Nothing, Any, Nothing, Any] = + new RequestHandlerMiddleware.Simple[Any, Nothing] { + override def apply[R1 <: Any, Err1 >: Nothing]( + handler: Handler[R1, Err1, Request, Response], + )(implicit trace: Trace): Handler[R1, Err1, Request, Response] = + Handler.fromFunctionHandler[Request] { request => + if (verify(request.headers)) handler + else Handler.status(responseStatus).addHeaders(responseHeaders) + } + } + +``` + +More examples: + +- [BasicAuth](https://github.com/zio/zio-http/blob/main/example/src/main/scala/BasicAuth.scala) +- [Authentication](https://github.com/zio/zio-http/blob/main/example/src/main/scala/Authentication.scala) + +
+ +## Client-side + +### Adding headers to `Request` + +ZIO-HTTP provides a simple way to add headers to a client `Request`. + +```scala mdoc:silent +val headers = Headers(Header.Host("sports.api.decathlon.com"), Header.Accept(MediaType.application.json)) +Client.request("http://sports.api.decathlon.com/test", headers = headers) +``` + +### Reading headers from `Response` + +```scala mdoc:silent +Client.request("http://sports.api.decathlon.com/test").map(_.headers) +``` + +
+Detailed examples + +- The sample below shows how a header could be added to a client request: + +```scala mdoc:silent +import zio._ +import zio.http._ + +object SimpleClientJson extends ZIOAppDefault { + val url = "http://sports.api.decathlon.com/groups/water-aerobics" + // Construct headers + val headers = Headers(Header.Host("sports.api.decathlon.com"), Header.Accept(MediaType.application.json)) + + val program = for { + // Pass headers to request + res <- Client.request(url, headers = headers) + // List all response headers + _ <- Console.printLine(res.headers.toList.mkString("\n")) + data <- + // Check if response contains a specified header with a specified value. + if (res.header(Header.ContentType).exists(_.mediaType == MediaType.application.json)) + res.body.asString + else + res.body.asString + _ <- Console.printLine(data) + } yield () + + override def run = + program.provide(Client.default) + +} +``` + +
+ +## Headers DSL + +Headers DSL provides plenty of powerful operators that can be used to add, remove, modify and verify headers. Headers +APIs could be used on client, server, and middleware. + +`zio.http.Headers` - represents an immutable collection of headers +`zio.http.Header` - a collection of all the standard headers + +`Headers` have following type of helpers + +Constructors - Provides a list of helpful methods that can create `Headers`. + +```scala mdoc +// create a simple Accept header: +Headers(Header.Accept(MediaType.application.json)) + +// create a basic authentication header: +Headers(Header.Authorization.Basic("username", "password")) +``` + +Getters - Provides a list of operators that parse and extract data from the `Headers`. + +```scala mdoc + +// retrieving the value of Accept header value: +val acceptHeader: Headers = Headers(Header.Accept(MediaType.application.json)) +val acceptHeaderValue: Option[CharSequence] = acceptHeader.header(Header.Accept).map(_.renderedValue) + + +// retrieving a bearer token from Authorization header: +val authorizationHeader: Headers = Headers(Header.Authorization.Bearer("test")) +val authorizationHeaderValue: Option[String] = acceptHeader.header(Header.Authorization).map(_.renderedValue) +``` + +Modifiers - Provides a list of operators that modify the current `Headers`. Once modified, a new instance of the same +type is returned. + +```scala mdoc +// add Accept header: +Headers.empty.addHeader(Header.Accept(MediaType.application.json)) +``` + +Checks - Provides a list of operators that checks if the `Headers` meet the give constraints. + +```scala mdoc +val contentTypeHeader: Headers = Headers(Header.ContentType(MediaType.application.json)) +val isHeaderPresent: Boolean = contentTypeHeader.hasHeader(Header.ContentType) +val isJsonContentType: Boolean = contentTypeHeader.hasJsonContentType +``` diff --git a/docs/Reference/dsl/html.md b/docs/Reference/dsl/html.md new file mode 100644 index 0000000000..cca03e5173 --- /dev/null +++ b/docs/Reference/dsl/html.md @@ -0,0 +1,78 @@ +--- +id: html +title: Html +--- + +The package `zio.http.html._` contains lightweight helpers for generating statically typed, safe html similiar in spirit to `scalatags`. + +## Html and DOM + +### Html from string + +One possible way is to create an instance of `Html` directly from a `String` value, with the obvious drawback of not having checks +from the compiler: + +```scala mdoc:silent +import zio.http.html._ + +val divHtml1: Html = Html.fromString("""""") +``` + +### Html from constructors + +In order to improve on type safety one could use `Html` with `Dom` constructor functions, with the drawback that the resulting +code is much more verbose: + +```scala mdoc:silent +import zio.http.html._ + +val divHtml2: Html = Html.fromDomElement( + Dom.element( + "div", + Dom.attr("class", "container1 container2"), + Dom.element( + "a", + Dom.attr("href", "http://zio.dev"), + Dom.text("ZIO Homepage") + ) + ) +) +``` + +Please note that both values `divHtml1` and `divHtml2` produce identical html output. + +### Html from Tag API + +Practically one would very likely not use one of the above mentioned versions but instead use the `Tag API`. That API lets one use not only html +elements like `div` or `a` but also html attributes like `hrefAttr` or `styleAttr` as scala functions. By convention values of html attributes +are suffixed `attr` to easily distinguish those from html elements: + +```scala mdoc:silent +import zio.http.html._ + +val divHtml3: Html = div( + classAttr := "container1" :: "container2" :: Nil, + a(hrefAttr := "http://zio.dev", "ZIO Homepage") +) +``` + +Also `divHtml3` produces identical html output as `divHtml1` and `divHtml2`. + +Html elements like `div` or `a` are represented as values of `PartialElement` which have an `apply` method for nesting html elements, +html attributes and text values. Html attributes are represented as values of `PartialAttribute` which provides an operator `:=` for "assigning" +attribute values. Besides `:=` attributes also have an `apply` method that provides an alternative syntax e.g. instead +of `a(hrefAttr := "http://zio.dev", "ZIO Homepage")` one can use `a(hrefAttr("http://zio.dev"), "ZIO Homepage")`. + +### Html composition + +One can compose values of `Html` sequentially using the operator `++` to produce a larger `Html`: + +```scala mdoc:silent +import zio.http.html._ + +val fullHtml: Html = divHtml1 ++ divHtml2 ++ divHtml3 +``` + +## Html response + +One can create a successful http response in routing code from a given value of `Html` with `Response.html`. diff --git a/docs/Reference/dsl/http.md b/docs/Reference/dsl/http.md new file mode 100644 index 0000000000..b218944b1b --- /dev/null +++ b/docs/Reference/dsl/http.md @@ -0,0 +1,364 @@ +--- +id: http +title: Http +--- + +`Http` is a functional domain that models HTTP applications. It’s polymorphic on input and output type. + +A `Http[-R, +E, -A, +B]` models a function from `A` to `ZIO[R, Option[E], Handler[R, E, A, B]]`. +When a value of type `A` is evaluated against an `Http[R,E,A,B]`, it can either succeed with a `Handler`, fail with +a `Some[E]` or if `A` is not defined in the application, fail with `None`. + +`Handler[-R, +E, -A, +B]` models a function that takes an `A` and returns a `B`, possibly failing with an `E` and using +a ZIO effect. A handler can always succeed with a value (or fail) no matter what its input is. + +`Http` on the other hand can decide to not handle a particular input, so it adds input based _routing_ on top of +the `Handler` type. + +Both `Handler` and `Http` provides several operators and constructors to model the application as per your use case. + +## Creating an HTTP Application + +### HTTP application that always succeeds + +To create an HTTP application that always returns the same response and never fails, you can use the `succeed` +constructor. + +```scala mdoc:silent +import zio._ +import zio.http._ + +val app1: Http[Any, Nothing, Any, Int] = Handler.succeed(1).toHttp +``` + +### HTTP application that always fails + +To create an HTTP application that always fails with the given error, you can use the `fail` constructor. + +```scala mdoc:silent +val app2: Http[Any, Error, Any, Nothing] = Handler.fail(new Error("Error_Message")).toHttp +``` + +HTTP applications can also be created from total and partial functions. These are some constructors to create HTTP +applications from total as well as partial functions. + +### HTTP application from a partial function + +`Http.collect` can create an `Http[Any, Nothing, A, B]` from a `PartialFunction[A, B]`. In case the input is not defined +for the partial function, the application will return a `None` type error. + +```scala mdoc:silent +val app3: Http[Any, Nothing, String, String] = + Http.collect[String] { + case "case 1" => "response 1" + case "case 2" => "response 2" + } +``` + +`Http.collectZIO` can be used to create a `Http[R, E, A, B]` from a partial function that returns a ZIO effect, +i.e `PartialFunction[A, ZIO[R, E, B]`. This constructor is used when the output is effectful. + +```scala mdoc:silent +val app4: Http[Any, Nothing, String, String] = + Http.collectZIO[String] { + case "case 1" => ZIO.succeed("response 1") + } +``` + +### HTTP application from a total function + +`Handler.fromFunction` can create an `Http[Any, Nothing, A, B]` from a function `f: A=>B`. This can be converted to +a `Http` which always routes to that given `Handler` by using `toHttp`: + +```scala mdoc:silent +val app5: Http[Any, Nothing, Int, Int] = + Handler.fromFunction[Int](i => i + 1).toHttp +``` + +`Handler.fromFunctionZIO` can create a `Http[R, E, A, B]` from a function that returns a ZIO effect, +i.e `f: A => ZIO[R, E, B]`. + +```scala mdoc:silent +val app6: Http[Any, Nothing, Int, Int] = + Handler.fromFunctionZIO[Int](i => ZIO.succeed(i + 1)).toHttp +``` + +## Transforming Http Applications + +Http operators are used to transform one or more HTTP applications to create a new HTTP application. Http domain +provides plenty of such powerful operators. + +### Transforming the output + +To transform the output of the HTTP application, you can use `map` operator . It takes a function `f: B=>C` to convert +a `Http[R,E,A,B]`to `Http[R,E,A,C]`. + +```scala mdoc:silent +val app7: Http[Any, Nothing, Any, String] = Handler.succeed("text").toHttp +val app8: Http[Any, Nothing, Any, Int] = app7.map(s => s.length()) +``` + +To transform the output of the HTTP application effectfully, you can use `mapZIO` operator. It takes a +function `B => ZIO[R1, E1, C]` to convert a `Http[R,E,A,B]` to `Http[R,E,A,C]`. + +```scala mdoc:silent +val app9: Http[Any, Nothing, Any, Int] = app7.mapZIO(s => ZIO.succeed(s.length())) +``` + +### Transforming the input + +To transform the input of a `Handler`, you can use `contramap` operator. +Before passing the input on to the HTTP application, `contramap` applies a function `xa: X => A` on it. + +```scala mdoc:silent +val handler1: Handler[Any, Nothing, String, String] = Handler.fromFunction[String](s => s + ' ' + s) +val app10: Http[Any, Nothing, Int, String] = handler1.contramap[Int](_.toString).toHttp +``` + +To transform the input of the handler effectfully, you can use `contramapZIO` operator. Before passing the +input on to the HTTP application, `contramapZIO` applies a function `xa: X => ZIO[R1, E1, A]` on it. + +```scala mdoc:silent +val app11: Http[Any, Any, Int, String] = handler1.contramapZIO[Any, Any, Int](a => ZIO.succeed(a.toString)).toHttp +``` + +### Chaining handlers + +To chain two handlers applications, you can use `flatMap` operator.It creates a new `Handler[R1, E1, A1, C1]` from the +output +of a `Handler[R,E,A,B]`, using a function `f: B => Handler[R1, E1, A1, C1]`. `>>=` is an alias for flatMap. + +```scala mdoc:silent +val handler2: Handler[Any, Nothing, Any, String] = Handler.succeed("text1") +val handler3: Handler[Any, Nothing, Any, String] = handler2 >>= (s => Handler.succeed(s + " text2")) +``` + +### Folding a handler + +`foldHandler` lets you handle the success and failure values of a handler. It takes in two functions, one for +failure and one for success, and one more handler. + +- If the handler fails with `E` the first function will be executed with `E`, +- If the application succeeds with `B`, the second function will be executed with `B` and + +```scala mdoc:silent +val handler4: Handler[Any, String, String, String] = Handler.fromFunctionHandler[String] { + case "case" => Handler.fail("1") + case _ => Handler.succeed("2") +} +val handler5: Handler[Any, Nothing, String, String] = handler4.foldHandler(e => Handler.succeed(e), s => Handler.succeed(s)) +``` + +## Error Handling + +These are several ways in which error handling can be done in both the `Http` and the `Handler` domains. + +### Catch all errors + +To catch all errors in case of failure of an HTTP application, you can use `catchAllZIO` operator. It pipes the error to +a +function `f: E => Http[R1, E1, A1, B1]`. + +```scala mdoc:silent +val app12: Http[Any, Throwable, Any, Nothing] = Handler.fail(new Throwable("Error_Message")).toHttp +val app13: Http[Any, Nothing, Any, Option[Throwable]] = app12.catchAllZIO(e => ZIO.succeed(Option(e))) +``` + +### Mapping the error + +To transform the failure of an HTTP application, you can use `mapError` operator. It pipes the error to a +function `ee: E => E1`. + +```scala mdoc:silent +val app14: Http[Any, Throwable, Any, Nothing] = Handler.fail(new Throwable("Error_Message")).toHttp +val app15: Http[Any, Option[Throwable], Any, Nothing] = app14.mapError(e => Option(e)) +``` + +## Composition of HTTP applications + +HTTP applications can be composed using several special operators. + +### Using `++` + +`++` is an alias for `defaultWith`. While using `++`, if the first HTTP application returns `None` the second HTTP +application will be evaluated, ignoring the result from the first. If the first HTTP application is failing with +a `Some[E]` the second HTTP application won't be evaluated. + +```scala mdoc:silent +val app16: Http[Any, Nothing, String, String] = Http.collect[String] { + case "case 1" => "response 1" + case "case 2" => "response 2" +} +val app17: Http[Any, Nothing, String, String] = Http.collect[String] { + case "case 3" => "response 3" + case "case 4" => "response 4" +} +val app18: Http[Any, Nothing, String, String] = app16 ++ app17 +``` + +### Using `<>` + +`<>` is an alias for `orElse`. While using `<>`, if the first handler fails with `Some[E]`, the second handler will be +evaluated, ignoring the result from the first. This operator is not available on the `Http` level, to keep the rules of +applying middlewares simple. + +```scala mdoc:silent +val handler6: Handler[Any, Nothing, Any, Int] = Handler.fail(1) <> Handler.succeed(2) +``` + +### Using `>>>` + +`>>>` is an alias for `andThen`. It runs the first HTTP application and pipes the output into the other handler. +The right side must be a `Handler`, it cannot perform further routing. + +```scala mdoc:silent +val app19: Http[Any, Nothing, Int, Int] = Handler.fromFunction[Int](a => a + 1).toHttp +val handler7: Handler[Any, Nothing, Int, Unit] = Handler.fromFunctionZIO[Int](b => ZIO.debug(b * 2)) +val app20: Http[Any, Nothing, Int, Unit] = app19 >>> handler7 +``` + +### Using `<<<` + +`<<<` is the alias for `compose`. Compose is similar to andThen, but it is only available on the `Handler` level. +It runs the second handler and pipes the output to the first handler. + +```scala mdoc:silent +val handler8: Handler[Any, Nothing, Int, Int] = Handler.fromFunction[Int](a => a + 1) +val handler9: Handler[Any, Nothing, Int, Int] = Handler.fromFunction[Int](b => b * 2) +val handler10: Handler[Any, Nothing, Int, Int] = handler8 <<< handler9 +``` + +## Providing environments + +There are many operators to provide the HTTP application with its required environment, they work the same as the ones +on `ZIO`. + +## Attaching Middleware + +Middlewares are essentially transformations that one can apply to any `Http` or a `Handler` to produce a new one. To +attach middleware +to the HTTP application, you can use `middleware` operator. `@@` is an alias for `middleware`. + +`RequestHandlerMiddleware` applies to `Handler`s converting a HTTP `Request` to `Response`. You can apply +a `RequestHandlerMiddleware` to both `Handler` and `Http`. +When applying it to a `Http`, it is equivalent to applying it to all handlers the `Http` can route to. +`HttpAppMiddleware` applies only to `Http`s and they are capable of change the routing behavior. + +## Unit testing + +Since an HTTP application `Http[R, E, A, B]` is a function from `A` to `ZIO[R, Option[E], B]`, we can write unit tests +just like we do for normal functions. + +The below snippet tests an app that takes `Int` as input and responds by adding 1 to the input. + +```scala mdoc:silent +import zio.test.Assertion.equalTo +import zio.test.{test, _} + +object Spec extends ZIOSpecDefault { + + def spec = suite("http")( + test("1 + 1 = 2") { + val app: Http[Any, Nothing, Int, Int] = Handler.fromFunction[Int](_ + 1).toHttp + assertZIO(app.runZIO(1))(equalTo(2)) + } + ) +} +``` + +# What is App? + +`App[-R]` is a type alias for `Http[R, Response, Request, Response]`. +ZIO HTTP server runs `App[E]` only. It is an application that takes a `Request` as an input, and it either produces +a successful `Response` or in case of failure it produces also a `Response`, representing the failure message to be sent +back. + +## Special Constructors for Http and Handler + +These are some special constructors for `Http` and `Handler`: + +### Handler.ok + +Creates a `Handler` that always responds with a 200 status code. + +```scala mdoc:silent +Handler.ok +``` + +### Handler.text + +Creates a `Handler` that always responds with the same plain text. + +```scala mdoc:silent +Handler.text("Text Response") +``` + +### Handler.status + +Creates a `Handler` that always responds with the same status code and empty data. + +```scala mdoc:silent +Handler.status(Status.Ok) +``` + +### Handler.error + +Creates a `Handler` that always fails with the given `HttpError`. + +```scala mdoc:silent +Handler.error(HttpError.Forbidden()) +``` + +### Handler.response + +Creates an `Handler` that always responds with the same `Response`. + +```scala mdoc:silent +Handler.response(Response.ok) +``` + +## Special operators on Handler + +These are some special operators for `Handler`s. + +### withMethod + +Overwrites the method in the incoming request to the `Handler` + +```scala mdoc:silent +val handler11 = Handler.fromFunction((request: Request) => Response.text(request.method.toString)) +handler11.withMethod(Method.POST) +``` + +### patch + +Patches the response produced by the request handler using a `Patch`. + +```scala mdoc:silent +val handler12 = Handler.response(Response.text("Hello World!")) +val handler13 = handler12.patch(Response.Patch.withStatus(Status.Accepted)) +``` + +## Converting an `Http` to `App` + +If you want to run an `Http[R, E, A, B]` app on the ZIO HTTP server you need to convert it to `App[R]` using +operators like `map`, `contramap`, etc. + +Custom errors can be converted to `Response` using `mapError` or you can use `withDefaultErrorHandling` to convert +all custom errors into internal server error responses. + +If a `Http` can never fail (has `Nothing` as its error type), there is no need to use `withDefaultErrorHandling` +or `mapError`. + +## Running an App + +ZIO HTTP server needs an `App[R]` for running. We can use `Server.serve()` method to bootstrap the server with +an `App[R]`: + +```scala mdoc:silent +object HelloWorld extends ZIOAppDefault { + val app: App[Any] = Handler.ok.toHttp + + override def run = Server.serve(app).provide(Server.default) +} +``` diff --git a/docs/Reference/dsl/middleware.md b/docs/Reference/dsl/middleware.md new file mode 100644 index 0000000000..9791e18704 --- /dev/null +++ b/docs/Reference/dsl/middleware.md @@ -0,0 +1,275 @@ +--- +id: middleware +title: Middleware +--- + +Before introducing middleware, let us understand why they are needed. + +Consider the following example where we have two endpoints within HttpApp +* GET a single user by id +* GET all users + +```scala +private val app = Http.collectZIO[Request] { + case Method.GET -> Root / "users" / id => + // core business logic + dbService.lookupUsersById(id).map(Response.json(_.json)) + case Method.GET -> Root / "users" => + // core business logic + dbService.paginatedUsers(pageNum).map(Response.json(_.json)) +} +``` + +#### The polluted code violates the principle of "Separation of concerns" + +As our application grows, we want to code the following aspects like +* Basic Auth +* Request logging +* Response logging +* Timeout and retry + +For both of our example endpoints, our core business logic gets buried under boilerplate like this + +```scala +(for { + // validate user + _ <- MyAuthService.doAuth(request) + // log request + _ <- logRequest(request) + // core business logic + user <- dbService.lookupUsersById(id).map(Response.json(_.json)) + resp <- Response.json(user.toJson) + // log response + _ <- logResponse(resp) +} yield resp) + .timeout(2.seconds) + .retryN(5) +``` +Imagine repeating this for all our endpoints!!! + +So there are two problems with this approach +* We are dangerously coupling our business logic with cross-cutting concerns (like applying timeouts) +* Also, addressing these concerns will require updating code for every single route in the system. For 100 routes we will need to repeat 100 timeouts!!! +* For example, any change related to a concern like the logging mechanism from logback to log4j2 may cause changing signature of `log(..)` function in 100 places. +* On the other hand, this also makes testing core business logic more cumbersome. + + +This can lead to a lot of boilerplate clogging our neatly written endpoints affecting readability, thereby leading to increased maintenance costs. + +## Need for middlewares and handling "aspects" + +If we refer to Wikipedia for the definition of an "[Aspect](https://en.wikipedia.org/wiki/Aspect_(computer_programming))" we can glean the following points. + +* An aspect of a program is a feature linked to many other parts of the program (**_most common example, logging_**)., +* But it is not related to the program's primary function (**_core business logic_**) +* An aspect crosscuts the program's core concerns (**_for example logging code intertwined with core business logic_**), +* Therefore, it can violate the principle of "separation of concerns" which tries to encapsulate unrelated functions. (**_Code duplication and maintenance nightmare_**) + +Or in short, aspect is a common concern required throughout the application, and its implementation could lead to repeated boilerplate code and in violation of the principle of separation of concerns. + +There is a paradigm in the programming world called [aspect-oriented programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming) that aims for modular handling of these common concerns in an application. + +Some examples of common "aspects" required throughout the application +- logging, +- timeouts (preventing long-running code) +- retries (or handling flakiness for example while accessing third party APIs) +- authenticating a user before using the REST resource (basic, or custom ones like OAuth / single sign-on, etc). + +This is where middleware comes to the rescue. +Using middlewares we can compose out-of-the-box middlewares (or our custom middlewares) to address the above-mentioned concerns using ++ and @@ operators as shown below. + +#### Cleaned up code using middleware to address cross-cutting concerns like auth, request/response logging, etc. +Observe, how we can address multiple cross-cutting concerns using neatly composed middlewares, in a single place. + +```scala mdoc:silent +import zio._ +import zio.http._ + +// compose basic auth, request/response logging, timeouts middlewares +val composedMiddlewares = RequestHandlerMiddlewares.basicAuth("user","pw") ++ + RequestHandlerMiddlewares.debug ++ + RequestHandlerMiddlewares.timeout(5.seconds) +``` + +And then we can attach our composed bundle of middlewares to an Http using `@@` + +```scala +val app = Http.collectZIO[Request] { + case Method.GET -> Root / "users" / id => + // core business logic + dbService.lookupUsersById(id).map(Response.json(_.json)) + case Method.GET -> Root / "users" => + // core business logic + dbService.paginatedUsers(pageNum).map(Response.json(_.json)) +} @@ composedMiddlewares // attach composedMiddlewares to the app using @@ +``` + +Observe how we gained the following benefits by using middlewares +* **Readability**: de-cluttering business logic. +* **Modularity**: we can manage aspects independently without making changes in 100 places. For example, + * replacing the logging mechanism from logback to log4j2 will require a change in one place, the logging middleware. + * replacing the authentication mechanism from OAuth to single sign-on will require changing the auth middleware +* **Testability**: we can test our aspects independently. + +## Middleware in zio-http + +A middleware helps in addressing common crosscutting concerns without duplicating boilerplate code. + +#### Attaching middleware to Http + +`@@` operator is used to attach a middleware to an Http. Example below shows a middleware attached to an HttpApp + +```scala mdoc:silent +val app = Http.collect[Request] { + case Method.GET -> Root / name => Response.text(s"Hello $name") +} +val appWithMiddleware = app @@ RequestHandlerMiddlewares.debug +``` + +Logically the code above translates to `Middleware.debug(app)` + +#### A simple middleware example + +Let us consider a simple example using out-of-the-box middleware called ```addHeader``` +We will write a middleware that will attach a custom header to the response. + +We create a middleware that appends an additional header to the response indicating whether it is a Dev/Prod/Staging environment. + +```scala mdoc:silent:reset +import zio._ +import zio.http._ + +lazy val patchEnv = RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") +``` + +A test `App` with attached middleware: + +```scala mdoc:silent +val app = Http.collect[Request] { + case Method.GET -> Root / name => Response.text(s"Hello $name") +} +val appWithMiddleware = app @@ patchEnv +``` + +Start the server: + +```scala mdoc:silent +Server.serve(appWithMiddleware).provide(Server.default) +``` + +Fire a curl request, and we see an additional header added to the response indicating the "Dev" environment: + +``` +curl -i http://localhost:8080/Bob + +HTTP/1.1 200 OK +content-type: text/plain +X-Environment: Dev +content-length: 12 + +Hello Bob +``` + +## Combining middlewares + +Middlewares can be combined using several special operators like `++`, `<>` and `>>>` + +### Using `++` combinator + +`>>>` and `++` are aliases for `andThen`. It combines two middlewares. + +For example, if we have three middlewares f1, f2, f3 + +f1 ++ f2 ++ f3 applies on an `http`, from left to right with f1 first followed by others, like this +```scala + f3(f2(f1(http))) +``` +#### A simple example using `++` combinator + +Start with imports: + +```scala mdoc:silent:reset +import zio.http._ +import zio.http.RequestHandlerMiddlewares.basicAuth +import zio._ +``` + +A user app with single endpoint that welcomes a user: + +```scala mdoc:silent +val userApp = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => + Response.text(s"Welcome to the ZIO party! ${name}") +} +``` + +A basicAuth middleware with hardcoded user pw and another patches response with environment value: + +```scala mdoc:silent +val basicAuthMW = basicAuth("admin", "admin") +val patchEnv = RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") +// apply combined middlewares to the userApp +val appWithMiddleware = userApp @@ (basicAuthMW ++ patchEnv) +``` + +Start the server: + +```scala mdoc:silent +Server.serve(appWithMiddleware).provide(Server.default) +``` + +Fire a curl request with an incorrect user/password combination: + +``` +curl -i --user admin:wrong http://localhost:8080/user/admin/greet + +HTTP/1.1 401 Unauthorized +www-authenticate: Basic +X-Environment: Dev +content-length: 0 +``` + +We notice in the response that first basicAuth middleware responded `HTTP/1.1 401 Unauthorized` and then patch middleware attached a `X-Environment: Dev` header. + +## Conditional application of middlewares + +- `when` applies middleware only if the condition function evaluates to true +-`whenZIO` applies middleware only if the condition function(with effect) evaluates + + +## A complete example of a middleware + +
+Detailed example showing "debug" and "addHeader" middlewares + +```scala mdoc:silent:reset +import zio.http._ +import zio._ + +import java.io.IOException +import java.util.concurrent.TimeUnit + +object Example extends ZIOAppDefault { + val app: App[Any] = + Http.collectZIO[Request] { + // this will return result instantly + case Method.GET -> Root / "text" => ZIO.succeed(Response.text("Hello World!")) + // this will return result after 5 seconds, so with 3 seconds timeout it will fail + case Method.GET -> Root / "long-running" => ZIO.succeed(Response.text("Hello World!")).delay(5.seconds) + } + + val middlewares = + RequestHandlerMiddlewares.debug ++ // print debug info about request and response + RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") // add static header + + override def run = + Server.serve(app @@ middlewares).provide(Server.default) +} +``` + +
+ +### A few "Out of the box" middlewares +- [Basic Auth](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/middleware_basic_auth) +- [CORS](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/middleware_cors) +- [CSRF](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/middleware_csrf) + diff --git a/docs/Reference/dsl/request.md b/docs/Reference/dsl/request.md new file mode 100644 index 0000000000..ef9c4c3986 --- /dev/null +++ b/docs/Reference/dsl/request.md @@ -0,0 +1,70 @@ +--- +id: request +title: Request +--- + +**ZIO HTTP** `Request` is designed in the simplest way possible to decode HTTP Request into a ZIO HTTP request. + It supports all HTTP request methods (as defined in [RFC2616](https://datatracker.ietf.org/doc/html/rfc2616) ) and headers along with custom methods and headers. + +## Creating a Request + +`Request` can be created with `method`, `url`, `headers`, `remoteAddress` and `data`. +Creating requests using `Request` is useful while writing unit tests. + +The below snippet creates a request with default params, `headers` as `Headers.empty`, `data` as `Body.Empty`, `remoteAddress` as `None` +```scala mdoc +import zio.http._ +import zio._ + +Request.default(Method.GET, URL(Root)) +``` + +## Matching and Extracting Requests + +`Request` can be extracted into an HTTP Method and Path via `->`. On the left side is the `Method`, and on the right side, the `Path`. + +```scala +Method.GET -> Root / "text" +``` + +### Method + +`Method` represents HTTP methods like POST, GET, PUT, PATCH, and DELETE. You can create existing HTTP methods such as `Method.GET`, `Method.POST` etc or create a custom one. + +### Path + `Path` can be created using + - `Root` which represents the root + - `/` which represents the path delimiter and starts the extraction from the left-hand side of the expression + - `/:` which represents the path delimiter and starts the extraction from the right-hand side of the expression and can match paths partially + +The below snippet creates an `HttpApp` that accepts an input of type `Request` and output of type `Response` with two paths. +According to the request path, it will respond with the corresponding response: +- if the request has path `/name` it will match the first route. +- if the request has path `/name/joe/wilson` it will match the second route as `/:` matches the path partially as well. + +```scala mdoc:silent + val app: HttpApp[Any, Nothing] = Http.collect[Request] { + case Method.GET -> Root / a => Response.text(s"$a") + case Method.GET -> "" /: "name" /: a => Response.text(s"$a") + } +``` + +## Accessing the Request + +- `body` to access the content of request as a `Body` +- `headers` to get all the headers in the Request +- `method` to access request method +- `url` to access request URL +- `remoteAddress` to access request's remote address if available +- `version` to access the HTTP version + +## Creating and reading a Request with query params + +Query params can be added in the request using `url` in `Request`, `URL` stores query params as `Map[String, List[String]]`. + +The below snippet creates a request with query params: `?q=a&q=b&q=c` +```scala mdoc +Request.get(url = URL(Root, queryParams = QueryParams("q" -> Chunk("a","b","c")))) +``` + +`url.queryParams` can be used to read query params from the request diff --git a/docs/Reference/dsl/response.md b/docs/Reference/dsl/response.md new file mode 100644 index 0000000000..e1d46efae6 --- /dev/null +++ b/docs/Reference/dsl/response.md @@ -0,0 +1,83 @@ +--- +id: response +title: Response +--- + +**ZIO HTTP** `Response` is designed to encode HTTP Response. +It supports all HTTP status codes and headers along with custom methods and headers (as defined in [RFC2616](https://datatracker.ietf.org/doc/html/rfc2616) ) + +## Creating a Response + +`Response` can be created with `status`, `headers` and `data`. + +The below snippet creates a response with default params, `status` as `Status.OK`, `headers` as `Headers.empty` and `data` as `Body.Empty`. +```scala mdoc +import zio.http._ +import zio._ + +Response() +``` +### Empty Response + +`ok` creates an empty response with status code 200 + +```scala mdoc +Response.ok +``` + +`status` creates an empty response with provided status code. + +```scala mdoc +Response.status(Status.Continue) +``` + +### Specialized Response Constructors + +`text` creates a response with data as text, content-type header set to text/plain and status code 200 + +```scala mdoc +Response.text("hey") +``` + +`json` creates a response with data as json, content-type header set to application/json and status code 200 + +```scala mdoc +Response.json("""{"greetings": "Hello World!"}""") +``` + +`html` creates a response with data as html, content-type header set to text/html and status code 200 +```scala mdoc +import zio.http.html._ + +Response.html(Html.fromString("html text")) +``` + +### Specialized Response Operators + +`withStatus` to update the `status` of `Response` + +```scal mdoca +Response.text("Hello World!").withStatus(Status.NOT_FOUND) +``` + +`updateHeaders` to update the `headers` of `Response` + +```scala mdoc +Response.ok.updateHeaders(_ => Headers("key", "value")) +``` + +### Response from HttpError + +`fromHttpError` creates a response with provided `HttpError` + +```scala mdoc + Response.fromHttpError(HttpError.BadRequest()) +``` + +## Adding Cookie to Response + +`addCookie` adds cookies in the headers of the response. +```scala mdoc +val cookie = Cookie.Response("key", "value") +Response.ok.addCookie(cookie) +``` \ No newline at end of file diff --git a/docs/Reference/dsl/server.md b/docs/Reference/dsl/server.md new file mode 100644 index 0000000000..466af18984 --- /dev/null +++ b/docs/Reference/dsl/server.md @@ -0,0 +1,63 @@ +--- +id: server +title: 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 mdoc:silent +import zio.http._ +import zio._ + +def app: App[Any] = ??? +``` + +```scala mdoc:silent:crash +Server.serve(app).provide(Server.default) +``` + +A quick shortcut to only customize the port is `Server.defaultWithPort`: + +```scala mdoc:silent:crash +Server.serve(app).provide(Server.defaultWithPort(8081)) +``` + +Or to customize more properties of the _default configuration_: + +```scala mdoc:silent:crash +Server.serve(app).provide( + Server.defaultWith( + _.port(8081).enableRequestStreaming + ) +) +``` + +## Start a ZIO HTTP Server with custom configurations. + +The `live` layer expects a `Server.Config` holding the custom configuration for the server. + +```scala mdoc:silent:crash +Server + .serve(app) + .provide( + ZLayer.succeed(Server.Config.default.port(8081)), + Server.live + ) +``` + +The `configured` layer loads the server configuration using the application's _ZIO configuration provider_, which +is using the environment by default but can be attached to a different backends using +the [ZIO Config library](https://zio.github.io/zio-config/). + +```scala mdoc:silent:crash +Server + .serve(app) + .provide( + Server.configured() + ) +``` + +In order to customize Netty-specific properties, the `customized` layer can be used, providing not only `Server.Config` +but also `NettyConfig`. \ No newline at end of file diff --git a/docs/Reference/dsl/socket/socket.md b/docs/Reference/dsl/socket/socket.md new file mode 100644 index 0000000000..9b7ea2f25d --- /dev/null +++ b/docs/Reference/dsl/socket/socket.md @@ -0,0 +1,56 @@ +--- +id: socket +title: "Socket" +--- + +Websocket support can be added to your Http application using the same `Http` domain, something like this — + +```scala mdoc:silent +import zio.http._ +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 http = Http.collectZIO[Request] { + case Method.GET -> Root / "subscriptions" => socket.toResponse +} +``` + +The WebSocket API leverages the already powerful `Http` domain to write web socket apps. The difference is that instead +of collecting `Request` we collect `Channel` or more specifically `WebSocketChannel`. And, instead of +returning +a `Response` we return `Unit`, because we use the channel to write content directly. + +## Channel + +Essentially, whenever there is a connection created between a server and client a channel is created on both sides. The +channel is a low level api that allows us to send and receive arbitrary messages. + +When we upgrade a Http connection to WebSocket, we create a specialized channel that only allows websocket frames to be +sent and received. The access to channel is available through the `Channel` api. + +## ChannelEvents + +A `ChannelEvent` is an immutable, type-safe representation of an event that's happened on a channel, and it looks like +this: + +```scala +sealed trait ChannelEvent[A] +``` + +It is the **Event** that was triggered. The type param `A` on the ChannelEvent represents the kind of message the event contains. + +The type `WebSocketChannelEvent` is a type alias to `ChannelEvent[WebsocketFrame]`. Meaning an event that contains `WebSocketFrame` typed messages. + +## Using `Http` + +We can use `Http.collect` to select the events that we care about for our use case, like in the above example we are +only interested in the `ChannelRead` event. There are other life cycle events such as `ChannelRegistered` +and `ChannelUnregistered` that one might want to hook onto for some other use cases. diff --git a/docs/Reference/dsl/socket/websocketframe.md b/docs/Reference/dsl/socket/websocketframe.md new file mode 100644 index 0000000000..9c6fe10ff5 --- /dev/null +++ b/docs/Reference/dsl/socket/websocketframe.md @@ -0,0 +1,106 @@ +--- +id: websocketframe +title: "WebSocketFrame" +--- + +In the [WebSocket](https://datatracker.ietf.org/doc/html/rfc6455) protocol, communication happens using frames. ZIO +HTTP's [WebSocketFrame](https://github.com/zio/zio-http/blob/main/zio-http/src/main/scala/zio/socket/WebSocketFrame.scala) +is the representation of those frames. The domain defines the following type of frames: + +* Text +* Binary +* Continuation +* Close +* Ping +* Pong + +## Text + +To create a Text frame that models textual data in the WebSocket protocol, you can use the `text` constructor. + +```scala mdoc:silent +import zio.http._ + +WebSocketFrame.text("Hello from ZIO-HTTP") +``` + +## Binary + +To create a Binary frame that models raw binary data, you can use the `binary` constructor. + +```scala mdoc:silent +import zio.Chunk +import java.nio.charset.StandardCharsets + +WebSocketFrame.binary(Chunk.fromArray("Hello from ZIO-HTTP".getBytes(StandardCharsets.UTF_16))) +``` + +## Continuation + +To create a Continuation frame to model a continuation fragment of the previous message, you can use the `continuation` +constructor. + +```scala mdoc:silent +WebSocketFrame.continuation(Chunk.fromArray("Hello from ZIO-HTTP".getBytes(StandardCharsets.UTF_16))) +``` + +## Close + +To create a Close frame for a situation where the connection needs to be closed, you can use the `close` constructor. +The constructor requires two arguments: + +* Status +* Optional reason. + +### Constructing Close with just status + +```scala mdoc:silent +WebSocketFrame.close(1000) +``` + +### Constructing Close with status and a reason + +```scala mdoc:silent +WebSocketFrame.close(1000, Some("Normal Closure")) +``` + +More information on status codes can be found +in [Section 7.4](https://datatracker.ietf.org/doc/html/rfc6455#section-7.4) of IETF's Data Tracker. + +## Ping + +Ping models heartbeat in the WebSocket protocol. The server or the client can at any time, after a successful handshake, +send a ping frame. + +```scala mdoc:silent +WebSocketFrame.ping +``` + +## Pong + +Pong models the second half of the heartbeat in the WebSocket protocol. Upon receiving [ping](#ping), a pong needs to be +sent back. + +```scala mdoc:silent +WebSocketFrame.ping +``` + +### Pattern Matching on WebSocketFrame + +ZIO HTTP envisions the WebSocketFrame as a [Sum](https://en.wikipedia.org/wiki/Tagged_union) type, which allows +exhaustive pattern matching to be performed on it. + +You can do pattern matching on the WebSocketFrame type in the following way: + +```scala +val frame: WebSocketFrame = ... + +frame match { + case WebSocketFrame.Binary(bytes) => ??? + case WebSocketFrame.Text(text) => ??? + case WebSocketFrame.Close(status, reason) => ??? + case WebSocketFrame.Ping => ??? + case WebSocketFrame.Pong => ??? + case WebSocketFrame.Continuation(buffer) => ??? +} +``` From f91647e7227eb13a1cea0bbe47eca2dd6125f069 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 14 Jun 2023 20:29:27 +0100 Subject: [PATCH 37/71] added dsl to reference --- docs/sidebar.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/sidebar.js b/docs/sidebar.js index ceeb04844d..2be00c5579 100644 --- a/docs/sidebar.js +++ b/docs/sidebar.js @@ -69,7 +69,32 @@ const sidebars = { "reference/websockets", "reference/json-handling", "reference/metrics", - "reference/request-logging" + "reference/request-logging", + { + type: "category", + label: "DSL", + link: { type: "doc", id: "index" }, + items: [ + "dsl/server", + "dsl/http", + "dsl/request", + "dsl/response", + "dsl/body", + "dsl/headers", + "dsl/cookies", + "dsl/middleware", + "dsl/html", + { + type: "category", + label: "DSL", + collapsed: false, + items: [ + "dsl/socket/socket", + "dsl/socket/websocketframe" + ] + }, + ] + }, ] } ] From 3031ab14adafa329a67a198ee541af94066ef644 Mon Sep 17 00:00:00 2001 From: daveads Date: Thu, 15 Jun 2023 11:01:27 +0100 Subject: [PATCH 38/71] how-to-guide --- .../basic-web-application-with-zio-http.md | 62 ++++++++++++++ docs/how-to-guides/cookie-authentication.md | 61 +++++++++++++ ...-handle-WebSocket-exceptions-and-errors.md | 71 ++++++++++++++++ .../how-to-utilize-signed-cookies.md | 30 +++++++ docs/how-to-guides/multipart-form-data.md | 85 +++++++++++++++++++ 5 files changed, 309 insertions(+) create mode 100644 docs/how-to-guides/basic-web-application-with-zio-http.md create mode 100644 docs/how-to-guides/cookie-authentication.md create mode 100644 docs/how-to-guides/how-to-handle-WebSocket-exceptions-and-errors.md create mode 100644 docs/how-to-guides/how-to-utilize-signed-cookies.md create mode 100644 docs/how-to-guides/multipart-form-data.md diff --git a/docs/how-to-guides/basic-web-application-with-zio-http.md b/docs/how-to-guides/basic-web-application-with-zio-http.md new file mode 100644 index 0000000000..13ff7929b0 --- /dev/null +++ b/docs/how-to-guides/basic-web-application-with-zio-http.md @@ -0,0 +1,62 @@ +--- +id: basic-web-application-with-zio-http +title: "Basic web Application With Zio Http" +--- + +This guide demonstrates the process of using ZIO HTTP and its HTML templating capabilities to build and run a basic web application that generates and serves HTML content. + +## Code + +```scala +package example + +import zio._ + +import zio.http._ + +object HtmlTemplating extends ZIOAppDefault { + // Importing everything from `zio.html` + import zio.http.html._ + + def app: Handler[Any, Nothing, Any, Response] = { + // Html response takes in a `Html` instance. + Handler.html { + + // Support for default Html tags + html( + // Support for child nodes + head( + title("ZIO Http"), + ), + body( + div( + // Support for css class names + css := "container" :: "text-align-left" :: Nil, + h1("Hello World"), + ul( + // Support for inline css + styles := Seq("list-style" -> "none"), + li( + // Support for attributes + a(href := "/hello/world", "Hello World"), + ), + li( + a(href := "/hello/world/again", "Hello World Again"), + ), + + // Support for Seq of Html elements + (2 to 10) map { i => + li( + a(href := s"/hello/world/i", s"Hello World $i"), + ) + }, + ), + ), + ), + ) + } + } + + def run = Server.serve(app.toHttp.withDefaultErrorResponse).provide(Server.default) +} +``` \ No newline at end of file diff --git a/docs/how-to-guides/cookie-authentication.md b/docs/how-to-guides/cookie-authentication.md new file mode 100644 index 0000000000..4afaaecea9 --- /dev/null +++ b/docs/how-to-guides/cookie-authentication.md @@ -0,0 +1,61 @@ +--- +id: cookie-authentication +title: "Cookie Authentication in ZIO Server" +--- + +This guide will demonstrate how to implement cookie-based authentication in a ZIO server-side application. You'll learn how to set and validate authentication cookies to secure your endpoints. + +## Code + +```scala +package example + +import zio._ + +import zio.http._ + +/** + * Example to make app using cookies + */ +object CookieServerSide extends ZIOAppDefault { + + // Setting cookies with an expiry of 5 days + private val cookie = Cookie.Response("key", "value", maxAge = Some(5 days)) + val res = Response.ok.addCookie(cookie) + + private val app = Http.collect[Request] { + case Method.GET -> Root / "cookie" => + Response.ok.addCookie(cookie.copy(path = Some(Root / "cookie"), isHttpOnly = true)) + + case Method.GET -> Root / "secure-cookie" => + Response.ok.addCookie(cookie.copy(isSecure = true, path = Some(Root / "secure-cookie"))) + + case Method.GET -> Root / "cookie" / "remove" => + res.addCookie(Cookie.clear("key")) + } + + // Run it like any simple app + val run = + Server.serve(app).provide(Server.default) +} +``` + +## Explaination + +- It imports necessary dependencies from the ZIO and ZIO HTTP modules. + +- It defines an object called `CookieServerSide` that extends the `ZIOAppDefault` trait, which provides a default implementation for running the ZIO application. + +- It creates a `Cookie.Response` object named `cookie` with a key-value pair and an expiry of 5 days. + +- It creates a `Response` object named `res` with an `ok` status and adds the cookie to it. + +- It defines a `Http.collect` function that takes a `Request` object and pattern matches on different routes. + +- For the route `GET /cookie`, it returns a response with an added cookie using the `cookie` object, specifying the path and setting the `isHttpOnly` flag to `true`. + +- For the route `GET /secure-cookie`, it returns a response with an added cookie using the `cookie` object, setting the `isSecure` flag to `true` and specifying the path. + +- For the route `GET /cookie/remove`, it returns a response with the res object, which contains the original `cookie` object with a cleared value for the "key" field. + +- Finally, it defines a `run` value that runs the server by serving the `app` using the `Server.serve` method and providing a default server configuration `(Server.default)`. \ No newline at end of file diff --git a/docs/how-to-guides/how-to-handle-WebSocket-exceptions-and-errors.md b/docs/how-to-guides/how-to-handle-WebSocket-exceptions-and-errors.md new file mode 100644 index 0000000000..9702ce8a19 --- /dev/null +++ b/docs/how-to-guides/how-to-handle-WebSocket-exceptions-and-errors.md @@ -0,0 +1,71 @@ +--- +id: how-to-handle-WebSocket-exceptions-and-errors +title: "How to handle WebSocket exceptions and errors in ZIO" +--- + +This code demonstrates an implementation of a WebSocket client using the ZIO library that supports automatic reconnection. + + +```scala +package example + +import zio._ + +import zio.http.ChannelEvent.{ExceptionCaught, Read, UserEvent, UserEventTriggered} +import zio.http._ + +object WebSocketReconnectingClient extends ZIOAppDefault { + + val url = "ws://ws.vi-server.org/mirror" + + // A promise is used to be able to notify application about websocket errors + def makeSocketApp(p: Promise[Nothing, Throwable]): SocketApp[Any] = + Handler + + // Listen for all websocket channel events + .webSocket { channel => + channel.receiveAll { + + // On connect send a "foo" message to the server to start the echo loop + case UserEventTriggered(UserEvent.HandshakeComplete) => + channel.send(ChannelEvent.Read(WebSocketFrame.text("foo"))) + + // On receiving "foo", we'll reply with another "foo" to keep echo loop going + case Read(WebSocketFrame.Text("foo")) => + ZIO.logInfo("Received foo message.") *> + ZIO.sleep(1.second) *> + channel.send(ChannelEvent.Read(WebSocketFrame.text("foo"))) + + // Handle exception and convert it to failure to signal the shutdown of the socket connection via the promise + case ExceptionCaught(t) => + ZIO.fail(t) + + case _ => + ZIO.unit + } + }.tapErrorZIO { f => + // signal failure to application + p.succeed(f) + } + + val app: ZIO[Any with Client with Scope, Throwable, Unit] = { + (for { + p <- zio.Promise.make[Nothing, Throwable] + _ <- makeSocketApp(p).connect(url).catchAll { t => + // convert a failed connection attempt to an error to trigger a reconnect + p.succeed(t) + } + f <- p.await + _ <- ZIO.logError(s"App failed: $f") + _ <- ZIO.logError(s"Trying to reconnect...") + _ <- ZIO.sleep(1.seconds) + } yield { + () + }) *> app + } + + val run = + app.provide(Client.default, Scope.default) + +} +``` \ No newline at end of file diff --git a/docs/how-to-guides/how-to-utilize-signed-cookies.md b/docs/how-to-guides/how-to-utilize-signed-cookies.md new file mode 100644 index 0000000000..35e6788ea3 --- /dev/null +++ b/docs/how-to-guides/how-to-utilize-signed-cookies.md @@ -0,0 +1,30 @@ +--- +id: how-to-utilize-signed-cookies +title: "How to utilize signed cookies" +--- + +This guide shows how to utilize signed cookies with ZIO HTTP, covering cookie definition, request handling, and server execution. + +## Code + +```scala +import zio.http._ + +/** + * Example to make app using signed-cookies + */ + + +object SignCookies extends ZIOAppDefault { + + // Setting cookies with an expiry of 5 days + private val cookie = Cookie.Response("key", "hello", maxAge = Some(5 days)) + + private val app = Http.collect[Request] { case Method.GET -> Root / "cookie" => + Response.ok.addCookie(cookie.sign("secret")) + } + + // Run it like any simple app + val run = Server.serve(app).provide(Server.default) +} +``` \ No newline at end of file diff --git a/docs/how-to-guides/multipart-form-data.md b/docs/how-to-guides/multipart-form-data.md new file mode 100644 index 0000000000..9c1b89c3d3 --- /dev/null +++ b/docs/how-to-guides/multipart-form-data.md @@ -0,0 +1,85 @@ +--- +id: multipart-form-data +title: "How to Handle Multipart Form Data" +--- + +Handling multipart form data is a common task in web development, especially when dealing with file uploads or complex form submissions. this guide shows how to handle multipart form data in Scala using the ZIO HTTP library. + +## Code + +```scala +package example + +import zio.{Chunk, Scope, ZIO, ZIOAppArgs, ZIOAppDefault} + +import zio.http._ + +object MultipartFormData extends ZIOAppDefault { + + private val app: App[Any] = + Http.collectZIO[Request] { + case req @ Method.POST -> Root / "upload" + if req.header(Header.ContentType).exists(_.mediaType == MediaType.multipart.`form-data`) => + for { + form <- req.body.asMultipartForm + .mapError(ex => + Response( + Status.InternalServerError, + body = Body.fromString(s"Failed to decode body as multipart/form-data (${ex.getMessage}"), + ), + ) + response <- form.get("file") match { + case Some(file) => + file match { + case FormField.Binary(_, data, contentType, transferEncoding, filename) => + ZIO.succeed( + Response.text( + s"Received ${data.length} bytes of $contentType filename $filename and transfer encoding $transferEncoding", + ), + ) + case _ => + ZIO.fail( + Response(Status.BadRequest, body = Body.fromString("Parameter 'file' must be a binary file")), + ) + } + case None => + ZIO.fail(Response(Status.BadRequest, body = Body.fromString("Missing 'file' from body"))) + } + } yield response + } + + private def program: ZIO[Client with Server, Throwable, Unit] = + for { + port <- Server.install(app) + _ <- ZIO.logInfo(s"Server started on port $port") + client <- ZIO.service[Client] + response <- client + .host("localhost") + .port(port) + .post( + "/upload", + Body.fromMultipartForm( + Form( + FormField.binaryField( + "file", + Chunk.fromArray("Hello, world!".getBytes), + MediaType.application.`octet-stream`, + filename = Some("hello.txt"), + ), + ), + Boundary("AaB03x"), + ), + ) + responseBody <- response.body.asString + _ <- ZIO.logInfo(s"Response: [${response.status}] $responseBody") + _ <- ZIO.never + } yield () + + override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] = + program + .provide( + Server.default, + Client.default, + ) +} +``` \ No newline at end of file From d8b36dda876125668abb38b2368bca09505b8b9f Mon Sep 17 00:00:00 2001 From: daveads Date: Thu, 15 Jun 2023 11:01:48 +0100 Subject: [PATCH 39/71] typo --- docs/how-to-guides/authentication.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/how-to-guides/authentication.md b/docs/how-to-guides/authentication.md index 16eeb089f0..5e7123fd76 100644 --- a/docs/how-to-guides/authentication.md +++ b/docs/how-to-guides/authentication.md @@ -4,8 +4,7 @@ title: "Authentication Server Example" --- -This code shows how to how to implement a server with bearer authentication middle ware in `zio-http` - +This code shows how to implement a server with bearer authentication middle ware in `zio-http` ```scala From 8473eaf00cf3c8866d64a3336cd2635636ed8968 Mon Sep 17 00:00:00 2001 From: daveads Date: Thu, 15 Jun 2023 12:33:41 +0100 Subject: [PATCH 40/71] reference --- docs/Reference/api-docs.md | 223 ------------------------------ docs/Reference/json-handling.md | 5 + docs/Reference/metrics.md | 117 ++++++++-------- docs/Reference/request-logging.md | 53 +++++++ docs/Reference/server-backend.md | 4 + docs/Reference/websockets.md | 77 +++++++---- docs/faq.md | 9 +- docs/index.md | 4 +- docs/sidebar.js | 7 +- 9 files changed, 183 insertions(+), 316 deletions(-) delete mode 100644 docs/Reference/api-docs.md create mode 100644 docs/Reference/request-logging.md diff --git a/docs/Reference/api-docs.md b/docs/Reference/api-docs.md deleted file mode 100644 index 759a034add..0000000000 --- a/docs/Reference/api-docs.md +++ /dev/null @@ -1,223 +0,0 @@ - -# ZMX Metric Reference - -All metrics in ZMX are defined as aspects that can be applied to effects without changing the signature of the effect it is applied to. - -Metric aspects are further qualified by a type parameter `A` that must be compatible with the output type of the effect. This means that a `MetricAspect[Any]` can be applied to any effect, while a `MetricAspect[Double]` can only be applied to effects producing a `Double` value. - -Each metric understands a certain data type it can observe to manipulate its state. Counters, Gauges, Histograms, and Summaries all understand `Double` values, while a Set understands `String` values. - -In cases where the output type of an effect is not compatible with the type required to manipulate the metric, the API defines a `xxxxWith` method to construct a `MetricAspect[A]` with a mapper function from `A` to the type required by the metric. - -The API functions in this document are implemented in the `MetricAspect` object. An aspect can be applied to an effect with the `@@` operator. - -Once an application is instrumented with ZMX aspects, it can be configured with a client implementation that is responsible for providing the captured metrics to an appropriate backend. Currently, ZMX supports clients for StatsD and Prometheus out of the box. - -## Counter - -A counter in ZMX is simply a named variable that increases over time. - -### API - -Create a counter that is incremented by 1 every time it is executed successfully. This can be applied to any effect. - -```scala -def count(name: String, tags: Label*): MetricAspect[Any] -``` - -Create a counter that counts the number of failed executions of the effect it is applied to. This can be applied to any effect. - -```scala -def countErrors(name: String, tags: Label*): MetricAspect[Any] -``` - -Create a counter that can be applied to effects producing a `Double` value. The counter will be increased by the value the effect produces. - -```scala -def countValue(name: String, tags: Label*): MetricAspect[Double] -``` - -Create a counter that can be applied to effects producing a value of type `A`. Given the effect produces `v: A`, the counter will be increased by `f(v)`. - -```scala -def countValueWith[A](name: String, tags: Label*)(f: A => Double): MetricAspect[A] -``` - -### Examples - -Create a counter named `countAll` that is incremented by 1 every time it is invoked. - -```scala -val aspCountAll = MetricAspect.count("countAll") -``` - -Now the counter can be applied to any effect. Note that the same aspect can be applied to more than one effect. In the example, we count the sum of executions of both effects in the `for` comprehension. - -```scala -val countAll = for { - _ <- ZIO.unit @@ aspCountAll - _ <- ZIO.unit @@ aspCountAll -} yield () -``` - -Create a counter named `countBytes` that can be applied to effects producing a `Double` value. - -```scala -val aspCountBytes = MetricAspect.countValue("countBytes") -``` - -Now we can apply it to effects producing a `Double`. In a real application, the value might be the number of bytes read from a stream or something similar. - -```scala -val countBytes = nextDoubleBetween(0.0d, 100.0d) @@ aspCountBytes -``` - -## Gauges - -A gauge in ZMX is a named - - variable of type `Double` that can change over time. It can either be set to an absolute value or relative to the current value. - -### API - -Create a gauge that can be set to absolute values. It can be applied to effects yielding a `Double` value. - -```scala -def setGauge(name: String, tags: Label*): MetricAspect[Double] -``` - -Create a gauge that can be set to absolute values. It can be applied to effects producing a value of type `A`. Given the effect produces `v: A`, the gauge will be set to `f(v)` upon successful execution of the effect. - -```scala -def setGaugeWith[A](name: String, tags: Label*)(f: A => Double): MetricAspect[A] -``` - -Create a gauge that can be set relative to its previous value. It can be applied to effects yielding a `Double` value. - -```scala -def adjustGauge(name: String, tags: Label*): MetricAspect[Double] -``` - -Create a gauge that can be set relative to its previous value. It can be applied to effects producing a value of type `A`. Given the effect produces `v: A`, the gauge will be modified by `_ + f(v)` upon successful execution of the effect. - -```scala -def adjustGaugeWith[A](name: String, tags: Label*)(f: A => Double): MetricAspect[A] -``` - -### Examples - -Create a gauge that can be set to absolute values. It can be applied to effects yielding a `Double` value. - -```scala -val aspGaugeAbs = MetricAspect.setGauge("setGauge") -``` - -Create a gauge that can be set relative to its current value. It can be applied to effects yielding a `Double` value. - -```scala -val aspGaugeRel = MetricAspect.adjustGauge("adjustGauge") -``` - -Now we can apply these effects to effects having an output type `Double`. Note that we can instrument an effect with any number of aspects if the type constraints are satisfied. - -```scala -val gaugeSomething = for { - _ <- nextDoubleBetween(0.0d, 100.0d) @@ aspGaugeAbs @@ aspCountAll - _ <- nextDoubleBetween(-50d, 50d) @@ aspGaugeRel @@ aspCountAll -} yield () -``` - -## Histograms - -A histogram observes `Double` values and counts the observed values in buckets. Each bucket is defined by an upper boundary, and the count for a bucket with the upper boundary `b` increases by 1 if an observed value `v` is less than or equal to `b`. - -As a consequence, all buckets that have a boundary `b1` with `b1 > b` will increase by 1 after observing `v`. - -A histogram also keeps track of the overall count of observed values and the sum of all observed values. - -By definition, the last bucket is always defined as `Double.MaxValue`, so that the count of observed values in the last bucket is always equal to the overall count of observed values within the histogram. - -To define a histogram aspect, the API requires specifying the boundaries for the histogram when creating the aspect. - -The mental model for a ZMX histogram is inspired by Prometheus. - -### API - -Create a histogram that can be applied to effects producing `Double` values. The values will be counted as outlined above. - -```scala -def observeHistogram(name: String, boundaries: Chunk[Double], tags: Label*): MetricAspect[Double] -``` - -Create a histogram that can be applied to effects producing values `v` of type `A`. The values `f(v)` will be counted as outlined above. - -```scala -def observeHistogramWith[A - -](name: String, boundaries: Chunk[Double], tags: Label*)(f: A => Double): MetricAspect[A] -``` - -### Examples - -Create a histogram with 12 buckets: 0..100 in steps of 10 and `Double.MaxValue`. It can be applied to effects yielding a `Double` value. - -```scala -val aspHistogram = - MetricAspect.observeHistogram("histogram", DoubleHistogramBuckets.linear(0.0d, 10.0d, 11).boundaries) -``` - -Now we can apply the histogram to effects producing `Double`: - -```scala -val histogram = nextDoubleBetween(0.0d, 120.0d) @@ aspHistogram -``` - -## Summaries - -Similar to a histogram, a summary also observes `Double` values. While a histogram directly modifies the bucket counters and does not keep the individual samples, the summary keeps the observed samples in its internal state. To avoid the set of samples growing uncontrolled, the summary needs to be configured with a maximum age `t` and a maximum size `n`. To calculate the statistics, at most `n` samples will be used, all of which are not older than `t`. - -Essentially, the set of samples is a sliding window over the last observed samples matching the conditions above. - -A summary is used to calculate a set of quantiles over the current set of samples. A quantile is defined by a `Double` value `q` with `0 <= q <= 1` and resolves to a `Double` as well. - -The value of a given quantile `q` is the maximum value `v` out of the current sample buffer with size `n` where at most `q * n` values out of the sample buffer are less than or equal to `v`. - -Typical quantiles for observation are 0.5 (the median) and 0.95. Quantiles are very good for monitoring Service Level Agreements. - -The ZMX API also allows summaries to be configured with an error margin `e`. The error margin is applied to the count of values, so that a quantile `q` for a set of size `s` resolves to value `v` if the number `n` of values less than or equal to `v` is `(1 - e)q * s <= n <= (1+e)q`. - -### API - -A metric aspect that adds a value to a summary each time the effect it is applied to succeeds. This aspect can be applied to effects producing a `Double` value. - -```scala -def observeSummary( - name: String, - maxAge: Duration, - maxSize: Int, - error: Double, - quantiles: Chunk[Double], - tags: Label* -): MetricAspect[Double] -``` - -A metric aspect that adds a value to a summary each time the effect it is applied to succeeds, using the specified function to transform the value returned by the effect to the value to add to the summary. - -```scala -def observeSummaryWith[A]( - name: String, - maxAge: Duration, - maxSize: Int, - error: Double, - quantiles: Chunk[Double], - tags: Label* -)(f: A => Double): MetricAspect[A] -``` - -### Examples - -Create a summary that can hold 100 samples. The maximum age of the samples is 1 day, and the error margin is 3%. The summary should report the 10%, 50%, and 90% quantiles. It can be applied to effects yielding an `Int`. - -```scala -val aspSummary = - MetricAspect.observeSummaryWith[Int]("mySummary", 1.day, 100, 0.03d, Chunk(0.1, \ No newline at end of file diff --git a/docs/Reference/json-handling.md b/docs/Reference/json-handling.md index b151c30810..6b5ec8f500 100644 --- a/docs/Reference/json-handling.md +++ b/docs/Reference/json-handling.md @@ -1,3 +1,8 @@ +--- +id: json-handling +title: "Json Handling" +--- + **JSON Handling in ZIO HTTP** ZIO HTTP provides built-in support for handling JSON data in your applications. This allows you to easily parse incoming JSON requests, serialize data into JSON responses, and work with JSON data structures in a type-safe manner. Here's an overview of how JSON handling is typically done in ZIO HTTP. diff --git a/docs/Reference/metrics.md b/docs/Reference/metrics.md index ac891d4ba7..a2d2a876c1 100644 --- a/docs/Reference/metrics.md +++ b/docs/Reference/metrics.md @@ -1,63 +1,70 @@ -**Metrics in ZIO HTTP** - -Metrics play a crucial role in monitoring and understanding the performance and behavior of your HTTP applications. ZIO HTTP provides support for integrating metrics into your applications, allowing you to collect and analyze various metrics related to request processing, response times, error rates, and more. Here's an overview of how you can incorporate metrics into your ZIO HTTP applications. - -**1. Metric Types** - -ZIO HTTP supports different types of metrics that you can track in your applications: - -- **Counter**: A counter is a simple metric that keeps track of the number of occurrences of a particular event. For example, you can use a counter to count the number of incoming requests or the number of successful responses. - -- **Timer**: A timer measures the duration of a specific operation or process. You can use timers to measure the processing time of requests or specific parts of your application logic. - -- **Gauge**: A gauge provides a way to track a specific value or metric at a particular point in time. It can be used to monitor things like the number of active connections or the current memory usage of your application. - -- **Histogram**: A histogram captures the statistical distribution of values over a period of time. It can be useful for tracking response times or request sizes. - -**2. Metrics Collection** - -ZIO HTTP integrates with popular metrics libraries, such as Micrometer, which provides a unified way to collect and export metrics to various monitoring systems (e.g., Prometheus, Graphite, etc.). To collect metrics in your ZIO HTTP application, you can create a `Metrics` object and instrument your routes or middleware with the desired metrics. - -Here's an example of using Micrometer with ZIO HTTP to collect request count and response time metrics: - +--- +id: metrics +title: "Metrics reference" +--- + +### Reference on metrics + +1. APIs and Classes: + - `zio.metrics.Metric`: Provides APIs for creating and managing metrics. + - `Metric.counterInt(name: String): Counter[RuntimeFlags]`: Creates a counter metric of type `Int` with the given name. + - `Metric.gauge(name: String): Gauge[Double]`: Creates a gauge metric of type `Double` with the given name. + - `Metric.histogram(name: String, boundaries: MetricKeyType.Histogram.Boundaries): Histogram[Double]`: Creates a histogram metric of type `Double` with the given name and boundaries. + - `zio.metrics.MetricLabel`: Represents a label associated with a metric. + +2. Functions: + - `metrics`: A function that adds metrics to a ZIO-HTTP server. + - Parameters: + - `pathLabelMapper: PartialFunction[Request, String] = Map.empty`: A mapping function to map incoming paths to patterns. + - `concurrentRequestsName: String = "http_concurrent_requests_total"`: Name of the concurrent requests metric. + - `totalRequestsName: String = "http_requests_total"`: Name of the total requests metric. + - `requestDurationName: String = "http_request_duration_seconds"`: Name of the request duration metric. + - `requestDurationBoundaries: MetricKeyType.Histogram.Boundaries = Metrics.defaultBoundaries`: Boundaries for the request duration metric. + - `extraLabels: Set[MetricLabel] = Set.empty`: A set of extra labels that will be tagged with all metrics. + - Returns: An `HttpAppMiddleware` that adds metrics to the server. + +3. Usage Example: ```scala -import zio.http._ -import zio.metrics._ -import zio._ -import zio.clock.Clock - -val httpApp: HttpApp[Clock with Metrics, Throwable] = Http.collectM { - case Method.GET -> Root / "api" / "endpoint" => - for { - startTime <- clock.nanoTime - _ <- metrics.incrementCounter("requests") - response <- ZIO.succeed(Response.text("Hello, World!")) - endTime <- clock.nanoTime - elapsedTime = (endTime - startTime) / 1000000 // Calculate elapsed time in milliseconds - _ <- metrics.recordTimer("responseTime", elapsedTime) - } yield response +import zio.http.{RequestHandlerMiddlewares, _} +import zio.metrics.Metric.{Counter, Gauge, Histogram} +import zio.metrics.{Metric, MetricKeyType, MetricLabel} + +private[zio] trait Metrics { self: RequestHandlerMiddlewares => + // ... + + def metrics( + pathLabelMapper: PartialFunction[Request, String] = Map.empty, + concurrentRequestsName: String = "http_concurrent_requests_total", + totalRequestsName: String = "http_requests_total", + requestDurationName: String = "http_request_duration_seconds", + requestDurationBoundaries: MetricKeyType.Histogram.Boundaries = Metrics.defaultBoundaries, + extraLabels: Set[MetricLabel] = Set.empty, + ): HttpAppMiddleware[Nothing, Any, Nothing, Any] = { + // ... + } + + // ... } -``` - -In this example, we create a `Metrics` object by mixing the `Clock` and `Metrics` capabilities into the environment. Within the HTTP route, we increment the "requests" counter to track the number of incoming requests and record the elapsed time in the "responseTime" timer to measure the response processing time. -**3. Exporting Metrics** - -Once you have collected the metrics, you can export them to your preferred monitoring system. Micrometer provides integrations with various monitoring systems, allowing you to configure the export of metrics. +object Metrics { + // ... +} +``` -For example, to export the metrics to Prometheus, you can include the Prometheus Micrometer library in your project and configure it to scrape the metrics: +To use the `metrics` function, you can create an instance of a `Metrics` object and call the `metrics` method, providing the desired parameters. Here's an example: ```scala -import io.micrometer.prometheus.PrometheusMeterRegistry - -val registry = new PrometheusMeterRegistry() -Metrics.export(registry) +import zio.http.HttpAppMiddleware.metrics + +val app: HttpApp[Any, Nothing] = ??? +val metricsMiddleware = new Metrics with RequestHandlerMiddlewares {} +val middleware = metricsMiddleware.metrics( + pathLabelMapper = { case Method.GET -> Root / "user" / _ => + "/user/:id" + }, + extraLabels = Set(MetricLabel("test", "http_requests_total with path label mapper")), +) +val appWithMetrics = middleware(app) ``` -In this example, we create a `PrometheusMeterRegistry` and configure the `Metrics` object to export the collected metrics to this registry. You can then expose an endpoint in your application to expose the Prometheus metrics endpoint, which can be scraped by Prometheus for monitoring and visualization. - -**Summary** - -By integrating metrics into your ZIO HTTP applications, you can gain insights into the performance, behavior, and health of your HTTP services. ZIO HTTP provides support for different metric types, allowing you to track request counts, response times, and more. Integration with libraries like Micrometer enables - - you to export metrics to various monitoring systems for analysis and visualization. \ No newline at end of file +This example creates an HTTP app `app` and applies the `metrics` middleware with custom parameters. The `pathLabelMapper` is used to map specific paths to patterns, and extra labels are provided. The resulting `appWithMetrics` is the original app with the metrics middleware applied. \ No newline at end of file diff --git a/docs/Reference/request-logging.md b/docs/Reference/request-logging.md new file mode 100644 index 0000000000..272b09e5d8 --- /dev/null +++ b/docs/Reference/request-logging.md @@ -0,0 +1,53 @@ +--- +id: request-logging +title: "Request Logging" +--- + +**RequestLogging Traits, Functions, and Classes** + +1. `trait RequestLogging`: Represents the trait responsible for request logging. + +2. `requestLogging`: A function that creates a request logging middleware. + - Parameters: + - `level: Status => LogLevel`: Determines the log level based on the response status. + - `failureLevel: LogLevel`: Determines the log level for failed requests. + - `loggedRequestHeaders: Set[HeaderType]`: Defines the set of request headers to be logged. + - `loggedResponseHeaders: Set[HeaderType]`: Defines the set of response headers to be logged. + - `logRequestBody: Boolean`: Indicates whether to log the request body. + - `logResponseBody: Boolean`: Indicates whether to log the response body. + - `requestCharset: Charset`: Specifies the character set for the request. + - `responseCharset: Charset`: Specifies the character set for the response. + - Returns: `RequestHandlerMiddleware[Nothing, Any, Nothing, Any]`: A middleware that performs request logging. + +**RequestLogging Usage** + +1. Import the necessary dependencies: + - `import zio.http.internal.middlewares.{RequestLogging, RequestHandlerMiddlewares}` + - `import zio.http.{Request, Response, HttpError, Status, Header, Method, URL}` + - `import zio.{Exit, LogAnnotation, LogLevel, Trace, ZIO}` + +2. Create an instance of the HTTP application (`app`) that handles various requests. + +3. Use the `requestLogging` function to create a middleware for request logging and attach it to the HTTP application using the `@@` operator. + ```scala + (app @@ requestLogging()) + ``` + +4. Customize the request logging behavior by providing appropriate values for the `level`, `failureLevel`, `loggedRequestHeaders`, `loggedResponseHeaders`, `logRequestBody`, `logResponseBody`, `requestCharset`, and `responseCharset` parameters. + +5. Optionally, access and analyze the log entries to verify the expected request logging behavior. + +Here's an example of how to use the `RequestLogging` middleware: + +```scala +val app = Http.collectHandler[Request] { + case Method.GET -> Root / "ok" => Handler.ok + case Method.GET -> Root / "error" => Handler.error(HttpError.InternalServerError()) + case Method.GET -> Root / "fail" => Handler.fail(Response.status(Status.Forbidden)) + case Method.GET -> Root / "defect" => Handler.die(new Throwable("boom")) +} + +(app @@ requestLogging()).runZIO(Request.get(url = URL(Root / "ok"))) +``` + +This example attaches the `requestLogging` middleware to the `app` HTTP application and runs a `GET` request to the `/ok` endpoint. The request will be logged according to the specified configuration. \ No newline at end of file diff --git a/docs/Reference/server-backend.md b/docs/Reference/server-backend.md index bffd50d6ee..85f7a2dd2d 100644 --- a/docs/Reference/server-backend.md +++ b/docs/Reference/server-backend.md @@ -1,3 +1,7 @@ +--- +id: server-backend +title: "Server Backend" +--- # ZIO HTTP Server Configurations diff --git a/docs/Reference/websockets.md b/docs/Reference/websockets.md index 9d97315c28..e387398c2d 100644 --- a/docs/Reference/websockets.md +++ b/docs/Reference/websockets.md @@ -1,45 +1,64 @@ -**Adding WebSocket Support to your ZIO HTTP Application** +--- +id: websockets +title: "Websockets" +--- -WebSocket support can be seamlessly integrated into your ZIO HTTP application using the same HTTP domain. Here's an example of how you can add WebSocket functionality: +Reference guide for the WebSocket functionality -```scala -import zio.http._ -import zio._ +WebSocket Server APIs and Classes -val socket = Handler.webSocket { channel => - channel.receiveAll { - case ChannelEvent.Read(WebSocketFrame.Text("foo")) => - channel.send(ChannelEvent.Read(WebSocketFrame.text("bar"))) - case _ => - ZIO.unit - } -} +- `SocketApp[R]`: Represents a WebSocket application that handles incoming messages and defines the behavior of the server. + - `Handler.webSocket`: Constructs a WebSocket handler that takes a channel and defines the logic to process incoming messages. -val http = Http.collectZIO[Request] { - case Method.GET -> Root / "subscriptions" => socket.toResponse -} -``` +- `WebSocketChannel[R]`: Represents a WebSocket channel that allows reading from and writing to the WebSocket connection. + + - `channel.receiveAll`: Handles various incoming WebSocket frames and defines different responses based on the received message. + + - `channel.send(Read(WebSocketFrame.text))`: Sends a response back to the client by writing a text frame to the WebSocket channel. -In this example, we define a `socket` by using the `Handler.webSocket` method. This method takes a function that receives a `WebSocketChannel` and returns a `ZIO` effect. Inside this function, we can define the behavior of the WebSocket channel. +- `WebSocketFrame`: Represents a WebSocket frame. + + - `WebSocketFrame.Text(message)`: Represents a text frame with the specified message. + + - `WebSocketFrame.Close(status, reason)`: Represents a frame for closing the connection with the specified status and reason. -The `channel.receiveAll` method is used to handle incoming messages from the WebSocket client. In this case, we check if the received message is `"foo"` and respond with a message `"bar"`. Any other message is ignored. +- `ChannelEvent`: Represents different events that can occur on a WebSocket channel. + + - `ChannelEvent.Read(frame)`: Indicates the reading of a WebSocket frame. + + - `ChannelEvent.UserEventTriggered(event)`: Indicates a user-triggered event. + + - `ChannelEvent.ExceptionCaught(cause)`: Indicates an exception that occurred during WebSocket communication. -The `http` value represents your ZIO HTTP application. We use the `Http.collectZIO` combinator to handle incoming HTTP requests. In this example, we match the request pattern `Method.GET -> Root / "subscriptions"` and return the `socket.toResponse`, which converts the WebSocket channel to an HTTP response. +### WebSocket Server Usage -**Channel and ChannelEvents** +1. Create a `SocketApp[R]` instance using `Handler.webSocket` to define the WebSocket application logic. -A channel is created on both the server and client sides whenever a connection is established between them. It provides a low-level API to send and receive arbitrary messages. +2. Within the `SocketApp`, use `WebSocketChannel.receiveAll` to handle incoming WebSocket frames and define appropriate responses. -When a HTTP connection is upgraded to WebSocket, a specialized channel is created that only allows WebSocket frames to be sent and received. The WebSocketChannel is a subtype of the generic Channel API and provides specific methods for WebSocket communication. +3. Use `WebSocketChannel.send` to write WebSocket frames back to the client. -ChannelEvents represent immutable, type-safe events that occur on a channel. These events are wrapped in the `ChannelEvent` type. For WebSocket channels, the type alias `WebSocketChannelEvent` is used, which represents events containing WebSocketFrame messages. +4. Construct an HTTP application (`Http[Any, Nothing, Request, Response]`) that serves WebSocket connections. -**Using Http.collect** +5. Use `Http.collectZIO` to specify routes and associate them with appropriate WebSocket applications. -The `Http.collect` combinator allows you to select the events you are interested in for your specific use case. In the example, we use `Http.collectZIO` to handle ZIO effects. We match the desired events, such as `ChannelEvent.Read`, to perform custom logic based on the received WebSocket frames. -Other lifecycle events, such as `ChannelRegistered` and `ChannelUnregistered`, can also be hooked into for different use cases. +### WebSocket Client APIs and Classes -By leveraging the ZIO HTTP domain, you can build WebSocket applications using the same powerful abstractions provided for regular HTTP handling. +1. `makeSocketApp(p: Promise[Nothing, Throwable])`: Constructs a WebSocket application that connects to a specific URL and handles events. + - `Handler.webSocket`: Constructs a WebSocket handler that takes a channel and defines the logic to process incoming messages. -Please note that the example provided focuses on integrating WebSocket functionality into your ZIO HTTP application. Make sure to adapt it to your specific requirements and implement the necessary logic for handling WebSocket events and messages. +2. `WebSocketChannel[R]`: Represents a WebSocket channel that allows reading from and writing to the WebSocket connection. + - `channel.send(Read(WebSocketFrame.text))`: Sends a WebSocket frame to the server by writing it to the channel. + +### WebSocket Client Usage + +- Create a `makeSocketApp` function that constructs a `SocketApp[R]` instance for the WebSocket client. + +- Within the `SocketApp`, use `WebSocketChannel.receiveAll` to handle incoming WebSocket frames and define appropriate responses. + +- Use `WebSocketChannel.send` to write WebSocket frames to the server. + +- Use `connect(url)` to initiate a WebSocket connection to the specified URL. + +- Optionally, use promises or other mechanisms to handle WebSocket errors and implement reconnecting behavior. \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md index 0e441b0d4e..831d682a70 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -5,14 +5,12 @@ title: "common zio-http asked questions" Explore the most commonly asked questions about zio-http and find detailed answers in this informative resource. - **Q. What is ZIO-HTTP ?** ZIO Http is a functional Scala library utilized for constructing high-performance HTTP services and clients. It leverages the power of ZIO's concurrency library and the Netty network library. With ZIO-HTTP, you can create efficient and expressive HTTP applications through its comprehensive high-level API.
- **Q. Is zio-http a library or a framework?** ZIO-HTTP is primarily a library rather than a framework. It provides a set of tools, components, and abstractions that you can utilize to build HTTP-based services and clients in a functional programming style using Scala and the ZIO concurrency library. It offers a high-level API for constructing HTTP applications, but it does not impose a rigid framework structure or dictate the overall architecture of your application. Instead, it focuses on providing the necessary building blocks and utilities for working with HTTP protocols in a composable and expressive manner. @@ -27,7 +25,6 @@ ZIO's concurrency model is designed to handle high scalability and performance r With ZIO-HTTP, you can take advantage of these asynchronous features and design your applications to handle high loads, making it well-suited for webscale scenarios. Checkout the [benachmark results](https://web-frameworks-benchmark.netlify.app/compare?f=zio-http) To assess how ZIO-HTTP compares to other JVM-based web libraries in relation to their synchronous and asynchronous capabilities. -
**Q. Does ZIO-HTTP support middleware for request/response modification?** @@ -36,11 +33,11 @@ Yes, ZIO-HTTP does support middleware for request/response modification. Middlew You can define custom middleware functions that can perform operations such as request/response transformation, authentication, logging, error handling, and more. Middleware functions can be composed and applied to specific routes or globally to the entire application. -
+ Example -```scala mdoc:silent:reset +```scala package example import java.util.concurrent.TimeUnit @@ -78,5 +75,5 @@ object HelloWorldWithMiddlewares extends ZIOAppDefault { val run = Server.serve((app @@ middlewares).withDefaultErrorResponse).provide(Server.default) } ``` -
+ diff --git a/docs/index.md b/docs/index.md index ffcc5d452e..07c15a9a12 100644 --- a/docs/index.md +++ b/docs/index.md @@ -49,7 +49,7 @@ By leveraging the power of ZIO and the simplicity of functional programming, ZIO ## Quickstart -Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart]() guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. +Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart](https://github.com/zio/zio-http/tree/main/zio-http-example) guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. ## Module feature overview @@ -162,7 +162,7 @@ object BasicAuth extends ZIOAppDefault { } ``` -# Explanation of the code above +## Explanation of the code above - The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. diff --git a/docs/sidebar.js b/docs/sidebar.js index 2be00c5579..4079ce52ef 100644 --- a/docs/sidebar.js +++ b/docs/sidebar.js @@ -35,6 +35,7 @@ const sidebars = { "tutorials/your-first-zio-http-app", "tutorials/deploying-a-zio-http-app", "tutorials/testing-your-zio-http-app", + "tutorials/deeper-dive-into-middleware", ] }, { @@ -56,6 +57,11 @@ const sidebars = { "how-to-guides/endpoint", "how-to-guides/middleware", "how-to-guides/concrete-entity", + "how-to-guides/cookie-authentication", + "how-to-guides/basic-web-application-with-zio-http", + "how-to-guides/multipart-form-data", + "how-to-guides/how-to-utilize-signed-cookies", + "how-to-guides/how-to-handle-WebSocket-exceptions-and-errors", ] }, { @@ -64,7 +70,6 @@ const sidebars = { collapsed: false, link: { type: "doc", id: "index" }, items: [ - "reference/api-docs", "reference/server-backend", "reference/websockets", "reference/json-handling", From e06159c01ec474cf40af5a0712e17ebc4a5b1ee4 Mon Sep 17 00:00:00 2001 From: daveads Date: Thu, 15 Jun 2023 13:32:30 +0100 Subject: [PATCH 41/71] support --- docs/examples/basic/http-client.md | 25 ------------------------- docs/sidebar.js | 1 + docs/support.md | 17 +++++++++++++++++ 3 files changed, 18 insertions(+), 25 deletions(-) delete mode 100644 docs/examples/basic/http-client.md create mode 100644 docs/support.md diff --git a/docs/examples/basic/http-client.md b/docs/examples/basic/http-client.md deleted file mode 100644 index fdcddb97a4..0000000000 --- a/docs/examples/basic/http-client.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: http-client -title: Http Client Example -sidebar_label: Http Client ---- - -```scala mdoc:silent -import zio._ - -import zio.http.Client - -object SimpleClient extends ZIOAppDefault { - val url = "http://sports.api.decathlon.com/groups/water-aerobics" - - val program = for { - res <- Client.request(url) - data <- res.body.asString - _ <- Console.printLine(data) - } yield () - - override val run = program.provide(Client.default, Scope.default) - -} - -``` \ No newline at end of file diff --git a/docs/sidebar.js b/docs/sidebar.js index 4079ce52ef..5590a32d2d 100644 --- a/docs/sidebar.js +++ b/docs/sidebar.js @@ -12,6 +12,7 @@ const sidebars = { "quickstart", "performance", "faq", + "support", { type: "category", label: "Concepts", diff --git a/docs/support.md b/docs/support.md new file mode 100644 index 0000000000..306247ed96 --- /dev/null +++ b/docs/support.md @@ -0,0 +1,17 @@ +--- +id: support +title: "Support" +--- + + +## Seeking assistance: General Support + +
+ +- Check if there is a pertinent example available in the How-To Guides. We are continuously adding more examples to these resources." + +- [For issues](https://github.com/zio/zio-http/issues), Please kindly provide a comprehensive description, including the version used and step-by-step instructions for reproducing the issue." + +- **Discord Sever** : [zio-http](https://discord.gg/DH3erjBypH) + +- **Twitter** : [@zioscala](https://twitter.com/zioscala) From 0ea03bff706d81ab1e516ed6347efba5548bc896 Mon Sep 17 00:00:00 2001 From: felicien Date: Sat, 17 Jun 2023 08:45:44 +0000 Subject: [PATCH 42/71] syntax --- docs/tutorials/deeper-dive-into-middleware.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/tutorials/deeper-dive-into-middleware.md b/docs/tutorials/deeper-dive-into-middleware.md index 271414c178..7ea4c7305e 100644 --- a/docs/tutorials/deeper-dive-into-middleware.md +++ b/docs/tutorials/deeper-dive-into-middleware.md @@ -55,10 +55,7 @@ val app = HttpApp.collectZIO[Request] { The `BasicAuthMiddleware` middleware will inspect the `Authorization` header of each request and verify that it contains a valid username and password. If the credentials are valid, the middleware will continue the request processing chain. If the credentials are invalid, the middleware will return a `Unauthorized` response. - -## Code - - +## Code ## Conclusion From 978e38642d4977802046533c834fd8f221810366 Mon Sep 17 00:00:00 2001 From: daveads Date: Sun, 18 Jun 2023 17:03:46 +0100 Subject: [PATCH 43/71] sidebar config --- docs/sidebar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sidebar.js b/docs/sidebar.js index 5590a32d2d..76af0b8d84 100644 --- a/docs/sidebar.js +++ b/docs/sidebar.js @@ -4,7 +4,7 @@ const sidebars = { type: "category", label: "ZIO Http", - collapsed: false, + collapsed: True, link: { type: "doc", id: "index" }, items: [ From 7affa67187264df4d76f5cd3e8c669b4aab215a1 Mon Sep 17 00:00:00 2001 From: daveads Date: Sun, 18 Jun 2023 17:48:38 +0100 Subject: [PATCH 44/71] updates from upstream-main #2261 --- docs/how-to-guides/authentication.md | 1 - docs/how-to-guides/http-client.md | 7 +- docs/sidebar.js | 109 ------------------------- docs/sidebars.js | 115 +++++++++++++++++---------- 4 files changed, 77 insertions(+), 155 deletions(-) delete mode 100644 docs/sidebar.js diff --git a/docs/how-to-guides/authentication.md b/docs/how-to-guides/authentication.md index 04810c6f6b..3a1c9576df 100644 --- a/docs/how-to-guides/authentication.md +++ b/docs/how-to-guides/authentication.md @@ -8,7 +8,6 @@ This code shows how to implement a server with bearer authentication middle ware ```scala - import java.time.Clock import zio._ diff --git a/docs/how-to-guides/http-client.md b/docs/how-to-guides/http-client.md index 16be9e0340..53a2de8966 100644 --- a/docs/how-to-guides/http-client.md +++ b/docs/how-to-guides/http-client.md @@ -28,15 +28,14 @@ object SimpleClient extends ZIOAppDefault { val url = "http://sports.api.decathlon.com/groups/water-aerobics" val program = for { - res <- Client.request(url) + res <- Client.request(Request.get(url)) data <- res.body.asString _ <- Console.printLine(data).catchAll(e => ZIO.logError(e.getMessage)) } yield () - override val run = program.provide(Client.default) + override val run = program.provide(Client.default, Scope.default) + } ``` - - diff --git a/docs/sidebar.js b/docs/sidebar.js deleted file mode 100644 index 76af0b8d84..0000000000 --- a/docs/sidebar.js +++ /dev/null @@ -1,109 +0,0 @@ -const sidebars = { - sidebar: [ - { - - type: "category", - label: "ZIO Http", - collapsed: True, - link: { type: "doc", id: "index" }, - - items: [ - "setup", - "quickstart", - "performance", - "faq", - "support", - { - type: "category", - label: "Concepts", - collapsed: false, - link: { type: "doc", id: "index" }, - items: [ - "concepts/routing", - "concepts/request-handling", - "concepts/server", - "concepts/client", - "concepts/middleware", - "concepts/endpoint" - ] - }, - { - type: "category", - label: "Tutorials", - collapsed: false, - link: { type: "doc", id: "index" }, - items: [ - "tutorials/your-first-zio-http-app", - "tutorials/deploying-a-zio-http-app", - "tutorials/testing-your-zio-http-app", - "tutorials/deeper-dive-into-middleware", - ] - }, - { - type: "category", - label: "How-to-guides", - collapsed: false, - link: { type: "doc", id: "index" }, - items: [ - "how-to-guides/advance-http-sever", - "how-to-guides/http-client", - "how-to-guides/http-sever", - "how-to-guides/https-server", - "how-to-guides/https-client", - "how-to-guides/middleware-basic-authentication", - "how-to-guides/middleware-cors-handling", - "how-to-guides/streaming-file", - "how-to-guides/streaming-response", - "how-to-guides/websocket", - "how-to-guides/endpoint", - "how-to-guides/middleware", - "how-to-guides/concrete-entity", - "how-to-guides/cookie-authentication", - "how-to-guides/basic-web-application-with-zio-http", - "how-to-guides/multipart-form-data", - "how-to-guides/how-to-utilize-signed-cookies", - "how-to-guides/how-to-handle-WebSocket-exceptions-and-errors", - ] - }, - { - type: "category", - label: "Reference", - collapsed: false, - link: { type: "doc", id: "index" }, - items: [ - "reference/server-backend", - "reference/websockets", - "reference/json-handling", - "reference/metrics", - "reference/request-logging", - { - type: "category", - label: "DSL", - link: { type: "doc", id: "index" }, - items: [ - "dsl/server", - "dsl/http", - "dsl/request", - "dsl/response", - "dsl/body", - "dsl/headers", - "dsl/cookies", - "dsl/middleware", - "dsl/html", - { - type: "category", - label: "DSL", - collapsed: false, - items: [ - "dsl/socket/socket", - "dsl/socket/websocketframe" - ] - }, - ] - }, - ] - } - ] - } - ] -}; \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js index aaed5be099..76af0b8d84 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -1,13 +1,81 @@ const sidebars = { sidebar: [ { + type: "category", label: "ZIO Http", - collapsed: true, + collapsed: True, link: { type: "doc", id: "index" }, + items: [ "setup", - "getting-started", + "quickstart", + "performance", + "faq", + "support", + { + type: "category", + label: "Concepts", + collapsed: false, + link: { type: "doc", id: "index" }, + items: [ + "concepts/routing", + "concepts/request-handling", + "concepts/server", + "concepts/client", + "concepts/middleware", + "concepts/endpoint" + ] + }, + { + type: "category", + label: "Tutorials", + collapsed: false, + link: { type: "doc", id: "index" }, + items: [ + "tutorials/your-first-zio-http-app", + "tutorials/deploying-a-zio-http-app", + "tutorials/testing-your-zio-http-app", + "tutorials/deeper-dive-into-middleware", + ] + }, + { + type: "category", + label: "How-to-guides", + collapsed: false, + link: { type: "doc", id: "index" }, + items: [ + "how-to-guides/advance-http-sever", + "how-to-guides/http-client", + "how-to-guides/http-sever", + "how-to-guides/https-server", + "how-to-guides/https-client", + "how-to-guides/middleware-basic-authentication", + "how-to-guides/middleware-cors-handling", + "how-to-guides/streaming-file", + "how-to-guides/streaming-response", + "how-to-guides/websocket", + "how-to-guides/endpoint", + "how-to-guides/middleware", + "how-to-guides/concrete-entity", + "how-to-guides/cookie-authentication", + "how-to-guides/basic-web-application-with-zio-http", + "how-to-guides/multipart-form-data", + "how-to-guides/how-to-utilize-signed-cookies", + "how-to-guides/how-to-handle-WebSocket-exceptions-and-errors", + ] + }, + { + type: "category", + label: "Reference", + collapsed: false, + link: { type: "doc", id: "index" }, + items: [ + "reference/server-backend", + "reference/websockets", + "reference/json-handling", + "reference/metrics", + "reference/request-logging", { type: "category", label: "DSL", @@ -21,6 +89,7 @@ const sidebars = { "dsl/headers", "dsl/cookies", "dsl/middleware", + "dsl/html", { type: "category", label: "DSL", @@ -30,47 +99,11 @@ const sidebars = { "dsl/socket/websocketframe" ] }, - "dsl/html" ] }, - { - type: "category", - label: "Examples", - collapsed: false, - link: { type: "doc", id: "index" }, - items: [ - { - type: "category", - label: "Basic Examples", - collapsed: false, - items: [ - "examples/basic/http-client", - "examples/basic/https-client", - "examples/basic/http-server", - "examples/basic/https-server", - "examples/basic/websocket", - ] - }, - { - type: "category", - label: "Advanced Examples", - collapsed: false, - items: [ - "examples/advanced/authentication-server", - "examples/advanced/concrete-entity", - "examples/advanced/middleware-basic-authentication", - "examples/advanced/middleware-cors-handling", - "examples/advanced/server", - "examples/advanced/streaming-file", - "examples/advanced/streaming-response", - "examples/advanced/websocket-server" - ] - } - ] - } ] } + ] + } ] -}; - -module.exports = sidebars; +}; \ No newline at end of file From c406d7fcb0c61986d2600833b91f4e093680b189 Mon Sep 17 00:00:00 2001 From: daveads Date: Tue, 20 Jun 2023 11:19:23 +0100 Subject: [PATCH 45/71] first zio http app --- docs/tutorials/your-first-zio-http-app.md | 144 ++++++++++++++-------- 1 file changed, 90 insertions(+), 54 deletions(-) diff --git a/docs/tutorials/your-first-zio-http-app.md b/docs/tutorials/your-first-zio-http-app.md index b962608bf9..5d647532c7 100644 --- a/docs/tutorials/your-first-zio-http-app.md +++ b/docs/tutorials/your-first-zio-http-app.md @@ -3,79 +3,115 @@ id: your-first-zio-http-app title: Your first zio http app --- -# ZIO Quickstart: Hello World +# Your first zio-http app -The ZIO Quickstart Hello World is a simple example that demonstrates the basics of writing a ZIO application. It showcases how to interact with the console, read input from the user, and perform simple operations using ZIO's effect system. +Welcome to the realm of ZIO-HTTP! This guide will take you through the necessary steps to start and set up your first Scala server application. By the end, you'll have a fully functional app that is built. -## Running The Example +
-To run the example, follow these steps: +### **Pre-requisites**: +To install Scala and ZIO-HTTP, you'll need to have a few prerequisites in place. Here are the steps you can follow: -1. Open the console and clone the ZIO Quickstarts project using Git. You can also download the project directly. - ``` - git clone git@github.com:zio/zio-quickstarts.git - ``` +To install Scala and ZIO-HTTP, you'll need to have a few prerequisites in place. Here are the steps you can follow: -2. Change the directory to the `zio-quickstarts/zio-quickstart-hello-world` folder. - ``` - cd zio-quickstarts/zio-quickstart-hello-world - ``` +1. **Java Development Kit (JDK):** Scala runs on the Java Virtual Machine (JVM), so you'll need to have a JDK installed. Scala 2.13.x and ZIO-HTTP 1.0.x are compatible with Java 8 or later versions. Ensure that you have a JDK installed on your system by executing the `java -version` command in your terminal or command prompt. If Java is not installed, you can download it from the official Oracle website or use a package manager like Homebrew (for macOS) or apt (for Ubuntu). -3. Once you are inside the project directory, execute the following command to run the application: - ``` - sbt run - ``` +2. **Scala Build Tool (sbt):** sbt is the recommended build tool for Scala projects. It simplifies project setup and dependency management. To install sbt, you can follow the installation instructions provided on the sbt website (https://www.scala-sbt.org/download.html). Make sure to download and install the appropriate version for your operating system. [scala](https://www.scala-lang.org/) -## Testing The Quickstart +3. **IDE or Text Editor (Optional):** While not strictly necessary, having an Integrated Development Environment (IDE) or a text editor with Scala support can enhance your development experience. Popular choices include IntelliJ IDEA with the Scala plugin, Visual Studio Code with the Scala Metals extension, or Sublime Text with the Scala Syntax package. -The `sbt run` command searches for the executable class defined in the project, which in this case is `zio.dev.quickstart.MainApp`. The code for this class is as follows: +Once you have these prerequisites set up, you can proceed with creating a new Scala project and adding ZIO-HTTP as a dependency using sbt. -```scala -import zio._ +Follow this steps: -object MainApp extends ZIOAppDefault { - def run = Console.printLine("Hello, World!") -} -``` +1. Create a new directory for your project `first-zio-http-app` name it what ever you want. -This code uses ZIO's `Console.printLine` effect to print "Hello, World!" to the console. +2. Inside the project directory, create a new `build.sbt` file and open it with a text editor. +3. Add the following lines to `build.sbt` to define the project and its dependencies: -To enhance the quickstart and ask for the user's name, modify the code as shown below: + ```scala + name := "YourProjectName" -```scala -import zio._ + version := "1.0" + + scalaVersion := "2.13.6" + + libraryDependencies ++= Seq( + "dev.zio" %% "zio-http" % "3.0.0-RC2" + ) + + Compile / unmanagedSourceDirectories += baseDirectory.value / "src" + + ``` + +4. Save the `build.sbt` file. + +5. create a `src` direcotry in the same project directory This is where is `MainApp.scala` will reside in. + +6. navigate to the `src` directory and create a scala file name it `MainApp.scala` add the following lines in it. + + ```scala + import zio._ + import zio.http._ + + object MainApp extends ZIOAppDefault { + + val app: App[Any] = + Http.collect[Request] { + case Method.GET -> Root / "text" => Response.text("Hello World!") + case Method.GET -> Root / "fruits" / "b" => Response.text("banana") + case Method.GET -> Root / "json" => Response.json("""{"greetings": "Hello World!"}""") + } + + override val run = + Server.serve(app).provide(Server.default) + } + ``` +7. move over to the root directory and run the command `sbt run` -object MainApp extends ZIOAppDefault { - def run = - for { - _ <- Console.print("Please enter your name: ") - name <- Console.readLine - _ <- Console.printLine(s"Hello, $name!") - } yield () -} -``` -In this updated example, we use a for-comprehension to compose ZIO effects. It prompts the user to enter their name, reads the input using `Console.readLine`, and prints a customized greeting using `Console.printLine`. +
+
-Alternatively, you can rewrite the code using explicit `flatMap` operations: +**Let's break down what the code does ?** -```scala -import zio._ +The code defines an HTTP server using the ZIO HTTP library: -object MainApp extends ZIOAppDefault { - def run = - Console.print("Please enter your name: ") - .flatMap { _ => - Console.readLine - .flatMap { name => - Console.printLine(s"Hello, $name!") - } - } -} +- Imports: The code imports the necessary dependencies from the ZIO and ZIO HTTP libraries. + +- `MainApp` object: The `MainApp` object serves as the entry point of the application. + +- `app` value: The `app` value is an instance of `zio.http.Http.App[Any]`, which represents an HTTP application. It is constructed using the `Http.collect` method, which allows you to define routes and their corresponding responses. + +- Route Definitions: Within the `Http.collect` block, three routes are defined using pattern matching: + + - `case Method.GET -> Root / "text"`: This route matches a GET request with the path "/text". It responds with a text response containing the message "Hello World!". + - `case Method.GET -> Root / "fruits" / "b"`: This route matches a GET request with the path "/fruits/b". It responds with a text response containing the word "banana". + - `case Method.GET -> Root / "json"`: This route matches a GET request with the path "/json". It responds with a JSON response containing the message `{"greetings": "Hello World!"}`. + +- `run` method: The `run` method is overridden from `ZIOAppDefault` and serves as the entry point for running the application. It starts an HTTP server using `Server.serve`, passing the `app` as the application to serve. The server is provided with a default configuration using `Server.default`. + +
+
+ +To curl the routes defined in your ZIO HTTP application, you can use the following commands: + +1. Route: GET /text + +```shell +curl -X GET http://localhost:8080/text +``` + +2. Route: GET /fruits/b + +```shell +curl -X GET http://localhost:8080/fruits/b ``` -Both versions of the code achieve the same result. +3. Route: GET /json -By running the application with the modified code, you will be prompted to enter your name, and the program will respond with a personalized greeting. +```shell +curl -X GET http://localhost:8080/json +``` -Feel free to experiment and modify the code to explore more features and capabilities of ZIO. \ No newline at end of file +**You can find the source code of the Complete implementation [Here](https://github.com/daveads/zio-http-examples)** \ No newline at end of file From 57b50cbd62b3f6b4703d783030bdd1f181bb6782 Mon Sep 17 00:00:00 2001 From: daveads Date: Thu, 20 Jul 2023 14:03:09 +0100 Subject: [PATCH 46/71] added to how-to-guide --- docs/examples/basic/http-client.md | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 docs/examples/basic/http-client.md diff --git a/docs/examples/basic/http-client.md b/docs/examples/basic/http-client.md deleted file mode 100644 index fabd6d5226..0000000000 --- a/docs/examples/basic/http-client.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: http-client -title: Http Client Example -sidebar_label: Http Client ---- - -```scala mdoc:silent -import zio._ - -import zio.http._ - -object SimpleClient extends ZIOAppDefault { - val url = "http://sports.api.decathlon.com/groups/water-aerobics" - - val program = for { - res <- Client.request(Request.get(url)) - data <- res.body.asString - _ <- Console.printLine(data) - } yield () - - override val run = program.provide(Client.default, Scope.default) - -} - -``` \ No newline at end of file From 1be3d35cd80fae644c67ca5bad42b5223244daa1 Mon Sep 17 00:00:00 2001 From: daveads Date: Fri, 21 Jul 2023 09:15:27 +0100 Subject: [PATCH 47/71] case-sensitivity error --- docs/sidebars.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sidebars.js b/docs/sidebars.js index 76af0b8d84..0dc73fb472 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -4,7 +4,7 @@ const sidebars = { type: "category", label: "ZIO Http", - collapsed: True, + collapsed: true, link: { type: "doc", id: "index" }, items: [ From 4eb75bff6f3468d062b5ec37ef515a9c16671fe2 Mon Sep 17 00:00:00 2001 From: daveads Date: Thu, 27 Jul 2023 17:51:59 +0100 Subject: [PATCH 48/71] routes, middleware --- docs/concepts/client.md | 12 +- docs/concepts/endpoint.md | 106 ++----- docs/concepts/middleware.md | 329 ++-------------------- docs/concepts/request-handling.md | 29 +- docs/concepts/routing.md | 65 ++--- docs/concepts/server.md | 24 +- docs/examples/basic/http-client.md | 26 -- docs/examples/basic/websocket.md | 40 --- docs/getting-started.md | 199 ++----------- docs/how-to-guides/http-client.md | 14 +- docs/how-to-guides/multipart-form-data.md | 69 ++--- docs/how-to-guides/websocket.md | 33 +-- 12 files changed, 208 insertions(+), 738 deletions(-) delete mode 100644 docs/examples/basic/http-client.md delete mode 100644 docs/examples/basic/websocket.md diff --git a/docs/concepts/client.md b/docs/concepts/client.md index aa69929f1a..3ae4812536 100644 --- a/docs/concepts/client.md +++ b/docs/concepts/client.md @@ -31,16 +31,17 @@ package example import zio._ import zio.http.Header.AcceptEncoding +import zio.http._ import zio.http.netty.NettyConfig -import zio.http.{Client, DnsResolver, Headers, ZClient} object ClientWithDecompression extends ZIOAppDefault { - val url = "http://sports.api.decathlon.com/groups/water-aerobics" + val url = URL.decode("http://sports.api.decathlon.com/groups/water-aerobics").toOption.get val program = for { - res <- Client.request(url, headers = Headers(AcceptEncoding(AcceptEncoding.GZip(), AcceptEncoding.Deflate()))) - data <- res.body.asString - _ <- Console.printLine(data) + client <- ZIO.service[Client] + res <- client.addHeader(AcceptEncoding(AcceptEncoding.GZip(), AcceptEncoding.Deflate())).url(url).get("") + data <- res.body.asString + _ <- Console.printLine(data) } yield () val config = ZClient.Config.default.requestDecompression(true) @@ -50,6 +51,7 @@ object ClientWithDecompression extends ZIOAppDefault { Client.live, ZLayer.succeed(NettyConfig.default), DnsResolver.default, + Scope.default, ) } diff --git a/docs/concepts/endpoint.md b/docs/concepts/endpoint.md index 3bfc4951bf..abe0e8bc2b 100644 --- a/docs/concepts/endpoint.md +++ b/docs/concepts/endpoint.md @@ -5,108 +5,36 @@ title: Endpoint ## Endpoint -Endpoints in ZIO-HTTP represent individual API operations or routes that the server can handle. They define the structure and behavior of the API endpoints in a type-safe manner. Let's break down the key aspects: +Endpoints in ZIO-HTTP represent individual API operations or routes that the server can handle. They encapsulate the structure and behavior of the API endpoints in a type-safe manner, making them a fundamental building block for defining HTTP APIs. -- Endpoint Definition: - - Endpoints are defined using the `Endpoint` object's combinators, such as `get`, `post`, `path`, `query`, and more. - - - Combinators allow you to specify the HTTP method, URL path, query parameters, request/response bodies, and other details of the endpoint. +### Endpoint Definition: -- Middleware: - - Middleware can be applied to endpoints using the `@@` operator to add additional behavior or processing to the endpoint. +- Endpoints are defined using the `Endpoint` object's combinators, such as `get`, `post`, `path`, `query`, and more. These combinators allow you to specify various aspects of the endpoint, such as the HTTP method, URL path, query parameters, request/response bodies, and more. - - Middleware can handle authentication, validation, error handling, logging, or any custom logic needed for the endpoint. +### Middleware: -- Endpoint Implementation: - - Endpoints are implemented using the `implement` method, which takes a function specifying the logic to handle the request and generate the response. +- Middleware can be applied to endpoints using the `@@` operator. Middleware allows you to add additional behavior or processing to the endpoint. For instance, you can handle authentication, validation, error handling, logging, or implement any custom logic needed for the endpoint using middleware. - - Inside the implementation function, you can use ZIO effects to perform computations, interact with dependencies, and produce the desired response. +### Endpoint Implementation: -- Endpoint Composition: - - Endpoints can be composed together using operators like `++`, allowing you to build a collection of endpoints that make up your API. +- Endpoints are implemented using the `implement` method, which takes a function specifying the logic to handle the request and generate the response. This implementation function receives an instance of the request as input and produces an instance of the response as output. - - Composition enables structuring the API by grouping related endpoints or creating reusable components. +- Inside the implementation function, you can use ZIO effects to perform computations, interact with dependencies, and produce the desired response. ZIO's functional approach makes it easy to handle errors, perform asynchronous operations, and compose complex behaviors within the endpoint implementation. -- Converting to App: - - To serve the defined endpoints, they need to be converted to an HTTP application (`HttpApp`). +### Endpoint Composition: - - This conversion is done using the `toApp` method, which prepares the endpoints to be served as an HTTP application. +- Endpoints can be composed together using operators like `++`, allowing you to build a collection of endpoints that make up your API. This composition enables you to structure the API by grouping related endpoints or creating reusable components. - - Any required middleware can be applied during this conversion to the final app. +### Converting to App: -- Server: - - The server is responsible for starting the HTTP server and making the app available to handle incoming requests. +- To serve the defined endpoints, they need to be converted to an HTTP application (`HttpApp`). This conversion is done using the `toApp` method, which prepares the endpoints to be served as an HTTP application. - - The `Server.serve` method is used to start the server by providing the HTTP application and any necessary configurations. +- Any required middleware can be applied during this conversion to the final app. Middleware added to the endpoints will be applied to the HTTP application, ensuring that the specified behavior is enforced for each incoming request. -- Client Interaction: - - ZIO-HTTP also provides facilities to interact with endpoints as a client. +### Running an App: - - An `EndpointExecutor` can be created to execute the defined endpoints on the client-side, providing input values and handling the response. +- ZIO HTTP server requires an `HttpApp[R]` to run. The server can be started using the `Server.serve()` method, which takes the HTTP application as input and any necessary configurations. -Overall, endpoints in ZIO-HTTP define the structure, behavior, and implementation of individual API operations. They enable type-safe routing, request/response handling, and composition of API routes. Endpoints can be converted into an HTTP application and served by a server, or executed on the client-side using an `EndpointExecutor`. +- The server is responsible for listening on the specified port, accepting incoming connections, and handling the incoming HTTP requests by routing them to the appropriate endpoints. -Here is an Example: - -```scala -package example - -import zio._ - -import zio.http.Header.Authorization -import zio.http._ -import zio.http.codec.HttpCodec -import zio.http.endpoint._ - -object EndpointExamples extends ZIOAppDefault { - import HttpCodec._ - - val auth = EndpointMiddleware.auth - - // MiddlewareSpec can be added at the service level as well - val getUser = - Endpoint.get("users" / int("userId")).out[Int] @@ auth - - val getUserRoute = - getUser.implement { id => - ZIO.succeed(id) - } - - val getUserPosts = - Endpoint - .get("users" / int("userId") / "posts" / int("postId")) - .query(query("name")) - .out[List[String]] @@ auth - - val getUserPostsRoute = - getUserPosts.implement[Any] { case (id1: Int, id2: Int, query: String) => - ZIO.succeed(List(s"API2 RESULT parsed: users/$id1/posts/$id2?name=$query")) - } - - val routes = getUserRoute ++ getUserPostsRoute - - val app = routes.toApp(auth.implement(_ => ZIO.unit)(_ => ZIO.unit)) - - val request = Request.get(url = URL.decode("/users/1").toOption.get) - - val run = Server.serve(app).provide(Server.default) - - object ClientExample { - def example(client: Client) = { - val locator = - EndpointLocator.fromURL(URL.decode("http://localhost:8080").toOption.get) - - val executor: EndpointExecutor[Authorization] = - EndpointExecutor(client, locator, ZIO.succeed(Authorization.Basic("user", "pass"))) - - val x1 = getUser(42) - val x2 = getUserPosts(42, 200, "adam") - - val result1: UIO[Int] = executor(x1) - val result2: UIO[List[String]] = executor(x2) - - result1.zip(result2).debug - } - } -} -``` \ No newline at end of file +With `Endpoint` in ZIO-HTTP, you can define and implement your API endpoints in a type-safe and composable way. The DSL allows you to specify the details of each endpoint, handle middleware for additional behavior, and easily compose endpoints to structure your API. This powerful concept empowers developers to build robust and scalable API services using ZIO-HTTP. \ No newline at end of file diff --git a/docs/concepts/middleware.md b/docs/concepts/middleware.md index 072990da85..2e9e019a28 100644 --- a/docs/concepts/middleware.md +++ b/docs/concepts/middleware.md @@ -3,329 +3,56 @@ id: middleware title: Middleware --- +## Middleware Concepts in ZIO HTTP -Before introducing middleware, let us understand why they are needed. +Middleware plays a crucial role in addressing cross-cutting concerns without cluttering the core business logic. Cross-cutting concerns are aspects of a program that are linked to many parts of the application but are not directly related to its primary function. Examples of cross-cutting concerns include logging, timeouts, retries, authentication, and more. -Consider the following example where we have two endpoints within HttpApp -* GET a single user by id -* GET all users +### The Need for Middleware -```scala -private val app = Http.collectZIO[Request] { - case Method.GET -> Root / "users" / id => - // core business logic - dbService.lookupUsersById(id).map(Response.json(_.json)) - case Method.GET -> Root / "users" => - // core business logic - dbService.paginatedUsers(pageNum).map(Response.json(_.json)) -} -``` - -#### The polluted code violates the principle of "Separation of concerns" - -As our application grows, we want to code the following aspects like -* Basic Auth -* Request logging -* Response logging -* Timeout and retry - -For both of our example endpoints, our core business logic gets buried under boilerplate like this - -```scala -(for { - // validate user - _ <- MyAuthService.doAuth(request) - // log request - _ <- logRequest(request) - // core business logic - user <- dbService.lookupUsersById(id).map(Response.json(_.json)) - resp <- Response.json(user.toJson) - // log response - _ <- logResponse(resp) -} yield resp) - .timeout(2.seconds) - .retryN(5) -``` -Imagine repeating this for all our endpoints!!! - -So there are two problems with this approach -* We are dangerously coupling our business logic with cross-cutting concerns (like applying timeouts) -* Also, addressing these concerns will require updating code for every single route in the system. For 100 routes we will need to repeat 100 timeouts!!! -* For example, any change related to a concern like the logging mechanism from logback to log4j2 may cause changing signature of `log(..)` function in 100 places. -* On the other hand, this also makes testing core business logic more cumbersome. - - -This can lead to a lot of boilerplate clogging our neatly written endpoints affecting readability, thereby leading to increased maintenance costs. - -## Need for middlewares and handling "aspects" +Before introducing middleware, let's understand the challenges that arise when dealing with cross-cutting concerns in our application. Consider the following example, where we have two endpoints within an `HttpApp`: -If we refer to Wikipedia for the definition of an "[Aspect](https://en.wikipedia.org/wiki/Aspect_(computer_programming))" we can glean the following points. +1. GET a single user by id +2. GET all users -* An aspect of a program is a feature linked to many other parts of the program (**_most common example, logging_**)., -* But it is not related to the program's primary function (**_core business logic_**) -* An aspect crosscuts the program's core concerns (**_for example logging code intertwined with core business logic_**), -* Therefore, it can violate the principle of "separation of concerns" which tries to encapsulate unrelated functions. (**_Code duplication and maintenance nightmare_**) +The core business logic for each endpoint is interwoven with concerns like request validation, logging, timeouts, retries, and authentication. As the application grows, the code becomes polluted with boilerplate, making it difficult to maintain and test. Furthermore, any changes to these concerns might require updates in multiple places, leading to code duplication and violation of the "Separation of concerns" principle. -Or in short, aspect is a common concern required throughout the application, and its implementation could lead to repeated boilerplate code and in violation of the principle of separation of concerns. +### The Concept of Middleware -There is a paradigm in the programming world called [aspect-oriented programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming) that aims for modular handling of these common concerns in an application. +Middleware in ZIO HTTP enables a modular approach to handle cross-cutting concerns in an application. Middleware can be thought of as reusable components that can be composed together to address different aspects of the application. The idea is to separate these concerns from the core business logic, making the codebase cleaner, more maintainable, and easier to test. -Some examples of common "aspects" required throughout the application -- logging, -- timeouts (preventing long-running code) -- retries (or handling flakiness for example while accessing third party APIs) -- authenticating a user before using the REST resource (basic, or custom ones like OAuth / single sign-on, etc). +### Composing Middleware -This is where middleware comes to the rescue. -Using middlewares we can compose out-of-the-box middlewares (or our custom middlewares) to address the above-mentioned concerns using ++ and @@ operators as shown below. +Middleware can be combined using the `++` operator, which applies the middlewares from left to right. This composition allows us to build complex middleware bundles that cater to multiple aspects of the application. By composing middleware functions, we can attach them to specific routes or the entire `HttpApp` in one go, keeping the core business logic clean and free from boilerplate. -#### Cleaned up code using middleware to address cross-cutting concerns like auth, request/response logging, etc. -Observe, how we can address multiple cross-cutting concerns using neatly composed middlewares, in a single place. +### Middleware in Action -```scala mdoc:silent -import zio._ -import zio.http._ - -// compose basic auth, request/response logging, timeouts middlewares -val composedMiddlewares = RequestHandlerMiddlewares.basicAuth("user","pw") ++ - RequestHandlerMiddlewares.debug ++ - RequestHandlerMiddlewares.timeout(5.seconds) -``` - -And then we can attach our composed bundle of middlewares to an Http using `@@` +Let's look at an example of middleware usage in ZIO HTTP. Assume we have a basic `HttpApp` with a single endpoint to greet a user. We want to apply two middlewares: one for basic authentication and another to add a custom header indicating the environment. ```scala -val app = Http.collectZIO[Request] { - case Method.GET -> Root / "users" / id => - // core business logic - dbService.lookupUsersById(id).map(Response.json(_.json)) - case Method.GET -> Root / "users" => - // core business logic - dbService.paginatedUsers(pageNum).map(Response.json(_.json)) -} @@ composedMiddlewares // attach composedMiddlewares to the app using @@ -``` - -Observe how we gained the following benefits by using middlewares -* **Readability**: de-cluttering business logic. -* **Modularity**: we can manage aspects independently without making changes in 100 places. For example, - * replacing the logging mechanism from logback to log4j2 will require a change in one place, the logging middleware. - * replacing the authentication mechanism from OAuth to single sign-on will require changing the auth middleware -* **Testability**: we can test our aspects independently. - -## Middleware in zio-http - -A middleware helps in addressing common crosscutting concerns without duplicating boilerplate code. - -#### Attaching middleware to Http - -`@@` operator is used to attach a middleware to an Http. Example below shows a middleware attached to an HttpApp - -```scala mdoc:silent -val app = Http.collect[Request] { - case Method.GET -> Root / name => Response.text(s"Hello $name") -} -val appWithMiddleware = app @@ RequestHandlerMiddlewares.debug -``` - -Logically the code above translates to `Middleware.debug(app)` - -#### A simple middleware example - -Let us consider a simple example using out-of-the-box middleware called ```addHeader``` -We will write a middleware that will attach a custom header to the response. - -We create a middleware that appends an additional header to the response indicating whether it is a Dev/Prod/Staging environment. - -```scala mdoc:silent:reset -import zio._ -import zio.http._ - -lazy val patchEnv = RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") -``` - -A test `App` with attached middleware: - -```scala mdoc:silent -val app = Http.collect[Request] { - case Method.GET -> Root / name => Response.text(s"Hello $name") -} -val appWithMiddleware = app @@ patchEnv -``` - -Start the server: - -```scala mdoc:silent -Server.serve(appWithMiddleware).provide(Server.default) -``` - -Fire a curl request, and we see an additional header added to the response indicating the "Dev" environment: - -``` -curl -i http://localhost:8080/Bob - -HTTP/1.1 200 OK -content-type: text/plain -X-Environment: Dev -content-length: 12 - -Hello Bob -``` - -## Combining middlewares - -Middlewares can be combined using several special operators like `++`, `<>` and `>>>` - -### Using `++` combinator - -`>>>` and `++` are aliases for `andThen`. It combines two middlewares. - -For example, if we have three middlewares f1, f2, f3 - -f1 ++ f2 ++ f3 applies on an `http`, from left to right with f1 first followed by others, like this -```scala - f3(f2(f1(http))) -``` -#### A simple example using `++` combinator - -Start with imports: - -```scala mdoc:silent:reset -import zio.http._ -import zio.http.RequestHandlerMiddlewares.basicAuth -import zio._ -``` - -A user app with single endpoint that welcomes a user: - -```scala mdoc:silent -val userApp = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => - Response.text(s"Welcome to the ZIO party! ${name}") -} -``` - -A basicAuth middleware with hardcoded user pw and another patches response with environment value: - -```scala mdoc:silent -val basicAuthMW = basicAuth("admin", "admin") -val patchEnv = RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") -// apply combined middlewares to the userApp -val appWithMiddleware = userApp @@ (basicAuthMW ++ patchEnv) -``` - -Start the server: - -```scala mdoc:silent -Server.serve(appWithMiddleware).provide(Server.default) -``` - -Fire a curl request with an incorrect user/password combination: - -``` -curl -i --user admin:wrong http://localhost:8080/user/admin/greet - -HTTP/1.1 401 Unauthorized -www-authenticate: Basic -X-Environment: Dev -content-length: 0 -``` - -We notice in the response that first basicAuth middleware responded `HTTP/1.1 401 Unauthorized` and then patch middleware attached a `X-Environment: Dev` header. - -## Conditional application of middlewares - -- `when` applies middleware only if the condition function evaluates to true --`whenZIO` applies middleware only if the condition function(with effect) evaluates - - -## A complete example of a middleware - -
-Detailed example showing "debug" and "addHeader" middlewares - -```scala mdoc:silent:reset -import zio.http._ -import zio._ - -import java.io.IOException -import java.util.concurrent.TimeUnit - -object Example extends ZIOAppDefault { - val app: App[Any] = - Http.collectZIO[Request] { - // this will return result instantly - case Method.GET -> Root / "text" => ZIO.succeed(Response.text("Hello World!")) - // this will return result after 5 seconds, so with 3 seconds timeout it will fail - case Method.GET -> Root / "long-running" => ZIO.succeed(Response.text("Hello World!")).delay(5.seconds) - } - - val middlewares = - RequestHandlerMiddlewares.debug ++ // print debug info about request and response - RequestHandlerMiddlewares.addHeader("X-Environment", "Dev") // add static header - - override def run = - Server.serve(app @@ middlewares).provide(Server.default) -} -``` - -
- -
-
- -Here is another example of a simple middleware and code break down : - -```scala -package example - -import java.util.concurrent.TimeUnit - -import zio._ - -import zio.http._ - -object HelloWorldWithMiddlewares extends ZIOAppDefault { - - val app: HttpApp[Any, Nothing] = Http.collectZIO[Request] { - // this will return result instantly - case Method.GET -> Root / "text" => ZIO.succeed(Response.text("Hello World!")) - // this will return result after 5 seconds, so with 3 seconds timeout it will fail - case Method.GET -> Root / "long-running" => ZIO.succeed(Response.text("Hello World!")).delay(5 seconds) +val userApp = Routes( + Method.GET / "user" / string("name") / "greet" -> handler { (name: String, req: Request) => + Response.text(s"Welcome to the ZIO party! ${name}") } +).toHttpApp - val serverTime: RequestHandlerMiddleware[Nothing, Any, Nothing, Any] = HttpAppMiddleware.patchZIO(_ => - for { - currentMilliseconds <- Clock.currentTime(TimeUnit.MILLISECONDS) - withHeader = Response.Patch.addHeader("X-Time", currentMilliseconds.toString) - } yield withHeader, - ) - val middlewares = - // print debug info about request and response - HttpAppMiddleware.debug ++ - // close connection if request takes more than 3 seconds - HttpAppMiddleware.timeout(3 seconds) ++ - // add static header - HttpAppMiddleware.addHeader("X-Environment", "Dev") ++ - // add dynamic header - serverTime +val basicAuthMW = Middleware.basicAuth("admin", "admin") +val patchEnv = Middleware.addHeader("X-Environment", "Dev") - // Run it like any simple app - val run = Server.serve((app @@ middlewares).withDefaultErrorResponse).provide(Server.default) -} +val appWithMiddleware = userApp @@ (basicAuthMW ++ patchEnv) ``` -**Break down of the code**: -In the code above, the `middlewares` value is a composition of multiple middleware functions using the `++` operator. Each middleware function adds a specific behavior to the HTTP application (`app`) by modifying requests or responses. +Here, we use the `basicAuth` middleware provided by ZIO HTTP to secure the endpoint with basic authentication. Additionally, we add a custom header to the response indicating the environment is "Dev" using the `addHeader` middleware. -The middleware functions used in the example are: +### Advantages of Middleware -- `HttpAppMiddleware.debug`: This middleware logs debug information about the incoming requests and outgoing responses. +Using middleware offers several benefits: -- `HttpAppMiddleware.timeout(3 seconds)`: This middleware closes the connection if the request takes more than 3 seconds, enforcing a timeout. +1. **Readability**: By removing boilerplate code from the core business logic, the application code becomes more readable and easier to understand. -- `HttpAppMiddleware.addHeade("X-Environment", "Dev")`: This middleware adds a static header "X-Environment: Dev" to every response. +2. **Modularity**: Middleware allows us to manage cross-cutting concerns independently, facilitating easier maintenance and updates. -- `serverTime`: This middleware is a custom middleware that adds a dynamic header "X-Time" with the current timestamp to every response. +3. **Testability**: With middleware, we can test concerns independently, which simplifies testing and ensures the core business logic remains clean. -The composition of these middleware functions using the `++` operator creates a new HTTP application (`app @@ middlewares`) that incorporates all the defined middleware behaviors. The resulting application is then served by the server (`Server.serve`) with the addition of a default error response configuration. +4. **Reusability**: Middleware can be composed and reused across different routes and applications, promoting code reuse and consistency. -By using middleware, you can modify the behavior of your HTTP application at various stages of the request-response cycle, such as request preprocessing, response post-processing, error handling, authentication, and more. +Middleware is a powerful concept in ZIO HTTP that enables developers to handle cross-cutting concerns in a modular and organized manner. By composing middleware functions, developers can keep the core business logic clean and focus on writing maintainable, readable, and efficient code. \ No newline at end of file diff --git a/docs/concepts/request-handling.md b/docs/concepts/request-handling.md index 93a8c56ac8..9e8c925019 100644 --- a/docs/concepts/request-handling.md +++ b/docs/concepts/request-handling.md @@ -40,7 +40,7 @@ import zio.http._ object RequestStreaming extends ZIOAppDefault { // Create HTTP route which echos back the request body - val app = Http.collect[Request] { case req @ Method.POST -> Root / "echo" => + val app = Routes(Method.POST / "echo" -> handler { (req: Request) => // Returns a stream of bytes from the request // The stream supports back-pressure val stream = req.body.asStream @@ -50,7 +50,7 @@ object RequestStreaming extends ZIOAppDefault { val data = Body.fromStream(stream) Response(body = data) - } + }).toHttpApp // Run it like any simple app val run: UIO[ExitCode] = @@ -58,19 +58,26 @@ object RequestStreaming extends ZIOAppDefault { } ``` -**Explainaition**: +**Explanation**: -- `app` Definition: The `app` value represents an HttpApp that handles incoming requests. It is defined using the `Http.collect` combinator, which pattern matches on the incoming Request. In this case, it matches a POST request to the `/echo` endpoint. +The code defines an HTTP server application using ZIO and ZIO-HTTP, which handles incoming requests and echoes back the request body : -- Request Matching: The `Http.collect` combinator pattern matches on the request using a partial function. It checks if the request's method is `POST` and if the request path is `/echo`. +1. **Routes and Handler Function**: + - The application uses the `Routes` builder to define HTTP routes and their corresponding handler functions. In this case, there's a single route that matches POST requests to the `/echo` endpoint. + - The handler function takes a `Request` as input, representing the incoming HTTP request. It processes the request body as a stream of bytes using `req.body.asStream`. -- Request Processing: Once a matching rule is found, the code inside the `Http.collect` block is executed. In this case, it retrieves the request body as a stream of bytes using `req.body.asStream`. +2. **Creating HttpData from Stream**: + - The stream of bytes obtained from the request body is used to create an `HttpData` instance. `HttpData` represents the body of an HTTP response and can be created from various sources, including streams, byte arrays, or strings. -- Creating `HttpData`: The code then creates an HttpData instance from the request stream using `Body.fromStream(stream)`. `HttpData` represents the body of an HTTP response and can be created from various sources, such as streams, byte arrays, or strings. +3. **Generating the Response**: + - The handler constructs a `Response` with the created `HttpData` as the response body. This means that the server will echo back the received request body to the client. -- Generating the Response: Finally, the code constructs a `Response` with the created `HttpData` as the body. The response will echo back the received request body. +4. **Converting Routes to HttpApp**: + - The `Routes` instance is transformed into an `HttpApp` using the `toHttpApp` method. This conversion allows the `Routes` to be used as a standard `HttpApp`, compatible with the server. -- Running the Server: The `run` value is responsible for starting the server and handling incoming requests. It uses the `Server.serve` method to serve the app as the main application. The server is provided with the default server configuration using `Server.default`. The `exitCode` method is used to provide an appropriate exit code for the application. +5. **Running the Server**: + - The `run` value is responsible for starting the server and handling incoming requests. It uses the `Server.serve` method to serve the transformed `HttpApp`. + - The server is provided with the default server configuration using `Server.default`. + - The `exitCode` method is used to provide an appropriate exit code for the application. - -Overall, the concept of request handling in ZIO-HTTP revolves around defining HttpApp instances, matching incoming requests, processing them, generating responses, and composing multiple HttpApp instances to build a complete HTTP server application. The functional and composable nature of ZIO allows for a flexible and modular approach to building robust and scalable HTTP services. +Overall, the concept of request handling in ZIO-HTTP revolves around defining HttpApp instances, matching incoming requests, processing them, generating responses, and composing multiple HttpApp instances to build a complete HTTP server application. The functional and composable nature of ZIO allows for a flexible and modular approach to building robust and scalable HTTP services. \ No newline at end of file diff --git a/docs/concepts/routing.md b/docs/concepts/routing.md index 84a8b54256..c83f169df1 100644 --- a/docs/concepts/routing.md +++ b/docs/concepts/routing.md @@ -5,11 +5,14 @@ title: Routing # Routing -ZIO-HTTP provides a powerful and expressive routing system for defining HTTP routes and handling requests. It leverages the capabilities of the ZIO functional programming library to provide a purely functional and composable approach to routing. +In ZIO-HTTP, routes are a fundamental concept used to define how incoming HTTP requests are handled by an API. A route represents a specific combination of an HTTP method and a URL path pattern, along with a corresponding handler function. -In ZIO-HTTP, routing is based on the concept of "endpoints." An endpoint represents a specific combination of an HTTP method, URL path pattern, and input/output types. It defines how to handle incoming requests that match the specified method and path. +Routes are organized into a collection known as a "routing table." The routing table acts as a mapping that decides where to direct each endpoint in the API based on the method and path of the incoming request. Each route in the routing table is associated with a handler function responsible for processing the request and generating an appropriate response. + +To build a collection of routes, you can use the Routes.apply constructor or start with an empty route and gradually add routes to it. The process of defining routes is facilitated by the provided DSL, which allows you to construct route patterns using the `/` operator on the `Method` type. + +Once you have built the routes, you can convert them into an HTTP application using the `toHttpApp` method. This transformation prepares the routes for execution by the ZIO HTTP server. -The core abstraction for defining endpoints in ZIO-HTTP is the Endpoint type, which is built using a DSL (Domain Specific Language) provided by the library. The DSL allows you to define endpoints and combine them together to create more complex routing configurations. Here's an example of how you can define an endpoint using ZIO-HTTP: @@ -19,38 +22,42 @@ package example import zio._ import zio.http.Header.Authorization -import zio.http._ -import zio.http.codec.HttpCodec +import zio.http.codec.{HttpCodec, PathCodec} import zio.http.endpoint._ +import zio.http.{int => _, _} object EndpointExamples extends ZIOAppDefault { import HttpCodec._ + import PathCodec._ val auth = EndpointMiddleware.auth // MiddlewareSpec can be added at the service level as well val getUser = - Endpoint.get("users" / int("userId")).out[Int] @@ auth + Endpoint(Method.GET / "users" / int("userId")).out[Int] @@ auth val getUserRoute = - getUser.implement { id => - ZIO.succeed(id) + getUser.implement { + Handler.fromFunction[Int] { id => + id + } } val getUserPosts = - Endpoint - .get("users" / int("userId") / "posts" / int("postId")) + Endpoint(Method.GET / "users" / int("userId") / "posts" / int("postId")) .query(query("name")) .out[List[String]] @@ auth val getUserPostsRoute = - getUserPosts.implement[Any] { case (id1: Int, id2: Int, query: String) => - ZIO.succeed(List(s"API2 RESULT parsed: users/$id1/posts/$id2?name=$query")) + getUserPosts.implement[Any] { + Handler.fromFunctionZIO[(Int, Int, String)] { case (id1: Int, id2: Int, query: String) => + ZIO.succeed(List(s"API2 RESULT parsed: users/$id1/posts/$id2?name=$query")) + } } - val routes = getUserRoute ++ getUserPostsRoute + val routes = Routes(getUserRoute, getUserPostsRoute) - val app = routes.toApp(auth.implement(_ => ZIO.unit)(_ => ZIO.unit)) + val app = routes.toHttpApp // (auth.implement(_ => ZIO.unit)(_ => ZIO.unit)) val request = Request.get(url = URL.decode("/users/1").toOption.get) @@ -67,8 +74,8 @@ object EndpointExamples extends ZIOAppDefault { val x1 = getUser(42) val x2 = getUserPosts(42, 200, "adam") - val result1: UIO[Int] = executor(x1) - val result2: UIO[List[String]] = executor(x2) + val result1: ZIO[Scope, Nothing, Int] = executor(x1) + val result2: ZIO[Scope, Nothing, List[String]] = executor(x2) result1.zip(result2).debug } @@ -78,28 +85,16 @@ object EndpointExamples extends ZIOAppDefault { In the given example above, the concepts of routing in ZIO-HTTP are demonstrated using the ZIO and zio-http libraries. Let's go through the code to understand these concepts: -- `Endpoint`: An Endpoint represents a specific HTTP endpoint that your application can handle. It consists of a combination of path segments, query parameters, request and response codecs, and middleware. Endpoints define the structure of the HTTP request and response. - -- `EndpointMiddleware`: EndpointMiddleware is a helper object that provides middleware functions for endpoints. Middleware allows you to add additional functionality to your endpoints, such as authentication, logging, error handling, etc. - -- `getUser`: This is an example of an endpoint defined using the Endpoint object. It represents a GET request to the `"/users/{userId}"` path, where `"{userId}"` is a path parameter of type Int. The `@@` operator is used to add middleware (auth in this case) to the endpoint. - -- `getUserRoute`: This defines the implementation of the getUser endpoint. The implementation is a ZIO effect that takes an Int as input and returns a successful ZIO effect with the same Int value. This implementation can be any business logic you want to execute when the endpoint is called. - -- `getUserPosts`: This is another example of an endpoint representing a GET request to the `"/users/{userId}/posts/{postId}"` path. It also includes a query parameter named `"name"`. The response type is `List[String]`. The `@@` operator is used to add the auth middleware to this endpoint as well. - -- `getUserPostsRoute`: This defines the implementation of the getUserPosts endpoint. The implementation takes three input parameters: `id1: Int, id2: Int, and query: String`. It returns a successful ZIO effect that constructs a list of strings based on the input parameters. - -- `routes`: This combines the getUserRoute and getUserPostsRoute endpoints into a single Routes object. The `++` operator is used to concatenate the two routes. +1. **Endpoint Definition**: Endpoints are defined using the `Endpoint` class, representing different routes of the API. Each endpoint is constructed using combinators like `Method.GET`, `int("userId")`, `query("name")`, etc. These combinators help define the HTTP method, path parameters, and query parameters, respectively. -- `app`: This converts the routes object into a ZIO HttpApp by using the toApp method. The toApp method takes an additional auth.implement function, which provides the implementation for the authentication middleware. In this case, it's a simple implementation that returns ZIO.unit. +2. **Middleware**: Middleware is added to the endpoints using the `@@` operator. In this case, the `auth` middleware is added to both `getUser` and `getUserPosts` endpoints using the `@@ auth` syntax. -- `request`: This creates an example HTTP request to the `"/users/1"` path using the Request.get method. +3. **Implementation**: Each endpoint is implemented using the `implement` method. The implementation is provided as a `Handler` or `Handler.fromFunctionZIO`. The former is used when the implementation is a pure function, and the latter is used when the implementation is a ZIO effect. -- `run`: This sets up a server using the Server.serve method, passing in the app and a default server configuration. +4. **Routes**: The defined endpoints are collected into a `Routes` object using the `Routes` constructor. This creates a mapping of endpoint paths to their implementations. -- `ClientExample`: This is an example client code that demonstrates how to interact with the server using the defined endpoints. It uses an EndpointLocator to map endpoint paths to URLs and an EndpointExecutor to execute the endpoints on the server. +5. **Server**: The `Routes` are converted into an `HttpApp` using the `toHttpApp` method. An `HttpApp` is a type alias for `Request => Task[Response]`, representing a function that takes an HTTP request and produces a ZIO task returning an HTTP response. The `Server.serve` method is used to run the `HttpApp` on a default server. -- `example`: This method takes a Client as input and demonstrates how to execute the getUser and getUserPosts endpoints using the EndpointExecutor. It sets up the executor with a basic authentication header and executes the endpoints, resulting in two ZIO effects: `result1 (type UIO[Int])` and `result2 (type UIO[List[String]])`. +6. **ClientExample**: The `ClientExample` object demonstrates how to use the defined endpoints to make HTTP requests to the server. It shows how to create an `EndpointLocator` from the server URL and an `EndpointExecutor` to execute the requests. -The example code shows how to define and implement endpoints using ZIO-HTTP, how to set up a server to handle those endpoints, and how to execute those endpoints using a client. It also demonstrates the use of middleware for authentication (auth), path parameters, query parameters, and response types. +ZIO-HTTP allows you to define endpoints representing HTTP routes and then compose and implement those endpoints to create a complete API. By using middleware, you can add cross-cutting concerns like authentication and authorization to specific endpoints or to the entire API. The `HttpApp` is the final representation of the API, and it can be served on a server to handle incoming HTTP requests. \ No newline at end of file diff --git a/docs/concepts/server.md b/docs/concepts/server.md index 675d6bcedf..df43b44293 100644 --- a/docs/concepts/server.md +++ b/docs/concepts/server.md @@ -7,19 +7,31 @@ title: Server The concept of a server in ZIO-HTTP revolves around handling incoming HTTP requests and producing corresponding HTTP responses. The server is responsible for listening on a specific port, accepting incoming connections, and routing requests to appropriate handlers based on their HTTP method and path. -ZIO-HTTP provides a simple and composable DSL for defining HTTP servers using the `Http` type. The `Http` type represents an HTTP route or endpoint that can handle incoming requests and produce responses. Servers in ZIO-HTTP are created by defining an Http route and then using the `Server.serve` method to start the server and bind it to a specific port. +ZIO-HTTP provides a simple and composable DSL for defining HTTP servers using the `Http` type. The `Http` type represents an HTTP route or endpoint that can handle incoming requests and produce responses. Servers in ZIO-HTTP are created by defining an HTTP route and then using the `Server.serve` method to start the server and bind it to a specific port. Here are the key components involved in the server concept in ZIO-HTTP: -- HTTP Route: A route is defined using the `Http.collect` method, which takes a partial function mapping requests to their corresponding responses. The partial function matches on the HTTP method and path of the request and returns a `Response` for each matched request. +1. **HTTP Route**: + - A route is defined using the `Http.collect` method, which takes a partial function mapping requests to their corresponding responses. + - The partial function matches on the HTTP method and path of the request and returns a `Response` for each matched request. + - This allows developers to define multiple routes and their corresponding handlers to handle different types of requests. -- Server Configuration: The server configuration includes information such as the host, port, SSL settings, and other options. In ZIO-HTTP, the server configuration is provided through the `ServerConfig` type, which can be customized as needed. +2. **Server Configuration**: + - The server configuration includes information such as the host, port, SSL settings, and other options. + - In ZIO-HTTP, the server configuration is provided through the `ServerConfig` type, which can be customized as needed. + - This allows developers to configure the server to meet specific requirements, such as enabling HTTPS or adjusting thread pool sizes. -- Starting the Server: The `Server.serve` method is used to start the server by providing it with the HTTP route and the server configuration. This method creates a ZIO effect that represents the running server. The server will listen for incoming connections and route the requests to the appropriate handlers defined in the route. +3. **Starting the Server**: + - The `Server.serve` method is used to start the server by providing it with the HTTP route and the server configuration. + - This method creates a ZIO effect that represents the running server. The server will listen for incoming connections and route the requests to the appropriate handlers defined in the route. + - The server is started asynchronously and can run indefinitely until explicitly shut down. -- Server Environment: ZIO-HTTP leverages the ZIO library, which uses an environment-based approach for dependency injection. The server environment consists of the necessary services and resources required for the server to operate, such as the event loop, clock, console, and any other dependencies needed by the defined routes. +4. **Server Environment**: + - ZIO-HTTP leverages the ZIO library, which uses an environment-based approach for dependency injection. + - The server environment consists of the necessary services and resources required for the server to operate, such as the event loop, clock, console, and any other dependencies needed by the defined routes. + - This ensures that the server has access to all the required resources and allows for easy testing and mocking of dependencies. -By combining these components, ZIO-HTTP allows you to define and run servers that handle incoming HTTP requests and produce HTTP responses. The server concept in ZIO-HTTP emphasizes composability, type-safety, and a functional programming approach, making it easier to build robust and scalable HTTP servers in a purely functional manner. +By combining these components, ZIO-HTTP allows you to define and run servers that handle incoming HTTP requests and produce HTTP responses. The server concept in ZIO-HTTP emphasizes composability, type-safety, and a functional programming approach, making it easier to build robust and scalable HTTP servers in a purely functional manner. The flexible and composable nature of ZIO-HTTP enables developers to create sophisticated and high-performance servers with ease. Here is an example: diff --git a/docs/examples/basic/http-client.md b/docs/examples/basic/http-client.md deleted file mode 100644 index c664a8cf4c..0000000000 --- a/docs/examples/basic/http-client.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -id: http-client -title: Http Client Example -sidebar_label: Http Client ---- - -```scala mdoc:silent -import zio._ - -import zio.http._ - -object SimpleClient extends ZIOAppDefault { - val url = URL.decode("http://sports.api.decathlon.com/groups/water-aerobics").toOption.get - - val program = for { - client <- ZIO.service[Client] - res <- client.url(url).get("/") - data <- res.body.asString - _ <- Console.printLine(data) - } yield () - - override val run = program.provide(Client.default, Scope.default) - -} - -``` \ No newline at end of file diff --git a/docs/examples/basic/websocket.md b/docs/examples/basic/websocket.md deleted file mode 100644 index 21f6cdae62..0000000000 --- a/docs/examples/basic/websocket.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: websocket -title: WebSocket Example -sidebar_label: WebSocket ---- - -```scala mdoc:silent -import zio._ - -import zio.http.ChannelEvent.Read -import zio.http._ -import zio.http.codec.PathCodec.string - -object WebSocketEcho extends ZIOAppDefault { - private val socketApp: SocketApp[Any] = - Handler.webSocket { channel => - channel.receiveAll { - case Read(WebSocketFrame.Text("FOO")) => - channel.send(Read(WebSocketFrame.Text("BAR"))) - case Read(WebSocketFrame.Text("BAR")) => - channel.send(Read(WebSocketFrame.Text("FOO"))) - case Read(WebSocketFrame.Text(text)) => - channel.send(Read(WebSocketFrame.Text(text))).repeatN(10) - case _ => - ZIO.unit - } - } - - private val app: HttpApp[Any] = - Routes( - Method.GET / "greet" / string("name") -> handler { (name: String, req: Request) => - Response.text(s"Greetings {$name}!") - }, - Method.GET / "subscriptions" -> handler(socketApp.toResponse), - ).toHttpApp - - override val run = Server.serve(app).provide(Server.default) -} - -``` diff --git a/docs/getting-started.md b/docs/getting-started.md index da48bbbf98..fa99a6167d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,195 +1,54 @@ --- -id: getting-started -title: Getting Started +id: index +title: "Introduction to ZIO Http" +sidebar_label: "ZIO Http" --- -**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 is a scala library for building http apps. It is powered by ZIO and [Netty](https://netty.io/) and aims at being the defacto solution for writing, highly scalable and performant web applications using idiomatic Scala. -ZIO HTTP has powerful functional domains that help in creating, modifying, composing apps easily. Let's start with the HTTP domain. +@PROJECT_BADGES@ -The first step when using ZIO HTTP is creating an HTTP app. +## Installation -## Http and Handler +Setup via `build.sbt`: -`Handler` describes the transformation from an incoming `Request` to an outgoing `Response` type. The `Http` -type on top if this provides input-dependent routing to different `Handler` values. There are some default -handler constructors such as `Handler.text`, `Handler.html`, `Handler.fromFile`, `Handler.fromData`, `Handler.fromStream`, `Handler.fromEffect`. - -A `Handler` can always be transformed to a `HttpApp` value using the `.toHttpApp` method, in which case the -HTTP application will handle all routes. - -### 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 mdoc:silent -import zio.http._ - -val app = Handler.text("Hello World!").toHttpApp -``` - -An application can be made using any of the available operators on `zio.Http`. In the above program for any Http request, the response is always `"Hello World!"`. - -### Routing - -For handling routes, ZIO HTTP has a `Routes` value, which allows you to aggregate a collection of -individual routes. - -Behind the scenes, ZIO HTTP builds an efficient prefix-tree whenever needed to optimize dispatch. - -The example below shows how to create routes: - -```scala mdoc:silent:reset -import zio.http._ - -val routes = Routes( - Method.GET / "fruits" / "a" -> handler(Response.text("Apple")), - Method.GET / "fruits" / "b" -> handler(Response.text("Banana")) -) -``` - -You can create parameterized routes as well: - -```scala mdoc:silent:reset -import zio.http._ - -val routes = Routes( - Method.GET / "Apple" / int("count") -> - handler { (count: Int, req: Request) => - Response.text(s"Apple: $count") - } -) +```scala +libraryDependencies += "dev.zio" %% "zio-http" % "@VERSION@" ``` -### Composition +**NOTES ON VERSIONING:** -Routes can be composed using the `++` operator, which works by combining the routes. +- Older library versions `1.x` or `2.x` with organization `io.d11` of ZIO Http are derived from Dream11, the organization that donated ZIO Http to the ZIO organization in 2022. +- Newer library versions, starting in 2023 and resulting from the ZIO organization (`dev.zio`) started with `0.0.x`, reaching `1.0.0` release candidates in April of 2023 -```scala mdoc:silent:reset -import zio.http._ +## Getting Started -val a = Routes(Method.GET / "a" -> Handler.ok) -val b = Routes(Method.GET / "b" -> Handler.ok) +A simple Http server can be built using a few lines of code. -val routes = a ++ b -``` - -### ZIO Integration - -For creating effectful apps, you can use handlers that return ZIO effects: - -```scala mdoc:silent:reset -import zio.http._ +```scala mdoc:silent import zio._ - -val routes = Routes( - Method.GET / "hello" -> handler(ZIO.succeed(Response.text("Hello World"))) -) -``` - -### 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(_)) - } -) -``` - -### Testing -You can run `HttpApp` as a function of `A => ZIO[R, Response, Response]` to test it by using the `runZIO` method. - -```scala mdoc:silent:reset -import zio.test._ -import zio.test.Assertion.equalTo -import zio.http._ +object HelloWorld extends ZIOAppDefault { -object Spec extends ZIOSpecDefault { + val app: HttpApp[Any] = + Routes( + Method.GET / "text" -> handler(Response.text("Hello World!")) + ).toHttpApp - def spec = suite("http")( - test("should be ok") { - val app = Handler.ok.toHttpApp - val req = Request.get(URL(Root)) - assertZIO(app.runZIO(req))(equalTo(Response.ok)) - } - ) + override val run = + Server.serve(app).provide(Server.default) } ``` -## 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 `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) - ) -``` - -## 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 your server. The server can be configured according to the leak detection level, request size, address etc. - -### Starting an HTTP App +## Steps to run an example -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`. +1. Edit the [RunSettings](https://github.com/zio/zio-http/blob/main/project/BuildHelper.scala#L107) - modify `className` to the example you'd like to run. +2. From sbt shell, run `~example/reStart`. You should see `Server started on port: 8080`. +3. Send curl request for defined `http Routes`, for eg : `curl -i "http://localhost:8080/text"` for `example.HelloWorld`. -```scala mdoc:silent:reset -import zio.http._ -import zio._ - -object HelloWorld extends ZIOAppDefault { - val app = Handler.ok.toHttpApp - - override def run = - Server.serve(app).provide(Server.defaultWithPort(8090)) -} -``` +## Watch Mode -## Examples +You can use the [sbt-revolver] plugin to start the server and run it in watch mode using `~ reStart` command on the SBT console. -- [HTTP Server](https://zio.github.io/zio-http/docs/v1.x/examples/zio-http-basic-examples/http_server) -- [Advanced Server](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/advanced_server) -- [WebSocket Server](https://zio.github.io/zio-http/docs/v1.x/examples/zio-http-basic-examples/web-socket) -- [Streaming Response](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/stream-response) -- [HTTP Client](https://zio.github.io/zio-http/docs/v1.x/examples/zio-http-basic-examples/http_client) -- [File Streaming](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/stream-file) -- [Authentication](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/authentication) +[sbt-revolver]: https://github.com/spray/sbt-revolver \ No newline at end of file diff --git a/docs/how-to-guides/http-client.md b/docs/how-to-guides/http-client.md index 53a2de8966..447b0baa59 100644 --- a/docs/how-to-guides/http-client.md +++ b/docs/how-to-guides/http-client.md @@ -7,6 +7,7 @@ title: HTTP Client This example provided demonstrates how to perform an HTTP client request using the zio-http library in Scala with ZIO. + ## `build.sbt` Setup ```scala @@ -22,20 +23,21 @@ libraryDependencies ++= Seq( ```scala import zio._ -import zio.http.Client + +import zio.http._ object SimpleClient extends ZIOAppDefault { - val url = "http://sports.api.decathlon.com/groups/water-aerobics" + val url = URL.decode("http://sports.api.decathlon.com/groups/water-aerobics").toOption.get val program = for { - res <- Client.request(Request.get(url)) - data <- res.body.asString - _ <- Console.printLine(data).catchAll(e => ZIO.logError(e.getMessage)) + client <- ZIO.service[Client] + res <- client.url(url).get("/") + data <- res.body.asString + _ <- Console.printLine(data) } yield () override val run = program.provide(Client.default, Scope.default) - } ``` diff --git a/docs/how-to-guides/multipart-form-data.md b/docs/how-to-guides/multipart-form-data.md index 9c1b89c3d3..a1e3652bb0 100644 --- a/docs/how-to-guides/multipart-form-data.md +++ b/docs/how-to-guides/multipart-form-data.md @@ -16,39 +16,42 @@ import zio.http._ object MultipartFormData extends ZIOAppDefault { - private val app: App[Any] = - Http.collectZIO[Request] { - case req @ Method.POST -> Root / "upload" - if req.header(Header.ContentType).exists(_.mediaType == MediaType.multipart.`form-data`) => - for { - form <- req.body.asMultipartForm - .mapError(ex => - Response( - Status.InternalServerError, - body = Body.fromString(s"Failed to decode body as multipart/form-data (${ex.getMessage}"), - ), - ) - response <- form.get("file") match { - case Some(file) => - file match { - case FormField.Binary(_, data, contentType, transferEncoding, filename) => - ZIO.succeed( - Response.text( - s"Received ${data.length} bytes of $contentType filename $filename and transfer encoding $transferEncoding", - ), - ) - case _ => - ZIO.fail( - Response(Status.BadRequest, body = Body.fromString("Parameter 'file' must be a binary file")), - ) + private val app: HttpApp[Any] = + Routes( + Method.POST / "upload" -> + handler { (req: Request) => + if (req.header(Header.ContentType).exists(_.mediaType == MediaType.multipart.`form-data`)) + for { + form <- req.body.asMultipartForm + .mapError(ex => + Response( + Status.InternalServerError, + body = Body.fromString(s"Failed to decode body as multipart/form-data (${ex.getMessage}"), + ), + ) + response <- form.get("file") match { + case Some(file) => + file match { + case FormField.Binary(_, data, contentType, transferEncoding, filename) => + ZIO.succeed( + Response.text( + s"Received ${data.length} bytes of $contentType filename $filename and transfer encoding $transferEncoding", + ), + ) + case _ => + ZIO.fail( + Response(Status.BadRequest, body = Body.fromString("Parameter 'file' must be a binary file")), + ) + } + case None => + ZIO.fail(Response(Status.BadRequest, body = Body.fromString("Missing 'file' from body"))) } - case None => - ZIO.fail(Response(Status.BadRequest, body = Body.fromString("Missing 'file' from body"))) - } - } yield response - } + } yield response + else ZIO.succeed(Response(status = Status.NotFound)) + }, + ).sandbox.toHttpApp - private def program: ZIO[Client with Server, Throwable, Unit] = + private def program: ZIO[Client with Server with Scope, Throwable, Unit] = for { port <- Server.install(app) _ <- ZIO.logInfo(s"Server started on port $port") @@ -56,8 +59,7 @@ object MultipartFormData extends ZIOAppDefault { response <- client .host("localhost") .port(port) - .post( - "/upload", + .post("/upload")( Body.fromMultipartForm( Form( FormField.binaryField( @@ -80,6 +82,7 @@ object MultipartFormData extends ZIOAppDefault { .provide( Server.default, Client.default, + Scope.default, ) } ``` \ No newline at end of file diff --git a/docs/how-to-guides/websocket.md b/docs/how-to-guides/websocket.md index db76740b29..b4de284a66 100644 --- a/docs/how-to-guides/websocket.md +++ b/docs/how-to-guides/websocket.md @@ -3,7 +3,6 @@ id: websocket title: Websocket --- - This code sets up a WebSocket echo server and creates an HTTP server that handles WebSocket connections and echoes back received messages.
@@ -13,30 +12,30 @@ import zio._ import zio.http.ChannelEvent.Read import zio.http._ +import zio.http.codec.PathCodec.string object WebSocketEcho extends ZIOAppDefault { private val socketApp: SocketApp[Any] = Handler.webSocket { channel => channel.receiveAll { case Read(WebSocketFrame.Text("FOO")) => - channel.send(Read(WebSocketFrame.text("BAR"))) - + channel.send(Read(WebSocketFrame.Text("BAR"))) case Read(WebSocketFrame.Text("BAR")) => - channel.send(Read(WebSocketFrame.text("FOO"))) - - case Read(WebSocketFrame.Text(text)) => - channel.send(Read(WebSocketFrame.text(text))).repeatN(10) - - case _ => + channel.send(Read(WebSocketFrame.Text("FOO"))) + case Read(WebSocketFrame.Text(text)) => + channel.send(Read(WebSocketFrame.Text(text))).repeatN(10) + case _ => ZIO.unit } } - private val app: Http[Any, Nothing, Request, Response] = - Http.collectZIO[Request] { - case Method.GET -> Root / "greet" / name => ZIO.succeed(Response.text(s"Greetings {$name}!")) - case Method.GET -> Root / "subscriptions" => socketApp.toResponse - } + private val app: HttpApp[Any] = + Routes( + Method.GET / "greet" / string("name") -> handler { (name: String, req: Request) => + Response.text(s"Greetings {$name}!") + }, + Method.GET / "subscriptions" -> handler(socketApp.toResponse), + ).toHttpApp override val run = Server.serve(app).provide(Server.default) } @@ -46,10 +45,12 @@ object WebSocketEcho extends ZIOAppDefault {

-## Advanced WebSocket server +### Advanced WebSocket server -```scala +This code sets up an HTTP server with WebSocket support. Clients can establish WebSocket connections to the server and perform various actions based on the received WebSocket messages. + +```scala import zio._ import zio.http.ChannelEvent.{ExceptionCaught, Read, UserEvent, UserEventTriggered} From 6053b839d7cfcd0163beb7f75d6bebe6e44573f5 Mon Sep 17 00:00:00 2001 From: daveads Date: Tue, 8 Aug 2023 12:37:34 +0100 Subject: [PATCH 49/71] websocket -> how-to-guide --- docs/examples/basic/websocket.md | 40 -------------------------------- docs/how-to-guides/websocket.md | 6 ++--- 2 files changed, 3 insertions(+), 43 deletions(-) delete mode 100644 docs/examples/basic/websocket.md diff --git a/docs/examples/basic/websocket.md b/docs/examples/basic/websocket.md deleted file mode 100644 index 045f3e4f6b..0000000000 --- a/docs/examples/basic/websocket.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: websocket -title: WebSocket Example -sidebar_label: WebSocket ---- - -```scala mdoc:silent -import zio._ - -import zio.http.ChannelEvent.Read -import zio.http._ -import zio.http.codec.PathCodec.string - -object WebSocketEcho extends ZIOAppDefault { - private val socketApp: WebSocketApp[Any] = - Handler.webSocket { channel => - channel.receiveAll { - case Read(WebSocketFrame.Text("FOO")) => - channel.send(Read(WebSocketFrame.Text("BAR"))) - case Read(WebSocketFrame.Text("BAR")) => - channel.send(Read(WebSocketFrame.Text("FOO"))) - case Read(WebSocketFrame.Text(text)) => - channel.send(Read(WebSocketFrame.Text(text))).repeatN(10) - case _ => - ZIO.unit - } - } - - private val app: HttpApp[Any] = - Routes( - Method.GET / "greet" / string("name") -> handler { (name: String, req: Request) => - Response.text(s"Greetings {$name}!") - }, - Method.GET / "subscriptions" -> handler(socketApp.toResponse), - ).toHttpApp - - override val run = Server.serve(app).provide(Server.default) -} - -``` diff --git a/docs/how-to-guides/websocket.md b/docs/how-to-guides/websocket.md index e70af926c1..83222ba67d 100644 --- a/docs/how-to-guides/websocket.md +++ b/docs/how-to-guides/websocket.md @@ -7,7 +7,7 @@ This code sets up a WebSocket echo server and creates an HTTP server that handle
-```scala +```scala mdoc:silent import zio._ import zio.http.ChannelEvent.Read @@ -15,7 +15,7 @@ import zio.http._ import zio.http.codec.PathCodec.string object WebSocketEcho extends ZIOAppDefault { - private val socketApp: SocketApp[Any] = + private val socketApp: WebSocketApp[Any] = Handler.webSocket { channel => channel.receiveAll { case Read(WebSocketFrame.Text("FOO")) => @@ -50,7 +50,7 @@ object WebSocketEcho extends ZIOAppDefault { This code sets up an HTTP server with WebSocket support. Clients can establish WebSocket connections to the server and perform various actions based on the received WebSocket messages. -```scala +```scala mdoc:silent import zio._ import zio.http.ChannelEvent.{ExceptionCaught, Read, UserEvent, UserEventTriggered} From 05592e575df20f06ba9020abf38721b91ed18fba Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Tue, 5 Sep 2023 10:46:13 +0000 Subject: [PATCH 50/71] generateReadme --- README.md | 168 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 597d02e9da..be3fa9b4f4 100644 --- a/README.md +++ b/README.md @@ -6,52 +6,178 @@ ZIO HTTP is a scala library for building http apps. It is powered by ZIO and [Netty](https://netty.io/) and aims at being the defacto solution for writing, highly scalable and performant web applications using idiomatic Scala. -[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Releases](https://img.shields.io/nexus/r/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Release)](https://oss.sonatype.org/content/repositories/releases/dev/zio/zio-http_2.13/) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![javadoc](https://javadoc.io/badge2/dev.zio/zio-http-docs_2.13/javadoc.svg)](https://javadoc.io/doc/dev.zio/zio-http-docs_2.13) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) +[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) ## Installation Setup via `build.sbt`: ```scala -libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0-RC2" +package example + +import zio._ + +import zio.http._ + +object RequestStreaming extends ZIOAppDefault { + + val app: HttpApp[Any] = + Routes( + Method.GET / "text" -> handler(Response.text("Hello World!")) + ).toHttpApp + + // Creating HttpData from the stream + // This works for file of any size + val data = Body.fromStream(stream) + + Response(body = data) + } + + // Run it like any simple app + val run: UIO[ExitCode] = + Server.serve(app).provide(Server.default).exitCode +} ``` -**NOTES ON VERSIONING:** +ZIO-HTTP provides a core library, `zhttp-http`, which includes the base HTTP implementation and server/client capabilities based on ZIO. Additional functionality like serverless support, templating, and websockets are available through separate add-on modules. ZIO-HTTP applications can be easily integrated into different deployment platforms, such as server-based, serverless, or compiled to native binaries. + +The principles of ZIO-HTTP are: + +- Application as a Function: HTTP services in ZIO-HTTP are composed of simple functions. The `HttpApp` type represents a function from an `HttpRequest` to a `ZIO` effect that produces an `HttpResponse`. +- Immutability: Entities in ZIO-HTTP are immutable by default, promoting functional programming principles. +- Symmetric: The same `HttpApp` interface is used for both defining HTTP services and making HTTP requests. This enables easy testing and integration of services without requiring an HTTP container. +- Minimal Dependencies: The core `zhttp-http` module has minimal dependencies, and additional add-on modules only include dependencies required for specific functionality. +- Testability: ZIO-HTTP supports easy in-memory and port-based testing of individual endpoints, applications, websockets/SSE, and complete suites of microservices. +- Portability: ZIO-HTTP applications are portable across different deployment platforms, making them versatile and adaptable. + +By leveraging the power of ZIO and the simplicity of functional programming, ZIO-HTTP provides a robust and flexible toolkit for building scalable and composable HTTP services in Scala. + +## Quickstart + +Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart](https://github.com/zio/zio-http/tree/main/zio-http-example) guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. + +## Module feature overview + +Core: + +- Lightweight and performant HTTP handler and message objects +- Powerful routing system with support for path-based and parameterized routes +- Typesafe HTTP message construction and deconstruction +- Extensible filters for common HTTP functionalities such as caching, compression, and request/response logging +- Support for cookie handling +- Servlet implementation for integration with Servlet containers +- Built-in support for launching applications with an embedded server backend + +Client: + +- Robust and flexible HTTP client with support for synchronous and asynchronous operations +- Adapters for popular HTTP client libraries such as Apache HttpClient, OkHttp, and Jetty HttpClient +- Websocket client with blocking and non-blocking modes +- GraphQL client integration for consuming GraphQL APIs + +Server: + +- Lightweight server backend spin-up for various platforms including Apache, Jetty, Netty, and SunHttp +- Support for SSE (Server-Sent Events) and Websocket communication +- Easy customization of underlying server backend +- Native-friendly for compilation with GraalVM and Quarkus + +Serverless: + +- Function-based support for building serverless HTTP and event-driven applications +- Adapters for AWS Lambda, Google Cloud Functions, Azure Functions, and other serverless platforms +- Custom AWS Lambda runtime for improved performance and reduced startup time -- Older library versions `1.x` or `2.x` with organization `io.d11` of ZIO Http are derived from Dream11, the organization that donated ZIO Http to the ZIO organization in 2022. -- Newer library versions, starting in 2023 and resulting from the ZIO organization (`dev.zio`) started with `0.0.x`, reaching `1.0.0` release candidates in April of 2023 +Contract: -## Getting Started +- Typesafe HTTP contract definition with support for path parameters, query parameters, headers, and request/response bodies +- Automatic validation of incoming requests based on contract definition +- Self-documenting routes with built-in support for OpenAPI (Swagger) descriptions -A simple Http server can be built using a few lines of code. +Templating: + +- Pluggable templating system support for popular template engines such as Dust, Freemarker, Handlebars, and Thymeleaf +- Caching and hot-reload template support for efficient rendering + +Message Formats: + +- First-class support for various message formats such as JSON, XML, YAML, and CSV +- Seamless integration with popular libraries like Jackson, Gson, and Moshi for automatic marshalling and unmarshalling + +Resilience: + +- Integration with Resilience4J for implementing resilience patterns such as circuit breakers, retries, rate-limiting, and bulkheading + +Metrics: + +- Support for integrating zio-http applications with Micrometer for monitoring and metrics collection + +Security: + +- OAuth support for implementing authorization flows with popular providers like Auth0, Google, Facebook, and more +- Digest authentication support for secure client-server communication + +Cloud Native: + +- Tooling and utilities for operating zio-http applications in cloud environments such as Kubernetes and CloudFoundry +- Support for 12-factor configuration, dual-port servers, and health checks + +Testing: + +- Approval testing extensions for testing zio-http Request and Response messages +- Chaos testing API for injecting failure modes and evaluating application behavior under different failure conditions +- Matchers for popular testing frameworks like Hamkrest, Kotest, and Strikt + +Service Virtualization: + +- Record and replay HTTP contracts to simulate virtualized services using Servirtium Markdown format +- Includes Servirtium MiTM (Man-in-the-Middle) server for capturing and replaying HTTP interactions + +WebDriver: + +- Lightweight implementation of Selenium WebDriver for testing zio-http applications + +These features provide a comprehensive set of tools and capabilities for building scalable, performant, and secure HTTP applications with zio-http. + +## Example + +This brief illustration is intended to showcase the ease and capabilities of zio-http. Additionally, refer to the quickstart guide for a minimalistic starting point that demonstrates serving and consuming HTTP services with dynamic routing. + +To install, add these dependencies to your `build.sbt`: ```scala +package example + import zio._ +import zio.http.HttpAppMiddleware.basicAuth import zio.http._ -object HelloWorld extends ZIOAppDefault { +object BasicAuth extends ZIOAppDefault { - val app: HttpApp[Any] = - Routes( - Method.GET / "text" -> handler(Response.text("Hello World!")) - ).toHttpApp + // Define an HTTP application that requires a JWT claim + val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => + Response.text(s"Welcome to the ZIO party! ${name}") + } + + // Compose all the HttpApps together + val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") - override val run = - Server.serve(app).provide(Server.default) + // Run the application like any simple app + val run = Server.serve(app).provide(Server.default) } ``` -## Steps to run an example +## Explanation of the code above + +- The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. -1. Edit the [RunSettings](https://github.com/zio/zio-http/blob/main/project/BuildHelper.scala#L107) - modify `className` to the example you'd like to run. -2. From sbt shell, run `~example/reStart`. You should see `Server started on port: 8080`. -3. Send curl request for defined `http Routes`, for eg : `curl -i "http://localhost:8080/text"` for `example.HelloWorld`. +- The code imports the necessary dependencies from ZIO and ZIO HTTP. -## Watch Mode +- The user value represents an HTTP application that requires a JWT claim. It uses the Http.collect combinator to pattern match on GET requests with a specific path pattern (Root / "user" / name / "greet") and responds with a greeting message that includes the extracted name. -You can use the [sbt-revolver] plugin to start the server and run it in watch mode using `~ reStart` command on the SBT console. +- The app value is created by composing the user HTTP application with the basicAuth middleware. The basicAuth function takes a username and password as arguments and returns a middleware that performs basic authentication. It applies basic authentication with the username "admin" and password "admin" to the user application. -[sbt-revolver]: https://github.com/spray/sbt-revolver +- Finally, the server is run using the Server.serve method. The app is provided as the HTTP application, and Server.default is provided as the server configuration. The server configuration contains default settings for the server, such as the port to listen on. The run value represents the execution of the server. It starts the ZIO runtime and executes the server, making it ready to receive and respond to HTTP requests. ## Documentation From 863a0e2112e847502705dc83e7810175ef74dc44 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 27 Sep 2023 07:35:34 -0400 Subject: [PATCH 51/71] generateReadme --- README.md | 166 +++++++----------------------------------------------- 1 file changed, 20 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index be3fa9b4f4..550e24c506 100644 --- a/README.md +++ b/README.md @@ -13,171 +13,45 @@ ZIO HTTP is a scala library for building http apps. It is powered by ZIO and [Ne Setup via `build.sbt`: ```scala -package example - -import zio._ - -import zio.http._ - -object RequestStreaming extends ZIOAppDefault { - - val app: HttpApp[Any] = - Routes( - Method.GET / "text" -> handler(Response.text("Hello World!")) - ).toHttpApp - - // Creating HttpData from the stream - // This works for file of any size - val data = Body.fromStream(stream) - - Response(body = data) - } - - // Run it like any simple app - val run: UIO[ExitCode] = - Server.serve(app).provide(Server.default).exitCode -} +libraryDependencies += "dev.zio" %% "zio-http" % "" ``` -ZIO-HTTP provides a core library, `zhttp-http`, which includes the base HTTP implementation and server/client capabilities based on ZIO. Additional functionality like serverless support, templating, and websockets are available through separate add-on modules. ZIO-HTTP applications can be easily integrated into different deployment platforms, such as server-based, serverless, or compiled to native binaries. - -The principles of ZIO-HTTP are: - -- Application as a Function: HTTP services in ZIO-HTTP are composed of simple functions. The `HttpApp` type represents a function from an `HttpRequest` to a `ZIO` effect that produces an `HttpResponse`. -- Immutability: Entities in ZIO-HTTP are immutable by default, promoting functional programming principles. -- Symmetric: The same `HttpApp` interface is used for both defining HTTP services and making HTTP requests. This enables easy testing and integration of services without requiring an HTTP container. -- Minimal Dependencies: The core `zhttp-http` module has minimal dependencies, and additional add-on modules only include dependencies required for specific functionality. -- Testability: ZIO-HTTP supports easy in-memory and port-based testing of individual endpoints, applications, websockets/SSE, and complete suites of microservices. -- Portability: ZIO-HTTP applications are portable across different deployment platforms, making them versatile and adaptable. - -By leveraging the power of ZIO and the simplicity of functional programming, ZIO-HTTP provides a robust and flexible toolkit for building scalable and composable HTTP services in Scala. - -## Quickstart - -Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart](https://github.com/zio/zio-http/tree/main/zio-http-example) guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. - -## Module feature overview - -Core: - -- Lightweight and performant HTTP handler and message objects -- Powerful routing system with support for path-based and parameterized routes -- Typesafe HTTP message construction and deconstruction -- Extensible filters for common HTTP functionalities such as caching, compression, and request/response logging -- Support for cookie handling -- Servlet implementation for integration with Servlet containers -- Built-in support for launching applications with an embedded server backend - -Client: - -- Robust and flexible HTTP client with support for synchronous and asynchronous operations -- Adapters for popular HTTP client libraries such as Apache HttpClient, OkHttp, and Jetty HttpClient -- Websocket client with blocking and non-blocking modes -- GraphQL client integration for consuming GraphQL APIs - -Server: - -- Lightweight server backend spin-up for various platforms including Apache, Jetty, Netty, and SunHttp -- Support for SSE (Server-Sent Events) and Websocket communication -- Easy customization of underlying server backend -- Native-friendly for compilation with GraalVM and Quarkus - -Serverless: - -- Function-based support for building serverless HTTP and event-driven applications -- Adapters for AWS Lambda, Google Cloud Functions, Azure Functions, and other serverless platforms -- Custom AWS Lambda runtime for improved performance and reduced startup time +**NOTES ON VERSIONING:** -Contract: +- Older library versions `1.x` or `2.x` with organization `io.d11` of ZIO Http are derived from Dream11, the organization that donated ZIO Http to the ZIO organization in 2022. +- Newer library versions, starting in 2023 and resulting from the ZIO organization (`dev.zio`) started with `0.0.x`, reaching `1.0.0` release candidates in April of 2023 -- Typesafe HTTP contract definition with support for path parameters, query parameters, headers, and request/response bodies -- Automatic validation of incoming requests based on contract definition -- Self-documenting routes with built-in support for OpenAPI (Swagger) descriptions +## Getting Started -Templating: - -- Pluggable templating system support for popular template engines such as Dust, Freemarker, Handlebars, and Thymeleaf -- Caching and hot-reload template support for efficient rendering - -Message Formats: - -- First-class support for various message formats such as JSON, XML, YAML, and CSV -- Seamless integration with popular libraries like Jackson, Gson, and Moshi for automatic marshalling and unmarshalling - -Resilience: - -- Integration with Resilience4J for implementing resilience patterns such as circuit breakers, retries, rate-limiting, and bulkheading - -Metrics: - -- Support for integrating zio-http applications with Micrometer for monitoring and metrics collection - -Security: - -- OAuth support for implementing authorization flows with popular providers like Auth0, Google, Facebook, and more -- Digest authentication support for secure client-server communication - -Cloud Native: - -- Tooling and utilities for operating zio-http applications in cloud environments such as Kubernetes and CloudFoundry -- Support for 12-factor configuration, dual-port servers, and health checks - -Testing: - -- Approval testing extensions for testing zio-http Request and Response messages -- Chaos testing API for injecting failure modes and evaluating application behavior under different failure conditions -- Matchers for popular testing frameworks like Hamkrest, Kotest, and Strikt - -Service Virtualization: - -- Record and replay HTTP contracts to simulate virtualized services using Servirtium Markdown format -- Includes Servirtium MiTM (Man-in-the-Middle) server for capturing and replaying HTTP interactions - -WebDriver: - -- Lightweight implementation of Selenium WebDriver for testing zio-http applications - -These features provide a comprehensive set of tools and capabilities for building scalable, performant, and secure HTTP applications with zio-http. - -## Example - -This brief illustration is intended to showcase the ease and capabilities of zio-http. Additionally, refer to the quickstart guide for a minimalistic starting point that demonstrates serving and consuming HTTP services with dynamic routing. - -To install, add these dependencies to your `build.sbt`: +A simple Http server can be built using a few lines of code. ```scala -package example - import zio._ -import zio.http.HttpAppMiddleware.basicAuth import zio.http._ -object BasicAuth extends ZIOAppDefault { - - // Define an HTTP application that requires a JWT claim - val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => - Response.text(s"Welcome to the ZIO party! ${name}") - } +object HelloWorld extends ZIOAppDefault { - // Compose all the HttpApps together - val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") + val app: HttpApp[Any] = + Routes( + Method.GET / "text" -> handler(Response.text("Hello World!")) + ).toHttpApp - // Run the application like any simple app - val run = Server.serve(app).provide(Server.default) + override val run = + Server.serve(app).provide(Server.default) } ``` -## Explanation of the code above - -- The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. +## Steps to run an example -- The code imports the necessary dependencies from ZIO and ZIO HTTP. +1. Edit the [RunSettings](https://github.com/zio/zio-http/blob/main/project/BuildHelper.scala#L107) - modify `className` to the example you'd like to run. +2. From sbt shell, run `~example/reStart`. You should see `Server started on port: 8080`. +3. Send curl request for defined `http Routes`, for eg : `curl -i "http://localhost:8080/text"` for `example.HelloWorld`. -- The user value represents an HTTP application that requires a JWT claim. It uses the Http.collect combinator to pattern match on GET requests with a specific path pattern (Root / "user" / name / "greet") and responds with a greeting message that includes the extracted name. +## Watch Mode -- The app value is created by composing the user HTTP application with the basicAuth middleware. The basicAuth function takes a username and password as arguments and returns a middleware that performs basic authentication. It applies basic authentication with the username "admin" and password "admin" to the user application. +You can use the [sbt-revolver] plugin to start the server and run it in watch mode using `~ reStart` command on the SBT console. -- Finally, the server is run using the Server.serve method. The app is provided as the HTTP application, and Server.default is provided as the server configuration. The server configuration contains default settings for the server, such as the port to listen on. The run value represents the execution of the server. It starts the ZIO runtime and executes the server, making it ready to receive and respond to HTTP requests. +[sbt-revolver]: https://github.com/spray/sbt-revolver ## Documentation From b7124db3948b78a3d76397f0dc2f0044616663ab Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 27 Sep 2023 07:38:27 -0400 Subject: [PATCH 52/71] readme --- README.md | 166 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 146 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 550e24c506..be3fa9b4f4 100644 --- a/README.md +++ b/README.md @@ -13,45 +13,171 @@ ZIO HTTP is a scala library for building http apps. It is powered by ZIO and [Ne Setup via `build.sbt`: ```scala -libraryDependencies += "dev.zio" %% "zio-http" % "" +package example + +import zio._ + +import zio.http._ + +object RequestStreaming extends ZIOAppDefault { + + val app: HttpApp[Any] = + Routes( + Method.GET / "text" -> handler(Response.text("Hello World!")) + ).toHttpApp + + // Creating HttpData from the stream + // This works for file of any size + val data = Body.fromStream(stream) + + Response(body = data) + } + + // Run it like any simple app + val run: UIO[ExitCode] = + Server.serve(app).provide(Server.default).exitCode +} ``` -**NOTES ON VERSIONING:** +ZIO-HTTP provides a core library, `zhttp-http`, which includes the base HTTP implementation and server/client capabilities based on ZIO. Additional functionality like serverless support, templating, and websockets are available through separate add-on modules. ZIO-HTTP applications can be easily integrated into different deployment platforms, such as server-based, serverless, or compiled to native binaries. + +The principles of ZIO-HTTP are: + +- Application as a Function: HTTP services in ZIO-HTTP are composed of simple functions. The `HttpApp` type represents a function from an `HttpRequest` to a `ZIO` effect that produces an `HttpResponse`. +- Immutability: Entities in ZIO-HTTP are immutable by default, promoting functional programming principles. +- Symmetric: The same `HttpApp` interface is used for both defining HTTP services and making HTTP requests. This enables easy testing and integration of services without requiring an HTTP container. +- Minimal Dependencies: The core `zhttp-http` module has minimal dependencies, and additional add-on modules only include dependencies required for specific functionality. +- Testability: ZIO-HTTP supports easy in-memory and port-based testing of individual endpoints, applications, websockets/SSE, and complete suites of microservices. +- Portability: ZIO-HTTP applications are portable across different deployment platforms, making them versatile and adaptable. + +By leveraging the power of ZIO and the simplicity of functional programming, ZIO-HTTP provides a robust and flexible toolkit for building scalable and composable HTTP services in Scala. + +## Quickstart + +Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart](https://github.com/zio/zio-http/tree/main/zio-http-example) guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. + +## Module feature overview + +Core: + +- Lightweight and performant HTTP handler and message objects +- Powerful routing system with support for path-based and parameterized routes +- Typesafe HTTP message construction and deconstruction +- Extensible filters for common HTTP functionalities such as caching, compression, and request/response logging +- Support for cookie handling +- Servlet implementation for integration with Servlet containers +- Built-in support for launching applications with an embedded server backend + +Client: + +- Robust and flexible HTTP client with support for synchronous and asynchronous operations +- Adapters for popular HTTP client libraries such as Apache HttpClient, OkHttp, and Jetty HttpClient +- Websocket client with blocking and non-blocking modes +- GraphQL client integration for consuming GraphQL APIs + +Server: + +- Lightweight server backend spin-up for various platforms including Apache, Jetty, Netty, and SunHttp +- Support for SSE (Server-Sent Events) and Websocket communication +- Easy customization of underlying server backend +- Native-friendly for compilation with GraalVM and Quarkus + +Serverless: + +- Function-based support for building serverless HTTP and event-driven applications +- Adapters for AWS Lambda, Google Cloud Functions, Azure Functions, and other serverless platforms +- Custom AWS Lambda runtime for improved performance and reduced startup time -- Older library versions `1.x` or `2.x` with organization `io.d11` of ZIO Http are derived from Dream11, the organization that donated ZIO Http to the ZIO organization in 2022. -- Newer library versions, starting in 2023 and resulting from the ZIO organization (`dev.zio`) started with `0.0.x`, reaching `1.0.0` release candidates in April of 2023 +Contract: -## Getting Started +- Typesafe HTTP contract definition with support for path parameters, query parameters, headers, and request/response bodies +- Automatic validation of incoming requests based on contract definition +- Self-documenting routes with built-in support for OpenAPI (Swagger) descriptions -A simple Http server can be built using a few lines of code. +Templating: + +- Pluggable templating system support for popular template engines such as Dust, Freemarker, Handlebars, and Thymeleaf +- Caching and hot-reload template support for efficient rendering + +Message Formats: + +- First-class support for various message formats such as JSON, XML, YAML, and CSV +- Seamless integration with popular libraries like Jackson, Gson, and Moshi for automatic marshalling and unmarshalling + +Resilience: + +- Integration with Resilience4J for implementing resilience patterns such as circuit breakers, retries, rate-limiting, and bulkheading + +Metrics: + +- Support for integrating zio-http applications with Micrometer for monitoring and metrics collection + +Security: + +- OAuth support for implementing authorization flows with popular providers like Auth0, Google, Facebook, and more +- Digest authentication support for secure client-server communication + +Cloud Native: + +- Tooling and utilities for operating zio-http applications in cloud environments such as Kubernetes and CloudFoundry +- Support for 12-factor configuration, dual-port servers, and health checks + +Testing: + +- Approval testing extensions for testing zio-http Request and Response messages +- Chaos testing API for injecting failure modes and evaluating application behavior under different failure conditions +- Matchers for popular testing frameworks like Hamkrest, Kotest, and Strikt + +Service Virtualization: + +- Record and replay HTTP contracts to simulate virtualized services using Servirtium Markdown format +- Includes Servirtium MiTM (Man-in-the-Middle) server for capturing and replaying HTTP interactions + +WebDriver: + +- Lightweight implementation of Selenium WebDriver for testing zio-http applications + +These features provide a comprehensive set of tools and capabilities for building scalable, performant, and secure HTTP applications with zio-http. + +## Example + +This brief illustration is intended to showcase the ease and capabilities of zio-http. Additionally, refer to the quickstart guide for a minimalistic starting point that demonstrates serving and consuming HTTP services with dynamic routing. + +To install, add these dependencies to your `build.sbt`: ```scala +package example + import zio._ +import zio.http.HttpAppMiddleware.basicAuth import zio.http._ -object HelloWorld extends ZIOAppDefault { +object BasicAuth extends ZIOAppDefault { - val app: HttpApp[Any] = - Routes( - Method.GET / "text" -> handler(Response.text("Hello World!")) - ).toHttpApp + // Define an HTTP application that requires a JWT claim + val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => + Response.text(s"Welcome to the ZIO party! ${name}") + } + + // Compose all the HttpApps together + val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") - override val run = - Server.serve(app).provide(Server.default) + // Run the application like any simple app + val run = Server.serve(app).provide(Server.default) } ``` -## Steps to run an example +## Explanation of the code above + +- The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. -1. Edit the [RunSettings](https://github.com/zio/zio-http/blob/main/project/BuildHelper.scala#L107) - modify `className` to the example you'd like to run. -2. From sbt shell, run `~example/reStart`. You should see `Server started on port: 8080`. -3. Send curl request for defined `http Routes`, for eg : `curl -i "http://localhost:8080/text"` for `example.HelloWorld`. +- The code imports the necessary dependencies from ZIO and ZIO HTTP. -## Watch Mode +- The user value represents an HTTP application that requires a JWT claim. It uses the Http.collect combinator to pattern match on GET requests with a specific path pattern (Root / "user" / name / "greet") and responds with a greeting message that includes the extracted name. -You can use the [sbt-revolver] plugin to start the server and run it in watch mode using `~ reStart` command on the SBT console. +- The app value is created by composing the user HTTP application with the basicAuth middleware. The basicAuth function takes a username and password as arguments and returns a middleware that performs basic authentication. It applies basic authentication with the username "admin" and password "admin" to the user application. -[sbt-revolver]: https://github.com/spray/sbt-revolver +- Finally, the server is run using the Server.serve method. The app is provided as the HTTP application, and Server.default is provided as the server configuration. The server configuration contains default settings for the server, such as the port to listen on. The run value represents the execution of the server. It starts the ZIO runtime and executes the server, making it ready to receive and respond to HTTP requests. ## Documentation From 55c58f920e0ccdaf5cbf92e6dcd736ed3693f196 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 27 Sep 2023 08:15:54 -0400 Subject: [PATCH 53/71] readme --- README.md | 166 +++++++----------------------------------------------- 1 file changed, 20 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index be3fa9b4f4..550e24c506 100644 --- a/README.md +++ b/README.md @@ -13,171 +13,45 @@ ZIO HTTP is a scala library for building http apps. It is powered by ZIO and [Ne Setup via `build.sbt`: ```scala -package example - -import zio._ - -import zio.http._ - -object RequestStreaming extends ZIOAppDefault { - - val app: HttpApp[Any] = - Routes( - Method.GET / "text" -> handler(Response.text("Hello World!")) - ).toHttpApp - - // Creating HttpData from the stream - // This works for file of any size - val data = Body.fromStream(stream) - - Response(body = data) - } - - // Run it like any simple app - val run: UIO[ExitCode] = - Server.serve(app).provide(Server.default).exitCode -} +libraryDependencies += "dev.zio" %% "zio-http" % "" ``` -ZIO-HTTP provides a core library, `zhttp-http`, which includes the base HTTP implementation and server/client capabilities based on ZIO. Additional functionality like serverless support, templating, and websockets are available through separate add-on modules. ZIO-HTTP applications can be easily integrated into different deployment platforms, such as server-based, serverless, or compiled to native binaries. - -The principles of ZIO-HTTP are: - -- Application as a Function: HTTP services in ZIO-HTTP are composed of simple functions. The `HttpApp` type represents a function from an `HttpRequest` to a `ZIO` effect that produces an `HttpResponse`. -- Immutability: Entities in ZIO-HTTP are immutable by default, promoting functional programming principles. -- Symmetric: The same `HttpApp` interface is used for both defining HTTP services and making HTTP requests. This enables easy testing and integration of services without requiring an HTTP container. -- Minimal Dependencies: The core `zhttp-http` module has minimal dependencies, and additional add-on modules only include dependencies required for specific functionality. -- Testability: ZIO-HTTP supports easy in-memory and port-based testing of individual endpoints, applications, websockets/SSE, and complete suites of microservices. -- Portability: ZIO-HTTP applications are portable across different deployment platforms, making them versatile and adaptable. - -By leveraging the power of ZIO and the simplicity of functional programming, ZIO-HTTP provides a robust and flexible toolkit for building scalable and composable HTTP services in Scala. - -## Quickstart - -Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart](https://github.com/zio/zio-http/tree/main/zio-http-example) guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. - -## Module feature overview - -Core: - -- Lightweight and performant HTTP handler and message objects -- Powerful routing system with support for path-based and parameterized routes -- Typesafe HTTP message construction and deconstruction -- Extensible filters for common HTTP functionalities such as caching, compression, and request/response logging -- Support for cookie handling -- Servlet implementation for integration with Servlet containers -- Built-in support for launching applications with an embedded server backend - -Client: - -- Robust and flexible HTTP client with support for synchronous and asynchronous operations -- Adapters for popular HTTP client libraries such as Apache HttpClient, OkHttp, and Jetty HttpClient -- Websocket client with blocking and non-blocking modes -- GraphQL client integration for consuming GraphQL APIs - -Server: - -- Lightweight server backend spin-up for various platforms including Apache, Jetty, Netty, and SunHttp -- Support for SSE (Server-Sent Events) and Websocket communication -- Easy customization of underlying server backend -- Native-friendly for compilation with GraalVM and Quarkus - -Serverless: - -- Function-based support for building serverless HTTP and event-driven applications -- Adapters for AWS Lambda, Google Cloud Functions, Azure Functions, and other serverless platforms -- Custom AWS Lambda runtime for improved performance and reduced startup time +**NOTES ON VERSIONING:** -Contract: +- Older library versions `1.x` or `2.x` with organization `io.d11` of ZIO Http are derived from Dream11, the organization that donated ZIO Http to the ZIO organization in 2022. +- Newer library versions, starting in 2023 and resulting from the ZIO organization (`dev.zio`) started with `0.0.x`, reaching `1.0.0` release candidates in April of 2023 -- Typesafe HTTP contract definition with support for path parameters, query parameters, headers, and request/response bodies -- Automatic validation of incoming requests based on contract definition -- Self-documenting routes with built-in support for OpenAPI (Swagger) descriptions +## Getting Started -Templating: - -- Pluggable templating system support for popular template engines such as Dust, Freemarker, Handlebars, and Thymeleaf -- Caching and hot-reload template support for efficient rendering - -Message Formats: - -- First-class support for various message formats such as JSON, XML, YAML, and CSV -- Seamless integration with popular libraries like Jackson, Gson, and Moshi for automatic marshalling and unmarshalling - -Resilience: - -- Integration with Resilience4J for implementing resilience patterns such as circuit breakers, retries, rate-limiting, and bulkheading - -Metrics: - -- Support for integrating zio-http applications with Micrometer for monitoring and metrics collection - -Security: - -- OAuth support for implementing authorization flows with popular providers like Auth0, Google, Facebook, and more -- Digest authentication support for secure client-server communication - -Cloud Native: - -- Tooling and utilities for operating zio-http applications in cloud environments such as Kubernetes and CloudFoundry -- Support for 12-factor configuration, dual-port servers, and health checks - -Testing: - -- Approval testing extensions for testing zio-http Request and Response messages -- Chaos testing API for injecting failure modes and evaluating application behavior under different failure conditions -- Matchers for popular testing frameworks like Hamkrest, Kotest, and Strikt - -Service Virtualization: - -- Record and replay HTTP contracts to simulate virtualized services using Servirtium Markdown format -- Includes Servirtium MiTM (Man-in-the-Middle) server for capturing and replaying HTTP interactions - -WebDriver: - -- Lightweight implementation of Selenium WebDriver for testing zio-http applications - -These features provide a comprehensive set of tools and capabilities for building scalable, performant, and secure HTTP applications with zio-http. - -## Example - -This brief illustration is intended to showcase the ease and capabilities of zio-http. Additionally, refer to the quickstart guide for a minimalistic starting point that demonstrates serving and consuming HTTP services with dynamic routing. - -To install, add these dependencies to your `build.sbt`: +A simple Http server can be built using a few lines of code. ```scala -package example - import zio._ -import zio.http.HttpAppMiddleware.basicAuth import zio.http._ -object BasicAuth extends ZIOAppDefault { - - // Define an HTTP application that requires a JWT claim - val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => - Response.text(s"Welcome to the ZIO party! ${name}") - } +object HelloWorld extends ZIOAppDefault { - // Compose all the HttpApps together - val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") + val app: HttpApp[Any] = + Routes( + Method.GET / "text" -> handler(Response.text("Hello World!")) + ).toHttpApp - // Run the application like any simple app - val run = Server.serve(app).provide(Server.default) + override val run = + Server.serve(app).provide(Server.default) } ``` -## Explanation of the code above - -- The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. +## Steps to run an example -- The code imports the necessary dependencies from ZIO and ZIO HTTP. +1. Edit the [RunSettings](https://github.com/zio/zio-http/blob/main/project/BuildHelper.scala#L107) - modify `className` to the example you'd like to run. +2. From sbt shell, run `~example/reStart`. You should see `Server started on port: 8080`. +3. Send curl request for defined `http Routes`, for eg : `curl -i "http://localhost:8080/text"` for `example.HelloWorld`. -- The user value represents an HTTP application that requires a JWT claim. It uses the Http.collect combinator to pattern match on GET requests with a specific path pattern (Root / "user" / name / "greet") and responds with a greeting message that includes the extracted name. +## Watch Mode -- The app value is created by composing the user HTTP application with the basicAuth middleware. The basicAuth function takes a username and password as arguments and returns a middleware that performs basic authentication. It applies basic authentication with the username "admin" and password "admin" to the user application. +You can use the [sbt-revolver] plugin to start the server and run it in watch mode using `~ reStart` command on the SBT console. -- Finally, the server is run using the Server.serve method. The app is provided as the HTTP application, and Server.default is provided as the server configuration. The server configuration contains default settings for the server, such as the port to listen on. The run value represents the execution of the server. It starts the ZIO runtime and executes the server, making it ready to receive and respond to HTTP requests. +[sbt-revolver]: https://github.com/spray/sbt-revolver ## Documentation From 2ae64adf21fb8b57c98c4333d8f3af54ecbe8753 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 27 Sep 2023 08:20:55 -0400 Subject: [PATCH 54/71] . --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 550e24c506..597d02e9da 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ ZIO HTTP is a scala library for building http apps. It is powered by ZIO and [Netty](https://netty.io/) and aims at being the defacto solution for writing, highly scalable and performant web applications using idiomatic Scala. -[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) +[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Releases](https://img.shields.io/nexus/r/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Release)](https://oss.sonatype.org/content/repositories/releases/dev/zio/zio-http_2.13/) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![javadoc](https://javadoc.io/badge2/dev.zio/zio-http-docs_2.13/javadoc.svg)](https://javadoc.io/doc/dev.zio/zio-http-docs_2.13) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) ## Installation Setup via `build.sbt`: ```scala -libraryDependencies += "dev.zio" %% "zio-http" % "" +libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0-RC2" ``` **NOTES ON VERSIONING:** From 9e515270ee1be86fc32a5b66bce819bab0b91568 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 27 Sep 2023 08:38:35 -0400 Subject: [PATCH 55/71] Genreadme --- README.md | 168 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 597d02e9da..be3fa9b4f4 100644 --- a/README.md +++ b/README.md @@ -6,52 +6,178 @@ ZIO HTTP is a scala library for building http apps. It is powered by ZIO and [Netty](https://netty.io/) and aims at being the defacto solution for writing, highly scalable and performant web applications using idiomatic Scala. -[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Releases](https://img.shields.io/nexus/r/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Release)](https://oss.sonatype.org/content/repositories/releases/dev/zio/zio-http_2.13/) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![javadoc](https://javadoc.io/badge2/dev.zio/zio-http-docs_2.13/javadoc.svg)](https://javadoc.io/doc/dev.zio/zio-http-docs_2.13) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) +[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) ## Installation Setup via `build.sbt`: ```scala -libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0-RC2" +package example + +import zio._ + +import zio.http._ + +object RequestStreaming extends ZIOAppDefault { + + val app: HttpApp[Any] = + Routes( + Method.GET / "text" -> handler(Response.text("Hello World!")) + ).toHttpApp + + // Creating HttpData from the stream + // This works for file of any size + val data = Body.fromStream(stream) + + Response(body = data) + } + + // Run it like any simple app + val run: UIO[ExitCode] = + Server.serve(app).provide(Server.default).exitCode +} ``` -**NOTES ON VERSIONING:** +ZIO-HTTP provides a core library, `zhttp-http`, which includes the base HTTP implementation and server/client capabilities based on ZIO. Additional functionality like serverless support, templating, and websockets are available through separate add-on modules. ZIO-HTTP applications can be easily integrated into different deployment platforms, such as server-based, serverless, or compiled to native binaries. + +The principles of ZIO-HTTP are: + +- Application as a Function: HTTP services in ZIO-HTTP are composed of simple functions. The `HttpApp` type represents a function from an `HttpRequest` to a `ZIO` effect that produces an `HttpResponse`. +- Immutability: Entities in ZIO-HTTP are immutable by default, promoting functional programming principles. +- Symmetric: The same `HttpApp` interface is used for both defining HTTP services and making HTTP requests. This enables easy testing and integration of services without requiring an HTTP container. +- Minimal Dependencies: The core `zhttp-http` module has minimal dependencies, and additional add-on modules only include dependencies required for specific functionality. +- Testability: ZIO-HTTP supports easy in-memory and port-based testing of individual endpoints, applications, websockets/SSE, and complete suites of microservices. +- Portability: ZIO-HTTP applications are portable across different deployment platforms, making them versatile and adaptable. + +By leveraging the power of ZIO and the simplicity of functional programming, ZIO-HTTP provides a robust and flexible toolkit for building scalable and composable HTTP services in Scala. + +## Quickstart + +Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart](https://github.com/zio/zio-http/tree/main/zio-http-example) guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. + +## Module feature overview + +Core: + +- Lightweight and performant HTTP handler and message objects +- Powerful routing system with support for path-based and parameterized routes +- Typesafe HTTP message construction and deconstruction +- Extensible filters for common HTTP functionalities such as caching, compression, and request/response logging +- Support for cookie handling +- Servlet implementation for integration with Servlet containers +- Built-in support for launching applications with an embedded server backend + +Client: + +- Robust and flexible HTTP client with support for synchronous and asynchronous operations +- Adapters for popular HTTP client libraries such as Apache HttpClient, OkHttp, and Jetty HttpClient +- Websocket client with blocking and non-blocking modes +- GraphQL client integration for consuming GraphQL APIs + +Server: + +- Lightweight server backend spin-up for various platforms including Apache, Jetty, Netty, and SunHttp +- Support for SSE (Server-Sent Events) and Websocket communication +- Easy customization of underlying server backend +- Native-friendly for compilation with GraalVM and Quarkus + +Serverless: + +- Function-based support for building serverless HTTP and event-driven applications +- Adapters for AWS Lambda, Google Cloud Functions, Azure Functions, and other serverless platforms +- Custom AWS Lambda runtime for improved performance and reduced startup time -- Older library versions `1.x` or `2.x` with organization `io.d11` of ZIO Http are derived from Dream11, the organization that donated ZIO Http to the ZIO organization in 2022. -- Newer library versions, starting in 2023 and resulting from the ZIO organization (`dev.zio`) started with `0.0.x`, reaching `1.0.0` release candidates in April of 2023 +Contract: -## Getting Started +- Typesafe HTTP contract definition with support for path parameters, query parameters, headers, and request/response bodies +- Automatic validation of incoming requests based on contract definition +- Self-documenting routes with built-in support for OpenAPI (Swagger) descriptions -A simple Http server can be built using a few lines of code. +Templating: + +- Pluggable templating system support for popular template engines such as Dust, Freemarker, Handlebars, and Thymeleaf +- Caching and hot-reload template support for efficient rendering + +Message Formats: + +- First-class support for various message formats such as JSON, XML, YAML, and CSV +- Seamless integration with popular libraries like Jackson, Gson, and Moshi for automatic marshalling and unmarshalling + +Resilience: + +- Integration with Resilience4J for implementing resilience patterns such as circuit breakers, retries, rate-limiting, and bulkheading + +Metrics: + +- Support for integrating zio-http applications with Micrometer for monitoring and metrics collection + +Security: + +- OAuth support for implementing authorization flows with popular providers like Auth0, Google, Facebook, and more +- Digest authentication support for secure client-server communication + +Cloud Native: + +- Tooling and utilities for operating zio-http applications in cloud environments such as Kubernetes and CloudFoundry +- Support for 12-factor configuration, dual-port servers, and health checks + +Testing: + +- Approval testing extensions for testing zio-http Request and Response messages +- Chaos testing API for injecting failure modes and evaluating application behavior under different failure conditions +- Matchers for popular testing frameworks like Hamkrest, Kotest, and Strikt + +Service Virtualization: + +- Record and replay HTTP contracts to simulate virtualized services using Servirtium Markdown format +- Includes Servirtium MiTM (Man-in-the-Middle) server for capturing and replaying HTTP interactions + +WebDriver: + +- Lightweight implementation of Selenium WebDriver for testing zio-http applications + +These features provide a comprehensive set of tools and capabilities for building scalable, performant, and secure HTTP applications with zio-http. + +## Example + +This brief illustration is intended to showcase the ease and capabilities of zio-http. Additionally, refer to the quickstart guide for a minimalistic starting point that demonstrates serving and consuming HTTP services with dynamic routing. + +To install, add these dependencies to your `build.sbt`: ```scala +package example + import zio._ +import zio.http.HttpAppMiddleware.basicAuth import zio.http._ -object HelloWorld extends ZIOAppDefault { +object BasicAuth extends ZIOAppDefault { - val app: HttpApp[Any] = - Routes( - Method.GET / "text" -> handler(Response.text("Hello World!")) - ).toHttpApp + // Define an HTTP application that requires a JWT claim + val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => + Response.text(s"Welcome to the ZIO party! ${name}") + } + + // Compose all the HttpApps together + val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") - override val run = - Server.serve(app).provide(Server.default) + // Run the application like any simple app + val run = Server.serve(app).provide(Server.default) } ``` -## Steps to run an example +## Explanation of the code above + +- The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. -1. Edit the [RunSettings](https://github.com/zio/zio-http/blob/main/project/BuildHelper.scala#L107) - modify `className` to the example you'd like to run. -2. From sbt shell, run `~example/reStart`. You should see `Server started on port: 8080`. -3. Send curl request for defined `http Routes`, for eg : `curl -i "http://localhost:8080/text"` for `example.HelloWorld`. +- The code imports the necessary dependencies from ZIO and ZIO HTTP. -## Watch Mode +- The user value represents an HTTP application that requires a JWT claim. It uses the Http.collect combinator to pattern match on GET requests with a specific path pattern (Root / "user" / name / "greet") and responds with a greeting message that includes the extracted name. -You can use the [sbt-revolver] plugin to start the server and run it in watch mode using `~ reStart` command on the SBT console. +- The app value is created by composing the user HTTP application with the basicAuth middleware. The basicAuth function takes a username and password as arguments and returns a middleware that performs basic authentication. It applies basic authentication with the username "admin" and password "admin" to the user application. -[sbt-revolver]: https://github.com/spray/sbt-revolver +- Finally, the server is run using the Server.serve method. The app is provided as the HTTP application, and Server.default is provided as the server configuration. The server configuration contains default settings for the server, such as the port to listen on. The run value represents the execution of the server. It starts the ZIO runtime and executes the server, making it ready to receive and respond to HTTP requests. ## Documentation From 4b773a96636c42d73a79253eb92ce6ca0ae76d77 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 27 Sep 2023 13:55:11 +0100 Subject: [PATCH 56/71] . --- README.md | 168 +++++++----------------------------------------------- 1 file changed, 21 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index be3fa9b4f4..597d02e9da 100644 --- a/README.md +++ b/README.md @@ -6,178 +6,52 @@ ZIO HTTP is a scala library for building http apps. It is powered by ZIO and [Netty](https://netty.io/) and aims at being the defacto solution for writing, highly scalable and performant web applications using idiomatic Scala. -[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) +[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Releases](https://img.shields.io/nexus/r/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Release)](https://oss.sonatype.org/content/repositories/releases/dev/zio/zio-http_2.13/) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![javadoc](https://javadoc.io/badge2/dev.zio/zio-http-docs_2.13/javadoc.svg)](https://javadoc.io/doc/dev.zio/zio-http-docs_2.13) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) ## Installation Setup via `build.sbt`: ```scala -package example - -import zio._ - -import zio.http._ - -object RequestStreaming extends ZIOAppDefault { - - val app: HttpApp[Any] = - Routes( - Method.GET / "text" -> handler(Response.text("Hello World!")) - ).toHttpApp - - // Creating HttpData from the stream - // This works for file of any size - val data = Body.fromStream(stream) - - Response(body = data) - } - - // Run it like any simple app - val run: UIO[ExitCode] = - Server.serve(app).provide(Server.default).exitCode -} +libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0-RC2" ``` -ZIO-HTTP provides a core library, `zhttp-http`, which includes the base HTTP implementation and server/client capabilities based on ZIO. Additional functionality like serverless support, templating, and websockets are available through separate add-on modules. ZIO-HTTP applications can be easily integrated into different deployment platforms, such as server-based, serverless, or compiled to native binaries. - -The principles of ZIO-HTTP are: - -- Application as a Function: HTTP services in ZIO-HTTP are composed of simple functions. The `HttpApp` type represents a function from an `HttpRequest` to a `ZIO` effect that produces an `HttpResponse`. -- Immutability: Entities in ZIO-HTTP are immutable by default, promoting functional programming principles. -- Symmetric: The same `HttpApp` interface is used for both defining HTTP services and making HTTP requests. This enables easy testing and integration of services without requiring an HTTP container. -- Minimal Dependencies: The core `zhttp-http` module has minimal dependencies, and additional add-on modules only include dependencies required for specific functionality. -- Testability: ZIO-HTTP supports easy in-memory and port-based testing of individual endpoints, applications, websockets/SSE, and complete suites of microservices. -- Portability: ZIO-HTTP applications are portable across different deployment platforms, making them versatile and adaptable. - -By leveraging the power of ZIO and the simplicity of functional programming, ZIO-HTTP provides a robust and flexible toolkit for building scalable and composable HTTP services in Scala. - -## Quickstart - -Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart](https://github.com/zio/zio-http/tree/main/zio-http-example) guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. - -## Module feature overview - -Core: - -- Lightweight and performant HTTP handler and message objects -- Powerful routing system with support for path-based and parameterized routes -- Typesafe HTTP message construction and deconstruction -- Extensible filters for common HTTP functionalities such as caching, compression, and request/response logging -- Support for cookie handling -- Servlet implementation for integration with Servlet containers -- Built-in support for launching applications with an embedded server backend - -Client: - -- Robust and flexible HTTP client with support for synchronous and asynchronous operations -- Adapters for popular HTTP client libraries such as Apache HttpClient, OkHttp, and Jetty HttpClient -- Websocket client with blocking and non-blocking modes -- GraphQL client integration for consuming GraphQL APIs - -Server: - -- Lightweight server backend spin-up for various platforms including Apache, Jetty, Netty, and SunHttp -- Support for SSE (Server-Sent Events) and Websocket communication -- Easy customization of underlying server backend -- Native-friendly for compilation with GraalVM and Quarkus - -Serverless: - -- Function-based support for building serverless HTTP and event-driven applications -- Adapters for AWS Lambda, Google Cloud Functions, Azure Functions, and other serverless platforms -- Custom AWS Lambda runtime for improved performance and reduced startup time +**NOTES ON VERSIONING:** -Contract: +- Older library versions `1.x` or `2.x` with organization `io.d11` of ZIO Http are derived from Dream11, the organization that donated ZIO Http to the ZIO organization in 2022. +- Newer library versions, starting in 2023 and resulting from the ZIO organization (`dev.zio`) started with `0.0.x`, reaching `1.0.0` release candidates in April of 2023 -- Typesafe HTTP contract definition with support for path parameters, query parameters, headers, and request/response bodies -- Automatic validation of incoming requests based on contract definition -- Self-documenting routes with built-in support for OpenAPI (Swagger) descriptions +## Getting Started -Templating: - -- Pluggable templating system support for popular template engines such as Dust, Freemarker, Handlebars, and Thymeleaf -- Caching and hot-reload template support for efficient rendering - -Message Formats: - -- First-class support for various message formats such as JSON, XML, YAML, and CSV -- Seamless integration with popular libraries like Jackson, Gson, and Moshi for automatic marshalling and unmarshalling - -Resilience: - -- Integration with Resilience4J for implementing resilience patterns such as circuit breakers, retries, rate-limiting, and bulkheading - -Metrics: - -- Support for integrating zio-http applications with Micrometer for monitoring and metrics collection - -Security: - -- OAuth support for implementing authorization flows with popular providers like Auth0, Google, Facebook, and more -- Digest authentication support for secure client-server communication - -Cloud Native: - -- Tooling and utilities for operating zio-http applications in cloud environments such as Kubernetes and CloudFoundry -- Support for 12-factor configuration, dual-port servers, and health checks - -Testing: - -- Approval testing extensions for testing zio-http Request and Response messages -- Chaos testing API for injecting failure modes and evaluating application behavior under different failure conditions -- Matchers for popular testing frameworks like Hamkrest, Kotest, and Strikt - -Service Virtualization: - -- Record and replay HTTP contracts to simulate virtualized services using Servirtium Markdown format -- Includes Servirtium MiTM (Man-in-the-Middle) server for capturing and replaying HTTP interactions - -WebDriver: - -- Lightweight implementation of Selenium WebDriver for testing zio-http applications - -These features provide a comprehensive set of tools and capabilities for building scalable, performant, and secure HTTP applications with zio-http. - -## Example - -This brief illustration is intended to showcase the ease and capabilities of zio-http. Additionally, refer to the quickstart guide for a minimalistic starting point that demonstrates serving and consuming HTTP services with dynamic routing. - -To install, add these dependencies to your `build.sbt`: +A simple Http server can be built using a few lines of code. ```scala -package example - import zio._ -import zio.http.HttpAppMiddleware.basicAuth import zio.http._ -object BasicAuth extends ZIOAppDefault { - - // Define an HTTP application that requires a JWT claim - val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => - Response.text(s"Welcome to the ZIO party! ${name}") - } +object HelloWorld extends ZIOAppDefault { - // Compose all the HttpApps together - val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") + val app: HttpApp[Any] = + Routes( + Method.GET / "text" -> handler(Response.text("Hello World!")) + ).toHttpApp - // Run the application like any simple app - val run = Server.serve(app).provide(Server.default) + override val run = + Server.serve(app).provide(Server.default) } ``` -## Explanation of the code above - -- The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. +## Steps to run an example -- The code imports the necessary dependencies from ZIO and ZIO HTTP. +1. Edit the [RunSettings](https://github.com/zio/zio-http/blob/main/project/BuildHelper.scala#L107) - modify `className` to the example you'd like to run. +2. From sbt shell, run `~example/reStart`. You should see `Server started on port: 8080`. +3. Send curl request for defined `http Routes`, for eg : `curl -i "http://localhost:8080/text"` for `example.HelloWorld`. -- The user value represents an HTTP application that requires a JWT claim. It uses the Http.collect combinator to pattern match on GET requests with a specific path pattern (Root / "user" / name / "greet") and responds with a greeting message that includes the extracted name. +## Watch Mode -- The app value is created by composing the user HTTP application with the basicAuth middleware. The basicAuth function takes a username and password as arguments and returns a middleware that performs basic authentication. It applies basic authentication with the username "admin" and password "admin" to the user application. +You can use the [sbt-revolver] plugin to start the server and run it in watch mode using `~ reStart` command on the SBT console. -- Finally, the server is run using the Server.serve method. The app is provided as the HTTP application, and Server.default is provided as the server configuration. The server configuration contains default settings for the server, such as the port to listen on. The run value represents the execution of the server. It starts the ZIO runtime and executes the server, making it ready to receive and respond to HTTP requests. +[sbt-revolver]: https://github.com/spray/sbt-revolver ## Documentation From ef8bdfe5bc27addfc684345ee6044313dab67fd8 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Wed, 27 Sep 2023 13:02:32 +0000 Subject: [PATCH 57/71] samething --- README.md | 168 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 597d02e9da..be3fa9b4f4 100644 --- a/README.md +++ b/README.md @@ -6,52 +6,178 @@ ZIO HTTP is a scala library for building http apps. It is powered by ZIO and [Netty](https://netty.io/) and aims at being the defacto solution for writing, highly scalable and performant web applications using idiomatic Scala. -[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Releases](https://img.shields.io/nexus/r/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Release)](https://oss.sonatype.org/content/repositories/releases/dev/zio/zio-http_2.13/) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![javadoc](https://javadoc.io/badge2/dev.zio/zio-http-docs_2.13/javadoc.svg)](https://javadoc.io/doc/dev.zio/zio-http-docs_2.13) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) +[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) ## Installation Setup via `build.sbt`: ```scala -libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0-RC2" +package example + +import zio._ + +import zio.http._ + +object RequestStreaming extends ZIOAppDefault { + + val app: HttpApp[Any] = + Routes( + Method.GET / "text" -> handler(Response.text("Hello World!")) + ).toHttpApp + + // Creating HttpData from the stream + // This works for file of any size + val data = Body.fromStream(stream) + + Response(body = data) + } + + // Run it like any simple app + val run: UIO[ExitCode] = + Server.serve(app).provide(Server.default).exitCode +} ``` -**NOTES ON VERSIONING:** +ZIO-HTTP provides a core library, `zhttp-http`, which includes the base HTTP implementation and server/client capabilities based on ZIO. Additional functionality like serverless support, templating, and websockets are available through separate add-on modules. ZIO-HTTP applications can be easily integrated into different deployment platforms, such as server-based, serverless, or compiled to native binaries. + +The principles of ZIO-HTTP are: + +- Application as a Function: HTTP services in ZIO-HTTP are composed of simple functions. The `HttpApp` type represents a function from an `HttpRequest` to a `ZIO` effect that produces an `HttpResponse`. +- Immutability: Entities in ZIO-HTTP are immutable by default, promoting functional programming principles. +- Symmetric: The same `HttpApp` interface is used for both defining HTTP services and making HTTP requests. This enables easy testing and integration of services without requiring an HTTP container. +- Minimal Dependencies: The core `zhttp-http` module has minimal dependencies, and additional add-on modules only include dependencies required for specific functionality. +- Testability: ZIO-HTTP supports easy in-memory and port-based testing of individual endpoints, applications, websockets/SSE, and complete suites of microservices. +- Portability: ZIO-HTTP applications are portable across different deployment platforms, making them versatile and adaptable. + +By leveraging the power of ZIO and the simplicity of functional programming, ZIO-HTTP provides a robust and flexible toolkit for building scalable and composable HTTP services in Scala. + +## Quickstart + +Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart](https://github.com/zio/zio-http/tree/main/zio-http-example) guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. + +## Module feature overview + +Core: + +- Lightweight and performant HTTP handler and message objects +- Powerful routing system with support for path-based and parameterized routes +- Typesafe HTTP message construction and deconstruction +- Extensible filters for common HTTP functionalities such as caching, compression, and request/response logging +- Support for cookie handling +- Servlet implementation for integration with Servlet containers +- Built-in support for launching applications with an embedded server backend + +Client: + +- Robust and flexible HTTP client with support for synchronous and asynchronous operations +- Adapters for popular HTTP client libraries such as Apache HttpClient, OkHttp, and Jetty HttpClient +- Websocket client with blocking and non-blocking modes +- GraphQL client integration for consuming GraphQL APIs + +Server: + +- Lightweight server backend spin-up for various platforms including Apache, Jetty, Netty, and SunHttp +- Support for SSE (Server-Sent Events) and Websocket communication +- Easy customization of underlying server backend +- Native-friendly for compilation with GraalVM and Quarkus + +Serverless: + +- Function-based support for building serverless HTTP and event-driven applications +- Adapters for AWS Lambda, Google Cloud Functions, Azure Functions, and other serverless platforms +- Custom AWS Lambda runtime for improved performance and reduced startup time -- Older library versions `1.x` or `2.x` with organization `io.d11` of ZIO Http are derived from Dream11, the organization that donated ZIO Http to the ZIO organization in 2022. -- Newer library versions, starting in 2023 and resulting from the ZIO organization (`dev.zio`) started with `0.0.x`, reaching `1.0.0` release candidates in April of 2023 +Contract: -## Getting Started +- Typesafe HTTP contract definition with support for path parameters, query parameters, headers, and request/response bodies +- Automatic validation of incoming requests based on contract definition +- Self-documenting routes with built-in support for OpenAPI (Swagger) descriptions -A simple Http server can be built using a few lines of code. +Templating: + +- Pluggable templating system support for popular template engines such as Dust, Freemarker, Handlebars, and Thymeleaf +- Caching and hot-reload template support for efficient rendering + +Message Formats: + +- First-class support for various message formats such as JSON, XML, YAML, and CSV +- Seamless integration with popular libraries like Jackson, Gson, and Moshi for automatic marshalling and unmarshalling + +Resilience: + +- Integration with Resilience4J for implementing resilience patterns such as circuit breakers, retries, rate-limiting, and bulkheading + +Metrics: + +- Support for integrating zio-http applications with Micrometer for monitoring and metrics collection + +Security: + +- OAuth support for implementing authorization flows with popular providers like Auth0, Google, Facebook, and more +- Digest authentication support for secure client-server communication + +Cloud Native: + +- Tooling and utilities for operating zio-http applications in cloud environments such as Kubernetes and CloudFoundry +- Support for 12-factor configuration, dual-port servers, and health checks + +Testing: + +- Approval testing extensions for testing zio-http Request and Response messages +- Chaos testing API for injecting failure modes and evaluating application behavior under different failure conditions +- Matchers for popular testing frameworks like Hamkrest, Kotest, and Strikt + +Service Virtualization: + +- Record and replay HTTP contracts to simulate virtualized services using Servirtium Markdown format +- Includes Servirtium MiTM (Man-in-the-Middle) server for capturing and replaying HTTP interactions + +WebDriver: + +- Lightweight implementation of Selenium WebDriver for testing zio-http applications + +These features provide a comprehensive set of tools and capabilities for building scalable, performant, and secure HTTP applications with zio-http. + +## Example + +This brief illustration is intended to showcase the ease and capabilities of zio-http. Additionally, refer to the quickstart guide for a minimalistic starting point that demonstrates serving and consuming HTTP services with dynamic routing. + +To install, add these dependencies to your `build.sbt`: ```scala +package example + import zio._ +import zio.http.HttpAppMiddleware.basicAuth import zio.http._ -object HelloWorld extends ZIOAppDefault { +object BasicAuth extends ZIOAppDefault { - val app: HttpApp[Any] = - Routes( - Method.GET / "text" -> handler(Response.text("Hello World!")) - ).toHttpApp + // Define an HTTP application that requires a JWT claim + val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => + Response.text(s"Welcome to the ZIO party! ${name}") + } + + // Compose all the HttpApps together + val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") - override val run = - Server.serve(app).provide(Server.default) + // Run the application like any simple app + val run = Server.serve(app).provide(Server.default) } ``` -## Steps to run an example +## Explanation of the code above + +- The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. -1. Edit the [RunSettings](https://github.com/zio/zio-http/blob/main/project/BuildHelper.scala#L107) - modify `className` to the example you'd like to run. -2. From sbt shell, run `~example/reStart`. You should see `Server started on port: 8080`. -3. Send curl request for defined `http Routes`, for eg : `curl -i "http://localhost:8080/text"` for `example.HelloWorld`. +- The code imports the necessary dependencies from ZIO and ZIO HTTP. -## Watch Mode +- The user value represents an HTTP application that requires a JWT claim. It uses the Http.collect combinator to pattern match on GET requests with a specific path pattern (Root / "user" / name / "greet") and responds with a greeting message that includes the extracted name. -You can use the [sbt-revolver] plugin to start the server and run it in watch mode using `~ reStart` command on the SBT console. +- The app value is created by composing the user HTTP application with the basicAuth middleware. The basicAuth function takes a username and password as arguments and returns a middleware that performs basic authentication. It applies basic authentication with the username "admin" and password "admin" to the user application. -[sbt-revolver]: https://github.com/spray/sbt-revolver +- Finally, the server is run using the Server.serve method. The app is provided as the HTTP application, and Server.default is provided as the server configuration. The server configuration contains default settings for the server, such as the port to listen on. The run value represents the execution of the server. It starts the ZIO runtime and executes the server, making it ready to receive and respond to HTTP requests. ## Documentation From 49d17f99aa4b499a35ae62c2c53ada0550f989f9 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Wed, 27 Sep 2023 15:02:59 +0000 Subject: [PATCH 58/71] . --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index be3fa9b4f4..9fca11c7b6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ZIO HTTP is a scala library for building http apps. It is powered by ZIO and [Netty](https://netty.io/) and aims at being the defacto solution for writing, highly scalable and performant web applications using idiomatic Scala. -[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) +[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Releases](https://img.shields.io/nexus/r/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Release)](https://oss.sonatype.org/content/repositories/releases/dev/zio/zio-http_2.13/) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![javadoc](https://javadoc.io/badge2/dev.zio/zio-http-docs_2.13/javadoc.svg)](https://javadoc.io/doc/dev.zio/zio-http-docs_2.13) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) ## Installation From c9012d8a6500030853e7692cce16e30c63b50510 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Wed, 27 Sep 2023 23:38:46 +0000 Subject: [PATCH 59/71] test --- docs/faq.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 831d682a70..faf941314d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -37,7 +37,7 @@ You can define custom middleware functions that can perform operations such as r Example -```scala +```scala mdoc:silent:reset package example import java.util.concurrent.TimeUnit @@ -61,6 +61,7 @@ object HelloWorldWithMiddlewares extends ZIOAppDefault { withHeader = Response.Patch.addHeader("X-Time", currentMilliseconds.toString) } yield withHeader, ) + val middlewares = // print debug info about request and response HttpAppMiddleware.debug ++ From e24a2e5cb610b78a421bba2ae112fc9fe5095893 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Wed, 27 Sep 2023 23:45:27 +0000 Subject: [PATCH 60/71] faq --- docs/faq.md | 39 ++------------------------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index faf941314d..70f0fe5af1 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -38,43 +38,8 @@ You can define custom middleware functions that can perform operations such as r Example ```scala mdoc:silent:reset -package example - -import java.util.concurrent.TimeUnit - -import zio._ - -import zio.http._ - -object HelloWorldWithMiddlewares extends ZIOAppDefault { - - val app: HttpApp[Any, Nothing] = Http.collectZIO[Request] { - // this will return result instantly - case Method.GET -> Root / "text" => ZIO.succeed(Response.text("Hello World!")) - // this will return result after 5 seconds, so with 3 seconds timeout it will fail - case Method.GET -> Root / "long-running" => ZIO.succeed(Response.text("Hello World!")).delay(5 seconds) - } - - val serverTime: RequestHandlerMiddleware[Nothing, Any, Nothing, Any] = HttpAppMiddleware.patchZIO(_ => - for { - currentMilliseconds <- Clock.currentTime(TimeUnit.MILLISECONDS) - withHeader = Response.Patch.addHeader("X-Time", currentMilliseconds.toString) - } yield withHeader, - ) - - val middlewares = - // print debug info about request and response - HttpAppMiddleware.debug ++ - // close connection if request takes more than 3 seconds - HttpAppMiddleware.timeout(3 seconds) ++ - // add static header - HttpAppMiddleware.addHeader("X-Environment", "Dev") ++ - // add dynamic header - serverTime - - // Run it like any simple app - val run = Server.serve((app @@ middlewares).withDefaultErrorResponse).provide(Server.default) -} + +// check later ``` From 5747c83aae097fa74277da5ec49c9d02e0f7c041 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Wed, 27 Sep 2023 23:46:26 +0000 Subject: [PATCH 61/71] faq --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 70f0fe5af1..24465b7eff 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -37,7 +37,7 @@ You can define custom middleware functions that can perform operations such as r Example -```scala mdoc:silent:reset +```scala mdoc // check later ``` From b35d36915cb6e38914daaf4e1fe2d852ffc84d08 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Wed, 27 Sep 2023 23:54:24 +0000 Subject: [PATCH 62/71] buildtest --- docs/how-to-guides/http-sever.md | 4 ++-- docs/how-to-guides/websocket.md | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/how-to-guides/http-sever.md b/docs/how-to-guides/http-sever.md index f815767303..49f134da85 100644 --- a/docs/how-to-guides/http-sever.md +++ b/docs/how-to-guides/http-sever.md @@ -11,7 +11,7 @@ This example demonstrates the creation of a simple HTTP server in zio-http with ## `build.sbt` Setup -```scala +```scala mdoc:silent scalaVersion := "2.13.6" libraryDependencies ++= Seq( @@ -22,7 +22,7 @@ libraryDependencies ++= Seq( ## code -```scala +```scala mdoc:silent import zio._ import zio.http._ diff --git a/docs/how-to-guides/websocket.md b/docs/how-to-guides/websocket.md index 83222ba67d..4960a2c513 100644 --- a/docs/how-to-guides/websocket.md +++ b/docs/how-to-guides/websocket.md @@ -39,10 +39,8 @@ object WebSocketEcho extends ZIOAppDefault { override val run = Server.serve(app).provide(Server.default) } - ``` -

### Advanced WebSocket server From c24cbba956dc2e97d6bf8ac49f7781d71d6d9bc0 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Thu, 28 Sep 2023 00:43:47 +0000 Subject: [PATCH 63/71] fix --- docs/how-to-guides/authentication.md | 4 ---- docs/how-to-guides/concrete-entity.md | 5 ----- docs/how-to-guides/http-client.md | 13 ------------- docs/how-to-guides/http-sever.md | 12 ------------ 4 files changed, 34 deletions(-) diff --git a/docs/how-to-guides/authentication.md b/docs/how-to-guides/authentication.md index abc8df694f..14fafe98fc 100644 --- a/docs/how-to-guides/authentication.md +++ b/docs/how-to-guides/authentication.md @@ -3,15 +3,11 @@ id: authentication title: "Authentication Server Example" --- -<<<<<<< HEAD:docs/how-to-guides/authentication.md This code shows how to implement a server with bearer authentication middle ware in `zio-http` ```scala -======= -```scala mdoc:silent ->>>>>>> main:docs/examples/advanced/authentication.md import java.time.Clock import zio._ diff --git a/docs/how-to-guides/concrete-entity.md b/docs/how-to-guides/concrete-entity.md index 6651681d9e..e0b35ba80e 100644 --- a/docs/how-to-guides/concrete-entity.md +++ b/docs/how-to-guides/concrete-entity.md @@ -3,17 +3,12 @@ id: concrete-entity title: "Concrete Entity Example" --- -<<<<<<< HEAD:docs/how-to-guides/concrete-entity.md `Concrete entities` refer to specific data models or classes that represent the request and response payloads in an HTTP application. This code is an example demonstrating how to build an application using concrete entities in ZIO-HTTP. ## Code ```scala -======= -```scala mdoc:silent - ->>>>>>> main:docs/examples/advanced/concrete-entity.md import zio._ import zio.http._ diff --git a/docs/how-to-guides/http-client.md b/docs/how-to-guides/http-client.md index 447b0baa59..08e7db2fe7 100644 --- a/docs/how-to-guides/http-client.md +++ b/docs/how-to-guides/http-client.md @@ -6,19 +6,6 @@ title: HTTP Client This example provided demonstrates how to perform an HTTP client request using the zio-http library in Scala with ZIO. - - -## `build.sbt` Setup - -```scala -scalaVersion := "2.13.6" - -libraryDependencies ++= Seq( - "dev.zio" %% "zio-http" % "3.0.0-RC2" -) -``` - - ## code ```scala diff --git a/docs/how-to-guides/http-sever.md b/docs/how-to-guides/http-sever.md index 49f134da85..1b9daae773 100644 --- a/docs/how-to-guides/http-sever.md +++ b/docs/how-to-guides/http-sever.md @@ -8,18 +8,6 @@ title: HTTP server This example demonstrates the creation of a simple HTTP server in zio-http with ZIO. - -## `build.sbt` Setup - -```scala mdoc:silent -scalaVersion := "2.13.6" - -libraryDependencies ++= Seq( - "dev.zio" %% "zio-http" % "3.0.0-RC2" -) -``` - - ## code ```scala mdoc:silent From 7c40256945261635a80fa4849720bfdae5d76333 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Thu, 28 Sep 2023 01:08:00 +0000 Subject: [PATCH 64/71] test* --- docs/how-to-guides/websocket.md | 63 --------------------------------- 1 file changed, 63 deletions(-) diff --git a/docs/how-to-guides/websocket.md b/docs/how-to-guides/websocket.md index 4960a2c513..c8ff24e0e2 100644 --- a/docs/how-to-guides/websocket.md +++ b/docs/how-to-guides/websocket.md @@ -39,67 +39,4 @@ object WebSocketEcho extends ZIOAppDefault { override val run = Server.serve(app).provide(Server.default) } -``` - -
- -### Advanced WebSocket server - -This code sets up an HTTP server with WebSocket support. Clients can establish WebSocket connections to the server and perform various actions based on the received WebSocket messages. - - -```scala mdoc:silent -import zio._ - -import zio.http.ChannelEvent.{ExceptionCaught, Read, UserEvent, UserEventTriggered} -import zio.http._ -import zio.http.codec.PathCodec.string - -object WebSocketAdvanced extends ZIOAppDefault { - - val socketApp: WebSocketApp[Any] = - Handler.webSocket { channel => - channel.receiveAll { - case Read(WebSocketFrame.Text("end")) => - channel.shutdown - - // Send a "bar" if the server sends a "foo" - case Read(WebSocketFrame.Text("foo")) => - channel.send(Read(WebSocketFrame.text("bar"))) - - // Send a "foo" if the server sends a "bar" - case Read(WebSocketFrame.Text("bar")) => - channel.send(Read(WebSocketFrame.text("foo"))) - - // Echo the same message 10 times if it's not "foo" or "bar" - case Read(WebSocketFrame.Text(text)) => - channel.send(Read(WebSocketFrame.text(text))).repeatN(10) - - // Send a "greeting" message to the server once the connection is established - case UserEventTriggered(UserEvent.HandshakeComplete) => - channel.send(Read(WebSocketFrame.text("Greetings!"))) - - // Log when the channel is getting closed - case Read(WebSocketFrame.Close(status, reason)) => - Console.printLine("Closing channel with status: " + status + " and reason: " + reason) - - // Print the exception if it's not a normal close - case ExceptionCaught(cause) => - Console.printLine(s"Channel error!: ${cause.getMessage}") - - case _ => - ZIO.unit - } - } - - val app: HttpApp[Any] = - Routes( - Method.GET / "greet" / string("name") -> handler { (name: String, req: Request) => - Response.text(s"Greetings ${name}!") - }, - Method.GET / "subscriptions" -> handler(socketApp.toResponse), - ).toHttpApp - - override val run = Server.serve(app).provide(Server.default) -} ``` \ No newline at end of file From c705ffa461011cf889cf56dded2a5e6998605af0 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Thu, 28 Sep 2023 01:27:15 +0000 Subject: [PATCH 65/71] test* --- docs/how-to-guides/websocket.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/how-to-guides/websocket.md b/docs/how-to-guides/websocket.md index c8ff24e0e2..7ff4ab56c9 100644 --- a/docs/how-to-guides/websocket.md +++ b/docs/how-to-guides/websocket.md @@ -38,5 +38,4 @@ object WebSocketEcho extends ZIOAppDefault { ).toHttpApp override val run = Server.serve(app).provide(Server.default) -} -``` \ No newline at end of file +} \ No newline at end of file From a30c3769cf13fcd9b31517da2a7a8d0c22b5b254 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Thu, 28 Sep 2023 01:42:58 +0000 Subject: [PATCH 66/71] push --- docs/how-to-guides/websocket.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/how-to-guides/websocket.md b/docs/how-to-guides/websocket.md index 7ff4ab56c9..c8ff24e0e2 100644 --- a/docs/how-to-guides/websocket.md +++ b/docs/how-to-guides/websocket.md @@ -38,4 +38,5 @@ object WebSocketEcho extends ZIOAppDefault { ).toHttpApp override val run = Server.serve(app).provide(Server.default) -} \ No newline at end of file +} +``` \ No newline at end of file From 5916f5f93f3282522d5b018697c28a2890af1198 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Thu, 28 Sep 2023 08:28:57 +0000 Subject: [PATCH 67/71] test*** --- docs/how-to-guides/http-sever.md | 34 -------------------------- docs/how-to-guides/websocket.md | 42 -------------------------------- 2 files changed, 76 deletions(-) delete mode 100644 docs/how-to-guides/http-sever.md delete mode 100644 docs/how-to-guides/websocket.md diff --git a/docs/how-to-guides/http-sever.md b/docs/how-to-guides/http-sever.md deleted file mode 100644 index 1b9daae773..0000000000 --- a/docs/how-to-guides/http-sever.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -id: http-sever -title: HTTP server ---- - - -# Simple http sever - -This example demonstrates the creation of a simple HTTP server in zio-http with ZIO. - -## code - -```scala mdoc:silent -import zio._ - -import zio.http._ - -object HelloWorld extends ZIOAppDefault { - val textRoute = - Method.GET / "text" -> handler(Response.text("Hello World!")) - - val jsonRoute = - Method.GET / "json" -> handler(Response.json("""{"greetings": "Hello World!"}""")) - - // Create HTTP route - val app = Routes(textRoute, jsonRoute).toHttpApp - - // Run it like any simple app - override val run = Server.serve(app).provide(Server.default) -} - -``` -
-
\ No newline at end of file diff --git a/docs/how-to-guides/websocket.md b/docs/how-to-guides/websocket.md deleted file mode 100644 index c8ff24e0e2..0000000000 --- a/docs/how-to-guides/websocket.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -id: websocket -title: Websocket ---- - -This code sets up a WebSocket echo server and creates an HTTP server that handles WebSocket connections and echoes back received messages. - -
- -```scala mdoc:silent -import zio._ - -import zio.http.ChannelEvent.Read -import zio.http._ -import zio.http.codec.PathCodec.string - -object WebSocketEcho extends ZIOAppDefault { - private val socketApp: WebSocketApp[Any] = - Handler.webSocket { channel => - channel.receiveAll { - case Read(WebSocketFrame.Text("FOO")) => - channel.send(Read(WebSocketFrame.Text("BAR"))) - case Read(WebSocketFrame.Text("BAR")) => - channel.send(Read(WebSocketFrame.Text("FOO"))) - case Read(WebSocketFrame.Text(text)) => - channel.send(Read(WebSocketFrame.Text(text))).repeatN(10) - case _ => - ZIO.unit - } - } - - private val app: HttpApp[Any] = - Routes( - Method.GET / "greet" / string("name") -> handler { (name: String, req: Request) => - Response.text(s"Greetings {$name}!") - }, - Method.GET / "subscriptions" -> handler(socketApp.toResponse), - ).toHttpApp - - override val run = Server.serve(app).provide(Server.default) -} -``` \ No newline at end of file From 132f56e0d56a055fb26f7f96c70d9d6aa6143097 Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Thu, 28 Sep 2023 08:29:13 +0000 Subject: [PATCH 68/71] test*** --- docs/faq.md | 45 --------- docs/support.md | 17 ---- docs/tutorials/your-first-zio-http-app.md | 117 ---------------------- 3 files changed, 179 deletions(-) delete mode 100644 docs/faq.md delete mode 100644 docs/support.md delete mode 100644 docs/tutorials/your-first-zio-http-app.md diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index 24465b7eff..0000000000 --- a/docs/faq.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -id: faq -title: "common zio-http asked questions" ---- - -Explore the most commonly asked questions about zio-http and find detailed answers in this informative resource. - -**Q. What is ZIO-HTTP ?** - -ZIO Http is a functional Scala library utilized for constructing high-performance HTTP services and clients. It leverages the power of ZIO's concurrency library and the Netty network library. With ZIO-HTTP, you can create efficient and expressive HTTP applications through its comprehensive high-level API. - -
- -**Q. Is zio-http a library or a framework?** - -ZIO-HTTP is primarily a library rather than a framework. It provides a set of tools, components, and abstractions that you can utilize to build HTTP-based services and clients in a functional programming style using Scala and the ZIO concurrency library. It offers a high-level API for constructing HTTP applications, but it does not impose a rigid framework structure or dictate the overall architecture of your application. Instead, it focuses on providing the necessary building blocks and utilities for working with HTTP protocols in a composable and expressive manner. - -
- -**Q. Does ZIO-HTTP provide support for an asynchronous programming model?** - -Yes, ZIO-HTTP supports an asynchronous model for handling HTTP requests and responses. It is built on top of the ZIO concurrency library, which provides powerful asynchronous and concurrent programming capabilities. - -ZIO's concurrency model is designed to handle high scalability and performance requirements. It utilizes lightweight fibers for efficient concurrency management, allowing you to handle a large number of concurrent requests without incurring significant overhead. By leveraging the asynchronous nature of ZIO, you can write non-blocking and highly performant code, which is essential for building webscale applications. - -With ZIO-HTTP, you can take advantage of these asynchronous features and design your applications to handle high loads, making it well-suited for webscale scenarios. Checkout the [benachmark results](https://web-frameworks-benchmark.netlify.app/compare?f=zio-http) To assess how ZIO-HTTP compares to other JVM-based web libraries in relation to their synchronous and asynchronous capabilities. - -
- -**Q. Does ZIO-HTTP support middleware for request/response modification?** - -Yes, ZIO-HTTP does support middleware for request/response modification. Middleware in ZIO-HTTP allows you to intercept and modify requests and responses as they flow through the application's request/response pipeline. - -You can define custom middleware functions that can perform operations such as request/response transformation, authentication, logging, error handling, and more. Middleware functions can be composed and applied to specific routes or globally to the entire application. - -
- -Example - -```scala mdoc - -// check later -``` - -
diff --git a/docs/support.md b/docs/support.md deleted file mode 100644 index 306247ed96..0000000000 --- a/docs/support.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -id: support -title: "Support" ---- - - -## Seeking assistance: General Support - -
- -- Check if there is a pertinent example available in the How-To Guides. We are continuously adding more examples to these resources." - -- [For issues](https://github.com/zio/zio-http/issues), Please kindly provide a comprehensive description, including the version used and step-by-step instructions for reproducing the issue." - -- **Discord Sever** : [zio-http](https://discord.gg/DH3erjBypH) - -- **Twitter** : [@zioscala](https://twitter.com/zioscala) diff --git a/docs/tutorials/your-first-zio-http-app.md b/docs/tutorials/your-first-zio-http-app.md deleted file mode 100644 index 5d647532c7..0000000000 --- a/docs/tutorials/your-first-zio-http-app.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -id: your-first-zio-http-app -title: Your first zio http app ---- - -# Your first zio-http app - -Welcome to the realm of ZIO-HTTP! This guide will take you through the necessary steps to start and set up your first Scala server application. By the end, you'll have a fully functional app that is built. - -
- -### **Pre-requisites**: -To install Scala and ZIO-HTTP, you'll need to have a few prerequisites in place. Here are the steps you can follow: - -To install Scala and ZIO-HTTP, you'll need to have a few prerequisites in place. Here are the steps you can follow: - -1. **Java Development Kit (JDK):** Scala runs on the Java Virtual Machine (JVM), so you'll need to have a JDK installed. Scala 2.13.x and ZIO-HTTP 1.0.x are compatible with Java 8 or later versions. Ensure that you have a JDK installed on your system by executing the `java -version` command in your terminal or command prompt. If Java is not installed, you can download it from the official Oracle website or use a package manager like Homebrew (for macOS) or apt (for Ubuntu). - -2. **Scala Build Tool (sbt):** sbt is the recommended build tool for Scala projects. It simplifies project setup and dependency management. To install sbt, you can follow the installation instructions provided on the sbt website (https://www.scala-sbt.org/download.html). Make sure to download and install the appropriate version for your operating system. [scala](https://www.scala-lang.org/) - -3. **IDE or Text Editor (Optional):** While not strictly necessary, having an Integrated Development Environment (IDE) or a text editor with Scala support can enhance your development experience. Popular choices include IntelliJ IDEA with the Scala plugin, Visual Studio Code with the Scala Metals extension, or Sublime Text with the Scala Syntax package. - -Once you have these prerequisites set up, you can proceed with creating a new Scala project and adding ZIO-HTTP as a dependency using sbt. - -Follow this steps: - -1. Create a new directory for your project `first-zio-http-app` name it what ever you want. - -2. Inside the project directory, create a new `build.sbt` file and open it with a text editor. -3. Add the following lines to `build.sbt` to define the project and its dependencies: - - ```scala - name := "YourProjectName" - - version := "1.0" - - scalaVersion := "2.13.6" - - libraryDependencies ++= Seq( - "dev.zio" %% "zio-http" % "3.0.0-RC2" - ) - - Compile / unmanagedSourceDirectories += baseDirectory.value / "src" - - ``` - -4. Save the `build.sbt` file. - -5. create a `src` direcotry in the same project directory This is where is `MainApp.scala` will reside in. - -6. navigate to the `src` directory and create a scala file name it `MainApp.scala` add the following lines in it. - - ```scala - import zio._ - import zio.http._ - - object MainApp extends ZIOAppDefault { - - val app: App[Any] = - Http.collect[Request] { - case Method.GET -> Root / "text" => Response.text("Hello World!") - case Method.GET -> Root / "fruits" / "b" => Response.text("banana") - case Method.GET -> Root / "json" => Response.json("""{"greetings": "Hello World!"}""") - } - - override val run = - Server.serve(app).provide(Server.default) - } - ``` -7. move over to the root directory and run the command `sbt run` - - -
-
- -**Let's break down what the code does ?** - -The code defines an HTTP server using the ZIO HTTP library: - -- Imports: The code imports the necessary dependencies from the ZIO and ZIO HTTP libraries. - -- `MainApp` object: The `MainApp` object serves as the entry point of the application. - -- `app` value: The `app` value is an instance of `zio.http.Http.App[Any]`, which represents an HTTP application. It is constructed using the `Http.collect` method, which allows you to define routes and their corresponding responses. - -- Route Definitions: Within the `Http.collect` block, three routes are defined using pattern matching: - - - `case Method.GET -> Root / "text"`: This route matches a GET request with the path "/text". It responds with a text response containing the message "Hello World!". - - `case Method.GET -> Root / "fruits" / "b"`: This route matches a GET request with the path "/fruits/b". It responds with a text response containing the word "banana". - - `case Method.GET -> Root / "json"`: This route matches a GET request with the path "/json". It responds with a JSON response containing the message `{"greetings": "Hello World!"}`. - -- `run` method: The `run` method is overridden from `ZIOAppDefault` and serves as the entry point for running the application. It starts an HTTP server using `Server.serve`, passing the `app` as the application to serve. The server is provided with a default configuration using `Server.default`. - -
-
- -To curl the routes defined in your ZIO HTTP application, you can use the following commands: - -1. Route: GET /text - -```shell -curl -X GET http://localhost:8080/text -``` - -2. Route: GET /fruits/b - -```shell -curl -X GET http://localhost:8080/fruits/b -``` - -3. Route: GET /json - -```shell -curl -X GET http://localhost:8080/json -``` - -**You can find the source code of the Complete implementation [Here](https://github.com/daveads/zio-http-examples)** \ No newline at end of file From 4b9f9f5bca7448cc68fb4a3b5bf7544c15ff4c57 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 20 Dec 2023 11:59:35 +0100 Subject: [PATCH 69/71] conflict --- README.md | 166 +++++++----------------------------------------------- 1 file changed, 20 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index 9fca11c7b6..1727665fb9 100644 --- a/README.md +++ b/README.md @@ -13,171 +13,45 @@ ZIO HTTP is a scala library for building http apps. It is powered by ZIO and [Ne Setup via `build.sbt`: ```scala -package example - -import zio._ - -import zio.http._ - -object RequestStreaming extends ZIOAppDefault { - - val app: HttpApp[Any] = - Routes( - Method.GET / "text" -> handler(Response.text("Hello World!")) - ).toHttpApp - - // Creating HttpData from the stream - // This works for file of any size - val data = Body.fromStream(stream) - - Response(body = data) - } - - // Run it like any simple app - val run: UIO[ExitCode] = - Server.serve(app).provide(Server.default).exitCode -} +libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0-RC4" ``` -ZIO-HTTP provides a core library, `zhttp-http`, which includes the base HTTP implementation and server/client capabilities based on ZIO. Additional functionality like serverless support, templating, and websockets are available through separate add-on modules. ZIO-HTTP applications can be easily integrated into different deployment platforms, such as server-based, serverless, or compiled to native binaries. - -The principles of ZIO-HTTP are: - -- Application as a Function: HTTP services in ZIO-HTTP are composed of simple functions. The `HttpApp` type represents a function from an `HttpRequest` to a `ZIO` effect that produces an `HttpResponse`. -- Immutability: Entities in ZIO-HTTP are immutable by default, promoting functional programming principles. -- Symmetric: The same `HttpApp` interface is used for both defining HTTP services and making HTTP requests. This enables easy testing and integration of services without requiring an HTTP container. -- Minimal Dependencies: The core `zhttp-http` module has minimal dependencies, and additional add-on modules only include dependencies required for specific functionality. -- Testability: ZIO-HTTP supports easy in-memory and port-based testing of individual endpoints, applications, websockets/SSE, and complete suites of microservices. -- Portability: ZIO-HTTP applications are portable across different deployment platforms, making them versatile and adaptable. - -By leveraging the power of ZIO and the simplicity of functional programming, ZIO-HTTP provides a robust and flexible toolkit for building scalable and composable HTTP services in Scala. - -## Quickstart - -Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart](https://github.com/zio/zio-http/tree/main/zio-http-example) guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. - -## Module feature overview - -Core: - -- Lightweight and performant HTTP handler and message objects -- Powerful routing system with support for path-based and parameterized routes -- Typesafe HTTP message construction and deconstruction -- Extensible filters for common HTTP functionalities such as caching, compression, and request/response logging -- Support for cookie handling -- Servlet implementation for integration with Servlet containers -- Built-in support for launching applications with an embedded server backend - -Client: - -- Robust and flexible HTTP client with support for synchronous and asynchronous operations -- Adapters for popular HTTP client libraries such as Apache HttpClient, OkHttp, and Jetty HttpClient -- Websocket client with blocking and non-blocking modes -- GraphQL client integration for consuming GraphQL APIs - -Server: - -- Lightweight server backend spin-up for various platforms including Apache, Jetty, Netty, and SunHttp -- Support for SSE (Server-Sent Events) and Websocket communication -- Easy customization of underlying server backend -- Native-friendly for compilation with GraalVM and Quarkus - -Serverless: - -- Function-based support for building serverless HTTP and event-driven applications -- Adapters for AWS Lambda, Google Cloud Functions, Azure Functions, and other serverless platforms -- Custom AWS Lambda runtime for improved performance and reduced startup time +**NOTES ON VERSIONING:** -Contract: +- Older library versions `1.x` or `2.x` with organization `io.d11` of ZIO Http are derived from Dream11, the organization that donated ZIO Http to the ZIO organization in 2022. +- Newer library versions, starting in 2023 and resulting from the ZIO organization (`dev.zio`) started with `0.0.x`, reaching `1.0.0` release candidates in April of 2023 -- Typesafe HTTP contract definition with support for path parameters, query parameters, headers, and request/response bodies -- Automatic validation of incoming requests based on contract definition -- Self-documenting routes with built-in support for OpenAPI (Swagger) descriptions +## Getting Started -Templating: - -- Pluggable templating system support for popular template engines such as Dust, Freemarker, Handlebars, and Thymeleaf -- Caching and hot-reload template support for efficient rendering - -Message Formats: - -- First-class support for various message formats such as JSON, XML, YAML, and CSV -- Seamless integration with popular libraries like Jackson, Gson, and Moshi for automatic marshalling and unmarshalling - -Resilience: - -- Integration with Resilience4J for implementing resilience patterns such as circuit breakers, retries, rate-limiting, and bulkheading - -Metrics: - -- Support for integrating zio-http applications with Micrometer for monitoring and metrics collection - -Security: - -- OAuth support for implementing authorization flows with popular providers like Auth0, Google, Facebook, and more -- Digest authentication support for secure client-server communication - -Cloud Native: - -- Tooling and utilities for operating zio-http applications in cloud environments such as Kubernetes and CloudFoundry -- Support for 12-factor configuration, dual-port servers, and health checks - -Testing: - -- Approval testing extensions for testing zio-http Request and Response messages -- Chaos testing API for injecting failure modes and evaluating application behavior under different failure conditions -- Matchers for popular testing frameworks like Hamkrest, Kotest, and Strikt - -Service Virtualization: - -- Record and replay HTTP contracts to simulate virtualized services using Servirtium Markdown format -- Includes Servirtium MiTM (Man-in-the-Middle) server for capturing and replaying HTTP interactions - -WebDriver: - -- Lightweight implementation of Selenium WebDriver for testing zio-http applications - -These features provide a comprehensive set of tools and capabilities for building scalable, performant, and secure HTTP applications with zio-http. - -## Example - -This brief illustration is intended to showcase the ease and capabilities of zio-http. Additionally, refer to the quickstart guide for a minimalistic starting point that demonstrates serving and consuming HTTP services with dynamic routing. - -To install, add these dependencies to your `build.sbt`: +A simple Http server can be built using a few lines of code. ```scala -package example - import zio._ -import zio.http.HttpAppMiddleware.basicAuth import zio.http._ -object BasicAuth extends ZIOAppDefault { - - // Define an HTTP application that requires a JWT claim - val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => - Response.text(s"Welcome to the ZIO party! ${name}") - } +object HelloWorld extends ZIOAppDefault { - // Compose all the HttpApps together - val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") + val app: HttpApp[Any] = + Routes( + Method.GET / "text" -> handler(Response.text("Hello World!")) + ).toHttpApp - // Run the application like any simple app - val run = Server.serve(app).provide(Server.default) + override val run = + Server.serve(app).provide(Server.default) } ``` -## Explanation of the code above - -- The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. +## Steps to run an example -- The code imports the necessary dependencies from ZIO and ZIO HTTP. +1. Edit the [RunSettings](https://github.com/zio/zio-http/blob/main/project/BuildHelper.scala#L107) - modify `className` to the example you'd like to run. +2. From sbt shell, run `~example/reStart`. You should see `Server started on port: 8080`. +3. Send curl request for defined `http Routes`, for eg : `curl -i "http://localhost:8080/text"` for `example.HelloWorld`. -- The user value represents an HTTP application that requires a JWT claim. It uses the Http.collect combinator to pattern match on GET requests with a specific path pattern (Root / "user" / name / "greet") and responds with a greeting message that includes the extracted name. +## Watch Mode -- The app value is created by composing the user HTTP application with the basicAuth middleware. The basicAuth function takes a username and password as arguments and returns a middleware that performs basic authentication. It applies basic authentication with the username "admin" and password "admin" to the user application. +You can use the [sbt-revolver] plugin to start the server and run it in watch mode using `~ reStart` command on the SBT console. -- Finally, the server is run using the Server.serve method. The app is provided as the HTTP application, and Server.default is provided as the server configuration. The server configuration contains default settings for the server, such as the port to listen on. The run value represents the execution of the server. It starts the ZIO runtime and executes the server, making it ready to receive and respond to HTTP requests. +[sbt-revolver]: https://github.com/spray/sbt-revolver ## Documentation From f1b379962a18f35e9a9850e7f646cb04bd76d141 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 20 Dec 2023 13:09:44 +0100 Subject: [PATCH 70/71] sidebars --- docs/how-to-guides/static-files.md | 1 - docs/sidebars.js | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to-guides/static-files.md b/docs/how-to-guides/static-files.md index 81ae81d392..d3d9ba9244 100644 --- a/docs/how-to-guides/static-files.md +++ b/docs/how-to-guides/static-files.md @@ -1,7 +1,6 @@ --- id: static-files title: "Serving Static Files" -sidebar_label: "Static Files" --- how to host static resources like images, CSS and JavaScript files using ZIO HTTP's built-in middleware. diff --git a/docs/sidebars.js b/docs/sidebars.js index 6928b7539c..e6f9fec626 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -63,6 +63,7 @@ const sidebars = { "how-to-guides/multipart-form-data", "how-to-guides/how-to-utilize-signed-cookies", "how-to-guides/how-to-handle-WebSocket-exceptions-and-errors", + "how-to-guides/static-files", ] }, { From a787cf7da8a8c9ff3a1467eb0f9bd9c165e55cec Mon Sep 17 00:00:00 2001 From: Adejumo David Adewale Date: Wed, 20 Dec 2023 12:35:28 +0000 Subject: [PATCH 71/71] readme --- README.md | 168 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 1727665fb9..be3fa9b4f4 100644 --- a/README.md +++ b/README.md @@ -6,52 +6,178 @@ ZIO HTTP is a scala library for building http apps. It is powered by ZIO and [Netty](https://netty.io/) and aims at being the defacto solution for writing, highly scalable and performant web applications using idiomatic Scala. -[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Releases](https://img.shields.io/nexus/r/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Release)](https://oss.sonatype.org/content/repositories/releases/dev/zio/zio-http_2.13/) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![javadoc](https://javadoc.io/badge2/dev.zio/zio-http-docs_2.13/javadoc.svg)](https://javadoc.io/doc/dev.zio/zio-http-docs_2.13) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) +[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-http/workflows/Continuous%20Integration/badge.svg) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-http_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-http_2.13/) [![ZIO Http](https://img.shields.io/github/stars/zio/zio-http?style=social)](https://github.com/zio/zio-http) ## Installation Setup via `build.sbt`: ```scala -libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0-RC4" +package example + +import zio._ + +import zio.http._ + +object RequestStreaming extends ZIOAppDefault { + + val app: HttpApp[Any] = + Routes( + Method.GET / "text" -> handler(Response.text("Hello World!")) + ).toHttpApp + + // Creating HttpData from the stream + // This works for file of any size + val data = Body.fromStream(stream) + + Response(body = data) + } + + // Run it like any simple app + val run: UIO[ExitCode] = + Server.serve(app).provide(Server.default).exitCode +} ``` -**NOTES ON VERSIONING:** +ZIO-HTTP provides a core library, `zhttp-http`, which includes the base HTTP implementation and server/client capabilities based on ZIO. Additional functionality like serverless support, templating, and websockets are available through separate add-on modules. ZIO-HTTP applications can be easily integrated into different deployment platforms, such as server-based, serverless, or compiled to native binaries. + +The principles of ZIO-HTTP are: + +- Application as a Function: HTTP services in ZIO-HTTP are composed of simple functions. The `HttpApp` type represents a function from an `HttpRequest` to a `ZIO` effect that produces an `HttpResponse`. +- Immutability: Entities in ZIO-HTTP are immutable by default, promoting functional programming principles. +- Symmetric: The same `HttpApp` interface is used for both defining HTTP services and making HTTP requests. This enables easy testing and integration of services without requiring an HTTP container. +- Minimal Dependencies: The core `zhttp-http` module has minimal dependencies, and additional add-on modules only include dependencies required for specific functionality. +- Testability: ZIO-HTTP supports easy in-memory and port-based testing of individual endpoints, applications, websockets/SSE, and complete suites of microservices. +- Portability: ZIO-HTTP applications are portable across different deployment platforms, making them versatile and adaptable. + +By leveraging the power of ZIO and the simplicity of functional programming, ZIO-HTTP provides a robust and flexible toolkit for building scalable and composable HTTP services in Scala. + +## Quickstart + +Eager to start coding without delay? If you're in a hurry, you can follow the [quickstart](https://github.com/zio/zio-http/tree/main/zio-http-example) guide or explore the [examples repository](https://github.com/zio/zio-http/tree/main/zio-http-example), which demonstrates different use cases and features of ZIO-HTTP. + +## Module feature overview + +Core: + +- Lightweight and performant HTTP handler and message objects +- Powerful routing system with support for path-based and parameterized routes +- Typesafe HTTP message construction and deconstruction +- Extensible filters for common HTTP functionalities such as caching, compression, and request/response logging +- Support for cookie handling +- Servlet implementation for integration with Servlet containers +- Built-in support for launching applications with an embedded server backend + +Client: + +- Robust and flexible HTTP client with support for synchronous and asynchronous operations +- Adapters for popular HTTP client libraries such as Apache HttpClient, OkHttp, and Jetty HttpClient +- Websocket client with blocking and non-blocking modes +- GraphQL client integration for consuming GraphQL APIs + +Server: + +- Lightweight server backend spin-up for various platforms including Apache, Jetty, Netty, and SunHttp +- Support for SSE (Server-Sent Events) and Websocket communication +- Easy customization of underlying server backend +- Native-friendly for compilation with GraalVM and Quarkus + +Serverless: + +- Function-based support for building serverless HTTP and event-driven applications +- Adapters for AWS Lambda, Google Cloud Functions, Azure Functions, and other serverless platforms +- Custom AWS Lambda runtime for improved performance and reduced startup time -- Older library versions `1.x` or `2.x` with organization `io.d11` of ZIO Http are derived from Dream11, the organization that donated ZIO Http to the ZIO organization in 2022. -- Newer library versions, starting in 2023 and resulting from the ZIO organization (`dev.zio`) started with `0.0.x`, reaching `1.0.0` release candidates in April of 2023 +Contract: -## Getting Started +- Typesafe HTTP contract definition with support for path parameters, query parameters, headers, and request/response bodies +- Automatic validation of incoming requests based on contract definition +- Self-documenting routes with built-in support for OpenAPI (Swagger) descriptions -A simple Http server can be built using a few lines of code. +Templating: + +- Pluggable templating system support for popular template engines such as Dust, Freemarker, Handlebars, and Thymeleaf +- Caching and hot-reload template support for efficient rendering + +Message Formats: + +- First-class support for various message formats such as JSON, XML, YAML, and CSV +- Seamless integration with popular libraries like Jackson, Gson, and Moshi for automatic marshalling and unmarshalling + +Resilience: + +- Integration with Resilience4J for implementing resilience patterns such as circuit breakers, retries, rate-limiting, and bulkheading + +Metrics: + +- Support for integrating zio-http applications with Micrometer for monitoring and metrics collection + +Security: + +- OAuth support for implementing authorization flows with popular providers like Auth0, Google, Facebook, and more +- Digest authentication support for secure client-server communication + +Cloud Native: + +- Tooling and utilities for operating zio-http applications in cloud environments such as Kubernetes and CloudFoundry +- Support for 12-factor configuration, dual-port servers, and health checks + +Testing: + +- Approval testing extensions for testing zio-http Request and Response messages +- Chaos testing API for injecting failure modes and evaluating application behavior under different failure conditions +- Matchers for popular testing frameworks like Hamkrest, Kotest, and Strikt + +Service Virtualization: + +- Record and replay HTTP contracts to simulate virtualized services using Servirtium Markdown format +- Includes Servirtium MiTM (Man-in-the-Middle) server for capturing and replaying HTTP interactions + +WebDriver: + +- Lightweight implementation of Selenium WebDriver for testing zio-http applications + +These features provide a comprehensive set of tools and capabilities for building scalable, performant, and secure HTTP applications with zio-http. + +## Example + +This brief illustration is intended to showcase the ease and capabilities of zio-http. Additionally, refer to the quickstart guide for a minimalistic starting point that demonstrates serving and consuming HTTP services with dynamic routing. + +To install, add these dependencies to your `build.sbt`: ```scala +package example + import zio._ +import zio.http.HttpAppMiddleware.basicAuth import zio.http._ -object HelloWorld extends ZIOAppDefault { +object BasicAuth extends ZIOAppDefault { - val app: HttpApp[Any] = - Routes( - Method.GET / "text" -> handler(Response.text("Hello World!")) - ).toHttpApp + // Define an HTTP application that requires a JWT claim + val user: HttpApp[Any, Nothing] = Http.collect[Request] { case Method.GET -> Root / "user" / name / "greet" => + Response.text(s"Welcome to the ZIO party! ${name}") + } + + // Compose all the HttpApps together + val app: HttpApp[Any, Nothing] = user @@ basicAuth("admin", "admin") - override val run = - Server.serve(app).provide(Server.default) + // Run the application like any simple app + val run = Server.serve(app).provide(Server.default) } ``` -## Steps to run an example +## Explanation of the code above + +- The BasicAuth object extends ZIOAppDefault, which is a trait that provides a default implementation for running ZIO applications. -1. Edit the [RunSettings](https://github.com/zio/zio-http/blob/main/project/BuildHelper.scala#L107) - modify `className` to the example you'd like to run. -2. From sbt shell, run `~example/reStart`. You should see `Server started on port: 8080`. -3. Send curl request for defined `http Routes`, for eg : `curl -i "http://localhost:8080/text"` for `example.HelloWorld`. +- The code imports the necessary dependencies from ZIO and ZIO HTTP. -## Watch Mode +- The user value represents an HTTP application that requires a JWT claim. It uses the Http.collect combinator to pattern match on GET requests with a specific path pattern (Root / "user" / name / "greet") and responds with a greeting message that includes the extracted name. -You can use the [sbt-revolver] plugin to start the server and run it in watch mode using `~ reStart` command on the SBT console. +- The app value is created by composing the user HTTP application with the basicAuth middleware. The basicAuth function takes a username and password as arguments and returns a middleware that performs basic authentication. It applies basic authentication with the username "admin" and password "admin" to the user application. -[sbt-revolver]: https://github.com/spray/sbt-revolver +- Finally, the server is run using the Server.serve method. The app is provided as the HTTP application, and Server.default is provided as the server configuration. The server configuration contains default settings for the server, such as the port to listen on. The run value represents the execution of the server. It starts the ZIO runtime and executes the server, making it ready to receive and respond to HTTP requests. ## Documentation