Sean Schulte

Running two pieces of middleware with Dependo

31 July 2012

Middleware is an awesome feature of Rack -- I can add functionality that wraps around an HTTP call without having to change the actual server.

We use it with some success in our r509-ca-http project. r509-ca-http is a Certificate Authority based on r509 that serves over an HTTP REST API. Its functionality is intentionally as simple as possible -- the r509-ca-http project is intentionally not responsible for storing information about certificates' validity or a record of which certificates have been issued. Its only responsibility is issuing and revoking certificates.

But if we're going to actually run a CA, we need it to store validity information, among other things. Enter middleware.

We created a new project, r509-middleware-validity, responsible for storing validity information (issuance and revocation) about every certificate into a Redis database. Originally, we structured it such that it relied on the server it was wrapping around to have a #log method on it:

And on each call to the server, the middleware will be executed first; you need to send the call along to the server with that @app.call(env) line, and return the results after you're done. But because we're using that @app.log.info method call, we're relying on our Sinatra server having a #log method; since it uses the Dependo::Mixin, and we added a Logger to the Dependo::Registry in our config.ru, that method will be available.

Which is pretty cool, and was working -- until we needed to add another piece of middleware. In addition to storing validity information in a database, we also need to store every issued certificate on disk. This is another thing we don't want to add to the HTTP service, so middleware is the perfect place for it. So we created r509-middleware-certwriter and got it saving every certificate that we issued. It was working great in testing ... and then we tried running the server with both middlewares active at once.

use R509::Middleware::Certwriter
use R509::Middleware::Validity
server = R509::CertificateAuthority::Http::Server
run server

Turns out that when you're using two middlewares, the second one wraps around the server, and the first one wraps around the second one -- ie, they don't both somehow wrap directly around the server. So in our case, the #log method exists on the HTTP server so the validity middleware can use it, but it doesn't exist on the validity middleware so the certwriter middleware can't use it and dies.