[ANN] Opium 0.19.0

On behalf of the Opium team, I am pleased to announce a new version of Opium (0.19.0) is available on Opam.

This release comes with a complete rewrite of Opium’s internals to switch from Cohttp to Httpaf (work done by @anuragsoni).

As demonstrated in several benchmarks, Httpaf’s latency is much lower than Cohttp’s in stress tests, so it is expected that Opium will perform better in these high-pressure situations.

The underlying HTTP server implementation is now contained in a rock package, that provides a Service and Filter implementation, inspired by Finagle’s. The architecture is similar to Ruby’s Rack library (hence the name), so one can compose complex web applications by combining Rock applications.

The rock package offers a very slim API, with very few dependencies, so it should be an attractive option for other Web frameworks to build on, which would allow the re-usability of middlewares and handlers, independently of the framework used (e.g. one could use Sihl middlewares with Opium, and vice versa).

Apart from the architectural changes, this release comes with a lot of additional utilities and middlewares which should make Opium a better candidate for complex web applications, without having to re-write a lot of common Web server functionalities.

The Request and Response modules now provide:

  • JSON encoders/decoders with Yojson
  • HTML encoders/decoders with Tyxml
  • XML encoders/decoders with Tyxml
  • SVG encoders/decoders with Tyxml
  • multipart/form encoders/decoders with multipart_form_data
  • urlencoded encoders/decoders with Uri

And the following middlewares are now built-in:

  • debugger to display an HTML page with the errors in case of failures
  • logger to log requests and responses, with a timer
  • allow_cors to add CORS headers
  • static to serve static content given a custom read function (e.g. read from S3)
  • static_unix to serve static content from the local filesystem
  • content_length to add the Content-Length header to responses
  • method_override to replace the HTTP method with the one found in the _method field of application/x-www-form-urlencoded encoded POST requests.
  • etag to add ETag header to the responses and send an HTTP code 304 when the computed ETag matches the one specified in the request.
  • method_required to filter the requests by the HTTP method and respond with an HTTP code 405 if the method is not allowed.
  • head to add supports for HEAD request for handlers that receive GET requests.

Lastly, this release also adds a package opium-testing that can be used to test Opium applications with Alcotest. It provides Testable modules for every Opium types, and implements helper functions to easily get an Opium.Response from an Opium.Request.

As this release changes the API drastically, we will keep maintaining the 0.18.0 branch for bug fixes, for users who don’t want to (or can’t) migrate to 0.19.0.

What’s next?

Recent discussions have shown that building optimized applications was not trivial. This is partly due to the lack of documentation, and probably because some configurations that should come by default, are left to the user to optimize. Therefore, we will keep performance in mind for the next release and investigate the current bottlenecks in Opium.

We will also continue adding higher-level functionalities to Opium to make users productive with real-world applications. This includes:

  • Sessions support (with signed cookies)
  • Handlers for authentication
  • Adding more middlewares (compression, flash messages, caching, etc.)

Your feedback is welcome, don’t hesitate to open Issues on Github!

Best,
The Opium team

21 Likes

This sounds great.
Does Opium + Httpaf support TLS?

@Haudegen It doesn’t at the moment.

If you want to use this new version of Opium there are ways around this problem. You could have Haproxy (or similar) terminate your TLS connections externally and if your environment requires TLS for your internal network something like Consul Connect can cover that use-case for you.

2 Likes

I would go as far as to say that it’s not a problem but rather a best practice to have a reverse proxy do SSL termination instead of the application server.

3 Likes

According the interface of opium, it’s possible to have the support of TLS (with ocaml-tls) with the new version of Conduit and paf (which is a MirageOS compatible layer of HTTP/AF - unreleased):

let stack ip =
  Tcpip_stack_socket.UDPV4.connect (Some ip) >>= fun udpv4 ->
  Tcpip_stack_socket.TCPV4.connect (Some ip) >>= fun tcpv4 ->
  Tcpip_stack_socket.connect [ ip ] udpv4 tcpv4

let http_with_conduit (ip, port) error_handler request_handler =
  Paf.https httpaf_config ~error_handler ~request_handler:(fun _ -> request_handler)
    ({ Paf.TCP.stack= stack ip
     ; keepalive= None
     ; nodelay= false
     ; port= port}, Tls.Config.server ~certificates ())

let () = match Lwt_main.run (Opium.run (https_with_conduit (Ipaddr.V4.localhost, 4343)) opium_app) with
  | Ok () -> ()
  | Error err -> Fmt.epr "%a.\n%!" Conduit_mirage.pp_error err

I used it for a long time on my personal unikernels and did some tests to ensure that it does not fails when it handles many requests. Note that you are able to use OpenSSL too if you want.

5 Likes

paf is really nice! I’ve used it along with Opium as well and it was very easy to get up and running. Thanks!

I should have mentioned this in my original response too, the Rock module will work with any server implementation that can work with Httpaf’s request_handler and error_handler. Like you mentioned it works with paf, and I know there are forks of httpaf that support ocaml-tls and lwt_ssl and opium should work really well with them as well.

3 Likes

Congrats for the release! We wrote a little wrapper around Opium 0.18.0 with the API of Opium 0.19.0 for Sihl, so that we don’t break too much when updating. Can’t wait to use all the new middlewares that you ship :slight_smile:

2 Likes