cache-revalidation-handler.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. 'use strict'
  2. const assert = require('node:assert')
  3. /**
  4. * This takes care of revalidation requests we send to the origin. If we get
  5. * a response indicating that what we have is cached (via a HTTP 304), we can
  6. * continue using the cached value. Otherwise, we'll receive the new response
  7. * here, which we then just pass on to the next handler (most likely a
  8. * CacheHandler). Note that this assumes the proper headers were already
  9. * included in the request to tell the origin that we want to revalidate the
  10. * response (i.e. if-modified-since or if-none-match).
  11. *
  12. * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-validation
  13. *
  14. * @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler}
  15. */
  16. class CacheRevalidationHandler {
  17. #successful = false
  18. /**
  19. * @type {((boolean, any) => void) | null}
  20. */
  21. #callback
  22. /**
  23. * @type {(import('../../types/dispatcher.d.ts').default.DispatchHandler)}
  24. */
  25. #handler
  26. #context
  27. /**
  28. * @type {boolean}
  29. */
  30. #allowErrorStatusCodes
  31. /**
  32. * @param {(boolean) => void} callback Function to call if the cached value is valid
  33. * @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
  34. * @param {boolean} allowErrorStatusCodes
  35. */
  36. constructor (callback, handler, allowErrorStatusCodes) {
  37. if (typeof callback !== 'function') {
  38. throw new TypeError('callback must be a function')
  39. }
  40. this.#callback = callback
  41. this.#handler = handler
  42. this.#allowErrorStatusCodes = allowErrorStatusCodes
  43. }
  44. onRequestStart (_, context) {
  45. this.#successful = false
  46. this.#context = context
  47. }
  48. onRequestUpgrade (controller, statusCode, headers, socket) {
  49. this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
  50. }
  51. onResponseStart (
  52. controller,
  53. statusCode,
  54. headers,
  55. statusMessage
  56. ) {
  57. assert(this.#callback != null)
  58. // https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo
  59. // https://datatracker.ietf.org/doc/html/rfc5861#section-4
  60. this.#successful = statusCode === 304 ||
  61. (this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504)
  62. this.#callback(this.#successful, this.#context)
  63. this.#callback = null
  64. if (this.#successful) {
  65. return true
  66. }
  67. this.#handler.onRequestStart?.(controller, this.#context)
  68. this.#handler.onResponseStart?.(
  69. controller,
  70. statusCode,
  71. headers,
  72. statusMessage
  73. )
  74. }
  75. onResponseData (controller, chunk) {
  76. if (this.#successful) {
  77. return
  78. }
  79. return this.#handler.onResponseData?.(controller, chunk)
  80. }
  81. onResponseEnd (controller, trailers) {
  82. if (this.#successful) {
  83. return
  84. }
  85. this.#handler.onResponseEnd?.(controller, trailers)
  86. }
  87. onResponseError (controller, err) {
  88. if (this.#successful) {
  89. return
  90. }
  91. if (this.#callback) {
  92. this.#callback(false)
  93. this.#callback = null
  94. }
  95. if (typeof this.#handler.onResponseError === 'function') {
  96. this.#handler.onResponseError(controller, err)
  97. } else {
  98. throw err
  99. }
  100. }
  101. }
  102. module.exports = CacheRevalidationHandler