Repository: cloudflare/worker-speedtest-template
Branch: master
Commit: d9b0c5535594
Files: 12
Total size: 11.5 KB
Directory structure:
gitextract_n7fvj002/
├── .github/
│ └── workflows/
│ └── semgrep.yml
├── .gitignore
├── .prettierrc
├── README.md
├── index.js
├── package.json
├── src/
│ ├── down.js
│ ├── index.js
│ ├── router.js
│ ├── test.js
│ └── up.js
└── wrangler.toml
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/semgrep.yml
================================================
on:
pull_request: {}
workflow_dispatch: {}
push:
branches:
- main
- master
schedule:
- cron: '0 0 * * *'
name: Semgrep config
jobs:
semgrep:
name: semgrep/ci
runs-on: ubuntu-20.04
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
SEMGREP_URL: https://cloudflare.semgrep.dev
SEMGREP_APP_URL: https://cloudflare.semgrep.dev
SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version
container:
image: returntocorp/semgrep
steps:
- uses: actions/checkout@v3
- run: semgrep ci
================================================
FILE: .gitignore
================================================
node_modules/*
================================================
FILE: .prettierrc
================================================
{
"trailingComma": "none",
"singleQuote": true
}
================================================
FILE: README.md
================================================
## Speed Test
Worker for measuring download / upload connection speed from the client side, using the [Performance Timing API](https://w3c.github.io/perf-timing-primer/).
### Installation
[`index.js`](https://github.com/cloudflare/worker-speedtest-template/blob/master/router.js) is the content of the Workers script.
_Note:_ when running this as your own worker, your latency measurements may differ a small amount from the [official version](https://speed.cloudflare.com). This is due to the fact that we rely on an internal mechanism to determine the amount of server processing time, which is then subtracted from the measurement.
#### Wrangler
You can use [wrangler](https://github.com/cloudflare/wrangler) to generate a new Cloudflare Workers project based on this template by running the following command from your terminal:
```
wrangler generate myApp https://github.com/cloudflare/worker-speedtest-template
```
Before publishing your code you need to edit `wrangler.toml` file and add your Cloudflare `account_id` - more information about publishing your code can be found [in the documentation](https://workers.cloudflare.com/docs/quickstart/configuring-and-publishing/).
Once you are ready, you can publish your code by running the following command:
```
wrangler publish
```
#### Serverless
To deploy using serverless add a [`serverless.yml`](https://serverless.com/framework/docs/providers/cloudflare/) file.
### API Reference
This worker exposes two endpoints designed to support the measuring of bandwidth and latency from the client side.
#### Download
**GET** `/down` Request binary content of a certain size
| Param | Description | Required | Default |
| ----- | -------------------------------------- | :------: | :-----: |
| bytes | The size of the response body in bytes | no | 0 |
Example: `/down?bytes=10000`
#### Upload
**POST** `/up` Receive content posted to the server
The content is discarded by the endpoint. A response is sent once all the content has been received from the client.
No query string parameters.
================================================
FILE: index.js
================================================
const handleRequest = require('./src');
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
================================================
FILE: package.json
================================================
{
"name": "speedtest-worker",
"private": true,
"version": "1.0.0",
"description": "Worker for testing connection speed",
"author": "Vasco Asturiano <vasco@cloudflare.com>",
"main": "index.js",
"scripts": {
"build": "wrangler build",
"test": "NODE_ENV=test jest --verbose --no-cache",
"test-dev": "NODE_ENV=test jest --watch --no-cache",
"format": "prettier --write '**/*.{js,css,json,md}'"
},
"husky": {
"hooks": {
"pre-commit": "yarn format",
"pre-push": "yarn test && yarn format"
}
},
"dependencies": {},
"devDependencies": {
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.5",
"@babel/runtime": "^7.4.5",
"@dollarshaveclub/cloudworker": "^0.0.11",
"husky": "^2.4.1",
"jest": "^24.8.0",
"prettier": "^1.18.2",
"request": "^2.88.0"
}
}
================================================
FILE: src/down.js
================================================
const DEFAULT_NUM_BYTES = 0;
const MAX_BYTES = 1e8;
const getQs = url => {
const sp = url.split('?');
if (sp.length < 2) {
return {}; // no qs
}
const qs = sp[1];
return Object.assign(
{},
...qs.split('&').map(s => {
const sp = s.split('=');
if (sp.length !== 2) {
return {};
}
return { [sp[0]]: sp[1] };
})
);
};
const genContent = (numBytes = 0) => '0'.repeat(Math.max(0, numBytes));
async function handleRequest(request) {
const reqTime = new Date();
const qs = getQs(request.url);
const numBytes = qs.hasOwnProperty('bytes')
? Math.min(MAX_BYTES, Math.abs(+qs.bytes))
: DEFAULT_NUM_BYTES;
const res = new Response(genContent(numBytes));
res.headers.set('access-control-allow-origin', '*');
res.headers.set('timing-allow-origin', '*');
res.headers.set('cache-control', 'no-store');
res.headers.set('content-type', 'application/octet-stream');
request.cf &&
request.cf.colo &&
res.headers.set('cf-meta-colo', request.cf.colo);
res.headers.set('cf-meta-request-time', +reqTime);
res.headers.set(
'access-control-expose-headers',
'cf-meta-colo, cf-meta-request-time'
);
return res;
}
module.exports = handleRequest;
================================================
FILE: src/index.js
================================================
const Router = require('./router');
const downHandler = require('./down');
const upHandler = require('./up');
async function handleRequest(request) {
const r = new Router();
r.get('.*/down', downHandler);
r.post('.*/up', upHandler);
return await r.route(request);
}
module.exports = handleRequest;
================================================
FILE: src/router.js
================================================
/**
* Helper functions that when passed a request will return a
* boolean indicating if the request uses that HTTP method,
* header, host or referrer.
*/
const Method = method => req =>
req.method.toLowerCase() === method.toLowerCase();
const Connect = Method('connect');
const Delete = Method('delete');
const Get = Method('get');
const Head = Method('head');
const Options = Method('options');
const Patch = Method('patch');
const Post = Method('post');
const Put = Method('put');
const Trace = Method('trace');
const Header = (header, val) => req => req.headers.get(header) === val;
const Host = host => Header('host', host.toLowerCase());
const Referrer = host => Header('referrer', host.toLowerCase());
const Path = regExp => req => {
const url = new URL(req.url);
const path = url.pathname;
const match = path.match(regExp) || [];
return match[0] === path;
};
/**
* The Router handles determines which handler is matched given the
* conditions present for each request.
*/
class Router {
constructor() {
this.routes = [];
}
handle(conditions, handler) {
this.routes.push({
conditions,
handler
});
return this;
}
connect(url, handler) {
return this.handle([Connect, Path(url)], handler);
}
delete(url, handler) {
return this.handle([Delete, Path(url)], handler);
}
get(url, handler) {
return this.handle([Get, Path(url)], handler);
}
head(url, handler) {
return this.handle([Head, Path(url)], handler);
}
options(url, handler) {
return this.handle([Options, Path(url)], handler);
}
patch(url, handler) {
return this.handle([Patch, Path(url)], handler);
}
post(url, handler) {
return this.handle([Post, Path(url)], handler);
}
put(url, handler) {
return this.handle([Put, Path(url)], handler);
}
trace(url, handler) {
return this.handle([Trace, Path(url)], handler);
}
all(handler) {
return this.handle([], handler);
}
route(req) {
const route = this.resolve(req);
if (route) {
return route.handler(req);
}
return new Response('resource not found', {
status: 404,
statusText: 'not found',
headers: {
'content-type': 'text/plain'
}
});
}
/**
* resolve returns the matching route for a request that returns
* true for all conditions (if any).
*/
resolve(req) {
return this.routes.find(r => {
if (!r.conditions || (Array.isArray(r) && !r.conditions.length)) {
return true;
}
if (typeof r.conditions === 'function') {
return r.conditions(req);
}
return r.conditions.every(c => c(req));
});
}
}
module.exports = Router;
================================================
FILE: src/test.js
================================================
const fetch = require('@dollarshaveclub/node-fetch');
const Request = fetch.Request;
const Response = fetch.Response;
const handleRequest = require('./index');
beforeAll(async () => {
Object.assign(global, { Response });
});
describe('speedtest-down', () => {
const getResponse = async numBytes => {
const url = new URL(
`https://someurl.com/down${
numBytes !== undefined ? `?bytes=${numBytes}` : ''
}`
);
return await handleRequest(new Request(url));
};
const getContent = async (...params) =>
await (await getResponse(...params)).text();
test('default bytes', async () => {
const content = await getContent();
expect(content.length).toBeLessThan(100);
});
describe('low request bytes', () => {
[0, 1, 10, 50, 99].forEach(bytes => {
test(`get ${bytes} bytes`, async () => {
const content = await getContent(bytes);
expect(content.length).toBeLessThan(100);
});
});
});
describe('request bytes', () => {
[100, 1e3, 1e6, 1e7].forEach(bytes => {
test(`get ${bytes} bytes`, async () => {
const content = await getContent(bytes);
expect(content.length).toBe(bytes);
});
});
});
test('max bytes', async () => {
const content = await getContent(Infinity);
expect(content.length).toBe(1e8);
});
test('negative bytes', async () => {
const content = await getContent(-100);
expect(content.length).toBe(100);
});
test('includes request time', async () => {
const headers = (await getResponse()).headers;
const reqTime = headers.get('cf-meta-request-time');
expect(reqTime).toBeDefined();
expect(+reqTime).toBeLessThanOrEqual(+new Date());
expect(+reqTime).toBeGreaterThan(+new Date() - 60 * 1000);
});
});
describe('speedtest-up', () => {
const getResponse = async ({
numBytes = 0,
method = 'POST',
...other
} = {}) => {
const config = { method, ...other };
if (method === 'POST') {
config.body = '0'.repeat(numBytes);
config.headers = { 'content-length': numBytes };
}
const req = new Request('https://someurl.com/up', config);
return await handleRequest(req);
};
const getContent = async (...params) =>
await (await getResponse(...params)).text();
test('get request', async () => {
const content = await getContent({ method: 'GET' });
expect(content.length).toBeGreaterThan(0);
});
test('empty post request', async () => {
const content = await getContent({ numBytes: 0 });
expect(content.length).toBeGreaterThan(0);
});
test('small post request', async () => {
const content = await getContent({ numBytes: 10 });
expect(content.length).toBeGreaterThan(0);
});
test('large post request', async () => {
const content = await getContent({ numBytes: 1e8 });
expect(content.length).toBeGreaterThan(0);
});
test('includes request time', async () => {
const headers = (await getResponse()).headers;
const reqTime = headers.get('cf-meta-request-time');
expect(reqTime).toBeDefined();
expect(+reqTime).toBeLessThanOrEqual(+new Date());
expect(+reqTime).toBeGreaterThan(+new Date() - 60 * 1000);
});
});
================================================
FILE: src/up.js
================================================
async function handleRequest(request) {
const reqTime = new Date();
const res = new Response('ok');
res.headers.set('access-control-allow-origin', '*');
res.headers.set('timing-allow-origin', '*');
request.cf &&
request.cf.colo &&
res.headers.set('cf-meta-colo', request.cf.colo);
res.headers.set('cf-meta-request-time', +reqTime);
res.headers.set(
'access-control-expose-headers',
'cf-meta-colo, cf-meta-request-time'
);
return res;
}
module.exports = handleRequest;
================================================
FILE: wrangler.toml
================================================
type = "webpack"
zone_id = ""
account_id = ""
route = ""
workers_dev = true
gitextract_n7fvj002/ ├── .github/ │ └── workflows/ │ └── semgrep.yml ├── .gitignore ├── .prettierrc ├── README.md ├── index.js ├── package.json ├── src/ │ ├── down.js │ ├── index.js │ ├── router.js │ ├── test.js │ └── up.js └── wrangler.toml
SYMBOL INDEX (20 symbols across 4 files)
FILE: src/down.js
constant DEFAULT_NUM_BYTES (line 1) | const DEFAULT_NUM_BYTES = 0;
constant MAX_BYTES (line 2) | const MAX_BYTES = 1e8;
function handleRequest (line 26) | async function handleRequest(request) {
FILE: src/index.js
function handleRequest (line 6) | async function handleRequest(request) {
FILE: src/router.js
class Router (line 33) | class Router {
method constructor (line 34) | constructor() {
method handle (line 38) | handle(conditions, handler) {
method connect (line 46) | connect(url, handler) {
method delete (line 50) | delete(url, handler) {
method get (line 54) | get(url, handler) {
method head (line 58) | head(url, handler) {
method options (line 62) | options(url, handler) {
method patch (line 66) | patch(url, handler) {
method post (line 70) | post(url, handler) {
method put (line 74) | put(url, handler) {
method trace (line 78) | trace(url, handler) {
method all (line 82) | all(handler) {
method route (line 86) | route(req) {
method resolve (line 106) | resolve(req) {
FILE: src/up.js
function handleRequest (line 1) | async function handleRequest(request) {
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (13K chars).
[
{
"path": ".github/workflows/semgrep.yml",
"chars": 591,
"preview": "\non:\n pull_request: {}\n workflow_dispatch: {}\n push: \n branches:\n - main\n - master\n schedule:\n - cro"
},
{
"path": ".gitignore",
"chars": 15,
"preview": "node_modules/*\n"
},
{
"path": ".prettierrc",
"chars": 53,
"preview": "{\n \"trailingComma\": \"none\",\n \"singleQuote\": true\n}\n"
},
{
"path": "README.md",
"chars": 2103,
"preview": "## Speed Test\n\nWorker for measuring download / upload connection speed from the client side, using the [Performance Timi"
},
{
"path": "index.js",
"chars": 133,
"preview": "const handleRequest = require('./src');\n\naddEventListener('fetch', event => {\n event.respondWith(handleRequest(event.re"
},
{
"path": "package.json",
"chars": 856,
"preview": "{\n \"name\": \"speedtest-worker\",\n \"private\": true,\n \"version\": \"1.0.0\",\n \"description\": \"Worker for testing connection"
},
{
"path": "src/down.js",
"chars": 1240,
"preview": "const DEFAULT_NUM_BYTES = 0;\nconst MAX_BYTES = 1e8;\n\nconst getQs = url => {\n const sp = url.split('?');\n if (sp.length"
},
{
"path": "src/index.js",
"chars": 311,
"preview": "const Router = require('./router');\n\nconst downHandler = require('./down');\nconst upHandler = require('./up');\n\nasync fu"
},
{
"path": "src/router.js",
"chars": 2704,
"preview": "/**\n * Helper functions that when passed a request will return a\n * boolean indicating if the request uses that HTTP met"
},
{
"path": "src/test.js",
"chars": 3217,
"preview": "const fetch = require('@dollarshaveclub/node-fetch');\nconst Request = fetch.Request;\nconst Response = fetch.Response;\n\nc"
},
{
"path": "src/up.js",
"chars": 509,
"preview": "async function handleRequest(request) {\n const reqTime = new Date();\n\n const res = new Response('ok');\n\n res.headers."
},
{
"path": "wrangler.toml",
"chars": 76,
"preview": "type = \"webpack\"\nzone_id = \"\"\naccount_id = \"\"\nroute = \"\"\nworkers_dev = true\n"
}
]
About this extraction
This page contains the full source code of the cloudflare/worker-speedtest-template GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (11.5 KB), approximately 3.3k tokens, and a symbol index with 20 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.