Open-source project
wangluozhe/requests avatar
wangluozhe/requests

requests is a Go HTTP client that can change its TLS fingerprint

用于快速请求HTTP或HTTPS,并支持修改ja3、ja4指纹

571 stars119 forksGoGPL-3.0

At a glance

What is it?
requests is a Go library modelled on Python's requests that defaults to HTTP/2, falls back to HTTP/1.1 when the connection fails, and lets you set the JA3, JA4, TLS and HTTP/2 fingerprints of the connection. It is GPL-3.0 licensed, needs Go 1.24, and pulls in uTLS and a custom http2 stack to do it.
Who is it for?
requests suits a Go developer who wants the ergonomics of Python's requests library and needs to control the TLS and HTTP/2 fingerprints of the client, whether for interoperability testing against a picky server or for a research stack that speaks a specific protocol dialect.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The Python requests API, in Go

The README opens with the feature list, and the fifth line sets the framing: this module is modelled on Python's requests library.

That modelling shows in the API shape immediately. A GET is one call:

go
r, err := requests.Get("https://api.github.com/events", nil)

And the other verbs follow the same pattern, with a data object built up in a couple of lines:

go
data := url.NewData()
data.Set("key","value")
r, err := requests.Post("http://httpbin.org/post", &url.Request{Data: data})
r, err = requests.Delete("http://httpbin.org/delete")
r, err = requests.Head("http://httpbin.org/get")
r, err = requests.Options("http://httpbin.org/get")

For a Go developer who has used the Python library, that is a familiar surface. The response object exposes the same kind of accessors, with r.Text() returning the body as a string, and the README links to the Python requests Response documentation for reference.

The difference from the standard library is in what the Go ecosystem does not give you. net/http is deliberately low level, so cookies, redirects, session reuse and form encoding all have to be assembled by hand or pulled in from third-party packages. This library puts them in one place, in the way the Python original did.

The parameters live in a separate url package inside the same module, and you import both:

go
import (
    "github.com/wangluozhe/requests"
    "github.com/wangluozhe/requests/url"
)

That split is where the request options live, and it is where the interesting behaviour is, because the Request struct carries settings for TLS fingerprints, HTTP/2 settings, proxies and redirect policy alongside the ordinary parameters.

HTTP/2 by default, with a fallback

The first item in the feature list is about protocol negotiation.

It supports HTTP/2, connecting by default over HTTP/2, and falling back to an HTTP/1.1 connection if that fails.

Defaulting to HTTP/2 is a departure from what most Go HTTP clients do, since the standard library negotiates HTTP/1.1 unless the transport is configured otherwise. The fallback is the important half of that sentence, because a server that refuses the HTTP/2 upgrade should not leave you with a broken client, and the requirement to handle both means the implementation carries its own HTTP/2 stack rather than delegating to net/http.

The dependency list confirms it. github.com/wangluozhe/chttp at v1.1.1 is a separate HTTP/2 implementation, and it is the project's own module rather than a well-known third-party one, which means the HTTP/2 behaviour, including the SETTINGS frame contents, is something the library controls and can shape.

The examples directory backs that up with http2settings-test.go and tlsextensions-test.go, which are the two places where a client fingerprint really lives: the HTTP/2 SETTINGS frame parameters and the TLS extension list.

There is also a per-request override. The Request struct carries a ForceHTTP1 field, typed as *bool, and setting it to url.Bool(true) forces the use of HTTP/1 for that request. That is the escape hatch for the cases where a server advertises HTTP/2 but misbehaves on it, which is common enough in older infrastructure.

What changing a TLS fingerprint actually means

The remaining four features are all about impersonation of a specific client stack, and they are the reason this library exists separately from a convenience wrapper.

It supports modifying the JA3 fingerprint, supports HTTP/2 combined with a JA3 fingerprint, supports modifying the TLS fingerprint, and supports modifying the HTTP/2 fingerprint.

JA3 and JA4 are hashes of the parameters a client presents during the TLS handshake. JA3 summarises the cipher suites, extensions and elliptic curves a client offers, and two clients with the same JA3 are very likely to be running the same TLS stack. JA4 does something similar for a newer generation of clients, and it is the one that has been adopted more widely for fingerprinting traffic. Servers use these values to recognise client libraries, usually in order to serve different behaviour to different clients.

So changing the fingerprint means presenting a handshake characteristic of a different TLS implementation, and changing the HTTP/2 fingerprint means presenting HTTP/2 SETTINGS that match a different implementation. The library does this through github.com/refraction-networking/utls at v1.8.2, which is a uTLS fork, and uTLS is the standard way to do this in Go because the standard library gives no control over the ClientHello contents.

