mirror of
https://github.com/sot-tech/mochi.git
synced 2026-06-08 06:01:55 -07:00
change httprouter with fasthttp and simple handler
* add http benchmark * move HTTP query parameters parsing to http subpackage * update dependencies
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
+9
-1
@@ -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 {
|
||||
|
||||
Vendored
+6
-4
@@ -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.
|
||||
|
||||
Vendored
-1
@@ -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
|
||||
|
||||
Vendored
-1
@@ -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
|
||||
|
||||
+93
-89
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
+32
-38
@@ -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,
|
||||
|
||||
+23
-30
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
+4
-38
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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() {
|
||||
|
||||
+3
-7
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user