[WIP] Begin implementing CachingDereferencer

This commit is contained in:
Jeremy Dormitzer 2019-04-28 16:08:22 -04:00
parent e0037a84bd
commit 615fcb2e45
3 changed files with 184 additions and 10 deletions

View File

@ -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"
}, },

View 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' );
}
}

View File

@ -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() )