http.DefaultClient
“1024 lashes for anyone using http.DefaultClient in their code” — that’s the kind of “joke” you hear at our
standups. The problem is that the defaults don’t set any timeouts. There’s a great article on the topic on the Cloudflare blog1.
We’ll try to keep http.DefaultClient from being used in our code at all. There are at least two tools for this:
wreulicke/http-timeout2golangci-lint
For the experiments we’ll need a sample piece of code:
package main
import (
"net/http"
. "net/http"
)
func main() {
_ = http.DefaultClient
_ = DefaultClient
http.Get("url")
Get("url")
http.Post("url", "application/bar", nil)
Post("url", "application/bar", nil)
http.PostForm("url", nil)
PostForm("url", nil)
http.ListenAndServe(":1337", nil)
ListenAndServe(":1337", nil)
http.Handle("/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
Handle("/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
http.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
_ = http.Server{}
_ = Server{}
_ = &http.Server{}
_ = &Server{}
_ = http.Client{}
_ = Client{}
_ = &http.Client{}
_ = &Client{}
}
wreulicke/http-timeout
This tool isn’t part of the golangci-lint linter set; it uses Go’s internal code-checking machinery. That means
you’ll have to run it separately from golangci-lint, which entails reworking your existing pipelines.
The current releases of this tool trip on the following error:
http-timeout ./...
http-timeout: internal error: package "net/http" without types was imported from "<your-module-name>"
To make the tool work, you need to rebuild it after updating its golang.org/x/tools dependency:
go get -u golang.org/x/tools
go mod tidy
goreleaser build --snapshot --rm-dist
After that the tool handles the job perfectly:
.../main.go:10:6: Do not use net/http.DefaultClient because default client has no timeout
.../main.go:13:2: Do not use net/http.Get because default client has no timeout
.../main.go:16:2: Do not use net/http.Post because default client has no timeout
.../main.go:19:2: Do not use net/http.PostForm because default client has no timeout
.../main.go:22:2: Do not use net/http.ListenAndServe because default http server has no timeout
.../main.go:25:2: Do not use net/http.Handle because default http server has no timeout
.../main.go:28:2: Do not use net/http.HandleFunc because default http server has no timeout
.../main.go:37:6: Do not use net/http.Client with no Timeout
.../main.go:40:7: Do not use net/http.Client with no Timeout
.../main.go:31:6: Do not use net/http.Server with no ReadTimeout
.../main.go:31:6: Do not use net/http.Server with no WriteTimeout
.../main.go:34:7: Do not use net/http.Server with no ReadTimeout
.../main.go:34:7: Do not use net/http.Server with no WriteTimeout
golangci-lint
You can’t simply set up a check for required http.Client struct fields with golangci-lint. But we’ll try to get
close to the result above.
The configuration below detects and forbids using the default client. Don’t copy it blindly — fold the necessary bits into your existing configuration:
run:
concurrency: 8
linters:
disable-all: true
enable:
- forbidigo
- exhaustruct
linters-settings:
forbidigo:
exclude-godoc-examples: true
analyze-types: true
forbid:
- p: ^http\.DefaultClient.*$
msg: 'https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/'
- p: ^http\.Get.*$
msg: 'https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/'
- p: ^http\.Post.*$
msg: 'https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/'
- p: ^http\.PostForm.*$
msg: 'https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/'
- p: ^http\.ListenAndServe.*$
msg: 'https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/'
- p: ^http\.Handle.*$
msg: 'https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/'
- p: ^http\.HandleFunc.*$
msg: 'https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/'
exhaustruct:
exclude:
- ^http\.Server$
- ^http\.Client$
Conclusion
It’s very easy to forget about the problems with http.DefaultClient if your service isn’t running under load,
traffic is small, and these nuances don’t bother you.
I’ve highlighted a real problem and offered a way to deal with it. Let me know if I missed something, or if you can suggest a better approach.