Skip to content

Commit

Permalink
Merge pull request #318 from digitalkaoz/s3_proxy_cache
Browse files Browse the repository at this point in the history
added proxy cache resolver
  • Loading branch information
havvg committed Feb 7, 2014
2 parents 142d30a + 13d2d8c commit 5598514
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 0 deletions.
118 changes: 118 additions & 0 deletions Imagine/Cache/Resolver/ProxyResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

namespace Liip\ImagineBundle\Imagine\Cache\Resolver;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Kernel;

/**
* ProxyResolver
*
* @author Robert Schönthal <robert.schoenthal@gmail.com>
*/
class ProxyResolver implements ResolverInterface
{
/**
* @var ResolverInterface
*/
private $resolver;

/**
* a list of proxy hosts (picks a random one for each generation to seed browser requests among multiple hosts)
*
* @var array
*/
private $hosts = array();

/**
* @param ResolverInterface $resolver
*/
public function __construct(ResolverInterface $resolver, array $hosts)
{
$this->resolver = $resolver;
$this->hosts = $hosts;
}

/**
* {@inheritDoc}
*/
public function resolve(Request $request, $path, $filter)
{
$response = $this->resolver->resolve($request, $path, $filter);
$this->rewriteResponse($response);

return $response;
}

/**
* {@inheritDoc}
*/
public function store(Response $response, $targetPath, $filter)
{
$response = $this->resolver->store($response, $targetPath, $filter);
$this->rewriteResponse($response);

return $response;
}

/**
* {@inheritDoc}
*/
public function getBrowserPath($path, $filter, $absolute = false)
{
$response = $this->resolver->getBrowserPath($path, $filter, $absolute);
$this->rewriteResponse($response);

return $response;
}

/**
* {@inheritDoc}
*/
public function remove($targetPath, $filter)
{
return $this->resolver->remove($targetPath, $filter);
}

/**
* {@inheritDoc}
*/
public function clear($cachePrefix)
{
$this->resolver->clear($cachePrefix);
}

private function rewriteResponse($response)
{
if (!$response instanceof RedirectResponse || !$this->hosts) {
return;
}

if ('2' == Kernel::MAJOR_VERSION && '0' == Kernel::MINOR_VERSION) {
$redirectLocation = $response->headers->get('Location');
} else {
$redirectLocation = $response->getTargetUrl();
}
$path = parse_url($redirectLocation, PHP_URL_PATH);

if ($path == $redirectLocation) {
//relative path, so strip of SCRIPT_FILE_NAME if existient
$path = substr($path, (strpos($path, '.php') !== false ? strpos($path, '.php') + 4 : 0));
}

if ('2' == Kernel::MAJOR_VERSION && '0' == Kernel::MINOR_VERSION) {
$response->headers->set('Location', $this->createProxyUrl($path));
} else {
$response->setTargetUrl($this->createProxyUrl($path));
}
}

private function createProxyUrl($path)
{
$domain = $this->hosts[rand(0, count($this->hosts) - 1)];

return $domain . $path;
}
}
26 changes: 26 additions & 0 deletions Resources/doc/cache-resolver/proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# ProxyResolver

The ProxyResolver is a `decorator` for every other Resolver

This Resolver adds the possibility to use Proxy Hosts for your Assets.
If no Proxy Domains are set, it behaves like the underlying `Resolver`

## Set Proxy Domains

In order to use this Resolver you must create a Service and inject some domains and your underlying Resolver

``` yaml
services:
acme.imagine.cache.resolver.proxy:
class: Liip\ImagineBundle\Imagine\Cache\Resolver\ProxyResolver
arguments:
- "@acme.imagine.cache.resolver.amazon_s3"
- [ 'http://images0.domain.com', 'http://images1.domain.com','http://images2.domain.com' ]
tags:
- { name: 'liip_imagine.cache.resolver', resolver: 'proxy' }
```
Now your Resolver would generate following Urls `http://images0.domain.com/thumbs/article_thumb/foo.jpg` instead of the original path from the underlying Resolver `bucket.s3.awsamazoncloud.com/thumbs/article_thumb/foo.jpg` for every relevant Action.

- [Back to cache resolvers](../cache-resolvers.md)
- [Back to the index](../index.md)
1 change: 1 addition & 0 deletions Resources/doc/cache-resolvers.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* [AmazonS3](cache-resolver/amazons3.md)
* [AwsS3](cache-resolver/aws_s3.md) - for SDK version 2
* [CacheResolver](cache-resolver/cache.md)
* [ProxyResolver](cache-resolver/proxy.md)

# Custom cache resolver

