{"id":1126,"date":"2010-01-06T21:03:13","date_gmt":"2010-01-07T03:03:13","guid":{"rendered":"http:\/\/bililite.nfshost.com\/blog\/?p=1126"},"modified":"2012-06-13T07:40:25","modified_gmt":"2012-06-13T13:40:25","slug":"parsing-the-http-accept-header","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2010\/01\/06\/parsing-the-http-accept-header\/","title":{"rendered":"Parsing the HTTP Accept: header"},"content":{"rendered":"<p>I wanted the <a href=\"\/blog\/2009\/12\/31\/bililite-com-webservices\/\">webservices<\/a> to be as <a href=\"http:\/\/en.wikipedia.org\/wiki\/Representational_State_Transfer\">RESTful<\/a> as possible, so they should use the <a href=\"http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec14.html#sec14.1\"><code>Accept:<\/code> header<\/a> rather than file name extensions to determine the type.<!--more--> As I noted before, this won't work in general, because <a href=\"http:\/\/www.newmediacampaigns.com\/page\/browser-rest-http-accept-headers\">most browsers don't send the correct <code>Accept:<\/code> headers<\/a>. Still, that's no reason not to use the header if no extension is sent.<\/p>\r\n<p>Parsing the header is straightforward; the mime-types are separated by commas (note that the top-level separation is the comma, not the semicolon) and within each mime-type description the parameters are separated by semicolons. The first field is the mime-type itself (with possible wild card '*'), the second (if present) is the quality factor, \"q={number from 0 to 1}\" which describes how much the requestor wants that particular mime-type, and the remainder of the fields are other parameters.<\/p>\r\n<p>As the <a href=\"http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec14.html#sec14.1\">specification<\/a> says, parameters in the <code>Accept:<\/code> header are rare, and my programs don't use them, so we will ignore them. That means that <code>Accept: text\/html; q=0.8; level=2, text\/html; q=0.2; level=1<\/code>\r\nis interpreted the same as <code>Accept: text\/html; q=0.2<\/code>; not exactly the same as the spec but easier to code.<\/p>\r\n<p>The q parameter technically should be weighted by how much the <em>host<\/em> wants to send that type. Thus, <code>Accept: image\/jpeg; q=1, image\/gif; q=0.2<\/code> means \"Send me a <code>jpeg<\/code>; I'll accept\r\na <code>gif<\/code>, but I'll be only 20% as happy.\" If the host feels that the <code>gif<\/code> is 10 times better than the <code>jpeg<\/code>, it should rank the <code>gif<\/code> as 10*0.2 = 2 and the <code>jpeg<\/code> as 1*1 = 1, and send the <code>gif<\/code>. My code doesn't have such strong feelings and ignores such niceties.<\/p>\r\n<p>A q parameter of 0 should be interpreted as \"never send me this type\" but my code simply ignores it and, if there are no other mime-types in the <code>Accept:<\/code> header, sends the default. If it's the default type that has the q=0, then the requestor loses.<\/p>\r\n<p>One subtle trick from <a href=\"http:\/\/github.com\/recess\/recess\/blob\/master\/recess\/recess\/http\/AcceptsList.class.php\">Kris Jordan's Recess<\/a>: wild card ('text\/*' and '*\/*') types should rank <em>below<\/em> more specific types with the same q parameter. We subtract 0.0001 from the stated q to push them lower; the specification says the value cannot have more than 3 decimal places.<\/p>\r\n<pre><code class=\"language-php\">function parseAccept ($accept){\r\n\t$mimetypes = array( \/\/ associate types with file extensions\r\n\t\t'*\/*' =&gt; 'html',\r\n\t\t'text\/*' =&gt; 'txt',\r\n\t\t'text\/plain' =&gt; 'txt',\r\n\t\t'text\/html' =&gt; 'html',\r\n\t\t'text\/csv' =&gt; 'csv',\r\n\t\t'text\/javascript' =&gt; 'js',\r\n\t\t'image\/*' =&gt; 'png',\r\n\t\t'image\/png' =&gt; 'png',\r\n\t\t'image\/gif' =&gt; 'gif',\r\n\t\t'image\/jpeg' =&gt; 'jpg',\r\n\t\t'application\/*' =&gt; 'js',\r\n\t\t'application\/json' =&gt; 'js',\r\n\t\t'application\/xml' =&gt; 'xml'\r\n\t);\r\n\r\n\t$types = array();\r\n\tforeach (explode(',', $accept) as $mediaRange){\r\n\t\t@list ($type, $qparam) = preg_split ('\/\\s*;\\s*\/', $mediaRange); \/\/ the q parameter must be the first one according to the RFC\r\n\t\t$q = substr ($qparam, 0, 2) == 'q=' ? floatval(substr($qparam,2)) : 1;\r\n\t\tif ($q &lt;= 0) continue;\r\n\t\tif (substr($type, -1) == '*') $q -= 0.0001;\r\n\t\tif (@$type[0] == '*') $q -= 0.0001;\r\n\t\t$types[$type] = $q;\r\n\t}\r\n\tarsort ($types); \/\/ sort from highest to lowest q value\r\n\tforeach ($types as $type =&gt; $q){\r\n\t\tif (isset ($mimetypes[$type])) return $mimetypes[$type];\r\n\t}\r\n\treturn 'html';\r\n}<\/code><\/pre>\r\n<p>The same sort of code would work for <code>Accept-Encoding:<\/code> and the other Accept-type headers.<\/p>","protected":false},"excerpt":{"rendered":"I wanted the webservices to be as RESTful as possible, so they should use the Accept: header rather than file name extensions to determine the type.","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[],"_links":{"self":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/1126"}],"collection":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/comments?post=1126"}],"version-history":[{"count":12,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/1126\/revisions"}],"predecessor-version":[{"id":2410,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/1126\/revisions\/2410"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=1126"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=1126"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=1126"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}