Skip to content

Commit

Permalink
fix: handles different hosts on actual request and X-Real-Origin
Browse files Browse the repository at this point in the history
  • Loading branch information
esroyo committed Nov 21, 2023
1 parent e82d01d commit 3dfc18b
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 32 deletions.
19 changes: 19 additions & 0 deletions src/esm-proxy-request-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,25 @@ Deno.test('esmProxyRequestHandler', async (t) => {
},
);

await t.step(
'should forward the request to $esmOrigin taking into account that `X-Real-Origin` and the current request URL may differ in origin',
async () => {
Deno.env.set('BASE_PATH', '/sub-dir');
const fetchStub = stub(
_internals,
'fetch',
returnsNext([fetchReturn()]),
);
const req = new Request(`${SELF_ORIGIN}/sub-dir/foo?bundle`, {
headers: { 'X-Real-Origin': 'https://systemjs.sh/' },
});
await esmProxyRequestHandler(req);
assertSpyCallArg(fetchStub, 0, 0, `${ESM_ORIGIN}/foo?bundle`);
fetchStub.restore();
Deno.env.set('BASE_PATH', '');
},
);

await t.step(
'should forward the ESM_SEVICE_HOST response headers back to the client',
async () => {
Expand Down
74 changes: 47 additions & 27 deletions src/esm-proxy-request-handler.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
import { cloneHeaders, _internals } from './utils.ts';
import { _internals, cloneHeaders } from './utils.ts';
import { resolveConfig } from './resolve-config.ts';
import { toSystemjs } from './to-systemjs.ts';
import { ulid } from '../deps.ts';

const markNames = ['body', 'fetch1', 'fetch2', 'curl', 'node', 'tosystemjs', 'total'] as const;
const markNames = [
'body',
'fetch1',
'fetch2',
'curl',
'node',
'tosystemjs',
'total',
] as const;

export async function esmProxyRequestHandler(
req: Request,
): Promise<Response | never> {
const reqHash = ulid();
const prefix = (name: string) => `${reqHash}-${name}`;
markNames.forEach(name => {
markNames.forEach((name) => {
const prefixedName = prefix(name);
performance.clearMarks(prefixedName);
performance.clearMeasures(prefixedName);
});
const mark = (name: typeof markNames[number]) => performance.mark(prefix(name));
const measure = (name: typeof markNames[number]) => performance.measure(prefix(name), prefix(name));
const buildDebugPerformance = () => JSON.stringify([...performance.getEntriesByType('measure').filter((entry) => entry.name.startsWith(reqHash)).map(({ name, duration, startTime }) => ({ name: name.replace(`${reqHash}-`, ''), duration, startTime }))])
const mark = (name: typeof markNames[number]) =>
performance.mark(prefix(name));
const measure = (name: typeof markNames[number]) =>
performance.measure(prefix(name), prefix(name));
const buildDebugPerformance = () =>
JSON.stringify([
...performance.getEntriesByType('measure').filter((entry) =>
entry.name.startsWith(reqHash)
).map(({ name, duration, startTime }) => ({
name: name.replace(`${reqHash}-`, ''),
duration,
startTime,
})),
]);
mark('total');
const {
BASE_PATH,
Expand All @@ -33,8 +52,8 @@ export async function esmProxyRequestHandler(
if ([`${basePath}`, '/', ''].includes(selfUrl.pathname)) {
return Response.redirect(HOMEPAGE || esmOrigin, 302);
}
const realUrl = new URL(req.headers.get('X-Real-Origin') ?? selfUrl);
const finalizeHeaders = (headers: Headers, lowCache = false): void => {
const finalUrl = new URL(req.headers.get('X-Real-Origin') ?? selfUrl);
const finalizeHeaders = (headers: Headers, lowCache = false): void => {
if (lowCache) {
headers.delete('Cache-Control');
headers.set(
Expand All @@ -43,30 +62,31 @@ export async function esmProxyRequestHandler(
);
}
headers.delete('X-Typescript-Types');
headers.set('X-Real-Origin', realUrl.origin);
headers.set('X-Real-Origin', finalUrl.origin);
headers.set('X-Debug-Performance', buildDebugPerformance());
}
const selfOrigin = `${realUrl.origin}${basePath}`;
const esmUrl = new URL(req.url.replace(selfOrigin, ''), esmOrigin);
};
const selfOriginActual = `${selfUrl.origin}${basePath}`;
const selfOriginFinal = `${finalUrl.origin}${basePath}`;
const esmUrl = new URL(req.url.replace(selfOriginActual, ''), esmOrigin);
const replaceOrigin = (() => {
const esmOriginRegExp = new RegExp(esmOrigin, 'ig');
return (str: string) => str.replace(esmOriginRegExp, selfOrigin);
return (str: string) => str.replace(esmOriginRegExp, selfOriginFinal);
})();
if (!!req.headers.get('X-Debug')) {
return Response.json({
BASE_PATH,
ESM_ORIGIN,
HOMEPAGE,
OUTPUT_BANNER,
REDIRECT_DETECT,
REDIRECT_FAILURE_CACHE,
selfUrl,
basePath,
esmOrigin,
realUrl,
selfOrigin,
esmUrl,
xRealOrigin: req.headers.get('X-Real-Origin'),
BASE_PATH,
ESM_ORIGIN,
HOMEPAGE,
OUTPUT_BANNER,
REDIRECT_DETECT,
REDIRECT_FAILURE_CACHE,
selfUrl,
basePath,
esmOrigin,
finalUrl,
selfOriginFinal,
esmUrl,
xRealOrigin: req.headers.get('X-Real-Origin'),
});
}
mark('fetch1');
Expand Down Expand Up @@ -117,7 +137,7 @@ export async function esmProxyRequestHandler(
mark('body');
const esmCode = await esmResponse.text();
measure('body');
mark('tosystemjs')
mark('tosystemjs');
const systemjsCode = await toSystemjs(esmCode, { banner: OUTPUT_BANNER });
measure('tosystemjs');
const headers = cloneHeaders(esmResponse.headers.entries(), replaceOrigin);
Expand Down
15 changes: 10 additions & 5 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { httpz, request } from '../deps.ts';

import type { HttpZResponseModel, HttpZHeader } from './types.ts';
import type { HttpZHeader, HttpZResponseModel } from './types.ts';

type PartialHttpResponse = Pick<
HttpZResponseModel,
Expand Down Expand Up @@ -52,13 +52,18 @@ export const node = async (

export const fetch = globalThis.fetch;

export const cloneHeaders = (headers: IterableIterator<[string, string]> | HttpZHeader[], iteratee = (value: string) => value): Headers => (new Headers(
export const cloneHeaders = (
headers: IterableIterator<[string, string]> | HttpZHeader[],
iteratee = (value: string) => value,
): Headers => (new Headers(
Object.fromEntries(
[...headers].map((item) => {
const [name, value] = Array.isArray(item) ? item : [item.name, item.value];
return value ? [name, iteratee(value)] : [name]
const [name, value] = Array.isArray(item)
? item
: [item.name, item.value];
return value ? [name, iteratee(value)] : [name];
}),
)
),
));

export const _internals = {
Expand Down

0 comments on commit 3dfc18b

Please # to comment.