From e5c672b24ae3ac5c3e4622337dc3756446589487 Mon Sep 17 00:00:00 2001 From: Suhail Patel Date: Tue, 11 Aug 2026 14:28:27 +0100 Subject: [PATCH 1/8] Update to Go 1.24 in go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index af35d9ce..87d751e3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/monzo/typhon -go 1.19 +go 1.24 require ( github.com/deckarep/golang-set v1.8.0 From 3259c26e6e2f5aa808f1f4c24bb763729decca40 Mon Sep 17 00:00:00 2001 From: Suhail Patel Date: Tue, 11 Aug 2026 14:33:12 +0100 Subject: [PATCH 2/8] Make H2cFilter a no-op This will be managed by the protocol options on the server itself --- h2c.go | 153 +++------------------------------------------------------ 1 file changed, 8 insertions(+), 145 deletions(-) diff --git a/h2c.go b/h2c.go index 0d532505..2c0e949a 100644 --- a/h2c.go +++ b/h2c.go @@ -1,150 +1,13 @@ package typhon -import ( - "bufio" - "context" - "math" - "net" - "net/http" - "net/textproto" - "sync" - - mapset "github.com/deckarep/golang-set" - "github.com/monzo/terrors" - "golang.org/x/net/http/httpguts" - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" -) - -// H2cFilter adds HTTP/2 h2c upgrade support to the wrapped Service (as defined in RFC 7540 §3.2, §3.4). +// H2cFilter previously added HTTP/2 h2c upgrade support to the wrapped Service by hijacking the +// connection and running golang.org/x/net/http2/h2c. +// +// Deprecated: h2c (HTTP/2 cleartext, prior knowledge) is now enabled natively for every Typhon +// server via Go's net/http support (Go 1.24+); see Serve. This filter is a no-op retained only for +// backwards compatibility and will be removed in a future release. It is no longer necessary to +// apply it. Note that the native implementation only supports h2c with prior knowledge, not the +// (unused by Go clients) RFC 7540 §3.2 `Upgrade: h2c` handshake. func H2cFilter(req Request, svc Service) Response { - h := req.Header - // h2c with prior knowledge (RFC 7540 §3.4) - isPrior := (req.Method == "PRI" && len(h) == 0 && req.URL.Path == "*" && req.Proto == "HTTP/2.0") - // h2c upgrade (RFC 7540 §3.2) - isUpgrade := httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Upgrade")], "h2c") && - httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Connection")], "HTTP2-Settings") - if isPrior || isUpgrade { - rsp := NewResponse(req) - rw, h2s, err := setupH2cHijacker(req, rsp.Writer()) - if err != nil { - return Response{Error: err} - } - h2c.NewHandler(HttpHandler(svc), h2s).ServeHTTP(rw, &req.Request) - return rsp - } return svc(req) } - -// Dear reader: I'm sorry, the code below isn't fun. This is because Go's h2c implementation doesn't have support for -// connection draining, and all the hooks that would make this easy are unexported. -// -// If this ticket gets resolved this code can be dramatically simplified, but it is not a priority for the Go team: -// https://github.com/golang/go/issues/26682 -// -// 🤢 - -var h2cConns sync.Map // map[*Server]*h2cInfo - -// h2cInfo stores information about connections that have been upgraded by a single Typhon server -type h2cInfo struct { - conns mapset.Set - h2s *http2.Server -} - -// hijackedConn represents a network connection that has been hijacked for a h2c upgrade. This is necessary because we -// need to know when the connection has been closed, to know if/when graceful shutdown completes. -type hijackedConn struct { - net.Conn - onClose func(*hijackedConn) - closed chan struct{} - closeOnce sync.Once -} - -func (c *hijackedConn) Close() error { - defer c.closeOnce.Do(func() { - close(c.closed) - c.onClose(c) - }) - return c.Conn.Close() -} - -type h2cHijacker struct { - http.ResponseWriter - http.Hijacker - onHijack func(*hijackedConn) -} - -func (h h2cHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) { - c, r, err := h.Hijacker.Hijack() - conn := &hijackedConn{ - Conn: c, - closed: make(chan struct{})} - h.onHijack(conn) - return conn, r, err -} - -func shutdownH2c(ctx context.Context, srv *Server, h2c *h2cInfo) { -gracefulCloseLoop: - for _, _c := range h2c.conns.ToSlice() { - c := _c.(*hijackedConn) - select { - case <-ctx.Done(): - break gracefulCloseLoop - case <-c.closed: - h2c.conns.Remove(c) - } - } - // If any connections remain after gracefulCloseLoop, we need to forcefully close them - for _, _c := range h2c.conns.ToSlice() { - c := _c.(*hijackedConn) - c.Close() - h2c.conns.Remove(c) - } - h2cConns.Delete(srv) -} - -func setupH2cHijacker(req Request, rw http.ResponseWriter) (http.ResponseWriter, *http2.Server, error) { - hijacker, ok := rw.(http.Hijacker) - if !ok { - err := terrors.InternalService("hijack_impossible", "Cannot hijack response; h2c upgrade impossible", nil) - return nil, nil, err - } - srv := req.server - if srv == nil { - return rw, &http2.Server{}, nil - } - - h2c := &h2cInfo{ - conns: mapset.NewSet(), - h2s: &http2.Server{ - // We're copying envoy and grpc by setting this to the max uint32. - // The Go default is 250 which is not ideal for long lived streaming requests. - MaxConcurrentStreams: math.MaxUint32, - }} - _h2c, loaded := h2cConns.LoadOrStore(srv, h2c) - h2c = _h2c.(*h2cInfo) - if !loaded { - // http2.ConfigureServer wires up an unexported method within the http2 library so it gracefully drains h2c - // connections when the http1 server is stopped. However, this happens asynchronously: the http1 server will - // think it has shut down before the h2c connections have finished draining. To work around this, we add - // a shutdown function of our own in the Typhon server which waits for connections to be drained, or if things - // timeout before then to terminate them forcefully. - http2.ConfigureServer(srv.srv, h2c.h2s) - srv.addShutdownFunc(func(ctx context.Context) { - shutdownH2c(ctx, srv, h2c) - }) - } - - h := h2cHijacker{ - ResponseWriter: rw, - Hijacker: hijacker, - onHijack: func(c *hijackedConn) { - h2c.conns.Add(c) - // when the connection closes, remove from h2cInfo's to prevent refs to dead connections accumulating - c.onClose = func(c *hijackedConn) { - h2c.conns.Remove(c) - } - }} - return h, h2c.h2s, nil -} From 89d8b5615da5e52f4c0dac24d7757c8036211de3 Mon Sep 17 00:00:00 2001 From: Suhail Patel Date: Tue, 11 Aug 2026 14:33:54 +0100 Subject: [PATCH 3/8] Enable HTTP2 protocol options on the server for h2c --- server.go | 71 +++++++++++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/server.go b/server.go index f0ba998e..378001de 100644 --- a/server.go +++ b/server.go @@ -3,6 +3,7 @@ package typhon import ( "context" "fmt" + "math" "net" "net/http" "os" @@ -14,12 +15,10 @@ import ( ) type Server struct { - l net.Listener - srv *http.Server - shuttingDown chan struct{} - shutdownOnce sync.Once - shutdownFuncs []func(context.Context) - shutdownFuncsM sync.Mutex + l net.Listener + srv *http.Server + shuttingDown chan struct{} + shutdownOnce sync.Once } // ServerOption allows customizing the underling http.Server @@ -39,55 +38,45 @@ func (s *Server) Done() <-chan struct{} { // Stop shuts down the server, returning when there are no more connections still open. Graceful shutdown will be // attempted until the passed context expires, at which time all connections will be forcibly terminated. func (s *Server) Stop(ctx context.Context) { - s.shutdownFuncsM.Lock() - defer s.shutdownFuncsM.Unlock() s.shutdownOnce.Do(func() { close(s.shuttingDown) - // Shut down the HTTP server in parallel to calling any custom shutdown functions - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - if err := s.srv.Shutdown(ctx); err != nil { - slog.Debug(ctx, "Graceful shutdown failed; forcibly closing connections 👢") - if err := s.srv.Close(); err != nil { - slog.Critical(ctx, "Forceful shutdown failed, exiting 😱: %v", err) - panic(err) // Something is super hosed here - } + // Gracefully shut down the HTTP server, draining in-flight requests until the context + // expires. Since Go 1.24 this drains h2c connections just like HTTP/1.1 ones, so no bespoke + // connection tracking is required (see H2cFilter). + if err := s.srv.Shutdown(ctx); err != nil { + slog.Debug(ctx, "Graceful shutdown failed; forcibly closing connections 👢") + if err := s.srv.Close(); err != nil { + slog.Critical(ctx, "Forceful shutdown failed, exiting 😱: %v", err) + panic(err) // Something is super hosed here } - }() - for _, f := range s.shutdownFuncs { - f := f // capture range variable - wg.Add(1) - go func() { - defer wg.Done() - f(ctx) - }() } - wg.Wait() }) } -// addShutdownFunc registers a function that will be called when the server is stopped. The function is expected to try -// to shutdown gracefully until the context expires, at which time it should terminate its work forcefully. -func (s *Server) addShutdownFunc(f func(context.Context)) { - s.shutdownFuncsM.Lock() - defer s.shutdownFuncsM.Unlock() - s.shutdownFuncs = append(s.shutdownFuncs, f) -} - // Serve starts a HTTP server, binding the passed Service to the passed listener and applying the passed ServerOptions. func Serve(svc Service, l net.Listener, opts ...ServerOption) (*Server, error) { s := &Server{ l: l, shuttingDown: make(chan struct{})} - svc = svc.Filter(func(req Request, svc Service) Response { - req.server = s - return svc(req) - }) + + // Enable HTTP/1.1, TLS HTTP/2, and unencrypted HTTP/2 (h2c, prior knowledge). Since Go 1.24 + // net/http supports h2c natively, so we no longer need golang.org/x/net/http2/h2c and its + // connection-draining workarounds: h2c connections now drain during graceful shutdown just like + // HTTP/1.1 ones. See https://github.com/golang/go/issues/26682. + protocols := new(http.Protocols) + protocols.SetHTTP1(true) + protocols.SetHTTP2(true) + protocols.SetUnencryptedHTTP2(true) + s.srv = &http.Server{ Handler: HttpHandler(svc), MaxHeaderBytes: http.DefaultMaxHeaderBytes, + Protocols: protocols, + HTTP2: &http.HTTP2Config{ + // We're copying envoy and grpc by setting this to the max uint32. + // The Go default is 250 which is not ideal for long lived streaming requests. + MaxConcurrentStreams: math.MaxUint32, + }, } // Apply any given ServerOptions @@ -136,8 +125,6 @@ func Listen(svc Service, addr string, opts ...ServerOption) (*Server, error) { // TimeoutOptions specifies various server timeouts. See http.Server for details of what these do. // There's a nice post explaining them here: https://ieftimov.com/posts/make-resilient-golang-net-http-servers-using-timeouts-deadlines-context-cancellation/#server-timeouts---first-principles -// WARNING: Due to a Go bug, connections using h2c do not respect these timeouts. -// See https://github.com/golang/go/issues/52868 type TimeoutOptions struct { Read time.Duration ReadHeader time.Duration From 89e39ab23e0f710f35f2d9e77f90b96961b7cb4c Mon Sep 17 00:00:00 2001 From: Suhail Patel Date: Tue, 11 Aug 2026 14:35:47 +0100 Subject: [PATCH 4/8] Update client to speak native h2c This removes the use of golang.org/x/net/http2 --- client.go | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/client.go b/client.go index 8adc010a..13c8bc00 100644 --- a/client.go +++ b/client.go @@ -2,13 +2,10 @@ package typhon import ( "context" - "crypto/tls" - "net" "net/http" "time" "github.com/monzo/terrors" - "golang.org/x/net/http2" ) var ( @@ -28,18 +25,25 @@ var ( } // H2cRoundTripper is a prior-knowledge H2c client. It does not support ProxyFromEnvironment. - H2cRoundTripper http.RoundTripper = &http2.Transport{ - AllowHTTP: true, - // This monstrosity is needed to get the http2 Transport to dial over cleartext. - // See https://github.com/thrawn01/h2c-golang-example - DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) { - var d net.Dialer - return d.DialContext(ctx, network, addr) + H2cRoundTripper http.RoundTripper = newH2cRoundTripper() +) + +// newH2cRoundTripper builds a transport that speaks unencrypted HTTP/2 (h2c) with prior knowledge +func newH2cRoundTripper() *http.Transport { + t := &http.Transport{ + HTTP2: &http.HTTP2Config{ + // Health-check idle connections: send a ping after 30s without a frame, and close the + // connection if no response arrives within 10s. + SendPingTimeout: 30 * time.Second, + PingTimeout: 10 * time.Second, }, - ReadIdleTimeout: 30 * time.Second, - PingTimeout: 10 * time.Second, } -) + // Enabling UnencryptedHTTP2 without HTTP1 makes the transport use h2c (prior knowledge) for + // http:// URLs with no HTTP/1.1 fallback. + t.Protocols = new(http.Protocols) + t.Protocols.SetUnencryptedHTTP2(true) + return t +} // A ResponseFuture is a container for a Response which will materialise at some point. type ResponseFuture struct { From 23a6a127d1b883736574d786ca4dee6a75299d7c Mon Sep 17 00:00:00 2001 From: Suhail Patel Date: Tue, 11 Aug 2026 14:37:07 +0100 Subject: [PATCH 5/8] Remove now unused github.com/deckarep/golang-set dependency --- go.mod | 1 - go.sum | 2 - .../github.com/deckarep/golang-set/.gitignore | 22 -- vendor/github.com/deckarep/golang-set/LICENSE | 22 -- .../github.com/deckarep/golang-set/README.md | 95 ----- .../deckarep/golang-set/iterator.go | 58 --- vendor/github.com/deckarep/golang-set/set.go | 217 ----------- .../deckarep/golang-set/threadsafe.go | 283 --------------- .../deckarep/golang-set/threadunsafe.go | 340 ------------------ vendor/golang.org/x/net/http2/h2c/h2c.go | 240 ------------- vendor/modules.txt | 4 - 11 files changed, 1284 deletions(-) delete mode 100644 vendor/github.com/deckarep/golang-set/.gitignore delete mode 100644 vendor/github.com/deckarep/golang-set/LICENSE delete mode 100644 vendor/github.com/deckarep/golang-set/README.md delete mode 100644 vendor/github.com/deckarep/golang-set/iterator.go delete mode 100644 vendor/github.com/deckarep/golang-set/set.go delete mode 100644 vendor/github.com/deckarep/golang-set/threadsafe.go delete mode 100644 vendor/github.com/deckarep/golang-set/threadunsafe.go delete mode 100644 vendor/golang.org/x/net/http2/h2c/h2c.go diff --git a/go.mod b/go.mod index 87d751e3..6a41a13c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/monzo/typhon go 1.24 require ( - github.com/deckarep/golang-set v1.8.0 github.com/fortytw2/leaktest v1.3.0 github.com/golang/protobuf v1.5.2 github.com/monzo/slog v0.0.0-20211123154010-52a5ddb2ba55 diff --git a/go.sum b/go.sum index 6f61732f..2fd0abef 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= -github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= diff --git a/vendor/github.com/deckarep/golang-set/.gitignore b/vendor/github.com/deckarep/golang-set/.gitignore deleted file mode 100644 index 00268614..00000000 --- a/vendor/github.com/deckarep/golang-set/.gitignore +++ /dev/null @@ -1,22 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe diff --git a/vendor/github.com/deckarep/golang-set/LICENSE b/vendor/github.com/deckarep/golang-set/LICENSE deleted file mode 100644 index b5768f89..00000000 --- a/vendor/github.com/deckarep/golang-set/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Open Source Initiative OSI - The MIT License (MIT):Licensing - -The MIT License (MIT) -Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/deckarep/golang-set/README.md b/vendor/github.com/deckarep/golang-set/README.md deleted file mode 100644 index c3b50b2c..00000000 --- a/vendor/github.com/deckarep/golang-set/README.md +++ /dev/null @@ -1,95 +0,0 @@ -[![Build Status](https://travis-ci.org/deckarep/golang-set.svg?branch=master)](https://travis-ci.org/deckarep/golang-set) -[![Go Report Card](https://goreportcard.com/badge/github.com/deckarep/golang-set)](https://goreportcard.com/report/github.com/deckarep/golang-set) -[![GoDoc](https://godoc.org/github.com/deckarep/golang-set?status.svg)](http://godoc.org/github.com/deckarep/golang-set) - -## golang-set - - -The missing set collection for the Go language. Until Go has sets built-in...use this. - -Coming from Python one of the things I miss is the superbly wonderful set collection. This is my attempt to mimic the primary features of the set from Python. -You can of course argue that there is no need for a set in Go, otherwise the creators would have added one to the standard library. To those I say simply ignore this repository -and carry-on and to the rest that find this useful please contribute in helping me make it better by: - -* Helping to make more idiomatic improvements to the code. -* Helping to increase the performance of it. ~~(So far, no attempt has been made, but since it uses a map internally, I expect it to be mostly performant.)~~ -* Helping to make the unit-tests more robust and kick-ass. -* Helping to fill in the [documentation.](http://godoc.org/github.com/deckarep/golang-set) -* Simply offering feedback and suggestions. (Positive, constructive feedback is appreciated.) - -I have to give some credit for helping seed the idea with this post on [stackoverflow.](http://programmers.stackexchange.com/questions/177428/sets-data-structure-in-golang) - -*Update* - as of 3/9/2014, you can use a compile-time generic version of this package in the [gen](http://clipperhouse.github.io/gen/) framework. This framework allows you to use the golang-set in a completely generic and type-safe way by allowing you to generate a supporting .go file based on your custom types. - -## Features (as of 9/22/2014) - -* a CartesianProduct() method has been added with unit-tests: [Read more about the cartesian product](http://en.wikipedia.org/wiki/Cartesian_product) - -## Features (as of 9/15/2014) - -* a PowerSet() method has been added with unit-tests: [Read more about the Power set](http://en.wikipedia.org/wiki/Power_set) - -## Features (as of 4/22/2014) - -* One common interface to both implementations -* Two set implementations to choose from - * a thread-safe implementation designed for concurrent use - * a non-thread-safe implementation designed for performance -* 75 benchmarks for both implementations -* 35 unit tests for both implementations -* 14 concurrent tests for the thread-safe implementation - - - -Please see the unit test file for additional usage examples. The Python set documentation will also do a better job than I can of explaining how a set typically [works.](http://docs.python.org/2/library/sets.html) Please keep in mind -however that the Python set is a built-in type and supports additional features and syntax that make it awesome. - -## Examples but not exhaustive: - -```go -requiredClasses := mapset.NewSet() -requiredClasses.Add("Cooking") -requiredClasses.Add("English") -requiredClasses.Add("Math") -requiredClasses.Add("Biology") - -scienceSlice := []interface{}{"Biology", "Chemistry"} -scienceClasses := mapset.NewSetFromSlice(scienceSlice) - -electiveClasses := mapset.NewSet() -electiveClasses.Add("Welding") -electiveClasses.Add("Music") -electiveClasses.Add("Automotive") - -bonusClasses := mapset.NewSet() -bonusClasses.Add("Go Programming") -bonusClasses.Add("Python Programming") - -//Show me all the available classes I can take -allClasses := requiredClasses.Union(scienceClasses).Union(electiveClasses).Union(bonusClasses) -fmt.Println(allClasses) //Set{Cooking, English, Math, Chemistry, Welding, Biology, Music, Automotive, Go Programming, Python Programming} - - -//Is cooking considered a science class? -fmt.Println(scienceClasses.Contains("Cooking")) //false - -//Show me all classes that are not science classes, since I hate science. -fmt.Println(allClasses.Difference(scienceClasses)) //Set{Music, Automotive, Go Programming, Python Programming, Cooking, English, Math, Welding} - -//Which science classes are also required classes? -fmt.Println(scienceClasses.Intersect(requiredClasses)) //Set{Biology} - -//How many bonus classes do you offer? -fmt.Println(bonusClasses.Cardinality()) //2 - -//Do you have the following classes? Welding, Automotive and English? -fmt.Println(allClasses.IsSuperset(mapset.NewSetFromSlice([]interface{}{"Welding", "Automotive", "English"}))) //true -``` - -Thanks! - --Ralph - -[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/deckarep/golang-set/trend.png)](https://bitdeli.com/free "Bitdeli Badge") - -[![Analytics](https://ga-beacon.appspot.com/UA-42584447-2/deckarep/golang-set)](https://github.com/igrigorik/ga-beacon) diff --git a/vendor/github.com/deckarep/golang-set/iterator.go b/vendor/github.com/deckarep/golang-set/iterator.go deleted file mode 100644 index 9dfecade..00000000 --- a/vendor/github.com/deckarep/golang-set/iterator.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Open Source Initiative OSI - The MIT License (MIT):Licensing - -The MIT License (MIT) -Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -package mapset - -// Iterator defines an iterator over a Set, its C channel can be used to range over the Set's -// elements. -type Iterator struct { - C <-chan interface{} - stop chan struct{} -} - -// Stop stops the Iterator, no further elements will be received on C, C will be closed. -func (i *Iterator) Stop() { - // Allows for Stop() to be called multiple times - // (close() panics when called on already closed channel) - defer func() { - recover() - }() - - close(i.stop) - - // Exhaust any remaining elements. - for range i.C { - } -} - -// newIterator returns a new Iterator instance together with its item and stop channels. -func newIterator() (*Iterator, chan<- interface{}, <-chan struct{}) { - itemChan := make(chan interface{}) - stopChan := make(chan struct{}) - return &Iterator{ - C: itemChan, - stop: stopChan, - }, itemChan, stopChan -} diff --git a/vendor/github.com/deckarep/golang-set/set.go b/vendor/github.com/deckarep/golang-set/set.go deleted file mode 100644 index 29eb2e5a..00000000 --- a/vendor/github.com/deckarep/golang-set/set.go +++ /dev/null @@ -1,217 +0,0 @@ -/* -Open Source Initiative OSI - The MIT License (MIT):Licensing - -The MIT License (MIT) -Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -// Package mapset implements a simple and generic set collection. -// Items stored within it are unordered and unique. It supports -// typical set operations: membership testing, intersection, union, -// difference, symmetric difference and cloning. -// -// Package mapset provides two implementations of the Set -// interface. The default implementation is safe for concurrent -// access, but a non-thread-safe implementation is also provided for -// programs that can benefit from the slight speed improvement and -// that can enforce mutual exclusion through other means. -package mapset - -// Set is the primary interface provided by the mapset package. It -// represents an unordered set of data and a large number of -// operations that can be applied to that set. -type Set interface { - // Adds an element to the set. Returns whether - // the item was added. - Add(i interface{}) bool - - // Returns the number of elements in the set. - Cardinality() int - - // Removes all elements from the set, leaving - // the empty set. - Clear() - - // Returns a clone of the set using the same - // implementation, duplicating all keys. - Clone() Set - - // Returns whether the given items - // are all in the set. - Contains(i ...interface{}) bool - - // Returns the difference between this set - // and other. The returned set will contain - // all elements of this set that are not also - // elements of other. - // - // Note that the argument to Difference - // must be of the same type as the receiver - // of the method. Otherwise, Difference will - // panic. - Difference(other Set) Set - - // Determines if two sets are equal to each - // other. If they have the same cardinality - // and contain the same elements, they are - // considered equal. The order in which - // the elements were added is irrelevant. - // - // Note that the argument to Equal must be - // of the same type as the receiver of the - // method. Otherwise, Equal will panic. - Equal(other Set) bool - - // Returns a new set containing only the elements - // that exist only in both sets. - // - // Note that the argument to Intersect - // must be of the same type as the receiver - // of the method. Otherwise, Intersect will - // panic. - Intersect(other Set) Set - - // Determines if every element in this set is in - // the other set but the two sets are not equal. - // - // Note that the argument to IsProperSubset - // must be of the same type as the receiver - // of the method. Otherwise, IsProperSubset - // will panic. - IsProperSubset(other Set) bool - - // Determines if every element in the other set - // is in this set but the two sets are not - // equal. - // - // Note that the argument to IsSuperset - // must be of the same type as the receiver - // of the method. Otherwise, IsSuperset will - // panic. - IsProperSuperset(other Set) bool - - // Determines if every element in this set is in - // the other set. - // - // Note that the argument to IsSubset - // must be of the same type as the receiver - // of the method. Otherwise, IsSubset will - // panic. - IsSubset(other Set) bool - - // Determines if every element in the other set - // is in this set. - // - // Note that the argument to IsSuperset - // must be of the same type as the receiver - // of the method. Otherwise, IsSuperset will - // panic. - IsSuperset(other Set) bool - - // Iterates over elements and executes the passed func against each element. - // If passed func returns true, stop iteration at the time. - Each(func(interface{}) bool) - - // Returns a channel of elements that you can - // range over. - Iter() <-chan interface{} - - // Returns an Iterator object that you can - // use to range over the set. - Iterator() *Iterator - - // Remove a single element from the set. - Remove(i interface{}) - - // Provides a convenient string representation - // of the current state of the set. - String() string - - // Returns a new set with all elements which are - // in either this set or the other set but not in both. - // - // Note that the argument to SymmetricDifference - // must be of the same type as the receiver - // of the method. Otherwise, SymmetricDifference - // will panic. - SymmetricDifference(other Set) Set - - // Returns a new set with all elements in both sets. - // - // Note that the argument to Union must be of the - - // same type as the receiver of the method. - // Otherwise, IsSuperset will panic. - Union(other Set) Set - - // Pop removes and returns an arbitrary item from the set. - Pop() interface{} - - // Returns all subsets of a given set (Power Set). - PowerSet() Set - - // Returns the Cartesian Product of two sets. - CartesianProduct(other Set) Set - - // Returns the members of the set as a slice. - ToSlice() []interface{} -} - -// NewSet creates and returns a reference to an empty set. Operations -// on the resulting set are thread-safe. -func NewSet(s ...interface{}) Set { - set := newThreadSafeSet() - for _, item := range s { - set.Add(item) - } - return &set -} - -// NewSetWith creates and returns a new set with the given elements. -// Operations on the resulting set are thread-safe. -func NewSetWith(elts ...interface{}) Set { - return NewSetFromSlice(elts) -} - -// NewSetFromSlice creates and returns a reference to a set from an -// existing slice. Operations on the resulting set are thread-safe. -func NewSetFromSlice(s []interface{}) Set { - a := NewSet(s...) - return a -} - -// NewThreadUnsafeSet creates and returns a reference to an empty set. -// Operations on the resulting set are not thread-safe. -func NewThreadUnsafeSet() Set { - set := newThreadUnsafeSet() - return &set -} - -// NewThreadUnsafeSetFromSlice creates and returns a reference to a -// set from an existing slice. Operations on the resulting set are -// not thread-safe. -func NewThreadUnsafeSetFromSlice(s []interface{}) Set { - a := NewThreadUnsafeSet() - for _, item := range s { - a.Add(item) - } - return a -} diff --git a/vendor/github.com/deckarep/golang-set/threadsafe.go b/vendor/github.com/deckarep/golang-set/threadsafe.go deleted file mode 100644 index 269b4ab0..00000000 --- a/vendor/github.com/deckarep/golang-set/threadsafe.go +++ /dev/null @@ -1,283 +0,0 @@ -/* -Open Source Initiative OSI - The MIT License (MIT):Licensing - -The MIT License (MIT) -Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -package mapset - -import "sync" - -type threadSafeSet struct { - s threadUnsafeSet - sync.RWMutex -} - -func newThreadSafeSet() threadSafeSet { - return threadSafeSet{s: newThreadUnsafeSet()} -} - -func (set *threadSafeSet) Add(i interface{}) bool { - set.Lock() - ret := set.s.Add(i) - set.Unlock() - return ret -} - -func (set *threadSafeSet) Contains(i ...interface{}) bool { - set.RLock() - ret := set.s.Contains(i...) - set.RUnlock() - return ret -} - -func (set *threadSafeSet) IsSubset(other Set) bool { - o := other.(*threadSafeSet) - - set.RLock() - o.RLock() - - ret := set.s.IsSubset(&o.s) - set.RUnlock() - o.RUnlock() - return ret -} - -func (set *threadSafeSet) IsProperSubset(other Set) bool { - o := other.(*threadSafeSet) - - set.RLock() - defer set.RUnlock() - o.RLock() - defer o.RUnlock() - - return set.s.IsProperSubset(&o.s) -} - -func (set *threadSafeSet) IsSuperset(other Set) bool { - return other.IsSubset(set) -} - -func (set *threadSafeSet) IsProperSuperset(other Set) bool { - return other.IsProperSubset(set) -} - -func (set *threadSafeSet) Union(other Set) Set { - o := other.(*threadSafeSet) - - set.RLock() - o.RLock() - - unsafeUnion := set.s.Union(&o.s).(*threadUnsafeSet) - ret := &threadSafeSet{s: *unsafeUnion} - set.RUnlock() - o.RUnlock() - return ret -} - -func (set *threadSafeSet) Intersect(other Set) Set { - o := other.(*threadSafeSet) - - set.RLock() - o.RLock() - - unsafeIntersection := set.s.Intersect(&o.s).(*threadUnsafeSet) - ret := &threadSafeSet{s: *unsafeIntersection} - set.RUnlock() - o.RUnlock() - return ret -} - -func (set *threadSafeSet) Difference(other Set) Set { - o := other.(*threadSafeSet) - - set.RLock() - o.RLock() - - unsafeDifference := set.s.Difference(&o.s).(*threadUnsafeSet) - ret := &threadSafeSet{s: *unsafeDifference} - set.RUnlock() - o.RUnlock() - return ret -} - -func (set *threadSafeSet) SymmetricDifference(other Set) Set { - o := other.(*threadSafeSet) - - set.RLock() - o.RLock() - - unsafeDifference := set.s.SymmetricDifference(&o.s).(*threadUnsafeSet) - ret := &threadSafeSet{s: *unsafeDifference} - set.RUnlock() - o.RUnlock() - return ret -} - -func (set *threadSafeSet) Clear() { - set.Lock() - set.s = newThreadUnsafeSet() - set.Unlock() -} - -func (set *threadSafeSet) Remove(i interface{}) { - set.Lock() - delete(set.s, i) - set.Unlock() -} - -func (set *threadSafeSet) Cardinality() int { - set.RLock() - defer set.RUnlock() - return len(set.s) -} - -func (set *threadSafeSet) Each(cb func(interface{}) bool) { - set.RLock() - for elem := range set.s { - if cb(elem) { - break - } - } - set.RUnlock() -} - -func (set *threadSafeSet) Iter() <-chan interface{} { - ch := make(chan interface{}) - go func() { - set.RLock() - - for elem := range set.s { - ch <- elem - } - close(ch) - set.RUnlock() - }() - - return ch -} - -func (set *threadSafeSet) Iterator() *Iterator { - iterator, ch, stopCh := newIterator() - - go func() { - set.RLock() - L: - for elem := range set.s { - select { - case <-stopCh: - break L - case ch <- elem: - } - } - close(ch) - set.RUnlock() - }() - - return iterator -} - -func (set *threadSafeSet) Equal(other Set) bool { - o := other.(*threadSafeSet) - - set.RLock() - o.RLock() - - ret := set.s.Equal(&o.s) - set.RUnlock() - o.RUnlock() - return ret -} - -func (set *threadSafeSet) Clone() Set { - set.RLock() - - unsafeClone := set.s.Clone().(*threadUnsafeSet) - ret := &threadSafeSet{s: *unsafeClone} - set.RUnlock() - return ret -} - -func (set *threadSafeSet) String() string { - set.RLock() - ret := set.s.String() - set.RUnlock() - return ret -} - -func (set *threadSafeSet) PowerSet() Set { - set.RLock() - unsafePowerSet := set.s.PowerSet().(*threadUnsafeSet) - set.RUnlock() - - ret := &threadSafeSet{s: newThreadUnsafeSet()} - for subset := range unsafePowerSet.Iter() { - unsafeSubset := subset.(*threadUnsafeSet) - ret.Add(&threadSafeSet{s: *unsafeSubset}) - } - return ret -} - -func (set *threadSafeSet) Pop() interface{} { - set.Lock() - defer set.Unlock() - return set.s.Pop() -} - -func (set *threadSafeSet) CartesianProduct(other Set) Set { - o := other.(*threadSafeSet) - - set.RLock() - o.RLock() - - unsafeCartProduct := set.s.CartesianProduct(&o.s).(*threadUnsafeSet) - ret := &threadSafeSet{s: *unsafeCartProduct} - set.RUnlock() - o.RUnlock() - return ret -} - -func (set *threadSafeSet) ToSlice() []interface{} { - keys := make([]interface{}, 0, set.Cardinality()) - set.RLock() - for elem := range set.s { - keys = append(keys, elem) - } - set.RUnlock() - return keys -} - -func (set *threadSafeSet) MarshalJSON() ([]byte, error) { - set.RLock() - b, err := set.s.MarshalJSON() - set.RUnlock() - - return b, err -} - -func (set *threadSafeSet) UnmarshalJSON(p []byte) error { - set.RLock() - err := set.s.UnmarshalJSON(p) - set.RUnlock() - - return err -} diff --git a/vendor/github.com/deckarep/golang-set/threadunsafe.go b/vendor/github.com/deckarep/golang-set/threadunsafe.go deleted file mode 100644 index 927eb231..00000000 --- a/vendor/github.com/deckarep/golang-set/threadunsafe.go +++ /dev/null @@ -1,340 +0,0 @@ -/* -Open Source Initiative OSI - The MIT License (MIT):Licensing - -The MIT License (MIT) -Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -package mapset - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" - "strings" -) - -type threadUnsafeSet map[interface{}]struct{} - -// An OrderedPair represents a 2-tuple of values. -type OrderedPair struct { - First interface{} - Second interface{} -} - -func newThreadUnsafeSet() threadUnsafeSet { - return make(threadUnsafeSet) -} - -// Equal says whether two 2-tuples contain the same values in the same order. -func (pair *OrderedPair) Equal(other OrderedPair) bool { - if pair.First == other.First && - pair.Second == other.Second { - return true - } - - return false -} - -func (set *threadUnsafeSet) Add(i interface{}) bool { - _, found := (*set)[i] - if found { - return false //False if it existed already - } - - (*set)[i] = struct{}{} - return true -} - -func (set *threadUnsafeSet) Contains(i ...interface{}) bool { - for _, val := range i { - if _, ok := (*set)[val]; !ok { - return false - } - } - return true -} - -func (set *threadUnsafeSet) IsSubset(other Set) bool { - _ = other.(*threadUnsafeSet) - if set.Cardinality() > other.Cardinality() { - return false - } - for elem := range *set { - if !other.Contains(elem) { - return false - } - } - return true -} - -func (set *threadUnsafeSet) IsProperSubset(other Set) bool { - return set.IsSubset(other) && !set.Equal(other) -} - -func (set *threadUnsafeSet) IsSuperset(other Set) bool { - return other.IsSubset(set) -} - -func (set *threadUnsafeSet) IsProperSuperset(other Set) bool { - return set.IsSuperset(other) && !set.Equal(other) -} - -func (set *threadUnsafeSet) Union(other Set) Set { - o := other.(*threadUnsafeSet) - - unionedSet := newThreadUnsafeSet() - - for elem := range *set { - unionedSet.Add(elem) - } - for elem := range *o { - unionedSet.Add(elem) - } - return &unionedSet -} - -func (set *threadUnsafeSet) Intersect(other Set) Set { - o := other.(*threadUnsafeSet) - - intersection := newThreadUnsafeSet() - // loop over smaller set - if set.Cardinality() < other.Cardinality() { - for elem := range *set { - if other.Contains(elem) { - intersection.Add(elem) - } - } - } else { - for elem := range *o { - if set.Contains(elem) { - intersection.Add(elem) - } - } - } - return &intersection -} - -func (set *threadUnsafeSet) Difference(other Set) Set { - _ = other.(*threadUnsafeSet) - - difference := newThreadUnsafeSet() - for elem := range *set { - if !other.Contains(elem) { - difference.Add(elem) - } - } - return &difference -} - -func (set *threadUnsafeSet) SymmetricDifference(other Set) Set { - _ = other.(*threadUnsafeSet) - - aDiff := set.Difference(other) - bDiff := other.Difference(set) - return aDiff.Union(bDiff) -} - -func (set *threadUnsafeSet) Clear() { - *set = newThreadUnsafeSet() -} - -func (set *threadUnsafeSet) Remove(i interface{}) { - delete(*set, i) -} - -func (set *threadUnsafeSet) Cardinality() int { - return len(*set) -} - -func (set *threadUnsafeSet) Each(cb func(interface{}) bool) { - for elem := range *set { - if cb(elem) { - break - } - } -} - -func (set *threadUnsafeSet) Iter() <-chan interface{} { - ch := make(chan interface{}) - go func() { - for elem := range *set { - ch <- elem - } - close(ch) - }() - - return ch -} - -func (set *threadUnsafeSet) Iterator() *Iterator { - iterator, ch, stopCh := newIterator() - - go func() { - L: - for elem := range *set { - select { - case <-stopCh: - break L - case ch <- elem: - } - } - close(ch) - }() - - return iterator -} - -func (set *threadUnsafeSet) Equal(other Set) bool { - _ = other.(*threadUnsafeSet) - - if set.Cardinality() != other.Cardinality() { - return false - } - for elem := range *set { - if !other.Contains(elem) { - return false - } - } - return true -} - -func (set *threadUnsafeSet) Clone() Set { - clonedSet := newThreadUnsafeSet() - for elem := range *set { - clonedSet.Add(elem) - } - return &clonedSet -} - -func (set *threadUnsafeSet) String() string { - items := make([]string, 0, len(*set)) - - for elem := range *set { - items = append(items, fmt.Sprintf("%v", elem)) - } - return fmt.Sprintf("Set{%s}", strings.Join(items, ", ")) -} - -// String outputs a 2-tuple in the form "(A, B)". -func (pair OrderedPair) String() string { - return fmt.Sprintf("(%v, %v)", pair.First, pair.Second) -} - -func (set *threadUnsafeSet) Pop() interface{} { - for item := range *set { - delete(*set, item) - return item - } - return nil -} - -func (set *threadUnsafeSet) PowerSet() Set { - powSet := NewThreadUnsafeSet() - nullset := newThreadUnsafeSet() - powSet.Add(&nullset) - - for es := range *set { - u := newThreadUnsafeSet() - j := powSet.Iter() - for er := range j { - p := newThreadUnsafeSet() - if reflect.TypeOf(er).Name() == "" { - k := er.(*threadUnsafeSet) - for ek := range *(k) { - p.Add(ek) - } - } else { - p.Add(er) - } - p.Add(es) - u.Add(&p) - } - - powSet = powSet.Union(&u) - } - - return powSet -} - -func (set *threadUnsafeSet) CartesianProduct(other Set) Set { - o := other.(*threadUnsafeSet) - cartProduct := NewThreadUnsafeSet() - - for i := range *set { - for j := range *o { - elem := OrderedPair{First: i, Second: j} - cartProduct.Add(elem) - } - } - - return cartProduct -} - -func (set *threadUnsafeSet) ToSlice() []interface{} { - keys := make([]interface{}, 0, set.Cardinality()) - for elem := range *set { - keys = append(keys, elem) - } - - return keys -} - -// MarshalJSON creates a JSON array from the set, it marshals all elements -func (set *threadUnsafeSet) MarshalJSON() ([]byte, error) { - items := make([]string, 0, set.Cardinality()) - - for elem := range *set { - b, err := json.Marshal(elem) - if err != nil { - return nil, err - } - - items = append(items, string(b)) - } - - return []byte(fmt.Sprintf("[%s]", strings.Join(items, ","))), nil -} - -// UnmarshalJSON recreates a set from a JSON array, it only decodes -// primitive types. Numbers are decoded as json.Number. -func (set *threadUnsafeSet) UnmarshalJSON(b []byte) error { - var i []interface{} - - d := json.NewDecoder(bytes.NewReader(b)) - d.UseNumber() - err := d.Decode(&i) - if err != nil { - return err - } - - for _, v := range i { - switch t := v.(type) { - case []interface{}, map[string]interface{}: - continue - default: - set.Add(t) - } - } - - return nil -} diff --git a/vendor/golang.org/x/net/http2/h2c/h2c.go b/vendor/golang.org/x/net/http2/h2c/h2c.go deleted file mode 100644 index a72bbed1..00000000 --- a/vendor/golang.org/x/net/http2/h2c/h2c.go +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2018 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. - -// Package h2c implements the unencrypted "h2c" form of HTTP/2. -// -// The h2c protocol is the non-TLS version of HTTP/2 which is not available from -// net/http or golang.org/x/net/http2. -package h2c - -import ( - "bufio" - "bytes" - "encoding/base64" - "errors" - "fmt" - "io" - "log" - "net" - "net/http" - "net/textproto" - "os" - "strings" - - "golang.org/x/net/http/httpguts" - "golang.org/x/net/http2" -) - -var ( - http2VerboseLogs bool -) - -func init() { - e := os.Getenv("GODEBUG") - if strings.Contains(e, "http2debug=1") || strings.Contains(e, "http2debug=2") { - http2VerboseLogs = true - } -} - -// h2cHandler is a Handler which implements h2c by hijacking the HTTP/1 traffic -// that should be h2c traffic. There are two ways to begin a h2c connection -// (RFC 7540 Section 3.2 and 3.4): (1) Starting with Prior Knowledge - this -// works by starting an h2c connection with a string of bytes that is valid -// HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to -// h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to -// h2c. When either of those situations occur we hijack the HTTP/1 connection, -// convert it to a HTTP/2 connection and pass the net.Conn to http2.ServeConn. -type h2cHandler struct { - Handler http.Handler - s *http2.Server -} - -// NewHandler returns an http.Handler that wraps h, intercepting any h2c -// traffic. If a request is an h2c connection, it's hijacked and redirected to -// s.ServeConn. Otherwise the returned Handler just forwards requests to h. This -// works because h2c is designed to be parseable as valid HTTP/1, but ignored by -// any HTTP server that does not handle h2c. Therefore we leverage the HTTP/1 -// compatible parts of the Go http library to parse and recognize h2c requests. -// Once a request is recognized as h2c, we hijack the connection and convert it -// to an HTTP/2 connection which is understandable to s.ServeConn. (s.ServeConn -// understands HTTP/2 except for the h2c part of it.) -// -// The first request on an h2c connection is read entirely into memory before -// the Handler is called. To limit the memory consumed by this request, wrap -// the result of NewHandler in an http.MaxBytesHandler. -func NewHandler(h http.Handler, s *http2.Server) http.Handler { - return &h2cHandler{ - Handler: h, - s: s, - } -} - -// extractServer extracts existing http.Server instance from http.Request or create an empty http.Server -func extractServer(r *http.Request) *http.Server { - server, ok := r.Context().Value(http.ServerContextKey).(*http.Server) - if ok { - return server - } - return new(http.Server) -} - -// ServeHTTP implement the h2c support that is enabled by h2c.GetH2CHandler. -func (s h2cHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Handle h2c with prior knowledge (RFC 7540 Section 3.4) - if r.Method == "PRI" && len(r.Header) == 0 && r.URL.Path == "*" && r.Proto == "HTTP/2.0" { - if http2VerboseLogs { - log.Print("h2c: attempting h2c with prior knowledge.") - } - conn, err := initH2CWithPriorKnowledge(w) - if err != nil { - if http2VerboseLogs { - log.Printf("h2c: error h2c with prior knowledge: %v", err) - } - return - } - defer conn.Close() - s.s.ServeConn(conn, &http2.ServeConnOpts{ - Context: r.Context(), - BaseConfig: extractServer(r), - Handler: s.Handler, - SawClientPreface: true, - }) - return - } - // Handle Upgrade to h2c (RFC 7540 Section 3.2) - if isH2CUpgrade(r.Header) { - conn, settings, err := h2cUpgrade(w, r) - if err != nil { - if http2VerboseLogs { - log.Printf("h2c: error h2c upgrade: %v", err) - } - w.WriteHeader(http.StatusInternalServerError) - return - } - defer conn.Close() - s.s.ServeConn(conn, &http2.ServeConnOpts{ - Context: r.Context(), - BaseConfig: extractServer(r), - Handler: s.Handler, - UpgradeRequest: r, - Settings: settings, - }) - return - } - s.Handler.ServeHTTP(w, r) - return -} - -// initH2CWithPriorKnowledge implements creating a h2c connection with prior -// knowledge (Section 3.4) and creates a net.Conn suitable for http2.ServeConn. -// All we have to do is look for the client preface that is suppose to be part -// of the body, and reforward the client preface on the net.Conn this function -// creates. -func initH2CWithPriorKnowledge(w http.ResponseWriter) (net.Conn, error) { - hijacker, ok := w.(http.Hijacker) - if !ok { - return nil, errors.New("h2c: connection does not support Hijack") - } - conn, rw, err := hijacker.Hijack() - if err != nil { - return nil, err - } - - const expectedBody = "SM\r\n\r\n" - - buf := make([]byte, len(expectedBody)) - n, err := io.ReadFull(rw, buf) - if err != nil { - return nil, fmt.Errorf("h2c: error reading client preface: %s", err) - } - - if string(buf[:n]) == expectedBody { - return newBufConn(conn, rw), nil - } - - conn.Close() - return nil, errors.New("h2c: invalid client preface") -} - -// h2cUpgrade establishes a h2c connection using the HTTP/1 upgrade (Section 3.2). -func h2cUpgrade(w http.ResponseWriter, r *http.Request) (_ net.Conn, settings []byte, err error) { - settings, err = getH2Settings(r.Header) - if err != nil { - return nil, nil, err - } - hijacker, ok := w.(http.Hijacker) - if !ok { - return nil, nil, errors.New("h2c: connection does not support Hijack") - } - - body, err := io.ReadAll(r.Body) - if err != nil { - return nil, nil, err - } - r.Body = io.NopCloser(bytes.NewBuffer(body)) - - conn, rw, err := hijacker.Hijack() - if err != nil { - return nil, nil, err - } - - rw.Write([]byte("HTTP/1.1 101 Switching Protocols\r\n" + - "Connection: Upgrade\r\n" + - "Upgrade: h2c\r\n\r\n")) - return newBufConn(conn, rw), settings, nil -} - -// isH2CUpgrade returns true if the header properly request an upgrade to h2c -// as specified by Section 3.2. -func isH2CUpgrade(h http.Header) bool { - return httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Upgrade")], "h2c") && - httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Connection")], "HTTP2-Settings") -} - -// getH2Settings returns the settings in the HTTP2-Settings header. -func getH2Settings(h http.Header) ([]byte, error) { - vals, ok := h[textproto.CanonicalMIMEHeaderKey("HTTP2-Settings")] - if !ok { - return nil, errors.New("missing HTTP2-Settings header") - } - if len(vals) != 1 { - return nil, fmt.Errorf("expected 1 HTTP2-Settings. Got: %v", vals) - } - settings, err := base64.RawURLEncoding.DecodeString(vals[0]) - if err != nil { - return nil, err - } - return settings, nil -} - -func newBufConn(conn net.Conn, rw *bufio.ReadWriter) net.Conn { - rw.Flush() - if rw.Reader.Buffered() == 0 { - // If there's no buffered data to be read, - // we can just discard the bufio.ReadWriter. - return conn - } - return &bufConn{conn, rw.Reader} -} - -// bufConn wraps a net.Conn, but reads drain the bufio.Reader first. -type bufConn struct { - net.Conn - *bufio.Reader -} - -func (c *bufConn) Read(p []byte) (int, error) { - if c.Reader == nil { - return c.Conn.Read(p) - } - n := c.Reader.Buffered() - if n == 0 { - c.Reader = nil - return c.Conn.Read(p) - } - if n < len(p) { - p = p[:n] - } - return c.Reader.Read(p) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 89d8681a..cb9a0a37 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,9 +1,6 @@ # github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew -# github.com/deckarep/golang-set v1.8.0 -## explicit; go 1.17 -github.com/deckarep/golang-set # github.com/fortytw2/leaktest v1.3.0 ## explicit github.com/fortytw2/leaktest @@ -36,7 +33,6 @@ github.com/stretchr/testify/require ## explicit; go 1.17 golang.org/x/net/http/httpguts golang.org/x/net/http2 -golang.org/x/net/http2/h2c golang.org/x/net/http2/hpack golang.org/x/net/idna # golang.org/x/text v0.7.0 From bae1a5bd095b5f5b318342273f2bf1c273cbc254 Mon Sep 17 00:00:00 2001 From: Suhail Patel Date: Tue, 11 Aug 2026 14:37:52 +0100 Subject: [PATCH 6/8] Update end to end tests for appropriate error Also add a test around forceful shutdown --- e2e_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/e2e_test.go b/e2e_test.go index 95a0c259..03ef274d 100644 --- a/e2e_test.go +++ b/e2e_test.go @@ -710,9 +710,11 @@ func TestE2EStreamingServerAbort(t *testing.T) { <-done body, err := ioutil.ReadAll(rsp.Body) assert.Equal(t, "derp\n", string(body)) - require.IsType(t, http2.StreamError{}, err) - streamErr := err.(http2.StreamError) - assert.Equal(t, streamErr.Code, http2.ErrCodeInternal) + // The client sees an HTTP/2 stream error carrying INTERNAL_ERROR. We match on the error + // string rather than a concrete type because the error comes from x/net/http2 for some + // flavours and from net/http's (unexported) native HTTP/2 for the prior-knowledge flavour. + require.Error(t, err) + assert.Contains(t, err.Error(), http2.ErrCodeInternal.String()) }) } @@ -809,16 +811,61 @@ func TestE2EDraining(t *testing.T) { }) } +// TestE2EForcefulShutdown verifies that when graceful shutdown can't complete before the context +// expires, the server forcibly terminates in-flight connections rather than hanging. This exercises +// the srv.Close() fallback in Server.Stop, which (for h2c) used to be provided by hand-rolled +// connection tracking but is now delegated to net/http. +func TestE2EForcefulShutdown(t *testing.T) { + flavours(t, func(t *testing.T, flav e2eFlavour) { + ctx, cancel := flav.Context() + defer cancel() + + returnRsp := make(chan bool) + svc := Service(func(req Request) Response { + <-returnRsp + return NewResponse(req) + }) + svc = svc.Filter(ErrorFilter) + s := flav.Serve(svc) + + // Send a request, which will hang in the handler until we send to returnRsp + req := NewRequest(ctx, "GET", flav.URL(s), nil) + rspF := req.Send() + time.Sleep(10 * time.Millisecond) // allow connection to be established + + // Stop the server with an already-expired context. Graceful shutdown can't complete (the + // handler is still blocked), so the server must fall back to forcibly closing connections + // and Stop must return promptly rather than waiting for the handler. + expiredCtx, cancelExpired := context.WithCancel(ctx) + cancelExpired() + serverClosed := make(chan struct{}) + go func() { + s.Stop(expiredCtx) + close(serverClosed) + }() + select { + case <-serverClosed: + case <-time.After(5 * time.Second): + require.FailNow(t, "Stop did not return promptly with an expired context") + } + + // The in-flight request should error out because its connection was forcibly closed. + rsp := rspF.Response() + require.Error(t, rsp.Error) + + // Unblock the (still-blocked) handler so its goroutine doesn't leak; its write races against + // the forcibly-closed connection, which is fine. + returnRsp <- true + }) +} + func TestE2EServerTimeouts(t *testing.T) { someFlavours(t, []string{ "http1.1", "http1.1-tls", "http2.0-h2", - - // The Go h2c implementation doesn't currently support server timeouts - // See https://github.com/golang/go/issues/52868 - //"http2.0-h2c", - //"http2.0-h2c-prior-knowledge", + "http2.0-h2c", + "http2.0-h2c-prior-knowledge", }, func(t *testing.T, flav e2eFlavour) { ctx, cancel := flav.Context() defer cancel() @@ -854,9 +901,8 @@ func TestE2EMaxConnectionAge_ConnectionAgeSet(t *testing.T) { "http1.1", "http1.1-tls", "http2.0-h2", - - // The Go h2c implementation doesn't currently support max - // connection age. + "http2.0-h2c", + "http2.0-h2c-prior-knowledge", }, func(t *testing.T, flav e2eFlavour) { ctx, cancel := flav.Context() defer cancel() From eb8f17cdf949ceb23ef270687e607f018a65da9a Mon Sep 17 00:00:00 2001 From: Suhail Patel Date: Tue, 11 Aug 2026 14:42:58 +0100 Subject: [PATCH 7/8] Remove the server from the request --- request.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/request.go b/request.go index ea9352ca..94c7b0b5 100644 --- a/request.go +++ b/request.go @@ -4,14 +4,15 @@ import ( "context" "encoding/json" "fmt" - legacyproto "github.com/golang/protobuf/proto" - "github.com/monzo/terrors" - "google.golang.org/protobuf/proto" "io" "io/ioutil" "net/http" "strings" "time" + + legacyproto "github.com/golang/protobuf/proto" + "github.com/monzo/terrors" + "google.golang.org/protobuf/proto" ) // A Request is Typhon's wrapper around http.Request, used by both clients and servers. @@ -23,7 +24,6 @@ type Request struct { context.Context err error // Any error from request construction; read by ErrorFilter hijacker http.Hijacker - server *Server } // unwrappedContext returns the most "unwrapped" Context possible for that in the request. From 76a06208285c1a065418698b5a9354c9348363e3 Mon Sep 17 00:00:00 2001 From: Suhail Patel Date: Tue, 11 Aug 2026 14:43:35 +0100 Subject: [PATCH 8/8] Connection Reset errors come as INTERNAL_ERROR --- e2e_http2_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e_http2_test.go b/e2e_http2_test.go index 19960a02..057b88c5 100644 --- a/e2e_http2_test.go +++ b/e2e_http2_test.go @@ -69,7 +69,7 @@ func (f http2H2cPriorKnowledgeFlavour) Context() (context.Context, func()) { func (f http2H2cPriorKnowledgeFlavour) AssertConnectionResetError(t *testing.T, terr *terrors.Error) { assert.Equal(t, terrors.ErrInternalService, terr.Code) - assert.Equal(t, "EOF", terr.Message) + assert.Contains(t, terr.Message, "INTERNAL_ERROR") } type http2H2Flavour struct {