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 diff --git a/docs/dsl/body.md b/docs/Reference/dsl/body.md similarity index 100% rename from docs/dsl/body.md rename to docs/Reference/dsl/body.md diff --git a/docs/dsl/cookies.md b/docs/Reference/dsl/cookies.md similarity index 100% rename from docs/dsl/cookies.md rename to docs/Reference/dsl/cookies.md diff --git a/docs/dsl/headers.md b/docs/Reference/dsl/headers.md similarity index 100% rename from docs/dsl/headers.md rename to docs/Reference/dsl/headers.md diff --git a/docs/dsl/middleware.md b/docs/Reference/dsl/middleware.md similarity index 100% rename from docs/dsl/middleware.md rename to docs/Reference/dsl/middleware.md diff --git a/docs/dsl/request.md b/docs/Reference/dsl/request.md similarity index 100% rename from docs/dsl/request.md rename to docs/Reference/dsl/request.md diff --git a/docs/dsl/response.md b/docs/Reference/dsl/response.md similarity index 100% rename from docs/dsl/response.md rename to docs/Reference/dsl/response.md diff --git a/docs/dsl/routes.md b/docs/Reference/dsl/routes.md similarity index 100% rename from docs/dsl/routes.md rename to docs/Reference/dsl/routes.md diff --git a/docs/dsl/server.md b/docs/Reference/dsl/server.md similarity index 100% rename from docs/dsl/server.md rename to docs/Reference/dsl/server.md diff --git a/docs/dsl/socket/socket.md b/docs/Reference/dsl/socket/socket.md similarity index 100% rename from docs/dsl/socket/socket.md rename to docs/Reference/dsl/socket/socket.md diff --git a/docs/dsl/socket/websocketframe.md b/docs/Reference/dsl/socket/websocketframe.md similarity index 100% rename from docs/dsl/socket/websocketframe.md rename to docs/Reference/dsl/socket/websocketframe.md diff --git a/docs/dsl/template.md b/docs/Reference/dsl/template.md similarity index 100% rename from docs/dsl/template.md rename to docs/Reference/dsl/template.md diff --git a/docs/Reference/json-handling.md b/docs/Reference/json-handling.md new file mode 100644 index 0000000000..6b5ec8f500 --- /dev/null +++ b/docs/Reference/json-handling.md @@ -0,0 +1,96 @@ +--- +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. + +**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 diff --git a/docs/Reference/metrics.md b/docs/Reference/metrics.md new file mode 100644 index 0000000000..a2d2a876c1 --- /dev/null +++ b/docs/Reference/metrics.md @@ -0,0 +1,70 @@ +--- +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.{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] = { + // ... + } + + // ... +} + +object 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 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) +``` + +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 new file mode 100644 index 0000000000..85f7a2dd2d --- /dev/null +++ b/docs/Reference/server-backend.md @@ -0,0 +1,118 @@ +--- +id: server-backend +title: "Server Backend" +--- + +# 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 diff --git a/docs/Reference/websockets.md b/docs/Reference/websockets.md new file mode 100644 index 0000000000..e387398c2d --- /dev/null +++ b/docs/Reference/websockets.md @@ -0,0 +1,64 @@ +--- +id: websockets +title: "Websockets" +--- + +Reference guide for the WebSocket functionality + +WebSocket Server APIs and Classes + +- `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. + +- `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. + +- `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. + +- `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. + +### WebSocket Server Usage + +1. Create a `SocketApp[R]` instance using `Handler.webSocket` to define the WebSocket application logic. + +2. Within the `SocketApp`, use `WebSocketChannel.receiveAll` to handle incoming WebSocket frames and define appropriate responses. + +3. Use `WebSocketChannel.send` to write WebSocket frames back to the client. + +4. Construct an HTTP application (`Http[Any, Nothing, Request, Response]`) that serves WebSocket connections. + +5. Use `Http.collectZIO` to specify routes and associate them with appropriate WebSocket applications. + + +### WebSocket Client APIs and Classes + +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. + +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/concepts/client.md b/docs/concepts/client.md new file mode 100644 index 0000000000..3ae4812536 --- /dev/null +++ b/docs/concepts/client.md @@ -0,0 +1,70 @@ +--- +id: client +title: Client +--- + +# 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._ +import zio.http.netty.NettyConfig + +object ClientWithDecompression 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.addHeader(AcceptEncoding(AcceptEncoding.GZip(), AcceptEncoding.Deflate())).url(url).get("") + 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, + Scope.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 new file mode 100644 index 0000000000..abe0e8bc2b --- /dev/null +++ b/docs/concepts/endpoint.md @@ -0,0 +1,40 @@ +--- +id: endpoint +title: Endpoint +--- + +## Endpoint + +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. 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: + +- 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. + +### Endpoint Implementation: + +- 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. + +- 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. + +### Endpoint Composition: + +- 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. + +### 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. Middleware added to the endpoints will be applied to the HTTP application, ensuring that the specified behavior is enforced for each incoming request. + +### Running an App: + +- 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. + +- 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. + +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 new file mode 100644 index 0000000000..2e9e019a28 --- /dev/null +++ b/docs/concepts/middleware.md @@ -0,0 +1,58 @@ +--- +id: middleware +title: Middleware +--- + +## Middleware Concepts in ZIO HTTP + +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. + +### The Need for Middleware + +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`: + +1. GET a single user by id +2. GET all users + +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. + +### The Concept of Middleware + +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. + +### Composing Middleware + +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. + +### Middleware in Action + +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 userApp = Routes( + Method.GET / "user" / string("name") / "greet" -> handler { (name: String, req: Request) => + Response.text(s"Welcome to the ZIO party! ${name}") + } +).toHttpApp + +val basicAuthMW = Middleware.basicAuth("admin", "admin") +val patchEnv = Middleware.addHeader("X-Environment", "Dev") + +val appWithMiddleware = userApp @@ (basicAuthMW ++ patchEnv) +``` + +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. + +### Advantages of Middleware + +Using middleware offers several benefits: + +1. **Readability**: By removing boilerplate code from the core business logic, the application code becomes more readable and easier to understand. + +2. **Modularity**: Middleware allows us to manage cross-cutting concerns independently, facilitating easier maintenance and updates. + +3. **Testability**: With middleware, we can test concerns independently, which simplifies testing and ensures the core business logic remains clean. + +4. **Reusability**: Middleware can be composed and reused across different routes and applications, promoting code reuse and consistency. + +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 new file mode 100644 index 0000000000..9e8c925019 --- /dev/null +++ b/docs/concepts/request-handling.md @@ -0,0 +1,83 @@ +--- +id: request-handling +title: Request Handling +--- + +## 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 = 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 + + // Creating HttpData from the stream + // This works for file of any size + val data = Body.fromStream(stream) + + Response(body = data) + }).toHttpApp + + // Run it like any simple app + val run: UIO[ExitCode] = + Server.serve(app).provide(Server.default).exitCode +} +``` + +**Explanation**: + +The code defines an HTTP server application using ZIO and ZIO-HTTP, which handles incoming requests and echoes back the request body : + +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`. + +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. + +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. + +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. + +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. \ No newline at end of file diff --git a/docs/concepts/routing.md b/docs/concepts/routing.md new file mode 100644 index 0000000000..c83f169df1 --- /dev/null +++ b/docs/concepts/routing.md @@ -0,0 +1,100 @@ +--- +id: routing +title: Routing +--- + +# 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. + +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. + + +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.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(Method.GET / "users" / int("userId")).out[Int] @@ auth + + val getUserRoute = + getUser.implement { + Handler.fromFunction[Int] { id => + id + } + } + + val getUserPosts = + Endpoint(Method.GET / "users" / int("userId") / "posts" / int("postId")) + .query(query("name")) + .out[List[String]] @@ auth + + val getUserPostsRoute = + 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 = Routes(getUserRoute, getUserPostsRoute) + + val app = routes.toHttpApp // (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: ZIO[Scope, Nothing, Int] = executor(x1) + val result2: ZIO[Scope, Nothing, 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: + +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. + +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. + +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. + +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. + +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. + +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. + +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 new file mode 100644 index 0000000000..df43b44293 --- /dev/null +++ b/docs/concepts/server.md @@ -0,0 +1,73 @@ +--- +id: server +title: Server +--- + +## 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: + +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. + +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. + +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. + +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. The flexible and composable nature of ZIO-HTTP enables developers to create sophisticated and high-performance servers with ease. + +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 diff --git a/docs/examples/advanced/websocket-server.md b/docs/examples/advanced/websocket-server.md deleted file mode 100644 index 2534d5e9de..0000000000 --- a/docs/examples/advanced/websocket-server.md +++ /dev/null @@ -1,61 +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._ -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 diff --git a/docs/examples/basic/http-server.md b/docs/examples/basic/http-server.md deleted file mode 100644 index 0e4b142bdb..0000000000 --- a/docs/examples/basic/http-server.md +++ /dev/null @@ -1,26 +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 { - 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/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/getting-started.md b/docs/getting-started.md index 27d414da6b..e504004855 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,8 +1,10 @@ --- -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 has powerful functional domains that help in creating, modifying, composing apps easily. Let's start with the HTTP domain. diff --git a/docs/examples/advanced/server.md b/docs/how-to-guides/advance-http-sever.md similarity index 88% rename from docs/examples/advanced/server.md rename to docs/how-to-guides/advance-http-sever.md index 947f16d031..584932df39 100644 --- a/docs/examples/advanced/server.md +++ b/docs/how-to-guides/advance-http-sever.md @@ -1,10 +1,15 @@ --- -id: server -title: "Advanced Server Example" -sidebar_label: "Server" +id: advance-http-sever +title: Advance Http server Example --- -```scala mdoc:silent +# Advanced http sever + +This demonstrates an advanced example of creating an HTTP server + +## code + +```scala import scala.util.Try import zio._ diff --git a/docs/examples/advanced/authentication.md b/docs/how-to-guides/authentication.md similarity index 85% rename from docs/examples/advanced/authentication.md rename to docs/how-to-guides/authentication.md index 8411c83788..14fafe98fc 100644 --- a/docs/examples/advanced/authentication.md +++ b/docs/how-to-guides/authentication.md @@ -1,10 +1,13 @@ --- -id: authentication-server +id: authentication title: "Authentication Server Example" -sidebar_label: "Authentication Server" --- -```scala mdoc:silent + +This code shows how to implement a server with bearer authentication middle ware in `zio-http` + + +```scala import java.time.Clock import zio._ @@ -69,4 +72,8 @@ object AuthenticationServer extends ZIOAppDefault { // Run it like any simple app override val run = Server.serve(app).provide(Server.default) } -``` \ No newline at end of file +``` + +### 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/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/examples/advanced/concrete-entity.md b/docs/how-to-guides/concrete-entity.md similarity index 75% rename from docs/examples/advanced/concrete-entity.md rename to docs/how-to-guides/concrete-entity.md index e97b35daca..e0b35ba80e 100644 --- a/docs/examples/advanced/concrete-entity.md +++ b/docs/how-to-guides/concrete-entity.md @@ -1,11 +1,14 @@ --- id: concrete-entity title: "Concrete Entity Example" -sidebar_label: "Concrete Entity" --- -```scala mdoc:silent +`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._ 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/endpoint.md b/docs/how-to-guides/endpoint.md new file mode 100644 index 0000000000..b3d2081591 --- /dev/null +++ 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/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/examples/basic/http-client.md b/docs/how-to-guides/http-client.md similarity index 73% rename from docs/examples/basic/http-client.md rename to docs/how-to-guides/http-client.md index c664a8cf4c..08e7db2fe7 100644 --- a/docs/examples/basic/http-client.md +++ b/docs/how-to-guides/http-client.md @@ -1,10 +1,14 @@ --- id: http-client -title: Http Client Example -sidebar_label: Http Client +title: HTTP Client --- -```scala mdoc:silent + +This example provided demonstrates how to perform an HTTP client request using the zio-http library in Scala with ZIO. + +## code + +```scala import zio._ import zio.http._ @@ -23,4 +27,4 @@ object SimpleClient extends ZIOAppDefault { } -``` \ No newline at end of file +``` diff --git a/docs/examples/basic/https-client.md b/docs/how-to-guides/https-client.md similarity index 85% rename from docs/examples/basic/https-client.md rename to docs/how-to-guides/https-client.md index f5a07397ba..199aaef4dc 100644 --- a/docs/examples/basic/https-client.md +++ b/docs/how-to-guides/https-client.md @@ -1,10 +1,14 @@ --- id: https-client -title: Https Client Example -sidebar_label: Https Client +title: HTTPS Client --- -```scala mdoc:silent +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._ @@ -40,4 +44,4 @@ object HttpsClient extends ZIOAppDefault { } -``` +``` \ No newline at end of file diff --git a/docs/examples/basic/https-server.md b/docs/how-to-guides/https-server.md similarity index 87% rename from docs/examples/basic/https-server.md rename to docs/how-to-guides/https-server.md index 14f2c16aea..315683a95b 100644 --- a/docs/examples/basic/https-server.md +++ b/docs/how-to-guides/https-server.md @@ -1,10 +1,13 @@ --- -id: https-server -title: Https Server Example -sidebar_label: Https Server +id: https-sever +title: Https server --- -```scala mdoc:silent +# 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._ diff --git a/docs/examples/advanced/middleware-basic-authentication.md b/docs/how-to-guides/middleware-basic-authentication.md similarity index 76% rename from docs/examples/advanced/middleware-basic-authentication.md rename to docs/how-to-guides/middleware-basic-authentication.md index f97798a3ba..d5afa218e9 100644 --- a/docs/examples/advanced/middleware-basic-authentication.md +++ b/docs/how-to-guides/middleware-basic-authentication.md @@ -1,9 +1,13 @@ --- id: middleware-basic-authentication title: "Middleware Basic Authentication Example" -sidebar_label: "Middleware Basic Authentication" --- +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._ diff --git a/docs/examples/advanced/middleware-cors-handling.md b/docs/how-to-guides/middleware-cors-handling.md similarity index 83% rename from docs/examples/advanced/middleware-cors-handling.md rename to docs/how-to-guides/middleware-cors-handling.md index 611b2f94f4..bb53e70a75 100644 --- a/docs/examples/advanced/middleware-cors-handling.md +++ b/docs/how-to-guides/middleware-cors-handling.md @@ -1,9 +1,10 @@ --- id: middleware-cors-handling title: "Middleware CORS Handling Example" -sidebar_label: "Middleware CORS Handling" --- +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._ 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..a1e3652bb0 --- /dev/null +++ b/docs/how-to-guides/multipart-form-data.md @@ -0,0 +1,88 @@ +--- +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: 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"))) + } + } yield response + else ZIO.succeed(Response(status = Status.NotFound)) + }, + ).sandbox.toHttpApp + + private def program: ZIO[Client with Server with Scope, 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, + Scope.default, + ) +} +``` \ No newline at end of file diff --git a/docs/examples/advanced/static-files.md b/docs/how-to-guides/static-files.md similarity index 82% rename from docs/examples/advanced/static-files.md rename to docs/how-to-guides/static-files.md index 8108509554..d3d9ba9244 100644 --- a/docs/examples/advanced/static-files.md +++ b/docs/how-to-guides/static-files.md @@ -1,9 +1,10 @@ --- 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. + ```scala mdoc:silent import zio._ import zio.http._ diff --git a/docs/examples/advanced/streaming-file.md b/docs/how-to-guides/streaming-file.md similarity index 91% rename from docs/examples/advanced/streaming-file.md rename to docs/how-to-guides/streaming-file.md index fb0e1b4554..f421554f17 100644 --- a/docs/examples/advanced/streaming-file.md +++ b/docs/how-to-guides/streaming-file.md @@ -1,9 +1,10 @@ --- id: streaming-file title: "Streaming File Example" -sidebar_label: "Streaming File" --- +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 diff --git a/docs/examples/advanced/streaming-response.md b/docs/how-to-guides/streaming-response.md similarity index 89% rename from docs/examples/advanced/streaming-response.md rename to docs/how-to-guides/streaming-response.md index 1389e67d45..19822863ad 100644 --- a/docs/examples/advanced/streaming-response.md +++ b/docs/how-to-guides/streaming-response.md @@ -1,9 +1,10 @@ --- id: streaming-response title: "Streaming Response Example" -sidebar_label: "Streaming Response" --- +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, _} @@ -36,5 +37,4 @@ object StreamingResponse extends ZIOAppDefault { ), ).toHttpApp } - ``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 347f28cd0f..3a6d049f24 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,42 +13,168 @@ 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" % "@VERSION@" +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 + +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: -A simple Http server can be built using a few lines of code. +- 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 -```scala mdoc:silent 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. diff --git a/docs/performance.md b/docs/performance.md new file mode 100644 index 0000000000..24fd2aff5e --- /dev/null +++ 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. diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000000..27f4b77428 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,62 @@ +--- +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. + +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. diff --git a/docs/setup.md b/docs/setup.md index 3d434ca6d3..4a3858363c 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -65,4 +65,4 @@ scalafix will mainly be used as a linting tool during everyday development, for ### 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. +sbt-native-packager can package the application in the most popular formats, for example Docker images, rpm packages or graalVM native images. \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js index fb7f029c3b..e6f9fec626 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -1,13 +1,82 @@ const sidebars = { sidebar: [ { + type: "category", label: "ZIO Http", 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", + "how-to-guides/static-files", + ] + }, + { + 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 +90,8 @@ const sidebars = { "dsl/headers", "dsl/cookies", "dsl/middleware", + "dsl/html", + "dsl/template", { type: "category", label: "DSL", @@ -30,47 +101,11 @@ const sidebars = { "dsl/socket/websocketframe" ] }, - "dsl/template" ] }, - { - 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 diff --git a/docs/tutorials/deeper-dive-into-middleware.md b/docs/tutorials/deeper-dive-into-middleware.md new file mode 100644 index 0000000000..7ea4c7305e --- /dev/null +++ b/docs/tutorials/deeper-dive-into-middleware.md @@ -0,0 +1,66 @@ +--- +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 new file mode 100644 index 0000000000..bac4fc8159 --- /dev/null +++ b/docs/tutorials/deploying-a-zio-http-app.md @@ -0,0 +1,134 @@ +--- +id: deploying-zio-http-application +title: Deploying Zio-http Application +--- + +# Deploying Zio-http Application + +## 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: + +```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 + +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: + +```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 + +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 + +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. \ No newline at end of file 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..c2c0c3f1b6 --- /dev/null +++ b/docs/tutorials/testing-your-zio-http-app.md @@ -0,0 +1,50 @@ +--- +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. + +## 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.