Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 32 additions & 9 deletions api/_proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function buildHeaders(sourceHeaders) {
return headers;
}

function requireHttpsTarget(envName) {
function requireProxyTarget(envName) {
const rawTarget = process.env[envName]?.trim();

if (!rawTarget) {
Expand All @@ -37,30 +37,52 @@ function requireHttpsTarget(envName) {

const target = new URL(rawTarget);

if (target.protocol !== 'https:') {
throw new Error(`${envName} must use an https URL.`);
if (!['http:', 'https:'].includes(target.protocol)) {
throw new Error(`${envName} must use an http or https URL.`);
}

return target;
}

function buildTargetUrl(requestUrl, prefix, targetBase) {
function normalizeProxyPrefixes({ prefix, prefixes }) {
if (Array.isArray(prefixes)) {
return prefixes;
}

if (prefix) {
return [prefix];
}

return [];
}

function stripProxyPrefix(pathname, prefixes) {
const matchedPrefix = prefixes.find((prefix) => {
return pathname === prefix || pathname.startsWith(`${prefix}/`);
});

if (!matchedPrefix) {
return pathname;
}

return pathname.slice(matchedPrefix.length) || '/';
}

function buildTargetUrl(requestUrl, options, targetBase) {
const incomingUrl = new URL(requestUrl, 'https://vercel.local');
const pathname = incomingUrl.pathname.startsWith(prefix)
? incomingUrl.pathname.slice(prefix.length)
: incomingUrl.pathname;
const pathname = stripProxyPrefix(incomingUrl.pathname, normalizeProxyPrefixes(options));

const targetUrl = new URL(pathname || '/', targetBase);
targetUrl.search = incomingUrl.search;

return targetUrl;
}

async function proxyRequest(request, response, { envName, prefix }) {
async function proxyRequest(request, response, { envName, prefix, prefixes }) {
let targetUrl;

try {
targetUrl = buildTargetUrl(request.url, prefix, requireHttpsTarget(envName));
targetUrl = buildTargetUrl(request.url, { prefix, prefixes }, requireProxyTarget(envName));
} catch (error) {
response.statusCode = 500;
response.setHeader('content-type', 'application/json; charset=utf-8');
Expand Down Expand Up @@ -110,5 +132,6 @@ async function proxyRequest(request, response, { envName, prefix }) {
}

module.exports = {
buildTargetUrl,
proxyRequest,
};
46 changes: 46 additions & 0 deletions api/_proxy.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { createRequire } from 'node:module';

import { describe, expect, it } from 'vitest';

const require = createRequire(import.meta.url);
const { buildTargetUrl } = require('./_proxy');

describe('buildTargetUrl', () => {
const targetBase = new URL('https://backend.example.com');

it('strips the rewritten Vercel function prefix', () => {
const targetUrl = buildTargetUrl(
'/api/be1/api/v1/scan?limit=3',
{
prefixes: ['/api/be1', '/be1'],
},
targetBase,
);

expect(targetUrl.toString()).toBe('https://backend.example.com/api/v1/scan?limit=3');
});

it('strips the original public proxy prefix', () => {
const targetUrl = buildTargetUrl(
'/be1/api/v1/scan',
{
prefixes: ['/api/be1', '/be1'],
},
targetBase,
);

expect(targetUrl.toString()).toBe('https://backend.example.com/api/v1/scan');
});

it('does not partially strip similarly named paths', () => {
const targetUrl = buildTargetUrl(
'/be10/api/v1/scan',
{
prefixes: ['/be1'],
},
targetBase,
);

expect(targetUrl.toString()).toBe('https://backend.example.com/be10/api/v1/scan');
});
});
2 changes: 1 addition & 1 deletion api/be1/[...path].js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ const { proxyRequest } = require('../_proxy');
module.exports = function handler(request, response) {
return proxyRequest(request, response, {
envName: 'BE1_PROXY_TARGET_URL',
prefix: '/api/be1',
prefixes: ['/api/be1', '/be1'],
});
};
2 changes: 1 addition & 1 deletion api/be3/[...path].js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ const { proxyRequest } = require('../_proxy');
module.exports = function handler(request, response) {
return proxyRequest(request, response, {
envName: 'BE3_PROXY_TARGET_URL',
prefix: '/api/be3',
prefixes: ['/api/be3', '/be3'],
});
};
Loading