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. As I noted before, this won't work in general, because most browsers don't send the correct Accept: headers. Still, that's no reason not to use the header if no extension is sent.

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.

As the specification says, parameters in the Accept: header are rare, and my programs don't use them, so we will ignore them. That means that Accept: text/html; q=0.8; level=2, text/html; q=0.2; level=1 is interpreted the same as Accept: text/html; q=0.2; not exactly the same as the spec but easier to code.

The q parameter technically should be weighted by how much the host wants to send that type. Thus, Accept: image/jpeg; q=1, image/gif; q=0.2 means "Send me a jpeg; I'll accept a gif, but I'll be only 20% as happy." If the host feels that the gif is 10 times better than the jpeg, it should rank the gif as 10*0.2 = 2 and the jpeg as 1*1 = 1, and send the gif. My code doesn't have such strong feelings and ignores such niceties.

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 Accept: header, sends the default. If it's the default type that has the q=0, then the requestor loses.

One subtle trick from Kris Jordan's Recess: wild card ('text/*' and '*/*') types should rank below 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.

function parseAccept ($accept){
	$mimetypes = array( // associate types with file extensions
		'*/*' => 'html',
		'text/*' => 'txt',
		'text/plain' => 'txt',
		'text/html' => 'html',
		'text/csv' => 'csv',
		'text/javascript' => 'js',
		'image/*' => 'png',
		'image/png' => 'png',
		'image/gif' => 'gif',
		'image/jpeg' => 'jpg',
		'application/*' => 'js',
		'application/json' => 'js',
		'application/xml' => 'xml'
	);

	$types = array();
	foreach (explode(',', $accept) as $mediaRange){
		@list ($type, $qparam) = preg_split ('/\s*;\s*/', $mediaRange); // the q parameter must be the first one according to the RFC
		$q = substr ($qparam, 0, 2) == 'q=' ? floatval(substr($qparam,2)) : 1;
		if ($q <= 0) continue;
		if (substr($type, -1) == '*') $q -= 0.0001;
		if (@$type[0] == '*') $q -= 0.0001;
		$types[$type] = $q;
	}
	arsort ($types); // sort from highest to lowest q value
	foreach ($types as $type => $q){
		if (isset ($mimetypes[$type])) return $mimetypes[$type];
	}
	return 'html';
}

The same sort of code would work for Accept-Encoding: and the other Accept-type headers.

2 Comments

  1. YouTalkTech says:

    Accept-Encoding http header variations deviations normalize gzip, deflate, identity…

    i was quite surprised about the problem that comes with the http accept-encoding header when using a reverse proxy or http accelorator. you might have the following response header from your backend webservers: Vary: Accept-Encoding which is telling th…

Leave a Reply


Warning: Undefined variable $user_ID in /home/public/blog/wp-content/themes/evanescence/comments.php on line 75