Skip to content

Commit

Permalink
Merge pull request #18 from poef/master
Browse files Browse the repository at this point in the history
fixed docblocks
fixed cache control checks
added support for multiple cache-control headers
made parseHeader more robust
  • Loading branch information
poef committed Jan 27, 2016
2 parents 612ab5f + 66c0c1a commit 954270d
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 67 deletions.
40 changes: 20 additions & 20 deletions src/http/ClientStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@
*/
class ClientStream implements Client
{
private $options = ['headers' => []];
private $options = ['headers' => []];

public $responseHeaders = null;
public $requestHeaders = null;
public $requestHeaders = null;

/**
* Merges header string and headers array to single string with all headers
* @return string
*/
private function mergeHeaders() {
$args = func_get_args();
$args = func_get_args();
$result = '';
foreach ( $args as $headers ) {
if (is_array($headers) || $headers instanceof \ArrayObject ) {
Expand All @@ -47,10 +47,10 @@ private function mergeHeaders() {

/**
* Send a HTTP request and return the response
* @param null $method The method to use, GET, POST, etc.
* @param null $url The URL to request
* @param null $request The query string
* @param array $options Any of the HTTP stream context options, e.g. extra headers.
* @param string $type The method to use, GET, POST, etc.
* @param string $url The URL to request
* @param array|string $request The query string
* @param array $options Any of the HTTP stream context options, e.g. extra headers.
* @return string
*/
public function request( $type, $url, $request = null, $options = [] )
Expand All @@ -62,7 +62,7 @@ public function request( $type, $url, $request = null, $options = [] )
}

$options = [
'method' => $type,
'method' => $type,
'content' => $request
] + $options;

Expand Down Expand Up @@ -93,9 +93,9 @@ public function __construct( $options = [] )

/**
* Send a GET request
* @param null $url The URL to request
* @param null $request The query string
* @param array $options Any of the HTTP stream context options, e.g. extra headers.
* @param string $url The URL to request
* @param array|string $request The query string
* @param array $options Any of the HTTP stream context options, e.g. extra headers.
* @return string
*/
public function get( $url, $request = null, $options = [] )
Expand All @@ -105,9 +105,9 @@ public function get( $url, $request = null, $options = [] )

/**
* Send a POST request
* @param null $url The URL to request
* @param null $request The query string
* @param array $options Any of the HTTP stream context options, e.g. extra headers.
* @param string $url The URL to request
* @param array|string $request The query string
* @param array $options Any of the HTTP stream context options, e.g. extra headers.
* @return string
*/
public function post( $url, $request = null, $options = [] )
Expand All @@ -117,9 +117,9 @@ public function post( $url, $request = null, $options = [] )

/**
* Send a PUT request
* @param null $url The URL to request
* @param null $request The query string
* @param array $options Any of the HTTP stream context options, e.g. extra headers.
* @param string $url The URL to request
* @param array|string $request The query string
* @param array $options Any of the HTTP stream context options, e.g. extra headers.
* @return string
*/
public function put( $url, $request = null, $options = [] )
Expand All @@ -129,9 +129,9 @@ public function put( $url, $request = null, $options = [] )

/**
* Send a DELETE request
* @param string $url
* @param array $request
* @param array $options
* @param string $url The URL to request
* @param array|string $request The query string
* @param array $options Any of the HTTP stream context options, e.g. extra headers.
* @return string
*/
public function delete( $url, $request = null, $options = [] )
Expand Down
143 changes: 96 additions & 47 deletions src/http/headers.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,22 @@ final class headers
* [ 'Location' => 'http://www.example.com', ... ]
* When multiple headers with the same name are present, all values will form an array, in the order in which
* they are present in the source.
* @param string $headers The headers string to parse.
* @param string|string[] $headers The headers string to parse.
* @return array
*/
public static function parse( $headers ) {
if ( !is_array($headers) && !$headers instanceof \ArrayObject ) {
$headers = array_filter(
array_map( "trim", explode( "\n", (string) $headers ) )
array_map( 'trim', explode( "\n", (string) $headers ) )
);
}
$result = [];
foreach( $headers as $header ) {
$temp = array_map('trim', explode(':', $header, 2) );
foreach( $headers as $key => $header ) {
if ( !is_array($header) ) {
$temp = array_map('trim', explode(':', $header, 2) );
} else {
$temp = $header;
}
if ( isset( $temp[1] ) ) {
if ( !isset($result[ $temp[0]]) ) {
// first entry for this header
Expand All @@ -48,8 +52,10 @@ public static function parse( $headers ) {
} else { // third or later header entry with same name
$result[ $temp[0] ][] = $temp[1];
}
} else { // e.g. HTTP1/1 200 OK
} else if (is_numeric($key)) {
$result[] = $temp[0];
} else { // e.g. HTTP1/1 200 OK
$result[$key] = $temp[0];
}
}
return $result;
Expand All @@ -67,59 +73,102 @@ private static function getLastHeader($headers) {
return $headers;
}

/**
* Return an array with values from a header like Cache-Control
* e.g. 'max-age=300,public,no-store'
* results in
* [ 'max-age' => '300', 'public' => 'public', 'no-store' => 'no-store' ]
* @param string $header
* @return array
*/
public static function parseHeader($header)
{
$header = (strpos($header, ':')!==false) ? explode(':', $header)[1] : $header;
$info = array_map('trim', explode(',', $header));
$header = [];
foreach ( $info as $entry ) {
$temp = array_map( 'trim', explode( '=', $entry ));
$header[ $temp[0] ] = (isset($temp[1]) ? $temp[1] : $temp[0] );
}
return $header;
}

/**
* Merge multiple occurances of a comma seperated header
* @param array $headers
* @return array
*/
public static function mergeHeaders( $headers )
{
$result = [];
if ( is_string($headers) ) {
$result = self::parseHeader( $headers );
} else foreach ( $headers as $header ) {
if (is_string($header)) {
$header = self::parseHeader($header);
}
$result = array_replace_recursive( $result, $header );
}
return $result;
}

private static function getCacheControlTime( $header, $private )
{
$result = null;
$dontcache = false;
foreach ( $header as $key => $value ) {
switch($key) {
case 'max-age':
case 's-maxage':
if ( isset($result) ) {
$result = min($result, (int) $value);
} else {
$result = (int) $value;
}
break;
case 'public':
break;
case 'private':
if ( !$private ) {
$dontcache = true;
}
break;
case 'no-cache':
case 'no-store':
$dontcache = true;
break;
case 'must-revalidate':
case 'proxy-revalidate':
$dontcache = true; // FIXME: should return more information than just the cache time instead
break;
default:
break;
}
}
if ( $dontcache ) {
$result = 0;
}
return $result;
}

/**
* Parse response headers to determine if and how long you may cache the response. Doesn't understand ETags.
* @param mixed $headers Headers string or array as returned by parse()
* @param string|string[] $headers Headers string or array as returned by parse()
* @param bool $private Whether to store a private cache or public cache image.
* @return int The number of seconds you may cache this result starting from now.
*/
public static function parseCacheTime( $headers, $private=true ) {
public static function parseCacheTime( $headers, $private=true )
{
$result = null;
if ( is_string($headers) || !isset($headers['Content-Type'] )) {
if ( is_string($headers) || ( !isset($headers['Cache-Control']) && !isset($headers['Expires']) ) ) {
$headers = \arc\http\headers::parse( $headers );
}
if ( isset( $headers['Cache-Control'] ) ) {
$header = self::getLastHeader($headers['Cache-Control']);
$info = array_map('trim', explode(',', $header));
$header = [];
foreach ( $info as $entry ) {
$temp = array_map( 'trim', explode( '=', $entry ));
$header[ $temp[0] ] = (isset($temp[1]) ? $temp[1] : $temp[0] );
}
$dontcache = false;
foreach ( $header as $key => $value ) {
switch($key) {
case 'max-age':
case 's-maxage':
if ( isset($result) ) {
$result = min($result, (int) $value);
} else {
$result = (int) $value;
}
break;
case 'public':
break;
case 'private':
if ( !$private ) {
$dontcache = true;
}
break;
case 'no-cache':
case 'no-store':
$dontcache = true;
break;
case 'must-revalidate':
case 'proxy-revalidate':
$dontcache = true; // FIXME: should return more information than just the cache time instead
break;
}
}
if ( $dontcache ) {
$result = 0;
}
$header = self::mergeHeaders( $headers['Cache-Control'] );
$result = self::getCacheControlTime( $header, $private );
}
if ( !isset($result) && isset( $headers['Expires'] ) ) {
$result = strtotime( self::getLastHeader($headers['Expires']) ) - time();
$result = strtotime( self::getLastHeader( $headers['Expires'] ) ) - time();
}
return (int) $result;
}
Expand Down
1 change: 1 addition & 0 deletions src/url/Url.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* $url->path = '/docs/search/';
* $url->query = 'a=1&a=2';
* echo $url; // => 'http://www.ariadne-cms.org/docs/search/?a=1&a=2'
* @property Query $query The query arguments
*/
class Url
{
Expand Down
43 changes: 43 additions & 0 deletions tests/http.Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function testClient()
$this->assertInstanceOf('\arc\http\Client',$res);
}


function testHeaders()
{
$headerString = <<< EOF
Expand All @@ -35,12 +36,54 @@ function testHeaders()
Server: AmazonS3
Vary: Accept-Encoding
X-Cache: HIT
X-Multiple: One
X-Multiple: Two
X-Multiple: Three
EOF;
$headers = \arc\http\headers::parse($headerString);
$this->assertEquals('AmazonS3', $headers['Server']);
$this->assertEquals('HTTP/1.1 200 OK', $headers[0]);
$this->assertEquals(['One','Two','Three'], $headers['X-Multiple']);

$cachetime = \arc\http\headers::parseCacheTime($headers);
$this->assertEquals(300, $cachetime);
}

function testCacheNoStore()
{
$headerString = <<< EOF
HTTP/1.1 200 OK
Cache-Control: no-store,public,max-age=300,s-maxage=900
EOF;
$headers = \arc\http\headers::parse($headerString);
$cachetime = \arc\http\headers::parseCacheTime($headers);
$this->assertEquals(0, $cachetime);
}

function testCachePrivate()
{
$headerString = <<< EOF
HTTP/1.1 200 OK
Cache-Control: private,max-age=300,s-maxage=900
EOF;
$headers = \arc\http\headers::parse($headerString);
$cachetime = \arc\http\headers::parseCacheTime($headers);
$this->assertEquals(300, $cachetime);

$cachetime = \arc\http\headers::parseCacheTime($headers, false);
$this->assertEquals(0, $cachetime);
}

function testCacheMultiple()
{
$headerString = <<< EOF
HTTP/1.1 200 OK
Cache-Control: public
Cache-Control: max-age=300,s-maxage=900
EOF;
$headers = \arc\http\headers::parse($headerString);
$cachetime = \arc\http\headers::parseCacheTime($headers);
$this->assertEquals(300, $cachetime);
}

}

0 comments on commit 954270d

Please sign in to comment.