Hi all,
Been meaning to say something in here for a bit, just to share a little bit about a personal project i'm working on (and which i'm hoping to open source very shortly, when it's a little bit more complete, and slightly more stable - I'm a terrible compulsive refactorer). It's not very much like what others have been doing in here, which looks great, but I think there's plenty of different valid approaches and people seeing that F# has variety and live projects can never be a bad thing (I hope!)
Anyway, as a brief intro, I started as a rough port/re-implementation of Webmachine/Liberator (Erlang/Clojure respectively, Liberator is also inspired by Webmachine). It's evolved since then in to a little more of a set of components which work together, but are more of a stack.
Bottom level is the approach I took around OWIN. I'm not a wild fan of the OWIN approach of mutable state you "do stuff" to, but it is what it is, and after various experiments I decided to live with it and make the best of it. So I have a set of lenses which provide strongly typed access to aspects of the OWIN state, without you need to know quite what it's doing in the middle, plus a core monad (called frost) which is essentially asyncState, where state is an OWIN environment. There are then a few functions for using the lenses to manipulate that in a nice way, along with some convenience operators. A couple of examples, pulled from Frost internals at the moment...
let internal isMethod m =
frost { return! (=) m <!> get Request.Method }
let internal defaultHandler code phrase =
frost {
do! Some code => Response.StatusCode
do! Some phrase => Response.ReasonPhrase }
The first is a quick predicate function which checks if the current method is of type m (m here is of type Method, a Frost type) and the second sets the response code and phrase. These are all "strongly typed" to the end user, but underneath are simply mutating the OWIN dictionary (the lens implementation is FSharpx, with various combinations of lenses, particularly xmap ones!) It's quite nice to be able to do things like let! accept = get (Request.Header "Accept") and get back a strongly typed Header option (or update, set, etc. where relevant). It does make me pine for some kind of match! implementation though!
Next level up in frost is a simple router, which again has a monad interface to the end user, with a couple of custom operations (it's essentially designed to be a declarative approach). Another quick example...
let routes =
frostRoutes {
resource "/tuples" tuples
resource "/tuples/:key" tuple }
Fairly obvious, but underneath it's a simple prefix-trie like router. They're composable, so you can include routes within routes, etc.
The current next level up from that (and the layer which adds the "resource" custom operation to the routing monad) is for resources, which essentially recreates Webmachine/Liberator in F#, with a monad allowing you to declaratively define which functions in the decision graph (RFC2616, essentially) you wish to override for this resource. (See the Clojure Liberator docs for a much better explanation if you're not already familiar!)
Examples...
let tuple =
frostResource {
allowedMethods [ GET; DELETE; PUT ]
availableMediaTypes [ "application/json" ]
doDelete tupleRemove
doPut tupleUpdate
exists tupleExists
handleOk tupleShow }
let tuples =
frostResource {
allowedMethods [ GET; POST ]
availableMediaTypes [ "application/json" ]
doPost tupleAdd
handleOk tupleList }
Those are the functions referred to in the routing example snippet, and the functions provided in them are "frost" functions, of some correct return type. They probably look a bit of an odd way to do things if you're not familiar with Webmachine, etc.! (Those are not fully wired up, also, I'd need to implement a little bit more to make it work properly, but you get the idea, hopefully) Composition of these things applies as well, so you define something like "frostResource secure { ... }" with functions defined for authorization and permission, etc. and then include that in other resources, so it can be quite neat for composition and re-use.
Anyway, just thought I'd share in case anyone was interested. I've still got a fair amount of work to do on the internals, and I want to build in a great inspection/debugging system as well before it's really considered usable, but I'm hoping to have it out for people to use (with some docs, if I pull my finger out) in the not too distant future. It probably isn't to everyone's taste, but like most things it's a personal itch to scratch. I've also got a few things which I haven't seen elsewhere that I want to roll in to it, like supported for semantically versioned resources, that kind of thing.
Any comments are welcome, hope that people won't hate me too much for adding "yet another web *" to an ecosystem!
Andrew