Skip to content

Commit

Permalink
improved bw test, added rate limiter
Browse files Browse the repository at this point in the history
  • Loading branch information
pl-42 committed Jan 4, 2024
1 parent 636f729 commit 16c1352
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 40 deletions.
Binary file removed mmdb/GeoLite2-ASN.mmdb
Binary file not shown.
Binary file removed mmdb/GeoLite2-City.mmdb
Binary file not shown.
Binary file removed mmdb/GeoLite2-Country.mmdb
Binary file not shown.
Binary file removed mmdb/dbip-country-lite-2024-01.mmdb
Binary file not shown.
79 changes: 57 additions & 22 deletions server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import config from "./config.ts";
import { STATUS_CODE } from "std/http/status.ts";
// import { UserAgent } from "std/http/user_agent.ts";
import { Buffer } from "node:buffer";

import { join as path_join } from "std/path/join.ts";
Expand All @@ -14,10 +13,9 @@ function log(level: LogLevel, ...message: (number | string | object)[]) {
console.log(new Date().toJSON(), level, ...message);
}

const req_cache = new Map<string, number>();
const geo_dbs = new Map<string, mmReader<mmResponse>>();

const ejs_templates = new Map<string, ejs.AsyncTemplateFunction>();

const static_files = new Map<string, Uint8Array>();

