- xhr.onload now owns the complete lifecycle: checks x.status on
the captured local variable, restarts only on 2xx.
- xhr.upload.onload no longer calls testStream() to avoid race
with xhr.onload overwriting xhr[i] before status is read.
- xhr.upload.onerror also uses the captured x for consistency.
- speedtest_worker.js: moved HTTP status check from xhr.upload.onload
(body sent, response may not be available) to xhr.onload
(full HTTP transaction complete, xhr.status is reliable).
- doc.md: added full IIS web.config example with maxAllowedContentLength.
- speedtest_worker.js: xhr.upload.onload now checks xhr.status
before restarting the upload stream. Non-2xx responses (e.g.
HTTP 413 from undersized client_max_body_size) are treated as
failures instead of inflating upload speed measurements.
- doc.md: added explicit nginx/Apache/IIS configuration examples
for client_max_body_size / LimitRequestBody to prevent HTTP 413
errors on the 20MB upload test blobs.
Drop the Debian COPY comment (it only restated the code) and the
version-fragility framing in the Alpine comment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous build-time discovery via PHP_CONFIG_FILE_SCAN_DIR worked
but invoked a PHP binary just to learn a path that's already a stable
contract of each base image:
* php:8-apache exports PHP_INI_DIR=/usr/local/etc/php as part of the
docker-library image template, stable across PHP majors. Reference
it directly in the COPY destination — no RUN, no validation needed.
* Alpine's apk php-apache2 always installs mod_php's conf.d at
/etc/phpXX/conf.d (currently /etc/php84). The FROM php:8-alpine
image also ships its own PHP at /usr/local/etc/php/conf.d, but
mod_php doesn't read from there. Glob /etc/php*/conf.d to track
the apk-installed PHP major automatically; explicit error if the
glob matches nothing.
Net effect: Debian goes from a 5-line RUN block to a 1-line COPY.
Alpine keeps a small RUN block but no longer invokes a PHP binary,
so it doesn't matter which of the two PHP installs `php` resolves
to via $PATH.
Verified: both variants build, install the override at the right
conf.d, mod_php reports post_max_size=32M, and a 20 MB POST to
/backend/empty.php returns HTTP 200 / 0 bytes with no warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
speedtest_worker.js uploads in 20 MB chunks by default
(xhr_ul_blob_megabytes: 20) but the official Docker images inherit
PHP's stock post_max_size = 8M / upload_max_filesize = 2M, so every
upload chunk:
* triggers a "POST Content-Length ... exceeds the limit" warning
(leaked into the response body of /backend/empty.php on the Debian
variant where display_errors is on; suppressed but still emitted on
Alpine where it's off);
* causes empty.php's subsequent header() calls to fail with "Cannot
modify header information - headers already sent", leaving the
response without proper status, cache, or CORS directives.
Ship a small docker/librespeed-php.ini with post_max_size = 32M,
upload_max_filesize = 32M, memory_limit = 256M and COPY it into the
right conf.d for each base image (/usr/local/etc/php/conf.d on
Debian, /etc/php84/conf.d on Alpine). 99- prefix follows the
NN-name.ini packaging convention so this loads after distro defaults
but never silently shadows operator overrides.
Verified post-fix on both variants: a 20 MB POST to /backend/empty.php
returns HTTP 200 / 0 bytes with no warning leakage.
- Load server list dynamically from servers.json for Docker frontend/dual modes
- Copy servers.json to web root in entrypoint.sh for frontend/dual modes
- Change back link from href="/" to href="./" for subdirectory installs
- Use binary search for visible chart data range (O(log n) vs O(n))
- Add 200ms minimum interval between pings to limit sample rate
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a prolonged ping-based stability test with real-time canvas chart,
stats (avg/min/max/jitter/packet loss), stability rating, external ping
targets, CSV export, and Docker support. Link from main page to stability test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Build an array of {idx, server} pairs before sorting so the original
SPEEDTEST_SERVERS index is carried through, eliminating the per-option
indexOf call.
Parse server names more robustly for sorting:
- "City, Country, Provider" → use second part as country
- "City, Country (1) (Hetzner)" → strip parentheticals from country
- "Frankfurt, Germany (FRA01)" → country is "Germany" not "Germany (FRA01)"
Address code review findings:
- Sort a shallow copy instead of mutating the caller's array
- Add null guard on server.name to handle malformed entries
- Use original SPEEDTEST_SERVERS index for classic UI option values
Sort the server dropdown by country first, then by city within the same
country. This makes it easier to find servers in a specific country when
the list is long. Applies to both modern and classic UIs.