Skip to content

Commit

Permalink
Add base support for non http error codes - WIP on #895
Browse files Browse the repository at this point in the history
This currently only supports http and the errors currently codified are
based on what errors were seen the most.

Some errors have new custom messages - this has been done to errors with
changing messages with the new custom message primarely removing the
changing parts, usually ip/ports and such.
  • Loading branch information
mstoykov committed Jan 29, 2019
1 parent 9af59bc commit df4aa05
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 3 deletions.
104 changes: 104 additions & 0 deletions js/modules/k6/http/error_codes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package http

import (
"crypto/tls"
"crypto/x509"
"net"
"syscall"

"github.com/pkg/errors"
"golang.org/x/net/http2"
)

type errCode uint32

const (
// non specific
defaultErrorCode errCode = 1000
defaultNetNonTCPErrorCode errCode = 1010
http2GoAwayErrorCode errCode = 1091
http2StreamErrorCode errCode = 1092
http2ConnectionErrorCode errCode = 1093
// DNS errors
defaultDNSErrorCode errCode = 1100
dnsNoSuchHostErrorCode errCode = 1101
// tcp errors
defaultTCPErrorCode errCode = 1200
tcpBrokenPipeErrorCode errCode = 1201
tcpDialErrorCode errCode = 1210
tcpDialTimeoutErrorCode errCode = 1211
tcpDialRefusedErrorCode errCode = 1212
tcpResetByPeerErrorCode errCode = 1220
// TLS errors
defaultTLSErrorCode errCode = 1300
x509UnknownAuthorityErrorCode errCode = 1310
x509HostnameErrorCode errCode = 1311
)

// If a given errorCode from above need to overwrite the error message this should be provided in
// this map
var customErrorMsgMap = map[errCode]string{
tcpResetByPeerErrorCode: "write: connection reset by peer",
tcpDialTimeoutErrorCode: "dial: i/o timeout",
tcpDialRefusedErrorCode: "dial: connection refused",
tcpBrokenPipeErrorCode: "write: broken pipe",
dnsNoSuchHostErrorCode: "lookup: no such host",
http2GoAwayErrorCode: "http2: received GoAway",
http2StreamErrorCode: "http2: stream error",
http2ConnectionErrorCode: "http2: connection error",
x509HostnameErrorCode: "x509: certificate doesn't match hostname",
}

func errorCodeForError(err error) errCode {
switch e := errors.Cause(err).(type) {
case *net.DNSError:
switch e.Err {
case "no such host": // defined as private in the go stdlib
return dnsNoSuchHostErrorCode
default:
return defaultDNSErrorCode
}
case *http2.GoAwayError:
// TODO: Add different error for all errcode for goaway
return http2GoAwayErrorCode
case *http2.StreamError:
// TODO: Add different error for all errcode for stream error
return http2StreamErrorCode
case *http2.ConnectionError:
// TODO: Add different error for all errcode for connetion error
return http2ConnectionErrorCode
case *net.OpError:
if e.Net != "tcp" && e.Net != "tcp6" {
// TODO: figure out how this happens
return defaultNetNonTCPErrorCode
}
if e.Op == "write" {
switch e.Err.Error() {
case syscall.ECONNRESET.Error():
return tcpResetByPeerErrorCode
case syscall.EPIPE.Error():
return tcpBrokenPipeErrorCode
}
}
if e.Op == "dial" {
if e.Timeout() {
return tcpDialTimeoutErrorCode
}
switch e.Err.Error() {
case syscall.ECONNREFUSED.Error():
return tcpDialRefusedErrorCode
}
return tcpDialErrorCode
}
return defaultTCPErrorCode

case *x509.UnknownAuthorityError:
return x509UnknownAuthorityErrorCode
case *x509.HostnameError:
return x509HostnameErrorCode
case *tls.RecordHeaderError:
return defaultTLSErrorCode
default:
return defaultErrorCode
}
}
55 changes: 55 additions & 0 deletions js/modules/k6/http/error_codes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package http

import (
"fmt"
"net"
"syscall"
"testing"

"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"golang.org/x/net/http2"
)

