[WIP] Begin implementing CachingDereferencer
This commit is contained in:
parent
e0037a84bd
commit
615fcb2e45
@ -15,21 +15,17 @@
|
|||||||
"test-debug": "XDEBUG_CONFIG='idekey=ap_session' php ./vendor/bin/phpunit test",
|
"test-debug": "XDEBUG_CONFIG='idekey=ap_session' php ./vendor/bin/phpunit test",
|
||||||
"docs": "phpdoc -d ./src -t ./docs"
|
"docs": "phpdoc -d ./src -t ./docs"
|
||||||
},
|
},
|
||||||
"repositories": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "https://git.friendi.ca/friendica/php-json-ld"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
"require": {
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
|
"cache/apc-adapter": "0.3.2",
|
||||||
|
"cache/apcu-adapter": "0.2.2",
|
||||||
|
"cache/filesystem-adapter": "0.3.3",
|
||||||
"doctrine/annotations": "1.2.7",
|
"doctrine/annotations": "1.2.7",
|
||||||
"doctrine/cache": "1.6.2",
|
"doctrine/cache": "1.6.2",
|
||||||
"doctrine/collections": "1.3.0",
|
"doctrine/collections": "1.3.0",
|
||||||
"doctrine/common": "2.6.2",
|
"doctrine/common": "2.6.2",
|
||||||
"doctrine/instantiator": "1.0.5",
|
"doctrine/instantiator": "1.0.5",
|
||||||
"doctrine/orm": "2.5.14",
|
"doctrine/orm": "2.5.14",
|
||||||
"friendica/json-ld": "^1.1",
|
|
||||||
"guzzlehttp/guzzle": "^6.3",
|
"guzzlehttp/guzzle": "^6.3",
|
||||||
"ml/json-ld": "1.1.0",
|
"ml/json-ld": "1.1.0",
|
||||||
"monolog/monolog": "^1.0",
|
"monolog/monolog": "^1.0",
|
||||||
@ -44,6 +40,7 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
|
"cache/array-adapter": "0.4.2",
|
||||||
"phpunit/dbunit": "^2.0",
|
"phpunit/dbunit": "^2.0",
|
||||||
"phpunit/phpunit": "^4.0"
|
"phpunit/phpunit": "^4.0"
|
||||||
},
|
},
|
||||||
|
160
src/JsonLd/Dereferencer/CachingDereferencer.php
Normal file
160
src/JsonLd/Dereferencer/CachingDereferencer.php
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace ActivityPub\JsonLd\Dereferencer;
|
||||||
|
|
||||||
|
use ActivityPub\Crypto\HttpSignatureService;
|
||||||
|
use ActivityPub\JsonLd\Exceptions\NodeNotFoundException;
|
||||||
|
use ActivityPub\Utils\Util;
|
||||||
|
use DateTime;
|
||||||
|
use DateTimeZone;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Psr7\Request;
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dereferencer that caches its results by id. Signs outgoing requests if provided with a keyId and a privateKey.
|
||||||
|
* Class CachingDereferencer
|
||||||
|
* @package ActivityPub\JsonLd\Dereferencer
|
||||||
|
*/
|
||||||
|
class CachingDereferencer implements DereferencerInterface
|
||||||
|
{
|
||||||
|
const DEFAULT_CACHE_TTL = 3600;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cache.
|
||||||
|
* @var CacheItemPoolInterface
|
||||||
|
*/
|
||||||
|
private $cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTTP client.
|
||||||
|
* @var Client
|
||||||
|
*/
|
||||||
|
private $httpClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var HttpSignatureService
|
||||||
|
*/
|
||||||
|
private $httpSignatureService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The keyId for signing requests, if there is one.
|
||||||
|
* @var null|string
|
||||||
|
*/
|
||||||
|
private $keyId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The private key for signing requests, if there is one.
|
||||||
|
* @var null|string
|
||||||
|
*/
|
||||||
|
private $privateKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CachingDereferencer constructor.
|
||||||
|
* @param CacheItemPoolInterface $cache
|
||||||
|
* @param Client $httpClient
|
||||||
|
* @param HttpSignatureService $httpSignatureService
|
||||||
|
* @param null|string $keyId
|
||||||
|
* @param null|string $privateKey
|
||||||
|
*/
|
||||||
|
public function __construct( LoggerInterface $logger,
|
||||||
|
CacheItemPoolInterface $cache,
|
||||||
|
Client $httpClient,
|
||||||
|
HttpSignatureService $httpSignatureService,
|
||||||
|
$keyId = null,
|
||||||
|
$privateKey = null )
|
||||||
|
{
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->cache = $cache;
|
||||||
|
$this->httpClient = $httpClient;
|
||||||
|
$this->httpSignatureService = $httpSignatureService;
|
||||||
|
$this->keyId = $keyId;
|
||||||
|
$this->privateKey = $privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setKeyId( $keyId )
|
||||||
|
{
|
||||||
|
$this->keyId = $keyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPrivateKey( $privateKey )
|
||||||
|
{
|
||||||
|
$this->privateKey = $privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $iri The IRI to dereference.
|
||||||
|
* @return stdClass|array The dereferenced node.
|
||||||
|
* @throws NodeNotFoundException If a node with the IRI could not be found.
|
||||||
|
*/
|
||||||
|
public function dereference( $iri )
|
||||||
|
{
|
||||||
|
$key = $this->makeCacheKey( $iri );
|
||||||
|
$cacheItem = $this->cache->getItem( $key );
|
||||||
|
if ( $cacheItem->isHit() ) {
|
||||||
|
return $cacheItem->get();
|
||||||
|
} else {
|
||||||
|
if ( Util::isLocalUri( $iri ) ) {
|
||||||
|
// TODO fetch object from persistence backend
|
||||||
|
}
|
||||||
|
$headers = array(
|
||||||
|
'Accept' => 'application/ld+json',
|
||||||
|
'Date' => $this->getNowRFC1123(),
|
||||||
|
);
|
||||||
|
$request = new Request( 'GET', $iri, $headers );
|
||||||
|
if ( $this->shouldSign() ) {
|
||||||
|
$signature = $this->httpSignatureService->sign( $request, $this->privateKey, $this->keyId );
|
||||||
|
$request = $request->withHeader( 'Signature', $signature );
|
||||||
|
}
|
||||||
|
$response = $this->httpClient->send( $request );
|
||||||
|
if ( $response->getStatusCode() >= 400 ) {
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
$this->logger->error(
|
||||||
|
"[ActivityPub-PHP] Received response with status $statusCode from $iri",
|
||||||
|
array( 'request' => $request, 'response' => $response )
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$body = json_decode( $response->getBody() );
|
||||||
|
if ( ! $body ) {
|
||||||
|
throw new NodeNotFoundException( $iri );
|
||||||
|
}
|
||||||
|
$cacheItem->set( $body );
|
||||||
|
$cacheItem->expiresAfter( self::DEFAULT_CACHE_TTL );
|
||||||
|
$this->cache->save( $cacheItem );
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a valid cache key for $id.
|
||||||
|
* @param string $id
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function makeCacheKey( $id )
|
||||||
|
{
|
||||||
|
return str_replace( array( '{', '}', '(', ')', '/', '\\', '@', ':' ), '_', $id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the dereferencer should sign outgoing requests.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function shouldSign()
|
||||||
|
{
|
||||||
|
return $this->keyId && $this->privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNowRFC1123()
|
||||||
|
{
|
||||||
|
$now = new DateTime( 'now', new DateTimeZone( 'GMT' ) );
|
||||||
|
return $now->format( 'D, d M Y H:i:s T' );
|
||||||
|
}
|
||||||
|
}
|
@ -441,6 +441,21 @@ class JsonLdNodeTest extends APTestCase
|
|||||||
'id' => 'https://example.org/articles/1',
|
'id' => 'https://example.org/articles/1',
|
||||||
'type' => 'Article',
|
'type' => 'Article',
|
||||||
)
|
)
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new stdClass(),
|
||||||
|
$this->asContext,
|
||||||
|
'to',
|
||||||
|
array(
|
||||||
|
(object) array(
|
||||||
|
'id' => 'https://example.org/sally',
|
||||||
|
'type' => 'Person',
|
||||||
|
),
|
||||||
|
(object) array(
|
||||||
|
'id' => 'https://example.org/bob',
|
||||||
|
'type' => 'Person',
|
||||||
|
)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -452,9 +467,11 @@ class JsonLdNodeTest extends APTestCase
|
|||||||
{
|
{
|
||||||
$parentNode = $this->makeJsonLdNode( $inputObj, $context );
|
$parentNode = $this->makeJsonLdNode( $inputObj, $context );
|
||||||
$parentNode->set( $newPropertyName, $newNodeValue );
|
$parentNode->set( $newPropertyName, $newNodeValue );
|
||||||
$childNode = $parentNode->get( $newPropertyName );
|
$childNodes = $parentNode->getMany( $newPropertyName );
|
||||||
$this->assertInstanceOf( JsonLdNode::class, $childNode );
|
foreach ( $childNodes as $childNode ) {
|
||||||
$this->assertEquals( $childNode->getBackReferences( $newPropertyName ), array( $parentNode ) );
|
$this->assertInstanceOf( JsonLdNode::class, $childNode );
|
||||||
|
$this->assertEquals( $childNode->getBackReferences( $newPropertyName ), array( $parentNode ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function makeJsonLdNode( $inputObj, $context, $nodeGraph = array() )
|
private function makeJsonLdNode( $inputObj, $context, $nodeGraph = array() )
|
||||||
|
Loading…
Reference in New Issue
Block a user