mirror of https://github.com/docker/cli.git
Update golang.org/x/net to not panic
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
parent
1a2244e384
commit
12c0825a4c
|
@ -67,7 +67,7 @@ github.com/xeipuuv/gojsonpointer e0fe6f68307607d540ed8eac07a342c33fa1b54a
|
||||||
github.com/xeipuuv/gojsonreference e02fc20de94c78484cd5ffb007f8af96be030a45
|
github.com/xeipuuv/gojsonreference e02fc20de94c78484cd5ffb007f8af96be030a45
|
||||||
github.com/xeipuuv/gojsonschema 93e72a773fade158921402d6a24c819b48aba29d
|
github.com/xeipuuv/gojsonschema 93e72a773fade158921402d6a24c819b48aba29d
|
||||||
golang.org/x/crypto 558b6879de74bc843225cde5686419267ff707ca
|
golang.org/x/crypto 558b6879de74bc843225cde5686419267ff707ca
|
||||||
golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6
|
golang.org/x/net a8b9294777976932365dabb6640cf1468d95c70f
|
||||||
golang.org/x/sys 95c6576299259db960f6c5b9b69ea52422860fce
|
golang.org/x/sys 95c6576299259db960f6c5b9b69ea52422860fce
|
||||||
golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756
|
golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756
|
||||||
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
|
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
This repository holds supplementary Go networking libraries.
|
|
||||||
|
|
||||||
To submit changes to this repository, see http://golang.org/doc/contribute.html.
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Go Networking
|
||||||
|
|
||||||
|
This repository holds supplementary Go networking libraries.
|
||||||
|
|
||||||
|
## Download/Install
|
||||||
|
|
||||||
|
The easiest way to install is to run `go get -u golang.org/x/net`. You can
|
||||||
|
also manually git clone the repository to `$GOPATH/src/golang.org/x/net`.
|
||||||
|
|
||||||
|
## Report Issues / Send Patches
|
||||||
|
|
||||||
|
This repository uses Gerrit for code changes. To learn how to submit
|
||||||
|
changes to this repository, see https://golang.org/doc/contribute.html.
|
||||||
|
The main issue tracker for the net repository is located at
|
||||||
|
https://github.com/golang/go/issues. Prefix your issue with "x/net:" in the
|
||||||
|
subject line, so it is easy to find.
|
|
@ -5,6 +5,8 @@
|
||||||
// Package context defines the Context type, which carries deadlines,
|
// Package context defines the Context type, which carries deadlines,
|
||||||
// cancelation signals, and other request-scoped values across API boundaries
|
// cancelation signals, and other request-scoped values across API boundaries
|
||||||
// and between processes.
|
// and between processes.
|
||||||
|
// As of Go 1.7 this package is available in the standard library under the
|
||||||
|
// name context. https://golang.org/pkg/context.
|
||||||
//
|
//
|
||||||
// Incoming requests to a server should create a Context, and outgoing calls to
|
// Incoming requests to a server should create a Context, and outgoing calls to
|
||||||
// servers should accept a Context. The chain of function calls between must
|
// servers should accept a Context. The chain of function calls between must
|
||||||
|
@ -36,103 +38,6 @@
|
||||||
// Contexts.
|
// Contexts.
|
||||||
package context // import "golang.org/x/net/context"
|
package context // import "golang.org/x/net/context"
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// A Context carries a deadline, a cancelation signal, and other values across
|
|
||||||
// API boundaries.
|
|
||||||
//
|
|
||||||
// Context's methods may be called by multiple goroutines simultaneously.
|
|
||||||
type Context interface {
|
|
||||||
// Deadline returns the time when work done on behalf of this context
|
|
||||||
// should be canceled. Deadline returns ok==false when no deadline is
|
|
||||||
// set. Successive calls to Deadline return the same results.
|
|
||||||
Deadline() (deadline time.Time, ok bool)
|
|
||||||
|
|
||||||
// Done returns a channel that's closed when work done on behalf of this
|
|
||||||
// context should be canceled. Done may return nil if this context can
|
|
||||||
// never be canceled. Successive calls to Done return the same value.
|
|
||||||
//
|
|
||||||
// WithCancel arranges for Done to be closed when cancel is called;
|
|
||||||
// WithDeadline arranges for Done to be closed when the deadline
|
|
||||||
// expires; WithTimeout arranges for Done to be closed when the timeout
|
|
||||||
// elapses.
|
|
||||||
//
|
|
||||||
// Done is provided for use in select statements:
|
|
||||||
//
|
|
||||||
// // Stream generates values with DoSomething and sends them to out
|
|
||||||
// // until DoSomething returns an error or ctx.Done is closed.
|
|
||||||
// func Stream(ctx context.Context, out chan<- Value) error {
|
|
||||||
// for {
|
|
||||||
// v, err := DoSomething(ctx)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// select {
|
|
||||||
// case <-ctx.Done():
|
|
||||||
// return ctx.Err()
|
|
||||||
// case out <- v:
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// See http://blog.golang.org/pipelines for more examples of how to use
|
|
||||||
// a Done channel for cancelation.
|
|
||||||
Done() <-chan struct{}
|
|
||||||
|
|
||||||
// Err returns a non-nil error value after Done is closed. Err returns
|
|
||||||
// Canceled if the context was canceled or DeadlineExceeded if the
|
|
||||||
// context's deadline passed. No other values for Err are defined.
|
|
||||||
// After Done is closed, successive calls to Err return the same value.
|
|
||||||
Err() error
|
|
||||||
|
|
||||||
// Value returns the value associated with this context for key, or nil
|
|
||||||
// if no value is associated with key. Successive calls to Value with
|
|
||||||
// the same key returns the same result.
|
|
||||||
//
|
|
||||||
// Use context values only for request-scoped data that transits
|
|
||||||
// processes and API boundaries, not for passing optional parameters to
|
|
||||||
// functions.
|
|
||||||
//
|
|
||||||
// A key identifies a specific value in a Context. Functions that wish
|
|
||||||
// to store values in Context typically allocate a key in a global
|
|
||||||
// variable then use that key as the argument to context.WithValue and
|
|
||||||
// Context.Value. A key can be any type that supports equality;
|
|
||||||
// packages should define keys as an unexported type to avoid
|
|
||||||
// collisions.
|
|
||||||
//
|
|
||||||
// Packages that define a Context key should provide type-safe accessors
|
|
||||||
// for the values stores using that key:
|
|
||||||
//
|
|
||||||
// // Package user defines a User type that's stored in Contexts.
|
|
||||||
// package user
|
|
||||||
//
|
|
||||||
// import "golang.org/x/net/context"
|
|
||||||
//
|
|
||||||
// // User is the type of value stored in the Contexts.
|
|
||||||
// type User struct {...}
|
|
||||||
//
|
|
||||||
// // key is an unexported type for keys defined in this package.
|
|
||||||
// // This prevents collisions with keys defined in other packages.
|
|
||||||
// type key int
|
|
||||||
//
|
|
||||||
// // userKey is the key for user.User values in Contexts. It is
|
|
||||||
// // unexported; clients use user.NewContext and user.FromContext
|
|
||||||
// // instead of using this key directly.
|
|
||||||
// var userKey key = 0
|
|
||||||
//
|
|
||||||
// // NewContext returns a new Context that carries value u.
|
|
||||||
// func NewContext(ctx context.Context, u *User) context.Context {
|
|
||||||
// return context.WithValue(ctx, userKey, u)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // FromContext returns the User value stored in ctx, if any.
|
|
||||||
// func FromContext(ctx context.Context) (*User, bool) {
|
|
||||||
// u, ok := ctx.Value(userKey).(*User)
|
|
||||||
// return u, ok
|
|
||||||
// }
|
|
||||||
Value(key interface{}) interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Background returns a non-nil, empty Context. It is never canceled, has no
|
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||||
// values, and has no deadline. It is typically used by the main function,
|
// values, and has no deadline. It is typically used by the main function,
|
||||||
// initialization, and tests, and as the top-level Context for incoming
|
// initialization, and tests, and as the top-level Context for incoming
|
||||||
|
@ -149,8 +54,3 @@ func Background() Context {
|
||||||
func TODO() Context {
|
func TODO() Context {
|
||||||
return todo
|
return todo
|
||||||
}
|
}
|
||||||
|
|
||||||
// A CancelFunc tells an operation to abandon its work.
|
|
||||||
// A CancelFunc does not wait for the work to stop.
|
|
||||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
|
||||||
type CancelFunc func()
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.9
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import "context" // standard library's context, as of Go 1.7
|
||||||
|
|
||||||
|
// A Context carries a deadline, a cancelation signal, and other values across
|
||||||
|
// API boundaries.
|
||||||
|
//
|
||||||
|
// Context's methods may be called by multiple goroutines simultaneously.
|
||||||
|
type Context = context.Context
|
||||||
|
|
||||||
|
// A CancelFunc tells an operation to abandon its work.
|
||||||
|
// A CancelFunc does not wait for the work to stop.
|
||||||
|
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||||
|
type CancelFunc = context.CancelFunc
|
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !go1.9
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// A Context carries a deadline, a cancelation signal, and other values across
|
||||||
|
// API boundaries.
|
||||||
|
//
|
||||||
|
// Context's methods may be called by multiple goroutines simultaneously.
|
||||||
|
type Context interface {
|
||||||
|
// Deadline returns the time when work done on behalf of this context
|
||||||
|
// should be canceled. Deadline returns ok==false when no deadline is
|
||||||
|
// set. Successive calls to Deadline return the same results.
|
||||||
|
Deadline() (deadline time.Time, ok bool)
|
||||||
|
|
||||||
|
// Done returns a channel that's closed when work done on behalf of this
|
||||||
|
// context should be canceled. Done may return nil if this context can
|
||||||
|
// never be canceled. Successive calls to Done return the same value.
|
||||||
|
//
|
||||||
|
// WithCancel arranges for Done to be closed when cancel is called;
|
||||||
|
// WithDeadline arranges for Done to be closed when the deadline
|
||||||
|
// expires; WithTimeout arranges for Done to be closed when the timeout
|
||||||
|
// elapses.
|
||||||
|
//
|
||||||
|
// Done is provided for use in select statements:
|
||||||
|
//
|
||||||
|
// // Stream generates values with DoSomething and sends them to out
|
||||||
|
// // until DoSomething returns an error or ctx.Done is closed.
|
||||||
|
// func Stream(ctx context.Context, out chan<- Value) error {
|
||||||
|
// for {
|
||||||
|
// v, err := DoSomething(ctx)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// select {
|
||||||
|
// case <-ctx.Done():
|
||||||
|
// return ctx.Err()
|
||||||
|
// case out <- v:
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// See http://blog.golang.org/pipelines for more examples of how to use
|
||||||
|
// a Done channel for cancelation.
|
||||||
|
Done() <-chan struct{}
|
||||||
|
|
||||||
|
// Err returns a non-nil error value after Done is closed. Err returns
|
||||||
|
// Canceled if the context was canceled or DeadlineExceeded if the
|
||||||
|
// context's deadline passed. No other values for Err are defined.
|
||||||
|
// After Done is closed, successive calls to Err return the same value.
|
||||||
|
Err() error
|
||||||
|
|
||||||
|
// Value returns the value associated with this context for key, or nil
|
||||||
|
// if no value is associated with key. Successive calls to Value with
|
||||||
|
// the same key returns the same result.
|
||||||
|
//
|
||||||
|
// Use context values only for request-scoped data that transits
|
||||||
|
// processes and API boundaries, not for passing optional parameters to
|
||||||
|
// functions.
|
||||||
|
//
|
||||||
|
// A key identifies a specific value in a Context. Functions that wish
|
||||||
|
// to store values in Context typically allocate a key in a global
|
||||||
|
// variable then use that key as the argument to context.WithValue and
|
||||||
|
// Context.Value. A key can be any type that supports equality;
|
||||||
|
// packages should define keys as an unexported type to avoid
|
||||||
|
// collisions.
|
||||||
|
//
|
||||||
|
// Packages that define a Context key should provide type-safe accessors
|
||||||
|
// for the values stores using that key:
|
||||||
|
//
|
||||||
|
// // Package user defines a User type that's stored in Contexts.
|
||||||
|
// package user
|
||||||
|
//
|
||||||
|
// import "golang.org/x/net/context"
|
||||||
|
//
|
||||||
|
// // User is the type of value stored in the Contexts.
|
||||||
|
// type User struct {...}
|
||||||
|
//
|
||||||
|
// // key is an unexported type for keys defined in this package.
|
||||||
|
// // This prevents collisions with keys defined in other packages.
|
||||||
|
// type key int
|
||||||
|
//
|
||||||
|
// // userKey is the key for user.User values in Contexts. It is
|
||||||
|
// // unexported; clients use user.NewContext and user.FromContext
|
||||||
|
// // instead of using this key directly.
|
||||||
|
// var userKey key = 0
|
||||||
|
//
|
||||||
|
// // NewContext returns a new Context that carries value u.
|
||||||
|
// func NewContext(ctx context.Context, u *User) context.Context {
|
||||||
|
// return context.WithValue(ctx, userKey, u)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // FromContext returns the User value stored in ctx, if any.
|
||||||
|
// func FromContext(ctx context.Context) (*User, bool) {
|
||||||
|
// u, ok := ctx.Value(userKey).(*User)
|
||||||
|
// return u, ok
|
||||||
|
// }
|
||||||
|
Value(key interface{}) interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A CancelFunc tells an operation to abandon its work.
|
||||||
|
// A CancelFunc does not wait for the work to stop.
|
||||||
|
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||||
|
type CancelFunc func()
|
|
@ -56,7 +56,7 @@ func configureTransport(t1 *http.Transport) (*Transport, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerHTTPSProtocol calls Transport.RegisterProtocol but
|
// registerHTTPSProtocol calls Transport.RegisterProtocol but
|
||||||
// convering panics into errors.
|
// converting panics into errors.
|
||||||
func registerHTTPSProtocol(t *http.Transport, rt http.RoundTripper) (err error) {
|
func registerHTTPSProtocol(t *http.Transport, rt http.RoundTripper) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
|
|
|
@ -87,13 +87,16 @@ type goAwayFlowError struct{}
|
||||||
|
|
||||||
func (goAwayFlowError) Error() string { return "connection exceeded flow control window size" }
|
func (goAwayFlowError) Error() string { return "connection exceeded flow control window size" }
|
||||||
|
|
||||||
// connErrorReason wraps a ConnectionError with an informative error about why it occurs.
|
// connError represents an HTTP/2 ConnectionError error code, along
|
||||||
|
// with a string (for debugging) explaining why.
|
||||||
|
//
|
||||||
// Errors of this type are only returned by the frame parser functions
|
// Errors of this type are only returned by the frame parser functions
|
||||||
// and converted into ConnectionError(ErrCodeProtocol).
|
// and converted into ConnectionError(Code), after stashing away
|
||||||
|
// the Reason into the Framer's errDetail field, accessible via
|
||||||
|
// the (*Framer).ErrorDetail method.
|
||||||
type connError struct {
|
type connError struct {
|
||||||
Code ErrCode
|
Code ErrCode // the ConnectionError error code
|
||||||
Reason string
|
Reason string // additional reason
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e connError) Error() string {
|
func (e connError) Error() string {
|
||||||
|
|
|
@ -52,3 +52,5 @@ func reqGetBody(req *http.Request) func() (io.ReadCloser, error) {
|
||||||
func reqBodyIsNoBody(body io.ReadCloser) bool {
|
func reqBodyIsNoBody(body io.ReadCloser) bool {
|
||||||
return body == http.NoBody
|
return body == http.NoBody
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func go18httpNoBody() io.ReadCloser { return http.NoBody } // for tests only
|
||||||
|
|
|
@ -376,12 +376,16 @@ func (s *sorter) SortStrings(ss []string) {
|
||||||
// validPseudoPath reports whether v is a valid :path pseudo-header
|
// validPseudoPath reports whether v is a valid :path pseudo-header
|
||||||
// value. It must be either:
|
// value. It must be either:
|
||||||
//
|
//
|
||||||
// *) a non-empty string starting with '/', but not with with "//",
|
// *) a non-empty string starting with '/'
|
||||||
// *) the string '*', for OPTIONS requests.
|
// *) the string '*', for OPTIONS requests.
|
||||||
//
|
//
|
||||||
// For now this is only used a quick check for deciding when to clean
|
// For now this is only used a quick check for deciding when to clean
|
||||||
// up Opaque URLs before sending requests from the Transport.
|
// up Opaque URLs before sending requests from the Transport.
|
||||||
// See golang.org/issue/16847
|
// See golang.org/issue/16847
|
||||||
|
//
|
||||||
|
// We used to enforce that the path also didn't start with "//", but
|
||||||
|
// Google's GFE accepts such paths and Chrome sends them, so ignore
|
||||||
|
// that part of the spec. See golang.org/issue/19103.
|
||||||
func validPseudoPath(v string) bool {
|
func validPseudoPath(v string) bool {
|
||||||
return (len(v) > 0 && v[0] == '/' && (len(v) == 1 || v[1] != '/')) || v == "*"
|
return (len(v) > 0 && v[0] == '/') || v == "*"
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,3 +25,5 @@ func reqGetBody(req *http.Request) func() (io.ReadCloser, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func reqBodyIsNoBody(io.ReadCloser) bool { return false }
|
func reqBodyIsNoBody(io.ReadCloser) bool { return false }
|
||||||
|
|
||||||
|
func go18httpNoBody() io.ReadCloser { return nil } // for tests only
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (p *pipe) Read(d []byte) (n int, err error) {
|
||||||
if p.breakErr != nil {
|
if p.breakErr != nil {
|
||||||
return 0, p.breakErr
|
return 0, p.breakErr
|
||||||
}
|
}
|
||||||
if p.b.Len() > 0 {
|
if p.b != nil && p.b.Len() > 0 {
|
||||||
return p.b.Read(d)
|
return p.b.Read(d)
|
||||||
}
|
}
|
||||||
if p.err != nil {
|
if p.err != nil {
|
||||||
|
|
|
@ -220,12 +220,15 @@ func ConfigureServer(s *http.Server, conf *Server) error {
|
||||||
} else if s.TLSConfig.CipherSuites != nil {
|
} else if s.TLSConfig.CipherSuites != nil {
|
||||||
// If they already provided a CipherSuite list, return
|
// If they already provided a CipherSuite list, return
|
||||||
// an error if it has a bad order or is missing
|
// an error if it has a bad order or is missing
|
||||||
// ECDHE_RSA_WITH_AES_128_GCM_SHA256.
|
// ECDHE_RSA_WITH_AES_128_GCM_SHA256 or ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.
|
||||||
const requiredCipher = tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
|
||||||
haveRequired := false
|
haveRequired := false
|
||||||
sawBad := false
|
sawBad := false
|
||||||
for i, cs := range s.TLSConfig.CipherSuites {
|
for i, cs := range s.TLSConfig.CipherSuites {
|
||||||
if cs == requiredCipher {
|
switch cs {
|
||||||
|
case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
// Alternative MTI cipher to not discourage ECDSA-only servers.
|
||||||
|
// See http://golang.org/cl/30721 for further information.
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
|
||||||
haveRequired = true
|
haveRequired = true
|
||||||
}
|
}
|
||||||
if isBadCipher(cs) {
|
if isBadCipher(cs) {
|
||||||
|
@ -235,7 +238,7 @@ func ConfigureServer(s *http.Server, conf *Server) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !haveRequired {
|
if !haveRequired {
|
||||||
return fmt.Errorf("http2: TLSConfig.CipherSuites is missing HTTP/2-required TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
|
return fmt.Errorf("http2: TLSConfig.CipherSuites is missing an HTTP/2-required AES_128_GCM_SHA256 cipher.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,7 +652,7 @@ func (sc *serverConn) condlogf(err error, format string, args ...interface{}) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err == io.EOF || err == io.ErrUnexpectedEOF || isClosedConnError(err) {
|
if err == io.EOF || err == io.ErrUnexpectedEOF || isClosedConnError(err) || err == errPrefaceTimeout {
|
||||||
// Boring, expected errors.
|
// Boring, expected errors.
|
||||||
sc.vlogf(format, args...)
|
sc.vlogf(format, args...)
|
||||||
} else {
|
} else {
|
||||||
|
@ -853,8 +856,13 @@ func (sc *serverConn) serve() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc.inGoAway && sc.curOpenStreams() == 0 && !sc.needToSendGoAway && !sc.writingFrame {
|
// Start the shutdown timer after sending a GOAWAY. When sending GOAWAY
|
||||||
return
|
// with no error code (graceful shutdown), don't start the timer until
|
||||||
|
// all open streams have been completed.
|
||||||
|
sentGoAway := sc.inGoAway && !sc.needToSendGoAway && !sc.writingFrame
|
||||||
|
gracefulShutdownComplete := sc.goAwayCode == ErrCodeNo && sc.curOpenStreams() == 0
|
||||||
|
if sentGoAway && sc.shutdownTimer == nil && (sc.goAwayCode != ErrCodeNo || gracefulShutdownComplete) {
|
||||||
|
sc.shutDownIn(goAwayTimeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -889,8 +897,11 @@ func (sc *serverConn) sendServeMsg(msg interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPreface reads the ClientPreface greeting from the peer
|
var errPrefaceTimeout = errors.New("timeout waiting for client preface")
|
||||||
// or returns an error on timeout or an invalid greeting.
|
|
||||||
|
// readPreface reads the ClientPreface greeting from the peer or
|
||||||
|
// returns errPrefaceTimeout on timeout, or an error if the greeting
|
||||||
|
// is invalid.
|
||||||
func (sc *serverConn) readPreface() error {
|
func (sc *serverConn) readPreface() error {
|
||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -908,7 +919,7 @@ func (sc *serverConn) readPreface() error {
|
||||||
defer timer.Stop()
|
defer timer.Stop()
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
return errors.New("timeout waiting for client preface")
|
return errPrefaceTimeout
|
||||||
case err := <-errc:
|
case err := <-errc:
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if VerboseLogs {
|
if VerboseLogs {
|
||||||
|
@ -1218,30 +1229,31 @@ func (sc *serverConn) startGracefulShutdown() {
|
||||||
sc.shutdownOnce.Do(func() { sc.sendServeMsg(gracefulShutdownMsg) })
|
sc.shutdownOnce.Do(func() { sc.sendServeMsg(gracefulShutdownMsg) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After sending GOAWAY, the connection will close after goAwayTimeout.
|
||||||
|
// If we close the connection immediately after sending GOAWAY, there may
|
||||||
|
// be unsent data in our kernel receive buffer, which will cause the kernel
|
||||||
|
// to send a TCP RST on close() instead of a FIN. This RST will abort the
|
||||||
|
// connection immediately, whether or not the client had received the GOAWAY.
|
||||||
|
//
|
||||||
|
// Ideally we should delay for at least 1 RTT + epsilon so the client has
|
||||||
|
// a chance to read the GOAWAY and stop sending messages. Measuring RTT
|
||||||
|
// is hard, so we approximate with 1 second. See golang.org/issue/18701.
|
||||||
|
//
|
||||||
|
// This is a var so it can be shorter in tests, where all requests uses the
|
||||||
|
// loopback interface making the expected RTT very small.
|
||||||
|
//
|
||||||
|
// TODO: configurable?
|
||||||
|
var goAwayTimeout = 1 * time.Second
|
||||||
|
|
||||||
func (sc *serverConn) startGracefulShutdownInternal() {
|
func (sc *serverConn) startGracefulShutdownInternal() {
|
||||||
sc.goAwayIn(ErrCodeNo, 0)
|
sc.goAway(ErrCodeNo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *serverConn) goAway(code ErrCode) {
|
func (sc *serverConn) goAway(code ErrCode) {
|
||||||
sc.serveG.check()
|
|
||||||
var forceCloseIn time.Duration
|
|
||||||
if code != ErrCodeNo {
|
|
||||||
forceCloseIn = 250 * time.Millisecond
|
|
||||||
} else {
|
|
||||||
// TODO: configurable
|
|
||||||
forceCloseIn = 1 * time.Second
|
|
||||||
}
|
|
||||||
sc.goAwayIn(code, forceCloseIn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *serverConn) goAwayIn(code ErrCode, forceCloseIn time.Duration) {
|
|
||||||
sc.serveG.check()
|
sc.serveG.check()
|
||||||
if sc.inGoAway {
|
if sc.inGoAway {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if forceCloseIn != 0 {
|
|
||||||
sc.shutDownIn(forceCloseIn)
|
|
||||||
}
|
|
||||||
sc.inGoAway = true
|
sc.inGoAway = true
|
||||||
sc.needToSendGoAway = true
|
sc.needToSendGoAway = true
|
||||||
sc.goAwayCode = code
|
sc.goAwayCode = code
|
||||||
|
@ -2252,6 +2264,7 @@ type responseWriterState struct {
|
||||||
wroteHeader bool // WriteHeader called (explicitly or implicitly). Not necessarily sent to user yet.
|
wroteHeader bool // WriteHeader called (explicitly or implicitly). Not necessarily sent to user yet.
|
||||||
sentHeader bool // have we sent the header frame?
|
sentHeader bool // have we sent the header frame?
|
||||||
handlerDone bool // handler has finished
|
handlerDone bool // handler has finished
|
||||||
|
dirty bool // a Write failed; don't reuse this responseWriterState
|
||||||
|
|
||||||
sentContentLen int64 // non-zero if handler set a Content-Length header
|
sentContentLen int64 // non-zero if handler set a Content-Length header
|
||||||
wroteBytes int64
|
wroteBytes int64
|
||||||
|
@ -2309,7 +2322,7 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
|
||||||
clen = strconv.Itoa(len(p))
|
clen = strconv.Itoa(len(p))
|
||||||
}
|
}
|
||||||
_, hasContentType := rws.snapHeader["Content-Type"]
|
_, hasContentType := rws.snapHeader["Content-Type"]
|
||||||
if !hasContentType && bodyAllowedForStatus(rws.status) {
|
if !hasContentType && bodyAllowedForStatus(rws.status) && len(p) > 0 {
|
||||||
ctype = http.DetectContentType(p)
|
ctype = http.DetectContentType(p)
|
||||||
}
|
}
|
||||||
var date string
|
var date string
|
||||||
|
@ -2333,6 +2346,7 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
|
||||||
date: date,
|
date: date,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
rws.dirty = true
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if endStream {
|
if endStream {
|
||||||
|
@ -2354,6 +2368,7 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
|
||||||
if len(p) > 0 || endStream {
|
if len(p) > 0 || endStream {
|
||||||
// only send a 0 byte DATA frame if we're ending the stream.
|
// only send a 0 byte DATA frame if we're ending the stream.
|
||||||
if err := rws.conn.writeDataFromHandler(rws.stream, p, endStream); err != nil {
|
if err := rws.conn.writeDataFromHandler(rws.stream, p, endStream); err != nil {
|
||||||
|
rws.dirty = true
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2365,6 +2380,9 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
|
||||||
trailers: rws.trailers,
|
trailers: rws.trailers,
|
||||||
endStream: true,
|
endStream: true,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
rws.dirty = true
|
||||||
|
}
|
||||||
return len(p), err
|
return len(p), err
|
||||||
}
|
}
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
|
@ -2472,7 +2490,26 @@ func (w *responseWriter) Header() http.Header {
|
||||||
return rws.handlerHeader
|
return rws.handlerHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkWriteHeaderCode is a copy of net/http's checkWriteHeaderCode.
|
||||||
|
func checkWriteHeaderCode(code int) {
|
||||||
|
// Issue 22880: require valid WriteHeader status codes.
|
||||||
|
// For now we only enforce that it's three digits.
|
||||||
|
// In the future we might block things over 599 (600 and above aren't defined
|
||||||
|
// at http://httpwg.org/specs/rfc7231.html#status.codes)
|
||||||
|
// and we might block under 200 (once we have more mature 1xx support).
|
||||||
|
// But for now any three digits.
|
||||||
|
//
|
||||||
|
// We used to send "HTTP/1.1 000 0" on the wire in responses but there's
|
||||||
|
// no equivalent bogus thing we can realistically send in HTTP/2,
|
||||||
|
// so we'll consistently panic instead and help people find their bugs
|
||||||
|
// early. (We can't return an error from WriteHeader even if we wanted to.)
|
||||||
|
if code < 100 || code > 999 {
|
||||||
|
panic(fmt.Sprintf("invalid WriteHeader code %v", code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (w *responseWriter) WriteHeader(code int) {
|
func (w *responseWriter) WriteHeader(code int) {
|
||||||
|
checkWriteHeaderCode(code)
|
||||||
rws := w.rws
|
rws := w.rws
|
||||||
if rws == nil {
|
if rws == nil {
|
||||||
panic("WriteHeader called after Handler finished")
|
panic("WriteHeader called after Handler finished")
|
||||||
|
@ -2504,7 +2541,7 @@ func cloneHeader(h http.Header) http.Header {
|
||||||
//
|
//
|
||||||
// * Handler calls w.Write or w.WriteString ->
|
// * Handler calls w.Write or w.WriteString ->
|
||||||
// * -> rws.bw (*bufio.Writer) ->
|
// * -> rws.bw (*bufio.Writer) ->
|
||||||
// * (Handler migth call Flush)
|
// * (Handler might call Flush)
|
||||||
// * -> chunkWriter{rws}
|
// * -> chunkWriter{rws}
|
||||||
// * -> responseWriterState.writeChunk(p []byte)
|
// * -> responseWriterState.writeChunk(p []byte)
|
||||||
// * -> responseWriterState.writeChunk (most of the magic; see comment there)
|
// * -> responseWriterState.writeChunk (most of the magic; see comment there)
|
||||||
|
@ -2543,10 +2580,19 @@ func (w *responseWriter) write(lenData int, dataB []byte, dataS string) (n int,
|
||||||
|
|
||||||
func (w *responseWriter) handlerDone() {
|
func (w *responseWriter) handlerDone() {
|
||||||
rws := w.rws
|
rws := w.rws
|
||||||
|
dirty := rws.dirty
|
||||||
rws.handlerDone = true
|
rws.handlerDone = true
|
||||||
w.Flush()
|
w.Flush()
|
||||||
w.rws = nil
|
w.rws = nil
|
||||||
|
if !dirty {
|
||||||
|
// Only recycle the pool if all prior Write calls to
|
||||||
|
// the serverConn goroutine completed successfully. If
|
||||||
|
// they returned earlier due to resets from the peer
|
||||||
|
// there might still be write goroutines outstanding
|
||||||
|
// from the serverConn referencing the rws memory. See
|
||||||
|
// issue 20704.
|
||||||
responseWriterStatePool.Put(rws)
|
responseWriterStatePool.Put(rws)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push errors.
|
// Push errors.
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
|
mathrand "math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -86,7 +87,7 @@ type Transport struct {
|
||||||
|
|
||||||
// MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
|
// MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
|
||||||
// send in the initial settings frame. It is how many bytes
|
// send in the initial settings frame. It is how many bytes
|
||||||
// of response headers are allow. Unlike the http2 spec, zero here
|
// of response headers are allowed. Unlike the http2 spec, zero here
|
||||||
// means to use a default limit (currently 10MB). If you actually
|
// means to use a default limit (currently 10MB). If you actually
|
||||||
// want to advertise an ulimited value to the peer, Transport
|
// want to advertise an ulimited value to the peer, Transport
|
||||||
// interprets the highest possible value here (0xffffffff or 1<<32-1)
|
// interprets the highest possible value here (0xffffffff or 1<<32-1)
|
||||||
|
@ -164,6 +165,7 @@ type ClientConn struct {
|
||||||
goAwayDebug string // goAway frame's debug data, retained as a string
|
goAwayDebug string // goAway frame's debug data, retained as a string
|
||||||
streams map[uint32]*clientStream // client-initiated
|
streams map[uint32]*clientStream // client-initiated
|
||||||
nextStreamID uint32
|
nextStreamID uint32
|
||||||
|
pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams
|
||||||
pings map[[8]byte]chan struct{} // in flight ping data to notification channel
|
pings map[[8]byte]chan struct{} // in flight ping data to notification channel
|
||||||
bw *bufio.Writer
|
bw *bufio.Writer
|
||||||
br *bufio.Reader
|
br *bufio.Reader
|
||||||
|
@ -172,6 +174,7 @@ type ClientConn struct {
|
||||||
// Settings from peer: (also guarded by mu)
|
// Settings from peer: (also guarded by mu)
|
||||||
maxFrameSize uint32
|
maxFrameSize uint32
|
||||||
maxConcurrentStreams uint32
|
maxConcurrentStreams uint32
|
||||||
|
peerMaxHeaderListSize uint64
|
||||||
initialWindowSize uint32
|
initialWindowSize uint32
|
||||||
|
|
||||||
hbuf bytes.Buffer // HPACK encoder writes into this
|
hbuf bytes.Buffer // HPACK encoder writes into this
|
||||||
|
@ -216,35 +219,45 @@ type clientStream struct {
|
||||||
resTrailer *http.Header // client's Response.Trailer
|
resTrailer *http.Header // client's Response.Trailer
|
||||||
}
|
}
|
||||||
|
|
||||||
// awaitRequestCancel runs in its own goroutine and waits for the user
|
// awaitRequestCancel waits for the user to cancel a request or for the done
|
||||||
// to cancel a RoundTrip request, its context to expire, or for the
|
// channel to be signaled. A non-nil error is returned only if the request was
|
||||||
// request to be done (any way it might be removed from the cc.streams
|
// canceled.
|
||||||
// map: peer reset, successful completion, TCP connection breakage,
|
func awaitRequestCancel(req *http.Request, done <-chan struct{}) error {
|
||||||
// etc)
|
|
||||||
func (cs *clientStream) awaitRequestCancel(req *http.Request) {
|
|
||||||
ctx := reqContext(req)
|
ctx := reqContext(req)
|
||||||
if req.Cancel == nil && ctx.Done() == nil {
|
if req.Cancel == nil && ctx.Done() == nil {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case <-req.Cancel:
|
case <-req.Cancel:
|
||||||
cs.cancelStream()
|
return errRequestCanceled
|
||||||
cs.bufPipe.CloseWithError(errRequestCanceled)
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// awaitRequestCancel waits for the user to cancel a request, its context to
|
||||||
|
// expire, or for the request to be done (any way it might be removed from the
|
||||||
|
// cc.streams map: peer reset, successful completion, TCP connection breakage,
|
||||||
|
// etc). If the request is canceled, then cs will be canceled and closed.
|
||||||
|
func (cs *clientStream) awaitRequestCancel(req *http.Request) {
|
||||||
|
if err := awaitRequestCancel(req, cs.done); err != nil {
|
||||||
cs.cancelStream()
|
cs.cancelStream()
|
||||||
cs.bufPipe.CloseWithError(ctx.Err())
|
cs.bufPipe.CloseWithError(err)
|
||||||
case <-cs.done:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *clientStream) cancelStream() {
|
func (cs *clientStream) cancelStream() {
|
||||||
cs.cc.mu.Lock()
|
cc := cs.cc
|
||||||
|
cc.mu.Lock()
|
||||||
didReset := cs.didReset
|
didReset := cs.didReset
|
||||||
cs.didReset = true
|
cs.didReset = true
|
||||||
cs.cc.mu.Unlock()
|
cc.mu.Unlock()
|
||||||
|
|
||||||
if !didReset {
|
if !didReset {
|
||||||
cs.cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
||||||
|
cc.forgetStreamID(cs.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,6 +274,13 @@ func (cs *clientStream) checkResetOrDone() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cs *clientStream) getStartedWrite() bool {
|
||||||
|
cc := cs.cc
|
||||||
|
cc.mu.Lock()
|
||||||
|
defer cc.mu.Unlock()
|
||||||
|
return cs.startedWrite
|
||||||
|
}
|
||||||
|
|
||||||
func (cs *clientStream) abortRequestBodyWrite(err error) {
|
func (cs *clientStream) abortRequestBodyWrite(err error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
panic("nil error")
|
panic("nil error")
|
||||||
|
@ -329,18 +349,29 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := authorityAddr(req.URL.Scheme, req.URL.Host)
|
addr := authorityAddr(req.URL.Scheme, req.URL.Host)
|
||||||
for {
|
for retry := 0; ; retry++ {
|
||||||
cc, err := t.connPool().GetClientConn(req, addr)
|
cc, err := t.connPool().GetClientConn(req, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err)
|
t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
traceGotConn(req, cc)
|
traceGotConn(req, cc)
|
||||||
res, err := cc.RoundTrip(req)
|
res, gotErrAfterReqBodyWrite, err := cc.roundTrip(req)
|
||||||
if err != nil {
|
if err != nil && retry <= 6 {
|
||||||
if req, err = shouldRetryRequest(req, err); err == nil {
|
if req, err = shouldRetryRequest(req, err, gotErrAfterReqBodyWrite); err == nil {
|
||||||
|
// After the first retry, do exponential backoff with 10% jitter.
|
||||||
|
if retry == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
backoff := float64(uint(1) << (uint(retry) - 1))
|
||||||
|
backoff += backoff * (0.1 * mathrand.Float64())
|
||||||
|
select {
|
||||||
|
case <-time.After(time.Second * time.Duration(backoff)):
|
||||||
|
continue
|
||||||
|
case <-reqContext(req).Done():
|
||||||
|
return nil, reqContext(req).Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.vlogf("RoundTrip failure: %v", err)
|
t.vlogf("RoundTrip failure: %v", err)
|
||||||
|
@ -362,22 +393,20 @@ func (t *Transport) CloseIdleConnections() {
|
||||||
var (
|
var (
|
||||||
errClientConnClosed = errors.New("http2: client conn is closed")
|
errClientConnClosed = errors.New("http2: client conn is closed")
|
||||||
errClientConnUnusable = errors.New("http2: client conn not usable")
|
errClientConnUnusable = errors.New("http2: client conn not usable")
|
||||||
|
|
||||||
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
|
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
|
||||||
errClientConnGotGoAwayAfterSomeReqBody = errors.New("http2: Transport received Server's graceful shutdown GOAWAY; some request body already written")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// shouldRetryRequest is called by RoundTrip when a request fails to get
|
// shouldRetryRequest is called by RoundTrip when a request fails to get
|
||||||
// response headers. It is always called with a non-nil error.
|
// response headers. It is always called with a non-nil error.
|
||||||
// It returns either a request to retry (either the same request, or a
|
// It returns either a request to retry (either the same request, or a
|
||||||
// modified clone), or an error if the request can't be replayed.
|
// modified clone), or an error if the request can't be replayed.
|
||||||
func shouldRetryRequest(req *http.Request, err error) (*http.Request, error) {
|
func shouldRetryRequest(req *http.Request, err error, afterBodyWrite bool) (*http.Request, error) {
|
||||||
switch err {
|
if !canRetryError(err) {
|
||||||
default:
|
|
||||||
return nil, err
|
return nil, err
|
||||||
case errClientConnUnusable, errClientConnGotGoAway:
|
}
|
||||||
|
if !afterBodyWrite {
|
||||||
return req, nil
|
return req, nil
|
||||||
case errClientConnGotGoAwayAfterSomeReqBody:
|
}
|
||||||
// If the Body is nil (or http.NoBody), it's safe to reuse
|
// If the Body is nil (or http.NoBody), it's safe to reuse
|
||||||
// this request and its Body.
|
// this request and its Body.
|
||||||
if req.Body == nil || reqBodyIsNoBody(req.Body) {
|
if req.Body == nil || reqBodyIsNoBody(req.Body) {
|
||||||
|
@ -387,7 +416,7 @@ func shouldRetryRequest(req *http.Request, err error) (*http.Request, error) {
|
||||||
// func defined.
|
// func defined.
|
||||||
getBody := reqGetBody(req) // Go 1.8: getBody = req.GetBody
|
getBody := reqGetBody(req) // Go 1.8: getBody = req.GetBody
|
||||||
if getBody == nil {
|
if getBody == nil {
|
||||||
return nil, errors.New("http2: Transport: peer server initiated graceful shutdown after some of Request.Body was written; define Request.GetBody to avoid this error")
|
return nil, fmt.Errorf("http2: Transport: cannot retry err [%v] after Request.Body was written; define Request.GetBody to avoid this error", err)
|
||||||
}
|
}
|
||||||
body, err := getBody()
|
body, err := getBody()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -396,7 +425,16 @@ func shouldRetryRequest(req *http.Request, err error) (*http.Request, error) {
|
||||||
newReq := *req
|
newReq := *req
|
||||||
newReq.Body = body
|
newReq.Body = body
|
||||||
return &newReq, nil
|
return &newReq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func canRetryError(err error) bool {
|
||||||
|
if err == errClientConnUnusable || err == errClientConnGotGoAway {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
if se, ok := err.(StreamError); ok {
|
||||||
|
return se.Code == ErrCodeRefusedStream
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) dialClientConn(addr string, singleUse bool) (*ClientConn, error) {
|
func (t *Transport) dialClientConn(addr string, singleUse bool) (*ClientConn, error) {
|
||||||
|
@ -481,6 +519,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
|
||||||
maxFrameSize: 16 << 10, // spec default
|
maxFrameSize: 16 << 10, // spec default
|
||||||
initialWindowSize: 65535, // spec default
|
initialWindowSize: 65535, // spec default
|
||||||
maxConcurrentStreams: 1000, // "infinite", per spec. 1000 seems good enough.
|
maxConcurrentStreams: 1000, // "infinite", per spec. 1000 seems good enough.
|
||||||
|
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
|
||||||
streams: make(map[uint32]*clientStream),
|
streams: make(map[uint32]*clientStream),
|
||||||
singleUse: singleUse,
|
singleUse: singleUse,
|
||||||
wantSettingsAck: true,
|
wantSettingsAck: true,
|
||||||
|
@ -560,6 +599,8 @@ func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CanTakeNewRequest reports whether the connection can take a new request,
|
||||||
|
// meaning it has not been closed or received or sent a GOAWAY.
|
||||||
func (cc *ClientConn) CanTakeNewRequest() bool {
|
func (cc *ClientConn) CanTakeNewRequest() bool {
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
defer cc.mu.Unlock()
|
defer cc.mu.Unlock()
|
||||||
|
@ -571,8 +612,7 @@ func (cc *ClientConn) canTakeNewRequestLocked() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return cc.goAway == nil && !cc.closed &&
|
return cc.goAway == nil && !cc.closed &&
|
||||||
int64(len(cc.streams)+1) < int64(cc.maxConcurrentStreams) &&
|
int64(cc.nextStreamID)+int64(cc.pendingRequests) < math.MaxInt32
|
||||||
cc.nextStreamID < math.MaxInt32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// onIdleTimeout is called from a time.AfterFunc goroutine. It will
|
// onIdleTimeout is called from a time.AfterFunc goroutine. It will
|
||||||
|
@ -694,7 +734,7 @@ func checkConnHeaders(req *http.Request) error {
|
||||||
// req.ContentLength, where 0 actually means zero (not unknown) and -1
|
// req.ContentLength, where 0 actually means zero (not unknown) and -1
|
||||||
// means unknown.
|
// means unknown.
|
||||||
func actualContentLength(req *http.Request) int64 {
|
func actualContentLength(req *http.Request) int64 {
|
||||||
if req.Body == nil {
|
if req.Body == nil || reqBodyIsNoBody(req.Body) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
if req.ContentLength != 0 {
|
if req.ContentLength != 0 {
|
||||||
|
@ -704,8 +744,13 @@ func actualContentLength(req *http.Request) int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, _, err := cc.roundTrip(req)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) roundTrip(req *http.Request) (res *http.Response, gotErrAfterReqBodyWrite bool, err error) {
|
||||||
if err := checkConnHeaders(req); err != nil {
|
if err := checkConnHeaders(req); err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
if cc.idleTimer != nil {
|
if cc.idleTimer != nil {
|
||||||
cc.idleTimer.Stop()
|
cc.idleTimer.Stop()
|
||||||
|
@ -713,20 +758,19 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
|
||||||
trailers, err := commaSeparatedTrailers(req)
|
trailers, err := commaSeparatedTrailers(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
hasTrailers := trailers != ""
|
hasTrailers := trailers != ""
|
||||||
|
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
cc.lastActive = time.Now()
|
if err := cc.awaitOpenSlotForRequest(req); err != nil {
|
||||||
if cc.closed || !cc.canTakeNewRequestLocked() {
|
|
||||||
cc.mu.Unlock()
|
cc.mu.Unlock()
|
||||||
return nil, errClientConnUnusable
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body := req.Body
|
body := req.Body
|
||||||
hasBody := body != nil
|
|
||||||
contentLen := actualContentLength(req)
|
contentLen := actualContentLength(req)
|
||||||
|
hasBody := contentLen != 0
|
||||||
|
|
||||||
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
|
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
|
||||||
var requestedGzip bool
|
var requestedGzip bool
|
||||||
|
@ -755,7 +799,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
hdrs, err := cc.encodeHeaders(req, requestedGzip, trailers, contentLen)
|
hdrs, err := cc.encodeHeaders(req, requestedGzip, trailers, contentLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cc.mu.Unlock()
|
cc.mu.Unlock()
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := cc.newStream()
|
cs := cc.newStream()
|
||||||
|
@ -767,7 +811,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
|
||||||
cc.wmu.Lock()
|
cc.wmu.Lock()
|
||||||
endStream := !hasBody && !hasTrailers
|
endStream := !hasBody && !hasTrailers
|
||||||
werr := cc.writeHeaders(cs.ID, endStream, hdrs)
|
werr := cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs)
|
||||||
cc.wmu.Unlock()
|
cc.wmu.Unlock()
|
||||||
traceWroteHeaders(cs.trace)
|
traceWroteHeaders(cs.trace)
|
||||||
cc.mu.Unlock()
|
cc.mu.Unlock()
|
||||||
|
@ -781,7 +825,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
// Don't bother sending a RST_STREAM (our write already failed;
|
// Don't bother sending a RST_STREAM (our write already failed;
|
||||||
// no need to keep writing)
|
// no need to keep writing)
|
||||||
traceWroteRequest(cs.trace, werr)
|
traceWroteRequest(cs.trace, werr)
|
||||||
return nil, werr
|
return nil, false, werr
|
||||||
}
|
}
|
||||||
|
|
||||||
var respHeaderTimer <-chan time.Time
|
var respHeaderTimer <-chan time.Time
|
||||||
|
@ -800,7 +844,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
bodyWritten := false
|
bodyWritten := false
|
||||||
ctx := reqContext(req)
|
ctx := reqContext(req)
|
||||||
|
|
||||||
handleReadLoopResponse := func(re resAndError) (*http.Response, error) {
|
handleReadLoopResponse := func(re resAndError) (*http.Response, bool, error) {
|
||||||
res := re.res
|
res := re.res
|
||||||
if re.err != nil || res.StatusCode > 299 {
|
if re.err != nil || res.StatusCode > 299 {
|
||||||
// On error or status code 3xx, 4xx, 5xx, etc abort any
|
// On error or status code 3xx, 4xx, 5xx, etc abort any
|
||||||
|
@ -816,19 +860,12 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
cs.abortRequestBodyWrite(errStopReqBodyWrite)
|
cs.abortRequestBodyWrite(errStopReqBodyWrite)
|
||||||
}
|
}
|
||||||
if re.err != nil {
|
if re.err != nil {
|
||||||
if re.err == errClientConnGotGoAway {
|
|
||||||
cc.mu.Lock()
|
|
||||||
if cs.startedWrite {
|
|
||||||
re.err = errClientConnGotGoAwayAfterSomeReqBody
|
|
||||||
}
|
|
||||||
cc.mu.Unlock()
|
|
||||||
}
|
|
||||||
cc.forgetStreamID(cs.ID)
|
cc.forgetStreamID(cs.ID)
|
||||||
return nil, re.err
|
return nil, cs.getStartedWrite(), re.err
|
||||||
}
|
}
|
||||||
res.Request = req
|
res.Request = req
|
||||||
res.TLS = cc.tlsState
|
res.TLS = cc.tlsState
|
||||||
return res, nil
|
return res, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -836,37 +873,37 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
case re := <-readLoopResCh:
|
case re := <-readLoopResCh:
|
||||||
return handleReadLoopResponse(re)
|
return handleReadLoopResponse(re)
|
||||||
case <-respHeaderTimer:
|
case <-respHeaderTimer:
|
||||||
cc.forgetStreamID(cs.ID)
|
|
||||||
if !hasBody || bodyWritten {
|
if !hasBody || bodyWritten {
|
||||||
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
||||||
} else {
|
} else {
|
||||||
bodyWriter.cancel()
|
bodyWriter.cancel()
|
||||||
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
||||||
}
|
}
|
||||||
return nil, errTimeout
|
cc.forgetStreamID(cs.ID)
|
||||||
|
return nil, cs.getStartedWrite(), errTimeout
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
cc.forgetStreamID(cs.ID)
|
|
||||||
if !hasBody || bodyWritten {
|
if !hasBody || bodyWritten {
|
||||||
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
||||||
} else {
|
} else {
|
||||||
bodyWriter.cancel()
|
bodyWriter.cancel()
|
||||||
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
||||||
}
|
}
|
||||||
return nil, ctx.Err()
|
cc.forgetStreamID(cs.ID)
|
||||||
|
return nil, cs.getStartedWrite(), ctx.Err()
|
||||||
case <-req.Cancel:
|
case <-req.Cancel:
|
||||||
cc.forgetStreamID(cs.ID)
|
|
||||||
if !hasBody || bodyWritten {
|
if !hasBody || bodyWritten {
|
||||||
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
cc.writeStreamReset(cs.ID, ErrCodeCancel, nil)
|
||||||
} else {
|
} else {
|
||||||
bodyWriter.cancel()
|
bodyWriter.cancel()
|
||||||
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
cs.abortRequestBodyWrite(errStopReqBodyWriteAndCancel)
|
||||||
}
|
}
|
||||||
return nil, errRequestCanceled
|
cc.forgetStreamID(cs.ID)
|
||||||
|
return nil, cs.getStartedWrite(), errRequestCanceled
|
||||||
case <-cs.peerReset:
|
case <-cs.peerReset:
|
||||||
// processResetStream already removed the
|
// processResetStream already removed the
|
||||||
// stream from the streams map; no need for
|
// stream from the streams map; no need for
|
||||||
// forgetStreamID.
|
// forgetStreamID.
|
||||||
return nil, cs.resetErr
|
return nil, cs.getStartedWrite(), cs.resetErr
|
||||||
case err := <-bodyWriter.resc:
|
case err := <-bodyWriter.resc:
|
||||||
// Prefer the read loop's response, if available. Issue 16102.
|
// Prefer the read loop's response, if available. Issue 16102.
|
||||||
select {
|
select {
|
||||||
|
@ -875,7 +912,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, cs.getStartedWrite(), err
|
||||||
}
|
}
|
||||||
bodyWritten = true
|
bodyWritten = true
|
||||||
if d := cc.responseHeaderTimeout(); d != 0 {
|
if d := cc.responseHeaderTimeout(); d != 0 {
|
||||||
|
@ -887,14 +924,52 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// awaitOpenSlotForRequest waits until len(streams) < maxConcurrentStreams.
|
||||||
|
// Must hold cc.mu.
|
||||||
|
func (cc *ClientConn) awaitOpenSlotForRequest(req *http.Request) error {
|
||||||
|
var waitingForConn chan struct{}
|
||||||
|
var waitingForConnErr error // guarded by cc.mu
|
||||||
|
for {
|
||||||
|
cc.lastActive = time.Now()
|
||||||
|
if cc.closed || !cc.canTakeNewRequestLocked() {
|
||||||
|
return errClientConnUnusable
|
||||||
|
}
|
||||||
|
if int64(len(cc.streams))+1 <= int64(cc.maxConcurrentStreams) {
|
||||||
|
if waitingForConn != nil {
|
||||||
|
close(waitingForConn)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Unfortunately, we cannot wait on a condition variable and channel at
|
||||||
|
// the same time, so instead, we spin up a goroutine to check if the
|
||||||
|
// request is canceled while we wait for a slot to open in the connection.
|
||||||
|
if waitingForConn == nil {
|
||||||
|
waitingForConn = make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
if err := awaitRequestCancel(req, waitingForConn); err != nil {
|
||||||
|
cc.mu.Lock()
|
||||||
|
waitingForConnErr = err
|
||||||
|
cc.cond.Broadcast()
|
||||||
|
cc.mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
cc.pendingRequests++
|
||||||
|
cc.cond.Wait()
|
||||||
|
cc.pendingRequests--
|
||||||
|
if waitingForConnErr != nil {
|
||||||
|
return waitingForConnErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// requires cc.wmu be held
|
// requires cc.wmu be held
|
||||||
func (cc *ClientConn) writeHeaders(streamID uint32, endStream bool, hdrs []byte) error {
|
func (cc *ClientConn) writeHeaders(streamID uint32, endStream bool, maxFrameSize int, hdrs []byte) error {
|
||||||
first := true // first frame written (HEADERS is first, then CONTINUATION)
|
first := true // first frame written (HEADERS is first, then CONTINUATION)
|
||||||
frameSize := int(cc.maxFrameSize)
|
|
||||||
for len(hdrs) > 0 && cc.werr == nil {
|
for len(hdrs) > 0 && cc.werr == nil {
|
||||||
chunk := hdrs
|
chunk := hdrs
|
||||||
if len(chunk) > frameSize {
|
if len(chunk) > maxFrameSize {
|
||||||
chunk = chunk[:frameSize]
|
chunk = chunk[:maxFrameSize]
|
||||||
}
|
}
|
||||||
hdrs = hdrs[len(chunk):]
|
hdrs = hdrs[len(chunk):]
|
||||||
endHeaders := len(hdrs) == 0
|
endHeaders := len(hdrs) == 0
|
||||||
|
@ -1002,9 +1077,18 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
|
||||||
var trls []byte
|
var trls []byte
|
||||||
if hasTrailers {
|
if hasTrailers {
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
defer cc.mu.Unlock()
|
trls, err = cc.encodeTrailers(req)
|
||||||
trls = cc.encodeTrailers(req)
|
cc.mu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
cc.writeStreamReset(cs.ID, ErrCodeInternal, err)
|
||||||
|
cc.forgetStreamID(cs.ID)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.mu.Lock()
|
||||||
|
maxFrameSize := int(cc.maxFrameSize)
|
||||||
|
cc.mu.Unlock()
|
||||||
|
|
||||||
cc.wmu.Lock()
|
cc.wmu.Lock()
|
||||||
defer cc.wmu.Unlock()
|
defer cc.wmu.Unlock()
|
||||||
|
@ -1012,7 +1096,7 @@ func (cs *clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (
|
||||||
// Two ways to send END_STREAM: either with trailers, or
|
// Two ways to send END_STREAM: either with trailers, or
|
||||||
// with an empty DATA frame.
|
// with an empty DATA frame.
|
||||||
if len(trls) > 0 {
|
if len(trls) > 0 {
|
||||||
err = cc.writeHeaders(cs.ID, true, trls)
|
err = cc.writeHeaders(cs.ID, true, maxFrameSize, trls)
|
||||||
} else {
|
} else {
|
||||||
err = cc.fr.WriteData(cs.ID, true, nil)
|
err = cc.fr.WriteData(cs.ID, true, nil)
|
||||||
}
|
}
|
||||||
|
@ -1106,36 +1190,37 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enumerateHeaders := func(f func(name, value string)) {
|
||||||
// 8.1.2.3 Request Pseudo-Header Fields
|
// 8.1.2.3 Request Pseudo-Header Fields
|
||||||
// The :path pseudo-header field includes the path and query parts of the
|
// The :path pseudo-header field includes the path and query parts of the
|
||||||
// target URI (the path-absolute production and optionally a '?' character
|
// target URI (the path-absolute production and optionally a '?' character
|
||||||
// followed by the query production (see Sections 3.3 and 3.4 of
|
// followed by the query production (see Sections 3.3 and 3.4 of
|
||||||
// [RFC3986]).
|
// [RFC3986]).
|
||||||
cc.writeHeader(":authority", host)
|
f(":authority", host)
|
||||||
cc.writeHeader(":method", req.Method)
|
f(":method", req.Method)
|
||||||
if req.Method != "CONNECT" {
|
if req.Method != "CONNECT" {
|
||||||
cc.writeHeader(":path", path)
|
f(":path", path)
|
||||||
cc.writeHeader(":scheme", req.URL.Scheme)
|
f(":scheme", req.URL.Scheme)
|
||||||
}
|
}
|
||||||
if trailers != "" {
|
if trailers != "" {
|
||||||
cc.writeHeader("trailer", trailers)
|
f("trailer", trailers)
|
||||||
}
|
}
|
||||||
|
|
||||||
var didUA bool
|
var didUA bool
|
||||||
for k, vv := range req.Header {
|
for k, vv := range req.Header {
|
||||||
lowKey := strings.ToLower(k)
|
if strings.EqualFold(k, "host") || strings.EqualFold(k, "content-length") {
|
||||||
switch lowKey {
|
|
||||||
case "host", "content-length":
|
|
||||||
// Host is :authority, already sent.
|
// Host is :authority, already sent.
|
||||||
// Content-Length is automatic, set below.
|
// Content-Length is automatic, set below.
|
||||||
continue
|
continue
|
||||||
case "connection", "proxy-connection", "transfer-encoding", "upgrade", "keep-alive":
|
} else if strings.EqualFold(k, "connection") || strings.EqualFold(k, "proxy-connection") ||
|
||||||
|
strings.EqualFold(k, "transfer-encoding") || strings.EqualFold(k, "upgrade") ||
|
||||||
|
strings.EqualFold(k, "keep-alive") {
|
||||||
// Per 8.1.2.2 Connection-Specific Header
|
// Per 8.1.2.2 Connection-Specific Header
|
||||||
// Fields, don't send connection-specific
|
// Fields, don't send connection-specific
|
||||||
// fields. We have already checked if any
|
// fields. We have already checked if any
|
||||||
// are error-worthy so just ignore the rest.
|
// are error-worthy so just ignore the rest.
|
||||||
continue
|
continue
|
||||||
case "user-agent":
|
} else if strings.EqualFold(k, "user-agent") {
|
||||||
// Match Go's http1 behavior: at most one
|
// Match Go's http1 behavior: at most one
|
||||||
// User-Agent. If set to nil or empty string,
|
// User-Agent. If set to nil or empty string,
|
||||||
// then omit it. Otherwise if not mentioned,
|
// then omit it. Otherwise if not mentioned,
|
||||||
|
@ -1148,20 +1233,43 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
|
||||||
if vv[0] == "" {
|
if vv[0] == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range vv {
|
for _, v := range vv {
|
||||||
cc.writeHeader(lowKey, v)
|
f(k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if shouldSendReqContentLength(req.Method, contentLength) {
|
if shouldSendReqContentLength(req.Method, contentLength) {
|
||||||
cc.writeHeader("content-length", strconv.FormatInt(contentLength, 10))
|
f("content-length", strconv.FormatInt(contentLength, 10))
|
||||||
}
|
}
|
||||||
if addGzipHeader {
|
if addGzipHeader {
|
||||||
cc.writeHeader("accept-encoding", "gzip")
|
f("accept-encoding", "gzip")
|
||||||
}
|
}
|
||||||
if !didUA {
|
if !didUA {
|
||||||
cc.writeHeader("user-agent", defaultUserAgent)
|
f("user-agent", defaultUserAgent)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a first pass over the headers counting bytes to ensure
|
||||||
|
// we don't exceed cc.peerMaxHeaderListSize. This is done as a
|
||||||
|
// separate pass before encoding the headers to prevent
|
||||||
|
// modifying the hpack state.
|
||||||
|
hlSize := uint64(0)
|
||||||
|
enumerateHeaders(func(name, value string) {
|
||||||
|
hf := hpack.HeaderField{Name: name, Value: value}
|
||||||
|
hlSize += uint64(hf.Size())
|
||||||
|
})
|
||||||
|
|
||||||
|
if hlSize > cc.peerMaxHeaderListSize {
|
||||||
|
return nil, errRequestHeaderListSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header list size is ok. Write the headers.
|
||||||
|
enumerateHeaders(func(name, value string) {
|
||||||
|
cc.writeHeader(strings.ToLower(name), value)
|
||||||
|
})
|
||||||
|
|
||||||
return cc.hbuf.Bytes(), nil
|
return cc.hbuf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1188,17 +1296,29 @@ func shouldSendReqContentLength(method string, contentLength int64) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// requires cc.mu be held.
|
// requires cc.mu be held.
|
||||||
func (cc *ClientConn) encodeTrailers(req *http.Request) []byte {
|
func (cc *ClientConn) encodeTrailers(req *http.Request) ([]byte, error) {
|
||||||
cc.hbuf.Reset()
|
cc.hbuf.Reset()
|
||||||
|
|
||||||
|
hlSize := uint64(0)
|
||||||
for k, vv := range req.Trailer {
|
for k, vv := range req.Trailer {
|
||||||
// Transfer-Encoding, etc.. have already been filter at the
|
for _, v := range vv {
|
||||||
|
hf := hpack.HeaderField{Name: k, Value: v}
|
||||||
|
hlSize += uint64(hf.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hlSize > cc.peerMaxHeaderListSize {
|
||||||
|
return nil, errRequestHeaderListSize
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, vv := range req.Trailer {
|
||||||
|
// Transfer-Encoding, etc.. have already been filtered at the
|
||||||
// start of RoundTrip
|
// start of RoundTrip
|
||||||
lowKey := strings.ToLower(k)
|
lowKey := strings.ToLower(k)
|
||||||
for _, v := range vv {
|
for _, v := range vv {
|
||||||
cc.writeHeader(lowKey, v)
|
cc.writeHeader(lowKey, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cc.hbuf.Bytes()
|
return cc.hbuf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *ClientConn) writeHeader(name, value string) {
|
func (cc *ClientConn) writeHeader(name, value string) {
|
||||||
|
@ -1246,7 +1366,9 @@ func (cc *ClientConn) streamByID(id uint32, andRemove bool) *clientStream {
|
||||||
cc.idleTimer.Reset(cc.idleTimeout)
|
cc.idleTimer.Reset(cc.idleTimeout)
|
||||||
}
|
}
|
||||||
close(cs.done)
|
close(cs.done)
|
||||||
cc.cond.Broadcast() // wake up checkResetOrDone via clientStream.awaitFlowControl
|
// Wake up checkResetOrDone via clientStream.awaitFlowControl and
|
||||||
|
// wake up RoundTrip if there is a pending request.
|
||||||
|
cc.cond.Broadcast()
|
||||||
}
|
}
|
||||||
return cs
|
return cs
|
||||||
}
|
}
|
||||||
|
@ -1254,17 +1376,12 @@ func (cc *ClientConn) streamByID(id uint32, andRemove bool) *clientStream {
|
||||||
// clientConnReadLoop is the state owned by the clientConn's frame-reading readLoop.
|
// clientConnReadLoop is the state owned by the clientConn's frame-reading readLoop.
|
||||||
type clientConnReadLoop struct {
|
type clientConnReadLoop struct {
|
||||||
cc *ClientConn
|
cc *ClientConn
|
||||||
activeRes map[uint32]*clientStream // keyed by streamID
|
|
||||||
closeWhenIdle bool
|
closeWhenIdle bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// readLoop runs in its own goroutine and reads and dispatches frames.
|
// readLoop runs in its own goroutine and reads and dispatches frames.
|
||||||
func (cc *ClientConn) readLoop() {
|
func (cc *ClientConn) readLoop() {
|
||||||
rl := &clientConnReadLoop{
|
rl := &clientConnReadLoop{cc: cc}
|
||||||
cc: cc,
|
|
||||||
activeRes: make(map[uint32]*clientStream),
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rl.cleanup()
|
defer rl.cleanup()
|
||||||
cc.readerErr = rl.run()
|
cc.readerErr = rl.run()
|
||||||
if ce, ok := cc.readerErr.(ConnectionError); ok {
|
if ce, ok := cc.readerErr.(ConnectionError); ok {
|
||||||
|
@ -1319,10 +1436,8 @@ func (rl *clientConnReadLoop) cleanup() {
|
||||||
} else if err == io.EOF {
|
} else if err == io.EOF {
|
||||||
err = io.ErrUnexpectedEOF
|
err = io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
for _, cs := range rl.activeRes {
|
|
||||||
cs.bufPipe.CloseWithError(err)
|
|
||||||
}
|
|
||||||
for _, cs := range cc.streams {
|
for _, cs := range cc.streams {
|
||||||
|
cs.bufPipe.CloseWithError(err) // no-op if already closed
|
||||||
select {
|
select {
|
||||||
case cs.resc <- resAndError{err: err}:
|
case cs.resc <- resAndError{err: err}:
|
||||||
default:
|
default:
|
||||||
|
@ -1345,8 +1460,9 @@ func (rl *clientConnReadLoop) run() error {
|
||||||
cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err)
|
cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err)
|
||||||
}
|
}
|
||||||
if se, ok := err.(StreamError); ok {
|
if se, ok := err.(StreamError); ok {
|
||||||
if cs := cc.streamByID(se.StreamID, true /*ended; remove it*/); cs != nil {
|
if cs := cc.streamByID(se.StreamID, false); cs != nil {
|
||||||
cs.cc.writeStreamReset(cs.ID, se.Code, err)
|
cs.cc.writeStreamReset(cs.ID, se.Code, err)
|
||||||
|
cs.cc.forgetStreamID(cs.ID)
|
||||||
if se.Cause == nil {
|
if se.Cause == nil {
|
||||||
se.Cause = cc.fr.errDetail
|
se.Cause = cc.fr.errDetail
|
||||||
}
|
}
|
||||||
|
@ -1399,7 +1515,7 @@ func (rl *clientConnReadLoop) run() error {
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if rl.closeWhenIdle && gotReply && maybeIdle && len(rl.activeRes) == 0 {
|
if rl.closeWhenIdle && gotReply && maybeIdle {
|
||||||
cc.closeIfIdle()
|
cc.closeIfIdle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1407,13 +1523,31 @@ func (rl *clientConnReadLoop) run() error {
|
||||||
|
|
||||||
func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
|
func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
|
||||||
cc := rl.cc
|
cc := rl.cc
|
||||||
cs := cc.streamByID(f.StreamID, f.StreamEnded())
|
cs := cc.streamByID(f.StreamID, false)
|
||||||
if cs == nil {
|
if cs == nil {
|
||||||
// We'd get here if we canceled a request while the
|
// We'd get here if we canceled a request while the
|
||||||
// server had its response still in flight. So if this
|
// server had its response still in flight. So if this
|
||||||
// was just something we canceled, ignore it.
|
// was just something we canceled, ignore it.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if f.StreamEnded() {
|
||||||
|
// Issue 20521: If the stream has ended, streamByID() causes
|
||||||
|
// clientStream.done to be closed, which causes the request's bodyWriter
|
||||||
|
// to be closed with an errStreamClosed, which may be received by
|
||||||
|
// clientConn.RoundTrip before the result of processing these headers.
|
||||||
|
// Deferring stream closure allows the header processing to occur first.
|
||||||
|
// clientConn.RoundTrip may still receive the bodyWriter error first, but
|
||||||
|
// the fix for issue 16102 prioritises any response.
|
||||||
|
//
|
||||||
|
// Issue 22413: If there is no request body, we should close the
|
||||||
|
// stream before writing to cs.resc so that the stream is closed
|
||||||
|
// immediately once RoundTrip returns.
|
||||||
|
if cs.req.Body != nil {
|
||||||
|
defer cc.forgetStreamID(f.StreamID)
|
||||||
|
} else {
|
||||||
|
cc.forgetStreamID(f.StreamID)
|
||||||
|
}
|
||||||
|
}
|
||||||
if !cs.firstByte {
|
if !cs.firstByte {
|
||||||
if cs.trace != nil {
|
if cs.trace != nil {
|
||||||
// TODO(bradfitz): move first response byte earlier,
|
// TODO(bradfitz): move first response byte earlier,
|
||||||
|
@ -1437,6 +1571,7 @@ func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
|
||||||
}
|
}
|
||||||
// Any other error type is a stream error.
|
// Any other error type is a stream error.
|
||||||
cs.cc.writeStreamReset(f.StreamID, ErrCodeProtocol, err)
|
cs.cc.writeStreamReset(f.StreamID, ErrCodeProtocol, err)
|
||||||
|
cc.forgetStreamID(cs.ID)
|
||||||
cs.resc <- resAndError{err: err}
|
cs.resc <- resAndError{err: err}
|
||||||
return nil // return nil from process* funcs to keep conn alive
|
return nil // return nil from process* funcs to keep conn alive
|
||||||
}
|
}
|
||||||
|
@ -1444,9 +1579,6 @@ func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
|
||||||
// (nil, nil) special case. See handleResponse docs.
|
// (nil, nil) special case. See handleResponse docs.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if res.Body != noBody {
|
|
||||||
rl.activeRes[cs.ID] = cs
|
|
||||||
}
|
|
||||||
cs.resTrailer = &res.Trailer
|
cs.resTrailer = &res.Trailer
|
||||||
cs.resc <- resAndError{res: res}
|
cs.resc <- resAndError{res: res}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1466,11 +1598,11 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
|
||||||
|
|
||||||
status := f.PseudoValue("status")
|
status := f.PseudoValue("status")
|
||||||
if status == "" {
|
if status == "" {
|
||||||
return nil, errors.New("missing status pseudo header")
|
return nil, errors.New("malformed response from server: missing status pseudo header")
|
||||||
}
|
}
|
||||||
statusCode, err := strconv.Atoi(status)
|
statusCode, err := strconv.Atoi(status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("malformed non-numeric status pseudo header")
|
return nil, errors.New("malformed response from server: malformed non-numeric status pseudo header")
|
||||||
}
|
}
|
||||||
|
|
||||||
if statusCode == 100 {
|
if statusCode == 100 {
|
||||||
|
@ -1668,6 +1800,7 @@ func (b transportResponseBody) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.bufPipe.BreakWithError(errClosedResponseBody)
|
cs.bufPipe.BreakWithError(errClosedResponseBody)
|
||||||
|
cc.forgetStreamID(cs.ID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1702,7 +1835,23 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if !cs.firstByte {
|
||||||
|
cc.logf("protocol error: received DATA before a HEADERS frame")
|
||||||
|
rl.endStreamError(cs, StreamError{
|
||||||
|
StreamID: f.StreamID,
|
||||||
|
Code: ErrCodeProtocol,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if f.Length > 0 {
|
if f.Length > 0 {
|
||||||
|
if cs.req.Method == "HEAD" && len(data) > 0 {
|
||||||
|
cc.logf("protocol error: received DATA on a HEAD request")
|
||||||
|
rl.endStreamError(cs, StreamError{
|
||||||
|
StreamID: f.StreamID,
|
||||||
|
Code: ErrCodeProtocol,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
// Check connection-level flow control.
|
// Check connection-level flow control.
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
if cs.inflow.available() >= int32(f.Length) {
|
if cs.inflow.available() >= int32(f.Length) {
|
||||||
|
@ -1713,16 +1862,27 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
|
||||||
}
|
}
|
||||||
// Return any padded flow control now, since we won't
|
// Return any padded flow control now, since we won't
|
||||||
// refund it later on body reads.
|
// refund it later on body reads.
|
||||||
if pad := int32(f.Length) - int32(len(data)); pad > 0 {
|
var refund int
|
||||||
cs.inflow.add(pad)
|
if pad := int(f.Length) - len(data); pad > 0 {
|
||||||
cc.inflow.add(pad)
|
refund += pad
|
||||||
|
}
|
||||||
|
// Return len(data) now if the stream is already closed,
|
||||||
|
// since data will never be read.
|
||||||
|
didReset := cs.didReset
|
||||||
|
if didReset {
|
||||||
|
refund += len(data)
|
||||||
|
}
|
||||||
|
if refund > 0 {
|
||||||
|
cc.inflow.add(int32(refund))
|
||||||
cc.wmu.Lock()
|
cc.wmu.Lock()
|
||||||
cc.fr.WriteWindowUpdate(0, uint32(pad))
|
cc.fr.WriteWindowUpdate(0, uint32(refund))
|
||||||
cc.fr.WriteWindowUpdate(cs.ID, uint32(pad))
|
if !didReset {
|
||||||
|
cs.inflow.add(int32(refund))
|
||||||
|
cc.fr.WriteWindowUpdate(cs.ID, uint32(refund))
|
||||||
|
}
|
||||||
cc.bw.Flush()
|
cc.bw.Flush()
|
||||||
cc.wmu.Unlock()
|
cc.wmu.Unlock()
|
||||||
}
|
}
|
||||||
didReset := cs.didReset
|
|
||||||
cc.mu.Unlock()
|
cc.mu.Unlock()
|
||||||
|
|
||||||
if len(data) > 0 && !didReset {
|
if len(data) > 0 && !didReset {
|
||||||
|
@ -1753,11 +1913,10 @@ func (rl *clientConnReadLoop) endStreamError(cs *clientStream, err error) {
|
||||||
err = io.EOF
|
err = io.EOF
|
||||||
code = cs.copyTrailers
|
code = cs.copyTrailers
|
||||||
}
|
}
|
||||||
cs.bufPipe.closeWithErrorAndCode(err, code)
|
|
||||||
delete(rl.activeRes, cs.ID)
|
|
||||||
if isConnectionCloseRequest(cs.req) {
|
if isConnectionCloseRequest(cs.req) {
|
||||||
rl.closeWhenIdle = true
|
rl.closeWhenIdle = true
|
||||||
}
|
}
|
||||||
|
cs.bufPipe.closeWithErrorAndCode(err, code)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case cs.resc <- resAndError{err: err}:
|
case cs.resc <- resAndError{err: err}:
|
||||||
|
@ -1805,6 +1964,8 @@ func (rl *clientConnReadLoop) processSettings(f *SettingsFrame) error {
|
||||||
cc.maxFrameSize = s.Val
|
cc.maxFrameSize = s.Val
|
||||||
case SettingMaxConcurrentStreams:
|
case SettingMaxConcurrentStreams:
|
||||||
cc.maxConcurrentStreams = s.Val
|
cc.maxConcurrentStreams = s.Val
|
||||||
|
case SettingMaxHeaderListSize:
|
||||||
|
cc.peerMaxHeaderListSize = uint64(s.Val)
|
||||||
case SettingInitialWindowSize:
|
case SettingInitialWindowSize:
|
||||||
// Values above the maximum flow-control
|
// Values above the maximum flow-control
|
||||||
// window size of 2^31-1 MUST be treated as a
|
// window size of 2^31-1 MUST be treated as a
|
||||||
|
@ -1882,7 +2043,6 @@ func (rl *clientConnReadLoop) processResetStream(f *RSTStreamFrame) error {
|
||||||
cs.bufPipe.CloseWithError(err)
|
cs.bufPipe.CloseWithError(err)
|
||||||
cs.cc.cond.Broadcast() // wake up checkResetOrDone via clientStream.awaitFlowControl
|
cs.cc.cond.Broadcast() // wake up checkResetOrDone via clientStream.awaitFlowControl
|
||||||
}
|
}
|
||||||
delete(rl.activeRes, cs.ID)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1971,6 +2131,7 @@ func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, err error)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit")
|
errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit")
|
||||||
|
errRequestHeaderListSize = errors.New("http2: request header list larger than peer's advertised limit")
|
||||||
errPseudoTrailers = errors.New("http2: invalid pseudo header in trailers")
|
errPseudoTrailers = errors.New("http2: invalid pseudo header in trailers")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/http2/hpack"
|
"golang.org/x/net/http2/hpack"
|
||||||
"golang.org/x/net/lex/httplex"
|
"golang.org/x/net/lex/httplex"
|
||||||
|
@ -90,11 +89,7 @@ type writeGoAway struct {
|
||||||
|
|
||||||
func (p *writeGoAway) writeFrame(ctx writeContext) error {
|
func (p *writeGoAway) writeFrame(ctx writeContext) error {
|
||||||
err := ctx.Framer().WriteGoAway(p.maxStreamID, p.code, nil)
|
err := ctx.Framer().WriteGoAway(p.maxStreamID, p.code, nil)
|
||||||
if p.code != 0 {
|
|
||||||
ctx.Flush() // ignore error: we're hanging up on them anyway
|
ctx.Flush() // ignore error: we're hanging up on them anyway
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
ctx.CloseConn()
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"golang.org/x/text/secure/bidirule"
|
"golang.org/x/text/secure/bidirule"
|
||||||
|
"golang.org/x/text/unicode/bidi"
|
||||||
"golang.org/x/text/unicode/norm"
|
"golang.org/x/text/unicode/norm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -67,6 +68,15 @@ func VerifyDNSLength(verify bool) Option {
|
||||||
return func(o *options) { o.verifyDNSLength = verify }
|
return func(o *options) { o.verifyDNSLength = verify }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveLeadingDots removes leading label separators. Leading runes that map to
|
||||||
|
// dots, such as U+3002 IDEOGRAPHIC FULL STOP, are removed as well.
|
||||||
|
//
|
||||||
|
// This is the behavior suggested by the UTS #46 and is adopted by some
|
||||||
|
// browsers.
|
||||||
|
func RemoveLeadingDots(remove bool) Option {
|
||||||
|
return func(o *options) { o.removeLeadingDots = remove }
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateLabels sets whether to check the mandatory label validation criteria
|
// ValidateLabels sets whether to check the mandatory label validation criteria
|
||||||
// as defined in Section 5.4 of RFC 5891. This includes testing for correct use
|
// as defined in Section 5.4 of RFC 5891. This includes testing for correct use
|
||||||
// of hyphens ('-'), normalization, validity of runes, and the context rules.
|
// of hyphens ('-'), normalization, validity of runes, and the context rules.
|
||||||
|
@ -83,7 +93,7 @@ func ValidateLabels(enable bool) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrictDomainName limits the set of permissable ASCII characters to those
|
// StrictDomainName limits the set of permissible ASCII characters to those
|
||||||
// allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the
|
// allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the
|
||||||
// hyphen). This is set by default for MapForLookup and ValidateForRegistration.
|
// hyphen). This is set by default for MapForLookup and ValidateForRegistration.
|
||||||
//
|
//
|
||||||
|
@ -141,6 +151,7 @@ type options struct {
|
||||||
useSTD3Rules bool
|
useSTD3Rules bool
|
||||||
validateLabels bool
|
validateLabels bool
|
||||||
verifyDNSLength bool
|
verifyDNSLength bool
|
||||||
|
removeLeadingDots bool
|
||||||
|
|
||||||
trie *idnaTrie
|
trie *idnaTrie
|
||||||
|
|
||||||
|
@ -149,14 +160,14 @@ type options struct {
|
||||||
|
|
||||||
// mapping implements a validation and mapping step as defined in RFC 5895
|
// mapping implements a validation and mapping step as defined in RFC 5895
|
||||||
// or UTS 46, tailored to, for example, domain registration or lookup.
|
// or UTS 46, tailored to, for example, domain registration or lookup.
|
||||||
mapping func(p *Profile, s string) (string, error)
|
mapping func(p *Profile, s string) (mapped string, isBidi bool, err error)
|
||||||
|
|
||||||
// bidirule, if specified, checks whether s conforms to the Bidi Rule
|
// bidirule, if specified, checks whether s conforms to the Bidi Rule
|
||||||
// defined in RFC 5893.
|
// defined in RFC 5893.
|
||||||
bidirule func(s string) bool
|
bidirule func(s string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Profile defines the configuration of a IDNA mapper.
|
// A Profile defines the configuration of an IDNA mapper.
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
options
|
options
|
||||||
}
|
}
|
||||||
|
@ -289,12 +300,16 @@ func (e runeError) Error() string {
|
||||||
// see http://www.unicode.org/reports/tr46.
|
// see http://www.unicode.org/reports/tr46.
|
||||||
func (p *Profile) process(s string, toASCII bool) (string, error) {
|
func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||||
var err error
|
var err error
|
||||||
|
var isBidi bool
|
||||||
if p.mapping != nil {
|
if p.mapping != nil {
|
||||||
s, err = p.mapping(p, s)
|
s, isBidi, err = p.mapping(p, s)
|
||||||
}
|
}
|
||||||
// Remove leading empty labels.
|
// Remove leading empty labels.
|
||||||
|
if p.removeLeadingDots {
|
||||||
for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
|
for ; len(s) > 0 && s[0] == '.'; s = s[1:] {
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// TODO: allow for a quick check of the tables data.
|
||||||
// It seems like we should only create this error on ToASCII, but the
|
// It seems like we should only create this error on ToASCII, but the
|
||||||
// UTS 46 conformance tests suggests we should always check this.
|
// UTS 46 conformance tests suggests we should always check this.
|
||||||
if err == nil && p.verifyDNSLength && s == "" {
|
if err == nil && p.verifyDNSLength && s == "" {
|
||||||
|
@ -320,6 +335,7 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||||
// Spec says keep the old label.
|
// Spec says keep the old label.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
isBidi = isBidi || bidirule.DirectionString(u) != bidi.LeftToRight
|
||||||
labels.set(u)
|
labels.set(u)
|
||||||
if err == nil && p.validateLabels {
|
if err == nil && p.validateLabels {
|
||||||
err = p.fromPuny(p, u)
|
err = p.fromPuny(p, u)
|
||||||
|
@ -334,6 +350,14 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||||
err = p.validateLabel(label)
|
err = p.validateLabel(label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isBidi && p.bidirule != nil && err == nil {
|
||||||
|
for labels.reset(); !labels.done(); labels.next() {
|
||||||
|
if !p.bidirule(labels.label()) {
|
||||||
|
err = &labelError{s, "B"}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if toASCII {
|
if toASCII {
|
||||||
for labels.reset(); !labels.done(); labels.next() {
|
for labels.reset(); !labels.done(); labels.next() {
|
||||||
label := labels.label()
|
label := labels.label()
|
||||||
|
@ -365,41 +389,77 @@ func (p *Profile) process(s string, toASCII bool) (string, error) {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalize(p *Profile, s string) (string, error) {
|
func normalize(p *Profile, s string) (mapped string, isBidi bool, err error) {
|
||||||
return norm.NFC.String(s), nil
|
// TODO: consider first doing a quick check to see if any of these checks
|
||||||
|
// need to be done. This will make it slower in the general case, but
|
||||||
|
// faster in the common case.
|
||||||
|
mapped = norm.NFC.String(s)
|
||||||
|
isBidi = bidirule.DirectionString(mapped) == bidi.RightToLeft
|
||||||
|
return mapped, isBidi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRegistration(p *Profile, s string) (string, error) {
|
func validateRegistration(p *Profile, s string) (idem string, bidi bool, err error) {
|
||||||
|
// TODO: filter need for normalization in loop below.
|
||||||
if !norm.NFC.IsNormalString(s) {
|
if !norm.NFC.IsNormalString(s) {
|
||||||
return s, &labelError{s, "V1"}
|
return s, false, &labelError{s, "V1"}
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
v, sz := trie.lookupString(s[i:])
|
v, sz := trie.lookupString(s[i:])
|
||||||
i += sz
|
if sz == 0 {
|
||||||
|
return s, bidi, runeError(utf8.RuneError)
|
||||||
|
}
|
||||||
|
bidi = bidi || info(v).isBidi(s[i:])
|
||||||
// Copy bytes not copied so far.
|
// Copy bytes not copied so far.
|
||||||
switch p.simplify(info(v).category()) {
|
switch p.simplify(info(v).category()) {
|
||||||
// TODO: handle the NV8 defined in the Unicode idna data set to allow
|
// TODO: handle the NV8 defined in the Unicode idna data set to allow
|
||||||
// for strict conformance to IDNA2008.
|
// for strict conformance to IDNA2008.
|
||||||
case valid, deviation:
|
case valid, deviation:
|
||||||
case disallowed, mapped, unknown, ignored:
|
case disallowed, mapped, unknown, ignored:
|
||||||
if err == nil {
|
|
||||||
r, _ := utf8.DecodeRuneInString(s[i:])
|
r, _ := utf8.DecodeRuneInString(s[i:])
|
||||||
err = runeError(r)
|
return s, bidi, runeError(r)
|
||||||
}
|
}
|
||||||
|
i += sz
|
||||||
}
|
}
|
||||||
}
|
return s, bidi, nil
|
||||||
return s, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAndMap(p *Profile, s string) (string, error) {
|
func (c info) isBidi(s string) bool {
|
||||||
|
if !c.isMapped() {
|
||||||
|
return c&attributesMask == rtl
|
||||||
|
}
|
||||||
|
// TODO: also store bidi info for mapped data. This is possible, but a bit
|
||||||
|
// cumbersome and not for the common case.
|
||||||
|
p, _ := bidi.LookupString(s)
|
||||||
|
switch p.Class() {
|
||||||
|
case bidi.R, bidi.AL, bidi.AN:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAndMap(p *Profile, s string) (vm string, bidi bool, err error) {
|
||||||
var (
|
var (
|
||||||
err error
|
|
||||||
b []byte
|
b []byte
|
||||||
k int
|
k int
|
||||||
)
|
)
|
||||||
|
// combinedInfoBits contains the or-ed bits of all runes. We use this
|
||||||
|
// to derive the mayNeedNorm bit later. This may trigger normalization
|
||||||
|
// overeagerly, but it will not do so in the common case. The end result
|
||||||
|
// is another 10% saving on BenchmarkProfile for the common case.
|
||||||
|
var combinedInfoBits info
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
v, sz := trie.lookupString(s[i:])
|
v, sz := trie.lookupString(s[i:])
|
||||||
|
if sz == 0 {
|
||||||
|
b = append(b, s[k:i]...)
|
||||||
|
b = append(b, "\ufffd"...)
|
||||||
|
k = len(s)
|
||||||
|
if err == nil {
|
||||||
|
err = runeError(utf8.RuneError)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
combinedInfoBits |= info(v)
|
||||||
|
bidi = bidi || info(v).isBidi(s[i:])
|
||||||
start := i
|
start := i
|
||||||
i += sz
|
i += sz
|
||||||
// Copy bytes not copied so far.
|
// Copy bytes not copied so far.
|
||||||
|
@ -408,7 +468,7 @@ func validateAndMap(p *Profile, s string) (string, error) {
|
||||||
continue
|
continue
|
||||||
case disallowed:
|
case disallowed:
|
||||||
if err == nil {
|
if err == nil {
|
||||||
r, _ := utf8.DecodeRuneInString(s[i:])
|
r, _ := utf8.DecodeRuneInString(s[start:])
|
||||||
err = runeError(r)
|
err = runeError(r)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
@ -426,7 +486,9 @@ func validateAndMap(p *Profile, s string) (string, error) {
|
||||||
}
|
}
|
||||||
if k == 0 {
|
if k == 0 {
|
||||||
// No changes so far.
|
// No changes so far.
|
||||||
|
if combinedInfoBits&mayNeedNorm != 0 {
|
||||||
s = norm.NFC.String(s)
|
s = norm.NFC.String(s)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
b = append(b, s[k:]...)
|
b = append(b, s[k:]...)
|
||||||
if norm.NFC.QuickSpan(b) != len(b) {
|
if norm.NFC.QuickSpan(b) != len(b) {
|
||||||
|
@ -435,7 +497,7 @@ func validateAndMap(p *Profile, s string) (string, error) {
|
||||||
// TODO: the punycode converters require strings as input.
|
// TODO: the punycode converters require strings as input.
|
||||||
s = string(b)
|
s = string(b)
|
||||||
}
|
}
|
||||||
return s, err
|
return s, bidi, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// A labelIter allows iterating over domain name labels.
|
// A labelIter allows iterating over domain name labels.
|
||||||
|
@ -530,8 +592,13 @@ func validateFromPunycode(p *Profile, s string) error {
|
||||||
if !norm.NFC.IsNormalString(s) {
|
if !norm.NFC.IsNormalString(s) {
|
||||||
return &labelError{s, "V1"}
|
return &labelError{s, "V1"}
|
||||||
}
|
}
|
||||||
|
// TODO: detect whether string may have to be normalized in the following
|
||||||
|
// loop.
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
v, sz := trie.lookupString(s[i:])
|
v, sz := trie.lookupString(s[i:])
|
||||||
|
if sz == 0 {
|
||||||
|
return runeError(utf8.RuneError)
|
||||||
|
}
|
||||||
if c := p.simplify(info(v).category()); c != valid && c != deviation {
|
if c := p.simplify(info(v).category()); c != valid && c != deviation {
|
||||||
return &labelError{s, "V6"}
|
return &labelError{s, "V6"}
|
||||||
}
|
}
|
||||||
|
@ -604,16 +671,13 @@ var joinStates = [][numJoinTypes]joinState{
|
||||||
|
|
||||||
// validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
|
// validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
|
||||||
// already implicitly satisfied by the overall implementation.
|
// already implicitly satisfied by the overall implementation.
|
||||||
func (p *Profile) validateLabel(s string) error {
|
func (p *Profile) validateLabel(s string) (err error) {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
if p.verifyDNSLength {
|
if p.verifyDNSLength {
|
||||||
return &labelError{s, "A4"}
|
return &labelError{s, "A4"}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if p.bidirule != nil && !p.bidirule(s) {
|
|
||||||
return &labelError{s, "B"}
|
|
||||||
}
|
|
||||||
if !p.validateLabels {
|
if !p.validateLabels {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -26,9 +26,9 @@ package idna
|
||||||
// 15..3 index into xor or mapping table
|
// 15..3 index into xor or mapping table
|
||||||
// }
|
// }
|
||||||
// } else {
|
// } else {
|
||||||
// 15..13 unused
|
// 15..14 unused
|
||||||
// 12 modifier (including virama)
|
// 13 mayNeedNorm
|
||||||
// 11 virama modifier
|
// 12..11 attributes
|
||||||
// 10..8 joining type
|
// 10..8 joining type
|
||||||
// 7..3 category type
|
// 7..3 category type
|
||||||
// }
|
// }
|
||||||
|
@ -49,15 +49,20 @@ const (
|
||||||
joinShift = 8
|
joinShift = 8
|
||||||
joinMask = 0x07
|
joinMask = 0x07
|
||||||
|
|
||||||
viramaModifier = 0x0800
|
// Attributes
|
||||||
|
attributesMask = 0x1800
|
||||||
|
viramaModifier = 0x1800
|
||||||
modifier = 0x1000
|
modifier = 0x1000
|
||||||
|
rtl = 0x0800
|
||||||
|
|
||||||
|
mayNeedNorm = 0x2000
|
||||||
)
|
)
|
||||||
|
|
||||||
// A category corresponds to a category defined in the IDNA mapping table.
|
// A category corresponds to a category defined in the IDNA mapping table.
|
||||||
type category uint16
|
type category uint16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
unknown category = 0 // not defined currently in unicode.
|
unknown category = 0 // not currently defined in unicode.
|
||||||
mapped category = 1
|
mapped category = 1
|
||||||
disallowedSTD3Mapped category = 2
|
disallowedSTD3Mapped category = 2
|
||||||
deviation category = 3
|
deviation category = 3
|
||||||
|
@ -110,5 +115,5 @@ func (c info) isModifier() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c info) isViramaModifier() bool {
|
func (c info) isViramaModifier() bool {
|
||||||
return c&(viramaModifier|catSmallMask) == viramaModifier
|
return c&(attributesMask|catSmallMask) == viramaModifier
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A PerHost directs connections to a default Dialer unless the hostname
|
// A PerHost directs connections to a default Dialer unless the host name
|
||||||
// requested matches one of a number of exceptions.
|
// requested matches one of a number of exceptions.
|
||||||
type PerHost struct {
|
type PerHost struct {
|
||||||
def, bypass Dialer
|
def, bypass Dialer
|
||||||
|
@ -61,7 +61,7 @@ func (p *PerHost) dialerForRequest(host string) Dialer {
|
||||||
return p.bypass
|
return p.bypass
|
||||||
}
|
}
|
||||||
if host == zone[1:] {
|
if host == zone[1:] {
|
||||||
// For a zone "example.com", we match "example.com"
|
// For a zone ".example.com", we match "example.com"
|
||||||
// too.
|
// too.
|
||||||
return p.bypass
|
return p.bypass
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ func (p *PerHost) dialerForRequest(host string) Dialer {
|
||||||
|
|
||||||
// AddFromString parses a string that contains comma-separated values
|
// AddFromString parses a string that contains comma-separated values
|
||||||
// specifying hosts that should use the bypass proxy. Each value is either an
|
// specifying hosts that should use the bypass proxy. Each value is either an
|
||||||
// IP address, a CIDR range, a zone (*.example.com) or a hostname
|
// IP address, a CIDR range, a zone (*.example.com) or a host name
|
||||||
// (localhost). A best effort is made to parse the string and errors are
|
// (localhost). A best effort is made to parse the string and errors are
|
||||||
// ignored.
|
// ignored.
|
||||||
func (p *PerHost) AddFromString(s string) {
|
func (p *PerHost) AddFromString(s string) {
|
||||||
|
@ -131,7 +131,7 @@ func (p *PerHost) AddZone(zone string) {
|
||||||
p.bypassZones = append(p.bypassZones, zone)
|
p.bypassZones = append(p.bypassZones, zone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddHost specifies a hostname that will use the bypass proxy.
|
// AddHost specifies a host name that will use the bypass proxy.
|
||||||
func (p *PerHost) AddHost(host string) {
|
func (p *PerHost) AddHost(host string) {
|
||||||
if strings.HasSuffix(host, ".") {
|
if strings.HasSuffix(host, ".") {
|
||||||
host = host[:len(host)-1]
|
host = host[:len(host)-1]
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Dialer is a means to establish a connection.
|
// A Dialer is a means to establish a connection.
|
||||||
|
@ -27,7 +28,7 @@ type Auth struct {
|
||||||
// FromEnvironment returns the dialer specified by the proxy related variables in
|
// FromEnvironment returns the dialer specified by the proxy related variables in
|
||||||
// the environment.
|
// the environment.
|
||||||
func FromEnvironment() Dialer {
|
func FromEnvironment() Dialer {
|
||||||
allProxy := os.Getenv("all_proxy")
|
allProxy := allProxyEnv.Get()
|
||||||
if len(allProxy) == 0 {
|
if len(allProxy) == 0 {
|
||||||
return Direct
|
return Direct
|
||||||
}
|
}
|
||||||
|
@ -41,7 +42,7 @@ func FromEnvironment() Dialer {
|
||||||
return Direct
|
return Direct
|
||||||
}
|
}
|
||||||
|
|
||||||
noProxy := os.Getenv("no_proxy")
|
noProxy := noProxyEnv.Get()
|
||||||
if len(noProxy) == 0 {
|
if len(noProxy) == 0 {
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
|
@ -92,3 +93,42 @@ func FromURL(u *url.URL, forward Dialer) (Dialer, error) {
|
||||||
|
|
||||||
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
|
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
allProxyEnv = &envOnce{
|
||||||
|
names: []string{"ALL_PROXY", "all_proxy"},
|
||||||
|
}
|
||||||
|
noProxyEnv = &envOnce{
|
||||||
|
names: []string{"NO_PROXY", "no_proxy"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// envOnce looks up an environment variable (optionally by multiple
|
||||||
|
// names) once. It mitigates expensive lookups on some platforms
|
||||||
|
// (e.g. Windows).
|
||||||
|
// (Borrowed from net/http/transport.go)
|
||||||
|
type envOnce struct {
|
||||||
|
names []string
|
||||||
|
once sync.Once
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *envOnce) Get() string {
|
||||||
|
e.once.Do(e.init)
|
||||||
|
return e.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *envOnce) init() {
|
||||||
|
for _, n := range e.names {
|
||||||
|
e.val = os.Getenv(n)
|
||||||
|
if e.val != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset is used by tests
|
||||||
|
func (e *envOnce) reset() {
|
||||||
|
e.once = sync.Once{}
|
||||||
|
e.val = ""
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||||||
// with an optional username and password. See RFC 1928.
|
// with an optional username and password. See RFC 1928 and RFC 1929.
|
||||||
func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
|
func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
|
||||||
s := &socks5{
|
s := &socks5{
|
||||||
network: network,
|
network: network,
|
||||||
|
@ -60,7 +60,7 @@ var socks5Errors = []string{
|
||||||
"address type not supported",
|
"address type not supported",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
|
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
|
||||||
func (s *socks5) Dial(network, addr string) (net.Conn, error) {
|
func (s *socks5) Dial(network, addr string) (net.Conn, error) {
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp", "tcp6", "tcp4":
|
case "tcp", "tcp6", "tcp4":
|
||||||
|
@ -120,6 +120,7 @@ func (s *socks5) connect(conn net.Conn, target string) error {
|
||||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See RFC 1929
|
||||||
if buf[1] == socks5AuthPassword {
|
if buf[1] == socks5AuthPassword {
|
||||||
buf = buf[:0]
|
buf = buf[:0]
|
||||||
buf = append(buf, 1 /* password protocol version */)
|
buf = append(buf, 1 /* password protocol version */)
|
||||||
|
@ -154,7 +155,7 @@ func (s *socks5) connect(conn net.Conn, target string) error {
|
||||||
buf = append(buf, ip...)
|
buf = append(buf, ip...)
|
||||||
} else {
|
} else {
|
||||||
if len(host) > 255 {
|
if len(host) > 255 {
|
||||||
return errors.New("proxy: destination hostname too long: " + host)
|
return errors.New("proxy: destination host name too long: " + host)
|
||||||
}
|
}
|
||||||
buf = append(buf, socks5Domain)
|
buf = append(buf, socks5Domain)
|
||||||
buf = append(buf, byte(len(host)))
|
buf = append(buf, byte(len(host)))
|
||||||
|
|
|
@ -39,9 +39,9 @@ var buckets = []bucket{
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderEvents renders the HTML page typically served at /debug/events.
|
// RenderEvents renders the HTML page typically served at /debug/events.
|
||||||
// It does not do any auth checking; see AuthRequest for the default auth check
|
// It does not do any auth checking. The request may be nil.
|
||||||
// used by the handler registered on http.DefaultServeMux.
|
//
|
||||||
// req may be nil.
|
// Most users will use the Events handler.
|
||||||
func RenderEvents(w http.ResponseWriter, req *http.Request, sensitive bool) {
|
func RenderEvents(w http.ResponseWriter, req *http.Request, sensitive bool) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
data := &struct {
|
data := &struct {
|
||||||
|
|
|
@ -110,7 +110,18 @@ var AuthRequest = func(req *http.Request) (any, sensitive bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
http.HandleFunc("/debug/requests", func(w http.ResponseWriter, req *http.Request) {
|
// TODO(jbd): Serve Traces from /debug/traces in the future?
|
||||||
|
// There is no requirement for a request to be present to have traces.
|
||||||
|
http.HandleFunc("/debug/requests", Traces)
|
||||||
|
http.HandleFunc("/debug/events", Events)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traces responds with traces from the program.
|
||||||
|
// The package initialization registers it in http.DefaultServeMux
|
||||||
|
// at /debug/requests.
|
||||||
|
//
|
||||||
|
// It performs authorization by running AuthRequest.
|
||||||
|
func Traces(w http.ResponseWriter, req *http.Request) {
|
||||||
any, sensitive := AuthRequest(req)
|
any, sensitive := AuthRequest(req)
|
||||||
if !any {
|
if !any {
|
||||||
http.Error(w, "not allowed", http.StatusUnauthorized)
|
http.Error(w, "not allowed", http.StatusUnauthorized)
|
||||||
|
@ -118,8 +129,14 @@ func init() {
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
Render(w, req, sensitive)
|
Render(w, req, sensitive)
|
||||||
})
|
}
|
||||||
http.HandleFunc("/debug/events", func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
|
// Events responds with a page of events collected by EventLogs.
|
||||||
|
// The package initialization registers it in http.DefaultServeMux
|
||||||
|
// at /debug/events.
|
||||||
|
//
|
||||||
|
// It performs authorization by running AuthRequest.
|
||||||
|
func Events(w http.ResponseWriter, req *http.Request) {
|
||||||
any, sensitive := AuthRequest(req)
|
any, sensitive := AuthRequest(req)
|
||||||
if !any {
|
if !any {
|
||||||
http.Error(w, "not allowed", http.StatusUnauthorized)
|
http.Error(w, "not allowed", http.StatusUnauthorized)
|
||||||
|
@ -127,13 +144,12 @@ func init() {
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
RenderEvents(w, req, sensitive)
|
RenderEvents(w, req, sensitive)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders the HTML page typically served at /debug/requests.
|
// Render renders the HTML page typically served at /debug/requests.
|
||||||
// It does not do any auth checking; see AuthRequest for the default auth check
|
// It does not do any auth checking. The request may be nil.
|
||||||
// used by the handler registered on http.DefaultServeMux.
|
//
|
||||||
// req may be nil.
|
// Most users will use the Traces handler.
|
||||||
func Render(w io.Writer, req *http.Request, sensitive bool) {
|
func Render(w io.Writer, req *http.Request, sensitive bool) {
|
||||||
data := &struct {
|
data := &struct {
|
||||||
Families []string
|
Families []string
|
||||||
|
|
Loading…
Reference in New Issue