Spaces:
Sleeping
Sleeping
# app.py | |
from flask import Flask, Response, request, jsonify | |
import os | |
app = Flask(__name__) | |
# ββββββββββββββββββββββββββββββ | |
# Configuration: Test size = 1 GiB | |
# ββββββββββββββββββββββββββββββ | |
DOWNLOAD_SIZE_BYTES = 1 * 1024 * 1024 * 1024 # 1 GiB | |
# ββββββββββββββββββββββββββββββ | |
# Generator for streaming 1 GiB of random bytes in 10 MiB chunks | |
# ββββββββββββββββββββββββββββββ | |
def generate_random_blob(): | |
sent = 0 | |
chunk_size = 10 * 1024 * 1024 # 10 MiB per chunk | |
while sent < DOWNLOAD_SIZE_BYTES: | |
to_send = min(chunk_size, DOWNLOAD_SIZE_BYTES - sent) | |
yield os.urandom(to_send) | |
sent += to_send | |
# ββββββββββββββββββββββββββββββ | |
# Route: β/β β serve the HTML + JS client | |
# ββββββββββββββββββββββββββββββ | |
def index(): | |
html = """ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<title>Client-Side Speed Test (1 GiB)</title> | |
<style> | |
body { font-family: sans-serif; max-width: 600px; margin: 2em auto; } | |
button { padding: 0.5em 1em; font-size: 1rem; } | |
.result { margin-top: 1.5em; } | |
.label { font-weight: bold; } | |
</style> | |
</head> | |
<body> | |
<h2>Wi-Fi / Ethernet Speed Test (Up to 1 GiB)</h2> | |
<p>This test will measure your real Internet speed by downloading <strong>1 GiB</strong> of random data and uploading <strong>1 GiB</strong> back to the server. All timing runs entirely in your browser.</p> | |
<button id="startBtn">Start Speed Test</button> | |
<div class="result" id="results" style="display: none;"> | |
<p><span class="label">Download Speed:</span> <span id="dlSpeed">β</span></p> | |
<p><span class="label">Upload Speed:</span> <span id="ulSpeed">β</span></p> | |
<p><span class="label">Ping:</span> <span id="ping">β</span></p> | |
</div> | |
<script> | |
const startBtn = document.getElementById("startBtn"); | |
const resultsDiv = document.getElementById("results"); | |
const dlSpan = document.getElementById("dlSpeed"); | |
const ulSpan = document.getElementById("ulSpeed"); | |
const pingSpan = document.getElementById("ping"); | |
// Convert bits/sec to Mbps string | |
function toMbps(bits) { | |
return (bits / 1e6).toFixed(2) + " Mbps"; | |
} | |
startBtn.addEventListener("click", async () => { | |
startBtn.disabled = true; | |
resultsDiv.style.display = "block"; | |
dlSpan.textContent = "Testingβ¦"; | |
ulSpan.textContent = "Testingβ¦"; | |
pingSpan.textContent = "Testingβ¦"; | |
// 1) Measure ping: 5 tiny GETs to /ping_test | |
let pingTimes = []; | |
for (let i = 0; i < 5; i++) { | |
const t0 = performance.now(); | |
await fetch("/ping_test?t=" + i, { cache: "no-store" }); | |
const t1 = performance.now(); | |
pingTimes.push(t1 - t0); | |
} | |
const avgPing = pingTimes.reduce((a, b) => a + b, 0) / pingTimes.length; | |
pingSpan.textContent = avgPing.toFixed(2) + " ms"; | |
// 2) Download test: stream 1 GiB from /download_test | |
const dlStart = performance.now(); | |
const response = await fetch("/download_test", { cache: "no-store" }); | |
const reader = response.body.getReader(); | |
let dlBytes = 0; | |
while (true) { | |
const { done, value } = await reader.read(); | |
if (done) break; | |
dlBytes += value.length; | |
} | |
const dlEnd = performance.now(); | |
const dlBits = dlBytes * 8; | |
const dlDurationSec = (dlEnd - dlStart) / 1000; | |
const dlSpeedBps = dlBits / dlDurationSec; | |
dlSpan.textContent = toMbps(dlSpeedBps); | |
// 3) Upload test: stream 1 GiB of random data to /upload_test | |
const uploadTotalBytes = dlBytes; // should be 1 GiB | |
const chunkSize = 10 * 1024 * 1024; // 10 MiB | |
let sentBytes = 0; | |
const uploadStream = new ReadableStream({ | |
start(controller) { | |
function pushChunk() { | |
if (sentBytes >= uploadTotalBytes) { | |
controller.close(); | |
return; | |
} | |
const size = Math.min(chunkSize, uploadTotalBytes - sentBytes); | |
const chunk = new Uint8Array(size); | |
window.crypto.getRandomValues(chunk); | |
controller.enqueue(chunk); | |
sentBytes += size; | |
// immediately queue next | |
pushChunk(); | |
} | |
pushChunk(); | |
} | |
}); | |
const ulStart = performance.now(); | |
await fetch("/upload_test", { | |
method: "POST", | |
headers: { "Content-Type": "application/octet-stream" }, | |
body: uploadStream | |
}); | |
const ulEnd = performance.now(); | |
const ulDurationSec = (ulEnd - ulStart) / 1000; | |
const ulBits = uploadTotalBytes * 8; | |
const ulSpeedBps = ulBits / ulDurationSec; | |
ulSpan.textContent = toMbps(ulSpeedBps); | |
startBtn.disabled = false; | |
}); | |
</script> | |
</body> | |
</html> | |
""" | |
return Response(html, mimetype="text/html") | |
# ββββββββββββββββββββββββββββββ | |
# Route: /ping_test β empty 200 response for ping | |
# ββββββββββββββββββββββββββββββ | |
def ping_test(): | |
return Response(status=200) | |
# ββββββββββββββββββββββββββββββ | |
# Route: /download_test β stream 1 GiB of random data | |
# ββββββββββββββββββββββββββββββ | |
def download_test(): | |
headers = { | |
"Content-Disposition": f"attachment; filename=\"test_{DOWNLOAD_SIZE_BYTES}.bin\"", | |
"Content-Length": str(DOWNLOAD_SIZE_BYTES), | |
"Cache-Control": "no-store" | |
} | |
return Response( | |
generate_random_blob(), | |
mimetype="application/octet-stream", | |
headers=headers | |
) | |
# ββββββββββββββββββββββββββββββ | |
# Route: /upload_test β consume uploaded data in chunks and return JSON | |
# ββββββββββββββββββββββββββββββ | |
def upload_test(): | |
total = 0 | |
# Read the incoming stream in 1 MiB chunks and discard | |
while True: | |
chunk = request.stream.read(1 * 1024 * 1024) | |
if not chunk: | |
break | |
total += len(chunk) | |
# total should be near 1 GiB if upload completed | |
return jsonify({"received_bytes": total}) | |
# ββββββββββββββββββββββββββββββ | |
# Run the Flask app on port 7860 | |
# ββββββββββββββββββββββββββββββ | |
if __name__ == "__main__": | |
app.run(host="0.0.0.0", port=7860, debug=False) | |