aboutsummaryrefslogtreecommitdiffstats
path: root/vanilla/node_modules/undici/lib/handler/cache-revalidation-handler.js
blob: 393d16d52c60fb861e3154ec47e3552829c432e0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
'use strict'

const assert = require('node:assert')

/**
 * This takes care of revalidation requests we send to the origin. If we get
 *  a response indicating that what we have is cached (via a HTTP 304), we can
 *  continue using the cached value. Otherwise, we'll receive the new response
 *  here, which we then just pass on to the next handler (most likely a
 *  CacheHandler). Note that this assumes the proper headers were already
 *  included in the request to tell the origin that we want to revalidate the
 *  response (i.e. if-modified-since or if-none-match).
 *
 * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-validation
 *
 * @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler}
 */
class CacheRevalidationHandler {
  #successful = false

  /**
   * @type {((boolean, any) => void) | null}
   */
  #callback

  /**
   * @type {(import('../../types/dispatcher.d.ts').default.DispatchHandler)}
   */
  #handler

  #context

  /**
   * @type {boolean}
   */
  #allowErrorStatusCodes

  /**
   * @param {(boolean) => void} callback Function to call if the cached value is valid
   * @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
   * @param {boolean} allowErrorStatusCodes
   */
  constructor (callback, handler, allowErrorStatusCodes) {
    if (typeof callback !== 'function') {
      throw new TypeError('callback must be a function')
    }

    this.#callback = callback
    this.#handler = handler
    this.#allowErrorStatusCodes = allowErrorStatusCodes
  }

  onRequestStart (_, context) {
    this.#successful = false
    this.#context = context
  }

  onRequestUpgrade (controller, statusCode, headers, socket) {
    this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
  }

  onResponseStart (
    controller,
    statusCode,
    headers,
    statusMessage
  ) {
    assert(this.#callback != null)

    // https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo
    // https://datatracker.ietf.org/doc/html/rfc5861#section-4
    this.#successful = statusCode === 304 ||
      (this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504)
    this.#callback(this.#successful, this.#context)
    this.#callback = null

    if (this.#successful) {
      return true
    }

    this.#handler.onRequestStart?.(controller, this.#context)
    this.#handler.onResponseStart?.(
      controller,
      statusCode,
      headers,
      statusMessage
    )
  }

  onResponseData (controller, chunk) {
    if (this.#successful) {
      return
    }

    return this.#handler.onResponseData?.(controller, chunk)
  }

  onResponseEnd (controller, trailers) {
    if (this.#successful) {
      return
    }

    this.#handler.onResponseEnd?.(controller, trailers)
  }

  onResponseError (controller, err) {
    if (this.#successful) {
      return
    }

    if (this.#callback) {
      this.#callback(false)
      this.#callback = null
    }

    if (typeof this.#handler.onResponseError === 'function') {
      this.#handler.onResponseError(controller, err)
    } else {
      throw err
    }
  }
}

module.exports = CacheRevalidationHandler