Implement http link header processing w/default document loader.

- Use default document loader when testing remote documents.
This commit is contained in:
Dave Longley 2013-09-15 15:54:54 -04:00
parent bf08dd1e3d
commit d59ffc65bf
2 changed files with 107 additions and 31 deletions

View File

@ -1,7 +1,7 @@
<?php <?php
/** /**
* PHP implementation of the JSON-LD API. * PHP implementation of the JSON-LD API.
* Version: 0.2.0 * Version: 0.2.1
* *
* @author Dave Longley * @author Dave Longley
* *
@ -233,7 +233,7 @@ $jsonld_cache->activeCtx = new ActiveContextCache();
/** Stores the default JSON-LD document loader. */ /** Stores the default JSON-LD document loader. */
global $jsonld_default_load_document; global $jsonld_default_load_document;
$jsonld_default_load_document = null; $jsonld_default_load_document = 'jsonld_default_document_loader';
/** /**
* Sets the default JSON-LD document loader. * Sets the default JSON-LD document loader.
@ -276,6 +276,8 @@ function jsonld_get_url($url) {
* @return stdClass the RemoteDocument object. * @return stdClass the RemoteDocument object.
*/ */
function jsonld_default_document_loader($url) { function jsonld_default_document_loader($url) {
$doc = (object)array(
'contextUrl' => null, 'document' => null, 'documentUrl' => $url);
$redirects = array(); $redirects = array();
$opts = array( $opts = array(
@ -290,30 +292,61 @@ function jsonld_default_document_loader($url) {
'header' => 'header' =>
"Accept: application/ld+json\r\n" . "Accept: application/ld+json\r\n" .
"User-Agent: JSON-LD PHP Client/1.0\r\n")); "User-Agent: JSON-LD PHP Client/1.0\r\n"));
$stream = stream_context_create($opts); $context = stream_context_create($opts);
stream_context_set_params($stream, array('notification' => $content_type = null;
function($notification_code, $severity, $message) use (&$redirects) { stream_context_set_params($context, array('notification' =>
function($notification_code, $severity, $message) use (
&$redirects, &$content_type) {
switch($notification_code) { switch($notification_code) {
case STREAM_NOTIFY_REDIRECTED: case STREAM_NOTIFY_REDIRECTED:
$redirects[] = $message; $redirects[] = $message;
break; break;
case STREAM_NOTIFY_MIME_TYPE_IS:
$content_type = $message;
break;
}; };
})); }));
$result = @file_get_contents($url, false, $stream); $result = @file_get_contents($url, false, $context);
if($result === false) { if($result === false) {
throw new JsonLdException( throw new JsonLdException(
'Could not retrieve a JSON-LD document from the URL.', 'Could not retrieve a JSON-LD document from the URL.',
'jsonld.LoadDocumentError', 'loading document failed'); 'jsonld.LoadDocumentError', 'loading document failed');
} }
$link_header = array();
foreach($http_response_header as $header) {
if(strpos($header, 'link') === 0) {
$value = explode(': ', $header);
if(count($value) > 1) {
$link_header[] = $value[1];
}
}
}
$link_header = jsonld_parse_link_header(join(',', $link_header));
if(isset($link_header['http://www.w3.org/ns/json-ld#context'])) {
$link_header = $link_header['http://www.w3.org/ns/json-ld#context'];
}
else {
$link_header = null;
}
if($link_header && $content_type !== 'application/ld+json') {
// only 1 related link header permitted
if(is_array($link_header)) {
throw new JsonLdException(
'URL could not be dereferenced, it has more than one ' .
'associated HTTP Link Header.', 'jsonld.LoadDocumentError',
'multiple context link headers', array('url' => $url));
}
$doc->{'contextUrl'} = $link_header->target;
}
// update document url based on redirects
$redirs = count($redirects); $redirs = count($redirects);
if($redirs > 0) { if($redirs > 0) {
$url = $redirects[$redirs - 1]; $url = $redirects[$redirs - 1];
} }
// return RemoteDocument $doc->document = $result;
return (object)array( $doc->documentUrl = $url;
'contextUrl' => null, return $doc;
'document' => $result,
'documentUrl' => $url);
} }
/** /**
@ -330,6 +363,8 @@ function jsonld_default_secure_document_loader($url) {
'jsonld.LoadDocumentError', 'loading document failed'); 'jsonld.LoadDocumentError', 'loading document failed');
} }
$doc = (object)array(
'contextUrl' => null, 'document' => null, 'documentUrl' => $url);
$redirects = array(); $redirects = array();
// default JSON-LD https GET implementation // default JSON-LD https GET implementation
@ -340,21 +375,54 @@ function jsonld_default_secure_document_loader($url) {
'header' => 'header' =>
"Accept: application/ld+json\r\n" . "Accept: application/ld+json\r\n" .
"User-Agent: JSON-LD PHP Client/1.0\r\n")); "User-Agent: JSON-LD PHP Client/1.0\r\n"));
$stream = stream_context_create($opts); $context = stream_context_create($opts);
stream_context_set_params($stream, array('notification' => $content_type = null;
function($notification_code, $severity, $message) use (&$redirects) { stream_context_set_params($context, array('notification' =>
function($notification_code, $severity, $message) use (
&$redirects, &$content_type) {
switch($notification_code) { switch($notification_code) {
case STREAM_NOTIFY_REDIRECTED: case STREAM_NOTIFY_REDIRECTED:
$redirects[] = $message; $redirects[] = $message;
break; break;
case STREAM_NOTIFY_MIME_TYPE_IS:
$content_type = $message;
break;
}; };
})); }));
$result = @file_get_contents($url, false, $stream); $result = @file_get_contents($url, false, $context);
if($result === false) { if($result === false) {
throw new JsonLdException( throw new JsonLdException(
'Could not retrieve a JSON-LD document from the URL.', 'Could not retrieve a JSON-LD document from the URL.',
'jsonld.LoadDocumentError', 'loading document failed'); 'jsonld.LoadDocumentError', 'loading document failed');
} }
$link_header = array();
foreach($http_response_header as $header) {
if(strpos($header, 'link') === 0) {
$value = explode(': ', $header);
if(count($value) > 1) {
$link_header[] = $value[1];
}
}
}
$link_header = jsonld_parse_link_header(join(',', $link_header));
if(isset($link_header['http://www.w3.org/ns/json-ld#context'])) {
$link_header = $link_header['http://www.w3.org/ns/json-ld#context'];
}
else {
$link_header = null;
}
if($link_header && $content_type !== 'application/ld+json') {
// only 1 related link header permitted
if(is_array($link_header)) {
throw new JsonLdException(
'URL could not be dereferenced, it has more than one ' .
'associated HTTP Link Header.', 'jsonld.LoadDocumentError',
'multiple context link headers', array('url' => $url));
}
$doc->{'contextUrl'} = $link_header->target;
}
// update document url based on redirects
foreach($redirects as $redirect) { foreach($redirects as $redirect) {
if(strpos($redirect, 'https') !== 0) { if(strpos($redirect, 'https') !== 0) {
throw new JsonLdException( throw new JsonLdException(
@ -363,11 +431,9 @@ function jsonld_default_secure_document_loader($url) {
} }
$url = $redirect; $url = $redirect;
} }
// return RemoteDocument $doc->document = $result;
return (object)array( $doc->documentUrl = $url;
'contextUrl' => null, return $doc;
'document' => $result,
'documentUrl' => $url);
} }
/** Registered global RDF dataset parsers hashed by content-type. */ /** Registered global RDF dataset parsers hashed by content-type. */
@ -5514,7 +5580,11 @@ class JsonLdProcessor {
*/ */
protected static function _parse_json($json) { protected static function _parse_json($json) {
$rval = json_decode($json); $rval = json_decode($json);
switch(json_last_error()) { $error = json_last_error();
if($error === JSON_ERROR_NONE && $rval === null) {
$error = JSON_ERROR_SYNTAX;
}
switch($error) {
case JSON_ERROR_NONE: case JSON_ERROR_NONE:
break; break;
case JSON_ERROR_DEPTH: case JSON_ERROR_DEPTH:
@ -5561,6 +5631,9 @@ class JsonLdException extends Exception {
} }
public function __toString() { public function __toString() {
$rval = __CLASS__ . ": [{$this->type}]: {$this->message}\n"; $rval = __CLASS__ . ": [{$this->type}]: {$this->message}\n";
if($this->code) {
$rval .= 'Code: ' . $this->code . "\n";
}
if($this->details) { if($this->details) {
$rval .= 'Details: ' . print_r($this->details, true) . "\n"; $rval .= 'Details: ' . print_r($this->details, true) . "\n";
} }

View File

@ -286,9 +286,7 @@ class JsonLdTest {
} }
public function createDocumentLoader() { public function createDocumentLoader() {
global $jsonld_default_load_document;
$base = 'http://json-ld.org/test-suite'; $base = 'http://json-ld.org/test-suite';
$loader = $jsonld_default_load_document;
$test = $this; $test = $this;
$load_locally = function($url) use ($test, $base) { $load_locally = function($url) use ($test, $base) {
@ -314,8 +312,13 @@ class JsonLdTest {
if(is_array($link_header)) { if(is_array($link_header)) {
$link_header = join(',', $link_header); $link_header = join(',', $link_header);
} }
$link_header = jsonld_parse_link_header( $link_header = jsonld_parse_link_header($link_header);
$link_header)['http://www.w3.org/ns/json-ld#context'] ?: null; if(isset($link_header['http://www.w3.org/ns/json-ld#context'])) {
$link_header = $link_header['http://www.w3.org/ns/json-ld#context'];
}
else {
$link_header = null;
}
if($link_header && $content_type !== 'application/ld+json') { if($link_header && $content_type !== 'application/ld+json') {
if(is_array($link_header)) { if(is_array($link_header)) {
throw new Exception('multiple context link headers'); throw new Exception('multiple context link headers');
@ -336,12 +339,12 @@ class JsonLdTest {
return $doc; return $doc;
}; };
$local_loader = function($url) use ($loader, $load_locally) { $local_loader = function($url) use ($test, $base, $load_locally) {
// always load remote-doc and non-base tests remotely // always load remote-doc and non-base tests remotely
/*if(strpos($url, $base) !== 0 || if(strpos($url, $base) !== 0 ||
$test->manifest->data['name'] === 'Remote document') { $test->manifest->data->name === 'Remote document') {
return call_user_func($loader, $url); return call_user_func('jsonld_default_document_loader', $url);
}*/ }
// attempt to load locally // attempt to load locally
return call_user_func($load_locally, $url); return call_user_func($load_locally, $url);