Project challenges / verified progress
Beacon: build an SSL and uptime monitor in Go

The engineering notebook

Measure the HTTP answer

Once the secure connection exists, what exactly counts as the server's answer?

Loading statusStage 6 of 10

  • Workspace not ready
  • Agent not ready
Focus25:00
A small focus ritual

0 focus sessions completed. Every fourth session offers a longer break. Start each phase when you are ready.

Study time never unlocks verified lesson progress.

Loading...

Loading verified progress...

Loading GitHub account...
Phase 3 — A check you can trust

Step 01 of 06

Learn the concept

A server can resolve, accept TCP, pass TLS and still serve 503 Service Unavailable. That is not a contradiction. The lower layers proved reachability and identity; HTTP is where the application finally gives its verdict.

STATUS CLASSES MEAN DIFFERENT WORKHTTP responseheaders arrived2xxrequest handledusually healthy3xxgo somewhere elsepolicy choice4xxclient asked badlypath or auth5xxserver failedpage someone
A completed HTTP response is not the same as success. `404` and `503` both mean the transport worked; the application chose an unhappy status. Beacon records the code instead of flattening it into `err != nil`.
Step 01

The ideas this is made of

The request names the application, not just the machine

An HTTP request has a method, request target, protocol version, headers and maybe a body. The Host header is mandatory in HTTP/1.1 because many sites share one IP address. HTTP/2 carries the same idea in pseudo-headers. Reaching the socket is not enough; the request must identify the virtual host and path you actually want checked.

Headers and body measure different waiting

Time to first byte ends when response headers begin arriving. It includes server queueing and handler work before the status is known. Total time includes reading the body too. A health endpoint may make those nearly equal; a large download will not. Monitors usually care about status and header latency more than turning every check into a bandwidth test.

The client and transport have separate jobs

http.Request is one question. http.Transport obtains connections, handles proxies, performs TLS and manages pooling. http.Client applies policy: redirects, cookies and broad timeouts. http.Get hides all of that behind global defaults. Beacon owns a client because a monitor must own redirect behaviour, cancellation and reuse rather than inheriting them by accident.

Connection reuse depends on the body

HTTP/1.1 keeps connections alive by default; HTTP/2 multiplexes streams over one connection. Go can reuse a connection only when the response body is consumed or safely discarded and closed. io.Copy(io.Discard, resp.Body) before Close is not busywork. It prevents each check from paying a fresh TCP and TLS tax.

Redirects are status codes with consequences

Go follows redirects by default. That is friendly for browsers and surprising for monitors. A watched URL returning 302 to a login page may become a final 200, hiding the configured target's answer. Set CheckRedirect deliberately: stop at the first response, or record every hop. Do not let the default decide what health means.

Use a client you control
package main

import (
	"fmt"
	"io"
	"net/http"
	"time"
)

func main() {
	client := &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}

	req, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("User-Agent", "beacon-learning/1.0")

	start := time.Now()
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	_, _ = io.Copy(io.Discard, resp.Body)

	fmt.Println("status:", resp.StatusCode)
	fmt.Println("total_ms:", time.Since(start).Milliseconds())
}

The code builds an explicit request, uses one reusable client, disables automatic redirects and drains the body. That is the smallest shape that still resembles production HTTP probing.

Status classes without folklore

ClassMeaningMonitor action

1xx

Informational

Rare in simple checks

2xx

Handled successfully

Usually success

3xx

Redirect

Record or follow by policy

4xx

Request problem

Config, auth or path

5xx

Server problem

Service is unhealthy

What these are called on the job

  • Request line — The HTTP/1.1 method, target and version at the start of a request.

  • Host header — The header that identifies the virtual host being requested.

  • Transport — Go's HTTP component for connections, TLS, proxies and pooling.

  • TTFB — Time to first byte: delay until response bytes start arriving.

  • Drain — Read and discard a body so the connection can be reused.