diff --git a/bittorrent/params.go b/bittorrent/params.go index 7b2b272..00a8773 100644 --- a/bittorrent/params.go +++ b/bittorrent/params.go @@ -2,11 +2,6 @@ package bittorrent import ( "context" - "errors" - "net/url" - "strconv" - "strings" - "github.com/rs/zerolog" ) @@ -21,39 +16,9 @@ type Params interface { // returned as a string because they are encoded in the URL as strings. String(key string) (string, bool) - // RawPath returns the raw path from the request URL. - // The path returned can contain URL encoded data. - // For a request of the form "/announce?port=1234" this would return - // "/announce". - RawPath() string - - // RawQuery returns the raw query from the request URL, excluding the - // delimiter '?'. - // For a request of the form "/announce?port=1234" this would return - // "port=1234" - RawQuery() string - zerolog.LogObjectMarshaler } -var ( - // ErrKeyNotFound is returned when a provided key has no value associated with - // it. - ErrKeyNotFound = errors.New("query: value for the provided key does not exist") - // ErrInvalidQueryEscape is returned when a query string contains invalid - // escapes. - ErrInvalidQueryEscape = ClientError("invalid query escape") -) - -// QueryParams parses a URL Query and implements the Params interface with some -// additional helpers. -type QueryParams struct { - path string - query string - params map[string]string - infoHashes []InfoHash -} - type routeParamsKey struct{} // RouteParamsKey is a key for the context of a request that @@ -104,135 +69,3 @@ func RemapRouteParamsToBgContext(inCtx context.Context) context.Context { } return context.WithValue(context.Background(), RouteParamsKey, rp) } - -// ParseURLData parses a request URL or UDP URLData as defined in BEP41. -// It expects a concatenated string of the request's path and query parts as -// defined in RFC 3986. As both the udp: and http: scheme used by BitTorrent -// include an authority part the path part must always begin with a slash. -// An example of the expected URLData would be "/announce?port=1234&uploaded=0" -// or "/?auth=0x1337". -// HTTP servers should pass (*http.Request).RequestURI, UDP servers should -// pass the concatenated, unchanged URLData as defined in BEP41. -// -// Note that, in the case of a key occurring multiple times in the query, only -// the last value for that key is kept. -// The only exception to this rule is the key "info_hash" which will attempt to -// parse each value as an InfoHash and return an error if parsing fails. All -// InfoHashes are collected and can later be retrieved by calling the InfoHashes -// method. -// -// Also note that any error that is encountered during parsing is returned as a -// ClientError, as this method is expected to be used to parse client-provided -// data. -func ParseURLData(urlData string) (*QueryParams, error) { - var path, query string - - queryDelim := strings.IndexAny(urlData, "?") - if queryDelim == -1 { - path = urlData - } else { - path = urlData[:queryDelim] - query = urlData[queryDelim+1:] - } - - q, err := parseQuery(query) - if err != nil { - return nil, ClientError(err.Error()) - } - q.path = path - return q, nil -} - -// parseQuery parses a URL query into QueryParams. -// The query is expected to exclude the delimiting '?'. -func parseQuery(query string) (q *QueryParams, err error) { - // This is basically url.parseQuery, but with a map[string]string - // instead of map[string][]string for the values. - q = &QueryParams{ - query: query, - infoHashes: nil, - params: make(map[string]string), - } - - for query != "" { - key := query - if i := strings.IndexAny(key, "&;"); i >= 0 { - key, query = key[:i], key[i+1:] - } else { - query = "" - } - if key == "" { - continue - } - value := "" - if i := strings.Index(key, "="); i >= 0 { - key, value = key[:i], key[i+1:] - } - key, err = url.QueryUnescape(key) - if err != nil { - // QueryUnescape returns an error like "invalid escape: '%x'". - // But frontends record these errors to prometheus, which generates - // a lot of time series. - // We log it here for debugging instead. - return nil, ErrInvalidQueryEscape - } - value, err = url.QueryUnescape(value) - if err != nil { - // QueryUnescape returns an error like "invalid escape: '%x'". - // But frontends record these errors to prometheus, which generates - // a lot of time series. - // We log it here for debugging instead. - return nil, ErrInvalidQueryEscape - } - - if key == "info_hash" { - if ih, err := NewInfoHash(value); err == nil { - q.infoHashes = append(q.infoHashes, ih) - } else { - return nil, err - } - } else { - q.params[strings.ToLower(key)] = value - } - } - - return q, nil -} - -// String returns a string parsed from a query. Every key can be returned as a -// string because they are encoded in the URL as strings. -func (qp QueryParams) String(key string) (string, bool) { - value, ok := qp.params[strings.ToLower(key)] - return value, ok -} - -// Uint returns an uint parsed from a query. After being called, it is safe to -// cast the uint64 to your desired length. -func (qp QueryParams) Uint(key string, bitSize int) (uint64, error) { - str, exists := qp.params[strings.ToLower(key)] - if !exists { - return 0, ErrKeyNotFound - } - - return strconv.ParseUint(str, 10, bitSize) -} - -// InfoHashes returns a list of requested infohashes. -func (qp QueryParams) InfoHashes() []InfoHash { - return qp.infoHashes -} - -// RawPath returns the raw path from the parsed URL. -func (qp QueryParams) RawPath() string { - return qp.path -} - -// RawQuery returns the raw query from the parsed URL. -func (qp QueryParams) RawQuery() string { - return qp.query -} - -// MarshalZerologObject writes fields into zerolog event -func (qp QueryParams) MarshalZerologObject(e *zerolog.Event) { - e.Str("path", qp.path).Str("query", qp.query) -} diff --git a/bittorrent/peer.go b/bittorrent/peer.go index 701413b..3227e42 100644 --- a/bittorrent/peer.go +++ b/bittorrent/peer.go @@ -88,7 +88,15 @@ func NewInfoHash(data any) (InfoHash, error) { case [InfoHashV2Len]byte: ba = t[:] case []byte: - ba = t + l := len(t) + if l == InfoHashV1Len*2 || l == InfoHashV2Len*2 { + ba = make([]byte, l/2) + if _, err := hex.Decode(ba, t); err != nil { + return NoneInfoHash, err + } + } else { + ba = t + } case string: l := len(t) if l == InfoHashV1Len*2 || l == InfoHashV2Len*2 { diff --git a/dist/example_config.yaml b/dist/example_config.yaml index 558373c..e5d7afb 100644 --- a/dist/example_config.yaml +++ b/dist/example_config.yaml @@ -36,11 +36,12 @@ frontends: # Enable SO_REUSEPORT to allow starting multiple mochi instances with the same HTTP(S) port. # You can also use this parameter to define two or more listeners or separate processes # for the same address and port, and (possibly) increase throughput (faster queue processing - # because of multiple 'workers'). + # because of multiple processes). reuse_port: true - # Number of connection listeners to start. See reuse_port, default is 1. - workers: 1 + # For http frontend it's number of concurrent connections. + # Default is 262144. + workers: 0 # The timeout durations for HTTP requests. read_timeout: 5s @@ -119,7 +120,8 @@ frontends: # because of multiple 'workers'). reuse_port: true - # Number of connection listeners to start. See reuse_port, default is 1. + # For udp frontend it's number of listen goroutines to be used with reuse_port option. + # Default is 1. workers: 1 # The leeway for a timestamp on a connection ID. diff --git a/dist/example_config_pg.yaml b/dist/example_config_pg.yaml index e830291..4310d63 100644 --- a/dist/example_config_pg.yaml +++ b/dist/example_config_pg.yaml @@ -14,7 +14,6 @@ frontends: tls_cert_path: "" tls_key_path: "" reuse_port: true - workers: 1 read_timeout: 5s write_timeout: 5s enable_keepalive: false diff --git a/dist/example_config_redis.yaml b/dist/example_config_redis.yaml index adbbde9..1b5b497 100644 --- a/dist/example_config_redis.yaml +++ b/dist/example_config_redis.yaml @@ -14,7 +14,6 @@ frontends: tls_cert_path: "" tls_key_path: "" reuse_port: true - workers: 1 read_timeout: 5s write_timeout: 5s enable_keepalive: false diff --git a/frontend/http/frontend.go b/frontend/http/frontend.go index 3eea926..f5e29aa 100644 --- a/frontend/http/frontend.go +++ b/frontend/http/frontend.go @@ -3,16 +3,15 @@ package http import ( - "context" "crypto/tls" "errors" - "io" "net/http" "net/netip" + "path" "sync" "time" - "github.com/julienschmidt/httprouter" + "github.com/valyala/fasthttp" "github.com/sot-tech/mochi/bittorrent" "github.com/sot-tech/mochi/frontend" @@ -37,6 +36,8 @@ func init() { // Config represents all configurable options for an HTTP BitTorrent Frontend type Config struct { frontend.ListenOptions + ReadTimeout time.Duration `cfg:"read_timeout"` + WriteTimeout time.Duration `cfg:"write_timeout"` IdleTimeout time.Duration `cfg:"idle_timeout"` EnableKeepAlive bool `cfg:"enable_keepalive"` UseTLS bool `cfg:"tls"` @@ -49,6 +50,8 @@ type Config struct { } const ( + defaultReadTimeout = 2 * time.Second + defaultWriteTimeout = 2 * time.Second defaultIdleTimeout = 30 * time.Second defaultAnnounceRoute = "/announce" defaultScrapeRoute = "/scrape" @@ -58,11 +61,30 @@ const ( // default values replacing anything that is invalid. func (cfg Config) Validate() (validCfg Config, err error) { validCfg = cfg - validCfg.ListenOptions = cfg.ListenOptions.Validate(false, logger) + validCfg.ListenOptions = cfg.ListenOptions.Validate(logger) if cfg.UseTLS && (len(cfg.TLSCertPath) == 0 || len(cfg.TLSKeyPath) == 0) { err = errTLSNotProvided return } + + if cfg.ReadTimeout <= 0 { + validCfg.ReadTimeout = defaultReadTimeout + logger.Warn(). + Str("name", "ReadTimeout"). + Dur("provided", cfg.ReadTimeout). + Dur("default", validCfg.ReadTimeout). + Msg("falling back to default configuration") + } + + if cfg.WriteTimeout <= 0 { + validCfg.WriteTimeout = defaultWriteTimeout + logger.Warn(). + Str("name", "WriteTimeout"). + Dur("provided", cfg.WriteTimeout). + Dur("default", validCfg.WriteTimeout). + Msg("falling back to default configuration") + } + if cfg.IdleTimeout <= 0 { validCfg.IdleTimeout = defaultIdleTimeout if cfg.EnableKeepAlive { @@ -94,20 +116,8 @@ func (cfg Config) Validate() (validCfg Config, err error) { return } -// httpServer replaces http.Close method with http.Shutdown -type httpServer struct { - *http.Server -} - -func (c httpServer) Close() (err error) { - if c.Server != nil { - err = c.Shutdown(context.Background()) - } - return -} - type httpFE struct { - servers []httpServer + *fasthttp.Server logic *middleware.Logic collectTimings bool onceCloser sync.Once @@ -128,20 +138,17 @@ func NewFrontend(c conf.MapConfig, logic *middleware.Logic) (frontend.Frontend, f := &httpFE{ logic: logic, - servers: make([]httpServer, cfg.Workers), collectTimings: cfg.EnableRequestTiming, ParseOptions: cfg.ParseOptions, - } - - for i := range f.servers { - f.servers[i] = httpServer{ - &http.Server{ - ReadTimeout: cfg.ReadTimeout, - ReadHeaderTimeout: cfg.ReadTimeout, - WriteTimeout: cfg.WriteTimeout, - IdleTimeout: cfg.IdleTimeout, - }, - } + Server: &fasthttp.Server{ + ReadTimeout: cfg.ReadTimeout, + WriteTimeout: cfg.WriteTimeout, + IdleTimeout: cfg.IdleTimeout, + Concurrency: int(cfg.Workers), + DisableKeepalive: !cfg.EnableKeepAlive, + GetOnly: true, + Logger: logger, + }, } // If TLS is enabled, create a key pair. @@ -151,36 +158,50 @@ func NewFrontend(c conf.MapConfig, logic *middleware.Logic) (frontend.Frontend, return nil, err } certs := []tls.Certificate{cert} - for i := range f.servers { - f.servers[i].TLSConfig = &tls.Config{ - Certificates: certs, - MinVersion: tls.VersionTLS12, - } + f.Server.TLSConfig = &tls.Config{ + Certificates: certs, + MinVersion: tls.VersionTLS12, } } - router := httprouter.New() + pathRouting := make(map[string]func(*fasthttp.RequestCtx), + len(cfg.AnnounceRoutes)+len(cfg.ScrapeRoutes)+len(cfg.PingRoutes)) + for _, route := range cfg.AnnounceRoutes { - router.GET(route, f.announceRoute) + route = path.Clean(route) + if !path.IsAbs(route) { + route = "/" + route + } + pathRouting[route] = f.announceRoute } for _, route := range cfg.ScrapeRoutes { - router.GET(route, f.scrapeRoute) + route = path.Clean(route) + if !path.IsAbs(route) { + route = "/" + route + } + pathRouting[path.Clean(route)] = f.scrapeRoute } for _, route := range cfg.PingRoutes { - router.GET(route, f.ping) - router.HEAD(route, f.ping) + route = path.Clean(route) + if !path.IsAbs(route) { + route = "/" + route + } + pathRouting[path.Clean(route)] = f.ping } - for _, srv := range f.servers { - srv.Handler = router - srv.SetKeepAlivesEnabled(cfg.EnableKeepAlive) - go runServer(srv, &cfg) + f.Server.Handler = func(ctx *fasthttp.RequestCtx) { + if route, exists := pathRouting[string(ctx.Path())]; exists { + route(ctx) + } else { + ctx.NotFound() + } } + go runServer(f.Server, &cfg) return f, nil } -func runServer(s httpServer, cfg *Config) { +func runServer(s *fasthttp.Server, cfg *Config) { ln, err := cfg.ListenTCP() if err == nil { if s.TLSConfig == nil { @@ -197,30 +218,20 @@ func runServer(s httpServer, cfg *Config) { // Close provides a thread-safe way to gracefully shut down a currently running Frontend. func (f *httpFE) Close() (err error) { f.onceCloser.Do(func() { - cls := make([]io.Closer, len(f.servers)) - for i, s := range f.servers { - cls[i] = s + if f.Server != nil { + err = f.Server.Shutdown() } - err = frontend.CloseGroup(cls) }) return } -func httpParamsToRouteParams(in httprouter.Params) (out bittorrent.RouteParams) { - out = make([]bittorrent.RouteParam, 0, len(in)) - for _, p := range in { - out = append(out, bittorrent.RouteParam{Key: p.Key, Value: p.Value}) - } - return -} - // announceRoute parses and responds to an Announce. -func (f *httpFE) announceRoute(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { +func (f *httpFE) announceRoute(reqCtx *fasthttp.RequestCtx) { var err error var start time.Time var addr netip.Addr - var req *bittorrent.AnnounceRequest + var aReq *bittorrent.AnnounceRequest if f.collectTimings && metrics.Enabled() { start = time.Now() defer func() { @@ -228,34 +239,32 @@ func (f *httpFE) announceRoute(w http.ResponseWriter, r *http.Request, ps httpro }() } - req, err = ParseAnnounce(r, f.ParseOptions) + aReq, err = ParseAnnounce(reqCtx, f.ParseOptions) if err != nil { - WriteError(w, err) + writeErrorResponse(reqCtx, err) return } - addr = req.GetFirst() + addr = aReq.GetFirst() - ctx := bittorrent.InjectRouteParamsToContext(r.Context(), httpParamsToRouteParams(ps)) - ctx, resp, err := f.logic.HandleAnnounce(ctx, req) + ctx := bittorrent.InjectRouteParamsToContext(reqCtx, nil) + ctx, aResp, err := f.logic.HandleAnnounce(ctx, aReq) if err != nil { - WriteError(w, err) + writeErrorResponse(reqCtx, err) return } - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - err = WriteAnnounceResponse(w, resp) - if err != nil { - WriteError(w, err) - return - } + reqCtx.Response.Header.Set("Content-Type", "text/plain; charset=utf-8") + writeAnnounceResponse(reqCtx, aResp) // next actions are background and should not be canceled after http writer closed ctx = bittorrent.RemapRouteParamsToBgContext(ctx) - go f.logic.AfterAnnounce(ctx, req, resp) + // params mapped from fasthttp.QueryArgs will in the next request + aReq.Params = nil + go f.logic.AfterAnnounce(ctx, aReq, aResp) } // scrapeRoute parses and responds to a Scrape. -func (f *httpFE) scrapeRoute(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { +func (f *httpFE) scrapeRoute(reqCtx *fasthttp.RequestCtx) { var err error var start time.Time var addr netip.Addr @@ -266,47 +275,42 @@ func (f *httpFE) scrapeRoute(w http.ResponseWriter, r *http.Request, ps httprout }() } - req, err := ParseScrape(r, f.ParseOptions) + req, err := ParseScrape(reqCtx, f.ParseOptions) if err != nil { - WriteError(w, err) + writeErrorResponse(reqCtx, err) return } addr = req.GetFirst() - ctx := bittorrent.InjectRouteParamsToContext(r.Context(), httpParamsToRouteParams(ps)) + ctx := bittorrent.InjectRouteParamsToContext(reqCtx, nil) ctx, resp, err := f.logic.HandleScrape(ctx, req) if err != nil { - WriteError(w, err) + writeErrorResponse(reqCtx, err) return } - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - err = WriteScrapeResponse(w, resp) - if err != nil { - WriteError(w, err) - return - } + reqCtx.Response.Header.Set("Content-Type", "text/plain; charset=utf-8") + writeScrapeResponse(reqCtx, resp) // next actions are background and should not be canceled after http writer closed ctx = bittorrent.RemapRouteParamsToBgContext(ctx) + // params mapped from fasthttp.QueryArgs will in the next request + req.Params = nil go f.logic.AfterScrape(ctx, req, resp) } -func (f *httpFE) ping(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { +func (f *httpFE) ping(ctx *fasthttp.RequestCtx) { var err error status := http.StatusOK - ctx := r.Context() - if r.Method == http.MethodGet { - err = f.logic.Ping(ctx) - } + err = f.logic.Ping(ctx) if err != nil { logger.Error().Err(err).Msg("ping completed with error") status = http.StatusServiceUnavailable } if ctxErr := ctx.Err(); ctxErr == nil { - w.WriteHeader(status) + ctx.SetStatusCode(status) } else { - logger.Info().Err(ctxErr).Str("ip", r.RemoteAddr).Msg("ping request cancelled") + logger.Info().Err(ctxErr).Stringer("addr", ctx.RemoteAddr()).Msg("ping request cancelled") } } diff --git a/frontend/http/frontend_test.go b/frontend/http/frontend_test.go new file mode 100644 index 0000000..5652786 --- /dev/null +++ b/frontend/http/frontend_test.go @@ -0,0 +1,131 @@ +package http + +import ( + cr "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "github.com/sot-tech/mochi/bittorrent" + "github.com/sot-tech/mochi/middleware" + "github.com/sot-tech/mochi/pkg/log" + "io" + "math/rand" + "net/http" + "net/url" + "strings" + "testing" +) + +const iterations = 10000 + +var ( + addr = fmt.Sprintf("127.0.0.1:%d", rand.Int63n(10000)+16384) + hashes = make([]string, 10) + peers = make([]string, 10) +) + +func init() { + _ = log.ConfigureLogger("", "error", false, false) + for i := range hashes { + var bb []byte + if rand.Int()%2 == 0 { + bb = make([]byte, bittorrent.InfoHashV1Len) + } else { + bb = make([]byte, bittorrent.InfoHashV2Len) + } + if _, err := cr.Read(bb); err != nil { + panic(err) + } + hashes[i] = hex.EncodeToString(bb) + } + + for i := range peers { + bb := make([]byte, bittorrent.PeerIDLen) + if _, err := cr.Read(bb); err != nil { + panic(err) + } + peers[i] = string(bb) + } + _, err := NewFrontend(map[string]any{ + "addr": addr, + "enable_keepalive": true, + "ping_routes": []string{"ping"}, + }, &middleware.Logic{}) + if err != nil { + panic(err) + } +} + +func runGet(u string, checkResponse bool) (err error) { + var r *http.Response + if r, err = http.Get(u); err == nil { + if r.StatusCode < 400 { + if checkResponse { + var out []byte + if out, err = io.ReadAll(r.Body); err == nil { + sout := string(out) + if strings.Contains(sout, "failure reason") { + return errors.New(sout) + } + } + } else { + _ = r.Body.Close() + } + } else { + return errors.New(r.Status) + } + } + return +} + +func BenchmarkPing(b *testing.B) { + u := url.URL{ + Scheme: "http", + Host: addr, + Path: "ping", + } + us := u.String() + for i := 0; i < iterations; i++ { + if err := runGet(us, false); err != nil { + b.Error(err) + } + } +} + +func BenchmarkAnnounce(b *testing.B) { + for i := 0; i < iterations; i++ { + u := url.URL{ + Scheme: "http", + Host: addr, + Path: defaultAnnounceRoute, + RawQuery: url.Values{ + "event": []string{bittorrent.StartedStr}, + "compact": []string{"1"}, + "left": []string{"100"}, + "downloaded": []string{"0"}, + "uploaded": []string{"0"}, + "numwant": []string{"1"}, + "port": []string{"12345"}, + "info_hash": []string{hashes[rand.Intn(len(hashes))]}, + "peer_id": []string{peers[rand.Intn(len(peers))]}, + }.Encode(), + } + if err := runGet(u.String(), true); err != nil { + b.Error(err) + } + } +} + +func BenchmarkScrape(b *testing.B) { + for i := 0; i < iterations; i++ { + u := url.URL{ + Scheme: "http", + Host: addr, + Path: defaultScrapeRoute, + RawQuery: url.Values{"info_hash": hashes[:len(hashes)/2]}.Encode(), + } + if err := runGet(u.String(), true); err != nil { + b.Error(err) + } + } +} diff --git a/frontend/http/params.go b/frontend/http/params.go new file mode 100644 index 0000000..fde8aa7 --- /dev/null +++ b/frontend/http/params.go @@ -0,0 +1,66 @@ +package http + +import ( + "bytes" + "github.com/rs/zerolog" + "github.com/sot-tech/mochi/bittorrent" + "github.com/valyala/fasthttp" +) + +// queryParams parses a URL Query and implements the Params interface with some +// additional helpers. +type queryParams struct { + *fasthttp.Args +} + +// parseURLData parses a request URL or UDP URLData as defined in BEP41. +// It expects a concatenated string of the request's path and query parts as +// defined in RFC 3986. As both the udp: and http: scheme used by BitTorrent +// include an authority part the path part must always begin with a slash. +// An example of the expected URLData would be "/announce?port=1234&uploaded=0" +// or "/?auth=0x1337". +// HTTP servers should pass (*http.Request).RequestURI, UDP servers should +// pass the concatenated, unchanged URLData as defined in BEP41. +// +// Note that, in the case of a key occurring multiple times in the query, only +// the last value for that key is kept. +// The only exception to this rule is the key "info_hash" which will attempt to +// parse each value as an InfoHash and return an error if parsing fails. All +// InfoHashes are collected and can later be retrieved by calling the InfoHashes +// method. +// +// Also note that any error that is encountered during parsing is returned as a +// ClientError, as this method is expected to be used to parse client-provided +// data. +func parseURLData(urlData []byte) (*queryParams, error) { + i := bytes.IndexByte(urlData, '?') + if i >= 0 { + urlData = urlData[i+1:] + } + q := &queryParams{new(fasthttp.Args)} + q.ParseBytes(urlData) + return q, nil +} + +// String returns a string parsed from a query. Every key can be returned as a +// string because they are encoded in the URL as strings. +func (qp queryParams) String(key string) (string, bool) { + v := qp.Peek(key) + return string(v), v != nil +} + +// InfoHashes returns a list of requested infohashes. +func (qp queryParams) InfoHashes() bittorrent.InfoHashes { + var ihs bittorrent.InfoHashes + for _, bb := range qp.PeekMulti("info_hash") { + if ih, err := bittorrent.NewInfoHash(bb); err == nil { + ihs = append(ihs, ih) + } + } + return ihs +} + +// MarshalZerologObject writes fields into zerolog event +func (qp queryParams) MarshalZerologObject(e *zerolog.Event) { + e.Stringer("query", qp.Args) +} diff --git a/bittorrent/params_test.go b/frontend/http/params_test.go similarity index 68% rename from bittorrent/params_test.go rename to frontend/http/params_test.go index dced055..2b83c01 100644 --- a/bittorrent/params_test.go +++ b/frontend/http/params_test.go @@ -1,4 +1,4 @@ -package bittorrent +package http import ( "net/url" @@ -24,34 +24,34 @@ var ( {"peer_id": {""}, "compact": {""}}, } - InvalidQueries = []string{ - "/announce?" + "info_hash=%0%a", + InvalidQueries = [][]byte{ + []byte("/announce?info_hash=%0%a"), } // See https://github.com/chihaya/chihaya/issues/334. - shouldNotPanicQueries = []string{ - "/annnounce?" + "info_hash=" + testPeerID + "&a", - "/annnounce?" + "info_hash=" + testPeerID + "&=b?", + shouldNotPanicQueries = [][]byte{ + []byte("/annnounce?info_hash=" + testPeerID + "&a"), + []byte("/annnounce?info_hash=" + testPeerID + "&=b?"), } ) -func mapArrayEqual(boxed map[string][]string, unboxed map[string]string) bool { - if len(boxed) != len(unboxed) { - return false +func mapArrayEqual(boxed map[string][]string, unboxed *queryParams) (bool, string) { + if len(boxed) != unboxed.Len() { + return false, "" } for mapKey, mapVal := range boxed { // Always expect box to hold only one element - if len(mapVal) != 1 || mapVal[0] != unboxed[mapKey] { - return false + if len(mapVal) != 1 || mapVal[0] != string(unboxed.Peek(mapKey)) { + return false, mapVal[0] } } - return true + return true, "" } func TestParseEmptyURLData(t *testing.T) { - parsedQuery, err := ParseURLData("") + parsedQuery, err := parseURLData(nil) if err != nil { t.Fatal(err) } @@ -62,49 +62,32 @@ func TestParseEmptyURLData(t *testing.T) { func TestParseValidURLData(t *testing.T) { for parseIndex, parseVal := range ValidAnnounceArguments { - parsedQueryObj, err := ParseURLData("/announce?" + parseVal.Encode()) + parsedQueryObj, err := parseURLData([]byte("/announce?" + parseVal.Encode())) if err != nil { t.Fatal(err) } - if !mapArrayEqual(parseVal, parsedQueryObj.params) { - t.Fatalf("Incorrect parse at item %d.\n Expected=%v\n Received=%v\n", parseIndex, parseVal, parsedQueryObj.params) - } - - if parsedQueryObj.path != "/announce" { - t.Fatalf("Incorrect path, expected %q, got %q", "/announce", parsedQueryObj.path) - } - } -} - -func TestParseInvalidURLData(t *testing.T) { - for parseIndex, parseStr := range InvalidQueries { - parsedQueryObj, err := ParseURLData(parseStr) - if err == nil { - t.Fatal("Should have produced error", parseIndex) - } - - if parsedQueryObj != nil { - t.Fatal("Should be nil after error", parsedQueryObj, parseIndex) + if eq, exp := mapArrayEqual(parseVal, parsedQueryObj); !eq { + t.Fatalf("Incorrect parse at item %d.\n Expected=%v\n Received=%v\n", parseIndex, parseVal, exp) } } } func TestParseShouldNotPanicURLData(t *testing.T) { for _, parseStr := range shouldNotPanicQueries { - _, _ = ParseURLData(parseStr) + _, _ = parseURLData(parseStr) } } func BenchmarkParseQuery(b *testing.B) { - announceStrings := make([]string, 0) + announceStrings := make([][]byte, 0) for i := range ValidAnnounceArguments { - announceStrings = append(announceStrings, ValidAnnounceArguments[i].Encode()) + announceStrings = append(announceStrings, []byte(ValidAnnounceArguments[i].Encode())) } b.ResetTimer() for bCount := 0; bCount < b.N; bCount++ { i := bCount % len(announceStrings) - parsedQueryObj, err := parseQuery(announceStrings[i]) + parsedQueryObj, err := parseURLData(announceStrings[i]) if err != nil { b.Error(err, i) b.Log(parsedQueryObj) diff --git a/frontend/http/parser.go b/frontend/http/parser.go index 4247259..c3e5b35 100644 --- a/frontend/http/parser.go +++ b/frontend/http/parser.go @@ -1,13 +1,12 @@ package http import ( + "bytes" "errors" - "net/http" - "net/netip" - "strings" - "github.com/sot-tech/mochi/bittorrent" "github.com/sot-tech/mochi/frontend" + "github.com/valyala/fasthttp" + "net/netip" ) // ParseOptions is the configuration used to parse an Announce Request. @@ -31,16 +30,14 @@ var ( ) // ParseAnnounce parses an bittorrent.AnnounceRequest from an http.Request. -func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequest, error) { - qp, err := bittorrent.ParseURLData(r.RequestURI) - if err != nil { - return nil, err - } +func ParseAnnounce(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.AnnounceRequest, error) { + qp := &queryParams{r.QueryArgs()} request := &bittorrent.AnnounceRequest{Params: qp} // Attempt to parse the event from the request. var eventStr string + var err error eventStr, request.EventProvided = qp.String("event") if request.EventProvided { if request.Event, err = bittorrent.NewEvent(eventStr); err != nil { @@ -50,10 +47,6 @@ func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequ request.Event = bittorrent.None } - // Determine if the client expects a compact response. - compactStr, _ := qp.String("compact") - request.Compact = compactStr != "" && compactStr != "0" - // Parse the info hash from the request. infoHashes := qp.InfoHashes() if len(infoHashes) < 1 { @@ -62,50 +55,53 @@ func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequ if len(infoHashes) > 1 { return nil, errMultipleInfoHashes } + //FIXME: make sure that we have a copy of InfoHash request.InfoHash = infoHashes[0] + // Determine if the client expects a compact response. + request.Compact = qp.GetBool("compact") + // Parse the PeerID from the request. - peerID, ok := qp.String("peer_id") - if !ok { - return nil, errInvalidPeerID - } - request.ID, err = bittorrent.NewPeerID([]byte(peerID)) + request.ID, err = bittorrent.NewPeerID(qp.Peek("peer_id")) if err != nil { return nil, errInvalidPeerID } // Determine the number of remaining bytes for the client. - request.Left, err = qp.Uint("left", 64) + n, err := qp.GetUint("left") if err != nil { return nil, errInvalidParameterLeft } + request.Left = uint64(n) // Determine the number of bytes downloaded by the client. - request.Downloaded, err = qp.Uint("downloaded", 64) + n, err = qp.GetUint("downloaded") if err != nil { return nil, errInvalidParameterDownloaded } + request.Downloaded = uint64(n) // Determine the number of bytes shared by the client. - request.Uploaded, err = qp.Uint("uploaded", 64) + n, err = qp.GetUint("uploaded") if err != nil { return nil, errInvalidParameterUploaded } + request.Uploaded = uint64(n) // Determine the number of peers the client wants in the response. - numWant, err := qp.Uint("numwant", 32) - if err != nil && !errors.Is(err, bittorrent.ErrKeyNotFound) { + n, err = qp.GetUint("numwant") + if err != nil && !errors.Is(err, fasthttp.ErrNoArgValue) { return nil, errInvalidParameterNumWant } // If there were no errors, the user actually provided the numWant. request.NumWantProvided = err == nil - request.NumWant = uint32(numWant) + request.NumWant = uint32(n) // Parse the port where the client is listening. - port, err := qp.Uint("port", 16) + n, err = qp.GetUint("port") if err != nil { return nil, bittorrent.ErrInvalidPort } - request.Port = uint16(port) + request.Port = uint16(n) // Parse the IP address where the client is listening. request.RequestAddresses = requestedIPs(r, qp, opts) @@ -118,11 +114,8 @@ func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequ } // ParseScrape parses an bittorrent.ScrapeRequest from an http.Request. -func ParseScrape(r *http.Request, opts ParseOptions) (*bittorrent.ScrapeRequest, error) { - qp, err := bittorrent.ParseURLData(r.RequestURI) - if err != nil { - return nil, err - } +func ParseScrape(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.ScrapeRequest, error) { + qp := &queryParams{r.QueryArgs()} infoHashes := qp.InfoHashes() if len(infoHashes) < 1 { @@ -130,18 +123,19 @@ func ParseScrape(r *http.Request, opts ParseOptions) (*bittorrent.ScrapeRequest, } request := &bittorrent.ScrapeRequest{ + //FIXME: make sure that we have a copy of InfoHashes InfoHashes: infoHashes, Params: qp, RequestAddresses: requestedIPs(r, qp, opts), } - err = bittorrent.SanitizeScrape(request, opts.MaxScrapeInfoHashes, opts.FilterPrivateIPs) + err := bittorrent.SanitizeScrape(request, opts.MaxScrapeInfoHashes, opts.FilterPrivateIPs) return request, err } // requestedIPs determines the IP address for a BitTorrent client request. -func requestedIPs(r *http.Request, p bittorrent.Params, opts ParseOptions) (addresses bittorrent.RequestAddresses) { +func requestedIPs(r *fasthttp.RequestCtx, p *queryParams, opts ParseOptions) (addresses bittorrent.RequestAddresses) { if opts.AllowIPSpoofing { for _, f := range []string{"ip", "ipv4", "ipv6"} { if ipStr, ok := p.String(f); ok { @@ -150,16 +144,16 @@ func requestedIPs(r *http.Request, p bittorrent.Params, opts ParseOptions) (addr } } - if ipValues := r.Header.Values(opts.RealIPHeader); len(ipValues) > 0 && opts.RealIPHeader != "" { + if ipValues := r.Request.Header.PeekAll(opts.RealIPHeader); len(ipValues) > 0 && opts.RealIPHeader != "" { for _, ipStr := range ipValues { - for _, ipStr := range strings.Split(ipStr, ",") { - if ipStr = strings.TrimSpace(ipStr); len(ipStr) > 0 { - addresses.Add(parseRequestAddress(ipStr, false)) + for _, ipStr := range bytes.Split(ipStr, []byte{','}) { + if ipStr = bytes.TrimSpace(ipStr); len(ipStr) > 0 { + addresses.Add(parseRequestAddress(string(ipStr), false)) } } } } else { - addrPort, _ := netip.ParseAddrPort(r.RemoteAddr) + addrPort, _ := netip.ParseAddrPort(r.RemoteAddr().String()) addresses.Add(bittorrent.RequestAddress{ Addr: addrPort.Addr(), Provided: false, diff --git a/frontend/http/writer.go b/frontend/http/writer.go index 3b6641c..ef6cde5 100644 --- a/frontend/http/writer.go +++ b/frontend/http/writer.go @@ -3,8 +3,9 @@ package http import ( "bytes" "errors" + "github.com/valyala/fasthttp" + "io" "net" - "net/http" "strconv" "time" @@ -14,21 +15,18 @@ import ( var respBufferPool = bytepool.NewBufferPool() -// WriteError communicates an error to a BitTorrent client over HTTP. -func WriteError(w http.ResponseWriter, err error) { +func writeErrorResponse(w io.StringWriter, err error) { message := "internal server error" var clientErr bittorrent.ClientError if errors.As(err, &clientErr) { message = clientErr.Error() } else { - logger.Error().Err(err).Msg("http: internal error") + logger.Error().Err(err).Msg("internal error") } - _, _ = w.Write([]byte("d14:failure reason" + strconv.Itoa(len(message)) + ":" + message + "e")) + _, _ = w.WriteString("d14:failure reason" + strconv.Itoa(len(message)) + ":" + message + "e") } -// WriteAnnounceResponse communicates the results of an Announce to a -// BitTorrent client over HTTP. -func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceResponse) error { +func writeAnnounceResponse(w io.Writer, resp *bittorrent.AnnounceResponse) { bb := respBufferPool.Get() defer respBufferPool.Put(bb) @@ -40,13 +38,13 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo } bb.WriteString("d8:completei") - bb.WriteString(strconv.FormatUint(uint64(resp.Complete), 10)) + bb.Write(fasthttp.AppendUint(nil, int(resp.Complete))) bb.WriteString("e10:incompletei") - bb.WriteString(strconv.FormatUint(uint64(resp.Incomplete), 10)) + bb.Write(fasthttp.AppendUint(nil, int(resp.Incomplete))) bb.WriteString("e8:intervali") - bb.WriteString(strconv.FormatUint(uint64(resp.Interval), 10)) + bb.Write(fasthttp.AppendUint(nil, int(resp.Interval))) bb.WriteString("e12:min intervali") - bb.WriteString(strconv.FormatUint(uint64(resp.MinInterval), 10)) + bb.Write(fasthttp.AppendUint(nil, int(resp.MinInterval))) bb.WriteByte('e') // Add the peers to the dictionary in the compact format. @@ -68,8 +66,7 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo } bb.WriteByte('e') - _, err := bb.WriteTo(w) - return err + _, _ = bb.WriteTo(w) } func compactAddresses(bb *bytes.Buffer, peers bittorrent.Peers, v6 bool) { @@ -80,13 +77,12 @@ func compactAddresses(bb *bytes.Buffer, peers bittorrent.Peers, v6 bool) { key, al = "6:peers6", net.IPv6len } bb.WriteString(key) - bb.WriteString(strconv.Itoa((al + 2) * l)) + bb.Write(fasthttp.AppendUint(nil, (al+2)*l)) bb.WriteByte(':') for _, peer := range peers { bb.Write(peer.Addr().AsSlice()) port := peer.Port() - bb.WriteByte(byte(port >> 8)) - bb.WriteByte(byte(port)) + bb.Write([]byte{byte(port >> 8), byte(port)}) } } } @@ -94,35 +90,32 @@ func compactAddresses(bb *bytes.Buffer, peers bittorrent.Peers, v6 bool) { func dictAddress(bb *bytes.Buffer, peer bittorrent.Peer) { bb.WriteString("d2:ip") addr := peer.Addr().String() - bb.WriteString(strconv.Itoa(len(addr))) + bb.Write(fasthttp.AppendUint(nil, len(addr))) bb.WriteByte(':') bb.WriteString(addr) bb.WriteString("7:peer id20:") bb.Write(peer.ID[:]) bb.WriteString("4:porti") - bb.WriteString(strconv.FormatUint(uint64(peer.Port()), 10)) - bb.WriteString("ee") + bb.Write(fasthttp.AppendUint(nil, int(peer.Port()))) + bb.Write([]byte{'e', 'e'}) } -// WriteScrapeResponse communicates the results of a Scrape to a BitTorrent -// client over HTTP. -func WriteScrapeResponse(w http.ResponseWriter, resp *bittorrent.ScrapeResponse) error { +func writeScrapeResponse(w io.Writer, resp *bittorrent.ScrapeResponse) { bb := respBufferPool.Get() defer respBufferPool.Put(bb) bb.WriteString("d5:filesd") for _, scrape := range resp.Files { - bb.WriteString(strconv.Itoa(len(scrape.InfoHash))) + bb.Write(fasthttp.AppendUint(nil, len(scrape.InfoHash))) bb.WriteByte(':') bb.Write([]byte(scrape.InfoHash)) bb.WriteString("d8:completei") - bb.WriteString(strconv.FormatUint(uint64(scrape.Complete), 10)) + bb.Write(fasthttp.AppendUint(nil, int(scrape.Complete))) bb.WriteString("e10:downloadedi") - bb.WriteString(strconv.FormatUint(uint64(scrape.Snatches), 10)) + bb.Write(fasthttp.AppendUint(nil, int(scrape.Snatches))) bb.WriteString("e10:incompletei") - bb.WriteString(strconv.FormatUint(uint64(scrape.Incomplete), 10)) + bb.Write(fasthttp.AppendUint(nil, int(scrape.Incomplete))) bb.WriteString("ee") } - bb.WriteString("ee") - _, err := bb.WriteTo(w) - return err + bb.Write([]byte{'e', 'e'}) + _, _ = bb.WriteTo(w) } diff --git a/frontend/http/writer_test.go b/frontend/http/writer_test.go index bb5cb6a..69228db 100644 --- a/frontend/http/writer_test.go +++ b/frontend/http/writer_test.go @@ -26,7 +26,7 @@ func TestWriteError(t *testing.T) { for _, tt := range table { t.Run(fmt.Sprintf("%s expecting %s", tt.reason, tt.expected), func(t *testing.T) { r := httptest.NewRecorder() - WriteError(r, bittorrent.ClientError(tt.reason)) + writeErrorResponse(r, bittorrent.ClientError(tt.reason)) require.Equal(t, r.Body.String(), tt.expected) }) } @@ -42,7 +42,7 @@ func TestWriteStatus(t *testing.T) { for _, tt := range table { t.Run(fmt.Sprintf("%s expecting %s", tt.reason, tt.expected), func(t *testing.T) { r := httptest.NewRecorder() - WriteError(r, bittorrent.ClientError(tt.reason)) + writeErrorResponse(r, bittorrent.ClientError(tt.reason)) require.Equal(t, r.Body.String(), tt.expected) }) } diff --git a/frontend/options.go b/frontend/options.go index c6ca37a..3af4b7b 100644 --- a/frontend/options.go +++ b/frontend/options.go @@ -2,18 +2,12 @@ package frontend import ( "errors" - "net" - "time" - "github.com/libp2p/go-reuseport" "github.com/sot-tech/mochi/pkg/log" + "net" ) -const ( - defaultReadTimeout = 2 * time.Second - defaultWriteTimeout = 2 * time.Second - defaultListenAddress = ":6969" -) +const defaultListenAddress = ":6969" var errUnexpectedListenerType = errors.New("unexpected listener type") @@ -22,14 +16,12 @@ type ListenOptions struct { Addr string ReusePort bool `cfg:"reuse_port"` Workers uint - ReadTimeout time.Duration `cfg:"read_timeout"` - WriteTimeout time.Duration `cfg:"write_timeout"` - EnableRequestTiming bool `cfg:"enable_request_timing"` + EnableRequestTiming bool `cfg:"enable_request_timing"` } // Validate checks if listen address provided and sets default // timeout options if needed -func (lo ListenOptions) Validate(ignoreTimeouts bool, logger *log.Logger) (validOptions ListenOptions) { +func (lo ListenOptions) Validate(logger *log.Logger) (validOptions ListenOptions) { validOptions = lo if len(lo.Addr) == 0 { validOptions.Addr = defaultListenAddress @@ -39,32 +31,6 @@ func (lo ListenOptions) Validate(ignoreTimeouts bool, logger *log.Logger) (valid Str("default", validOptions.Addr). Msg("falling back to default configuration") } - if lo.Workers == 0 { - validOptions.Workers = 1 - } - if lo.Workers > 1 && !lo.ReusePort { - validOptions.ReusePort = true - logger.Warn().Msg("forcibly enabling ReusePort because Workers > 1") - } - if !ignoreTimeouts { - if lo.ReadTimeout <= 0 { - validOptions.ReadTimeout = defaultReadTimeout - logger.Warn(). - Str("name", "ReadTimeout"). - Dur("provided", lo.ReadTimeout). - Dur("default", validOptions.ReadTimeout). - Msg("falling back to default configuration") - } - - if lo.WriteTimeout <= 0 { - validOptions.WriteTimeout = defaultWriteTimeout - logger.Warn(). - Str("name", "WriteTimeout"). - Dur("provided", lo.WriteTimeout). - Dur("default", validOptions.WriteTimeout). - Msg("falling back to default configuration") - } - } return } diff --git a/frontend/udp/frontend.go b/frontend/udp/frontend.go index 6ee2360..cce7ed3 100644 --- a/frontend/udp/frontend.go +++ b/frontend/udp/frontend.go @@ -54,7 +54,15 @@ type Config struct { // default values replacing anything that is invalid. func (cfg Config) Validate() (validCfg Config) { validCfg = cfg - validCfg.ListenOptions = cfg.ListenOptions.Validate(true, logger) + validCfg.ListenOptions = cfg.ListenOptions.Validate(logger) + + if cfg.Workers == 0 { + cfg.Workers = 1 + } + if cfg.Workers > 1 && !cfg.ReusePort { + cfg.ReusePort = true + logger.Warn().Msg("forcibly enabling ReusePort because Workers > 1") + } // Generate a private key if one isn't provided by the user. if cfg.PrivateKey == "" { diff --git a/go.mod b/go.mod index fd33b1f..a3ba01b 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,12 @@ module github.com/sot-tech/mochi go 1.19 require ( - code.cloudfoundry.org/go-diodes v0.0.0-20230222233121-2712486f12be + code.cloudfoundry.org/go-diodes v0.0.0-20230317203753-49f1af6d2f1a github.com/MicahParks/keyfunc v1.9.0 github.com/anacrolix/torrent v1.48.0 github.com/cespare/xxhash/v2 v2.2.0 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/jackc/pgx/v5 v5.3.1 - github.com/julienschmidt/httprouter v1.3.0 github.com/libp2p/go-reuseport v0.2.0 github.com/minio/sha256-simd v1.0.0 github.com/mitchellh/mapstructure v1.5.0 @@ -17,6 +16,7 @@ require ( github.com/redis/go-redis/v9 v9.0.2 github.com/rs/zerolog v1.29.0 github.com/stretchr/testify v1.8.2 + github.com/valyala/fasthttp v1.45.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -25,31 +25,34 @@ require ( github.com/anacrolix/log v0.13.2-0.20221123232138-02e2764801c3 // indirect github.com/anacrolix/missinggo v1.3.0 // indirect github.com/anacrolix/missinggo/v2 v2.7.1 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.0 // indirect + github.com/klauspost/compress v1.16.3 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/onsi/ginkgo/v2 v2.8.4 // indirect + github.com/onsi/ginkgo/v2 v2.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.41.0 // indirect + github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - golang.org/x/crypto v0.6.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + golang.org/x/crypto v0.7.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - golang.org/x/tools v0.6.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + golang.org/x/tools v0.7.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect ) diff --git a/go.sum b/go.sum index 552b8fd..c47f4d5 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -code.cloudfoundry.org/go-diodes v0.0.0-20230222233121-2712486f12be h1:giafIIx83xPMKSFXt/RsSiZz3dGmENtrs+1DPTrwOqM= -code.cloudfoundry.org/go-diodes v0.0.0-20230222233121-2712486f12be/go.mod h1:7XxQ+IiO+UoOqgMzMXCQ5t0kvlEEq7QXaA7evlE20cU= +code.cloudfoundry.org/go-diodes v0.0.0-20230317203753-49f1af6d2f1a h1:ci46gsKkiAiqv9LjVgbVWRZer1Z5zxbQt1IGMkCjksE= +code.cloudfoundry.org/go-diodes v0.0.0-20230317203753-49f1af6d2f1a/go.mod h1:XfKqABWo2YXnsg9cp39TNwzSj0xPx/k2SbgNPE5Fgpc= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -42,6 +42,8 @@ github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pm github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= github.com/anacrolix/torrent v1.48.0 h1:OQe1aQb8WnhDzpcI7r3yWoHzHWKyPbfhXGfO9Q/pvbY= github.com/anacrolix/torrent v1.48.0/go.mod h1:3UtkJ8BnxXDRwvk+eT+uwiZalfFJ8YzAhvxe4QRPSJI= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -110,8 +112,8 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -124,8 +126,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso= -github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= +github.com/google/pprof v0.0.0-20230309165930-d61513b1440d h1:um9/pc7tKMINFfP1eE7Wv6PRGXlcCSJkVajF7KJw3uQ= +github.com/google/pprof v0.0.0-20230309165930-d61513b1440d/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -152,9 +154,9 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= +github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= @@ -192,10 +194,10 @@ github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOl github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.8.4 h1:gf5mIQ8cLFieruNLAdgijHF1PYfLphKm2dxxcUtcqK0= -github.com/onsi/ginkgo/v2 v2.8.4/go.mod h1:427dEDQZkDKsBvCjc2A/ZPefhKxsTTrsQegMlayL730= +github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= +github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= +github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -220,8 +222,8 @@ github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3d github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.41.0 h1:npo01n6vUlRViIj5fgwiK8vlNIh8bnoxqh3gypKsyAw= -github.com/prometheus/common v0.41.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -261,6 +263,10 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA= +github.com/valyala/fasthttp v1.45.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -268,8 +274,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -285,7 +291,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -311,20 +317,20 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -342,8 +348,8 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/middleware/jwt/jwt_test.go b/middleware/jwt/jwt_test.go index f2b7997..f54a94d 100644 --- a/middleware/jwt/jwt_test.go +++ b/middleware/jwt/jwt_test.go @@ -61,14 +61,6 @@ func (p params) String(key string) (out string, found bool) { return } -func (params) RawPath() (s string) { - return -} - -func (params) RawQuery() (s string) { - return -} - func (params) MarshalZerologObject(*zerolog.Event) {} func init() { diff --git a/pkg/log/log.go b/pkg/log/log.go index 798e07e..5df6a62 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -21,15 +21,11 @@ import ( var ( root = zl.Logger - rootWg = sync.WaitGroup{} + rootMu = sync.Mutex{} customOut io.WriteCloser customOutMu = sync.Mutex{} ) -func init() { - rootWg.Add(1) -} - // ConfigureLogger initializes root and all child loggers. // NOTE: this function MUST be called before any child log call // @@ -70,9 +66,10 @@ func ConfigureLogger(output, level string, formatted, colored bool) (err error) return err } } + rootMu.Lock() + defer rootMu.Unlock() root = zerolog.New(w).With().Timestamp().Logger() zerolog.SetGlobalLevel(lvl) - rootWg.Done() return nil } @@ -87,7 +84,6 @@ type Logger struct { func (l *Logger) init() { l.zlOnce.Do(func() { - rootWg.Wait() l.Logger = root.With().Str("component", l.comp).Logger() }) }