There is a legitimate use for this and a misuse, and it is worth being plain about which is which. Legitimate uses are interoperability testing, research into TLS fingerprinting, checking that your own server responds the same way to different clients, and reproducing a bug that only appears for one client stack. The misuse is evading a service's bot detection or access controls, which is generally against the terms of the service and, in some jurisdictions, unlawful.

The library itself takes no position on that, and it is worth noting that a tool for setting a legitimate TLS client fingerprint is also a tool for setting an illegitimate one, and that the feature exists in most TLS libraries with fingerprint control.

The cost of the feature is real, and it shows up in the dependency list. uTLS replaces the standard TLS stack for the affected connections, so you lose the standard library's connection pooling behaviour and its HTTP/2 integration, which is why the project carries its own HTTP/2 implementation.

Flexible parameter passing, and the pointer fields

The second feature area is the one that makes the library pleasant to use day to day, and it is a change of design rather than a Python port.

The parameters in the Request struct, meaning Params, Headers, Cookies, Data, Files and Body, now support the type any. You are no longer limited to specific structs, and you can pass a map, a string or another common type, and the library converts it automatically.

The supported list is long and specific per field. For Params, the query string, the options include a *url.Params, a string such as key=value&a=1, and maps of string, []string, int, []int, float64, []float64 and interface{}, with recursive parsing for the interface{} case. For Headers, a *http.Header, a string in header form such as User-Agent: abc with an Accept line, and maps whose values may be string, int, float64 or bool. Cookies follow the same pattern with a *cookiejar.Jar and the same map types. Data, the form body, mirrors Params including the recursive interface{} parsing. Files accepts a map from field name to file path, with the filename extracted automatically and the content type left empty. Body takes a string, a []byte or an io.Reader.

The consequence is that a request can be written in one line where the Python original would need a helper:

go
req := &url.Request{
    // 直接使用 map 传递查询参数
    Params: map[string]string{
        "page": "1",
        "size": "20",
    },
    // 直接使用 map 传递 Header
    Headers: map[string]string{
        "Authorization": "Bearer token123",
    },
    // 直接传递字符串 Body
    Body: "raw body content",
}

The queries are equally flexible. There is a constructor route with a Set call, and a ParseParams route that takes a map or a query string and returns the params object, so the same request can be written three ways.

One field group needs care. AllowRedirects, Verify, RandomJA3 and ForceHTTP1 are typed as *bool rather than bool, and the README explains why: nil means use the Session default, while url.Bool(true) or url.Bool(false) sets it explicitly.

go
req.Verify = url.Bool(false)        // 显式关闭 TLS 证书验证
req.AllowRedirects = url.Bool(false) // 显式禁用重定向
req.ForceHTTP1 = url.Bool(true)      // 显式强制使用 HTTP/1

The pointer type is there so that an unset field can be distinguished from an explicit false, which is exactly the distinction needed when a Session carries the default. Turning off certificate verification that way is a debugging tool, and it is worth keeping it out of anything that runs unattended.

Debug output, sessions and proxies

A few conveniences round out the API, and the debug mode is the one that helps most when something is not behaving as expected.

Setting debug mode makes it globally available and prints the results to stderr, so you can see the url.Request struct, the raw request and the raw response contents.

go
import (
    "github.com/wangluozhe/requests"
)

// 打开Debug模式
requests.SetDebug(true)

// 请求代码...

// 取消Debug模式
requests.SetDebug(false)

Printing to stderr rather than stdout is the correct choice for a library, because stdout is often piped, and the request struct plus the raw bytes are exactly what you need when the difference between what you sent and what you meant to send is the bug.

Proxies are set on the request, with the value being a proxy address, and credentials can be included in the same string:

go
req := url.NewRequest()
req.Proxies = "http://127.0.0.1:32768"
// 设置带账号密码的代理:req.Proxies = "http://username:password@ip:port"

r, err := requests.Get("https://api.github.com/events", req)

A single string field rather than a structured proxy configuration is a simplification, and it means the per-request proxy is the only knob unless the Session provides more.

Sessions live in sessions.go and the example directory has session-test.go, so sessions are a supported concept rather than an afterthought. The other examples give a good picture of the tested surface: cookies-test.go, data-test.go, json-test.go, middleware-test.go, multi-threading-test.go, custom_settings.go, orderHeader-test.go, verify-test.go, postForm-test.go, encode-test.go, body-test.go, get-test.go, zero-value-fix-test.go, and the crypto examples for HMAC, MD5, RC4 and SHA.

The middleware example is worth noting: it indicates that requests can be wrapped with your own handling, which is how you would add retries, logging or tracing without forking the library.

Where requests fits, and where it stops

The comparison worth making is with the Go standard library, and with the other TLS-fingerprinting libraries in Go.

