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
10 changes: 7 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Google reCAPTCHA Enterprise checkbox site key
VITE_RECAPTCHA_SITE_KEY=

# Backend base URLs. Replace these with your deployed HTTPS endpoints.
VITE_BE1_BASE_URL=https://be1-backend.example.com
VITE_BE3_BASE_URL=https://be3-backend.example.com
# Browser-facing backend base paths. In Vercel these route through vercel.json rewrites.
VITE_BE1_BASE_URL=/be1
VITE_BE3_BASE_URL=/be3

# Server-side proxy targets for Vercel Functions. Configure these in Vercel only.
BE1_PROXY_TARGET_URL=https://be1-backend.example.com
BE3_PROXY_TARGET_URL=https://be3-backend.example.com

# Shared request timeout
VITE_API_TIMEOUT_MS=10000
Expand Down
114 changes: 114 additions & 0 deletions api/_proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
const HOP_BY_HOP_HEADERS = new Set([
'connection',
'content-encoding',
'content-length',
'host',
'keep-alive',
'proxy-authenticate',
'proxy-authorization',
'te',
'trailer',
'transfer-encoding',
'upgrade',
]);

function buildHeaders(sourceHeaders) {
const headers = new Headers();

for (const [name, value] of Object.entries(sourceHeaders)) {
const normalizedName = name.toLowerCase();

if (HOP_BY_HOP_HEADERS.has(normalizedName) || value === undefined) {
continue;
}

headers.set(name, Array.isArray(value) ? value.join(', ') : value);
}

return headers;
}

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

if (!rawTarget) {
throw new Error(`${envName} is required.`);
}

const target = new URL(rawTarget);

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

return target;
}

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

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

return targetUrl;
}

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

try {
targetUrl = buildTargetUrl(request.url, prefix, requireHttpsTarget(envName));
} catch (error) {
response.statusCode = 500;
response.setHeader('content-type', 'application/json; charset=utf-8');
response.end(JSON.stringify({ message: error.message }));
return;
}

try {
const upstreamResponse = await fetch(targetUrl, {
body: ['GET', 'HEAD'].includes(request.method) ? undefined : request,
duplex: ['GET', 'HEAD'].includes(request.method) ? undefined : 'half',
headers: buildHeaders(request.headers),
method: request.method,
redirect: 'manual',
});

response.statusCode = upstreamResponse.status;

upstreamResponse.headers.forEach((value, name) => {
if (!HOP_BY_HOP_HEADERS.has(name.toLowerCase())) {
response.setHeader(name, value);
}
});

if (!upstreamResponse.body) {
response.end();
return;
}

const reader = upstreamResponse.body.getReader();

while (true) {
const { done, value } = await reader.read();

if (done) {
response.end();
return;
}

response.write(Buffer.from(value));
}
} catch {
response.statusCode = 502;
response.setHeader('content-type', 'application/json; charset=utf-8');
response.end(JSON.stringify({ message: 'Backend proxy request failed.' }));
}
}

module.exports = {
proxyRequest,
};
8 changes: 8 additions & 0 deletions api/be1/[...path].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { proxyRequest } = require('../_proxy');

module.exports = function handler(request, response) {
return proxyRequest(request, response, {
envName: 'BE1_PROXY_TARGET_URL',
prefix: '/api/be1',
});
};
8 changes: 8 additions & 0 deletions api/be3/[...path].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { proxyRequest } = require('../_proxy');

module.exports = function handler(request, response) {
return proxyRequest(request, response, {
envName: 'BE3_PROXY_TARGET_URL',
prefix: '/api/be3',
});
};
12 changes: 12 additions & 0 deletions vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"rewrites": [
{
"source": "/be1/:path*",
"destination": "/api/be1/:path*"
},
{
"source": "/be3/:path*",
"destination": "/api/be3/:path*"
}
]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Loading