123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- const CacheSemantics = require('http-cache-semantics')
- const Negotiator = require('negotiator')
- const ssri = require('ssri')
- // options passed to http-cache-semantics constructor
- const policyOptions = {
- shared: false,
- ignoreCargoCult: true,
- }
- // a fake empty response, used when only testing the
- // request for storability
- const emptyResponse = { status: 200, headers: {} }
- // returns a plain object representation of the Request
- const requestObject = (request) => {
- const _obj = {
- method: request.method,
- url: request.url,
- headers: {},
- compress: request.compress,
- }
- request.headers.forEach((value, key) => {
- _obj.headers[key] = value
- })
- return _obj
- }
- // returns a plain object representation of the Response
- const responseObject = (response) => {
- const _obj = {
- status: response.status,
- headers: {},
- }
- response.headers.forEach((value, key) => {
- _obj.headers[key] = value
- })
- return _obj
- }
- class CachePolicy {
- constructor ({ entry, request, response, options }) {
- this.entry = entry
- this.request = requestObject(request)
- this.response = responseObject(response)
- this.options = options
- this.policy = new CacheSemantics(this.request, this.response, policyOptions)
- if (this.entry) {
- // if we have an entry, copy the timestamp to the _responseTime
- // this is necessary because the CacheSemantics constructor forces
- // the value to Date.now() which means a policy created from a
- // cache entry is likely to always identify itself as stale
- this.policy._responseTime = this.entry.metadata.time
- }
- }
- // static method to quickly determine if a request alone is storable
- static storable (request, options) {
- // no cachePath means no caching
- if (!options.cachePath) {
- return false
- }
- // user explicitly asked not to cache
- if (options.cache === 'no-store') {
- return false
- }
- // we only cache GET and HEAD requests
- if (!['GET', 'HEAD'].includes(request.method)) {
- return false
- }
- // otherwise, let http-cache-semantics make the decision
- // based on the request's headers
- const policy = new CacheSemantics(requestObject(request), emptyResponse, policyOptions)
- return policy.storable()
- }
- // returns true if the policy satisfies the request
- satisfies (request) {
- const _req = requestObject(request)
- if (this.request.headers.host !== _req.headers.host) {
- return false
- }
- if (this.request.compress !== _req.compress) {
- return false
- }
- const negotiatorA = new Negotiator(this.request)
- const negotiatorB = new Negotiator(_req)
- if (JSON.stringify(negotiatorA.mediaTypes()) !== JSON.stringify(negotiatorB.mediaTypes())) {
- return false
- }
- if (JSON.stringify(negotiatorA.languages()) !== JSON.stringify(negotiatorB.languages())) {
- return false
- }
- if (JSON.stringify(negotiatorA.encodings()) !== JSON.stringify(negotiatorB.encodings())) {
- return false
- }
- if (this.options.integrity) {
- return ssri.parse(this.options.integrity).match(this.entry.integrity)
- }
- return true
- }
- // returns true if the request and response allow caching
- storable () {
- return this.policy.storable()
- }
- // NOTE: this is a hack to avoid parsing the cache-control
- // header ourselves, it returns true if the response's
- // cache-control contains must-revalidate
- get mustRevalidate () {
- return !!this.policy._rescc['must-revalidate']
- }
- // returns true if the cached response requires revalidation
- // for the given request
- needsRevalidation (request) {
- const _req = requestObject(request)
- // force method to GET because we only cache GETs
- // but can serve a HEAD from a cached GET
- _req.method = 'GET'
- return !this.policy.satisfiesWithoutRevalidation(_req)
- }
- responseHeaders () {
- return this.policy.responseHeaders()
- }
- // returns a new object containing the appropriate headers
- // to send a revalidation request
- revalidationHeaders (request) {
- const _req = requestObject(request)
- return this.policy.revalidationHeaders(_req)
- }
- // returns true if the request/response was revalidated
- // successfully. returns false if a new response was received
- revalidated (request, response) {
- const _req = requestObject(request)
- const _res = responseObject(response)
- const policy = this.policy.revalidatedPolicy(_req, _res)
- return !policy.modified
- }
- }
- module.exports = CachePolicy
|