-
Notifications
You must be signed in to change notification settings - Fork 18.3k
Description
I would like to get meaningful errors from the net package. Right now I can not decide if I got an error while a connection is already established or if it's while creating a TCP or TLS handshake.
I want to be able to decide from an error, if I can retry the call or not. Possibly net.Error interface has to get a new function to decide this easily.
The code shows a workaround wrapping a net.Dialer to decide if the error happened was during Dial or an already established connection.
What version of Go are you using (go version
)?
go version go1.9.4 linux/amd64
Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (go env
)?
GOARCH="amd64"
GOBIN="/home/sszuecs/go/bin"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/sszuecs/go"
GORACE=""
GOROOT="/usr/share/go"
GOTOOLDIR="/usr/share/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build480041304=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
What did you do?
package main
import (
"context"
"fmt"
"log"
"net"
"net/http"
"time"
)
type proxyError struct {
err error
code int
dialingFailed bool
}
func (e proxyError) Error() string {
return fmt.Sprintf("proxyError: %v", e.err)
}
type myDialer struct {
net.Dialer
f func(ctx context.Context, network, addr string) (net.Conn, error)
}
func myDialerNew(d net.Dialer) *myDialer {
return &myDialer{
Dialer: d,
f: d.DialContext,
}
}
func (dc *myDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
con, err := dc.f(ctx, network, addr)
if err != nil {
return nil, proxyError{
err: err,
dialingFailed: true,
}
}
return con, nil
}
func main() {
tr := &http.Transport{
DialContext: myDialerNew(net.Dialer{
Timeout: 100 * time.Millisecond,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
TLSHandshakeTimeout: 200 * time.Millisecond,
ResponseHeaderTimeout: 500 * time.Millisecond,
IdleConnTimeout: 5 * time.Second,
}
go func(rt http.RoundTripper) {
for {
time.Sleep(1 * time.Second)
req, err := http.NewRequest("GET", "http://127.0.0.1:10000/", nil)
if err != nil {
log.Printf("Failed to create request: %v", err)
continue
}
resp, err := rt.RoundTrip(req)
if err != nil {
if perr, ok := err.(proxyError); ok {
if nerr, ok := perr.err.(interface {
Temporary() bool
Timeout() bool
}); ok {
log.Printf("Failed to do roundtrip %v %v: %v", nerr.Temporary(), nerr.Timeout(), perr.err)
}
log.Printf("dial error: %v", perr)
} else {
if nerr, ok := err.(interface {
Temporary() bool
Timeout() bool
}); ok {
log.Printf("Failed to do roundtrip %v %v: %v", nerr.Temporary(), nerr.Timeout(), err)
}
log.Printf("not a dial error: %v", err)
}
continue
}
resp.Body.Close()
log.Printf("resp status: %v", resp.Status)
}
}(tr)
ch := make(chan struct{})
<-ch
}
What did you expect to see?
I would like to do:
if nerr, ok := err.(net.Error); ok {
if nerr.DialError() {
retrySend(req)
}
}
What did you see instead?
net.Error has Temporary() and Timeout() defined and rest of the errors are "untyped" fmt.Errorf("connection refused").
I would like to be able to dispatch by retrieable error. In an http proxy/loadbalancer this means you want to decide if you sent data or not, because if you already sent data, you should not retry.
I have to built a workaround as shown above in zalando/skipper@d3fa9e9 .