From 04b1152224a3cec23cf3c94f22b6c16c7a92cc19 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Wed, 7 Oct 2020 09:29:30 -0500 Subject: [PATCH 1/4] Add grpc-web to http handler --- go.mod | 3 +++ go.sum | 8 ++++++++ http/handler.go | 7 +++++++ server/grpc.go | 25 +++++++++++++++++++++++++ server/server.go | 23 ++++++++++++----------- 5 files changed, 55 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 747138632..2c94909b2 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/cespare/xxhash v1.1.0 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect github.com/davecgh/go-spew v1.1.1 + github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200709123515-8e896a7af361 github.com/dustin/go-humanize v1.0.0 github.com/glycerine/idem v0.0.0-20190127113923-7a8083893311 @@ -21,6 +22,7 @@ require ( github.com/gorilla/handlers v1.3.0 github.com/gorilla/mux v1.7.0 github.com/hashicorp/memberlist v0.1.3 + github.com/improbable-eng/grpc-web v0.13.0 github.com/lib/pq v1.8.0 github.com/opentracing/opentracing-go v1.1.0 github.com/pelletier/go-toml v1.2.0 @@ -30,6 +32,7 @@ require ( github.com/prometheus/prom2json v1.3.0 github.com/rakyll/statik v0.1.7 github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 // indirect + github.com/rs/cors v1.7.0 // indirect github.com/satori/go.uuid v1.2.0 github.com/shirou/gopsutil v2.18.12+incompatible github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect diff --git a/go.sum b/go.sum index 9a94934aa..041527286 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr 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/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200709123515-8e896a7af361 h1:JBNM90aGLCiF9iJYvpvayMpYeW498v5ZDZqE2chqZ2A= github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200709123515-8e896a7af361/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= @@ -102,6 +104,7 @@ github.com/gorilla/handlers v1.3.0 h1:tsg9qP3mjt1h4Roxp+M1paRjrVBfPSOpBuVclh6Ylu github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -122,6 +125,8 @@ github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCO github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/improbable-eng/grpc-web v0.13.0 h1:7XqtaBWaOCH0cVGKHyvhtcuo6fgW32Y10yRKrDHFHOc= +github.com/improbable-eng/grpc-web v0.13.0/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6OL4eEvELdran/3cs= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -152,6 +157,7 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= @@ -197,6 +203,8 @@ github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Ung github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 h1:HQagqIiBmr8YXawX/le3+O26N+vPPC1PtjaF3mwnook= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= diff --git a/http/handler.go b/http/handler.go index 3d916fbcc..e0bb9eca9 100644 --- a/http/handler.go +++ b/http/handler.go @@ -94,6 +94,13 @@ type errorResponse struct { // handlerOption is a functional option type for pilosa.Handler type handlerOption func(s *Handler) error +func OptHandlerMiddleware(middleware func(http.Handler) http.Handler) handlerOption { + return func(h *Handler) error { + h.middleware = append(h.middleware, middleware) + return nil + } +} + func OptHandlerAllowedOrigins(origins []string) handlerOption { return func(h *Handler) error { h.middleware = append(h.middleware, handlers.CORS( diff --git a/server/grpc.go b/server/grpc.go index b25b48e84..c13a91955 100644 --- a/server/grpc.go +++ b/server/grpc.go @@ -19,10 +19,12 @@ import ( "crypto/tls" "fmt" "net" + "net/http" "strings" "sync" "time" + "github.com/improbable-eng/grpc-web/go/grpcweb" "github.com/pilosa/pilosa/v2" "github.com/pilosa/pilosa/v2/logger" pb "github.com/pilosa/pilosa/v2/proto" @@ -1241,6 +1243,29 @@ func (s *grpcServer) Serve(tlsConfig *tls.Config) error { return nil } +func (s *grpcServer) Middleware(origins []string) func(http.Handler) http.Handler { + httpOriginFunc := grpcweb.WithOriginFunc(func(origin string) bool { + for _, x := range origins { + if origin == x { + return true + } + } + return false + }) + + wrappedGrpc := grpcweb.WrapServer(s.grpcServer, httpOriginFunc) + + return func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if wrappedGrpc.IsGrpcWebRequest(r) || wrappedGrpc.IsAcceptableGrpcCorsRequest(r) { + wrappedGrpc.ServeHTTP(w, r) + } else { + h.ServeHTTP(w, r) + } + }) + } +} + // Stop stops the GRPC server. There's no error because the underlying GRPC // stuff doesn't report an error. func (s *grpcServer) Stop() { diff --git a/server/server.go b/server/server.go index 180fd4334..ff6dcce66 100644 --- a/server/server.go +++ b/server/server.go @@ -429,6 +429,16 @@ func (m *Command) SetupServer() error { return errors.Wrap(err, "new api") } + m.grpcServer, err = NewGRPCServer( + OptGRPCServerAPI(m.API), + OptGRPCServerListener(m.grpcLn), + OptGRPCServerLogger(m.logger), + OptGRPCServerStats(statsClient), + ) + if err != nil { + return errors.Wrap(err, "new grpc server") + } + m.Handler, err = http.NewHandler( http.OptHandlerAllowedOrigins(m.Config.Handler.AllowedOrigins), http.OptHandlerAPI(m.API), @@ -436,18 +446,9 @@ func (m *Command) SetupServer() error { http.OptHandlerFileSystem(&statik.FileSystem{}), http.OptHandlerListener(m.ln), http.OptHandlerCloseTimeout(m.closeTimeout), + http.OptHandlerMiddleware(m.grpcServer.Middleware(m.Config.Handler.AllowedOrigins)), ) - if err != nil { - return errors.Wrap(err, "new handler") - } - - m.grpcServer, err = NewGRPCServer( - OptGRPCServerAPI(m.API), - OptGRPCServerListener(m.grpcLn), - OptGRPCServerLogger(m.logger), - OptGRPCServerStats(statsClient), - ) - return errors.Wrap(err, "new grpc server") + return errors.Wrap(err, "new handler") } // setupNetworking sets up internode communication based on the configuration. From 31cd119a7e91451f84d6b434b81e11cfa7a273cf Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Thu, 8 Oct 2020 15:08:57 -0500 Subject: [PATCH 2/4] Move grpc.Server creation into initializer This fixes an issue with the grpc-web middleware using a nil grpc.Server instance. --- server/grpc.go | 52 +++++++++++++++++++++++++----------------------- server/server.go | 5 +++-- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/server/grpc.go b/server/grpc.go index c13a91955..8a4e74c80 100644 --- a/server/grpc.go +++ b/server/grpc.go @@ -1178,9 +1178,9 @@ func fieldDataType(f *pilosa.Field) string { type grpcServer struct { api *pilosa.API - mu sync.Mutex grpcServer *grpc.Server ln net.Listener + tlsConfig *tls.Config logger logger.Logger stats stats.StatsClient @@ -1202,6 +1202,13 @@ func OptGRPCServerListener(ln net.Listener) grpcServerOption { } } +func OptGRPCServerTLSConfig(tlsConfig *tls.Config) grpcServerOption { + return func(s *grpcServer) error { + s.tlsConfig = tlsConfig + return nil + } +} + func OptGRPCServerLogger(logger logger.Logger) grpcServerOption { return func(s *grpcServer) error { s.logger = logger @@ -1216,26 +1223,9 @@ func OptGRPCServerStats(stats stats.StatsClient) grpcServerOption { } } -func (s *grpcServer) Serve(tlsConfig *tls.Config) error { +func (s *grpcServer) Serve() error { s.logger.Printf("enabled grpc listening on %s", s.ln.Addr()) - opts := make([]grpc.ServerOption, 0) - if tlsConfig != nil { - creds := credentials.NewTLS(tlsConfig) - opts = append(opts, grpc.Creds(creds)) - } - - // create grpc server - s.mu.Lock() - s.grpcServer = grpc.NewServer(opts...) - grpcHandler := NewGRPCHandler(s.api).WithLogger(s.logger).WithStats(s.stats) - pb.RegisterPilosaServer(s.grpcServer, grpcHandler) - vdsm_pb.RegisterMoleculaServer(s.grpcServer, NewVDSMGRPCHandler(grpcHandler, s.api).WithLogger(s.logger).WithStats(s.stats)) - - // register the server so its services are available to grpc_cli and others - reflection.Register(s.grpcServer) - s.mu.Unlock() - // and start... if err := s.grpcServer.Serve(s.ln); err != nil { return errors.Wrap(err, "starting grpc server") @@ -1243,7 +1233,7 @@ func (s *grpcServer) Serve(tlsConfig *tls.Config) error { return nil } -func (s *grpcServer) Middleware(origins []string) func(http.Handler) http.Handler { +func (s *grpcServer) middleware(origins []string) func(http.Handler) http.Handler { httpOriginFunc := grpcweb.WithOriginFunc(func(origin string) bool { for _, x := range origins { if origin == x { @@ -1269,11 +1259,7 @@ func (s *grpcServer) Middleware(origins []string) func(http.Handler) http.Handle // Stop stops the GRPC server. There's no error because the underlying GRPC // stuff doesn't report an error. func (s *grpcServer) Stop() { - s.mu.Lock() - defer s.mu.Unlock() - if s.grpcServer != nil { - s.grpcServer.Stop() - } + s.grpcServer.Stop() } func NewGRPCServer(opts ...grpcServerOption) (*grpcServer, error) { @@ -1286,5 +1272,21 @@ func NewGRPCServer(opts ...grpcServerOption) (*grpcServer, error) { return nil, errors.Wrap(err, "applying option") } } + + gopts := make([]grpc.ServerOption, 0) + if server.tlsConfig != nil { + creds := credentials.NewTLS(server.tlsConfig) + gopts = append(gopts, grpc.Creds(creds)) + } + + // create grpc server + server.grpcServer = grpc.NewServer(gopts...) + grpcHandler := NewGRPCHandler(server.api).WithLogger(server.logger).WithStats(server.stats) + pb.RegisterPilosaServer(server.grpcServer, grpcHandler) + vdsm_pb.RegisterMoleculaServer(server.grpcServer, NewVDSMGRPCHandler(grpcHandler, server.api).WithLogger(server.logger).WithStats(server.stats)) + + // register the server so its services are available to grpc_cli and others + reflection.Register(server.grpcServer) + return server, nil } diff --git a/server/server.go b/server/server.go index ff6dcce66..f172f5647 100644 --- a/server/server.go +++ b/server/server.go @@ -171,7 +171,7 @@ func (m *Command) Start() (err error) { m.logger.Printf("listening as %s\n", m.listenURI) go func() { - if err := m.grpcServer.Serve(m.tlsConfig); err != nil { + if err := m.grpcServer.Serve(); err != nil { m.logger.Printf("grpc server error: %v", err) } }() @@ -432,6 +432,7 @@ func (m *Command) SetupServer() error { m.grpcServer, err = NewGRPCServer( OptGRPCServerAPI(m.API), OptGRPCServerListener(m.grpcLn), + OptGRPCServerTLSConfig(m.tlsConfig), OptGRPCServerLogger(m.logger), OptGRPCServerStats(statsClient), ) @@ -446,7 +447,7 @@ func (m *Command) SetupServer() error { http.OptHandlerFileSystem(&statik.FileSystem{}), http.OptHandlerListener(m.ln), http.OptHandlerCloseTimeout(m.closeTimeout), - http.OptHandlerMiddleware(m.grpcServer.Middleware(m.Config.Handler.AllowedOrigins)), + http.OptHandlerMiddleware(m.grpcServer.middleware(m.Config.Handler.AllowedOrigins)), ) return errors.Wrap(err, "new handler") } From a2ee0478683e40e7929e2262449cceb42f2e815e Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Tue, 13 Oct 2020 09:41:57 -0500 Subject: [PATCH 3/4] grpc-web cors test --- server/handler_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/server/handler_test.go b/server/handler_test.go index 156817f2e..e43111839 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -1357,6 +1357,36 @@ func TestHandler_Endpoints(t *testing.T) { t.Fatalf("%v != %v", target, resp.IDs) } }) + + t.Run("grpc-web-cors", func(t *testing.T) { + req := test.MustNewHTTPRequest("OPTIONS", "/pilosa.Pilosa/QueryPQL", nil) + req.Header.Add("Origin", "http://test/") + req.Header.Add("Access-Control-Request-Method", "POST") + + w := httptest.NewRecorder() + h.ServeHTTP(w, req) + result := w.Result() + + // Fail CORS preflight + if result.Header["Access-Control-Allow-Origin"] != nil { + t.Fatalf("CORS preflight includes Access-Control-Allow-Origin but should not.") + } + + clus := test.MustRunCluster(t, 1, []server.CommandOption{test.OptAllowedOrigins([]string{"http://test/"})}) + defer clus.Close() + w = httptest.NewRecorder() + h := clus.GetNode(0).Handler.(*http.Handler).Handler + h.ServeHTTP(w, req) + result = w.Result() + + if result.Header["Access-Control-Allow-Origin"] == nil { + t.Fatalf("CORS preflight does not include Access-Control-Allow-Origin.") + } + + if result.Header["Access-Control-Allow-Origin"][0] != "http://test/" { + t.Fatal("CORS header not present") + } + }) } func TestCluster_TranslateStore(t *testing.T) { From b86b0519af228cdf00b132fdab1edb36bc1d7c84 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Fri, 16 Oct 2020 11:37:13 -0500 Subject: [PATCH 4/4] go mod tidy --- go.sum | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/go.sum b/go.sum index 041527286..0389c9b4b 100644 --- a/go.sum +++ b/go.sum @@ -106,7 +106,9 @@ github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -137,6 +139,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -217,6 +220,7 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+D github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=