I don't think that we should replace the :uri key.
First, whilst it might make sense to have :path and :context keys in
addition to the :uri key, I don't think they should *replace* the :uri
key. Why remove potentially useful information?
Second, the Ring request map is a representation of a HTTP request,
and the URI is an intrinsic part of every HTTP request. I think we
should keep the basic request/response maps as much of a one-to-one
mapping to the HTTP protocol as possible.
Third, what you describe could be packaged up in a middleware function:
(defn wrap-context [handler context]
(fn [{uri :uri :as request}]
(handler
(if (.startsWith uri context)
(assoc request
:context context
:path (subs uri 0 (count context)))
request))))
One could construct similar middleware to pull the context from the
servlet info, or a configuration file, rather than have it statically
specified.
- James
I don't think that we should replace the :uri key.
First, whilst it might make sense to have :path and :context keys in
addition to the :uri key, I don't think they should *replace* the :uri
key. Why remove potentially useful information?
Second, the Ring request map is a representation of a HTTP request,
and the URI is an intrinsic part of every HTTP request. I think we
should keep the basic request/response maps as much of a one-to-one
mapping to the HTTP protocol as possible.
Third, what you describe could be packaged up in a middleware function:
(defn wrap-context [handler context]
(fn [{uri :uri :as request}]
(handler
(if (.startsWith uri context)
(assoc request
:context context
:path (subs uri 0 (count context)))
request))))
One could construct similar middleware to pull the context from the
servlet info, or a configuration file, rather than have it statically
specified.
Perhaps, but so is :content-type, :content-length and
:character-encoding. Removing the :uri key also breaks backward
compatibility.
> I am not saying the :uri key has to go, but the actual :uri shouldn't matter
> 99% of the time and having both a :path and :uri key in the request map
> might potentially lead to confusion.
Other HTTP interfaces provide both (such as the Java servlet
specification) and I don't think that potential confusion is
sufficient grounds for breaking backward compatibility.
However, I'm beginning to warm to the idea of having standard keys for
keeping track of context and path, in addition to the :uri key.
Allowing handlers to have a context is useful, for frameworks like
Compojure which can nest routes, and for supporting legacy systems
like Java servlets.
Currently this is usually done by using middleware to remove the
context from the :uri key:
(defn wrap-context [handler context]
(fn [{uri :uri :as request}]
(if (.startsWith uri context)
(handler (assoc request :uri (subs uri 0 (count context))))))))
So if the context is "/foo", then the URI "/foo/bar" becomes "/bar".
However, this has two problems:
1. We're removing information from the request, or at least moving it
to a non-standard key (e.g. :full-uri).
2. The :uri key no longer refers to the request URI, so it's somewhat misnamed.
3. There's no standard key for referring back to the context.
I'm tempted to suggest introducing :context and :path keys. Perhaps
:path should be :path-info, as in Java and Ruby, but the "info" part
seems superfluous to me.
The :path key would be mandatory in the updated SPEC (for Ring version
0.4.0?). The :context key would be optional. If the handler has no
context (e.g. if you pass it directly into the Jetty adapter), then
the :path key would be equal to the :uri key.
In other words:
(= (:uri request)
(str (:context request) (:path request)))
Would always be true for the new version of the SPEC. To support
previous versions, one could fall back to the :uri if the :path is not
found:
(= (:uri request)
(str (:context request) (:path request (:uri request))))
This would be useful for the following reasons:
1. Web frameworks could start to use :path instead of :uri, providing
a standard way of interpreting context.
2. The :context key could be added to URLs in links, so a handler
proxied into a Java servlet would have correct links, even if the
servlet path is changed.
3. Web frameworks could check for :path first, and fall-back to :uri
if it is not found, allowing previous versions of Ring to be
supported.
I'd be interested in criticisms of this idea. I'm by no means sold on it...
- James
Perhaps, but so is :content-type, :content-length and
:character-encoding. Removing the :uri key also breaks backward
compatibility.
Other HTTP interfaces provide both (such as the Java servlet
specification) and I don't think that potential confusion is
sufficient grounds for breaking backward compatibility.
I'd be interested in criticisms of this idea. I'm by no means sold on it...
I really, really like this. It would make combining Ring handlers and
regular Java servlets cleaner, since the Ring code could know both the
full :uri and the relative context-free :path.
One small comment: the spec above suggests that either :context needs
to have a trailing slash, or :path needs a leading slash. I'm
concerned that some code will require slash checking routines, as (=
"/myapp" "myapp") is false, but may be true as far as URL checking
goes.
On Sat, Oct 16, 2010 at 6:52 PM, James Reeves <jre...@weavejester.com> wrote:
> (= (:uri request)
> (str (:context request) (:path request)))
>
> Would always be true for the new version of the SPEC. To support
> previous versions, one could fall back to the :uri if the :path is not
> found:
>
> (= (:uri request)
> (str (:context request) (:path request (:uri request))))
--
Omnem crede diem tibi diluxisse supremum.
I suggest that :path follows the same rules as :uri, in that it must
always begin with a "/". This would be necessary if :path were to be
used as a substitute for :uri.
And since :path must begin with a "/", :context should not end with a "/".
- James
:path does seem more logical, but :path-info is the name used by Java,
Ruby, .NET, Python and PHP. So I'm thinking that :path-info actually
might be the better choice, as it's the name used everywhere else.
Conversely, the :context key has no universal name.
- James
A feature that is missing is a way to build an URL reference for a
specific handler. Speaking in terms of your example, imaginge to link
from a blog post to a forum thread.
For simple handlers and routing this can be addressed by looking the
path for the forum handler in a routing map. However I suppose things
get complicated when handlers can be mounted as you sketched it.
I will try if I can work out a way to combine "in-context" with the
lookup of a handlers path.
-billy.
I suspect we'd need separate middleware to wrap the handler in a binding:
(declare *context*)
(defn wrap-context-binding [handler]
(fn [request]
(binding [*context* (request :context)]
(handler request))))
> * How you are able to / not able to obtain web context at boot / start
> time
To an extent, everything could be done using middleware. For example:
(defn wrap-servlet-path-info [handler]
(fn [request]
(let [path-info (.getPathInfo (:servlet-request handler))]
(handler (assoc request :path-info path-info)))))
It may be that if we were to go down this route, the :path-info and
:context keys would become de-facto standards.
However, I don't really like the idea of basing a web framework on
de-facto standards. It feels too much like circumventing the SPEC, and
risks fragmentation.
> * Where your workarounds in current Ring apps fell over
Compojure currently matches paths on the :uri, so routes with some
form of context have to rewrite the :uri key.
I've been thinking about providing middleware to do that, but it's
doesn't strike me as very neat.
> * If / how you see nested routes as being different from mounted apps
I don't think there is a difference. Whether an handler is embedded in
another handler, or embedded in a servlet, the general idea is the
same.
- James
* The mechanics of how you feed :path-info and :context values into
uri generation for responses
* Where your workarounds in current Ring apps fell over
* If / how you see nested routes as being different from mounted apps
I emphasize the importance here of example code in motivating the
discussion. I haven't had these use cases recently in any of my Ring
apps, but I started to sketch out some code to get a feel for the
problem:
http://gist.github.com/644502
I was making some changes to Clout and Compojure, and having a
:path-info key would be rather useful. I'm planning on changing Clout
so it only matches routes against request maps, and I'm planning on
improving Compojure's ability to embed one route inside another.
I could match against the :uri key, and alter it for embedded routes,
but it seems a rather nasty solution. The :path-info and :context keys
would be very nice to have.
- James
On 25 October 2010 07:53, Mark McGranaghan <mmcg...@gmail.com> wrote:
Thanks for the poke.
I have continued to think about this, and am still not convinced that
it should be in Ring core / the Ring spec.
My main hesitation is that the context (and therefore the path-info as
it relates to the uri) may need to be known outside of the
request-response cycle, or within the request-response cycle but not
particularly within the request map. As an example of the first point,
some of my Clojure apps generate and cache HTML asynchronously, and
this generation requires knowing the context. As an example of the
second, generating self-referencing urls requires some notion of
context, but it is more convenient to access through a vanilla var
than to extract it on every request from the request map. So I agree
that apps may sometimes need to know their context, but I'm not sure
(one way or the the other) that :context-info as the canonical source
is the best approach.
I think that the best iterative step forward would be for users who
want :path-info and :context keys to start using them and treating
them as a de-facto standard, in the same way that we use :params. This
will give us a chance to see how they are actually used in real code
and give us more data on which to make a decision to move the keys
into the Ring spec. I know you said specifically that you don't like
basing web application libraries on de-facto standards, but I think
it's worth doing for at least some amount of time so that we can see
what it would look like in actual application code.
How does that sound?
- Mark
I think I agree that the context is often most usefully kept in a var.
If the plans for supporting cross-thread bindings in Clojure bear
fruit, a bound var would be even more appropriate.
The path-info, however, is perhaps more closely tied to the request
map. My main interest in path-info is to use it for routing purposes
in Clout and Compojure, which currently use the :uri key.
I am a little wary about relying on the presence of a :path-info key
when not part of the spec. However, I can always fall back on the :uri
key:
(:path-info request (:uri request))
Which makes this less of a problem, as the :uri key is always
guaranteed to exist.
So I think that's probably okay. I just have a couple of questions:
1. Would you be okay with having wrap-context middleware in ring-core
that would add a :context and :path-info key to the request map?
2. Would you be okay with having the ring.util.servlet code (and by
extension, ring.adapter.jetty) adding a :context and :path-info key
automatically to the request map?
- James
Yes. I think having a canonical implementation of ":context and
:path-info via middleware" would be good. Were you thinking that this
middleware would also bind a context var? Perhaps this could be an
addition later. Also, as I explain below, I think that this middleware
should take an explicit context argument.
> 2. Would you be okay with having the ring.util.servlet code (and by
> extension, ring.adapter.jetty) adding a :context and :path-info key
> automatically to the request map?
I'd rather not do this just yet - I don't want to make speculative
changes to the API of such a core component, especially if users may
immediately start relying on it. I think a good compromise is to
require explicitly specifying the context to wrap-context. I'm also
hesitant on this because it suggests a mindset that I really want to
avoid, which is that the adapter/servlet and then :context is the
canonical source of the app's context. The :context key is a mechanism
for propagating the context throughout the request/response processing
call graph in a uniform way. The context should be known and set by
the app deployer, and that info given explicitly to any runtime
components that require it (perhaps through wrap-context, perhaps
through a var, etc.). It is in my view an error to only be able to
discover the context of your app by interrogating the adapter on which
it is running. As a practical matter, you'll probably went to set the
context explicitly anyways in your request/response tests. This is why
I rejected another idea that I had here, which was to have
wrap-context try to pull :context out of :servlet
- Mark
Okay, that seems reasonable. The context is unique to a particular
handler, rather than the request map.
However, the path-info *is* specific to the request map. I guess one
could always derive manually it by subtracting the context from the
URI, but it seems a fairly useful and appropriate thing to add to the
request map.
Perhaps the wrap-context middleware should *only* set the :path-info
key. After all, if the context is fixed, then one can refer to it via
a var or some other static reference.
(def my-context "/foo")
(def app (wrap-context handler my-context))
In this case, the context is static, and therefore is stored in a var.
The wrap-context middleware just adds a :path-info key to the request.
That said, if there is also a :context key, then generic middleware
can access the context, which may be useful for redirections and so
forth that require a URI.
Another option is to have a (path-info request) function, which
derives the path info dynamically from the request. However, in such a
case, how would the context be stored?
I guess that's the main question: where should the context be stored,
if not in the request map? Ideally, the context needs to be:
1. Accessible by generic middleware that might need it.
2. Specific to a handler, but not necessarily the entire program.
In fact, I'm not even sure about number 2, because one could
potentially have the same handler function running under different
contexts.
- James
I'm also hesitant on this because it suggests a mindset that I really want to
avoid, which is that the adapter/servlet and then :context is the
canonical source of the app's context. The :context key is a mechanism
for propagating the context throughout the request/response processing
call graph in a uniform way. The context should be known and set by
the app deployer, and that info given explicitly to any runtime
components that require it (perhaps through wrap-context, perhaps
through a var, etc.). It is in my view an error to only be able to
discover the context of your app by interrogating the adapter on which
it is running.
That's a good point. In such a case, the context would need to be held
in a binding, or added to the request map.
Having the context in the request map has a certain symmetry, as the
path-info is also contained in the request.
- James
That said, if there is also a :context key, then generic middleware
can access the context, which may be useful for redirections and so
forth that require a URI.
I guess that's the main question: where should the context be stored,
if not in the request map? Ideally, the context needs to be:
1. Accessible by generic middleware that might need it.
2. Specific to a handler, but not necessarily the entire program.
In fact, I'm not even sure about number 2, because one could
potentially have the same handler function running under different
contexts.
I tried implementing this, but wound up with the problem of what to do
if the context doesn't match.
For instance: (def app (wrap-context handler "/foo"))
In this case:
(app {:uri "/foo/bar"})
Is the same as:
(hander {:path-info "/bar", :context "/foo", :uri "/foo/bar})
But what should happen if the URI doesn't match the context, e.g.
(app {:uri "/other/bar"})
In Compojure, routes that don't match return nil, so we could do the
same in this case.
However, this would seem out of place in Ring. As far as I'm aware,
all other Ring middleware returns a valid response map, so this
doesn't seem the right choice.
I can think of a few other ways to do this, but I'm tempted to
implement this first in Compojure, in order to test it out.
- James