shade

package module
v0.0.0-...-2835ce2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 19, 2026 License: MIT Imports: 2 Imported by: 0

README

shade

Pure-Go HTTP client that impersonates real browsers' TLS/JA3, HTTP/2 and HTTP/3 fingerprints, plus a headless browser that runs page JavaScript.

No cgo, no C, no code generation. Cross-compiles, including to Android/Termux.


Install

go get github.com/HashShin/shade

go install github.com/HashShin/shade/cmd/shade@latest

make build                     # -> bin/shade
make install                   # -> /usr/local/bin
make install PREFIX=$HOME/.local

Commands

One binary, bin/shade, carries both request paths and both servers. shade help <command> prints a command's flags; the list is generated from the definitions, so it cannot drift from what the command accepts.

Command What it does
get <url> Fast HTTP client with a browser's TLS/JA3, HTTP/2 and HTTP/3.
get <url> --render The same request, loaded in the pure-Go browser.
open <url> Shorthand for get --render.
serve CDP and WebDriver BiDi server on ws://127.0.0.1:9222.
mcp MCP tools over stdio, or over HTTP with --port.
targets List every impersonation target.
version, help Print the version, or a command's flags.

A bare method name is a command, so shade post ... is shade get -X POST .... fetch, browse, render and list are kept as aliases.

shade get tls.browserleaks.com/json -i chrome150
shade post httpbin.org/post -j '{"a":1}'

shade get https://quotes.toscrape.com/js/ --render --format text
shade open example.com --screenshot page.png
shade open example.com/login --fill '#user=me' --click 'button[type=submit]'

shade serve --port 9222
shade mcp --port 9223

--render is refused for post, put, patch, delete, head, options and trace, because a request with a body has no browser path. Exit status is 0 on success, 1 when the command ran and failed, 2 when the arguments were wrong, so a shell can tell a bad URL from a bad flag.

docs/cli.md has every flag of every command.

Working on the repository
make test                      # unit tests, no network
make test-race                 # the same under the race detector
make test-live                 # live fingerprints vs the curl_cffi baseline (network)
make vet                       # go vet ./...
make lint                      # vet, plus staticcheck when it is installed
make fmt                       # gofmt -w .
make clean                     # rm -rf bin

Usage

package main

import (
	"fmt"

	. "github.com/HashShin/shade"
)

