Description
Context
When writing clojure web applications and middleware it's common
to need HTTP responses with many different status codes, like 401 Unauthorized
, 403 Forbidden
and many others. A full list of
possible codes can be found at
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
Currently, ring.util.repsonse
has functions to generate a limited
set of response types:
redirect
with options for :moved-permanently, :found,
:see-other, :temporary-redirect, and :permanent-redirectredirect-after-post
(deprecated)created
bad-request
not-found
The problem
The remaining response types are not implemented by
ring.util.response, which causes friction.
First, as the programmer you generally forget which response types are
implemented in ring.util.response and which are not, so you don't
really know when you'll be running into the issue until you do. Also,
when a response is implemented as a redirect, it does not match the
response type name. That is, there is no found
function, you have to
use (response/redirect url :found)
which may not be expected.
Once you need an unimplemted response type, you have to pull in an
additional dependency just for having a 403 Forbidden
response or
similar, or you have to implement it yourself, risking silly mistakes
(using the wrong status number can be a serious issue, and there are
just too many status codes that it becomes hard to catch the issue
even in code review).
Proposed Solution
It would be very useful to have a complete and correct set of response
functions for every status code as part of ring-core
, implemented
directly in ring.util.response
.
These functions will follow the format of the existing response
generating functions mentioned above, for example:
(defn forbidden
[body]
{:status 403
:headers {}
:body body})
Following this logic there would also be separate functions for
moved-permanently
, found
etc, in addition to the existing
redirect
and redirect-after-post
function.
Implications
This would extend the ring.util.response public interface with a
fairly large set of functions, so clashes are possible with existing
code that does a :refer-all
or :use
to import everything in
ring.util.response
and already defines any of the new names. So the
following example would generate a warning:
(ns my.app
(:use [ring.util.response]))
(defn forbidden
[text]
...)
I consider this acceptable since it would only result in a warning and
not break functionality - the above (defn forbidden ...)
would
shadow the one imported from ring.util.response
- and in general in
Clojure, use
and refer-all
are discouraged anyway for the above
and other reasons.
In Clojure expanding an API with new vars is normally not considered a
breaking change. Code that would break given this change is too
clever for its own good IMO.
Alternatives
Vendoring response functions
We currently use this strategy and copy a list of functions and status
codes over from project to project. This is simple, but distracting.
Use other libraries
There are a few candidates.
metosin/ring-http-response
implements most of what we propose and is even intended as an almost
replacement for ring.util.response
but that pulls in potemkin
(which is a suspicious dependency), and it's incomplete (for instance,
421 Misdirected Request
is missing).
There is also
macchiato-http
which we haven't really investigated yet. And probably more.
The main problem from a developer point of view is that you'd need to
find and evaluate these candidates when you're not expecting to have
to pull in dependancy at all.
Conclusion
I've tried to make a good case for extending the ring-code
library
for better developer ergonomics and predictability. In my view,
ring-core should include response functions for all HTTP status codes.
If the consensus is that this is a good solution I'm prepared to do
the implementation work and prepare a PR.