function get_ip_details(ip: string) {
Expand All @@ -31,42 +29,82 @@ function get_ip_details(ip: string) {
return result;
}

async function serverHandler(req: Request, _info: Deno.ServeHandlerInfo) {
function rate_limit(ip_url: string): boolean {
const limit_ms = 1000 * 1; // 1 second
const cur_ts = Date.now();
const last_visit = req_cache.get(ip_url) || 0;
req_cache.set(ip_url, cur_ts);
if (cur_ts - last_visit < limit_ms) {
return true;
}
return false;
}

async function serverHandler(
req: Request,
_info: Deno.ServeHandlerInfo,
): Promise<Response> {
const url = new URL(req.url);
const url_pathname = url.pathname.toLowerCase();
const req_method = req.method.toLowerCase();
const req_ip = _info.remoteAddr.hostname;
const ua_header = req.headers.get("user-agent") ?? "";
const content_type = req.headers.get("content-type") ?? "";
const max_bw_test_length = 10000000;

if (
rate_limit(
req_ip + " " + req_method + " " + url_pathname.replaceAll("/", ""),
)
) {
return new Response("Too many requests from your IP", {
status: STATUS_CODE.TooManyRequests,
});
}

const static_content = static_files.get(url.pathname.replace(/\/*/, ""));
const static_content = static_files.get(url_pathname.replace(/\/*/, ""));
if (static_content) return new Response(static_content);

if (url.pathname === "/bandwidth") {
if (req.method.toLowerCase() === "get") {
if (url_pathname === "/bandwidth") {
if (req_method === "get") {
const length = Number.parseInt("" + url.searchParams.get("length")) ||
100000;
const sample = Date.now().toString() + crypto.randomUUID();
if (length > max_bw_test_length) {
return new Response("Request size is too big", {
status: STATUS_CODE.ContentTooLarge,
});
}
const sample = crypto.randomUUID();
return new Response(
"".padEnd(length, sample),
(Date.now().toString() + " ").padEnd(length, sample),
{ headers: { "Content-Encoding": "identity" } },
);
}
if (req.method.toLowerCase() === "post") {
if (req_method === "post") {
req.headers.get("content-length");
const data_length =
Number.parseInt(req.headers.get("content-length") || "") || 0;
if (data_length > max_bw_test_length) {
return new Response("Request size is too big", {
status: STATUS_CODE.ContentTooLarge,
});
}

await req.arrayBuffer();
return new Response(undefined, { status: STATUS_CODE.Accepted });
// const ab = await req.arrayBuffer();
// return new Response(ab.byteLength.toString(), {status:STATUS_CODE.Accepted});
}
}

if (req.method.toLowerCase() === "get") {
if (url.pathname === "/empty") {
if (req_method === "get") {
if (url_pathname === "/empty") {
// Empty reponsse used to measure round-trip time (aka latency).
return new Response();
}

if (url.pathname.replaceAll("/", "") === "") {
if (url_pathname.replaceAll("/", "") === "") {
// Default page

const ua_header = req.headers.get("user-agent") ?? "";
const content_type = req.headers.get("content-type") ?? "";

// for curl - just return IP address
if (ua_header.startsWith("curl/") && content_type === "") {
return new Response(_info.remoteAddr.hostname, {
Expand All @@ -80,7 +118,7 @@ async function serverHandler(req: Request, _info: Deno.ServeHandlerInfo) {
ip: _info.remoteAddr.hostname,
ua: ua_header,
ip_details: get_ip_details(_info.remoteAddr.hostname),
providers: config.geoip.providers
providers: config.geoip.providers,
};

// Return json for corresponding content types
Expand All @@ -90,7 +128,6 @@ async function serverHandler(req: Request, _info: Deno.ServeHandlerInfo) {
{ status: STATUS_CODE.OK },
);
}

// Return rendered page for browser
const render_fn = ejs_templates.get("index");
if (render_fn) {
Expand All @@ -99,15 +136,13 @@ async function serverHandler(req: Request, _info: Deno.ServeHandlerInfo) {
status: STATUS_CODE.OK,
});
}

// Fallback - return plaintext
return new Response(Deno.inspect(result), {
headers: { "content-type": "text/plain" },
headers: { "content-type": "text/plain;charset=UTF-8" },
status: STATUS_CODE.OK,
});
}
}

// Route not found
return new Response("Hello, I am IP123", { status: STATUS_CODE.NotFound });
}
Expand Down
48 changes: 31 additions & 17 deletions static/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ btn_test_all.addEventListener('click', async () => {
const btn = document.getElementById('test_' + region);
if (btn) {
btn.classList.add('pure-button-disabled');
try{
try {
await testRegionAsync(region);
} finally{
} finally {
btn.classList.remove('pure-button-disabled');
}
}
Expand All @@ -20,9 +20,9 @@ for (const region of regions) {
const btn = document.getElementById('test_' + region);
btn.addEventListener('click', async () => {
btn.classList.add('pure-button-disabled');
try{
try {
await testRegionAsync(region);
} finally{
} finally {
if (btn) btn.classList.remove('pure-button-disabled');
}
})
Expand All @@ -33,14 +33,24 @@ async function testRegionAsync(region) {
const dl_el = document.getElementById('dl_' + region);
const ul_el = document.getElementById('ul_' + region);

if (rtt_el) rtt_el.innerText = '';
if (dl_el) dl_el.innerText = '';
if (ul_el) ul_el.innerText = '';
if (rtt_el) rtt_el.innerText = 'Testing';
if (dl_el) dl_el.innerText = 'Testing';
if (ul_el) ul_el.innerText = 'Testing';

const rtt = await testRtt(region);
if (rtt_el) rtt_el.innerText = rtt + 'ms';

const dl = await testDownload(region);
let bw_test_size = 1000000;
if (rtt > 10) bw_test_size = 500000;
if (rtt > 100) bw_test_size = 300000;
if (rtt > 1000) bw_test_size = 200000;
if (rtt > 2000) bw_test_size = 100000;
// if (rtt > 1500) bw_test_size = 80000;
// if (rtt > 2000) bw_test_size = 50000;
// if (rtt > 3000) bw_test_size = 30000;
// if (rtt > 5000) bw_test_size = 10000;

const dl = await testDownload(region, bw_test_size);

let dl_speed_h = dl.speed.toString();
if (dl.speed > 1000) dl_speed_h = `${dl.speed / 1000}K`;
Expand All @@ -49,14 +59,14 @@ async function testRegionAsync(region) {

if (dl_el) dl_el.innerText = `${dl_speed_h}bps (${dl.time / 1000}s)`;

const ul = await testUpload(region);
const ul = await testUpload(region, bw_test_size);

let ul_speed_h = (ul.speed, '');
if (ul.speed > 1000) ul_speed_h = `${ul.speed / 1000}K`;
if (ul.speed > 1000000) ul_speed_h = `${Math.round(ul.speed / 1000) / 1000}M`;
if (ul.speed > 1000000000) ul_speed_h = `${Math.round(ul.speed / 1000000) / 1000}G`;

if (ul_el) ul_el.innerText = `${ul_speed_h}bps (${ul.time / 1000}s)`;
if (ul_el) ul_el.innerText = `${ul_speed_h}bps (${ul.rsp_time / 1000}s)`;
}

async function testRtt(region) {
Expand All @@ -68,23 +78,27 @@ async function testRtt(region) {
}

async function testDownload(region, length) {
const size = Number.parseInt(length) || 1000000;

const rsp = await fetch('/bandwidth?length=' + size);
const start_ts = Date.now();
const rsp = await fetch('/bandwidth?length=' + (Number.parseInt(length) || 100000));
const rsp_blob = await rsp.blob();
const rsp_blob = await rsp.arrayBuffer();
const end_ts = Date.now();
const rsp_time = Number.parseInt(rsp.headers.get('x-response-time')) || 0;
const rsp_length = rsp_blob.byteLength;
const time = end_ts - start_ts - rsp_time;
const speed = Math.round(rsp_blob.size / (time / 1000));
const speed = Math.round(rsp_length *8 / (time / 1000));
return {
blobsize: rsp_blob.size,
blobsize: rsp_length,
time: time,
rsp_time: rsp_time,
speed: speed
}
}

async function testUpload(region, length) {
const size = Number.parseInt(length) || 100000;
const size = Number.parseInt(length) || 1000000;

const start_ts = Date.now();
const rsp = await fetch('/bandwidth', {
method: 'POST',
Expand All @@ -93,11 +107,11 @@ async function testUpload(region, length) {
"Accept-Encoding": ""
}
})
await rsp.blob();
// await rsp.arrayBuffer();
const end_ts = Date.now();
const rsp_time = Number.parseInt(rsp.headers.get('x-response-time')) || 0;
const time = end_ts - start_ts;
const speed = Math.round(size / (time / 1000));
const speed = Math.round(size * 8 / (rsp_time / 1000));
return {
blobsize: size,
time: time,
Expand Down
2 changes: 1 addition & 1 deletion templates/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
</div>

<div class="pure-u-md-2-2" style="width:100%">
<h1>Network Diagnostics</h1>
<h1>Network Bandwidth Test</h1>
<table class="pure-table">
<thead>
<tr>
Expand Down

0 comments on commit 16c1352

Please sign in to comment.