Two weeks ago, we had a look at Fable remoting: the bridge between the server and client in SAFE Stack. In this blog post, we'll have a closer look at Giraffe, the F# server framework used by SAFE.
Being a functional layer on top of ASP.NET Core, Giraffe provides a lean alterative in a world of magical OOP frameworks inspired by Ruby On Rails, such as ASP.NET Core MVC. HTTP has a very simple flow: you receive a request, and return a response. In Giraffe, you write HttpHandlers to take in the information from the request, and build up the appropriate response. We do this by passing through a HttpContext object.
The anatomy of a HTTP handler
Check out the signature of the HttpHandler type below:
type HttpFuncResult = Task<HttpContext option>
type HttpFunc = HttpContext -> HttpFuncResult
type HttpHandler = HttpFunc -> HttpContext -> HttpFuncResult
Let's look at these elements individually, starting at the end.
The return value: HttpFuncResult
The return value of a HttpHandler is an HttpFuncResult. This is a task, as requests are dealt with asynchronously. More interestingly, it's an option: the handler has the opportunity to return None, signalling that it can not handle the request. An example of a handler using this possibility is the route
handler. (I slighly modified it here to make it more explicit what it does).
let route (path : string) : HttpHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
if (SubRouting.getNextPartOfPath ctx).Equals path
then next ctx
else task {return None}
If the route matches, we let whatever handler is next (more about that later) handle the request. Otherwise, we'll return None: This signals we were not able to deal with the request, but still allows other handlers to give it a go.
The context is provided as one of the arguments, but we don't have to pass it through directly: it's the way we can manipulate the response returned by this handler. For example, we can set the response body, as done by the text
handler.
The second argument: The HTTP context
The HTTP context is our main source of information when handling the request. As you can see in the example above, it gives us access to the path the request was made to, but it holds many other things, such as the session, the HTTP response that is being built up etc.
The first argument: the next function
To understand the first argument, it's important to realize that Giraffe revolves around composing handlers using the >=>
operator. Check out the following example using the route
function, and the text
function which sets the response body to the supplied string. The required outcome here is that only the route "/helloWorld" returns the text.
route "/helloWorld" >=> text "Hey there!"
The >=>
operator passes the text
handler to the route
handler as an argument in curried form. You might be wondering what was passed into the text
handler as the next handler: at the end of the pipeline, Giraffe inserts its own special handler, that makes sure the request is dealt with appropriately.
Back to the next argument in the route
handler: as you can see, in case the route matches, the handler simply calls the provided next
argument, passing it the context. Because the HttpFunction and HttpHandler have the same return type, this simply works, and you'll notice that a lot of handlers will end by calling the next handler this way.
Giraffe in practice
Let's extend a SAFE app with a simple Giraffe-based API
let myPublicApi =
subRoute
"/myApi"
(choose [
subRoute "/helloWorld" GET >=> (text "Hello there!")
subRoute "/bye" PUT >=>
choose [
subRoutef "/%s" byeHandler
byeHandler "Everyone"
]
setStatusCode 400 >=> text "Whoops! We don't know that one!"
])
The choose
handler lets each handler in a list attempt to handle the request, until one of them returns a response. Let's use a very simple choose
handler to add our new api to the webApp. Note that the application
CE we are updating here is NOT part of Giraffe, but is part of Saturn, which will be described more in-depth in another post in this series.
let app = application {
use_router (choose [webApp; myPublicApi])
memory_cache
use_static "public"
use_gzip
}
The app will now try to handle all requests by the webApp
(the default Fable.Remoting API that SAFE ships with) first. If that fails, myPublicApi
gets a go.
Let's have a closer look at the API we just created. The subRoute
handler allows us to filter on the first part of the route of the api. The second time you call it in the same handler, it matches on the next part of the route, skipping the part you already filtered on. This makes it very easy to create complex routes.
Another interesting example of choose
is the one handling our "bye" route. If we provide a name, we give a specific goodbye message. Otherwise we go for a generic "everyone". Order matters here! If we flipped around the two routes provided to choose, providing the name would not work anymore.
The GET
and PUT
handlers filters requests on their HTTP method. You can insert them at any point in the chain. Here we use them to control the methods we expect on specific endpoints, but if your whole API expects only GET
requests, you can put the handler at the very beginning of the chain.
Conclusion
As you can see, Giraffe provides us with some pretty neat and simple handlers, that we can combine to define complex behavior. Check out the docs for all the nifty things Giraffe can do for you! Since handlers have a standard form that we can combine with >=>
, you'll find that learning how to use the different handlers is a breeze. Now that you are familiar with the structure of a HttpHandler
, writing your own should not be a problem either!
Giraffe and provides it's own templates for dotnet new, so it's also very easy to start a project with it outside of the SAFE ecosystem.