Skip to content

Commit

Permalink
Add module with Http4s Blaze server
Browse files Browse the repository at this point in the history
  • Loading branch information
gregorath committed Oct 26, 2022
1 parent ce653df commit 9bade01
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 4 deletions.
15 changes: 14 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ lazy val root =
`http-scala-fx`,
documentation,
`sttp-scala-fx`,
`java-net-multipart-body-publisher`
`java-net-multipart-body-publisher`,
`http4s-scala-fx`
)

lazy val `scala-fx` = project.settings(scalafxSettings: _*)
Expand Down Expand Up @@ -79,6 +80,14 @@ lazy val `sttp-scala-fx` = (project in file("./sttp-scala-fx"))
`http-scala-fx`,
`munit-scala-fx` % "test -> compile")

lazy val `http4s-scala-fx` = (project in file("./http4s-scala-fx"))
.settings(http4sScalaFXSettings)
.dependsOn(
`cats-scala-fx`,
`scala-fx`,
`http-scala-fx` % "test -> test",
`munit-scala-fx` % "test -> compile")

lazy val commonSettings = Seq(
javaOptions ++= javaOptionsSettings,
autoAPIMappings := true,
Expand Down Expand Up @@ -145,6 +154,10 @@ lazy val sttpScalaFXSettings = commonSettings ++ Seq(
libraryDependencies += hedgehog % Test
)

lazy val http4sScalaFXSettings = commonSettings ++ Seq(
libraryDependencies += http4sBlaze
)

lazy val javaOptionsSettings = Seq(
"-XX:+IgnoreUnrecognizedVMOptions",
"-XX:-DetectLocksInCompiledFrames",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package http4s
package fx

import _root_.fx.handle
import _root_.fx.instances.StructuredF
import _root_.fx.instances.FxAsync.asyncInstance
import _root_.fx.{Control, Errors, Nullable, Resource, Resources, Structured}
import org.http4s.HttpApp
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.server.{Server, defaults}

class BlazeServerFXBackend private (
app: HttpApp[StructuredF]
)(
using structured: Structured,
control: Control[Throwable],
config: HttpServerConfig
) {
private var serverFinalizers: Nullable[(Server, StructuredF[Unit])] = Nullable.none

private val builder: BlazeServerBuilder[StructuredF] =
BlazeServerBuilder[StructuredF]
.bindHttp(config.port.getOrElse(defaults.HttpPort), config.host.getOrElse(defaults.IPv4Host))
.withIdleTimeout(config.idleTimeout.getOrElse(defaults.IdleTimeout))
.withSelectorThreadFactory(structured.threadFactory)
.withHttpApp(app)

def start(): BlazeServerFXBackend =
serverFinalizers = handle(
Nullable(builder.resource.allocated)
)((e: Throwable) => e.shift)
this

def close(): Unit =
serverFinalizers.map{ case (_, finalizers) => finalizers }.getOrElse(())

def server(): Server =
handle(
serverFinalizers.map{ case (server, _) => server }.value
)((e: Throwable) => e.shift)
}

object BlazeServerFXBackend:
def apply(app: HttpApp[StructuredF])(
using structured: Structured,
control: Control[Throwable],
config: HttpServerConfig
): Resource[BlazeServerFXBackend] =
Resource(new BlazeServerFXBackend(app).start(), (server, _) => server.close())
20 changes: 20 additions & 0 deletions http4s-scala-fx/src/main/scala/http4s/fx/HttpServerConfig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package http4s
package fx

import _root_.fx.Nullable

import java.net.InetSocketAddress
import scala.concurrent.duration.Duration

/**
* Defines the server configuration options for an http server. Not open for extension.
*/
final class HttpServerConfig(
val port: Nullable[Int],
val host: Nullable[String],
val idleTimeout: Nullable[Duration]
)

object HttpServerConfig:
given HttpServerConfig =
HttpServerConfig(Nullable.none, Nullable.none, Nullable.none)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package http4s
package fx

import _root_.fx.{defaultRetryPolicy, parallel, structured, GET, POST, Structured}
import munit.fx.ScalaFXSuite

import java.net.URI

class BlazeServerFXBackendSuite extends ScalaFXSuite, HttpServerFixtures:

httpServerHttp4s(pingPongApp)
.testFX("GET requests should be returned in non-blocking fibers") { serverResource =>
serverResource.use { baseServerAddress =>
println(baseServerAddress)

val pongResponse: Structured ?=> (String, String, String) =
parallel(
() => URI.create(s"$baseServerAddress/ping/1").GET[String]().body,
() => URI.create(s"$baseServerAddress/ping/2").GET[String]().body,
() => URI.create(s"$baseServerAddress/ping/3").GET[String]().body
)

assertEqualsFX(
structured(pongResponse),
("pong", "pong", "pong")
)
}
}

httpServerHttp4s(echoApp)
.testFX("POST requests should be returned".ignore) { serverResource =>
serverResource.use { baseServerAddress =>

val response = structured(URI.create(s"$baseServerAddress/echo").POST[String, String]("hello"))

assertEqualsFX(response.body, "hello")
assertEqualsFX(response.statusCode, 200)
}
}
38 changes: 38 additions & 0 deletions http4s-scala-fx/src/test/scala/http4s/fx/HttpServerFixtures.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package http4s
package fx

import _root_.fx.{Control, Resource, handle, structured, toEither}
import _root_.fx.instances.FxAsync.asyncInstance
import _root_.fx.instances.StructuredF
import _root_.fx.HttpExecutionException
import fx.BlazeServerFXBackend
import munit.FunSuite
import org.http4s.Method.{GET, POST}
import org.http4s.{HttpApp, HttpRoutes, Response}

trait HttpServerFixtures:
self: FunSuite =>

val pingPongApp: HttpApp[StructuredF] =
HttpApp.apply {
case r if r.method == GET && r.uri.path.renderString.contains("ping") =>
Response[StructuredF]().withEntity[String]("pong")
}

val echoApp: HttpApp[StructuredF] =
HttpApp.apply {
case r if r.method == POST && r.uri.path.renderString.contains("echo") =>
Response[StructuredF]().withBodyStream(r.body)
}

def httpServerHttp4s(app: HttpApp[StructuredF]): FunFixture[Resource[String]] =
FunFixture(
setup = _ => {
for {
server <- toEither(
structured(BlazeServerFXBackend(app))
).fold(e => throw e, identity)
} yield s"http:/${server.server().address}"
},
teardown = _ => ()
)
3 changes: 2 additions & 1 deletion project/project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ object Dependencies {
val sttp = "3.6.2"
val httpCore5 = "5.1.4"
val hedgehog = "0.9.0"
val http4sBlaze = "0.23.12"
}

object Compile {
Expand All @@ -47,7 +48,7 @@ object Dependencies {
val logback = "ch.qos.logback" % "logback-classic" % Versions.logback
val sttp = "com.softwaremill.sttp.client3" %% "core" % Versions.sttp
val httpCore5 = "org.apache.httpcomponents.core5" % "httpcore5" % Versions.httpCore5

val http4sBlaze = "org.http4s" %% "http4s-blaze-server" % Versions.http4sBlaze
}

object Test {
Expand Down
4 changes: 2 additions & 2 deletions scala-fx/src/main/scala/fx/Continuation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import scala.annotation.implicitNotFound
import scala.util.control.ControlThrowable
import java.util.UUID
import scala.util.control.NonFatal
import java.util.concurrent.ExecutionException
import java.util.concurrent.{CompletionException, ExecutionException}
import scala.annotation.tailrec

object Continuation:
Expand All @@ -30,7 +30,7 @@ object Continuation:

@tailrec def handleControl(control: Control[_], e: Throwable): Any =
e match
case e: ExecutionException =>
case e: (ExecutionException | CompletionException) =>
handleControl(control, e.getCause)
case e @ ControlToken(token, shifted, recover) =>
if (control.token == token)
Expand Down

0 comments on commit 9bade01

Please sign in to comment.