The Oxpecker

Vladimir Shchur
6 min readJan 21, 2024

--

https://twitter.com/AWF_Official/status/698525048211017729

TLDR: Oxpecker is a new F# ASP.NET core based web framework, the successor of the popular Giraffe framework, build with mostly the same concepts but also providing a number of improvements.

Why a new framework?

There are many F# server web frameworks already: Suave, Giraffe, Saturn, Falco, WebSharper and others less known or outdated. While those frameworks have their own strengths and weaknesses, for me for the long time Giraffe has been the framework of choice. It has the following outstanding features: strongly typed routing, easy handlers composition, lightweight functional core (no dependency on DI), ASP.NET Core goodness and relatively big community. In fact, I was one of it’s first adopters and promoters and even used it in the HighLoadCup competition 7 years ago!

Unfortunately, the biggest problem with Giraffe as for today is maintenance, it has become objectively unmaintained and Dustin doesn’t have intention to change that. It happens, it’s normal in some other situations, but as soon as Giraffe is the top web framework the F#, newcomers see it’s state and get disappointed and it’s poisonous for the ecosystem. Healthy mainstream web framework is a must for F#, so I decided to give it a go and build “Giraffe 2.0”. I didn’t want to keep the name to avoid confusion, however wanted to keep certain connection to Giraffe and Kestrel, hence Oxpecker name was chosen (see wiki to know more about the bird). I intend to move Oxpecker to fsprojects organization once it gains certain popularity to prevent the story with Giraffe maintenance and ownership to happen again.

I’ve tried to meet several goals for the new framework

  • to mostly keep Giraffe’s developer experience and main features
  • to make use of the newest ASP.NET Core updates while dropping some outdated Giraffe’s functionality
  • to make slight improvements wherever possible

Whether I could achieve that goals or not you can decide, I made up some examples in the repository. In the rest of the article I’ll cover the differences between Giraffe and Oxpecker.

License

Giraffe has Apache 2.0 license, which is good and that allowed me to move a lot of the code as is. However, for my other projects I use MIT license, so decided to continue with it, those licenses are compatible anyway

Target

Giraffe is .NET 6+, Oxpecker will be .NET 8+. I don’t believe that people will switch the framework in their existing projects anyway, and for the new projects newer .NET is an obvious choice.

Endpoint Routing

Giraffe historically had it’s own routing, some time later the endpoint routing support was introduced, however it wasn’t as well documented as the original one and not well promoted. Oxpecker only focuses on endpoint routing.

Giraffe classic routing
Oxpecker routing

Core Handlers

//Giraffe 
type HttpFuncResult = Task<HttpContext option>
type HttpFunc = HttpContext -> HttpFuncResult
type HttpHandler = HttpFunc -> HttpFunc

//Oxpecker
type EndpointHandler = HttpContext -> Task
type EndpointMiddleware = EndpointHandler -> EndpointHandler

Basically, everything is HttpHandler in Giraffe. This is very handy for composition, but doesn’t make sense for terminal handlers. In Oxpecker I’ve separated that to EndpointMiddleware (has additional next parameter) and EndpointHandler (doesn’t have it). This follows the ASP.NET Core middleware separation on terminal and non-terminal.

// return json in Giraffe
json myObj next ctx

// return json in Oxpecker
json myObj ctx

Both handlers and middlewares still support the famous composition operator (>=>).

View Engine

While the Giraffe view engine is very simple and flexible, it has a certain downside — it doesn’t help you determine which attributes should be used with which HTML elements. The IDE coloring is also not great. I’ve decided to come up with something similar but more convenient to use, you are welcome to judge:

Giraffe’s html DSL
Oxpecker’s html DSL

Routef

While static route parameter handling is Giraffe’s top feature, it is a bit clumsy. In classical routing route and routef handlers look differently. In endpoint routing they look same but parameters are still tuples instead of being separate parameters, which is not very fsharpy. In Oxpecker I’ve tried to kill two birds with one stone — to make them both uniform and not tuplified.

