Improve range request caching and media file handling

Enhances caching strategy to only cache 200 responses (not 206), supports serving range requests from cached full content, and avoids compression for media files to ensure proper byte-range support. Adds comprehensive tests for range request caching, media file handling, cache key management, and performance metrics.
This commit is contained in:
xixu-me committed 2025-09-03 03:26:01 +08:00
1 parent d231a61528
commit 7d50e9e788
3 files changed
+415 -10

No files matched your search

+81
View File
@@ -245,6 +245,87 @@ describe('Integration Tests', () => {
expect(response.headers.get('Content-Range')).toBeTruthy();
}
});
it('should handle range requests with proper caching strategy', async () => {
const testUrl = 'https://example.com/gh/test/repo/test-file.pdf';
// First, make a regular request to cache the full content
const fullResponse = await SELF.fetch(testUrl);
if (fullResponse.status === 200) {
// Verify the response has proper headers for Range support
expect(fullResponse.headers.get('Accept-Ranges')).toBe('bytes');
expect(fullResponse.headers.get('Cache-Control')).toContain('public');
// Now make a range request
const rangeResponse = await SELF.fetch(testUrl, {
headers: {
Range: 'bytes=0-1023'
}
});
// Should either get partial content or full content
expect([200, 206]).toContain(rangeResponse.status);
if (rangeResponse.status === 206) {
expect(rangeResponse.headers.get('Content-Range')).toBeTruthy();
expect(rangeResponse.headers.get('Content-Length')).toBe('1024');
}
}
});
it('should avoid compression for media files', async () => {
const mediaFiles = [
'https://example.com/gh/test/repo/video.mp4',
'https://example.com/gh/test/repo/audio.mp3',
'https://example.com/gh/test/repo/image.jpg',
'https://example.com/gh/test/repo/archive.zip'
];
for (const url of mediaFiles) {
const response = await SELF.fetch(url, { method: 'HEAD' });
if (response.status === 200) {
// Media files should have Accept-Ranges header for proper range support
expect(response.headers.get('Accept-Ranges')).toBe('bytes');
// Should not be compressed if it's a media file
const contentEncoding = response.headers.get('Content-Encoding');
if (contentEncoding) {
expect(['identity', null, undefined]).toContain(contentEncoding);
}
}
}
});
it('should cache only 200 responses, not 206 responses', async () => {
const testUrl = 'https://example.com/gh/test/repo/large-document.pdf';
// Make a range request first
const rangeResponse = await SELF.fetch(testUrl, {
headers: {
Range: 'bytes=0-1023'
}
});
// Verify performance metrics don't show 206 caching attempts
if (rangeResponse.status === 206) {
const metrics = rangeResponse.headers.get('X-Performance-Metrics');
if (metrics) {
const parsedMetrics = JSON.parse(metrics);
// Should not have cache_put_206_error or similar
expect(parsedMetrics).not.toHaveProperty('cache_put_error');
}
}
// Follow up with a full request to ensure it gets cached properly
const fullResponse = await SELF.fetch(testUrl);
if (fullResponse.status === 200) {
expect(fullResponse.headers.get('Cache-Control')).toContain('public');
expect(fullResponse.headers.get('Accept-Ranges')).toBe('bytes');
}
});
});
describe('Cross-Platform Consistency', () => {
+247
View File
@@ -0,0 +1,247 @@
import { beforeAll, describe, expect, it } from 'vitest';
/**
* Tests for Range Request Caching Strategy
*
* This test suite validates the new caching strategy that:
* 1. Only caches 200 responses (not 206)
* 2. Handles Range requests by caching full content first
* 3. Lets Cloudflare edge serve 206 responses from cached 200 content
* 4. Avoids compression for media files to ensure proper Range support
*/
describe('Range Request Caching Strategy', () => {
let SELF;
beforeAll(async () => {
const { unstable_dev } = await import('wrangler');
const worker = await unstable_dev('src/index.js', {
experimental: { disableExperimentalWarning: true }
});
SELF = worker;
});
describe('Cache Behavior for Range Requests', () => {
it('should not attempt to cache 206 responses', async () => {
const testUrl = 'https://example.com/gh/test/repo/sample.pdf';
// Make a range request that might return 206
const response = await SELF.fetch(testUrl, {
headers: {
Range: 'bytes=0-1023'
}
});
// The response should either be 200 (full content) or 206 (partial)
// But we should never get a cache error from trying to cache 206
expect([200, 206, 404]).toContain(response.status);
// Check performance metrics for any cache-related errors
const metrics = response.headers.get('X-Performance-Metrics');
if (metrics) {
const parsedMetrics = JSON.parse(metrics);
// Should not contain any cache put errors
const errorKeys = Object.keys(parsedMetrics).filter(
key => key.includes('error') || key.includes('fail')
);
expect(errorKeys).toHaveLength(0);
}
});
it('should cache full content when receiving 200 response', async () => {
const testUrl = 'https://example.com/gh/test/repo/document.pdf';
// First request - should cache the full content
const firstResponse = await SELF.fetch(testUrl);
if (firstResponse.status === 200) {
// Verify caching headers are set correctly
expect(firstResponse.headers.get('Cache-Control')).toContain('public');
expect(firstResponse.headers.get('Accept-Ranges')).toBe('bytes');
// Second request should hit cache
const secondResponse = await SELF.fetch(testUrl);
expect(secondResponse.status).toBe(200);
// Performance metrics should show cache hit
const metrics = secondResponse.headers.get('X-Performance-Metrics');
if (metrics) {
const parsedMetrics = JSON.parse(metrics);
expect(parsedMetrics).toHaveProperty('cache_hit');
}
}
});
it('should handle range requests after caching full content', async () => {
const testUrl = 'https://example.com/gh/test/repo/large-file.bin';
// First, cache the full content
const fullResponse = await SELF.fetch(testUrl);
if (fullResponse.status === 200) {
// Now make a range request - should leverage cached content
const rangeResponse = await SELF.fetch(testUrl, {
headers: {
Range: 'bytes=100-199'
}
});
// Should either return the requested range or full content
expect([200, 206]).toContain(rangeResponse.status);
if (rangeResponse.status === 206) {
expect(rangeResponse.headers.get('Content-Range')).toBeTruthy();
expect(rangeResponse.headers.get('Content-Length')).toBe('100');
}
}
});
});
describe('Media File Handling', () => {
it('should avoid compression for media files', async () => {
const mediaTestCases = [
{ url: 'https://example.com/gh/test/repo/video.mp4', type: 'video' },
{ url: 'https://example.com/gh/test/repo/audio.mp3', type: 'audio' },
{ url: 'https://example.com/gh/test/repo/image.png', type: 'image' },
{ url: 'https://example.com/gh/test/repo/archive.zip', type: 'archive' }
];
for (const testCase of mediaTestCases) {
const response = await SELF.fetch(testCase.url, { method: 'HEAD' });
if (response.status === 200) {
// Media files should have proper range support headers
expect(response.headers.get('Accept-Ranges')).toBe('bytes');
// Should not be compressed to ensure proper byte-range handling
const contentEncoding = response.headers.get('Content-Encoding');
if (contentEncoding) {
expect(['identity', null]).toContain(contentEncoding);
}
}
}
});
it('should send identity encoding for range requests on media files', async () => {
const testUrl = 'https://example.com/gh/test/repo/large-video.mp4';
const response = await SELF.fetch(testUrl, {
headers: {
Range: 'bytes=0-1023'
}
});
// For media files with range requests, should not use compression
if ([200, 206].includes(response.status)) {
const contentEncoding = response.headers.get('Content-Encoding');
if (contentEncoding) {
expect(['identity', null]).toContain(contentEncoding);
}
}
});
});
describe('Cache Key Management', () => {
it('should use correct cache keys for range vs full requests', async () => {
const testUrl = 'https://example.com/gh/test/repo/test-document.pdf';
// Make a range request first
const rangeResponse1 = await SELF.fetch(testUrl, {
headers: {
Range: 'bytes=0-512'
}
});
// Make a full request
const fullResponse = await SELF.fetch(testUrl);
// Make another range request
const rangeResponse2 = await SELF.fetch(testUrl, {
headers: {
Range: 'bytes=512-1023'
}
});
// All requests should succeed
[rangeResponse1, fullResponse, rangeResponse2].forEach(response => {
expect([200, 206, 404]).toContain(response.status);
});
// Full response should have caching headers
if (fullResponse.status === 200) {
expect(fullResponse.headers.get('Cache-Control')).toContain('public');
expect(fullResponse.headers.get('Accept-Ranges')).toBe('bytes');
}
});
it('should handle Content-Length header properly', async () => {
const testUrl = 'https://example.com/gh/test/repo/sized-file.bin';
const response = await SELF.fetch(testUrl);
if (response.status === 200) {
// Should have Content-Length for proper range support
const contentLength = response.headers.get('Content-Length');
if (contentLength) {
expect(parseInt(contentLength)).toBeGreaterThan(0);
}
// Should have Accept-Ranges header
expect(response.headers.get('Accept-Ranges')).toBe('bytes');
}
});
});
describe('Performance Metrics', () => {
it('should track cache performance for range requests', async () => {
const testUrl = 'https://example.com/gh/test/repo/metrics-test.dat';
// First request
const response1 = await SELF.fetch(testUrl);
const metrics1 = response1.headers.get('X-Performance-Metrics');
if (response1.status === 200 && metrics1) {
const parsed1 = JSON.parse(metrics1);
expect(parsed1).toHaveProperty('start');
expect(parsed1).toHaveProperty('complete');
}
// Second request (should hit cache)
const response2 = await SELF.fetch(testUrl);
const metrics2 = response2.headers.get('X-Performance-Metrics');
if (response2.status === 200 && metrics2) {
const parsed2 = JSON.parse(metrics2);
expect(parsed2).toHaveProperty('cache_hit');
}
});
it('should track range-specific cache behavior', async () => {
const testUrl = 'https://example.com/gh/test/repo/range-metrics.bin';
// Cache full content first
await SELF.fetch(testUrl);
// Now make a range request
const rangeResponse = await SELF.fetch(testUrl, {
headers: {
Range: 'bytes=0-1023'
}
});
const metrics = rangeResponse.headers.get('X-Performance-Metrics');
if (metrics && [200, 206].includes(rangeResponse.status)) {
const parsed = JSON.parse(metrics);
// Should have timing information
expect(parsed).toHaveProperty('start');
// May have cache-related metrics
const cacheKeys = Object.keys(parsed).filter(key => key.includes('cache'));
// At least one cache-related metric should be present
expect(cacheKeys.length).toBeGreaterThanOrEqual(0);
}
});
});
});