Welcome to http4s-stir, a library designed to bridge the gap between Pekko HTTP (Akka HTTP) and http4s. This README provides an overview of the library, its usage, project status, and more.
http4s-stir offers Pekko HTTP-style (Akka HTTP-style) DSL directives for http4s using cats-effect's IO as an effect system. About 85% of all directives have been ported. Some were omitted due to a lack of support in http4s, while others were modified to fit http4s' distinct architecture. For specifics, refer to the Missing section below.
Additionally, there's a compatibility layer, Http4sDirectives
, for http4s-dsl routes.
http4s-stir also furnishes a test kit akin to Pekko's (Akka's).
In SBT:
libraryDependencies += "pl.iterators" %% "http4s-stir" % "0.4.0"
libraryDependencies += "pl.iterators" %% "http4s-stir-testkit" % "0.4.0" % Test // if you need this
For scala-cli
see this example.
Here's an example in Scala 3 that you can run using scala-cli:
// Main.scala
//> using dep org.typelevel::cats-effect::3.5.4
//> using dep org.http4s::http4s-dsl::0.23.28
//> using dep org.http4s::http4s-ember-server::0.23.28
//> using dep org.http4s::http4s-circe::0.23.28
//> using dep io.circe::circe-core::0.14.10
//> using dep io.circe::circe-generic::0.14.10
//> using dep pl.iterators::http4s-stir::0.4.0
import org.http4s.Status
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.circe.CirceEntityEncoder.*
import org.http4s.circe.CirceEntityDecoder.*
import io.circe.*
import io.circe.generic.semiauto.*
import cats.effect.IO
import pl.iterators.stir.server.*
import pl.iterators.stir.server.Directives.*
import cats.effect.IOApp
// example rewritten from https://pekko.apache.org/docs/pekko-http/current/introduction.html#using-apache-pekko-http
var orders: List[Item] = Nil
// domain model
final case class Item(name: String, id: Long)
final case class Order(items: List[Item])
// formats for unmarshalling and marshalling
given Codec[Item] = deriveCodec[Item]
given Codec[Order] = deriveCodec[Order]
// (fake) async database query api
def fetchItem(itemId: Long): IO[Option[Item]] = IO.delay {
orders.find(o => o.id == itemId)
}
def saveOrder(order: Order): IO[List[Item]] = {
orders = order.items ::: orders
IO.delay(orders)
}
val route: Route =
concat(
get {
pathPrefix("item" / LongNumber) { id =>
// there might be no item for a given id
val maybeItem: IO[Option[Item]] = fetchItem(id)
onSuccess(maybeItem) {
case Some(item) => complete(item)
case None => complete(Status.NotFound)
}
}
},
post {
path("create-order") {
entity(as[Order]) { order =>
val saved: IO[List[Item]] = saveOrder(order)
onSuccess(saved) {
_ => // we are not interested in the result value `Done` but only in the fact that it was successful
complete("order created")
}
}
}
}
)
object Main extends IOApp.Simple {
val run = EmberServerBuilder
.default[IO]
.withHttpApp(route.toHttpRoutes.orNotFound)
.build
.use(_ => IO.never)
}
To run this service you can use scala-cli run .
.
Or maybe if you want, you can compile it to JS file: scala-cli --power package --js --js-module-kind commonjs Main.scala
.
// Main.test.scala
//> using test.dep org.specs2::specs2-core:5.5.8
//> using test.dep pl.iterators::http4s-stir-testkit:0.4.0
//> using test.dep org.http4s::http4s-circe:0.23.28
import org.http4s.Status
import org.http4s.circe.CirceEntityEncoder.*
import org.http4s.circe.CirceEntityDecoder.*
import cats.effect.IO
import cats.effect.unsafe.IORuntime
import org.specs2.mutable.Specification
import pl.iterators.stir.testkit.Specs2RouteTest
class MainRoutesSpec extends Specification with Specs2RouteTest {
override implicit val runtime: IORuntime = IORuntime.global
sequential
"The routes" should {
"create order" in {
Post("/create-order", Order(List(Item("foo", 42)))) ~> route ~> check {
responseAs[String] must contain("order created")
orders.head must beEqualTo(Item("foo", 42))
}
}
"retrieve an item if present" in {
orders = List(Item("foo", 42))
Get("/item/42") ~> route ~> check {
responseAs[Item] must beEqualTo(Item("foo", 42))
}
}
"return 404 if item is not present" in {
orders = List.empty
Get("/item/42") ~> route ~> check {
status must beEqualTo(Status.NotFound)
}
}
}
}
To run the tests you can use scala-cli test .
.
For a more comprehensive example showcasing additional directives see examples. You can run it with ~examples/reStart
.
Here's why I embarked on this project:
- After the license change for Akka, many contemplated transitioning to http4s and the Typelevel stack. I wanted to simplify this migration.
- While I'm a fan of cats-effect, I find the http4s DSL verbose and clunky. Marrying Pekko HTTP (Akka HTTP) with cats-effect seemed inelegant, so http4s-stir could be the remedy.
- I was curious about the internals of both Pekko HTTP and http4s and wanted to determine the feasibility of this project.
- And, of course, a bit of playful provocation - see the next section.
stir something up (pv)
to cause an unpleasant emotion or problem to begin or grow
There are folks who adore http4s but detest Pekko's (or Akka's) DSL. Conversely, there are those who champion Pekko's (or Akka's) but disdain http4s DSL. I aimed to ruffle feathers from both camps with a hybrid library.
This library is in preview, intended to collect initial feedback. Yet, I am dedicated to its ongoing maintenance and enhancement, especially as it undergoes real-world testing. Contributions are very welcome.
Certain directives from the original are either absent or have been modified:
- Assuming and converting to/from strict entity
CacheConditionDirectives
CodingDirectives
- directory listing in
FileAndResourceDirectives
RangeDirectives
checkSameOrigin
inHeaderDirectives
- handling of multipart forms in
FormFieldDirectives
(but I don't like it anyway) - Some of how akka configures things
withSizeLimit
withoutSizeLimit
requestEntityEmpty
requestEntityPresent
rejectEmptyResponse
extractRequestTimeout
withRequestTimeoutResponse
- AttributeDirectives
- FramedEntityStreamingDirectives
- WebSocketDirectives in large part
- Testkit needed significant changes
- Not async anymore
- Chunks not supported
- Request building incomplete (missing some minor header methods)
- All websocket thingies
- Some logic of transparent headers and default host info
If you run into any issues, unexpected behavior, or errors, we encourage you to report them. Your feedback is invaluable and helps us improve.
If there's a feature you'd like to see, or if you have an idea that would make this project even better, we'd love to hear about it!
Please create a new issue in our http4-stir. Ensure you provide as much detail as possible:
- For issues:
- Describe the issue you're facing.
- Steps to reproduce.
- Expected behavior.
- Actual behavior.
- For feature requests:
- Describe the feature and why you believe it would be useful.
- Any reference or example from other projects/tools, if applicable.
By providing detailed information, you'll help us address your concerns more efficiently.
Thank you for your contributions and for helping make this project better for everyone!
http4s-stir is under the Apache License, Version 2.0 ("the License"). You must comply with this License to use this software. A full license text is available in the repository.
http4s-stir incorporates significant portions of code adapted from Pekko HTTP, a fork of Akka HTTP.