-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[POC] Content negotiation implementation. #5711
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
Conversation
} | ||
|
||
/** | ||
* Returns true if the request is a XMLHttpRequest. | ||
* Gets a list of encodings acceptable by the client browser. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The client might not be a browser, but could be any machine requesting data from the server (search engine, another server, etc.). best to stick with a generic "client" terminology alone IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right that was a concrete example, not a generic one.
client browser
should be user agent
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you missed another "client browser" in src/Symfony/Component/HttpFoundation/Request.php (there were two of them)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, thanks!
@scor @fabpot I thing the POC is ready. |
* | ||
* @return array | ||
*/ | ||
public function mapValues(\Closure $closure) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should allow any callable, not only closures
@jfsimon does it work when using the cached router ? |
@stof you mean the |
well there isn't a "standard algorithm" afaik .. since the actual negotiation isnt covered in the spec. so i think its good indeed to make it possible to plugin different algorithms both the change the behavior but also to allow performance considerations. |
@lsmith77 I was thinking to http://httpd.apache.org/docs/2.2/en/content-negotiation.html#methods when talking about a "standard algorithm". I also keep http://pretty-rfc.herokuapp.com/RFC2295 in mind. In order to allow something more complex, I think I need to create some classes ( I also wonder how to manage the RFC2295's |
* | ||
* @return array Array indexed by the values of the Accept-* header in preferred order | ||
*/ | ||
static public function split($header) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's public static function
now following PSR-2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@jfsimon The discussion on the ticket requesting the feature was talking about creating a new component for the content negotiation |
@stof i dont think it necessarily needs to be a new component. but if an existing component is used, why not HttpFoundation? |
I would encourage making it a stand-alone component for easier reuse. There's no need to bind the negotiation algorithm to HttpFoundation's Request implementation. |
I'm not quite clear from the code, but the description above says that one would specify a route format like this: _format: html|json|xml That is not sufficient. That's just the serialization format. application/atom+xml and application/svg+xml are very different formats, but both "xml parsing". Similarly, application/json does not imply JSON-LD, which has its own mime type, I believe. |
@Crell: I think it makes sense to offer a simple algorithm just based on the "mapped" formats. But I agree there also needs to be a way to do negotiation based on actual mime types. |
@lsmith77 @Crell do you think that RFC2295 should be fully implemented? There's a lot of interesting features, but also many things involved. Would you like to discuss about it on IRC? I think the code could take place in
|
Yes, let's talk in #symfony-dev. I'm available now. |
@jfsimon who has actually implemented RFC 2295? I am not an expert in the entire world of RFCs, but from all I know that RFC requires client support. |
@Crell I improved the |
* | ||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> | ||
*/ | ||
class AcceptHeader |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would renamed it to AcceptHeaderParser
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would agree.
$bits = explode('=', $property); | ||
$this->values[$name][trim($bits[0])] = isset($bits[1]) ? trim($bits[1]) : null; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm not completely off, this parsing is too optimistic. It will break as soon as one of the characters used in explode()
appears in the value of a parameter. Consider e.g. the following Accept header:
text/html;q=0.9,text/plain; charset=utf-8; param="this;should,not,matter"; footnotes=true
The current code interprets not
and matter
as parameters whereas they really should be part of the value of param
.
I'm not sure if the adding complexity for these corner cases is worth it, but at least this limitations should be documented properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be quite easy to implement something more sophisticated using preg_split
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lanthaler @schmittjoh you're absolutely right. I'll propose a PR soon with this only feature (the accept-* headers parsing) and I'll use preg_split
which is awesome.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's of any use, we used a little trick to process the q
part here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the example at https://gist.github.com/3872155 still the right way to test this? I get a fatal error: |
@scor I fixed your exemple above, the value of |
@jfsimon ok, new error now. If this example working ok for you? I get this: |
@jfsimon you need to add the defaults for _locale and _format to the example route in the introduction. Otherwise |
@Tobion this should not be necessary, and it's not. Take a look at the @scor I didn't test it (I know it's bad), and I'm not really sure this approach is the best one. What do you think? I'll take a moment this afternoon to fix this. Thanks guys for your feedback. |
turns out the maintainer of the mimeparse library (@ramsey) is also a contributor to Symfony. @ramsey, is there anything from your lib that could be contributed in this PR? Could you maybe review the code here and the sub-PR @pdrakeweb created at jfsimon#1 ? any kind of feedback to make progress here would be great! :) thanks |
@pdrakeweb sorry for the late... I'll try to merge your PR after rebasing master. I'll surely face many conflicts :-/ I'm not sure this POC should be merged to Symfony, I think it's a well playground to test ideas, but real changes should be done in separate PR (as it was done for header parsing), @scor what do you think? IMHO, the difficult part of this is the framework integration, (not the variant negotiation). |
havent studied the code in detail yet but it seems like zf2 got a decently sophisticated implementation: |
speaking about locale and formats, you should also consider this: most browsers send accept-language implicitly with every request and it's value equals OS's locale. but this is not always the visitor's desired locale (i have eng windows, but wanna see website in czech). but the situation changes with other formats like json or xml. these formats are usually not requested by a browser, but by a 3rd party app (eg curl) or a xhr. and these "requestors" do not send accept-language headers implicitly, but only if their user sets them explicitly. therefore in this case, accept-language is more significant than session value. when requesting a html format, the priority for determining request's locale should be like this:
but with other formats (xml, json):
also would be nice if the usage of session for locale can be enabled/disabled via framework configuration |
The usage of session has been removed in 2.1. If you want to store the locale in the session, you should do it in your own listener. |
@kor3k maybe you should look into https://github.com/lunetics/LocaleBundle |
This PR was squashed before being merged into the master branch (closes symfony#5841). Commits ------- 6b601bd [http-foudation] Better accept header parsing Discussion ---------- [http-foudation] Better accept header parsing Bug fix: no Feature addition: yes Backwards compatibility break: yes Symfony2 tests pass: yes **Quality:** The special `q` item attribute represents its quality. I had to make some choices: * if I set `q` attribute, it's assigned to quality property, but not to attributes * the `__toString()` method only render `q` attribute if quality is less than 1 **BC break:** The return of `Request::splitHttpAcceptHeader()` has changed. It's result was an array of qualities indexed by an accept value, it now returns an array of `AcceptHeaderItem` indexed by its value. --------------------------------------------------------------------------- by jfsimon at 2012-10-26T08:35:55Z As dicussed in symfony#5711. --------------------------------------------------------------------------- by Seldaek at 2012-10-27T10:35:49Z Maybe you can pull Seldaek@5e8a526 into your branch (for some reason I can't send a PR to your repo, it doesn't show up in github's repo selector.. looks like they don't like projects with too many forks). It allows you to use usort() which hopefully is faster than your merge sort, though I did not bench it. I also added tests to confirm the functionality. --------------------------------------------------------------------------- by Seldaek at 2012-10-27T10:40:27Z Sorry please check Seldaek@376dd93 instead, I missed a few tests in the RequestTest class. --------------------------------------------------------------------------- by jfsimon at 2012-10-29T16:26:03Z @fabpot do you think the introduced BC break is acceptable? --------------------------------------------------------------------------- by fabpot at 2012-10-29T16:37:06Z @jfsimon Are all getAccept*() method BC? --------------------------------------------------------------------------- by jfsimon at 2012-10-29T16:39:26Z @fabpot nope, just `Request::splitHttpAcceptHeader()` --------------------------------------------------------------------------- by jfsimon at 2012-10-29T16:43:18Z @fabpot I think missunderstood... only `Request::splitHttpAcceptHeader()` breaks BC. --------------------------------------------------------------------------- by fabpot at 2012-10-29T16:53:22Z So, a BC break on just splitHttpAcceptHeader is possible... but should be documented properly. Another option would be to deprecate the current method (and keep it as is), and just use the new version everywhere. Sounds better as it won"t introduce any BC breaks. --------------------------------------------------------------------------- by jfsimon at 2012-10-29T16:55:57Z @fabpot Okay, I'll update this PR according to your second option. --------------------------------------------------------------------------- by jfsimon at 2012-10-29T20:14:46Z @fabpot done. As you can see here: https://github.com/symfony/symfony/pull/5841/files#L5L1029 value returned by `Request::splitHttpAcceptHeader()` is not **exactly** the same as before because all attributes are present (not only those before the `q` one). --------------------------------------------------------------------------- by fabpot at 2012-10-30T06:16:23Z The last thing missing before I can merge is a PR to update the documentation (should probably be just a note somewhere with the example you have in the UPGRADE file). --------------------------------------------------------------------------- by jfsimon at 2012-10-30T07:07:08Z @fabpot I could add this example here: http://symfony.com/doc/current/components/http_foundation/introduction.html#request after `Accessing the session`, what do you think? --------------------------------------------------------------------------- by fabpot at 2012-10-30T07:14:10Z Yes, looks good to me.
Closing (#10538) |
This PR attempts to implement server content negotiation features to Symfony2 components, as described here: http://httpd.apache.org/docs/2.2/en/content-negotiation.html.
How should it work:
Let says I wish to let the framework negotiate the format & locale, I would write my routes like this:
I need to have the following headers in my request:
Accept
which gives the accepted content types (_format
)Accept-Language
which gives the accepted languages (_locale
)I want to get the best resource variant when requesting
/my/route
URL.The following rules should do the trick:
en
,es
orfr
for_locale
) is present the correspondingAccept
header (ie.Accepted-Language
for_locale
), a 406 exception is thrown.Here is an example: https://gist.github.com/3872155