Skip to content

Commit

Permalink
feat(Worker): Supporting most CF Headers (#22)
Browse files Browse the repository at this point in the history
Closes #21
  • Loading branch information
gja authored Dec 18, 2018
1 parent 752c979 commit 99e5db7
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 38 deletions.
10 changes: 10 additions & 0 deletions app/__tests__/server_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,14 @@ describe("server", () => {
.expect(200, "goodbye")
.then(() => done());
});

it("passes the current ip onwards", done => {
const app = createApp(
'addEventListener("fetch", (e) => e.respondWith(new Response(e.request.headers.get("X-Forwarded-For"))))'
);
supertest(app)
.get("/some-route")
.expect(200, "127.0.0.1")
.then(() => done());
});
});
88 changes: 58 additions & 30 deletions app/__tests__/worker_spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const express = require("express");
const { Worker } = require("../worker");
const { InMemoryKVStore } = require("../in-memory-kv-store");
const { Headers } = require("node-fetch");

describe("Workers", () => {
test("It Can Create and Execute a Listener", () => {
Expand All @@ -9,9 +10,9 @@ describe("Workers", () => {
});

describe("Ensuring Things are in scope", () => {
test('It has self global', () => {
const worker = new Worker('foo.com', `addEventListener('test', () => self)`);
const self = worker.triggerEvent('test');
test("It has self global", () => {
const worker = new Worker("foo.com", `addEventListener('test', () => self)`);
const self = worker.triggerEvent("test");
expect(self).toBeDefined();
});

Expand All @@ -36,37 +37,34 @@ describe("Workers", () => {
expect(url.searchParams.get("foo")).toBe("bar");
});

test('It has support for URLSearchParams', () => {
const worker = new Worker(
'foo.com',
`addEventListener('test', () => new URLSearchParams({ foo: 'bar' }))`
);
const params = worker.triggerEvent('test');
expect(params.has('foo')).toBe(true);
expect(params.get('foo')).toBe('bar');
expect(params.has('baz')).toBe(false);
expect(params.get('baz')).toBe(null);
test("It has support for URLSearchParams", () => {
const worker = new Worker("foo.com", `addEventListener('test', () => new URLSearchParams({ foo: 'bar' }))`);
const params = worker.triggerEvent("test");
expect(params.has("foo")).toBe(true);
expect(params.get("foo")).toBe("bar");
expect(params.has("baz")).toBe(false);
expect(params.get("baz")).toBe(null);
});

test('It has support for base64 encoding APIs', () => {
test("It has support for base64 encoding APIs", () => {
const worker = new Worker(
'foo.com',
"foo.com",
`addEventListener('test', () => ({ encoded: btoa('test'), decoded: atob('dGVzdA==') }))`
);
const { encoded, decoded } = worker.triggerEvent('test');
expect(encoded).toBe('dGVzdA==');
expect(decoded).toBe('test')
const { encoded, decoded } = worker.triggerEvent("test");
expect(encoded).toBe("dGVzdA==");
expect(decoded).toBe("test");
});

test('It has support for crypto and Text encoding APIs', async () => {
test("It has support for crypto and Text encoding APIs", async () => {
const worker = new Worker(
'foo.com',
"foo.com",
`addEventListener('test', async () => {
const password = 'test';
const plainText = 'foo';
const ptUtf8 = new TextEncoder().encode(plainText);
const pwUtf8 = new TextEncoder().encode(password);
const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8);
const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8);
const iv = crypto.getRandomValues(new Uint8Array(12));
const alg = { name: 'AES-GCM', iv: iv };
const encKey = await crypto.subtle.importKey('raw', pwHash, alg, false, ['encrypt']);
Expand All @@ -77,18 +75,15 @@ describe("Workers", () => {
return plainText === plainText2;
})`
);
const decrypted = await worker.triggerEvent('test');
const decrypted = await worker.triggerEvent("test");
expect(decrypted).toBe(true);
});

test('It has support for the console API', () => {
const worker = new Worker(
'foo.com',
`addEventListener('test', () => console.log('test'))`
);
const spy = jest.spyOn(console, 'log');
worker.triggerEvent('test');
expect(spy).toHaveBeenCalledWith('test');
test("It has support for the console API", () => {
const worker = new Worker("foo.com", `addEventListener('test', () => console.log('test'))`);
const spy = jest.spyOn(console, "log");
worker.triggerEvent("test");
expect(spy).toHaveBeenCalledWith("test");
});
});

Expand All @@ -99,6 +94,39 @@ describe("Workers", () => {
expect(await response.text()).toBe("hello");
});

describe("Cloudflare Headers", () => {
it("Adds cloudflare headers", async () => {
const worker = new Worker(
"foo.com",
'addEventListener("fetch", (e) => e.respondWith(new Response("hello", {headers: e.request.headers})))'
);
const response = await worker.executeFetchEvent("http://foo.com");
expect(response.headers.get("CF-Ray")).toBe("0000000000000000");
expect(response.headers.get("CF-Visitor")).toBe('{"scheme":"http"}');
expect(response.headers.get("CF-IPCountry")).toBe("DEV");
expect(response.headers.get("CF-Connecting-IP")).toBe("127.0.0.1");
expect(response.headers.get("X-Real-IP")).toBe("127.0.0.1");

expect(response.headers.get("X-Forwarded-For")).toBe("127.0.0.1");
expect(response.headers.get("X-Forwarded-Proto")).toBe("http");
});

it("correctly appends to X-Forwarded-*", async () => {
const worker = new Worker(
"foo.com",
'addEventListener("fetch", (e) => e.respondWith(new Response("hello", {headers: e.request.headers})))'
);
const response = await worker.executeFetchEvent("http://foo.com", {
headers: new Headers({
"X-Forwarded-For": "8.8.8.8",
"X-Forwarded-Proto": "https"
})
});
expect(response.headers.get("X-Forwarded-For")).toBe("8.8.8.8, 127.0.0.1");
expect(response.headers.get("X-Forwarded-Proto")).toBe("https, http");
});
});

describe("Fetch Behavior", () => {
let upstreamServer;
let upstreamHost;
Expand Down
3 changes: 2 additions & 1 deletion app/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ async function callWorker(worker, req, res) {
const response = await worker.executeFetchEvent(url, {
headers: req.headers,
method: req.method,
body: ["GET", "HEAD"].includes(req.method) ? undefined : req.body
body: ["GET", "HEAD"].includes(req.method) ? undefined : req.body,
ip: req.connection.remoteAddress.split(":").pop()
});
const data = await response.arrayBuffer();

Expand Down
35 changes: 28 additions & 7 deletions app/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,33 @@ function buildKVStores(kvStoreFactory, kvStores) {
}, {});
}

function chomp(str) {
return str.substr(0, str.length - 1);
}

function buildRequest(url, opts) {
const { country = "DEV", ip = "127.0.0.1", ray = "0000000000000000", ...requestOpts } = opts;
const request = new Request(url, { redirect: "manual", ...requestOpts });
const headers = request.headers;
const parsedURL = new URL(request.url);

// CF Specific Headers
headers.set("CF-Ray", ray);
headers.set("CF-Visitor", JSON.stringify({ scheme: chomp(parsedURL.protocol) }));
headers.set("CF-IPCountry", country);
headers.set("CF-Connecting-IP", ip);
headers.set("X-Real-IP", ip);

// General Proxy Headers
headers.append("X-Forwarded-For", ip);
headers.append("X-Forwarded-Proto", chomp(parsedURL.protocol));

return new Request(request, { headers });
}

class Worker {
constructor(
origin,
workerContents,
{ upstreamHost, kvStores = [], kvStoreFactory = require("./in-memory-kv-store") } = {}
) {
constructor(origin, workerContents, opts = {}) {
const { upstreamHost, kvStores = [], kvStoreFactory = require("./in-memory-kv-store") } = opts;
this.listeners = {
fetch: e => e.respondWith(this.fetchUpstream(e.request))
};
Expand Down Expand Up @@ -71,12 +92,12 @@ class Worker {
return fetch(request);
}

async executeFetchEvent(url, opts) {
async executeFetchEvent(url, opts = {}) {
let responsePromise = null;
let waitUntil = [];
this.triggerEvent("fetch", {
type: "fetch",
request: new Request(url, { redirect: "manual", ...opts }),
request: buildRequest(url, opts),
respondWith: r => (responsePromise = r),
waitUntil: e => waitUntil.push(e)
});
Expand Down

0 comments on commit 99e5db7

Please sign in to comment.