http3: introduce an HTTP/3 error type (#4039)

* http3: introduce an HTTP/3 error type

* http3: use a pointer receiver for the Error
This commit is contained in:
Marten Seemann 2023-09-16 18:57:50 +07:00 committed by GitHub
parent ab1c1be9a9
commit d8cc4cb3ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 125 additions and 12 deletions

View file

@ -63,7 +63,8 @@ func (r *body) wasStreamHijacked() bool {
}
func (r *body) Read(b []byte) (int, error) {
return r.str.Read(b)
n, err := r.str.Read(b)
return n, maybeReplaceError(err)
}
func (r *body) Close() error {
@ -106,7 +107,7 @@ func (r *hijackableBody) Read(b []byte) (int, error) {
if err != nil {
r.requestDone()
}
return n, err
return n, maybeReplaceError(err)
}
func (r *hijackableBody) requestDone() {

View file

@ -318,13 +318,13 @@ func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon
}
conn.CloseWithError(quic.ApplicationErrorCode(rerr.connErr), reason)
}
return nil, rerr.err
return nil, maybeReplaceError(rerr.err)
}
if opt.DontCloseRequestStream {
close(reqDone)
<-done
}
return rsp, rerr.err
return rsp, maybeReplaceError(rerr.err)
}
// cancelingReader reads from the io.Reader.

58
http3/error.go Normal file
View file

@ -0,0 +1,58 @@
package http3
import (
"errors"
"fmt"
"github.com/quic-go/quic-go"
)
// Error is returned from the round tripper (for HTTP clients)
// and inside the HTTP handler (for HTTP servers) if an HTTP/3 error occurs.
// See section 8 of RFC 9114.
type Error struct {
Remote bool
ErrorCode ErrCode
ErrorMessage string
}
var _ error = &Error{}
func (e *Error) Error() string {
s := e.ErrorCode.string()
if s == "" {
s = fmt.Sprintf("H3 error (%#x)", uint64(e.ErrorCode))
}
// Usually errors are remote. Only make it explicit for local errors.
if !e.Remote {
s += " (local)"
}
if e.ErrorMessage != "" {
s += ": " + e.ErrorMessage
}
return s
}
func maybeReplaceError(err error) error {
if err == nil {
return nil
}
var (
e Error
strErr *quic.StreamError
appErr *quic.ApplicationError
)
switch {
default:
return err
case errors.As(err, &strErr):
e.Remote = strErr.Remote
e.ErrorCode = ErrCode(strErr.ErrorCode)
case errors.As(err, &appErr):
e.Remote = appErr.Remote
e.ErrorCode = ErrCode(appErr.ErrorCode)
e.ErrorMessage = appErr.ErrorMessage
}
return &e
}

View file

@ -30,6 +30,14 @@ const (
)
func (e ErrCode) String() string {
s := e.string()
if s != "" {
return s
}
return fmt.Sprintf("unknown error code: %#x", uint16(e))
}
func (e ErrCode) string() string {
switch e {
case ErrCodeNoError:
return "H3_NO_ERROR"
@ -68,6 +76,6 @@ func (e ErrCode) String() string {
case ErrCodeDatagramError:
return "H3_DATAGRAM_ERROR"
default:
return fmt.Sprintf("unknown error code: %#x", uint16(e))
return ""
}
}

41
http3/error_test.go Normal file
View file

@ -0,0 +1,41 @@
package http3
import (
"errors"
"github.com/quic-go/quic-go"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("HTTP/3 errors", func() {
It("converts", func() {
Expect(maybeReplaceError(nil)).To(BeNil())
Expect(maybeReplaceError(errors.New("foobar"))).To(MatchError("foobar"))
Expect(maybeReplaceError(&quic.StreamError{
ErrorCode: 1337,
Remote: true,
})).To(Equal(&Error{
Remote: true,
ErrorCode: 1337,
}))
Expect(maybeReplaceError(&quic.ApplicationError{
ErrorCode: 42,
Remote: true,
ErrorMessage: "foobar",
})).To(Equal(&Error{
Remote: true,
ErrorCode: 42,
ErrorMessage: "foobar",
}))
})
It("has a string representation", func() {
Expect((&Error{ErrorCode: 0x10c, Remote: true}).Error()).To(Equal("H3_REQUEST_CANCELLED"))
Expect((&Error{ErrorCode: 0x10c, Remote: true, ErrorMessage: "foobar"}).Error()).To(Equal("H3_REQUEST_CANCELLED: foobar"))
Expect((&Error{ErrorCode: 0x10c, Remote: false}).Error()).To(Equal("H3_REQUEST_CANCELLED (local)"))
Expect((&Error{ErrorCode: 0x10c, Remote: false, ErrorMessage: "foobar"}).Error()).To(Equal("H3_REQUEST_CANCELLED (local): foobar"))
Expect((&Error{ErrorCode: 0x1337, Remote: true}).Error()).To(Equal("H3 error (0x1337)"))
})
})

View file

@ -166,9 +166,10 @@ func (w *responseWriter) Write(p []byte) (int, error) {
w.buf = w.buf[:0]
w.buf = df.Append(w.buf)
if _, err := w.bufferedStr.Write(w.buf); err != nil {
return 0, err
return 0, maybeReplaceError(err)
}
return w.bufferedStr.Write(p)
n, err := w.bufferedStr.Write(p)
return n, maybeReplaceError(err)
}
func (w *responseWriter) FlushError() error {
@ -177,7 +178,7 @@ func (w *responseWriter) FlushError() error {
}
if !w.written {
if err := w.writeHeader(); err != nil {
return err
return maybeReplaceError(err)
}
w.written = true
}