{"id":13779409,"url":"https://github.com/ooni/oohttp","last_synced_at":"2026-04-06T04:31:53.095Z","repository":{"id":38847366,"uuid":"395291414","full_name":"ooni/oohttp","owner":"ooni","description":"Fork of Go stdlib's net/http that works with alternative TLS libraries like refraction-networking/utls.","archived":false,"fork":false,"pushed_at":"2025-02-08T22:32:14.000Z","size":7738,"stargazers_count":75,"open_issues_count":2,"forks_count":14,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-10-29T06:34:42.612Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://ooni.org","language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ooni.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-08-12T11:11:36.000Z","updated_at":"2025-08-13T09:36:38.000Z","dependencies_parsed_at":"2023-10-11T16:42:07.787Z","dependency_job_id":"4f0ab080-fed1-41d4-8a43-072d9c0a8b40","html_url":"https://github.com/ooni/oohttp","commit_stats":{"total_commits":1615,"total_committers":372,"mean_commits":4.341397849462366,"dds":0.6959752321981424,"last_synced_commit":"c1a18ae0b4d1b63ad2b90e69073659423fa4c46a"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"purl":"pkg:github/ooni/oohttp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ooni%2Foohttp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ooni%2Foohttp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ooni%2Foohttp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ooni%2Foohttp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ooni","download_url":"https://codeload.github.com/ooni/oohttp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ooni%2Foohttp/sbom","scorecard":{"id":707992,"data":{"date":"2025-08-11","repo":{"name":"github.com/ooni/oohttp","commit":"1b3ba112968a49b4edff7b31078165a4ab765437"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/10 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/go.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/ooni/oohttp/go.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/ooni/oohttp/go.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":3,"reason":"7 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2025-3754 / GHSA-2x5j-vhc8-9cwm","Warn: Project is vulnerable to: GO-2025-3638 / GHSA-pmc3-p9hx-jq96","Warn: Project is vulnerable to: GO-2024-3321 / GHSA-v778-237x-gjrc","Warn: Project is vulnerable to: GO-2025-3487 / GHSA-hcg3-q754-cr77","Warn: Project is vulnerable to: GO-2024-3333","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-22T07:17:25.100Z","repository_id":38847366,"created_at":"2025-08-22T07:17:25.100Z","updated_at":"2025-08-22T07:17:25.100Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31460020,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"online","status_checked_at":"2026-04-06T02:00:07.287Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T18:01:04.840Z","updated_at":"2026-04-06T04:31:53.074Z","avatar_url":"https://github.com/ooni.png","language":"Go","funding_links":[],"categories":["Credits"],"sub_categories":[],"readme":"# github.com/ooni/oohttp\n\nThis repository contains a fork of Go's standard library `net/http`\npackage including patches to allow using this HTTP code with\n[github.com/refraction-networking/utls](\nhttps://github.com/refraction-networking/utls).\n\n## Motivation and maintenance\n\nWe created this package [because it simplifies testing URLs using\nspecific TLS Client Hello messages](https://github.com/ooni/probe/issues/1731). We\nwill continue to keep it up to date as long as it serves our goals.\n\n## Limitations\n\n1. This fork does not include a fork of `pprof` because such package\ndepends on the stdlib's `internal/profile` package. If your code uses\n`http/pprof`, then you cannot switch to this fork.\n\n2. This fork's `httptrace` package is partly broken because there\nis no support for network events tracing, which requires the stdlib's\n`internal/nettrace` package. If your code depends on network events\ntracing, then you cannot switch to this fork.\n\n3. This fork tracks the latest stable version of Go by merging\nupstream changes into the `main` branch. This means that it _may_\nnot be working with earlier versions of Go. For example, when\nwriting this note we are at Go 1.16 and this package accordingly\nuses `io.ReadAll`. If you are compiling using Go 1.15, you should\nget build errors because `io.ReadAll` did not exist before Go 1.16.\n\n## Usage\n\nThe follow diagram shows your typical app architecture when you're\nusing this library as an alternative HTTP library.\n\n![architecture](oohttp.png)\n\nFrom the diagram, it stems that we need to discuss two interfaces:\n\n1. the interface between your code and this library;\n\n2. the interface between this library and a TLS library.\n\n### Interface between your code and this library\n\nThe simplest approach is to just replace\n\n```Go\nimport \"net/http\"\n```\n\nwith\n\n```Go\nimport \"github.com/ooni/oohttp\"\n```\n\neverywhere in your codebase.\n\nThis approach is not practical when your code or a dependency of yours\nalready assumes `net/http`. In such a case, use\n[stdlibwrapper.go](stdlibwrapper.go),\nwhich provides you with an adapter implementing `net/http.Transport`. It\ntakes the stdlib's `net/http.Request` as input and returns the stdlib's\n`net/http.Response` as output. But, internally, it uses the `Transport` defined\nby this library:\n\n```Go\n// StdlibTransport is an adapter for integrating net/http dependend code.\n// It looks like an http.RoundTripper but uses this fork internally.\ntype StdlibTransport struct {\n\t*Transport\n}\n\n// RoundTrip implements the http.RoundTripper interface.\nfunc (txp *StdlibTransport) RoundTrip(stdReq *http.Request) (*http.Response, error) {\n\t// ...\n}\n```\n\nSee [example/internal/utlsx/utlsx.go](example/internal/utlsx/utlsx.go) for a real\nworld example where we use `StdlibTransport` to be `net/http` compatible.\n\n### Interface between this library and any TLS library\n\nYou need to write a wrapper for your definition of the TLS connection that\nimplements the [TLSConn](tlsconn.go) interface:\n\n```Go\n// TLSConn is the interface representing a *tls.Conn compatible\n// connection, which could possibly be different from a *tls.Conn\n// as long as it implements the interface. You can use, for\n// example, refraction-networking/utls instead of the stdlib.\ntype TLSConn interface {\n\t// net.Conn is the underlying interface\n\tnet.Conn\n\n\t// ConnectionState returns the ConnectionState according\n\t// to the standard library.\n\tConnectionState() tls.ConnectionState\n\n\t// HandshakeContext performs an TLS handshake bounded\n\t// in time by the given context.\n\tHandshakeContext(ctx context.Context) error\n\n\t// NetConn returns the underlying net.Conn\n\tNetConn() net.Conn\n}\n```\n\nIf you are using `crypto/tls`, then\nyour `tls.Conn` is already a valid `TLSConn` and you don't need to do\nanything in particular. (However, if you are using\n`crypto/tls`, you shouldn't probably be using `oohttp` at all!)\n\nIf you are using `refraction-networking/utls` (or `Yawning/utls`), you need to write an\nadapter. Your TLS connection is\nalready a `net.Conn`. But you need to implement `ConnectionState`. And\nyou also need to implement `HandshakeContext`.\n\nThe following code shows, for reference, how we initially implemented\nthis functionality in [ooni/probe-cli](https://github.com/ooni/probe-cli):\n\n```Go\n// uconn is an adapter from utls.UConn to TLSConn.\ntype uconn struct {\n\t*utls.UConn\n}\n\n// ConnectionState implements TLSConn's ConnectionState.\nfunc (c *uconn) ConnectionState() tls.ConnectionState {\n\tustate := c.UConn.ConnectionState()\n\treturn tls.ConnectionState{\n\t\tVersion:                     ustate.Version,\n\t\tHandshakeComplete:           ustate.HandshakeComplete,\n\t\t//\n\t\t// [...]\n\t\t//\n\t\t// You get the idea. You need to copy all fields. We\n\t\t// intentionally snip early here so we are not forced\n\t\t// to ensure this code is always up-to-date.\n\t}\n}\n\n// HandshakeContext implements TLSConn's HandshakeContext.\nfunc (c *uconn) HandshakeContext(ctx context.Context) error {\n\terrch := make(chan error, 1)\n\tgo func() {\n\t\terrch \u003c- c.UConn.Handshake()\n\t}()\n\tselect {\n\tcase err := \u003c-errch:\n\t\treturn err\n\tcase \u003c-ctx.Done():\n\t\treturn ctx.Err()\n\t}\n}\n```\n\nSee [example/internal/utlsx/utlsx.go](example/internal/utlsx/utlsx.go) for a real-world\nexample of writing a `TLSConn` compatible adapter.\n\nOnce you have the adapter in place, you should write a factory for creating\nthe specific uTLS connection you'd like to use; for example:\n\n```Go\n// utlsFactory creates a new uTLS connection.\nfunc utlsFactory(conn net.Conn, config *tls.Config) oohttp.TLSConn {\n\tuConfig := \u0026utls.Config{\n\t\tRootCAs:                     config.RootCAs,\n\t\tNextProtos:                  config.NextProtos,\n\t\tServerName:                  config.ServerName,\n\t\tInsecureSkipVerify:          config.InsecureSkipVerify,\n\t\tDynamicRecordSizingDisabled: config.DynamicRecordSizingDisabled,\n\t}\n\treturn \u0026uconn{utls.UClient(conn, uConfig, utls.HelloFirefox_55)}\n}\n```\n\nFinally, you should configure `utlsFactory` as being your `TLSClientFactory`\nby setting the corresponding field of the `oohttp.Transport`:\n\n```Go\ntxp := \u0026oohttp.Transport{\n\t// ...\n\tTLSClientFactory: utlsFactory,\n}\n```\n\nThis `TLSClientFactory` will also work when using a proxy.\n\nSee [example/example-utls](example/example-utls) for a complete example\nthat does not use a proxy. Likewise, see [example/example-proxy](example/example-proxy)\nfor an example that uses a proxy. A more complex example, where we\noverride `Transport.DialTLSContext` is\n[example/example-utls-with-dial](example/example-utls-with-dial).\n\n## Issue tracker\n\nPlease, report issues in the [ooni/probe](https://github.com/ooni/probe)\nrepository. Make sure you mention `oohttp` in the issue title.\n\n## Patches\n\nWe started from the `src/net/http` subtree at `go1.16` and we\napplied patches to fork the codebase ([#1](https://github.com/ooni/oohttp/pull/1),\n[#2](https://github.com/ooni/oohttp/pull/2) and [#3](\nhttps://github.com/ooni/oohttp/pull/3)). Then, we introduced\nthe `http.TLSConn` abstraction that allows using different TLS\nlibraries ([#4](https://github.com/ooni/oohttp/pull/4)). We\nadded the `StdlibTransport` wrapped in [#8](https://github.com/ooni/oohttp/pull/8).\nand [#9](https://github.com/ooni/oohttp/pull/9). We added support\nfor `TLSClientFactory` in [#16](https://github.com/ooni/oohttp/pull/16),\n[#19](https://github.com/ooni/oohttp/pull/19), and\n[#22](https://github.com/ooni/oohttp/pull/22).\n\nEvery major change is documented by a pull request. We may push\nminor changes (e.g., updating docs) directly on the `main` branch.\n\n## Update procedure\n\n(Adapted from refraction-networking/utls instructions.)\n\n- [ ] update [UPSTREAM](UPSTREAM), commit the change, and then\nrun the `./tools/merge.bash` script to merge from upstream;\n\n- [ ] solve the very-likely merge conflicts and ensure [the original spirit of the\npatches](#patches) still hold;\n\n- [ ] make sure you synch [./internal/safefilepath](./internal/safefilepath) with the\n`./src/internal/safefilepath` of the Go release you're merging from;\n\n- [ ] make sure the codebase does not assume `*tls.Conn` *anywhere* (`git grep -n '\\*tls\\.Conn'`)\nand otherwise replace `*tls.Conn` with `TLSConn`;\n\n- [ ] make sure the codebase does not call `tls.Client` *anywhere* except for `tlsconn.go`\n(`git grep -n 'tls\\.Client'`) and otherwise replace `tls.Client` with `TLSClientFactory`;\n\n- [ ] diff with upstream (`./tools/compare.bash`) and make sure what you see\nmakes sense in terms of the original patches, save the diff, and include it into\nthe PR to document the actual changes between us and upstream.\n\n- [ ] ensure `go build -v ./...` still works;\n\n- [ ] ensure `go test -race ./...` is still passing;\n\n- [ ] ensure [stdlibwrapper.go](stdlibwrapper.go) copies all\nthe `Request` and `Response` fields;\n\n- [ ] run `go get -u -v ./... \u0026\u0026 go mod tidy`;\n\n- [ ] make sure the Go version used by GitHub actions is correct;\n\n- [ ] commit the changes and push `merged-main` to gitub;\n\n- [ ] open a PR using this check-list as part of the PR text and merge it *using a merge commit*;\n\n- [ ] create a new working branch to update the examples;\n\n- [ ] ensure [example/internal/utlsx/utlsx.go](example/internal/utlsx/utlsx.go)\ncopies all the `ConnectionState` fields;\n\n- [ ] go to [example](example), update *each submodule* and ensure\n`go test -race ./...` passes in each submodule;\n\n- [ ] open a PR and merge it *using a merge commit*.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fooni%2Foohttp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fooni%2Foohttp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fooni%2Foohttp/lists"}