138 lines
4.2 KiB
JavaScript
138 lines
4.2 KiB
JavaScript
const express = require('express');
|
|
const axios = require('axios');
|
|
const authMiddleware = require('../middleware/auth');
|
|
|
|
const router = express.Router();
|
|
|
|
const DEFAULT_PROXY_BASE_URL = 'http://localhost:5252';
|
|
const DEFAULT_PROXY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
const PROXY_USER_HEADER = 'x-commerceinsight-proxy-user';
|
|
const PROXY_SECRET_HEADER = 'x-commerceinsight-proxy-secret';
|
|
|
|
function getProxyBaseUrl() {
|
|
const baseUrl = String(process.env.TMALL_DOTNET_PROXY_BASE_URL || DEFAULT_PROXY_BASE_URL).trim();
|
|
return baseUrl.replace(/\/+$/, '');
|
|
}
|
|
|
|
function getProxyTimeoutMs() {
|
|
const timeoutMs = Number(process.env.TMALL_DOTNET_PROXY_TIMEOUT_MS);
|
|
if (Number.isFinite(timeoutMs) && timeoutMs >= 1000) {
|
|
return timeoutMs;
|
|
}
|
|
return DEFAULT_PROXY_TIMEOUT_MS;
|
|
}
|
|
|
|
function buildTargetUrl(req) {
|
|
const upstreamBaseUrl = new URL(`${getProxyBaseUrl()}/`);
|
|
const queryStringIndex = req.originalUrl.indexOf('?');
|
|
const queryString = queryStringIndex >= 0 ? req.originalUrl.slice(queryStringIndex) : '';
|
|
const upstreamBasePath = upstreamBaseUrl.pathname === '/' ? '' : upstreamBaseUrl.pathname.replace(/\/$/, '');
|
|
upstreamBaseUrl.pathname = `${upstreamBasePath}${req.baseUrl}${req.path}`;
|
|
upstreamBaseUrl.search = queryString.startsWith('?') ? queryString.slice(1) : queryString;
|
|
return upstreamBaseUrl.toString();
|
|
}
|
|
|
|
function buildProxyHeaders(req) {
|
|
const proxySharedSecret = String(process.env.TMALL_DOTNET_PROXY_SHARED_SECRET || process.env.CUBEJS_SECRET || '').trim();
|
|
const headers = {
|
|
accept: req.headers.accept,
|
|
'accept-language': req.headers['accept-language'],
|
|
authorization: req.headers.authorization,
|
|
cookie: req.headers.cookie,
|
|
'content-type': req.headers['content-type'],
|
|
'user-agent': req.headers['user-agent'],
|
|
'x-forwarded-for': req.ip,
|
|
'x-forwarded-host': req.get('host'),
|
|
'x-forwarded-prefix': req.baseUrl || '',
|
|
'x-forwarded-proto': req.protocol,
|
|
};
|
|
|
|
if (proxySharedSecret && req.user) {
|
|
headers[PROXY_SECRET_HEADER] = proxySharedSecret;
|
|
headers[PROXY_USER_HEADER] = Buffer.from(JSON.stringify(req.user), 'utf8').toString('base64url');
|
|
}
|
|
|
|
return Object.fromEntries(
|
|
Object.entries(headers).filter(([, value]) => value !== undefined && value !== null && value !== '')
|
|
);
|
|
}
|
|
|
|
function hasRequestBody(method) {
|
|
return !['GET', 'HEAD'].includes(String(method || '').toUpperCase());
|
|
}
|
|
|
|
function isJsonContentType(contentType) {
|
|
const normalized = String(contentType || '').toLowerCase();
|
|
return normalized.includes('application/json') || normalized.includes('+json');
|
|
}
|
|
|
|
function tryParseJsonPayload(buffer) {
|
|
if (!buffer || buffer.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(buffer.toString('utf8'));
|
|
} catch (_error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function normalizeJsonPayload(payload) {
|
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
return payload;
|
|
}
|
|
|
|
if (payload.success === false && !payload.error && typeof payload.message === 'string' && payload.message) {
|
|
return {
|
|
...payload,
|
|
error: payload.message,
|
|
};
|
|
}
|
|
|
|
return payload;
|
|
}
|
|
|
|
router.use(authMiddleware);
|
|
|
|
router.use(async (req, res) => {
|
|
try {
|
|
const response = await axios({
|
|
method: req.method,
|
|
url: buildTargetUrl(req),
|
|
headers: buildProxyHeaders(req),
|
|
data: hasRequestBody(req.method) ? req.body : undefined,
|
|
timeout: getProxyTimeoutMs(),
|
|
responseType: 'arraybuffer',
|
|
transformResponse: [(data) => data],
|
|
validateStatus: () => true,
|
|
maxBodyLength: Infinity,
|
|
maxContentLength: Infinity,
|
|
});
|
|
|
|
const contentType = response.headers['content-type'] || '';
|
|
if (isJsonContentType(contentType)) {
|
|
const payload = tryParseJsonPayload(response.data);
|
|
if (payload !== null) {
|
|
return res.status(response.status).json(normalizeJsonPayload(payload));
|
|
}
|
|
}
|
|
|
|
if (contentType) {
|
|
res.set('Content-Type', contentType);
|
|
}
|
|
|
|
return res.status(response.status).send(Buffer.from(response.data || []));
|
|
} catch (error) {
|
|
return res.status(502).json({
|
|
success: false,
|
|
error: 'Tmall .NET service unavailable',
|
|
message: error.message || 'Proxy request failed',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.prewarmHotBoards = async () => null;
|
|
|
|
module.exports = router;
|