{"id":15715730,"url":"https://github.com/digitaloptimizationgroup/cloudflare-edge-proxy","last_synced_at":"2025-04-19T12:02:22.838Z","repository":{"id":57200751,"uuid":"161835266","full_name":"DigitalOptimizationGroup/cloudflare-edge-proxy","owner":"DigitalOptimizationGroup","description":"A Cloudflare worker script used to enable a/b testing, canary releasing, gatekeeping, and SEO a/b/n testing.","archived":false,"fork":false,"pushed_at":"2023-01-03T15:38:45.000Z","size":377,"stargazers_count":75,"open_issues_count":5,"forks_count":8,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-26T20:49:31.424Z","etag":null,"topics":["abtesting","canary-release","cloudflare-worker","edge-proxy","gatekeeping","seo-abtesting"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DigitalOptimizationGroup.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-12-14T20:08:53.000Z","updated_at":"2025-02-14T18:29:09.000Z","dependencies_parsed_at":"2023-02-01T07:16:02.426Z","dependency_job_id":null,"html_url":"https://github.com/DigitalOptimizationGroup/cloudflare-edge-proxy","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DigitalOptimizationGroup%2Fcloudflare-edge-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DigitalOptimizationGroup%2Fcloudflare-edge-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DigitalOptimizationGroup%2Fcloudflare-edge-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DigitalOptimizationGroup%2Fcloudflare-edge-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DigitalOptimizationGroup","download_url":"https://codeload.github.com/DigitalOptimizationGroup/cloudflare-edge-proxy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246385451,"owners_count":20768668,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abtesting","canary-release","cloudflare-worker","edge-proxy","gatekeeping","seo-abtesting"],"created_at":"2024-10-03T21:42:35.187Z","updated_at":"2025-03-30T21:31:48.621Z","avatar_url":"https://github.com/DigitalOptimizationGroup.png","language":"JavaScript","readme":"### Cloudflare Edge Proxy\n\nA Cloudflare worker script used to enable a/b testing, canary releasing, gatekeeping, and SEO a/b/n testing.\n\n### Features\n\n-   A/B/n testing across multiple backends, even running across multiple cloud providers\n-   Canary releasing with gradual traffic migration\n-   Dynamic Gatekeeping\n-   SEO A/B testing\n\n### Usage\n\n`npm install --save cloudflare-edge-proxy`\n\nDeploy as a Cloudflare worker function: https://developers.cloudflare.com/workers/about/\n\n#### A/B/N Testing\n\nAll assignments are done deterministically, by hashing a salt and visitor Id. On every visit the proxy creates a unique request id (using `uuid/v4`) and forwards this to the origin as a `request-id` header. On first visits the proxy will use this id as the assignment id. To assure consistent hashing this MUST be set as a `_vq` cookie. On the first request the value of this cookie should be set from the `request-id` header and on subsequent visits it should come from the `_vq` cookie (not from the request-id header). For example, in an `express` app, that might be done as follows:\n\n```js\n// set or reset the visitor ID cookie\nres.cookie(\"_vq\", req.cookies[\"_vq\"] || req.headers[\"request-id\"], {\n    maxAge: 3600 * 1000 * 24 * 365\n});\n```\n\nYou can optionally have the proxy setup this cookie for you by passing an additional config param: `{setCookie: true}`\n\nThe worker script:\n\n```js\nimport cloudflareEdgeProxy from \"cloudflare-edge-proxy\";\n\nconst config = {\n    defaultBackend: \"https://a.com\",\n    abtest: true,\n    origins: [\n        { url: \"https://a.com\" },\n        { url: \"https://b.com\" },\n        { url: \"https://c.com\" }\n    ],\n    salt: \"test-abc-123\",\n    setCookie: true // default is false, if true proxy will set _vq cookie\n};\n\nconst proxy = cloudflareEdgeProxy(config);\n\naddEventListener(\"fetch\", event =\u003e {\n    event.respondWith(proxy(event));\n});\n```\n\n#### Canary Releasing Example\n\nCanary releasing can be used to gradually shift traffic from one backend to another. It should ONLY be used with two backends, (unlike a/b/n testing), so that users do not get reassigned as the traffic percentage is increased. An example config is shown below. To assure consistent assignment, for visitors, the `weight` parameter should only be increased.\n\n```js\nimport cloudflareEdgeProxy from \"cloudflare-edge-proxy\";\n\nconst config = {\n    canary: true,\n    weight: 50, // 0-100\n    canaryBackend: \"https://canary-backend.com\",\n    defaultBackend: \"https://default-backend.com\",\n    salt: \"canary-abc-123\",\n    setCookie: true // default is false, if true proxy will set _vq cookie\n};\n\nconst proxy = cloudflareEdgeProxy(config);\n\naddEventListener(\"fetch\", event =\u003e {\n    event.respondWith(proxy(event));\n});\n```\n\n#### Gatekeeping\n\nTo enable gatekeeping, you must pass a `JWT_SECRET_KEY` with the config.\n\n```js\nimport cloudflareEdgeProxy from \"cloudflare-edge-proxy\";\n\nconst config = {\n    JWT_SECRET_KEY: process.env[\"JWT_SECRET_KEY\"],\n    setGatekeepingCookie: true // default is false, if true will set a 1 day cookie\n    /* ... */\n};\n\nconst proxy = cloudflareEdgeProxy(config);\n\naddEventListener(\"fetch\", event =\u003e {\n    event.respondWith(proxy(event));\n});\n```\n\nYou can then encode desired backends into a JWT with your secret and then access your root domain with the JWT set as a query parameter `?devtoken=JWT` or as a cookie `devtoken=JWT`. This allows for ANY backend to be accessed through the proxy and can be useful for developing / testing, on your production domain, without having to update the proxy to add development backends.\n\nAn example of creating such a token is shown below.\n\n```js\nvar jwttoken = require(\"jsonwebtoken\");\n\nconst devtoken = jwttoken.sign(\n    {\n        {\n            url: \"https://example.com\"\n        }\n    },\n    process.env[\"JWT_SECRET_KEY\"]\n);\n```\n\n#### Search Engine Optimization - A/B Testing Example\n\nSearch engine optimization a/b testing is a technique used to validate changes that may impact search rankings. With a/b/n testing, as implemented in this proxy, a unique `visitor id` is used to hash to a backend. With SEO based a/b/n testing, the full `path` of each individual request is hashed and used to select the backend. After completion of the experiment period, traffic volumes between the two, or more, SEO implementation are compared for statistical significance.\n\nWhen running an a/b test, on natural search traffic, it would be wise to validate that your website has enough ranked pages such that a random split of urls results in a generally equal split of natural search traffic (pre-test).\n\nExample config.\n\n```js\nimport cloudflareEdgeProxy from \"cloudflare-edge-proxy\";\n\nconst config = {\n    SEOTest: true,\n    origins: [\n        { url: \"https://a.com\" },\n        { url: \"https://b.com\" },\n        { url: \"https://c.com\" }\n    ],\n    salt: \"seo-test-abc-123\"\n};\n\nconst proxy = cloudflareEdgeProxy(config);\n\naddEventListener(\"fetch\", event =\u003e {\n    event.respondWith(proxy(event));\n});\n```\n\n#### Default Backend (no testing)\n\n```js\nimport cloudflareEdgeProxy from \"cloudflare-edge-proxy\";\n\nconst config = {\n    defaultBackend: \"https://example.com\"\n};\n\nconst proxy = cloudflareEdgeProxy(config);\n\naddEventListener(\"fetch\", event =\u003e {\n    event.respondWith(proxy(event));\n});\n```\n\n#### Echoer\n\nTo enable the echoer, you must pass an `ECHO_TOKEN` with the config. You may then access `/echo?echotoken=ECHO_TOKEN` and you will get the `request` object returned as formatted JSON. Useful for debugging.\n\n```js\nimport cloudflareEdgeProxy from \"cloudflare-edge-proxy\";\n\nconst config = {\n    ECHO_TOKEN: process.env[\"ECHO_TOKEN\"]\n    /* ... */\n};\n\nconst proxy = cloudflareEdgeProxy(config);\n\naddEventListener(\"fetch\", event =\u003e {\n    event.respondWith(proxy(event));\n});\n```\n\n### Subtle differences of a/b testing, canary releasing, and seo a/b testing\n\nThis proxy utilizes hashing to make deterministic (and random) assignments. This is done by hashing an assignment string to a given number of assignment choices. The difference in these three modes lies in this assignment strategy.\n\n#### a/b testing\n\nA/B/n testing creates a hashing string by combining a `salt` and the `userId`. This generates a unique hashing string for each user and provides consistent (and stateless) assignment for that given hashing string. It can be used to a/b/n test any number of backends.\n\n#### canary releasing\n\nThe goal of canary releasing is to gradually shift traffic from the default backend to a new `canary` backend. The potential trouble here can be that when increasing the percent of traffic going to the new backend we must assure that users already shifted to the new backend remain there. To do this we again create a hash string from a `salt` and the `userId`. However, this time we use modulo 100 to hash each user to a consistent number between 0 and 99. To move traffic we pick the percent we want, for example 5%. So for all users assigned a number \u003c 5 we show them the canary release. If we then increase to 30% we show all users \u003c 30 the canary, obviously this logic continues to include all prior users shown the canary. In this way you can gradually shift users from the default to the canary backend without causing already shifted users to lose their assignment.\n\n#### seo a/b testing\n\nWith SEO a/b testing we are testing the impact of changes to our site on traffic volume from the search engines. To accomplish this we use a `salt` and the `pathname` to hash a given url to a given backend. In this way we randomize among our urls as opposed to our users. Any given path will always be consistently hashed to the same backend for the duration of the testing period.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigitaloptimizationgroup%2Fcloudflare-edge-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdigitaloptimizationgroup%2Fcloudflare-edge-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigitaloptimizationgroup%2Fcloudflare-edge-proxy/lists"}