Skip to content
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

Cache control #71

Closed
thekid opened this issue Mar 20, 2021 · 6 comments
Closed

Cache control #71

thekid opened this issue Mar 20, 2021 · 6 comments

Comments

@thekid
Copy link
Member

thekid commented Mar 20, 2021

http-cache verifies that the page and all its resources follow a good, sustainable caching strategy.

The right caching strategy can help improve site performance through:

  • Shorter load times
  • Reduced bandwidth
  • Reduced server costs
  • Having predictable behavior across browsers

image

See https://webhint.io/docs/user-guide/hints/hint-http-cache/

@thekid
Copy link
Member Author

thekid commented Mar 20, 2021

One idea would be to implement this as a handler:

new CacheControl('public, max-age=31557600', function($req, $res) {
  // ...
})

@thekid
Copy link
Member Author

thekid commented Mar 20, 2021

See https://ktor.io/docs/caching.html:

install(CachingHeaders) {
    options { outgoingContent ->
        when (outgoingContent.contentType?.withoutParameters()) {
            ContentType.Text.CSS -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 3600))
            else -> null
        }
    }
}

@thekid
Copy link
Member Author

thekid commented Mar 20, 2021

Take-aways from https://csswizardry.com/2019/03/cache-control-for-civilians/:

Assets

For immutable files including a fingerprint or version, e.g. style.cb381a32b229.css, we should use Cache-Control: max-age=31536000, immutable (31536000 seconds is one year, the maximum allowed value in this case)

For other static assets, Cache-Control: max-age=2419200, must-revalidate, stale-while-revalidate=86400 (store the image for 28 days, that we want to check with the server for updates after that 28-day time limit, and if the image is less than one day (86,400 seconds) out of date, let’s use that one while we fetch the latest version in the background.) could be appropriate

Pages

For pages with very sensitive data, e.g. login forms, Cache-Control: no-store is the way to go (the spec states: The no-store response directive indicates that a cache MUST NOT store any part of either the immediate request or response. This directive applies to both private and shared caches)

For dynamic pages, most commonly Cache-Control: no-cache will do the job (to quote: This simple directive will mean that the browser won’t show a response directly from cache without checking with the server that it is allowed to)

For dynamic pages which are unfrequently updated (say, a blog), we can cache a little bit more using Cache-Control: max-age=604800, must-revalidate (quote: This tells the browser to cache the HTML page for one week (604,800 seconds), and once that week is up, we need to check with the server for updates.)

@thekid
Copy link
Member Author

thekid commented Mar 27, 2021

Idea for a caching DSL instead of simply passing seconds (because of its general use, the CacheControl class would be provided by the web package).

//  Cache-Control: no-cache
new AssetsFrom(..., CacheControl::noCache())

// Cache-Control: max-age=31536000, immutable
new AssetsFrom(..., CacheControl::maxAge(seconds: 31536000)->immutable())

// Cache-Control: max-age=2419200, must-revalidate
new AssetsFrom(..., CacheControl::maxAge(seconds: 2419200)->mustRevalidate())

...and for fingerprinting using PHP 8 match expressions:

new AssetsFrom(..., fn($path, $mime) => match (true) {
  preg_match('/[a-z]+\.[a-f0-9]+\.[a-z]+$/', $path) => CacheControl::maxAge(seconds: 31536000)->immutable(),
  default => CacheControl::maxAge(seconds: 2419200)->mustRevalidate()
});

...or using an array:

new AssetsFrom(..., [
  '/^[a-z]+\.[a-f0-9]+\.[a-z]+/' => CacheControl::maxAge(seconds: 31536000)->immutable(),
  null => CacheControl::maxAge(seconds: 2419200)->mustRevalidate()
]);

@thekid
Copy link
Member Author

thekid commented Mar 27, 2021

Alternative use as filter:

//  Cache-Control: no-cache
new Filters([CacheControl::noCache()], new AssetsFrom(...))

// Cache-Control: max-age=31536000, immutable
new Filters([CacheControl::maxAge(seconds: 31536000)->immutable()], new AssetsFrom(...))

// Cache-Control: max-age=2419200, must-revalidate
new Filters([CacheControl::maxAge(seconds: 2419200)->mustRevalidate()], new AssetsFrom(...))

We cannot attach headers inside a filter after the invocation because the content may have already been sent. The following doesn't work:

class CacheControl implements Filter {
  // ...shortened for brevity

  public function filter($request, $response, $invocation) {
    $result= $invocation->proceed($request, $response);
    if (200 === $response->status()) {
      $response->header('Cache-Control', $this->header());
    }
    return $result;
  }
}

So, we need to rewrite this to the following:

class CacheControl implements Filter {
  // ...shortened for brevity

  public function filter($request, $response, $invocation) {
    $response->header('Cache-Control', $this->header());
    return $invocation->proceed($request, $response);
  }
}

However, this would attach to all responses and not just 2XX response codes. Caching 404s is most probably undesirable!

There are two possibilities here:

  • Conditional headers, e.g. something like $response->header('Cache-Control', $this->header(), for: [200, 201]);
  • Making all transfer actions asynchronous, meaning $response->write() and friends would defer until the initial service() call by the application (and maybe even be handed over to the I/O loop, solving Slow transfers block entire server #70)

@thekid
Copy link
Member Author

thekid commented Apr 10, 2021

PR #74 is released in 2.9.0

@thekid thekid closed this as completed Apr 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant