Documentation
¶
Overview ¶
Package daemon speaks JSON lines on a pipe, so a program in any language can borrow a browser's fingerprint without reimplementing one.
The protocol is one JSON object per line in, one per line out:
in : {"id":7,"url":"https://…","headers":{"a":"b"},"order":["a"]}
out: {"id":7,"status":200,"url":"…","body":"…","headers":{…}}
The process is long-lived and holds one client, which means one TLS fingerprint, one cookie jar and one exit IP for its whole life. That is not a simplification — it is the point. A fresh handshake and an empty jar for every request is itself a signal, and no browser produces it.
Why every response carries an id ¶
The protocol is strictly one request at a time, so an id looks redundant. It is not, and the failure it prevents is silent.
A caller that gives up on a slow request will typically kill this process and start another. But the abandoned process can already have a complete answer on its way up the pipe, and that answer arrives after the caller has moved on to the next request. Without an id it is indistinguishable from the new request's answer — and the response URL cannot stand in for one, because it is the post-redirect URL. The result is one page filed under another page's request: well-formed, plausible and wrong.
So the id is echoed on EVERY response, including every error path, and a caller is expected to drop any line whose id it is not waiting for.
Index ¶
Constants ¶
const ( BodyUTF8 = "utf8" BodyBase64 = "base64" )
How a body is spelled on the wire.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Client ¶
type Client interface {
Do(*tlsforge.Request) (*tlsforge.Response, error)
Headers() tlsforge.Header
}
Client is the part of *tlsforge.Client the daemon needs, named so tests can substitute one without opening a socket.
type Request ¶
type Request struct {
// ID is caller-assigned and echoed on the response. See the package comment.
ID uint64 `json:"id"`
Method string `json:"method,omitempty"`
URL string `json:"url"`
Headers map[string]string `json:"headers,omitempty"`
// Order is the header order to send. JSON objects have no order, so a caller
// that cares must say so here. When omitted, the profile's own order is
// used, and headers the profile does not name go last.
Order []string `json:"order,omitempty"`
Body string `json:"body,omitempty"`
// BodyEncoding says how to read Body. Absent or "utf8" means Body is the
// bytes themselves; "base64" means it is standard base64.
//
// A JSON string cannot hold arbitrary bytes: encoding/json replaces every
// byte that is not valid UTF-8 with U+FFFD, silently and without an error.
// So a body that is not text has to travel as base64 or not at all.
BodyEncoding string `json:"bodyEncoding,omitempty"`
// SetCookie seeds the jar with "name=value" pairs before the request.
SetCookie []string `json:"setCookie,omitempty"`
}
Request is one line of input.
type Response ¶
type Response struct {
// Never omitempty: a caller has to be able to tell "id 0" from "no id at
// all", and read the latter as "this binary is older than the code driving
// it" rather than letting every request time out unexplained.
ID uint64 `json:"id"`
Status int `json:"status"`
URL string `json:"url"`
Body string `json:"body"`
// BodyEncoding is absent when Body is the response text, and "base64" when
// the response was not valid UTF-8 and could not be carried as a JSON
// string. Absent rather than "utf8" for the common case, so a reader that
// predates this field is unaffected by it for every text response.
BodyEncoding string `json:"bodyEncoding,omitempty"`
Headers map[string][]string `json:"headers"`
Cookies []string `json:"cookies"`
Error string `json:"error,omitempty"`
}
Response is one line of output.