Skip to content

Commit 2a00a4e

Browse files
authored
Merge pull request #1 from pixelfusion/feat/presigned-protected
Performance and behavioural changes to support autoscaling a bit nicer
2 parents ae69d10 + 242dc56 commit 2a00a4e

File tree

9 files changed

+267
-102
lines changed

9 files changed

+267
-102
lines changed

_config/assetadmin.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ SilverStripe\AssetAdmin\Model\ThumbnailGenerator:
88
thumbnail_links:
99
protected: url
1010
public: url
11+
12+
# Asset-admin adds a `vid` parameter to preview URLs by default.
13+
# This confuses the AWS signature for protected assets, so we disable it.
14+
SilverStripe\AssetAdmin\Controller\AssetAdmin:
15+
bust_cache: false

_config/assets.yml

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,15 @@ After:
66
- '#assetsflysystem'
77
---
88
SilverStripe\Core\Injector\Injector:
9-
League\Flysystem\Adapter\Local:
10-
class: League\Flysystem\Adapter\Local
11-
constructor:
12-
root: '`TEMP_PATH`'
139
FullscreenInteractive\SilverStripe\AzureStorage\Adapter\PublicAdapter:
1410
constructor:
1511
connectionUrl: '`AZURE_CONNECTION_URL`'
1612
containerName: '`AZURE_CONTAINER_NAME`'
1713
assetDomain: '`AZURE_PUBLIC_BLOB_DOMAIN`'
18-
League\Flysystem\Cached\Storage\Memory.public:
19-
class: League\Flysystem\Cached\Storage\Memory
2014
League\Flysystem\Cached\Storage\Adapter.public:
21-
class: League\Flysystem\Cached\Storage\Adapter
15+
class: FullscreenInteractive\SilverStripe\AzureStorage\Adapter\DBCache
2216
constructor:
23-
adapter: '%$League\Flysystem\Adapter\Local'
24-
file: 'azuremetadata/public'
17+
key: 'azuremetadata/public'
2518
expire: 259200
2619
SilverStripe\Assets\Flysystem\PublicAdapter:
2720
class: FullscreenInteractive\SilverStripe\AzureStorage\Adapter\PublicCachedAdapter
@@ -33,23 +26,12 @@ SilverStripe\Core\Injector\Injector:
3326
connectionUrl: '`AZURE_CONNECTION_URL`'
3427
containerName: '`AZURE_PROTECTED_CONTAINER_NAME`'
3528
League\Flysystem\Cached\Storage\Adapter.protected:
36-
class: League\Flysystem\Cached\Storage\Adapter
37-
constructor:
38-
adapter: '%$League\Flysystem\Adapter\Local'
39-
file: 'azuremetadata/protected'
40-
expire: 259200
29+
class: FullscreenInteractive\SilverStripe\AzureStorage\Adapter\DBCache
30+
constructor:
31+
key: 'azuremetadata/protected'
32+
expire: 259200
4133
SilverStripe\Assets\Flysystem\ProtectedAdapter:
4234
class: FullscreenInteractive\SilverStripe\AzureStorage\Adapter\ProtectedCachedAdapter
4335
constructor:
4436
adapter: '%$FullscreenInteractive\SilverStripe\AzureStorage\Adapter\ProtectedAdapter'
4537
cache: '%$League\Flysystem\Cached\Storage\Adapter.protected'
46-
---
47-
Name: silverstripe-azure-assetscore
48-
Only:
49-
envvarset: AZURE_CONNECTION_URL
50-
After:
51-
- '#assetscore'
52-
---
53-
SilverStripe\Core\Injector\Injector:
54-
SilverStripe\Assets\Storage\AssetStore:
55-
class: SilverStripe\Assets\Flysystem\FlysystemAssetStore

_config/tinymce.yml

Lines changed: 0 additions & 18 deletions
This file was deleted.

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
}
1616
],
1717
"require": {
18-
"silverstripe/framework": "^4",
18+
"silverstripe/framework": "^4.8",
1919
"silverstripe/vendor-plugin": "^1.0",
2020
"league/flysystem-azure-blob-storage": "^1.0",
2121
"league/flysystem-cached-adapter": "^1.0"
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace FullscreenInteractive\SilverStripe\AzureStorage\Adapter;
4+
5+
use FullscreenInteractive\SilverStripe\AzureStorage\Service\BlobService;
6+
use InvalidArgumentException;
7+
use League\Flysystem\AzureBlobStorage\AzureBlobStorageAdapter as BaseAdapter;
8+
use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings;
9+
10+
abstract class AzureBlobStorageAdapter extends BaseAdapter
11+
{
12+
/**
13+
* @var string
14+
*/
15+
protected $assetDomain = null;
16+
17+
/**
18+
* @var StorageServiceSettings
19+
*/
20+
protected $container = null;
21+
22+
/**
23+
* @var StorageServiceSettings|null
24+
*/
25+
protected $settings = null;
26+
27+
public function __construct($connectionUrl = '', $containerName = '', $assetDomain = '')
28+
{
29+
if (!$connectionUrl) {
30+
throw new InvalidArgumentException("AZURE_CONNECTION_URL environment variable not set");
31+
}
32+
33+
if (!$containerName) {
34+
throw new InvalidArgumentException("AZURE_CONTAINER_NAME environment variable not set");
35+
}
36+
37+
// Store settings
38+
$this->container = $containerName;
39+
$this->settings = StorageServiceSettings::createFromConnectionString($connectionUrl);
40+
41+
// Generate client
42+
$client = BlobService::clientForConnection($connectionUrl);
43+
44+
// Determine url
45+
if ($assetDomain) {
46+
$this->assetDomain = $assetDomain;
47+
} else {
48+
$this->assetDomain = $client
49+
->getPsrPrimaryUri()
50+
->withPath($containerName)
51+
->__toString();
52+
}
53+
54+
parent::__construct($client, $containerName);
55+
}
56+
}

src/Adapter/DBCache.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
namespace FullscreenInteractive\SilverStripe\AzureStorage\Adapter;
4+
5+
use Exception;
6+
use FullscreenInteractive\SilverStripe\AzureStorage\Model\BlobCache;
7+
use League\Flysystem\Cached\Storage\AbstractCache;
8+
9+
class DBCache extends AbstractCache
10+
{
11+
/**
12+
* Cache key
13+
*
14+
* @var string|null
15+
*/
16+
protected $key = null;
17+
18+
/**
19+
* @var int|null seconds until cache expiration
20+
*/
21+
protected $expire = null;
22+
23+
protected $ready = false;
24+
25+
/**
26+
* Constructor.
27+
*
28+
* @param string $key storage key
29+
* @param int|null $expire
30+
*/
31+
public function __construct(string $key = 'flysystem', int $expire = null)
32+
{
33+
$this->key = $key;
34+
$this->expire = $expire;
35+
$this->ready = BlobCache::ready();
36+
}
37+
38+
/**
39+
* @throws Exception
40+
*/
41+
public function load()
42+
{
43+
if (!$this->ready) {
44+
return;
45+
}
46+
47+
$cache = BlobCache::instance($this->key, $this->expire);
48+
$contents = $cache->Contents;
49+
50+
if ($contents) {
51+
$this->setFromStorage($contents);
52+
}
53+
}
54+
55+
/**
56+
* @throws Exception
57+
*/
58+
public function save()
59+
{
60+
if (!$this->ready) {
61+
return;
62+
}
63+
64+
$contents = $this->getForStorage();
65+
$blobCache = BlobCache::instance($this->key, $this->expire);
66+
$blobCache->Contents = $contents;
67+
68+
// Force write to ensure modified date is refreshed
69+
$blobCache->write(false, false, true);
70+
}
71+
}

src/Adapter/ProtectedAdapter.php

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
namespace FullscreenInteractive\SilverStripe\AzureStorage\Adapter;
44

5-
use FullscreenInteractive\SilverStripe\AzureStorage\Service\BlobService;
6-
use InvalidArgumentException;
7-
use League\Flysystem\AzureBlobStorage\AzureBlobStorageAdapter;
5+
use DateTime;
6+
use MicrosoftAzure\Storage\Blob\BlobSharedAccessSignatureHelper;
7+
use MicrosoftAzure\Storage\Common\Internal\Resources;
88
use SilverStripe\Assets\Flysystem\ProtectedAdapter as SilverstripeProtectedAdapter;
99
use SilverStripe\Control\Controller;
1010

@@ -15,22 +15,7 @@ class ProtectedAdapter extends AzureBlobStorageAdapter implements SilverstripePr
1515
*
1616
* @var int|string
1717
*/
18-
protected $expiry = 300;
19-
20-
public function __construct($connectionUrl = '', $containerName = '')
21-
{
22-
if (!$connectionUrl) {
23-
throw new InvalidArgumentException("AZURE_CONNECTION_URL environment variable not set");
24-
}
25-
26-
if (!$containerName) {
27-
throw new InvalidArgumentException("AZURE_PROTECTED_CONTAINER_NAME environment variable not set");
28-
}
29-
30-
$client = BlobService::clientForConnection($connectionUrl);
31-
32-
parent::__construct($client, $containerName);
33-
}
18+
protected $expiry = 3600;
3419

3520
/**
3621
* @return int|string
@@ -47,7 +32,7 @@ public function getExpiry()
4732
* @param int|string $expiry
4833
* @return $this
4934
*/
50-
public function setExpiry($expiry)
35+
public function setExpiry($expiry): self
5136
{
5237
$this->expiry = $expiry;
5338
return $this;
@@ -61,18 +46,50 @@ public function setExpiry($expiry)
6146
public function getProtectedUrl($path)
6247
{
6348
if ($meta = $this->getMetadata($path)) {
64-
return Controller::join_links(ASSETS_DIR, $meta['path']);
49+
$token = '?' . $this->presignToken($meta['path']);
50+
return Controller::join_links($this->assetDomain, $meta['path'], $token);
6551
}
6652

6753
return '';
6854
}
6955

70-
public function getVisibility($path)
56+
public function getVisibility($path): array
7157
{
7258
// Save an API call
7359
return [
74-
'path' => $path,
60+
'path' => $path,
7561
'visibility' => self::VISIBILITY_PRIVATE
7662
];
7763
}
64+
65+
/**
66+
* Build a presigned token with an expiry
67+
*
68+
* @param string $path
69+
* @return string
70+
*/
71+
protected function presignToken(string $path): string
72+
{
73+
$sasHelper = new BlobSharedAccessSignatureHelper(
74+
$this->settings->getName(),
75+
$this->settings->getKey()
76+
);
77+
78+
// Get expiry string
79+
$expiry = $this->getExpiry();
80+
if (is_int($expiry)) {
81+
$expiry = "+{$expiry} seconds";
82+
}
83+
$validFrom = new DateTime();
84+
$expiry = (new DateTime())->modify($expiry);
85+
86+
// Build token
87+
return $sasHelper->generateBlobServiceSharedAccessSignatureToken(
88+
Resources::RESOURCE_TYPE_BLOB,
89+
$this->container . '/' . $path,
90+
'r',
91+
$expiry,
92+
$validFrom
93+
);
94+
}
7895
}

src/Adapter/PublicAdapter.php

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,22 @@
22

33
namespace FullscreenInteractive\SilverStripe\AzureStorage\Adapter;
44

5-
use FullscreenInteractive\SilverStripe\AzureStorage\Service\BlobService;
6-
use InvalidArgumentException;
7-
use League\Flysystem\AzureBlobStorage\AzureBlobStorageAdapter;
85
use SilverStripe\Assets\Flysystem\PublicAdapter as SilverstripePublicAdapter;
96
use SilverStripe\Control\Controller;
107

118
class PublicAdapter extends AzureBlobStorageAdapter implements SilverstripePublicAdapter
129
{
13-
private $assetDomain;
14-
15-
public function __construct($connectionUrl = '', $containerName = '', $assetDomain = '')
16-
{
17-
if (!$connectionUrl) {
18-
throw new InvalidArgumentException("AZURE_CONNECTION_URL environment variable not set");
19-
}
20-
21-
if (!$containerName) {
22-
throw new InvalidArgumentException("AZURE_CONTAINER_NAME environment variable not set");
23-
}
24-
25-
$client = BlobService::clientForConnection($connectionUrl);
26-
27-
if ($assetDomain && !empty($assetDomain)) {
28-
$this->assetDomain = $assetDomain;
29-
} else {
30-
$this->assetDomain = (string) BlobService::getClient()
31-
->getPsrPrimaryUri()
32-
->withPath($containerName);
33-
}
34-
35-
parent::__construct($client, $containerName);
36-
}
37-
3810
/**
3911
* @param string $path
4012
*
4113
* @return string
4214
*/
43-
public function getPublicUrl($path)
15+
public function getPublicUrl($path): string
4416
{
45-
$parts = explode('/', $path);
46-
47-
if (isset($parts[0]) && $parts[0] === ASSETS_DIR) {
48-
array_shift($parts);
17+
if ($meta = $this->getMetadata($path)) {
18+
return Controller::join_links($this->assetDomain, $meta['path']);
4919
}
5020

51-
$path = implode('/', $parts);
52-
53-
return Controller::join_links($this->assetDomain, $path);
21+
return '';
5422
}
5523
}

0 commit comments

Comments
 (0)