Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Scala Server Toolkit

[![Build Status](https://travis-ci.org/avast/scala-server-toolkit.svg?branch=master)](https://travis-ci.org/avast/scala-server-toolkit)
[![Maven Central](https://img.shields.io/maven-central/v/com.avast/scala-server-toolkit-pureconfig_2.12)](https://repo1.maven.org/maven2/com/avast/scala-server-toolkit-pureconfig_2.12/)
[![Maven Central](https://img.shields.io/maven-central/v/com.avast/scala-server-toolkit-http4s-blaze-server_2.12)](https://repo1.maven.org/maven2/com/avast/scala-server-toolkit-http4s-blaze-server_2.12/)
[![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-brightgreen.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org)

This project is a culmination of years of Scala development at Avast and tries to represent the best practices of Scala server development
Expand Down
29 changes: 26 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,22 @@ lazy val commonSettings = Seq(
libraryDependencies ++= Seq(
compilerPlugin(Dependencies.kindProjector),
Dependencies.catsEffect,
Dependencies.Test.scalaTest
Dependencies.logbackClassic % Test,
Dependencies.scalaTest % Test
),
Test / publishArtifact := false
)

lazy val root = project
.in(file("."))
.aggregate(example, jvmExecution, jvmSsl, jvmSystem, pureconfig)
.aggregate(example, http4sBlazeClient, http4sBlazeServer, jvmExecution, jvmSsl, jvmSystem, pureconfig)
.settings(
name := "scala-server-toolkit",
publish / skip := true
)

lazy val example = project
.dependsOn(jvmExecution, jvmSsl, jvmSystem, pureconfig)
.dependsOn(jvmExecution, http4sBlazeClient, http4sBlazeServer, jvmSsl, jvmSystem, pureconfig)
.enablePlugins(MdocPlugin)
.settings(
commonSettings,
Expand All @@ -46,6 +47,28 @@ lazy val example = project
)
)

lazy val http4sBlazeClient = project
.in(file("http4s-blaze-client"))
.dependsOn(jvmSsl)
.settings(commonSettings)
.settings(
name := "scala-server-toolkit-http4s-blaze-client",
libraryDependencies += Dependencies.http4sBlazeClient
)

lazy val http4sBlazeServer = project
.in(file("http4s-blaze-server"))
.dependsOn(http4sBlazeClient % Test)
.settings(commonSettings)
.settings(
name := "scala-server-toolkit-http4s-blaze-server",
libraryDependencies ++= Seq(
Dependencies.http4sBlazeServer,
Dependencies.http4sDsl,
Dependencies.slf4jApi
)
)

lazy val jvmExecution = project
.in(file("jvm-execution"))
.settings(
Expand Down
92 changes: 92 additions & 0 deletions docs/http4s.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Module http4s

[![Maven Central](https://img.shields.io/maven-central/v/com.avast/scala-server-toolkit-http4s-blaze-server_2.12)](https://repo1.maven.org/maven2/com/avast/scala-server-toolkit-http4s-blaze-server_2.12/)

`libraryDependencies += "com.avast" %% "scala-server-toolkit-http4s-blaze-server" % "<VERSION>"`

There are `http4s-*` modules that provide easy initialization of a server and a client. Http4s is an interface with multiple possible
implementations - for now we provide only implementations based on [Blaze](https://github.com/http4s/blaze).

Both server and client are configured via configuration `case class` which contains default values taken from the underlying implementations.

```scala
import cats.effect._
import com.avast.server.toolkit.execution.ExecutorModule
import com.avast.server.toolkit.http4s._
import com.avast.server.toolkit.system.console.ConsoleModule
import org.http4s.dsl.Http4sDsl
import org.http4s.HttpRoutes
import zio.DefaultRuntime
import zio.interop.catz._
import zio.interop.catz.implicits._
import zio.Task

implicit val runtime = new DefaultRuntime {} // this is just needed in example

val dsl = Http4sDsl[Task] // this is just needed in example
import dsl._

val routes = Http4sRouting.make {
HttpRoutes.of[Task] {
case GET -> Root / "hello" => Ok("Hello World!")
}
}

val resource = for {
executorModule <- ExecutorModule.makeDefault[Task]
console = ConsoleModule.make[Task]
server <- Http4sBlazeServerModule.make[Task](Http4sBlazeServerConfig("127.0.0.1", 0), routes, executorModule.executionContext)
client <- Http4sBlazeClient.make[Task](Http4sBlazeClientConfig(), executorModule.executionContext)
} yield (server, client, console)

val program = resource
.use {
case (server, client, console) =>
client
.expect[String](s"http://127.0.0.1:${server.address.getPort}/hello")
.flatMap(console.printLine)
}
```

```scala
runtime.unsafeRun(program)
// Hello World!
```

## Middleware

### Correlation ID Middleware

```scala
import cats.effect._
import com.avast.server.toolkit.execution.ExecutorModule
import com.avast.server.toolkit.http4s._
import com.avast.server.toolkit.http4s.middleware.CorrelationIdMiddleware
import org.http4s.dsl.Http4sDsl
import org.http4s.HttpRoutes
import zio.DefaultRuntime
import zio.interop.catz._
import zio.interop.catz.implicits._
import zio.Task

val dsl = Http4sDsl[Task] // this is just needed in example
import dsl._

implicit val runtime = new DefaultRuntime {} // this is just needed in example

for {
middleware <- Resource.liftF(CorrelationIdMiddleware.default[Task])
executorModule <- ExecutorModule.makeDefault[Task]
routes = Http4sRouting.make {
middleware.wrap {
HttpRoutes.of[Task] {
case req @ GET -> Root =>
// val correlationId = middleware.retrieveCorrelationId(req)
???
}
}
}
server <- Http4sBlazeServerModule.make[Task](Http4sBlazeServerConfig.localhost8080, routes, executorModule.executionContext)
} yield server
```

5 changes: 3 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* [Getting Started](#getting-started)
* [Rationale](rationale.md)
* [Modules http4s](http4s.md)
* [Modules JVM](jvm.md)
* [Module PureConfig](pureconfig.md)

Expand All @@ -11,6 +12,6 @@ Creating a simple HTTP server is as easy as this:

#### build.sbt

[![Maven Central](https://img.shields.io/maven-central/v/com.avast/scala-server-toolkit-pureconfig_2.12)](https://repo1.maven.org/maven2/com/avast/scala-server-toolkit-pureconfig_2.12/)
[![Maven Central](https://img.shields.io/maven-central/v/com.avast/scala-server-toolkit-http4s-blaze-server_2.12)](https://repo1.maven.org/maven2/com/avast/scala-server-toolkit-http4s-blaze-server_2.12/)

`libraryDependencies += "com.avast" %% "scala-server-toolkit-pureconfig" % "<VERSION>"`
`libraryDependencies += "com.avast" %% "scala-server-toolkit-http4s-blaze-server" % "<VERSION>"`
8 changes: 4 additions & 4 deletions docs/jvm.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ val program = for {
console = ConsoleModule.make[Task]
_ <- console.printLine(s"Random number: $randomNumber")
} yield ()
// program: zio.ZIO[Any, Throwable, Unit] = zio.ZIO$FlatMap@2f1f9515
// program: zio.ZIO[Any, Throwable, Unit] = zio.ZIO$FlatMap@4cc26df

val runtime = new DefaultRuntime {} // this is just in example
// runtime: AnyRef with DefaultRuntime = repl.Session$App$$anon$1@33ebe4f0 // this is just in example
val runtime = new DefaultRuntime {} // this is just needed in example
// runtime: AnyRef with DefaultRuntime = repl.Session$App$$anon$1@3bab95ca // this is just needed in example
runtime.unsafeRun(program)
// Random number: 776310297
// Random number: 1797916077
```

2 changes: 0 additions & 2 deletions docs/pureconfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ import zio.Task
final case class ServerConfiguration(listenAddress: String, listenPort: Int)

implicit val serverConfigurationReader: ConfigReader[ServerConfiguration] = deriveReader
// serverConfigurationReader: ConfigReader[ServerConfiguration] = pureconfig.generic.DerivedConfigReader1$$anon$3@2a8eed58

val maybeConfiguration = PureConfigModule.make[Task, ServerConfiguration]
// maybeConfiguration: Task[Either[cats.data.NonEmptyList[String], ServerConfiguration]] = zio.ZIO$EffectPartial@352bea0e
```

90 changes: 90 additions & 0 deletions example/src/main/mdoc/http4s.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Module http4s

[![Maven Central](https://img.shields.io/maven-central/v/com.avast/scala-server-toolkit-http4s-blaze-server_2.12)](https://repo1.maven.org/maven2/com/avast/scala-server-toolkit-http4s-blaze-server_2.12/)

`libraryDependencies += "com.avast" %% "scala-server-toolkit-http4s-blaze-server" % "<VERSION>"`

There are `http4s-*` modules that provide easy initialization of a server and a client. Http4s is an interface with multiple possible
implementations - for now we provide only implementations based on [Blaze](https://github.com/http4s/blaze).

Both server and client are configured via configuration `case class` which contains default values taken from the underlying implementations.

```scala mdoc:silent:reset-class
import cats.effect._
import com.avast.server.toolkit.execution.ExecutorModule
import com.avast.server.toolkit.http4s._
import com.avast.server.toolkit.system.console.ConsoleModule
import org.http4s.dsl.Http4sDsl
import org.http4s.HttpRoutes
import zio.DefaultRuntime
import zio.interop.catz._
import zio.interop.catz.implicits._
import zio.Task

implicit val runtime = new DefaultRuntime {} // this is just needed in example

val dsl = Http4sDsl[Task] // this is just needed in example
import dsl._

val routes = Http4sRouting.make {
HttpRoutes.of[Task] {
case GET -> Root / "hello" => Ok("Hello World!")
}
}

val resource = for {
executorModule <- ExecutorModule.makeDefault[Task]
console = ConsoleModule.make[Task]
server <- Http4sBlazeServerModule.make[Task](Http4sBlazeServerConfig("127.0.0.1", 0), routes, executorModule.executionContext)
client <- Http4sBlazeClient.make[Task](Http4sBlazeClientConfig(), executorModule.executionContext)
} yield (server, client, console)

val program = resource
.use {
case (server, client, console) =>
client
.expect[String](s"http://127.0.0.1:${server.address.getPort}/hello")
.flatMap(console.printLine)
}
```

```scala mdoc
runtime.unsafeRun(program)
```

## Middleware

### Correlation ID Middleware

```scala mdoc:silent:reset
import cats.effect._
import com.avast.server.toolkit.execution.ExecutorModule
import com.avast.server.toolkit.http4s._
import com.avast.server.toolkit.http4s.middleware.CorrelationIdMiddleware
import org.http4s.dsl.Http4sDsl
import org.http4s.HttpRoutes
import zio.DefaultRuntime
import zio.interop.catz._
import zio.interop.catz.implicits._
import zio.Task

val dsl = Http4sDsl[Task] // this is just needed in example
import dsl._

implicit val runtime = new DefaultRuntime {} // this is just needed in example

for {
middleware <- Resource.liftF(CorrelationIdMiddleware.default[Task])
executorModule <- ExecutorModule.makeDefault[Task]
routes = Http4sRouting.make {
middleware.wrap {
HttpRoutes.of[Task] {
case req @ GET -> Root =>
// val correlationId = middleware.retrieveCorrelationId(req)
???
}
}
}
server <- Http4sBlazeServerModule.make[Task](Http4sBlazeServerConfig.localhost8080, routes, executorModule.executionContext)
} yield server
```
5 changes: 3 additions & 2 deletions example/src/main/mdoc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* [Getting Started](#getting-started)
* [Rationale](rationale.md)
* [Modules http4s](http4s.md)
* [Modules JVM](jvm.md)
* [Module PureConfig](pureconfig.md)

Expand All @@ -11,6 +12,6 @@ Creating a simple HTTP server is as easy as this:

#### build.sbt

[![Maven Central](https://img.shields.io/maven-central/v/com.avast/scala-server-toolkit-pureconfig_2.12)](https://repo1.maven.org/maven2/com/avast/scala-server-toolkit-pureconfig_2.12/)
[![Maven Central](https://img.shields.io/maven-central/v/com.avast/scala-server-toolkit-http4s-blaze-server_2.12)](https://repo1.maven.org/maven2/com/avast/scala-server-toolkit-http4s-blaze-server_2.12/)

`libraryDependencies += "com.avast" %% "scala-server-toolkit-pureconfig" % "<VERSION>"`
`libraryDependencies += "com.avast" %% "scala-server-toolkit-http4s-blaze-server" % "<VERSION>"`
2 changes: 1 addition & 1 deletion example/src/main/mdoc/jvm.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ val program = for {
_ <- console.printLine(s"Random number: $randomNumber")
} yield ()

val runtime = new DefaultRuntime {} // this is just in example
val runtime = new DefaultRuntime {} // this is just needed in example
runtime.unsafeRun(program)
```
2 changes: 1 addition & 1 deletion example/src/main/mdoc/pureconfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ that your application's configuration will be in [HOCON](https://github.com/ligh

Loading of configuration is side-effectful so it is wrapped in `F` which is `Sync`. This module also tweaks the error messages a little.

```scala mdoc
```scala mdoc:silent
import com.avast.server.toolkit.pureconfig._
import pureconfig.ConfigReader
import pureconfig.generic.semiauto.deriveReader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import zio.{Task, ZIO}
object Main extends CatsApp {

def program: Resource[Task, Unit] = {

for {
configuration <- Resource.liftF(PureConfigModule.makeOrRaise[Task, Configuration])
executorModule <- ExecutorModule.makeFromExecutionContext[Task](runtime.Platform.executor.asEC)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.avast.server.toolkit.http4s

import cats.Traverse
import cats.effect.{ConcurrentEffect, Resource, Sync}
import cats.implicits._
import com.avast.server.toolkit.ssl.{SslContextConfig, SslContextModule}
import javax.net.ssl.SSLContext
import org.http4s.client.Client
import org.http4s.client.blaze.BlazeClientBuilder

import scala.concurrent.ExecutionContext
import scala.language.higherKinds

object Http4sBlazeClient {

/** Makes [[org.http4s.client.Client]] (Blaze) initialized with the given config.
*
* @param executionContext callback handling [[scala.concurrent.ExecutionContext]]
*/
def make[F[_]: ConcurrentEffect](config: Http4sBlazeClientConfig, executionContext: ExecutionContext): Resource[F, Client[F]] = {
for {
maybeSslContext <- Resource.liftF(sslContext(config.sslContext))
client <- {
val builder = BlazeClientBuilder[F](executionContext)
.withResponseHeaderTimeout(config.responseHeaderTimeout)
.withIdleTimeout(config.idleTimeout)
.withRequestTimeout(config.requestTimeout)
.withConnectTimeout(config.connectTimeout)
.withUserAgent(config.userAgent)
.withMaxTotalConnections(config.maxTotalConnections)
.withMaxWaitQueueLimit(config.maxWaitQueueLimit)
.withMaxConnectionsPerRequestKey(Function.const(config.maxConnectionsPerRequestkey))
.withCheckEndpointAuthentication(config.checkEndpointIdentification)
.withMaxResponseLineSize(config.maxResponseLineSize)
.withMaxHeaderLength(config.maxHeaderLength)
.withMaxChunkSize(config.maxChunkSize)
.withChunkBufferMaxSize(config.chunkBufferMaxSize)
.withParserMode(config.parserMode)
.withBufferSize(config.bufferSize)

maybeSslContext.map(builder.withSslContext).getOrElse(builder).resource
}
} yield client
}

private def sslContext[F[_]: Sync](maybeSslContextConfig: Option[SslContextConfig]): F[Option[SSLContext]] = {
Traverse[Option].traverse(maybeSslContextConfig)(SslContextModule.make[F])
}

}
Loading