joostvanveen/laravel-litespeedcache

Laravel implementation of joostvanveen/litespeedcache.

v1.3.0 2019-05-14 08:53 UTC

README

coverage

joostvanveen/laravel-litespeedcache

A Laravel wrapper for joostvanveen/litespeedcache. If you wish to use Litespeed Cache outside of Laravel check out this framework agnostic https://github.com/joostvanveen/litespeedcache

Installation

Require the package using composer:

composer require joostvanveen/laravel-litespeedcache

Enable the Litespeed in your .htaccess file.

<IfModule LiteSpeed>
    # Enable public cache
    CacheEnable public /
    
    # Enable private cache if you need to
    CacheEnable private /
    
    # Check the public cache
    CacheLookup public on
    
    # Ignore normal Cache Control headers
    CacheIgnoreCacheControl On
    
    # Maximum expiration time in seconds
    CacheMaxExpire 604800
</IfModule>

Usage

This package adds a LitespeedCache cache facade for easy use and sets middleware that automatically caches all requests, according to the config settings.

You can set which URLs (for instance any adminconsole*) and query strings not to cache in config. If you want to preview a page without cache, simply add ?cache_bypass=1 to a URL, or set a cache_bypass cookie with a vale of 1.

If you want to set your own middleware, you can set the package to not use the default middleware.

Configuration

To be able to adjust the configuration for this package, publish the configuration files to your project's /config folder like so:

php artisan vendor:publish --provider="Joostvanveen\LaravelLitespeedcache\LitespeedCacheServiceProvider" --tag=config

The config file adds the following settings to your Laravel configuration:

// Cache is enabled
config(['litespeedcache.defaults.enabled' => true]);

// ESI is enabled
config(['litespeedcache.defaults.esiEnabled' => true]);

// Whether or not to cache ajax calls
config(['litespeedcache.defaults.enable_ajax_cache' => false]);

// Array of request methods that should be cached
config(['litespeedcache.defaults.cache_http_verbs' => ['GET', 'HEAD']]);

// Whether or not to use the deafult middleware that comes with this package
config(['litespeedcache.defaults.use_middleware' => true]);

// Default cache type
config(['litespeedcache.defaults.type' => 'public']);

// Default TTL for cache in minutes
config(['litespeedcache.defaults.lifetime' => 240]);

// Array of URIs that should not be cached, can contain wildcards like '/admin*'
config(['litespeedcache.defaults.excludedUris' => [$csrfTokenUri . '*', $csrfFieldUri . '*']]);

// Array of query strings that should not be cached, can contain wildcards like '*utm_source=*'
config(['litespeedcache.defaults.excludedQueryStrings' => []]);

// Array of routes for this package 
config(['litespeedcache.routes' => ['token' => $csrfTokenUri, 'field' => $csrfFieldUri] ]);

Facade

The package registers \Joostvanveen\Litespeedcache\Cache as a facade and sets default config values for litespeedcache.defaults.enabled, litespeedcache.defaults.type, litespeedcache.defaults.lifetime, litespeedcache.defaults.excludedUris, and litespeedcache.defaults.excludedQueryStrings.

use LitespeedCache;

[...]

// Cache use all default settings from config.
LitespeedCache::cache();

// Purge the cache
LitespeedCache::purge();

You can use all methods from \Joostvanveen\Litespeedcache\Cache, see https://github.com/joostvanveen/litespeedcache/blob/master/README.md for full documentation.

Some examples:

// Full options example
// You can also set $excludedUris and $excludedQueryStrings in config.
$excludedUris = [
    'checkout*',
    'admin*',
];
$excludedQueryStrings = [
    '*direction=*',
];
LitespeedCache::setType('private')->setLifetime(120)
                                  ->addTags(['articles', 'en_GB'])
                                  ->addVary('value=mysubdomain')
                                  ->setExcludedUrls($excludedUris)
                                  ->setExcludedQueryStrings($excludedQueryStrings)
                                  ->cache();
                                  
// Purge cache using tags.
LitespeedCache::addTags('articles')->purge();

// If the lifetime is set to 0 the page will not be cached
(new Cache)->setEnabled(true)->setLifetime(0)->cache();

Middleware

By default, the package contains a middleware that caches all pages (except cli).

You can find this middleware at src/Middlewares/Cache.php.

