diff --git a/deno_dist/middleware/etag/index.ts b/deno_dist/middleware/etag/index.ts index 6c44ae6fd..e14b002ed 100644 --- a/deno_dist/middleware/etag/index.ts +++ b/deno_dist/middleware/etag/index.ts @@ -12,7 +12,7 @@ type ETagOptions = { * > would have been sent in an equivalent 200 OK response: Cache-Control, * > Content-Location, Date, ETag, Expires, and Vary. */ -const RETAINED_304_HEADERS = [ +export const RETAINED_304_HEADERS = [ 'cache-control', 'content-location', 'date', diff --git a/src/middleware/etag/index.test.ts b/src/middleware/etag/index.test.ts index 18d3660c3..d70ce2abc 100644 --- a/src/middleware/etag/index.test.ts +++ b/src/middleware/etag/index.test.ts @@ -1,11 +1,9 @@ import { Hono } from '../../hono' -import { etag } from '.' +import { etag, RETAINED_304_HEADERS } from '.' describe('Etag Middleware', () => { - let app: Hono - - beforeEach(() => { - app = new Hono() + it('Should return etag header', async () => { + const app = new Hono() app.use('/etag/*', etag()) app.get('/etag/abc', (c) => { return c.text('Hono is cool') @@ -13,9 +11,6 @@ describe('Etag Middleware', () => { app.get('/etag/def', (c) => { return c.json({ message: 'Hono is cool' }) }) - }) - - it('Should return etag header', async () => { let res = await app.request('http://localhost/etag/abc') expect(res.headers.get('ETag')).not.toBeFalsy() expect(res.headers.get('ETag')).toBe('"4e32298b1cb4edc595237405e5b696e105c2399a"') @@ -26,18 +21,21 @@ describe('Etag Middleware', () => { }) it('Should return etag header - binary', async () => { - app.use('/etag-binary/*', etag()) - app.get('/etag-binary', async (c) => { + const app = new Hono() + app.use('/etag/*', etag()) + app.get('/etag', async (c) => { return c.body(new Uint8Array(1)) }) - const res = await app.request('http://localhost/etag-binary') + const res = await app.request('http://localhost/etag') expect(res.headers.get('ETag')).not.toBeFalsy() const etagHeader = res.headers.get('ETag') expect(etagHeader).toBe('"5ba93c9db0cff93f52b521d7420e43f6eda2784f"') }) it('Should not be the same etag - arrayBuffer', async () => { + const app = new Hono() + app.use('/etag/*', etag()) app.get('/etag/ab1', (c) => { return c.body(new ArrayBuffer(1)) }) @@ -52,6 +50,8 @@ describe('Etag Middleware', () => { }) it('Should not be the same etag - Uint8Array', async () => { + const app = new Hono() + app.use('/etag/*', etag()) app.get('/etag/ui1', (c) => { return c.body(new Uint8Array([1, 2, 3])) }) @@ -66,17 +66,20 @@ describe('Etag Middleware', () => { }) it('Should return etag header - weak', async () => { - app.use('/etag-weak/*', etag({ weak: true })) - app.get('/etag-weak/abc', (c) => { + const app = new Hono() + app.use('/etag/*', etag({ weak: true })) + app.get('/etag/abc', (c) => { return c.text('Hono is cool') }) - const res = await app.request('http://localhost/etag-weak/abc') + const res = await app.request('http://localhost/etag/abc') expect(res.headers.get('ETag')).not.toBeFalsy() expect(res.headers.get('ETag')).toBe('W/"4e32298b1cb4edc595237405e5b696e105c2399a"') }) it('Should handle conditional GETs', async () => { + const app = new Hono() + app.use('/etag/*', etag()) app.get('/etag/ghi', (c) => c.text('Hono is great', 200, { 'cache-control': 'public, max-age=120', @@ -91,7 +94,7 @@ describe('Etag Middleware', () => { let res = await app.request('http://localhost/etag/ghi') expect(res.status).toBe(200) expect(res.headers.get('ETag')).not.toBeFalsy() - const etag = res.headers.get('ETag') || '' + const etagHeaderValue = res.headers.get('ETag') || '' // conditional GET with the wrong ETag: res = await app.request('http://localhost/etag/ghi', { @@ -104,11 +107,11 @@ describe('Etag Middleware', () => { // conditional GET with matching ETag: res = await app.request('http://localhost/etag/ghi', { headers: { - 'If-None-Match': etag, + 'If-None-Match': etagHeaderValue, }, }) expect(res.status).toBe(304) - expect(res.headers.get('Etag')).toBe(etag) + expect(res.headers.get('Etag')).toBe(etagHeaderValue) expect(await res.text()).toBe('') expect(res.headers.get('cache-control')).toBe('public, max-age=120') expect(res.headers.get('date')).toBe('Mon, Feb 27 2023 12:08:36 GMT') @@ -119,23 +122,26 @@ describe('Etag Middleware', () => { // conditional GET with matching ETag among list: res = await app.request('http://localhost/etag/ghi', { headers: { - 'If-None-Match': `"mismatch 1", ${etag}, "mismatch 2"`, + 'If-None-Match': `"mismatch 1", ${etagHeaderValue}, "mismatch 2"`, }, }) expect(res.status).toBe(304) }) it('Should not return duplicate etag header values', async () => { - app.use('/etag2/*', etag()) - app.use('/etag2/*', etag()) - app.get('/etag2/abc', (c) => c.text('Hono is cool')) + const app = new Hono() + app.use('/etag/*', etag()) + app.use('/etag/*', etag()) + app.get('/etag/abc', (c) => c.text('Hono is cool')) - const res = await app.request('http://localhost/etag2/abc') + const res = await app.request('http://localhost/etag/abc') expect(res.headers.get('ETag')).not.toBeFalsy() expect(res.headers.get('ETag')).toBe('"4e32298b1cb4edc595237405e5b696e105c2399a"') }) it('Should not override ETag headers from upstream', async () => { + const app = new Hono() + app.use('/etag/*', etag()) app.get('/etag/predefined', (c) => c.text('This response has an ETag', 200, { ETag: '"f-0194-d"' }) ) @@ -143,4 +149,34 @@ describe('Etag Middleware', () => { const res = await app.request('http://localhost/etag/predefined') expect(res.headers.get('ETag')).toBe('"f-0194-d"') }) + + it('Should retain the default and the specified headers', async () => { + const cacheControl = 'public, max-age=120' + const message = 'Hello!' + const app = new Hono() + app.use( + '/etag/*', + etag({ + retainedHeaders: ['x-message-retain', ...RETAINED_304_HEADERS], + }) + ) + app.get('/etag', (c) => { + return c.text('Hono is cool', 200, { + 'cache-control': cacheControl, + 'x-message-retain': message, + 'x-message': message, + }) + }) + const res = await app.request('/etag', { + headers: { + 'If-None-Match': '"4e32298b1cb4edc595237405e5b696e105c2399a"', + }, + }) + expect(res.status).toBe(304) + expect(res.headers.get('ETag')).not.toBeFalsy() + expect(res.headers.get('ETag')).toBe('"4e32298b1cb4edc595237405e5b696e105c2399a"') + expect(res.headers.get('Cache-Control')).toBe(cacheControl) + expect(res.headers.get('x-message-retain')).toBe(message) + expect(res.headers.get('x-message')).toBeFalsy() + }) }) diff --git a/src/middleware/etag/index.ts b/src/middleware/etag/index.ts index ba3d86c6f..bc25ae041 100644 --- a/src/middleware/etag/index.ts +++ b/src/middleware/etag/index.ts @@ -12,7 +12,7 @@ type ETagOptions = { * > would have been sent in an equivalent 200 OK response: Cache-Control, * > Content-Location, Date, ETag, Expires, and Vary. */ -const RETAINED_304_HEADERS = [ +export const RETAINED_304_HEADERS = [ 'cache-control', 'content-location', 'date',