mirror of
https://github.com/silverstripe/silverstripe-versionfeed
synced 2024-10-22 09:05:31 +00:00
API Added rate limiting and caching based policy filter
This commit is contained in:
parent
4946376793
commit
8eab6f5a1f
@ -24,6 +24,8 @@ Or just clone/download the git repository into a subfolder (usually called "vers
|
||||
|
||||
For usage instructions see [user manual](docs/en/user.md).
|
||||
|
||||
For developer-level instructions see [developer manual](docs/en/developer.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
### Translations
|
||||
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
SiteTree::add_extension('VersionFeed');
|
||||
ContentController::add_extension('VersionFeed_Controller');
|
||||
|
||||
// Set the cache lifetime to 5 mins.
|
||||
SS_Cache::set_cache_lifetime('VersionFeed_Controller', 5*60);
|
25
_config/versionfeed.yml
Normal file
25
_config/versionfeed.yml
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
Name: versionedfeedconfig
|
||||
---
|
||||
SiteTree:
|
||||
extensions:
|
||||
- VersionFeed
|
||||
ContentController:
|
||||
extensions:
|
||||
- VersionFeed_Controller
|
||||
---
|
||||
Name: versionedfeedpolicy
|
||||
Only:
|
||||
ModuleExists: policyfilter
|
||||
---
|
||||
ContentController:
|
||||
extensions:
|
||||
- FilteredExtension
|
||||
dependencies:
|
||||
ContentFilter: %$ContentFilter
|
||||
filter_policy:
|
||||
rate_lock_timeout: 10
|
||||
rate_lock_byitem: true
|
||||
rate_lock_byuserip: false
|
||||
cache_lifetime: 3600
|
||||
cache_factoryname: 'VersionFeed_Cache'
|
@ -3,7 +3,7 @@
|
||||
class VersionFeed extends SiteTreeExtension {
|
||||
|
||||
private static $db = array(
|
||||
'PublicHistory' => 'Boolean'
|
||||
'PublicHistory' => 'Boolean(true)'
|
||||
);
|
||||
|
||||
private static $defaults = array(
|
||||
|
@ -6,8 +6,20 @@ class VersionFeed_Controller extends Extension {
|
||||
'changes',
|
||||
'allchanges'
|
||||
);
|
||||
|
||||
/**
|
||||
* Evaluates the result of the given callback
|
||||
*
|
||||
* @param string $key Unique key for this
|
||||
* @param callable $callback Callback for evaluating the content
|
||||
* @return mixed Result of $callback()
|
||||
*/
|
||||
protected function getContent($key, $callback) {
|
||||
$result = $this->owner->extend('filterContent', $key, $callback);
|
||||
return reset($result) ?: call_user_func($callback);
|
||||
}
|
||||
|
||||
function onAfterInit() {
|
||||
public function onAfterInit() {
|
||||
// RSS feed for per-page changes.
|
||||
if ($this->owner->PublicHistory) {
|
||||
RSSFeed::linkToFeed($this->owner->Link() . 'changes',
|
||||
@ -26,18 +38,15 @@ class VersionFeed_Controller extends Extension {
|
||||
/**
|
||||
* Get page-specific changes in a RSS feed.
|
||||
*/
|
||||
function changes() {
|
||||
if(!$this->owner->PublicHistory) throw new SS_HTTPResponse_Exception('Page history not viewable', 404);;
|
||||
public function changes() {
|
||||
if(!$this->owner->PublicHistory) throw new SS_HTTPResponse_Exception('Page history not viewable', 404);
|
||||
|
||||
// Cache the diffs to remove DOS possibility.
|
||||
$cache = SS_Cache::factory('VersionFeed_Controller');
|
||||
$cache->setOption('automatic_serialization', true);
|
||||
$target = $this->owner;
|
||||
$key = implode('_', array('changes', $this->owner->ID, $this->owner->Version));
|
||||
$entries = $cache->load($key);
|
||||
if(!$entries || isset($_GET['flush'])) {
|
||||
$entries = $this->owner->getDiffedChanges();
|
||||
$cache->save($entries, $key);
|
||||
}
|
||||
$entries = $this->getContent($key, function() use ($target) {
|
||||
return $target->getDiffedChanges();
|
||||
});
|
||||
|
||||
// Generate the output.
|
||||
$title = sprintf(_t('RSSHistory.SINGLEPAGEFEEDTITLE', 'Updates to %s page'), $this->owner->Title);
|
||||
@ -49,42 +58,47 @@ class VersionFeed_Controller extends Extension {
|
||||
/**
|
||||
* Get all changes from the site in a RSS feed.
|
||||
*/
|
||||
function allchanges() {
|
||||
public function allchanges() {
|
||||
|
||||
$latestChanges = DB::query('SELECT * FROM "SiteTree_versions" WHERE "WasPublished"=\'1\' AND "CanViewType" IN (\'Anyone\', \'Inherit\') AND "ShowInSearch"=1 AND ("PublicHistory" IS NULL OR "PublicHistory" = \'1\') ORDER BY "LastEdited" DESC LIMIT 20');
|
||||
$latestChanges = DB::query('
|
||||
SELECT * FROM "SiteTree_versions"
|
||||
WHERE "WasPublished" = \'1\'
|
||||
AND "CanViewType" IN (\'Anyone\', \'Inherit\')
|
||||
AND "ShowInSearch" = 1
|
||||
AND ("PublicHistory" IS NULL OR "PublicHistory" = \'1\')
|
||||
ORDER BY "LastEdited" DESC LIMIT 20'
|
||||
);
|
||||
$lastChange = $latestChanges->record();
|
||||
$latestChanges->rewind();
|
||||
|
||||
if ($lastChange) {
|
||||
|
||||
// Cache the diffs to remove DOS possibility.
|
||||
$member = Member::currentUser();
|
||||
$cache = SS_Cache::factory('VersionFeed_Controller');
|
||||
$cache->setOption('automatic_serialization', true);
|
||||
$key = 'allchanges' . preg_replace('#[^a-zA-Z0-9_]#', '', $lastChange['LastEdited']) .
|
||||
($member ? $member->ID : 'public');
|
||||
|
||||
$changeList = $cache->load($key);
|
||||
if(!$changeList || isset($_GET['flush'])) {
|
||||
|
||||
$key = 'allchanges'
|
||||
. preg_replace('#[^a-zA-Z0-9_]#', '', $lastChange['LastEdited'])
|
||||
. (Member::currentUserID() ?: 'public');
|
||||
$changeList = $this->getContent($key, function() use ($latestChanges) {
|
||||
$changeList = new ArrayList();
|
||||
|
||||
$canView = array();
|
||||
foreach ($latestChanges as $record) {
|
||||
|
||||
// Check if the page should be visible.
|
||||
// WARNING: although we are providing historical details, we check the current configuration.
|
||||
$page = SiteTree::get()->filter(array('ID'=>$record['RecordID']))->First();
|
||||
if (!$page->canView(new Member())) continue;
|
||||
$id = $record['RecordID'];
|
||||
if(!isset($canView[$id])) {
|
||||
$page = SiteTree::get()->byID($id);
|
||||
$canView[$id] = $page && $page->canView(new Member());
|
||||
}
|
||||
if (!$canView[$id]) continue;
|
||||
|
||||
// Get the diff to the previous version.
|
||||
$version = new Versioned_Version($record);
|
||||
|
||||
$changes = $version->getDiffedChanges($version->Version, false);
|
||||
if ($changes && $changes->Count()) $changeList->push($changes->First());
|
||||
}
|
||||
|
||||
$cache->save($changeList, $key);
|
||||
}
|
||||
|
||||
return $changeList;
|
||||
});
|
||||
} else {
|
||||
$changeList = new ArrayList();
|
||||
}
|
||||
|
@ -14,5 +14,8 @@
|
||||
{
|
||||
"silverstripe/framework": "3.*",
|
||||
"silverstripe/cms": "3.*"
|
||||
},
|
||||
"suggest": {
|
||||
"silverstripe/policyfilter": "Provide caching and rate limiting of versioned content"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
docs/en/changelogs/1.0.3.md
Normal file
8
docs/en/changelogs/1.0.3.md
Normal file
@ -0,0 +1,8 @@
|
||||
# 1.0.3
|
||||
|
||||
## Upgrading
|
||||
|
||||
* Added support for the [Policy Filter Module](http://github.com/silverstripe/silverstripe-policyfilter)
|
||||
to apply rate limiting and caching of content.
|
||||
See the [Developer Documentation](../developer.md) for information on how these affect
|
||||
cache performance and behaviour.
|
@ -21,3 +21,32 @@ and returning the URL of your desired RSS feed:
|
||||
}
|
||||
|
||||
This can be used in templates as `$DefaultRSSLink`.
|
||||
|
||||
### Rate limiting and caching
|
||||
|
||||
If the [Policy Filter Module](http://github.com/silverstripe/silverstripe-policyfilter) has been installed then
|
||||
additional rate limiting and caching functionality will be applied automatically. For more information on
|
||||
how this modules works see the policy filter documentation.
|
||||
|
||||
By default all content is filtered based on the rules specified in `versionfeed/_config/versionfeed.yml`.
|
||||
|
||||
For large servers with a high level of traffic (more than 1 visits every 10 seconds) the default settings should
|
||||
be sufficient.
|
||||
|
||||
For smaller servers where it's reasonable to apply a more strict approach to rate limiting, consider setting the
|
||||
`ContentController.filter_policy.rate_lock_byitem` config to false, meaning that a single limit will be
|
||||
applied to all URLs. If left on true, then each URL will have its own rate limit, and on smaller servers with lots of
|
||||
concurrent requests this can still overwhelm capacity. This will also leave smaller servers vulnerable to DDoS
|
||||
attacks which target many URLs simultaneously. This config will have no effect on the `allchanges` method.
|
||||
|
||||
`ContentController.filter_policy.rate_lock_byuserip` can also be set to true in order to prevent requests
|
||||
from different users interfering with one another. However, this can provide an ineffective safeguard
|
||||
against malicious DDoS attacks which use multiple IP addresses.
|
||||
|
||||
Another important variable is the `ContentController.filter_policy.rate_lock_timeout` config, which is set to 10
|
||||
seconds by default. This should be increased on sites which may be slow to generate page versions, whether
|
||||
due to lower server capacity or volume of content (number of page versions). Requests to this page after
|
||||
the timeout will not trigger any rate limit safeguard, so you should be sure that this is set to an appropriate level.
|
||||
|
||||
The default cache of 1 hour can also be adjusted by setting the `ContentController.filter_policy.cache_lifetime`
|
||||
to any time period in seconds.
|
||||
|
@ -6,13 +6,6 @@ class VersionFeedFunctionalTest extends FunctionalTest {
|
||||
'Page_Controller' => array('VersionFeed_Controller'),
|
||||
);
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$cache = SS_Cache::factory('VersionFeed_Controller');
|
||||
$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
|
||||
}
|
||||
|
||||
public function testPublicHistory() {
|
||||
$page = $this->createPageWithChanges(array('PublicHistory' => false));
|
||||
|
||||
@ -101,4 +94,4 @@ class VersionFeedFunctionalTest extends FunctionalTest {
|
||||
return $page;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user