Expand Down
147 changes: 147 additions & 0 deletions Tests/Imagine/Cache/Resolver/ProxyResolverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php


namespace Liip\ImagineBundle\Tests\Imagine\Cache\Resolver;

use Liip\ImagineBundle\Imagine\Cache\Resolver\ProxyResolver;
use Liip\ImagineBundle\Imagine\Cache\Resolver\ResolverInterface;
use Liip\ImagineBundle\Tests\AbstractTest;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Kernel;


/**
* ProxyResolverTest
*
* @author Robert Schönthal <robert.schoenthal@gmail.com>
*/
class ProxyResolverTest extends AbstractTest
{
/**
* @var ResolverInterface
*/
private $primaryResolver;

/**
* @var ProxyResolver
*/
private $resolver;

public function setUp()
{
$this->primaryResolver = $this->getMock('Liip\ImagineBundle\Imagine\Cache\Resolver\ResolverInterface');

$this->resolver = new ProxyResolver($this->primaryResolver, array('http://images.example.com'));
}

public function testResolveWithResponse()
{
$this->primaryResolver
->expects($this->once())
->method('resolve')
->will($this->returnValue(new RedirectResponse('app_dev.php/thumbs/foo/bar/bazz.png')));

$result = $this->resolver->resolve(new Request(), '/foo/bar/bazz.png', 'test');

$this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $result);

if ('2' == Kernel::MAJOR_VERSION && '0' == Kernel::MINOR_VERSION) {
$this->assertEquals('http://images.example.com/thumbs/foo/bar/bazz.png', $result->headers->get('Location'));
} else {
$this->assertEquals('http://images.example.com/thumbs/foo/bar/bazz.png', $result->getTargetUrl());
}
}

public function testResolveWithoutResponse()
{
$this->primaryResolver
->expects($this->once())
->method('resolve')
->will($this->returnValue('app_dev.php/thumbs/foo/bar/bazz.png'));

$result = $this->resolver->resolve(new Request(), '/foo/bar/bazz.png', 'test');

$this->assertEquals('app_dev.php/thumbs/foo/bar/bazz.png', $result);
}

public function testStoreWithResponse()
{
$this->primaryResolver
->expects($this->once())
->method('store')
->will($this->returnValue(new RedirectResponse('http://foo.com/thumbs/foo/bar/bazz.png')));

$result = $this->resolver->store(new Response(), '/foo/bar/bazz.png', 'test');

$this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $result);

if ('2' == Kernel::MAJOR_VERSION && '0' == Kernel::MINOR_VERSION) {
$this->assertEquals('http://images.example.com/thumbs/foo/bar/bazz.png', $result->headers->get('Location'));
} else {
$this->assertEquals('http://images.example.com/thumbs/foo/bar/bazz.png', $result->getTargetUrl());
}
}

public function testStoreWithoutResponse()
{
$this->primaryResolver
->expects($this->once())
->method('store')
->will($this->returnValue('http://foo.com/thumbs/foo/bar/bazz.png'));

$result = $this->resolver->store(new Response(), '/foo/bar/bazz.png', 'test');

$this->assertEquals('http://foo.com/thumbs/foo/bar/bazz.png', $result);
}

public function testGetBrowserPathWithResponse()
{
$this->primaryResolver
->expects($this->once())
->method('getBrowserPath')
->will($this->returnValue(new RedirectResponse('s3://myfunkybucket/thumbs/foo/bar/bazz.png')));

$result = $this->resolver->getBrowserPath('/foo/bar/bazz.png', 'test');

$this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $result);

if ('2' == Kernel::MAJOR_VERSION && '0' == Kernel::MINOR_VERSION) {
$this->assertEquals('http://images.example.com/thumbs/foo/bar/bazz.png', $result->headers->get('Location'));
} else {
$this->assertEquals('http://images.example.com/thumbs/foo/bar/bazz.png', $result->getTargetUrl());
}
}

public function testGetBrowserPathWithoutResponse()
{
$this->primaryResolver
->expects($this->once())
->method('getBrowserPath')
->will($this->returnValue('s3://myfunkybucket/thumbs/foo/bar/bazz.png'));

$result = $this->resolver->getBrowserPath('/foo/bar/bazz.png', 'test');

$this->assertEquals('s3://myfunkybucket/thumbs/foo/bar/bazz.png', $result);
}

public function testRemove()
{
$this->primaryResolver
->expects($this->once())
->method('remove');

$this->resolver->remove('/foo/bar/bazz.png', 'test');
}

public function testClear()
{
$this->primaryResolver
->expects($this->once())
->method('clear');

$this->resolver->clear('test');
}

}

0 comments on commit 5598514

Please sign in to comment.