Against net/http, the difference is ergonomics and control. net/http gives you a transport and nothing else, so cookies, sessions, form encoding and redirect policy are all work you do. This library gives you the Python-style surface and adds fingerprint control that the standard library does not offer at any level. The cost is a dependency tree with uTLS, a custom HTTP/2 stack, brotli compression, simplejson, uuid and x/crypto, and a GPL-3.0 licence rather than the permissive BSD licence that the standard library carries.

That licence point matters for adoption. GPL-3.0 is fine for a tool you distribute, and awkward for a closed-source product that links it, so a company evaluating this library needs to read the LICENSE file and think about how the library is used. A permissive alternative exists in the ecosystem for the fingerprinting part alone, so the licence argument only applies to the convenience wrapper as a whole.

The limits are structural rather than accidental. Because the fingerprinting requires uTLS, the standard library's connection pooling is replaced for those requests, and the project carries its own HTTP/2 implementation to compensate, which is a larger surface for a client library to own. The proxy configuration is a single string, so there is no structured proxy list, no per-scheme proxy and no authentication beyond a URL. And the parameter conversion, while convenient, is done at runtime with reflection-style handling of many types, so a wrong type produces a conversion behaviour you have to learn rather than a compile error.

There is also the question of what the fingerprint feature is for, and it deserves saying plainly rather than leaving it in the code. A client that can present any TLS fingerprint can present a fingerprint that is not its own, and that capability has legitimate uses in testing and research and illegitimate ones in evading access controls. The library is a well-built tool with an unusually broad feature set for a convenience HTTP client, the last push to the repository was on 2026-04-22, and it is GPL-3.0 licensed with a Chinese-language README and documentation site at tls.wangluozhe.com.

For a Go team that needs fingerprint control, the alternative is assembling uTLS and a custom HTTP/2 transport yourself, which is most of what this library already does. For a Go team that does not, net/http plus a cookie jar is less code and no licence obligations.

Editorial conclusion

requests suits a Go developer who wants the ergonomics of Python's requests library and needs to control the TLS and HTTP/2 fingerprints of the client, whether for interoperability testing against a picky server or for a research stack that speaks a specific protocol dialect. It is the wrong tool for an ordinary API client, because the standard library covers that case without a third-party dependency and without a GPL-3.0 obligation, and it is the wrong tool for anyone using fingerprint control to defeat a service's bot detection without authorisation. Before adopting it, read the examples directory, where ja3-test.go, ja4-test.go, http2settings-test.go and tlsextensions-test.go show the fingerprint options in use, and check the LICENSE file since GPL-3.0 has consequences for a closed-source application that links it.

Frequently asked questions

What is the requests library for Go?

It is a Go HTTP client modelled on Python's requests library, offering a familiar API with Get, Post, Delete, Head and Options helpers, a Response object, session support and proxies. It connects over HTTP/2 by default and falls back to HTTP/1.1 when that fails.

How do I install requests in a Go project?

Run go get github.com/wangluozhe/requests for the latest version, or go get github.com/wangluozhe/[email protected] to pin a specific release. The module requires Go 1.24.0 and the documentation badge points at pkg.go.dev for the API reference.

What TLS fingerprint options does requests offer?

The README lists support for modifying the JA3 fingerprint, for HTTP/2 combined with a JA3 fingerprint, for modifying the TLS fingerprint and for modifying the HTTP/2 fingerprint. The TLS side is implemented with the refraction-networking/utls fork, and the HTTP/2 side with the project's own chttp module.

What types can I pass for query parameters and headers?

The Params, Headers, Cookies, Data, Files and Body fields of the Request struct accept any. For Params and Data the supported forms include a url.Params or url.Values struct, a query string, and maps of string, []string, int, []int, float64, []float64 and interface{}, with recursive parsing for the interface{} case. Body accepts a string, a []byte or an io.Reader.

Why are AllowRedirects, Verify, RandomJA3 and ForceHTTP1 pointer types?

They are typed as *bool so that nil, meaning use the Session default, can be distinguished from an explicit false. The README shows them being set with url.Bool, for example req.Verify = url.Bool(false) to turn off TLS certificate verification and req.ForceHTTP1 = url.Bool(true) to force HTTP/1 for that request.

How do I debug a request?

Call requests.SetDebug(true), which enables debug output globally and prints to stderr the url.Request struct, the raw request and the raw response contents, and call requests.SetDebug(false) to turn it off. Because the output goes to stderr it does not interfere with piping stdout.

Official sources

  1. License: GPL-3.0
  2. Project website
  3. README
  4. Releases
  5. wangluozhe/requests on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/wangluozhe-requests.svg)](https://hysenlabs.com/projects/wangluozhe-requests)

Was this useful?