Another change is having curly braces around route parameters, this allows using such feature as Route constraints if needed.

// Giraffe classic
route "/" >=> text "hello"
routef "/foo/%s/%i" (fun (s, i) -> text (sprintf "%s%i" s i))
// Giraffe endpoint
route "/" (text "hello")
routef "/foo/%s/%i" (fun (s, i) -> text (sprintf "%s%i" s i))
// Oxpecker
route "/" (text "hello")
routef "/foo/{%s}/{%i:int}" (fun s i -> text (sprintf "%s%i" s i))

And one more change — instead of hardcoding %O to some custom Short Guid, it is now used for any non-standard F# formatting type together with route constraint syntax. Right now {%O:guid} is available, other types can be added later as needed.

Error handling

Oxpecker doesn’t provide special errorHandler or notFoundHandler. Instead you should rely on the native middleware composition built in ASP.NET core.

let configureApp (appBuilder: IApplicationBuilder) =
appBuilder
.UseRouting()
.Use(errorHandler)
.UseOxpecker(endpoints)
.Run(notFoundHandler)

To simplify error handling process, bindModel is only shipped with the version without try- so you could handle such errors in generic error handler uniformly for the whole application and return 400 error in your desired format. Example error handler:

let errorHandler (ctx: HttpContext) (next: RequestDelegate) =
task {
try
return! next.Invoke(ctx)
with
| :? ModelBindException
| :? RouteParseException as ex ->
let logger = ctx.GetLogger()
logger.LogWarning(ex, "Unhandled 400 error")
ctx.SetStatusCode StatusCodes.Status400BadRequest
return! ctx.WriteHtmlView(errorView 400 (string ex))
| ex ->
let logger = ctx.GetLogger()
logger.LogError(ex, "Unhandled 500 error")
ctx.SetStatusCode StatusCodes.Status500InternalServerError
return! ctx.WriteHtmlView(errorView 500 (string ex))
} :> Task

Same story for the not found handler:

let notFoundHandler (ctx: HttpContext) =
let logger = ctx.GetLogger()
logger.LogWarning("Unhandled 404 error")
ctx.SetStatusCode 404
ctx.WriteHtmlView(errorView 404 "Page not found!")

Performance

Giraffe is well known for it’s performance, so I didn’t want ship a library without making sure that it outperforms classic Giraffe in all the performance tests. You can check the results from my machine in the repo.

Dropped features

*Ci extensions were dropped, since ASP.NET Core is already case independent. XML serializer is dropped until someone brings it back in, since I’m not sure it is still relevant. Short guid support was dropped in favor of future custom route constraints (if anyone needs them). Subroutef was dropped, since I couldn’t make a generic solution for it, my efforts ended in this branch.

Documentation

Giraffe has an outstanding documentation, having it all on one github page was a great idea, but with the same main problem of not being updated regularly. Oxpecker doesn’t have such a thorough document at the moment, but I’ll be working on it and will also be happy to accept contributions to the documentation quality.

Future

I’m planning to support Oxpecker for quite some time, which means helping with issues, merging PRs and promoting active participants to library admins. I also plan to keep all Oxpecker extension libraries (if they appear) in the same repo to simplify management, and move it to fsprojects once project gets 200 stars. I acknowledge that Giraffe is a collaborative work of many F# developers, so want to keep it’s successor healthy to help F# community thrive.

Credits

I want to express my gratitude to Dustin Morris and all the other contributors to Giraffe for their efforts, as they have truly taken a significant step towards democratizing F#. Much of their work will still persist in the Oxpecker repository. Also, thank you, dear reader, for making it to the end of the article.

You are welcome to try it and give feedback!
Link to the new repository: https://github.com/Lanayx/Oxpecker
Nuget: https://www.nuget.org/packages/Oxpecker

--

--