func main() {
	r, err := Send(Request{
		Method: "GET",
		URL:    "https://httpbun.com/get",
		Headers: Headers{
			"Accept: application/json",
			"X-Custom: value",
		},
		Impersonate: DefaultChrome,
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(r.StatusCode, r.Text())
}

Send is the fast path. The rest of the HTTP API - options, sessions, TLS - is in docs/api.md, and the browser's page API is in docs/browser.md.

Browser

The same request with the page's scripts run. One import does it: the facade links the browser in. Browse answers with the rendered bytes, Open with the page, so a page can be read and driven:

package main

import (
	"fmt"
	"os"
	"time"

	. "github.com/HashShin/shade"
)

func main() {
	r, err := Open(Request{
		Method: "GET",
		URL:    "https://quotes.toscrape.com/login",
		Headers: Headers{
			"Accept: text/html",
		},
		Impersonate: DefaultChrome,
	})
	if err != nil {
		panic(err)
	}
	defer r.Close()

	r.Fill("#username", "me")
	r.Fill("#password", "secret")
	r.Click("input[type=submit]")
	r.WaitForSelector("a[href='/logout']", 5*time.Second)

	png, _ := r.Screenshot(ScreenshotOptions{Width: 1280, Scale: 2})
	os.WriteFile("page.png", png, 0o644)

	fmt.Println(r.Title())
}

Every call is a method on the page, in the order written, and a click carries its own meaning: it submits the form a submit button belongs to, or follows the link it is on. Options that outlive one page - a proxy, robots.txt, a hook that blocks or answers requests - go on the browser from New.


Limitations

  • The browser is a document renderer, not a web renderer, and there is no WebAssembly: goja has no engine for it.
  • HTTP/1.1 header names are lower-cased on the wire, Chrome's per-connection extension permutation is replaced by one fixed valid order, and the IP, port and size counters on Response are unpopulated.
  • The browser is missing a further set of web APIs.
  • A click follows a link and submits a form, but a script's own form.submit()/requestSubmit() is absent, and a navigation a timer or Page.Eval asks for after the load is recorded and not followed.

docs/limitations.md has the rest, with the measurements.

License

MIT.

Documentation

Overview

Package shade is the whole client in one namespace, written so that it can be dot imported and a request reads without prefixes:

package main

import (
	"fmt"

	. "github.com/HashShin/shade"
)

func main() {
	sess := NewSession()
	defer sess.Close()

	rsp, err := sess.Get("https://httpbun.com/get",
		WithHeaders(Headers{
			"Accept: application/json",
			"X-Custom: value",
		}),
		WithImpersonate(Chrome146),
	)
	if err != nil {
		panic(err)
	}
	fmt.Println(rsp.StatusCode, rsp.Text())
}

Everything here is an alias of the same name in requests or [impersonate], so the unqualified form and the qualified one are the same value, type and function, and the two can be mixed freely. There is nothing to choose between them but spelling.

A request can also be loaded in the browser, which runs the page's scripts and returns the document as it settled:

rsp, err := Send("https://example.com/", WithBrowser())

That works with the import above and no other, because this package links the browser in. The cost is goja, the JavaScript engine the browser runs pages with, which roughly doubles the binary; a program that only makes requests imports requests instead and leaves it out.

The page object behind a rendered request is part of this namespace too, so one import drives a page as well as fetches it. Open takes the same input Browse does and answers with the page rather than the rendered bytes:

p, err := Open(Request{URL: "https://example.com/", Impersonate: Chrome131})
if err != nil {
	panic(err)
}
defer p.Close()

p.Fill("#user", "me")
p.Click("button[type=submit]")
png, _ := p.Screenshot(ScreenshotOptions{Width: 1280})

The names this package cannot carry keep their own spelling: Get and Options are the HTTP verbs here, and Request and Response are the HTTP messages, so the browser package's Get, Options, Request and Response are reached by importing it:

import "github.com/HashShin/shade/browser"

p, err := browser.Get("https://example.com/", browser.Chrome131)

A dot import is a deliberate trade. Go's tooling discourages it, and staticcheck reports it as ST1001, because a dotted package puts every name it exports into the file: this one brings in 40 types, 59 functions, 49 constants and one error sentinel. Import the requests package normally instead if any of those names is already taken in your file. To keep the dot import and silence the check, add "//lint:file-ignore ST1001 reason" at the top of the file, or exclude ST1001 in staticcheck.conf.

The package declares nothing of its own: it is a facade so that the shortest import path and a single namespace are available to a caller who wants them.

Index

Constants

View Source
const (
	Chrome100            = requests.Chrome100
	Chrome101            = requests.Chrome101
	Chrome104            = requests.Chrome104
	Chrome107            = requests.Chrome107
	Chrome110            = requests.Chrome110
	Chrome116            = requests.Chrome116
	Chrome119            = requests.Chrome119
	Chrome120            = requests.Chrome120
	Chrome123            = requests.Chrome123
	Chrome124            = requests.Chrome124
	Chrome131            = requests.Chrome131
	Chrome131Android     = requests.Chrome131Android
	Chrome133a           = requests.Chrome133a
	Chrome136            = requests.Chrome136
	Chrome142            = requests.Chrome142
	Chrome145            = requests.Chrome145
	Chrome146            = requests.Chrome146
	Chrome150            = requests.Chrome150
	Chrome99             = requests.Chrome99
	Chrome99Android      = requests.Chrome99Android
	Custom               = requests.Custom
	DefaultChrome        = requests.DefaultChrome
	DefaultChromeAndroid = requests.DefaultChromeAndroid
	DefaultEdge          = requests.DefaultEdge
	DefaultFirefox       = requests.DefaultFirefox
	DefaultSafari        = requests.DefaultSafari
	DefaultSafariBeta    = requests.DefaultSafariBeta
	DefaultSafariIOS     = requests.DefaultSafariIOS
	DefaultSafariIOSBeta = requests.DefaultSafariIOSBeta
	DefaultTor           = requests.DefaultTor
	Edge101              = requests.Edge101
	Edge99               = requests.Edge99
	Firefox133           = requests.Firefox133
	Firefox135           = requests.Firefox135
	Firefox144           = requests.Firefox144
	Firefox147           = requests.Firefox147
	OkHTTP4Android       = requests.OkHTTP4Android
	Safari153            = requests.Safari153
	Safari155            = requests.Safari155
	Safari170            = requests.Safari170
	Safari172Ios         = requests.Safari172Ios
	Safari180            = requests.Safari180
	Safari180Ios         = requests.Safari180Ios
	Safari184            = requests.Safari184
	Safari184Ios         = requests.Safari184Ios
	Safari260            = requests.Safari260
	Safari2601           = requests.Safari2601
	Safari260Ios         = requests.Safari260Ios
	Tor145               = requests.Tor145
)

Impersonation targets, including the family defaults such as DefaultChrome.

Variables

View Source
var (
	Block               = browser.Block
	Browse              = requests.Browse
	Delete              = requests.Delete
	Do                  = requests.Do
	Fulfill             = browser.Fulfill
	Get                 = requests.Get
	Head                = requests.Head
	IsTimeout           = requests.IsTimeout
	New                 = browser.New
	NewCookies          = requests.NewCookies
	NewHeaders          = requests.NewHeaders
	NewRequestException = requests.NewRequestException
	NewResponse         = requests.NewResponse
	NewSession          = requests.NewSession
	Open                = browser.Open
	Options             = requests.Options
	Patch               = requests.Patch
	Post                = requests.Post
	Put                 = requests.Put
	Send                = requests.Send
	ToRequest           = requests.ToRequest
	Trace               = requests.Trace
	// UseBrowser installs the renderer a Browser request delegates to. The
	// browser package calls it from its init, so this is only for a program
	// that brings a browser of its own.
	UseBrowser = requests.UseBrowser
)

Constructors and one-shot request helpers.

View Source
var (
	WithAcceptEncoding  = requests.WithAcceptEncoding
	WithBrowser         = requests.WithBrowser
	WithAkamai          = requests.WithAkamai
	WithAllowRedirects  = requests.WithAllowRedirects
	WithAuth            = requests.WithAuth
	WithBaseURL         = requests.WithBaseURL
	WithCert            = requests.WithCert
	WithContent         = requests.WithContent
	WithContentCallback = requests.WithContentCallback
	WithCookies         = requests.WithCookies
	WithData            = requests.WithData
	WithDebug           = requests.WithDebug
	WithDefaultEncoding = requests.WithDefaultEncoding
	WithDefaultHeaders  = requests.WithDefaultHeaders
	WithDiscardCookies  = requests.WithDiscardCookies
	WithExtraFP         = requests.WithExtraFP
	WithHTTPVersion     = requests.WithHTTPVersion
	WithHeader          = requests.WithHeader
	WithHeaders         = requests.WithHeaders
	WithImpersonate     = requests.WithImpersonate
	WithInterface       = requests.WithInterface
	WithJA3             = requests.WithJA3
	WithJSON            = requests.WithJSON
	WithMaxRecvSpeed    = requests.WithMaxRecvSpeed
	WithMaxRedirects    = requests.WithMaxRedirects
	WithParams          = requests.WithParams
	WithProxies         = requests.WithProxies
	WithProxy           = requests.WithProxy
	WithProxyAuth       = requests.WithProxyAuth
	WithQuote           = requests.WithQuote
	WithRaiseForStatus  = requests.WithRaiseForStatus
	WithReferer         = requests.WithReferer
	WithRetry           = requests.WithRetry
	WithStream          = requests.WithStream
	WithTimeout         = requests.WithTimeout
	WithTimeoutSeconds  = requests.WithTimeoutSeconds
	WithTrustEnv        = requests.WithTrustEnv
	WithVerify          = requests.WithVerify
)

Request options.

View Source
var (
	ErrTimeout = requests.ErrTimeout
)

Errors and sentinels.

Functions

This section is empty.

Types

type BasicAuth

type BasicAuth = requests.BasicAuth

Types, aliased so that a value of one is a value of the other.

type Browser

type Browser = browser.Browser

Types, aliased so that a value of one is a value of the other.

type BrowserOptions

type BrowserOptions = browser.Options

Types, aliased so that a value of one is a value of the other.

type BrowserRenderer

type BrowserRenderer = requests.BrowserRenderer

Types, aliased so that a value of one is a value of the other.

type BrowserRequest

type BrowserRequest = browser.Request

Types, aliased so that a value of one is a value of the other.

type BrowserResponse

type BrowserResponse = browser.Response

Types, aliased so that a value of one is a value of the other.

type CertificateVerifyError

type CertificateVerifyError = requests.CertificateVerifyError

Types, aliased so that a value of one is a value of the other.

type ClientCert

type ClientCert = requests.ClientCert

Types, aliased so that a value of one is a value of the other.

type ConnectionError

type ConnectionError = requests.ConnectionError

Types, aliased so that a value of one is a value of the other.

type Cookie = requests.Cookie

Types, aliased so that a value of one is a value of the other.

type CookieTypes

type CookieTypes = requests.CookieTypes

Types, aliased so that a value of one is a value of the other.

type Cookies

type Cookies = requests.Cookies

Types, aliased so that a value of one is a value of the other.

type DNSError

type DNSError = requests.DNSError

Types, aliased so that a value of one is a value of the other.

type ExtraFingerprints

type ExtraFingerprints = requests.ExtraFingerprints

Types, aliased so that a value of one is a value of the other.

type HTTPError

type HTTPError = requests.HTTPError

Types, aliased so that a value of one is a value of the other.

type HeaderPair

type HeaderPair = requests.HeaderPair

Types, aliased so that a value of one is a value of the other.

type HeaderTypes

type HeaderTypes = requests.HeaderTypes

Types, aliased so that a value of one is a value of the other.

type Headers

type Headers = requests.Headers

Types, aliased so that a value of one is a value of the other.

type ImpersonateError

type ImpersonateError = requests.ImpersonateError

Types, aliased so that a value of one is a value of the other.

type IncompleteRead

type IncompleteRead = requests.IncompleteRead

Types, aliased so that a value of one is a value of the other.

type InterfaceError

type InterfaceError = requests.InterfaceError

Types, aliased so that a value of one is a value of the other.

type InvalidSchema

type InvalidSchema = requests.InvalidSchema

Types, aliased so that a value of one is a value of the other.

type InvalidURL

type InvalidURL = requests.InvalidURL

Types, aliased so that a value of one is a value of the other.

type Option

type Option = requests.Option

Types, aliased so that a value of one is a value of the other.

type Page

type Page = browser.Page

Types, aliased so that a value of one is a value of the other.

type Param

type Param = requests.Param

Types, aliased so that a value of one is a value of the other.

type Params

type Params = requests.Params

Types, aliased so that a value of one is a value of the other.

type ProxyError

type ProxyError = requests.ProxyError

Types, aliased so that a value of one is a value of the other.

type Request

type Request = requests.Request

Types, aliased so that a value of one is a value of the other.

type RequestException

type RequestException = requests.RequestException

Types, aliased so that a value of one is a value of the other.

type RequestTypes

type RequestTypes = requests.RequestTypes

Types, aliased so that a value of one is a value of the other.

type Response

type Response = requests.Response

Types, aliased so that a value of one is a value of the other.

type SSLError

type SSLError = requests.SSLError

Types, aliased so that a value of one is a value of the other.

type ScreenshotOptions

type ScreenshotOptions = browser.ScreenshotOptions

Types, aliased so that a value of one is a value of the other.

type Session

type Session = requests.Session

Types, aliased so that a value of one is a value of the other.

type SessionClosed

type SessionClosed = requests.SessionClosed

Types, aliased so that a value of one is a value of the other.

type Timeout

type Timeout = requests.Timeout

Types, aliased so that a value of one is a value of the other.

type TooManyRedirects

type TooManyRedirects = requests.TooManyRedirects

Types, aliased so that a value of one is a value of the other.

type UnrewindableBodyError

type UnrewindableBodyError = requests.UnrewindableBodyError

Types, aliased so that a value of one is a value of the other.

type UnsupportedOptionError

type UnsupportedOptionError = requests.UnsupportedOptionError

Types, aliased so that a value of one is a value of the other.

Directories

Path Synopsis
Package browser is a pure-Go, dependency-light headless browser for scraping and automation.
Package browser is a pure-Go, dependency-light headless browser for scraping and automation.
cmd
shade command
Command shade is a curl_cffi-style HTTP client with browser TLS/HTTP fingerprint impersonation, and a pure-Go headless browser in the same binary.
Command shade is a curl_cffi-style HTTP client with browser TLS/HTTP fingerprint impersonation, and a pure-Go headless browser in the same binary.
Package impersonate exposes browser TLS/HTTP fingerprint presets ported from curl-impersonate (https://github.com/lexiforest/curl-impersonate), the native engine behind the Python curl_cffi project.
Package impersonate exposes browser TLS/HTTP fingerprint presets ported from curl-impersonate (https://github.com/lexiforest/curl-impersonate), the native engine behind the Python curl_cffi project.
internal
capturehello command
Command capturehello captures and parses the TLS ClientHello produced by an arbitrary command, so fingerprints can be inspected without external tools.
Command capturehello captures and parses the TLS ClientHello produced by an arbitrary command, so fingerprints can be inspected without external tools.
cli
Package cli implements the shade command line: one binary with two request paths and the servers that expose the browser to other tools.
Package cli implements the shade command line: one binary with two request paths and the servers that expose the browser to other tools.
Package requests is a Go port of the curl_cffi.requests API.
Package requests is a Go port of the curl_cffi.requests API.
Package server implements a Chrome DevTools Protocol (CDP) server on top of the pure-Go browser, so Puppeteer, Playwright and chromedp can drive it over a WebSocket the way they drive Chrome.
Package server implements a Chrome DevTools Protocol (CDP) server on top of the pure-Go browser, so Puppeteer, Playwright and chromedp can drive it over a WebSocket the way they drive Chrome.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL