-
Notifications
You must be signed in to change notification settings - Fork 38k
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
Use parameters declared in consumes or produces condition to narrow the request mapping [SPR-17133] #21670
Comments
Rossen Stoyanchev commented I see the same issue with this proposal as with the other tickets that were closed. It treats all parameters as equally important which may not be the case. To quote again from RFC 7231 3.1.1.1:
So if a client sends It also means you're forced to declare a parameter in order to handle a media type with it. In other words there is no way for a controller declare that it can produce The switch for ProducesRequestCondition to use includes from isCompatibleWith also doesn't seem right. That symmetry is deliberate. If a client sends |
Jose Montoya commented Hey Rossen, thanks for the feedback.
I did considered a different approach, to somehow allow the developer to implement and pass their own parameter matching. If the significance of parameters is dependent on the media type definition then there's no other option but to accommodate different matching logic by media type. I suppose that would be the most correct approach. Perhaps we can make this PR the default implementation yet also allow developers to implement their own if it doesn't suit their purposes. ie. "By default all parameters are equally important, though this behavior can be overriden by providing...." or something along those lines. On the other hand, in order to minimize backwards incompatibility we can leave the current implementation as default, offer this PR logic as an optional configuration and lastly allow devs to include their own ie. "By default parameters are not included in content negotiation process, but if you do the following ... then the process will include the parameters in a all-params-are-significant manner, or you can override all behavior by providing ..." |
Rossen Stoyanchev commented Charset is just one example. Special logic does not address the general point. For the isCompatible vs includes check, I gave a fundamental example of why that is the way it is, and why it can't just change from one to the other. Conceptually, isCompatible is what we're looking to do here. To summarize, if a requested media type has a parameter we are not in any position to make conclusions, or to insist that mappings also have it. That is not a viable path. At best I can imagine that if a |
Jose Montoya commented Again, thank you for your feedback. I definitely don't have half as much insight as you do into the framework, but I'm hoping we can work out a solution. Take a media type like json+ld for example, if the server only produces If we agree that this might not be the correct behavior for ALL media types, and forgetting the PR, what changes could accommodate those media types for which it would be the correct behavior? Off the top of my head media types |
Rossen Stoyanchev commented Good to have concrete examples! I'm not familiar with JSON-LD but the profile parameter sounds like a preference rather than a different representation. I'd expect one The media type definition confirms it's the "same representation" and "preference":
Connecting to the summary from my last comment:
For example |
Jose Montoya commented Ah, I think we're starting to achieve some understanding! I'll try to propose more concrete examples: On
which would only be the case if the parameter may indeed vary the content.
A
|
Rossen Stoyanchev commented You're suggesting that any parameters in the requested media type should not match to controller methods that do not declare the same parameters. That is way too strict and doesn't really add up. Let's use those examples.
I don't know much about how such text is typically stored, i.e. whether the "flowed" formatting is applied on the fly, but logically
So we have a mix of examples here. For some, it's a single controller method that returns the same data, with the message converter transparently handling media types parameters. For others, it's multiple controller methods returning different data based on a media type parameter value, with the message converter simply rendering whatever object it has been given. The Spring framework has no knowledge of which parameters fall the first category that I would call "not significant" for request mapping purposes, and which fall in the second category which I would call "significant" and that should be used to narrow the request mappings. So it can't selectively choose from the parameters in the requested media type. At the same time forcing controllers to declare any combination of potential parameters and values makes zero sense too. Instead I'm proposing for controller methods to declare what parameters they want to match on, and which should be used to narrow request mappings. This should work fine for the Atom case I think where a controller would have to declare two methods, one
It has to be symmetric for type and sub-type. The check for parameters would be applied in addition, where any parameters on the declared, producible type would have to be present on the requested media type. |
Jose Montoya commented Hmm OK, I do think what you're proposing is a great start, maybe I'm just not familiar enough with HTTPMessageConverter, I'll dig into that. How would you envision the following use case working: Let's say we have a resource offered as Let's say we have a resource offered as HAL does not currently define a version parameter, but it's one that has been proposed frequently when discussing restful media types. Profile is something that would be defined by different applications, an unbound number of profiles could exist. Is this still something that the MessageConverter could/should handle? |
Rossen Stoyanchev commented Generally speaking, yes an HttpMessageConverter is given an Object (the return value from the controller) and a MediaType. It can examine the parameters and raise HttpMediaTypeNotAcceptableException which would be translated to a 406 response. Does the controller return the same or different Object type for |
Jose Montoya commented Oh I see! However HttpMediaTypeException is not part of the throws signature for |
Rossen Stoyanchev commented Again it would help to know if the Controller is expected to have two controller methods or not? Even if it is different controller methods, I think it would be up to the converter to reject profile values that are not supported and therefore cannot be rendered. In terms of mappings, "app/hal+xml" expresses a broad match by type and sub-type, i.e. not narrowed by profile parameter, if that makes sense -- e.g. the same controller method handles any request for the given type + sub-type regardless of parameters, or otherwise each controller method would have to narrow its mapping by profile. |
Rossen Stoyanchev commented We'll try and experiment for 5.2 with support for parameters explicitly declared in the Once an implementation is available in snapshots, it will be possible to experiment with the specific use cases mentioned here, or any others, in order to validate the approach. |
Jose Montoya commented I imagine it should be one controller method, just as we can do method=get
value=/pet
produces=json, xml I thought we should be able to do method=get
value=/pet
produces=hal+xml;profile=foo,hal+xml;profile=bar because I consider them to be logically different media types. But either way, your suggestion to include only the parameters explicitly declared would suffice for this use case. For more complicated matching use cases the ability to throw a HttpMediaTypeException from the converter would be great as well. |
Rossen Stoyanchev commented A mapping like below, with the profile parameter declared, should work based on the proposal: produces = {"hal+xml;profile=foo", "hal+xml;profile=bar"} It will not match to the controller method. Then |
Mark Hobson commented I've commented on #15531 regarding the issues I've had with honouring Atom media type parameters in content negotiation. As promised, here's some example code showing my use-cases and my current workarounds: https://github.com/markhobson/spring-atom-issue I commented the code accordingly but let me know if you have any questions. It feels like the approach emerging here and on #21578 is a step in the right direction though. |
Hey @rstoyanchev - can we possibly give this another look? I've recently come back to an use case where this'd be incredibly useful. I think maybe we can consider something like my original PR https://github.com/spring-projects/spring-framework/pull/1920/files but done in a way that allows users to provide their own parameter negotiation logic like @markhobson showed in a previous comment. Maybe through an SPI loader when parsing media type strings...? |
Jose Montoya opened SPR-17133 and commented
At Rossen Stoyanchev's request I'm creating this ticket with the intention to put forward a cleaner description of use cases to maybe facilitate discussion. This issue that has been argued before in #17949 and #15531 and sprung an atom specific one #21578. There's also a StackOverflow question related to this topic here.
As shown by RFC 7231 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content Section 5.3.2 Accept, media type parameters are to be included in the content negotiation process. Even if perhaps not called out explicitly in that section it is unarguably most evident that parameters participate in the process from the example in Page 39, where
text/plain;format=flowed
is considered independently fromtext/plain
. If we have consensus with that claim then we can move to some use cases. Following the logic from the RFC, but without adding quality attributes at this point:Setup:
For a given resource the server explicitly Produces
type/sub; param1=foo
only, which is implicitly included intype/sub
andtype/*
Use case 1, sub-type wildcard:
The client Accepts
type/*
The server should return the content as identified by
type/sub; param1=foo
, as it's the most specific one included.Use case 2, specific sub-type:
The client Accepts
type/sub
Same as UC-1.
Use case 3, specific param match:
The client Accepts
type/sub; param1=foo
The server should return the content as identified by
type/sub; param1=foo
, as it's an exact match.Use case 4, specific param no match:
The client Accepts
type/sub; param1=bar
The server should return a 406 (Not Acceptable), the param1 values are not equal.
Use case 5, multiple specific param no match:
The client Accepts
type/sub; param1=foo; param2=bar
The server should return a 406 (Not Acceptable), the param1 vales are equal, but param2 means the client only accepts something more specific than what the server produces. What the client wants does not include what the server produces.
First issue:
In spring-core/MimeType there are two methods for comparing media types
isCompatibleWith
andincludes
, none of them take into consideration parameters. UC-4 and UC-5 currently fail by returning200 (OK)
with the content oftype/sub; param1=foo
but headersContent-Type=type/sub; param1=bar
andContent-Type=type/sub; param1=foo; param2=bar
, respectively. This is obviously erroneous behavior.Fix:
MimeType.isCompatibleWith
should be enhanced to consider parameters as follows: "Parameters are incompatible only when they contain the same parameter with different values."MimeType.includes
should be enhanced to consider parameters as follows: "Parameters are not included when this MimeType contains more parameters than the supplied, when this contains a parameter that the supplied does not, or when they both contain the same parameter with different values." This would not be inconsistent with both methods' current implementation.Second issue
spring-webmvc/ProducesRequestCondition's
matchMediaType
method utilizesisCompatibleWith
which is a symmetric comparison, ie. even if we introduced parameters into its logic, UC-5 would fail by returning200 (OK)
with the content oftype/sub; param1=foo
but with headerContent-Type=type/sub; param1=foo; param2=bar
. This should be an asymmetric comparison because even though two media types may be mutually compatible the client may receive something that is not included by what it is explicitly requesting.Fix:
ProducesRequestCondition.matchMediaType
should be modified to use the enhancedincludes
instead ofisCompatibleWith
.Affects: 5.1 RC1
Reference URL: #15531
Issue Links:
Referenced from: pull request #1920
1 votes, 3 watchers
The text was updated successfully, but these errors were encountered: