Suave and Giraffe are functional web frameworks that work based on composition of handlers. A handler is a function that accepts an HttpContext
and returns an Async<HttpContext option>
, fitting nicely into an HTTP server's protocol of accepting HTTP messages with a request and some metadata, then returning a message with that metadata and a response. This makes the WebPart
in Suave, for example.
While the frameworks themselves only allow you to put a WebPart
into the processing pipeline, it is entirely up to you how you compose functions together to get that WebPart
. When you compose WebPart A and WebPart B (or HttpHandler A and HttpHandler B), you are using the a compose
operator - >=>
. Take a look at the function:
let compose (first : 'a -> Async<'b option>) (second : 'b -> Async<'c option>)
: 'a -> Async<'c option>
Leaving out the async part, it accepts a function that goes from 'a
to 'b option
and composes it with a function that goes from 'b
to 'c option
. (edited)
All composed together, you certainly need your to end up WebPart
that has a type HttpContext -> Async<HttpContext option>
but you can compose other functions that don’t take that as input and output.
type Whatever = {
Thing1: string
Thing2: int
Ctx: HttpContext
}
module ExampleTypedWebParts =
let webPartA =
fun (ctx:HttpContext) ->
async {
return { Thing1 = "part A"; Thing2 = 1; Ctx=ctx } |> Some
}
let webPartB =
fun (what:Whatever) ->
async {
// Do something with what
let responseContent = sprintf "thing 1 is %s and thing 2 is %i" what.Thing1 what.Thing2
return! Successful.OK responseContent what.Ctx
}
let myApp =
// composed together, webPartA >=> webPartB has the signature
// HttpContext -> Async<HttpContext option> so it plugs in nicely
Filters.GET >=> path "/what" >=> webPartA >=> webPartB
startWebServer defaultConfig myApp
Between webPartA
and webPartB
, you have types that can carry whatever data you like, and the compiler enforces that you compose these together safely.
The only real requirement is that to plug into the rest of the framework, the whole composition of WebParts needs to have the HttpContext -> Async<HttpContext>
signature. In myApp
above, once webPartA
and webPartB
are composed together with >=>
, you end up with a function with that very signature.
Parts of the composition (applicatives) can do whatever you like. A really good use of this is to make sure that some parts are only downstream of others. For instance, they may only accept something type like an AuthenticatedContext
that holds both the HttpContext
and some information that would have come from an authentication check, ensuring that no one ever composes them in the wrong order.
There is nothing in Suave or Giraffe that forces you to use custom types between composed functions - it only has the minimal type safety that is required to process HTTP messages. You can add all the type safety you want as long as you compose a function that meets the minimal requirement for the server to handle HTTP.