- Overview
- Understanding HttpServices
- Working with HttpServices
- Working with incoming requests
- Working with outgoing responses
- Validation
- Errors
- Error Handling
- Testing
--
When learning http4s, there are 4 main concepts you want to know about:
- Request
- Response
- HttpService
- Server
A Request encapsulates the concept of an HTTP request. It contains things like the URI/Path of the request,
HTTP method used, any headers sent, and the body of the request if there is one.
Like a Request, a Response encapsulates the concept of an HTTP response. it contains things like the HTTP status code,
a response body, and any headers.
An HttpService is a simple alias for
Kleisli[Task, Request, Response]. If that's meaningful to you,
great. If not, don't panic: Kleisli is just a convenient wrapper
around a Request => Task[Response], and Task is an asynchronous
operation. We'll teach you what you need to know as we go, or you
can, uh, fork a task to read these introductions first:
- [Scalaz Task: The Missing Documentation]
- [Kleisli: Composing monadic functions]
Breaking down that type signature (Request => Task[Response]), an HttpService represents an HTTP endpoint that can
take in an HTTP request, and will return some Response, asynchronously (the Task component of the signature.).
That's really it - an HttpService is a function from Request => Response in the Task effect. Because they are in
essence, just functions, it means they can be composed, merged and transformed, just like any other function.
And HttpService is great and all, but we need some machinery to actually route incoming connections to an
HttpService. A Server is just something that accepts an HttpService (or many), and binds them to connections.
http4s supports multiple server backends.
... A quick example here to whet peoples appetites ...
... Talk about imports here ...
... Talk more in depth about kleisli etc here ...
There are several different ways of creating an HttpService.
To create an empty HttpService that always returns a 404 Not Found response, you can use the factory method empty:
val emptyService: HttpService = HttpService.emptyNot overly useful. Suppose we have a constant value of type Task[Response], we can lift that into a function
that always returns that response using const:
val response: Task[Response] = Ok("Always returns 200")
val constantlyOkService: HttpService = Service.const(response)If we have a function returning Task[Response], we can lift that into an HttpService using lift:
val liftedService: HttpService = HttpService.lift { req =>
if (req.method == Method.GET) {
Ok("You sent a GET request!")
} else {
Ok("You sent something other than GET!")
}
}These are fairly low level ways to construct HttpService's, but they do illustrate that it's nothing more than a
function from A => B. https has a nicer way to construct them using the DSL:
import org.http4s.dsl._
val dslService: HttpService = HttpService {
case req @ GET -> Root => {
Ok("Using the DSL")
}
}The org.http4s.dsl._ brings in a bunch of new functionality like path matching, etc etc ... fill this bit in ...
The HttpService { } apply function has the signature:
def apply(pf: PartialFunction[Request, Task[Response]], default: HttpService = empty): HttpService. It takes in a
PartialFunction from Request to Task[Response], and a default service if nothing matches (which defaults
to empty - 404 Not Found). It proceeds to match top to bottom.