A middleware is a wrapper around a service that provides a means of manipulating the Request sent to service, and/or the Response returned by the service. In some cases, such as Authentication, middleware may even prevent the service from being called.

At its most basic, middleware is simply a function that takes one Service as a parameter and returns another Service. In addition to the Service, the middleware function can take any additional parameters it needs to perform its task. Let’s look at a simple example.

For this, we’ll need a dependency on the http4s dsl.

libraryDependencies ++= Seq(
  "org.http4s" %% "http4s-dsl" % http4sVersion
)

and some imports.

import org.http4s._
import org.http4s.dsl._

Then, we can create a middleware that adds a header to successful responses from the wrapped service like this.

def myMiddle(service: HttpService, header: Header): HttpService = Service.lift { req =>
  service(req).map {resp =>
    if (resp.status.isSuccess)
      resp.putHeaders(header)
    else
      resp
  }
}
// myMiddle: (service: org.http4s.HttpService, header: org.http4s.Header)org.http4s.HttpService

All we do here is pass the request to the service, which returns a Task[Response]. So, we use map to get the request out of the task, add the header if the response is a success, and then pass the response on. We could just as easily modify the request before we passed it to the service.

Now, let’s create a simple service. As mentioned in service, because Service is implemented as a Kleisli, which is just a function at heart, we can test a service without a server. Because an HttpService returns a Task[Response], we need to call run on the result of the function to extract the Response.

val service = HttpService {
  case GET -> Root / "bad" =>
    BadRequest()
  case _ =>
    Ok()
}
// service: org.http4s.HttpService = Kleisli(org.http4s.package$HttpService$$$Lambda$26383/885786014@3e814bd3)

val goodRequest = Request(Method.GET, uri("/"))
// goodRequest: org.http4s.Request = Request(method=GET, uri=/, headers=Headers())

val badRequest = Request(Method.GET, uri("/bad"))
// badRequest: org.http4s.Request = Request(method=GET, uri=/bad, headers=Headers())

service(goodRequest).run
// <console>:21: warning: method run in class Task is deprecated (since 7.2): use unsafePerformSync
//        service(goodRequest).run
//                             ^
// res0: org.http4s.Response = Response(status=200, headers=Headers())

service(badRequest).run
// <console>:21: warning: method run in class Task is deprecated (since 7.2): use unsafePerformSync
//        service(badRequest).run
//                            ^
// res1: org.http4s.Response = Response(status=400, headers=Headers())

Now, we’ll wrap the service in our middleware to create a new service, and try it out.

val wrappedService = myMiddle(service, Header("SomeKey", "SomeValue"));
// wrappedService: org.http4s.HttpService = Kleisli($$Lambda$26436/2032279813@2ad833c8)

wrappedService(goodRequest).run
// <console>:21: warning: method run in class Task is deprecated (since 7.2): use unsafePerformSync
//        wrappedService(goodRequest).run
//                                    ^
// res2: org.http4s.Response = Response(status=200, headers=Headers(SomeKey: SomeValue))

wrappedService(badRequest).run
// <console>:21: warning: method run in class Task is deprecated (since 7.2): use unsafePerformSync
//        wrappedService(badRequest).run
//                                   ^
// res3: org.http4s.Response = Response(status=400, headers=Headers())

Note that the successful response has your header added to it.

If you intend to use you middleware in multiple places, you may want to implement it as an object and use the apply method.

object MyMiddle {
  def addHeader(resp: Response, header: Header) =
    if (resp.status.isSuccess) resp.putHeaders(header)
    else resp

  def apply(service: HttpService, header: Header) =
    service.map(addHeader(_, header))
}
// defined object MyMiddle

val newService = MyMiddle(service, Header("SomeKey", "SomeValue"))
// newService: scalaz.Kleisli[scalaz.concurrent.Task,org.http4s.Request,org.http4s.Response] = Kleisli(scalaz.Kleisli$$Lambda$26440/538669445@58b818ad)

newService(goodRequest).run
// <console>:21: warning: method run in class Task is deprecated (since 7.2): use unsafePerformSync
//        newService(goodRequest).run
//                                ^
// res4: org.http4s.Response = Response(status=200, headers=Headers(SomeKey: SomeValue))

newService(badRequest).run
// <console>:21: warning: method run in class Task is deprecated (since 7.2): use unsafePerformSync
//        newService(badRequest).run
//                               ^
// res5: org.http4s.Response = Response(status=400, headers=Headers())

It is possible for the wrapped Service to have different Request and Response types than the middleware. Authentication is, again, a good example. Authentication middleware is an HttpService (an alias for Service[Request, Response]) that wraps an AuthedService (an alias for Service[AuthedRequest[T], Response]. There is a type defined for this in the http4s.server package:

type AuthMiddleware[T] = Middleware[AuthedRequest[T], Response, Request, Response]

See the Authentication documentation for more details.

Composing Services with Middleware

Because middleware returns a Service, you can compose services wrapped in middleware with other, unwrapped, services, or services wrapped in other middleware. You can also wrap a single service in multiple layers of middleware. For example:

val apiService = HttpService {
  case GET -> Root / "api" =>
    Ok()
}
// apiService: org.http4s.HttpService = Kleisli(org.http4s.package$HttpService$$$Lambda$26383/885786014@2e685aa5)

import org.http4s.server.syntax._
// import org.http4s.server.syntax._

val aggregateService = apiService orElse MyMiddle(service, Header("SomeKey", "SomeValue"))
// aggregateService: org.http4s.Service[org.http4s.Request,org.http4s.Response] = Kleisli(scalaz.Kleisli$$Lambda$26448/1687629674@4d3d84dc)

val apiRequest = Request(Method.GET, uri("/api"))
// apiRequest: org.http4s.Request = Request(method=GET, uri=/api, headers=Headers())

aggregateService(goodRequest).run
// <console>:24: warning: method run in class Task is deprecated (since 7.2): use unsafePerformSync
//        aggregateService(goodRequest).run
//                                      ^
// res6: org.http4s.Response = Response(status=200, headers=Headers(SomeKey: SomeValue))

aggregateService(apiRequest).run
// <console>:24: warning: method run in class Task is deprecated (since 7.2): use unsafePerformSync
//        aggregateService(apiRequest).run
//                                     ^
// res7: org.http4s.Response = Response(status=200, headers=Headers())

Note that goodRequest ran through the MyMiddle middleware and the Result had our header added to it. But, apiRequest did not go through the middleware and did not have the header added to it’s Result.

Included Middleware

Http4s includes some middleware Out of the Box in the org.http4s.server.middleware package. These include:

And a few others.