Lightweight and scalable reverse proxy and load balancing library built for Cloudflare Workers
🚀 Reflare is a lightweight and scalable reverse proxy and load balancing library built for Cloudflare Workers. It sits in front of web servers (e.g. web application, storage platform, or RESTful API), forwards HTTP requests or WebSocket traffics from clients to upstream servers, and transforms responses with several optimizations to improve page loading time.
reflare-template
Install wrangler
CLI and authorize wrangler
with a Cloudflare account.
npm install -g wrangler
wrangler login
Generate a new project from reflare-template and install the dependencies.
npm init cloudflare reflare-app https://github.com/xiaoyang-sde/reflare-template
cd reflare-app
npm install
Edit or add route definitions in src/index.ts
. Please read the examples and route definition section below for more details.
npm run dev
to preview Reflare with local development server provided by Miniflare.npm run deploy
to publish Reflare on Cloudflare Workers.Install the reflare
package.
npm install reflare
Import useReflare
from reflare
. useReflare
accepts an object of options.
provider
: The location of the list of route definitions. (optional, defaults to static
)
static
: Reflare loads the route definitions from routeList
.kv
: Reflare loads the route definitions from Workers KV. (Experimental)routeList
: The initial list of route definitions. (optional, defaults to []
, ignored if provider
is not static
)namespace
: The Workers KV namespace that stores the list of route definitions. (required if provider
is kv
)useReflare
returns an object with the handle
method and push
method.
handle
method takes the inbound Request to the Worker and returns the Response fetched from the upstream service.push
method takes a route and appends it to routeList
.import useReflare from 'reflare';
const handleRequest = async (
request: Request,
): Promise<Response> => {
const reflare = await useReflare();
reflare.push({
path: '/*',
upstream: {
domain: 'httpbin.org',
protocol: 'https',
},
});
return reflare.handle(request);
};
addEventListener('fetch', (event) => {
event.respondWith(handleRequest(event.request));
});
Edit the route definition to change the behavior of Reflare. For example, the route definition below lets Reflare add the Access-Control-Allow-Origin: *
header to each response from the upstream service.
{
path: '/*',
upstream: {
domain: 'httpbin.org',
protocol: 'https',
},
cors: {
origin: '*',
},
}
Set up a reverse proxy for MDN Web Docs:
{
path: '/*',
upstream: {
domain: 'developer.mozilla.org',
protocol: 'https',
},
}
Reflare could proxy WebSocket traffic to upstream services. Set up a reverse proxy for wss://echo.websocket.org:
{
path: '/*',
upstream: {
domain: 'echo.websocket.org',
protocol: 'https',
},
}
Reflare could set custom headers to the request and response. Set up a reverse proxy for https://example.s3.amazonaws.com:
{
path: '/*',
upstream: {
domain: 'example.s3.amazonaws.com',
protocol: 'https',
},
headers: {
response: {
'x-response-header': 'Hello from Reflare',
},
},
cors: {
origin: ['https://www.example.com'],
methods: ['GET', 'POST'],
credentials: true,
},
}
Reflare implements express-like route matching. Reflare matches the path and HTTP method of each incoming request with the list of route definitions and forwards the request to the first matched route.
path
(string | string[]
): The path or the list of paths that matches the routemethods
(string[]
): The list of HTTP methods that match the route// Matches all requests
reflare.push({
path: '/*',
/* ... */
});
// Matches GET and POST requests with path `/api`
reflare.push({
path: '/api',
methods: ['GET', 'POST'],
});
// Matches GET requests with path ending with `.json` or `.yaml` in `/data`
reflare.push({
path: ['/data/*.json', '/data/*.yaml'],
methods: ['GET'],
});
domain
(string
): The domain name of the upstream serverprotocol
(string
): The protocol scheme of the upstream server (optional, defaults to 'https'
)port
(number
): The port of the upstream server (optional, defaults to 80
or 443
based on protocol
)timeout
(number
): The maximum wait time on a request to the upstream server (optional, defaults to 10000
)weight
(number
): The weight of the server that will be accounted for as part of the load balancing decision (optional, defaults to 1
)onRequest(request: Request, url: string)
: The callback function that will be called before sending the request to upstreamonResponse(response: Response, url: string)
: The callback function that will be called after receiving the response from upstreamreflare.push({
path: '/*',
upstream: {
domain: 'httpbin.org',
protocol: 'https',
port: 443,
timeout: 10000,
weight: 1,
},
/* ... */
});
The onRequest
and onResponse
fields accept callback functions that could change the content of the request or response. For example, the following example replaces the URL of the request and sets the cache-control
header of the response based on its URL. These fields accept either a standalone function or an array of functions that will be executed sequentially.
reflare.push({
path: '/*',
upstream: {
domain: 'httpbin.org',
protocol: 'https',
port: 443,
timeout: 10000,
weight: 1,
onRequest: (request: Request, url: string): Request => {
// Modifies the URL of the request
return new Request(url.replace('/original/request/path', ''), request);
},
onResponse: (response: Response, url: string): Response => {
// If the URL ends with `.html` or `/`, sets the `cache-control` header
if (url.endsWith('.html') || url.endsWith('/')) {
response.headers.set('cache-control', 'public, max-age=240, s-maxage=60');
}
return response;
}
},
/* ... */
});
To load balance HTTP traffic to a group of servers, pass an array of server configurations to upstream
. The load balancer will forward the request to an upstream server based on the loadBalancing.policy
option.
random
: The load balancer will select a random upstream server from the server group. The optional weight
parameter in the server configuration could influence the load balancing algorithm.ip-hash
: The client's IP address is used as a hashing key to select the upstream server from the server group. It ensures that the requests from the same client will always be directed to the same server.reflare.push({
path: '/*',
loadBalancing: {
policy: 'random',
},
upstream: [
{
domain: 's1.example.com',
protocol: 'https',
weight: 20,
},
{
domain: 's2.example.com',
protocol: 'https',
weight: 30,
},
{
domain: 's3.example.com',
protocol: 'https',
weight: 50,
},
],
/* ... */
});
Each incoming request is inspected against the firewall rules defined in the firewall
property of the options object. The request will be blocked if it matches at least one firewall rule.
field
: The property of the incoming request to be inspected
asn
: The ASN number of the incoming request (number
)ip
: The IP address of the incoming request, e.g. 1.1.1.1
(string
)hostname
: The content of the host
header, e.g. github.com
(string | undefined
)user-agent
: The content of the user-agent
header, e.g. Mozilla/5.0
(string | undefined
)country
: The two-letter country code in the request, e.g. US
(string | undefined
)continent
: The continent of the incoming request, e.g. NA
(string | undefined
)value
(string | string[] | number | number[] | RegExp
): The value of the firewall ruleoperator
: The operator to be used to determine if the request is blocked
equal
: Block the request if field
is equal to value
not equal
: Block the request if field
is not equal to value
match
: Block the request if value
matches field
(Expect field
to be string
and value
to be RegExp
)not match
: Block the request if value
doesn't match field
(Expect field
to be string
and value
to be RegExp
)in
: Block the request if field
is in value
(Expect value
to be Array
)not in
: Block the request if field
is not in value
(Expect value
to be Array
)contain
: Block the request if field
contains value
(Expect field
and value
to be string
)not contain
: Block the request if field
doesn't contain value
(Expect field
and value
to be string
)greater
: Block the request if field
is greater than value
(Expect field
and value
to be number
)less
: Block the request if field
is less than value
(Expect field
and value
to be number
)reflare.push('/', {
path: '/*',
/* ... */
firewall: [
{
field: 'ip',
operator: 'in',
value: ['1.1.1.1', '1.0.0.1'],
},
{
field: 'user-agent',
operator: 'match',
value: /Chrome/,
}
],
});
request
(Record<string, string>
): Sets request header going upstream to the backend. Accepts an object. (optional, defaults to {}
)response
(Record<string, string>
): Sets response header coming downstream to the client. Accepts an object. (optional, defaults to {}
)reflare.push({
path: '/*',
/* ... */
headers: {
request: {
'x-example-header': 'hello server',
},
response: {
'x-example-header': 'hello client',
},
},
});
origin
: Configures the Access-Control-Allow-Origin
CORS header. (optional, defaults to false
)
boolean
: set to true
to reflect the origin of the request, or set to false
to disable CORS.string[]
: an array of acceptable origins.*
: allow any origin to access the resource.methods
(string[]
): Configures the Access-Control-Allow-Methods
CORS header. Expect an array of valid HTTP methods or *
. (optional, defaults to reflecting the method specified in the request’s Access-Control-Request-Method
header)
allowedHeaders
(string[]
): Configures the Access-Control-Allow-Headers
CORS header. Expect an array of HTTP headers or *. (optional, defaults to reflecting the headers specified in the request’s Access-Control-Request-Headers
header.)
exposedHeaders
(string[]
): Configures the Access-Control-Expose-Headers
CORS header. Expect an array of HTTP headers or *
. (optional, defaults to []
)
credentials
(boolean
): Configures the Access-Control-Allow-Credentials
CORS header. Set to true to pass the header, or it is omitted. (optional, defaults to false
)
maxAge
(number
): Configures the Access-Control-Max-Age
CORS header. Set to an integer to pass the header, or it is omitted. (optional)
reflare.push({
path: '/*',
/* ... */
cors: {
origin: true,
methods: [
'GET',
'POST',
],
allowedHeaders: [
'Example-Header',
],
exposedHeaders: [
'Example-Header',
],
credentials: true,
maxAge: 86400,
},
});
Cloudflare Workers provides several optimizations by default.
Reflare could load the route definitions from Workers KV. Set the provider
to kv
and namespace
to a Workers KV namespace (e.g. REFLARE
) that binds to the current Worker. Reflare fetches the route definitions from namespace
and handles each incoming request with the latest route definitions.
import useReflare from 'reflare';
declare const REFLARE: KVNamespace;
const handleRequest = async (
request: Request,
): Promise<Response> => {
const reflare = await useReflare({
provider: 'kv',
namespace: REFLARE,
});
return reflare.handle(request);
};
addEventListener('fetch', (event) => {
event.respondWith(handleRequest(event.request));
});
The route definitions should be stored as a JSON array in the route-list
key of namespace
. The KV namespace could be modified with wrangler
or Cloudflare API.
wrangler kv:key put --binding=[namespace] 'route-list' '[{"path":"/*","upstream":{"domain":"httpbin.org","protocol":"https"}}]'