@schoolofmotion/rocket-booster
Version:
Serverless reverse proxy and load balancing library built for Cloudflare Workers.
86 lines (77 loc) • 2.37 kB
text/typescript
import { Middleware } from '../types/middleware';
import { UpstreamOptions } from '../types/upstream';
import { LoadBalancingHandler, LoadBalancingPolicy } from '../types/load-balancing';
const validateUpstream = (
upstream: UpstreamOptions,
): void => {
if (upstream.domain === undefined) {
throw new Error('Invalid \'upstream\' field in the option object');
}
};
export const ipHashHandler: LoadBalancingHandler = (
upstream,
request,
) => {
const ipString = request.headers.get('cf-connecting-ip') || '0.0.0.0';
const userIP = ipString.split('.').map(
(octect, index, array) => parseInt(octect, 10) * (256 ** (array.length - index - 1)),
).reduce(
(accumulator, current) => accumulator + current,
);
return upstream[userIP % upstream.length];
};
export const randomHandler: LoadBalancingHandler = (
upstream,
) => {
const weights = upstream.map(
(option) => (option.weight === undefined ? 1 : option.weight),
);
const totalWeight = weights.reduce(
(acc, num, index) => {
const sum = acc + num;
weights[index] = sum;
return sum;
},
);
if (totalWeight === 0) {
throw new Error('Total weights should be greater than 0.');
}
const random = Math.random() * totalWeight;
for (const index of weights.keys()) {
if (weights[index] >= random) {
return upstream[index];
}
}
return upstream[Math.floor(Math.random() * upstream.length)];
};
const handlersMap: Record<
LoadBalancingPolicy,
LoadBalancingHandler
> = {
random: randomHandler,
'ip-hash': ipHashHandler,
};
export const useLoadBalancing: Middleware = async (
context,
next,
) => {
const { request, options } = context;
const { upstream, loadBalancing } = options;
if (upstream === undefined) {
throw new Error('The required \'upstream\' field in the option object is missing');
} else if (Array.isArray(upstream)) {
upstream.forEach(validateUpstream);
} else {
validateUpstream(upstream);
}
const upstreamArray = Array.isArray(upstream) ? upstream : [upstream];
if (loadBalancing === undefined) {
context.upstream = randomHandler(upstreamArray, request);
await next();
return;
}
const policy = loadBalancing.policy || 'random';
const policyHandler = handlersMap[policy];
context.upstream = policyHandler(upstreamArray, request);
await next();
};