Excited about Dream web framework

I don’t have a table ready, but I can share a list of comparisons with Opium from my notes. I can’t compare with Ocsigen, because I never tried it — it always seemed more complicated than what I needed.

The list below is some of what I consider to be advantages of Dream. Some of these could be addressed in Opium with work, but others, I think, are API-level design limitations.

Compared with Opium, Dream…

  • Supports HTTP/2, HTTPS and WebSockets, i.e. all modern Web transport protocols. Dream will experiment with HTTP3. Thanks to @anmonteiro here.

  • Includes a templater. Opium strongly favors TyXML, which also comes with a nice syntax. In both frameworks, the templater is easily interchangeable, and I already plan to add a TyXML example to Dream today.

  • Is one flat module. I didn’t think that Opium’s submodules are necessary for either framework, and it is confusing to jump between them while reading the documentation. They make code more verbose, e.g. Opium.Request.header vs. Dream.header, which by a small trick, also works for responses.

  • Has a much simpler programming model. For example, Opium middlewares are wrapped in records, whereas Dream middlewares are bare functions which you can trivially define anywhere like any others. I also simplified everything else to clear concepts wherever possible, and tried to stick to base OCaml types.

  • Dream is even simpler than Rock, the simple under-layer of Opium.

  • Opium’s more verbose middleware API interacts somewhat pathologically with its lack of a full set of predefined middlewares. When you are then forced to define your own, you almost inevitably have to deal with Hmaps, S-expressions, repeatedly wrapping and unwrapping records and application builders, etc.

  • As implied by the previous point, Dream has much more built-in middleware and helpers for getting things done (yet all of which remains loosely coupled). See the API docs for a survey.

  • Dream’s helpers “bake in” readings of RFCs, security recommendations, etc. For example, calling Dream.set_cookie with only the basic arguments automatically generates the most secure cookie possible for your site, and Dream.cookie “knows” how to parse it. You can still undo all that automation with optional arguments and/or lower-level helpers. Likewise, the form helpers automatically both generate CSRF-safe forms, and parse them with CSRF checking. Current Opium doesn’t offer this, leading to a paranoid experience, where you spend the first several days to a week learning or re-learning security topics, and/or are afraid of writing too much unfixably insecure code if you choose not to learn right away. You should still learn the topics with Dream, eventually, of course :slight_smile: To help, Dream’s docs and examples include security discussions and links throughout. There is even an example of creating a vulnerable “app” and exploiting it.

  • Current Opium routing is shoehorned into a middleware-like model (app builders). If you like algebra, Opium looks like (X, *), though it uses a “fat” * operation to recover +. Dream has first-class routers from the start, and looks like (X, *, +) — that is, handlers, handler sequencing (middlewares), and handler choice (routing). Dream.scope implements a distributive law, correctly applying a chain of middlewares to a subset of your routes. All of this is more loosely coupled in Dream than in Opium, because middlewares and routing are fully independent concerns in Dream, and, apart from Dream.scope, middlewares and routes don’t “know” anything about each other.

  • Again on simplicity, in Dream, out of (X, *, +), handlers are just plain OCaml functions, handler sequencing (middleware) is just plain OCaml function composition, and the main reason handler choice (routing) is not just pattern matching, is the need to implement the distributive law in Dream.scope concisely.

  • Dream has unified error handling, where even errors occuring in the HTTP stack below your application are passed to your error handler for decoration with a response. Dream grew out of an internationalized project, and its goal is to not surprise users with any untranslated and undecorated low-level messages directly generated by, say, http/af, or a low-level adapter to it.

  • I kept Dream’s request and response types (its only “real” data types) abstract, so that they can be internally optimized. For example, if we find people tend to read and re-read the same header a lot, we might add some kind of mutable cache for its parsed value.

Opium’s “fat middleware” and “app” model does have the advantage of easily supporting introspection, which is not immediately available on Dream’s bare function middlewares. However, in Dream, because of first-class routing, site structure is given by routes, not middlewares (or app builders), and Dream routes are “fat” objects and will eventually support introspection. And, it’s still possible to define an introspection-friendly composition that decorates even middlewares with names as they are composed.

In any case, I didn’t think that introspection by default is worth the considerable syntactic overhead that it imposes on everyone, whether they need introspection or not.

This is just what I was able to quickly recover from the notes and recall, but there are more differences, as I did many detailed passes through the API, and probably will still, during the Dream alphas :slight_smile:

22 Likes