-
Notifications
You must be signed in to change notification settings - Fork 889
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
accept handling during view lookup is unpredictable #1264
Comments
👍 This ultimately also comes down to the way that WebOb parses the accept headers, and what it considers a match. Maybe there should be a way to verify it matches exactly (ignore |
I don't actually think this is a problem with WebOb but rather how Pyramid handles the accept predicate in the |
FWIW, in WebOb they allow you specify a "server preference" for the accept offers by specifying a pair of (content_type, server_q) instead of just the content_type. If pyramid supported the same thing it could pass-through to that mechanism to let the user specify the ordering in cases where abiguity is a problem. |
Another idea: for a route one could optionally specify a list of content-types available for that route. The order specified in the route would be used to resolve ambiguities like the one mentioned to provide a final order of preference for the result representation. This order could be used to order the views before checking their other predicates. |
I think in cases where there IS ambiguity it might make sense to issue a warning of some sort and provide a way for developers to resolve the issue in some manner such as the two ideas above. |
I think that the |
Those two that you just mentioned should conflict. They are both of type 'application/json' and the second would NEVER match.
|
Yep, bad example on my part. |
The server quality is already supported by webob and so we could start supporting it with only minimal changes to pyramid's accept handling. I'm interested in arguments for or against this. |
The first argument against I can think of is that the interaction between server quality and client quality is a little hard to understand because they are multiplied together to come up with the final quality / sorted order of views (it's not defined at config time but rather per request). Regardless of that, I think it's not a bad solution. |
In most cases the server quality will be used mainly to disambiguate which one is preferred ("default") server side. |
Are you saying that the quality option is overkill then? You'd rather just have a server-side weighting that is not combined with the client-side weighting? |
I would like the option of having it take into account client side weighting (so that for example if my client sends Just based on my experience it is pretty rare to require full on matching. I haven't had a use case for it yet... All I generally care about is that |
So hypothetically let's use the solution I've described as it's the only one I can find that's actually concretely defined. How does a user get that to work? config.add_view(..., accept='application/json')
config.add_view(..., accept='text/html')
config.add_view(..., accept=None) This snippet works fine when the client specified different quality for application/json and text/html. How can we change that snippet to work when the client specifies the same quality? config.add_view(..., accept='application/json')
config.add_view(..., accept='text/html;q=0.9') Okay, we now declare that we prefer application/json. Using multiplication we get that Is this what we want? Now when a client requests text/html they will get back application/json unless they specific give text/html a higher quality. So let's say they do that and specify Long story short, I'm not sure multiplying |
Welcome to the shitty part of the HTTP standard ;-) There is also no defined standard for how Accept/Accept-Encoding/Accept-language and whatnot should be checked, so depending on implementation you could end up with different documents. |
Well the sanest thing I can think of is to use the quality of the client cross-checked against a sorted list from the server. The question is how to define the sorted list. It's not possible in pyramid to define one directly because
I'm trying my hardest to come up with some options! |
Use cases:
So, looking at the cases I can think of, I think the server-side matching should only be used to break a tie if it's not clear which type the client prefers. |
Thanks @dobesv, I think we are in agreement here that Pyramid currently works correctly in every situation except when the client specifies multiple accept types with the same quality. |
https://httpd.apache.org/docs/trunk/content-negotiation.html might be of interest to see how other projects handle this. |
@zart that document is one of the few that I've found that shows a concrete ordering, because none of the standards mention it. It basically devolves into a very large complicated matrix of possible choices... @mmerickel and I had a discussion using that document as an example a while back with some ideas that came out it, but it's still difficult within Pyramid. |
It's one of implementations of https://tools.ietf.org/html/rfc2295 as far as I understand. |
It sounds like this is a reasonably complicated issue made worse by ambiguity in the RFC. Would it be possible to pick the low-hanging fruit and at least provide a way to specify a predictable "fallback" choice for when the client doesn't send an |
The two scenarios that are really difficult in pyramid right now are:
We had a very long conversation in irc about this that I'll link here for now until I have time to go back and distill it into possible action items. https://botbot.me/freenode/pyramid/2016-11-28/?msg=77139347&page=2 |
I've been looking into this issue to see if I can help with fixing it, and have a couple of questions:
|
The
I think I disagree with this. I mean practically you could make that argument but theoretically a view can just say I accept these types of things and then internally decide between lots of things what type to return. We don't really know what the view wants to do with the request after it matches.
Yes, it's possible to override views later during config-time etc. There is 100% absolutely no guaranteed order between views defined by their place in a python module. |
Yes sorry, I was totally focusing on the wrong thing, but I see it now. In that case, we could add an optional order value, either specified with the accept argument as a pair, or as a separate argument. View configurations with no order value specified would take a default value of 0; when specified, the optional order value could be any integer (positive, 0, or negative). The views could then be looked up from highest order value to lowest? This made me wonder whether the order value might be useful beyond the accept parameter: are there other cases where we might need to control view lookup order? Such as when two view configurations have the same number of predicates (so same specificity), and they both match; or cases where we might need view lookup to ignore specificity completely and order the views in another way? I'm still familiarising myself with the internals of how Pyramid view lookup works, and what I've been able to gather from testing and reading the code so far is that there may be more to view lookup than just specificity; that each predicate also has its own weight that affects lookup? Would that be correct?
I think I see that possibility. But let's say the If we take the 'text/*' to mean "this view will handle any Or we could take the 'text/*' to mean "this view should match only when all subtypes under (Right now This behaviour is not in RFC 7231 — there are no wildcards on the server side. To me this seems to be something outside content negotiation, and Pyramid-specific; and it turns out it was actually introduced into WebOb in response to Pylons/webob#155, where it was noticed that Pyramid documentation allowed wildcards in the |
Is your concern about wildcards simply the |
The wildcard This issue (in my mind) really boils down to what does the client get when they don't send an
I think if you use a wildcard in your Not really a case I'd work hard to support, though, as you said it can easily be worked around by using the WebOb APIs yourself to pick a media type. |
The issue is present for any Accept header, where there is more than one equally-preferred "best match" given the view configurations. I proposed a fix in the first half of my last comment, using the optional order value — what do you think? I have read through many of the discussions on this issue, and believe similar solutions to the problem have been discussed, but not with 0 as the default value — I think 0 should work well. I mentioned the issue with wildcards because it seemed to me it would significantly simplify the task if wildcards weren't allowed in the What do you think about the fix for the issue in this ticket proposed in the first half of my last comment? |
For our app I treat any We don't use wildcards in the accept predicate, I think that is not a great idea even if it could be useful from time to time as a shortcut. I would suggest ignoring that case even if it is supported by accident. I don't worry about more complicated "theoretical" cases since I know the full range of Accept headers I need to support. That said, the original issue here was some vague feeling that Accept header processing should be more deterministic. I think once you have addressed the missing/empty/wildcard It's really just browsers (who always prefer text/html), API clients (who should use mandated, simple Accept headers or no Accept header if they are legacy clients), and |
But the problem isn't just with wildcards and missing header. Let's say your app has one view configuration with an accept argument of 'text/html', and another view configuration with an accept argument of 'application/json'. The header coming in is 'text/html, application/json'. Currently which view is called for the request is essentially random.
It's really not an error: there is nothing wrong with having more than one equally preferred 'best matches'. We find this in one of the examples in RFC 7231:
So if we have two views here, one with an accept argument of 'text/html', and the other with an accept argument of 'text/x-c', then they are both equally preferred. The RFC does not say what to do then, I imagine because there are applications where we might want to return both media types at the same time; but in Pyramid's case, the logical action to take is for the server to just choose one for the client. To choose a best match, Pyramid needs to pass a sequence-type (tuple/list) |
The only thing I don't like about the ordering thing is that it requires more book-keeping and, at the end of the day, you might end up with a lot of views with the same order value that are being selected arbitrarily. Or developers have to come up with some kind of internal rule like "set 100 for JSON, 50 for html, etc...". I wonder if setting a global preference on media types would be more practical, since you just have to do it once, globally for the application. Some kind of "tie breaker" rule where you can say "If in doubt, prefer application/json, then image/png, then text/html, then text/plain." I suppose the list is not complete you can still end up with an arbitrary choice. I guess at the end of the day we can never eliminate the possibility of a tie, we can provide some tools to help avoid them like the ordering parameter you gave. Honestly I don't know which is better, I'm happy with my solution, so I'm just putting in my 2 cents here. It's up to Michael in the end, I think. |
Hey folks I've been following this discussion and it has been excellent. I agree that handling the accept issues on a per-view basis can be pretty annoying and is why I'm also leaning toward some form of global hook which can sort the views however it wishes. The api isn't quite solidified in my head because I don't really want something that looks like view lookup. It's probably something that takes the Ideally this sorting would be done at config-time such that the sort wouldn't even need the request, but I'm not clear yet on whether that makes sense. It basically means sorting offers in a vacuum but that might be want we want here. |
Another place to put the ordering rule is on the route. I believe all the
matching views will be on the same route. You could provide a per route
sorting function for views. For someone who wants a global sort it's not
so bad to pass the same function or rule too every route, at least there
are fewer routes than views.
…On Fri, Sep 15, 2017, 8:31 AM Michael Merickel ***@***.***> wrote:
Hey folks I've been following this discussion and it has been excellent. I
agree that handling the accept issues on a per-view basis can be pretty
annoying and is why I'm also leaning toward some form of global hook which
can sort the views however it wishes. The api isn't quite solidified in my
head because I don't really want something that looks like view lookup.
It's probably something that takes the request and a list of accept
offers from view_config(accept=...) and returns the ordered list of
offers. The offers would then be used to order the views for the remainder
of view lookup. It's basically the get_views function here that needs
some assistance testing accepts in the right order.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1264 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAUAmX_E0-yRvXrQcGo7odFXY11NEgFAks5siphsgaJpZM4Bnz7K>
.
|
I agree that we need a way to specify the order globally — we are talking about being able to specify the order both globally and per view, right? Unfortunately I don't think we can specify the order on the route, as it also needs to work with traversal? I believe the sort can be done at config-time, as it is just about coming up with an ordered sequence-type |
An active PR and discussion on this topic is in #3326 and I would really appreciate anyone who thinks Pyramid should actually support media ranges in the accept predicate to weigh in on their use case because right now everyone seems to be against supporting them and requiring concrete media types. |
this issue is fixed via #3326 |
As per the examples in #1259, it should be clear that the accept handling in Pyramid is somewhat unpredictable. It is a smell that the view invoked is somewhat dependent on the ordering of a
set
. When defining some views with accept predicates, a user should be able to have a solid understanding of when each view will be invoked, and possibly be able to influence what happens what a client simply asks for*/*
or does not include anAccept
header.The text was updated successfully, but these errors were encountered: