Skip to content

Commit

Permalink
Merge pull request #70 from silverstripe/pr/performance
Browse files Browse the repository at this point in the history
feat: implement CacheItemsTrait for better performance handling
  • Loading branch information
wilr authored Jun 13, 2024
2 parents 6cea6b0 + 6cd20d6 commit 11678b2
Show file tree
Hide file tree
Showing 14 changed files with 982 additions and 217 deletions.
158 changes: 90 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,68 @@

SilverStripe module to store assets in S3 rather than on the local filesystem.

Note: This module does not currently implement any kind of bucket policy for
protected assets. It is up to you to implement this yourself using AWS bucket
policy.
```sh
composer require silverstripe/s3
```

> [!WARNING]
> This module does not currently implement any kind of bucket policy for
> protected assets. It is up to you to implement this yourself using AWS bucket
> policy.
> [!CAUTION]
> This replaces the built-in local asset store that comes with SilverStripe
> with one based on S3. Any files that had previously been uploaded to an
> existing asset store will be unavailable (though they won't be lost - just
> run `composer remove silverstripe/s3` to remove the module and restore
> access).
## Environment setup

The module requires a few environment variables to be set. Full details can be
seen in the `SilverStripeS3AdapterTrait` trait. These are mandatory.
The module requires a few environment variables to be set.

- `AWS_REGION`: The AWS region your S3 bucket is hosted in (e.g. `eu-central-1`)
- `AWS_BUCKET_NAME`: The name of the S3 bucket to store assets in.
- `AWS_REGION`: The AWS region your S3 bucket is hosted in (e.g. `eu-central-1`)
- `AWS_BUCKET_NAME`: The name of the S3 bucket to store assets in.

If running outside of an EC2 instance it will be necessary to specify an API key
and secret.

- `AWS_ACCESS_KEY_ID`: Your AWS access key that has access to the bucket you
want to access
- `AWS_SECRET_ACCESS_KEY`: Your AWS secret corresponding to the access key
- `AWS_ACCESS_KEY_ID`: Your AWS access key that has access to the bucket you
want to access
- `AWS_SECRET_ACCESS_KEY`: Your AWS secret corresponding to the access key

**Example YML Config when running outside of EC2:**

```yml
---
Only:
envvarset: AWS_BUCKET_NAME
envvarset: AWS_BUCKET_NAME
After:
- "#assetsflysystem"
- "#assetsflysystem"
---
SilverStripe\Core\Injector\Injector:
Aws\S3\S3Client:
constructor:
configuration:
region: "`AWS_REGION`"
version: latest
credentials:
key: "`AWS_ACCESS_KEY_ID`"
secret: "`AWS_SECRET_ACCESS_KEY`"
Aws\S3\S3Client:
constructor:
configuration:
region: "`AWS_REGION`"
version: latest
credentials:
key: "`AWS_ACCESS_KEY_ID`"
secret: "`AWS_SECRET_ACCESS_KEY`"
```
## (Optional) CDN Implementation
If you're serving assets from S3, it's recommended that you utilise CloudFront.
If you're serving assets from S3, it's recommended that you utilize CloudFront.
This improves performance and security over exposing from S3 directly.
Once you've set up your CloudFront distribution, ensure that assets are
reachable within the `assets` directory of the cdn (for example;
https://cdn.example.com/assets/Uploads/file.jpg) and set the following
environment variable:

- `AWS_PUBLIC_CDN_PREFIX`: Your CloudFront distribution domain that has access
to the bucket you want to access
- `AWS_PUBLIC_CDN_PREFIX`: Your CloudFront distribution domain that has access
to the bucket you want to access

For example, adding this to your `.env`:

Expand All @@ -66,47 +77,33 @@ to something like:

`https://cdn.example.com/assets/Uploads/file.jpg`

You can override the default `/assets/` path by redeclaring the PublicCDNAdapter constructor, with the paramater for the `cdnAssetsDir` set to a string of your folder name:
You can override the default `/assets/` path by declaring the PublicCDNAdapter
constructor, with the parameter for the `cdnAssetsDir` set to a string of your
folder name:

```yml
---
Name: silverstripes3-cdn
Only:
envvarset: AWS_PUBLIC_CDN_PREFIX
envvarset: AWS_PUBLIC_CDN_PREFIX
After:
- "#assetsflysystem"
- "#silverstripes3-flysystem"
- "#assetsflysystem"
- "#silverstripes3-flysystem"
---
SilverStripe\Core\Injector\Injector:
SilverStripe\S3\Adapter\PublicAdapter:
class: SilverStripe\S3\Adapter\PublicCDNAdapter
constructor:
s3Client: '%$Aws\S3\S3Client'
bucket: '`AWS_BUCKET_NAME`'
prefix: '`AWS_PUBLIC_BUCKET_PREFIX`'
visibility: null
mimeTypeDetector: null
cdnPrefix: '`AWS_PUBLIC_CDN_PREFIX`'
options: []
cdnAssetsDir: "cms-assets" # example of a custom assets folder name, which will produce https://cdn.example.com/cms-assets/Uploads/file.jpg
SilverStripe\S3\Adapter\PublicAdapter:
class: SilverStripe\S3\Adapter\PublicCDNAdapter
constructor:
s3Client: '%$Aws\S3\S3Client'
bucket: "`AWS_BUCKET_NAME`"
prefix: "`AWS_PUBLIC_BUCKET_PREFIX`"
visibility: null
mimeTypeDetector: null
cdnPrefix: "`AWS_PUBLIC_CDN_PREFIX`"
options: []
cdnAssetsDir: "cms-assets" # example of a custom assets folder name
```
## Installation
- Define the environment variables listed above.
- [Install Composer from
https://getcomposer.org](https://getcomposer.org/download/)
- Run `composer require silverstripe/s3`

This will install the most recent applicable version of the module given your
other Composer requirements.

**Note:** This currently immediately replaces the built-in local asset store
that comes with SilverStripe with one based on S3. Any files that had previously
been uploaded to an existing asset store will be unavailable (though they won't
be lost - just run `composer remove silverstripe/s3` to remove the module and
restore access).

## Configuration
Assets are classed as either 'public' or 'protected' by SilverStripe. Public
Expand All @@ -132,22 +129,22 @@ Make sure you replace `<bucket-name>` below with the appropriate values.

```json
{
"Policy": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::<bucket-name>/public/*"
}
]
}
"Policy": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::<bucket-name>/public/*"
}
]
}
}
```

If you are utilising a CloudFront distribution for your public assets, you will
If you are utilizing a CloudFront distribution for your public assets, you will
have the option of securing your S3 bucket against all public access while still
allowing access to your `public` files via your CloudFront distribution and
access to your `protected` files via signed URLs.
Expand All @@ -158,6 +155,31 @@ Read [Setting up a local sandbox for developing the Silverstripe S3
module](doc/en/setting-local-dev-environment.md) if you wish to do some local
development.

### Performance

This module comes with a basic in-memory cache for calls to S3. It is highly
recommended to add an additional layer of caching to achieve the best results.

See https://docs.silverstripe.org/en/5/developer_guides/performance/caching/ for
more information.

```yaml
Name: silverstripes3-flysystem-memcached
After:
- "#silverstripes3-flysystem"
---
SilverStripe\Core\Injector\Injector:
MemcachedClient:
class: "Memcached"
calls:
- [addServer, ["localhost", 11211]]
MemcachedCacheFactory:
class: 'SilverStripe\Core\Cache\MemcachedCacheFactory'
constructor:
client: "%$MemcachedClient"
SilverStripe\Core\Cache\CacheFactory: "%$MemcachedCacheFactory"
```

## Uninstalling

- Run `composer remove silverstripe/s3` to remove the module.
Run `composer remove silverstripe/s3` to remove the module.
73 changes: 22 additions & 51 deletions _config/assets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,74 +3,46 @@ Name: silverstripes3-flysystem
Only:
envvarset: AWS_BUCKET_NAME
After:
- '#assetsflysystem'
- "#assetsflysystem"
---
SilverStripe\Core\Injector\Injector:
Aws\S3\S3Client:
constructor:
configuration:
region: '`AWS_REGION`'
region: "`AWS_REGION`"
version: latest
Psr\SimpleCache\CacheInterface.s3Cache:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: "s3Cache"
League\Flysystem\Adapter\Local:
class: League\Flysystem\Adapter\Local
constructor:
root: '`TEMP_PATH`'

SilverStripe\S3\Adapter\PublicAdapter:
constructor:
s3Client: '%$Aws\S3\S3Client'
bucket: '`AWS_BUCKET_NAME`'
prefix: '`AWS_PUBLIC_BUCKET_PREFIX`'
Symfony\Component\Cache\Adapter\FilesystemAdapter.public:
class: Symfony\Component\Cache\Adapter\FilesystemAdapter
constructor:
namespace: 'flysystem-public'
expire: 2592000
root: "`TEMP_PATH`"
SilverStripe\Assets\Flysystem\PublicAdapter:
class: SilverStripe\S3\Adapter\PublicCachedAdapter
constructor:
adapter: '%$SilverStripe\S3\Adapter\PublicAdapter'
cache: '%$Symfony\Component\Cache\Adapter\FilesystemAdapter.public'
League\Flysystem\Filesystem.public:
class: SilverStripe\Assets\Flysystem\Filesystem
constructor:
FilesystemAdapter: '%$SilverStripe\Assets\Flysystem\PublicAdapter'
FilesystemConfig:
visibility: public

SilverStripe\S3\Adapter\ProtectedAdapter:
class: SilverStripe\S3\Adapter\PublicAdapter
constructor:
s3Client: '%$Aws\S3\S3Client'
bucket: '`AWS_BUCKET_NAME`'
prefix: '`AWS_PROTECTED_BUCKET_PREFIX`'
Symfony\Component\Cache\Adapter\FilesystemAdapter.protected:
class: Symfony\Component\Cache\Adapter\FilesystemAdapter
constructor:
namespace: 'flysystem-protected'
expire: 2592000
bucket: "`AWS_BUCKET_NAME`"
prefix: "`AWS_PUBLIC_BUCKET_PREFIX`"
SilverStripe\Assets\Flysystem\ProtectedAdapter:
class: SilverStripe\S3\Adapter\ProtectedCachedAdapter
class: SilverStripe\S3\Adapter\ProtectedAdapter
constructor:
adapter: '%$SilverStripe\S3\Adapter\ProtectedAdapter'
cache: '%$Symfony\Component\Cache\Adapter\FilesystemAdapter.protected'
League\Flysystem\Filesystem.protected:
class: SilverStripe\Assets\Flysystem\Filesystem
constructor:
FilesystemAdapter: '%$SilverStripe\Assets\Flysystem\ProtectedAdapter'
FilesystemConfig:
visibility: private
s3Client: '%$Aws\S3\S3Client'
bucket: "`AWS_BUCKET_NAME`"
prefix: "`AWS_PROTECTED_BUCKET_PREFIX`"
---
Name: silverstripes3-assetscore
Only:
envvarset: AWS_BUCKET_NAME
After:
- '#assetsflysystem'
- '#assetscore'
- "#assetsflysystem"
- "#assetscore"
---
SilverStripe\Core\Injector\Injector:
# Define our SS asset backend
SilverStripe\Assets\Storage\AssetStore:
class: SilverStripe\Assets\Flysystem\FlysystemAssetStore
class: SilverStripe\S3\S3FlysystemAssetStore
properties:
PublicFilesystem: '%$League\Flysystem\Filesystem.public'
ProtectedFilesystem: '%$League\Flysystem\Filesystem.protected'
Expand All @@ -81,22 +53,21 @@ SilverStripe\Core\Injector\Injector:
SilverStripe\View\Requirements_Backend:
properties:
AssetHandler: '%$SilverStripe\Assets\Storage\GeneratedAssetHandler'

---
Name: silverstripes3-cdn
Only:
envvarset: AWS_PUBLIC_CDN_PREFIX
After:
- '#assetsflysystem'
- '#silverstripes3-flysystem'
- "#assetsflysystem"
- "#silverstripes3-flysystem"
---
SilverStripe\Core\Injector\Injector:
SilverStripe\S3\Adapter\PublicAdapter:
class: SilverStripe\S3\Adapter\PublicCDNAdapter
constructor:
s3Client: '%$Aws\S3\S3Client'
bucket: '`AWS_BUCKET_NAME`'
prefix: '`AWS_PUBLIC_BUCKET_PREFIX`'
bucket: "`AWS_BUCKET_NAME`"
prefix: "`AWS_PUBLIC_BUCKET_PREFIX`"
visibility: null
mimeTypeDetector: null
cdnPrefix: '`AWS_PUBLIC_CDN_PREFIX`'
cdnPrefix: "`AWS_PUBLIC_CDN_PREFIX`"
5 changes: 2 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
],
"require": {
"silverstripe/framework": "^5",
"league/flysystem-aws-s3-v3": "^3.0",
"silverstripe/vendor-plugin": "^2",
"jgivoni/flysystem-cache-adapter": "^3.0"
"league/flysystem-aws-s3-v3": "^3",
"silverstripe/vendor-plugin": "^2"
},
"autoload": {
"psr-4": {
Expand Down
Loading

0 comments on commit 11678b2

Please sign in to comment.