Checks send no Referer header, so Hetzner answers (closes #191)
check / check (push) Waiting to run
check / check (push) Waiting to run
The Hetzner speed-test servers close the connection without an answer when the request has a Referer header. The browser sends one unless the page comes with the image's Referrer-Policy: no-referrer header, which a static host and make dev do not send. Each check now sets referrerPolicy: "no-referrer". Model: opus-5-5
This commit is contained in:
@@ -413,10 +413,12 @@ Local hosts are tracked separately from WAN stats.
|
||||
|
||||
### Latency measurement
|
||||
|
||||
GET requests to each target's URL as written, with `mode: 'no-cors'` and
|
||||
`cache: 'no-store'`, timed with `performance.now()`. No query string is added:
|
||||
the Hetzner speed-test servers close the connection without an answer when the
|
||||
URL has one, and `no-store` keeps the browser's cache out of the measurement.
|
||||
GET requests to each target's URL as written, with `mode: 'no-cors'`,
|
||||
`cache: 'no-store'` and `referrerPolicy: 'no-referrer'`, timed with
|
||||
`performance.now()`. The Hetzner speed-test servers close the connection without
|
||||
an answer when the URL has a query string or the request has a `Referer` header.
|
||||
So no query string is added, and no `Referer` header is sent, wherever the page
|
||||
is served from. `no-store` keeps the browser's cache out of the measurement.
|
||||
Each check times out after 80% of the refresh interval (24 seconds at 30
|
||||
seconds) and is then recorded as a timeout, so a round's checks have all
|
||||
finished before the next round is due. When no WAN host answers, a recovery
|
||||
|
||||
@@ -23,6 +23,12 @@ Run `make frontend-viewport-test` in CI as its own step; it is not part of
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-26: the six Hetzner targets answer wherever the page is served from
|
||||
([#191](https://git.eeqj.de/sneak/netwatch/issues/191)). They close the
|
||||
connection without an answer when the request has a `Referer` header. The
|
||||
browser sent one unless the page came with the image's
|
||||
`Referrer-Policy: no-referrer` header, which a static host and `make dev` do
|
||||
not send. Each check now sets `referrerPolicy: 'no-referrer'`
|
||||
- 2026-10-19: the server's `OnStart` hook in `internal/server/server.go` starts
|
||||
`listenAndServe` with `go func() { s.listenAndServe() }()`, as the Go style
|
||||
guide asks ([#162](https://git.eeqj.de/sneak/netwatch/issues/162)); it was
|
||||
|
||||
@@ -180,6 +180,17 @@ test("a check fetches the target's URL as written, with no query string added",
|
||||
);
|
||||
});
|
||||
|
||||
test("a check sends no Referer header, wherever the page is served from", async (t) => {
|
||||
mockTargets(t, () => 10);
|
||||
const check = measureLatency("https://fsn1-speed.hetzner.com");
|
||||
t.mock.timers.tick(10);
|
||||
assert.notEqual(await settled(check), "still waiting");
|
||||
assert.equal(
|
||||
fetch.mock.calls[0].arguments[1].referrerPolicy,
|
||||
"no-referrer",
|
||||
);
|
||||
});
|
||||
|
||||
test("at a 30000ms interval, a target answering after 1000ms shows in its row while another target's check is still waiting", async (t) => {
|
||||
CONFIG.updateInterval = 30000;
|
||||
const state = new AppState([
|
||||
|
||||
+6
-4
@@ -572,10 +572,11 @@ export class Reporter {
|
||||
// it as a timeout. A failed check's reason says what went wrong and after
|
||||
// how long; a check that answered has none.
|
||||
//
|
||||
// The URL is fetched as written, with nothing added to it: the Hetzner
|
||||
// speed-test servers close the connection without an answer when the URL
|
||||
// has a query string, and cache: "no-store" keeps the browser's cache out
|
||||
// of the measurement.
|
||||
// The Hetzner speed-test servers close the connection without an answer
|
||||
// when the URL has a query string or the request has a Referer header. So
|
||||
// the URL is fetched as written, and referrerPolicy: "no-referrer" sends
|
||||
// no Referer header, wherever the page is served from. cache: "no-store"
|
||||
// keeps the browser's cache out of the measurement.
|
||||
export async function measureLatency(url, signal) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(
|
||||
@@ -591,6 +592,7 @@ export async function measureLatency(url, signal) {
|
||||
method: "GET",
|
||||
mode: "no-cors",
|
||||
cache: "no-store",
|
||||
referrerPolicy: "no-referrer",
|
||||
signal: controller.signal,
|
||||
});
|
||||
const latency = Math.round(performance.now() - start);
|
||||
|
||||
Reference in New Issue
Block a user