Documentation
¶
Overview ¶
Package echo is a local HTTPS server that tells every client what it just sent.
It is the measuring instrument this library is verified with. Point a real browser at it and it reports that browser's ClientHello, HTTP/2 preamble and header order; point this library at it and it reports the same fields for the same connection. Comparing the two answers is the whole verification story, and because both sides are measured by one instrument on one machine, a difference is a real difference rather than a difference in how two services happened to render their output.
Running locally also removes the third-party dependency that makes this kind of check rot: no rate limit, no outage, no silent change of output format, no sending anybody's traffic to a stranger's host to find out what it looks like.
Index ¶
- type Option
- type Server
- func (s *Server) Addr() string
- func (s *Server) Await(ctx context.Context) (*Session, error)
- func (s *Server) CaptureURL() string
- func (s *Server) Certificate() []byte
- func (s *Server) Close() error
- func (s *Server) Panics() []string
- func (s *Server) Sessions() []*Session
- func (s *Server) URL() string
- type Session
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Option ¶
type Option func(*options)
Option configures a Server.
func WithAddr ¶
WithAddr sets the listen address. The default binds an ephemeral port on loopback, which is what makes it safe to run several at once — in tests, for instance.
func WithHost ¶
WithHost sets the hostname used in URL and in the certificate. "localhost" rather than an IP keeps the browser's SNI populated, which JA4 records as `d` instead of `i`.
func WithSessionTickets ¶
WithSessionTickets re-enables TLS session resumption, which is off by default.
Off by default because resumption changes the fingerprint being measured: a resumed connection carries pre_shared_key, which is one more extension, which is a different JA4. Real Chrome 151 sends t13d1516h2_…_806a8c22fdea on a cold connection and t13d1517h2_…_a87ad97598a9 on a resumed one. Both are honest, but only the cold one is what a server sees on first contact, and a capture that silently alternated between them would produce a profile that matched the browser about half the time.
Turn it on to study resumption itself.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the listener. The zero value is not usable; call Start.
func (*Server) Await ¶
Await blocks until a browser has completed a capture: it navigated to the capture page, and the page reported back what JavaScript can see.
The session returned is the one that carried the DOCUMENT NAVIGATION, which is not necessarily the one that delivered the report. Chrome keeps spare connections open and will happily send the page's fetch() down a different one — and that connection resumes the first one's session, so it carries an extra pre_shared_key extension and a different JA4, and its headers are a fetch's rather than a navigation's.
Measured, before this was separated out: the capture came back with `content-length, sec-ch-ua-platform, user-agent, content-type, origin…` — the header order of an XHR — presented as the browser's navigation fingerprint. It is wrong in a way that looks entirely plausible, which is why the navigation is tracked explicitly rather than inferred from whichever connection spoke last.
func (*Server) CaptureURL ¶
CaptureURL is the protocol-restricted endpoint the capture page navigates through after the main listener has recorded HTTP/2.
func (*Server) Certificate ¶
Certificate exposes the generated certificate so a client can pin it instead of disabling verification wholesale.
func (*Server) Panics ¶
Panics returns what has been recovered from connection handlers, which should be nothing.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is everything one connection revealed about its client.