If you want to use your own middleware, you can disable the default middleware by setting the config value litespeedcache.defaults.use_middleware to false

  1. Publish the config settings to your Laravel project.
php artisan vendor:publish --provider="Joostvanveen\LaravelLitespeedcache\LitespeedCacheServiceProvider" --tag=config
  1. Set use_middleware to false
<?php

return [
    'defaults' => [
        'enabled' => true, 
        'use_middleware' => false, // Set Litespeed Cache Middleware to inactive
        'type' => 'public', 
        'lifetime' => 240, 
    ],
];
  1. Create a middleware class
php artisan make:middleware LitespeedCache
  1. Edit your middleware
<?php

namespace App\Http\Middleware;

use Closure;
use Joostvanveen\LaravelLitespeedcache\Facades\LitespeedCache as LitespeedCacheFacade;

class LitespeedCache
{

    /**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     *
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $excludedUris = [
            'checkout*',
            'admin*',
        ];
        
        LitespeedCacheFacade::setEnabled(config('litespeedcache.defaults.enabled'))
                            ->setType(config('litespeedcache.defaults.type'))
                            ->setLifetime(config('litespeedcache.defaults.lifetime'))
                            ->setExcludedUrls($excludedUris)
                            ->cache();

        return $next($request);
    }
}

Cache and csrf tokens in form requests

Laravel has built in security to guard against csrf attacks, using a csrf token. A token check is done on every POST request.

Typically, you provide the csrf token as a hidden field in a form, using csrf_field() or in a POST ajax request, using something like $.post(route('my.route'), $('#form').serialize() + "&_token={{ csrf_token() }});

This poses a problem when used with a full page cache like Litespeed Cache or Varnish. The csrf is unique and should not be stored in cache. If it is you will be posting a cached csrf token instead of a fresh one, resulting in a 'This page has expired' error on doing a POST request. However, you do want to store the rest of the page in cache. You need some way to do hole punching in your cached page. ESI to the rescue.

This package provides two ways to use csrf tokens in you pages as an ESI block. On constructing the cached page, the ESI block will be replaced by the actual, uncached csrf token. This way you can have a fully cached paged, but with a uncached token.

Use the following code to include a hidden field in a form on a cached page, instead of csrf_field(). The 'litespeedcache.routes.field' route is part of this package an will return a hidden field with csrf token.

ESI helper functions

This package comes with an easy helper functions to display an ESI block in your form.

{!! getLitespeedCsrfField() !!}

This will display the proper ESI block if ESI is enabled, and a regular crsf_field() if ESI is not enabled.

// With esi enabled
<esi:include src="{{ route('litespeedcache.csrf.field') }}" />

// With esi disabled 
<input type="hidden" name="_token" value="TGZUHk9BSg6QTH1knqPLPXB20EHKLmqEZSt4f3Uk">

Sometimes you need just the token, for instance for ajax POST requests. That's why there is also a special helper function that returns just the token.

{{ getLitespeedCsrfToken() }}

This will display the proper ESI block if ESI is enabled, and a regular crsf_token() if ESI is not enabled.

// With esi enabled
<esi:include src="{{ route('litespeedcache.csrf.token') }}" />

// With esi disabled 
TGZUHk9BSg6QTH1knqPLPXB20EHKLmqEZSt4f3Uk

Of course, you are free to manually code the ESI blocks into your forms as well.

<esi:include src="{{ route('litespeedcache.csrf.field') }}" />
<esi:include src="{{ route('litespeedcache.csrf.token') }}" />

ESI not supported by your server?

Not all Litespeed servers support ESI. If you are on a Litespeed server that does not support ESI, you can insert an uncached token into your form using ajax, since ajax requests are never cached by joostvanveen/litespeecache.

<script>
    $(function() {
        // Populate all csrf tokens by ajax using an uncached route 
        $.get('{{ route('litespeedcache.csrf.token') }}', function(data) {
            $('input[name="_token"]').val(data.token);
        });
    });
</script>

joostvanveen/litespeedcache documentation

You can find the Litespeed Cache documentation here https://github.com/joostvanveen/litespeedcache/blob/master/README.md

Litespeed documentation

You can find the Litespeed Cache documentation here https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:cache:developer_guide:response_headers

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

Changelog

Changelog

License

MIT