HTTP request handler / middleware SRFI
Lassi Kortela 05 Apr 2019 11:34 UTC
The archive file SRFI/implementation is coming along nicely but I'm
getting a bit bored with the lack of variety so I just can't resist
proposing the next thing.
Is there any kind of standard for HTTP server middleware procedures?
This would make a great SRFI - nowadays an almost universally useful
abstraction, with a simple interface permitting numerous implementation
strategies.
As you likely know, there are such middleware standards in other
languages and they are extremely successful there:
* Clojure - Ring (https://github.com/ring-clojure/ring)
* Ruby - Rack (https://rack.github.io/)
* Python - WSGI (https://wsgi.readthedocs.io/)
According to Wikipedia there's also:
* PSGI – Perl Web Server Gateway Interface
* SCGI – Simple Common Gateway Interface
* JSGI – JavaScript web server gateway interface
There's a good chance you have already used one or more of these and
know how much they help smooth out some of the rough edges of web app
development and deployment.
I'll just steal the blurb from Clojure: "By abstracting the details of
HTTP into a simple, unified API, Ring allows web applications to be
constructed of modular components that can be shared among a variety of
applications, web servers, and web frameworks."
The abstract API is basically just:
(define (my-fancy-request-handler http-request-object)
(make-response-object-from-request-object http-request-object))
Half the point is that the API doesn't say where the HTTP request comes
from and where the response goes. So applications/middleware conforming
to this interface can be used on top of web servers implemented any
number of ways (CGI, FastCGI, SimpleCGI, Apache or Nginx module, TCP
socket made by a pure-Scheme web server, etc.) If I promise to write all
my handlers so they are stateless, I can also give them to a web server
that serves requests in parallel (using threads or subprocesses) without
worrying about concurrency in my application code.
The other half of the point is that handlers compose. So we can put
logging, monitoring, security, etc. middleware in re-usable libraries
and publish them in a package manager. E.g. if I have a web app and want
to log all its traffic in Apache Common Log Format, I could just install
a common-log-middleware library that somebody else defined like this:
(define (common-log-middleware log-output-port handle)
(lambda (request)
(let ((response (handle request)))
(write-string (common-log-line request response)
log-output-port)
response)))
And then I would replace this in my webapp:
(run-web-server my-webapp-handler)
With this:
(run-web-server
(common-log-middleware my-log-port
my-webapp-handler))
It's possible (and often done) to stack middlewares like this:
(run-web-server
(logging-middleware
(error-recovery-middleware
(access-control-middleware
(my-app-request-router)))))
There's a small caveat here - these handlers are based on a serial
Unix-pipe-like one thing in -- one thing out model. This is a great fit
for the current HTTP/1.1 protocol but future versions of HTTP will
likely parallelize things much more, so that e.g. a response can start
being written before the request is fully read; or several responses to
one request can start going out in parallel (e.g. all the static assets
for a website would start streaming at once, and a bit later some
results from database queries would go out in their own streams, all
independent of each other). AFAIK the high-level abstractions for
programming that stuff have not stabilized yet.
I would still specify this serial interface similar to what the other
languages have. It is an extremely simple and powerful abstraction, and
ubiquitous for now. It will also probably be just fine for small-time
websites even in the future. We can write another SRFI about a parallel
web interface in about 5 years once the dust has settled.
What do you think?
KR, Lassi