speed_test / app.py
euler314's picture
Update app.py
fb4f63a verified
raw
history blame
5.82 kB
# app.py
from flask import Flask, Response, request, jsonify
import os
app = Flask(__name__)
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Pre-generate a 10 MiB random blob for download tests
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
DOWNLOAD_SIZE_BYTES = 10 * 1024 * 1024
_blob = os.urandom(DOWNLOAD_SIZE_BYTES)
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Route: Root – serve the HTML + JS speed‐test page
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
@app.route("/")
def index():
html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Client-Side Speed Test</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</h2>
<p>This runs entirely in your browserβ€”download a 10 MiB file and upload random data to measure your real Internet link.</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) Ping: 5 small 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: fetch 10 MiB blob
const dlStart = performance.now();
const response = await fetch("/download_test", { cache: "no-store" });
const blob = await response.blob();
const dlEnd = performance.now();
const dlBytes = blob.size;
const dlBits = dlBytes * 8;
const dlDurationSec = (dlEnd - dlStart) / 1000;
const dlSpeedBps = dlBits / dlDurationSec;
dlSpan.textContent = toMbps(dlSpeedBps);
// 3) Upload test: send 10 MiB random data
const uploadSizeBytes = dlBytes;
const randomBuffer = new Uint8Array(uploadSizeBytes);
window.crypto.getRandomValues(randomBuffer);
const ulStart = performance.now();
await fetch("/upload_test", {
method: "POST",
headers: { "Content-Type": "application/octet-stream" },
body: randomBuffer
});
const ulEnd = performance.now();
const ulDurationSec = (ulEnd - ulStart) / 1000;
const ulBits = uploadSizeBytes * 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 (used for ping measurement)
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
@app.route("/ping_test")
def ping_test():
return Response(status=200)
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Route: /download_test – serve the 10 MiB random blob
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
@app.route("/download_test")
def download_test():
return Response(
_blob,
mimetype="application/octet-stream",
headers={
"Content-Disposition": f"attachment; filename=\"test_{DOWNLOAD_SIZE_BYTES}.bin\""
}
)
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Route: /upload_test – accept upload, return JSON with received size
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
@app.route("/upload_test", methods=["POST"])
def upload_test():
data = request.get_data() # read all bytes
size = len(data)
return jsonify({"received_bytes": size})
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Run the Flask app
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
if __name__ == "__main__":
# Bind to 0.0.0.0 so HF’s runtime can route traffic correctly
app.run(host="0.0.0.0", port=7860, debug=False)