diff --git a/src/lib/apiProxy.test.ts b/src/lib/apiProxy.test.ts new file mode 100644 index 0000000..54e3d2f --- /dev/null +++ b/src/lib/apiProxy.test.ts @@ -0,0 +1,52 @@ +// @vitest-environment node + +import { afterEach, describe, expect, it, vi } from 'vitest' +import { NextRequest } from 'next/server' + +import { proxyRequestToUpstream } from './apiProxy' + +describe('proxyRequestToUpstream', () => { + afterEach(() => { + vi.unstubAllGlobals() + }) + + it('strips stale encoding headers when the upstream body is already decoded', async () => { + const fetchMock = vi.fn().mockResolvedValue( + new Response(JSON.stringify([{ name: 'JP', address: 'jp-xhttp.svc.plus' }]), { + status: 200, + headers: { + 'Content-Encoding': 'gzip', + 'Content-Length': '405', + 'Content-Type': 'application/json; charset=utf-8', + }, + }), + ) + vi.stubGlobal('fetch', fetchMock) + + const response = await proxyRequestToUpstream( + new NextRequest('https://console.svc.plus/api/agent/nodes', { + headers: { + host: 'console.svc.plus', + }, + }), + { + upstreamBaseUrl: 'https://accounts.svc.plus', + upstreamPathPrefix: '/api/agent', + }, + ) + + expect(fetchMock).toHaveBeenCalledWith( + 'https://accounts.svc.plus/api/agent/nodes', + expect.objectContaining({ + cache: 'no-store', + method: 'GET', + redirect: 'manual', + }), + ) + expect(response.status).toBe(200) + expect(response.headers.get('content-encoding')).toBeNull() + expect(response.headers.get('content-length')).toBeNull() + expect(response.headers.get('content-type')).toBe('application/json; charset=utf-8') + await expect(response.json()).resolves.toEqual([{ name: 'JP', address: 'jp-xhttp.svc.plus' }]) + }) +}) diff --git a/src/lib/apiProxy.ts b/src/lib/apiProxy.ts index 19fff40..86d2a3a 100644 --- a/src/lib/apiProxy.ts +++ b/src/lib/apiProxy.ts @@ -14,6 +14,14 @@ const DEFAULT_FORWARD_HEADERS = [ 'x-trace-id', ] as const +const RESPONSE_HEADERS_TO_STRIP = new Set([ + 'connection', + 'content-encoding', + 'content-length', + 'keep-alive', + 'transfer-encoding', +]) + const BODYLESS_METHODS = new Set(['GET', 'HEAD']) type ProxyOptions = { @@ -107,7 +115,8 @@ export async function proxyRequestToUpstream(request: NextRequest, options: Prox const responseHeaders = new Headers() upstreamResponse.headers.forEach((value, key) => { - if (key.toLowerCase() === 'set-cookie') { + const normalizedKey = key.toLowerCase() + if (normalizedKey === 'set-cookie' || RESPONSE_HEADERS_TO_STRIP.has(normalizedKey)) { return } responseHeaders.set(key, value) diff --git a/src/modules/extensions/builtin/user-center/lib/fetchAgentNodes.test.ts b/src/modules/extensions/builtin/user-center/lib/fetchAgentNodes.test.ts index caff97e..8ec685c 100644 --- a/src/modules/extensions/builtin/user-center/lib/fetchAgentNodes.test.ts +++ b/src/modules/extensions/builtin/user-center/lib/fetchAgentNodes.test.ts @@ -20,7 +20,7 @@ describe('fetchAgentNodes', () => { await expect(fetchAgentNodes()).resolves.toEqual([{ name: 'JP', address: 'jp-xhttp.svc.plus' }]) expect(fetchMock).toHaveBeenCalledTimes(1) expect(fetchMock).toHaveBeenCalledWith( - '/api/agent-server/v1/nodes', + '/api/auth/sync/config?since_version=0', expect.objectContaining({ cache: 'no-store', credentials: 'include',