Skip to content

Optionally enable caching of any request method #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Feb 20, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# Change Log

## 1.3.0 - unreleased
### Added

- New `methods` setting which allows to configure the request methods which can be cached.

## 1.2.0 - 2016-08-16

### Changed

- The default value for `default_ttl` is changed from `null` to `0`.
- The default value for `default_ttl` is changed from `null` to `0`.

### Fixed

Expand Down
45 changes: 44 additions & 1 deletion spec/CachePluginSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function it_doesnt_store_failed_responses(CacheItemPoolInterface $pool, CacheIte
$this->handleRequest($request, $next, function () {});
}

function it_doesnt_store_post_requests(CacheItemPoolInterface $pool, CacheItemInterface $item, RequestInterface $request, ResponseInterface $response)
function it_doesnt_store_post_requests_by_default(CacheItemPoolInterface $pool, CacheItemInterface $item, RequestInterface $request, ResponseInterface $response)
{
$request->getMethod()->willReturn('POST');
$request->getUri()->willReturn('/');
Expand All @@ -97,6 +97,49 @@ function it_doesnt_store_post_requests(CacheItemPoolInterface $pool, CacheItemIn
$this->handleRequest($request, $next, function () {});
}

function it_stores_post_requests_when_allowed(CacheItemPoolInterface $pool, CacheItemInterface $item, RequestInterface $request, ResponseInterface $response, StreamFactory $streamFactory, StreamInterface $stream)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please format this according to PSR-1/2?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were some other PSR-2 related problems too so I made a separate PR #25.

{
$this->beConstructedWith($pool, $streamFactory, [
'default_ttl' => 60,
'cache_lifetime' => 1000,
'methods' => ['GET', 'HEAD', 'POST']
]);

$httpBody = 'hello=world';
$stream->__toString()->willReturn($httpBody);
$stream->isSeekable()->willReturn(true);
$stream->rewind()->shouldBeCalled();

$request->getMethod()->willReturn('POST');
$request->getUri()->willReturn('/post');
$request->getBody()->willReturn($stream);

$response->getStatusCode()->willReturn(200);
$response->getBody()->willReturn($stream);
$response->getHeader('Cache-Control')->willReturn(array())->shouldBeCalled();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use short array syntax (StyleCI is turned off for spec files because of the snake_case).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the CS of the rest of the file. Long array() syntax is used elsewhere the spec file.

$response->getHeader('Expires')->willReturn(array())->shouldBeCalled();
$response->getHeader('ETag')->willReturn(array())->shouldBeCalled();

$pool->getItem('e4311a9af932c603b400a54efab21b6d7dea7a90')->shouldBeCalled()->willReturn($item);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please split this too

$item->isHit()->willReturn(false);
$item->expiresAfter(1060)->willReturn($item)->shouldBeCalled();

$item->set($this->getCacheItemMatcher([
'response' => $response->getWrappedObject(),
'body' => $httpBody,
'expiresAt' => 0,
'createdAt' => 0,
'etag' => []
]))->willReturn($item)->shouldBeCalled();

$pool->save(Argument::any())->shouldBeCalled();

$next = function (RequestInterface $request) use ($response) {
return new FulfilledPromise($response->getWrappedObject());
};

$this->handleRequest($request, $next, function () {});
}

function it_calculate_age_from_response(CacheItemPoolInterface $pool, CacheItemInterface $item, RequestInterface $request, ResponseInterface $response, StreamInterface $stream)
{
Expand Down
12 changes: 10 additions & 2 deletions src/CachePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
{
$method = strtoupper($request->getMethod());
// if the request not is cachable, move to $next
if ($method !== 'GET' && $method !== 'HEAD') {
if (!in_array($method, $this->config['methods'])) {
return $next($request);
}

Expand Down Expand Up @@ -205,7 +205,6 @@ private function getCacheControlDirective(ResponseInterface $response, $name)
$headers = $response->getHeader('Cache-Control');
foreach ($headers as $header) {
if (preg_match(sprintf('|%s=?([0-9]+)?|i', $name), $header, $matches)) {

// return the value for $name if it exists
if (isset($matches[1])) {
return $matches[1];
Expand All @@ -225,6 +224,10 @@ private function getCacheControlDirective(ResponseInterface $response, $name)
*/
private function createCacheKey(RequestInterface $request)
{
if ('POST' === $request->getMethod()) {
return hash($this->config['hash_algo'], $request->getMethod().' '.$request->getUri().' '.$request->getBody());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't like that we specifically do this for POST (as i want to support configuring all request methods as cacheable). i think using the body in hash should not be an issue - its usually just empty. technically, GET requests can have a body as well, for example with elasticsearch. so its actually an improvement to also use the body for the hash.

Copy link
Contributor Author

@tuupola tuupola Feb 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on this one too. However some might consider changing the way how keys are generated a BC break. See discussion in #12. Although if I read it correctly the consensus in the end was this would not be considered a BC break.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would not consider that a BC break. chances are you replace the cache on a new deployment anyways, and even if not, its just a cache.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what's the verdict? Do we change this to allow any methods?

}

return hash($this->config['hash_algo'], $request->getMethod().' '.$request->getUri());
}

Expand Down Expand Up @@ -273,12 +276,17 @@ private function configureOptions(OptionsResolver $resolver)
'default_ttl' => 0,
'respect_cache_headers' => true,
'hash_algo' => 'sha1',
'methods' => ['GET', 'HEAD'],
]);

$resolver->setAllowedTypes('cache_lifetime', ['int', 'null']);
$resolver->setAllowedTypes('default_ttl', ['int', 'null']);
$resolver->setAllowedTypes('respect_cache_headers', 'bool');
$resolver->setAllowedTypes('methods', 'array');
$resolver->setAllowedValues('hash_algo', hash_algos());
$resolver->setAllowedValues('methods', function ($value) {
return 0 === count(array_diff($value, ['GET', 'HEAD', 'POST']));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think once we allow to configure, we should allow any methods. we need a big warning sign for this anyways, as people should know what they do before caching post and other potentially modifying requests... for example, it could make sense to cache a PROPFIND request (webdav, its a non-modifying request)...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Question is since RFC2616 says valid request method can be pretty much any string (not sure if A..Z or alphanumeric) should any request method be allowed or just the common methods defined in RFC2616-sec9

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd not do any validation apart from requiring an array of strings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found the spec. RFC2616 says request method should be a token and RFC7230 defines token to be any visible USASCII character except delimiters "(),/:;<=>?@[\]{}.

});
}

/**
Expand Down