func TestErrorCodeForError(t *testing.T) {
// TODO find better way to test
var (
nonTCPError = new(net.OpError)
econnreset = new(net.OpError)
epipeerror = new(net.OpError)
econnrefused = new(net.OpError)
tcperror = new(net.OpError)
)
nonTCPError.Net = "something"

econnreset.Net = "tcp"
econnreset.Op = "write"
econnreset.Err = syscall.ECONNRESET

epipeerror.Net = "tcp"
epipeerror.Op = "write"
epipeerror.Err = syscall.EPIPE

econnrefused.Net = "tcp"
econnrefused.Op = "dial"
econnrefused.Err = syscall.ECONNREFUSED

tcperror.Net = "tcp"

var testTable = map[error]errCode{
fmt.Errorf("random error"): defaultErrorCode,
new(http2.ConnectionError): http2ConnectionErrorCode,
new(http2.StreamError): http2StreamErrorCode,
new(http2.GoAwayError): http2GoAwayErrorCode,
nonTCPError: defaultNetNonTCPErrorCode,
econnreset: tcpResetByPeerErrorCode,
epipeerror: tcpBrokenPipeErrorCode,
econnrefused: tcpDialRefusedErrorCode,
tcperror: defaultTCPErrorCode,
}

for err, code := range testTable {
require.Equalf(t, code, errorCodeForError(err), "Wrong error code for error `%s`", err)
require.Equalf(t, code, errorCodeForError(errors.WithStack(err)), "Wrong error code for error `%s`", err)
}
}
6 changes: 3 additions & 3 deletions js/modules/k6/http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ func (h *HTTP) request(ctx context.Context, preq *parsedHTTPRequest) (*Response,
return nil, err
}

resp.Error = err.Error()
resp.setError(err)
return resp, nil
}

Expand Down Expand Up @@ -585,7 +585,7 @@ func (h *HTTP) request(ctx context.Context, preq *parsedHTTPRequest) (*Response,
}

if resErr != nil {
resp.Error = resErr.Error()
resp.setError(resErr)
} else {
if preq.activeJar != nil {
if rc := res.Cookies(); len(rc) > 0 {
Expand All @@ -594,7 +594,7 @@ func (h *HTTP) request(ctx context.Context, preq *parsedHTTPRequest) (*Response,
}

resp.URL = res.Request.URL.String()
resp.Status = res.StatusCode
resp.setStatusCode(res.StatusCode)
resp.Proto = res.Proto

if res.TLS != nil {
Expand Down
26 changes: 26 additions & 0 deletions js/modules/k6/http/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,38 @@ type Response struct {
TLSCipherSuite string `json:"tls_cipher_suite"`
OCSP netext.OCSP `js:"ocsp" json:"ocsp"`
Error string `json:"error"`
ErrorCode int `json:"error_code"`
Request Request `json:"request"`

cachedJSON goja.Value
validatedJSON bool
}

// This should be used instead of setting Error as it will correctly set ErrorCode as well
func (res *Response) setError(err error) {
res.setErrorCode(errorCodeForError(err), err)
}

// This should be used instead of setting Error as it will correctly set ErrorCode as well
func (res *Response) setStatusCode(statusCode int) {
res.Status = statusCode
if statusCode >= 400 && statusCode < 600 {
res.ErrorCode = 1000 + statusCode // TODO: Maybe this should not add 1000?
// TODO: maybe set the res.Error to some custom message
}
}

// setErrorCode should be used instead of directly setting the ErrorCode
// it takes care of setting the Error correctly taking into account customErrorMsgMap
func (res *Response) setErrorCode(errorCode errCode, err error) {
res.ErrorCode = int(errorCode)
if errMsg, ok := customErrorMsgMap[errorCode]; ok {
res.Error = errMsg
} else {
res.Error = err.Error()
}
}

func (res *Response) setTLSInfo(tlsState *tls.ConnectionState) {
tlsInfo, oscp := netext.ParseTLSConnState(tlsState)
res.TLSVersion = tlsInfo.Version
Expand Down

0 comments on commit df4aa05

Please sign in to comment.