diff --git a/server/common/default.go b/server/common/default.go index 5d97a3c4..a070560d 100644 --- a/server/common/default.go +++ b/server/common/default.go @@ -6,6 +6,8 @@ import ( "net" "net/http" "time" + + "github.com/mickael-kerjean/filestash/server/pkg/tracer" ) var USER_AGENT = fmt.Sprintf("Filestash/%s.%s (http://filestash.app)", APP_VERSION, BUILD_DATE) @@ -16,32 +18,45 @@ func init() { } } -type HTTPClientOption func(*http.Transport) - -func WithInsecure(t *http.Transport) { - t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} +type httpClientConfig struct { + transport *http.Transport + traceService string } -func WithoutTimeout(t *http.Transport) { - t.ResponseHeaderTimeout = 0 +type HTTPClientOption func(*httpClientConfig) + +func WithInsecure(c *httpClientConfig) { + c.transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} +} + +func WithoutTimeout(c *httpClientConfig) { + c.transport.ResponseHeaderTimeout = 0 +} + +func WithTrace(service string) HTTPClientOption { + return func(c *httpClientConfig) { + c.traceService = service + } } func HTTPClient(opts ...HTTPClientOption) *http.Client { - t := &http.Transport{ - Dial: (&net.Dialer{ - Timeout: 10 * time.Second, - KeepAlive: 10 * time.Second, - }).Dial, - TLSHandshakeTimeout: 5 * time.Second, - IdleConnTimeout: 60 * time.Second, - ResponseHeaderTimeout: 60 * time.Second, + cfg := &httpClientConfig{ + transport: &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 10 * time.Second, + }).Dial, + TLSHandshakeTimeout: 5 * time.Second, + IdleConnTimeout: 60 * time.Second, + ResponseHeaderTimeout: 60 * time.Second, + }, } for _, opt := range opts { - opt(t) + opt(cfg) } return &http.Client{ Timeout: 5 * time.Hour, - Transport: NewTransformedTransport(t), + Transport: tracer.NewTransport(cfg.traceService, NewTransformedTransport(cfg.transport)), } } diff --git a/server/pkg/tracer/transport.go b/server/pkg/tracer/transport.go new file mode 100644 index 00000000..9d7e2eed --- /dev/null +++ b/server/pkg/tracer/transport.go @@ -0,0 +1,38 @@ +package tracer + +import ( + "fmt" + "net/http" +) + +func NewTransport(svc string, orig http.RoundTripper) http.RoundTripper { + if svc == "" { + return orig + } + return &transport{svc, orig} +} + +type transport struct { + svc string + orig http.RoundTripper +} + +func (this *transport) RoundTrip(req *http.Request) (*http.Response, error) { + span := StartSpan(TraceFromContext(req.Context()), req.Method+" "+req.URL.Path, SpanOptions{ + Kind: KindClient, + Service: this.svc, + Attributes: map[string]string{ + "http.method": req.Method, + "http.url": req.URL.String(), + "http.host": req.URL.Host, + }, + }) + defer span.Close() + resp, err := this.orig.RoundTrip(req) + if err != nil { + span.SetError(err) + } else if resp.StatusCode >= 400 { + span.SetError(fmt.Errorf("status %d", resp.StatusCode)) + } + return resp, err +} diff --git a/server/plugin/plg_backend_artifactory/index.go b/server/plugin/plg_backend_artifactory/index.go index 07bfb4b1..e3d659f2 100644 --- a/server/plugin/plg_backend_artifactory/index.go +++ b/server/plugin/plg_backend_artifactory/index.go @@ -21,6 +21,7 @@ func init() { type ArtifactoryStorage struct { instance string token string + client *http.Client } func (this ArtifactoryStorage) Init(params map[string]string, app *App) (IBackend, error) { @@ -30,6 +31,7 @@ func (this ArtifactoryStorage) Init(params map[string]string, app *App) (IBacken } this.token = params["token"] this.instance = strings.TrimSuffix(strings.TrimSuffix(params["instance"], "/"), "/artifactory") + this.client = HTTPClient(WithTrace("artifactory")) return this, nil } @@ -157,7 +159,7 @@ func (this ArtifactoryStorage) Ls(path string) ([]os.FileInfo, error) { return nil, err } req.Header.Add("Authorization", "Bearer "+this.token) - res, err := HTTPClient().Do(req) + res, err := this.client.Do(req) if err != nil { return nil, err } @@ -189,7 +191,7 @@ func (this ArtifactoryStorage) Cat(path string) (io.ReadCloser, error) { return nil, err } req.Header.Add("Authorization", "Bearer "+this.token) - res, err := HTTPClient().Do(req) + res, err := this.client.Do(req) if err != nil { return nil, err } @@ -231,7 +233,7 @@ func (this ArtifactoryStorage) Cat(path string) (io.ReadCloser, error) { } req, err = http.NewRequest("GET", artifactoryResponse.DownloadLink, nil) req.Header.Add("Authorization", "Bearer "+this.token) - res, err = HTTPClient().Do(req) + res, err = this.client.Do(req) return res.Body, err } @@ -268,7 +270,7 @@ func (this ArtifactoryStorage) Mkdir(path string) error { } req.Header.Add("Content-Type", "application/json") req.Header.Add("Authorization", "Bearer "+this.token) - res, err := HTTPClient().Do(req) + res, err := this.client.Do(req) if err != nil { return err } @@ -306,7 +308,7 @@ func (this ArtifactoryStorage) Rm(path string) error { return err } req.Header.Add("Authorization", "Bearer "+this.token) - res, err := HTTPClient().Do(req) + res, err := this.client.Do(req) if err != nil { return err } @@ -335,7 +337,7 @@ func (this ArtifactoryStorage) Mv(from, to string) error { return err } req.Header.Add("Authorization", "Bearer "+this.token) - res, err := HTTPClient().Do(req) + res, err := this.client.Do(req) if err != nil { return err } @@ -366,7 +368,7 @@ func (this ArtifactoryStorage) Save(path string, content io.Reader) error { return err } req.Header.Add("Authorization", "Bearer "+this.token) - res, err := HTTPClient().Do(req) + res, err := this.client.Do(req) if err != nil { return err } diff --git a/server/plugin/plg_backend_backblaze/index.go b/server/plugin/plg_backend_backblaze/index.go index c3ac838a..2f9df4b7 100644 --- a/server/plugin/plg_backend_backblaze/index.go +++ b/server/plugin/plg_backend_backblaze/index.go @@ -28,6 +28,7 @@ type Backblaze struct { AccountId string `json:"accountId"` Token string `json:"authorizationToken"` Status int `json:"status"` + client *http.Client } type BackblazeError struct { @@ -43,6 +44,7 @@ func init() { func (this Backblaze) Init(params map[string]string, app *App) (IBackend, error) { this.params = params + this.client = HTTPClient(WithTrace("backblaze")) // By default backblaze required quite a few API calls to just find the data that's under a given bucket // This would result in a slow application hence we are caching everyting that's in the hot path @@ -526,7 +528,7 @@ func (this Backblaze) request(method string, url string, body io.Reader, fn func defer req.Body.Close() } - res, err := HTTPClient().Do(req) + res, err := this.client.Do(req) if err != nil { return res, err } diff --git a/server/plugin/plg_backend_ipfs/index.go b/server/plugin/plg_backend_ipfs/index.go index d094f1eb..bdcbdd9f 100644 --- a/server/plugin/plg_backend_ipfs/index.go +++ b/server/plugin/plg_backend_ipfs/index.go @@ -31,7 +31,7 @@ func (this Ipfs) Init(params map[string]string, app *App) (IBackend, error) { if _, err := this.Stat("/"); err != nil { return nil, err } - this.client = HTTPClient() + this.client = HTTPClient(WithTrace("ipfs")) return &this, nil } diff --git a/server/plugin/plg_backend_nop/index.go b/server/plugin/plg_backend_nop/index.go index 282daa81..3ca13c26 100644 --- a/server/plugin/plg_backend_nop/index.go +++ b/server/plugin/plg_backend_nop/index.go @@ -4,6 +4,7 @@ import ( . "github.com/mickael-kerjean/filestash/server/common" "io" "os" + "path/filepath" "strconv" "strings" ) @@ -69,7 +70,10 @@ func (this BlackHole) Ls(path string) ([]os.FileInfo, error) { } func (this BlackHole) Stat(path string) (os.FileInfo, error) { - return nil, ErrNotImplemented + if path == "/" { + return File{FType: "directory"}, nil + } + return File{FName: filepath.Base(path), FType: "file"}, nil } func (this BlackHole) Cat(path string) (io.ReadCloser, error) { diff --git a/server/plugin/plg_backend_perkeep/index.go b/server/plugin/plg_backend_perkeep/index.go index 6690fc7e..df8eecb6 100644 --- a/server/plugin/plg_backend_perkeep/index.go +++ b/server/plugin/plg_backend_perkeep/index.go @@ -18,6 +18,7 @@ func init() { type Perkeep struct { serverURL string + client *http.Client } func (this Perkeep) Init(params map[string]string, app *App) (IBackend, error) { @@ -30,6 +31,7 @@ func (this Perkeep) Init(params map[string]string, app *App) (IBackend, error) { } return &Perkeep{ serverURL: url, + client: HTTPClient(WithTrace("perkeep")), }, nil } @@ -220,7 +222,7 @@ func (this *Perkeep) query(searchRequest any) (*SearchResponse, error) { if err != nil { return nil, NewError("Failed to create request: "+err.Error(), 500) } - resp, err := HTTPClient().Do(req) + resp, err := this.client.Do(req) if err != nil { return nil, NewError("Failed to query perkeep: "+err.Error(), 500) } @@ -249,7 +251,7 @@ func (this *Perkeep) describe(blobRef string) (*DescribeResponse, error) { if err != nil { return nil, NewError("Failed to create request: "+err.Error(), 500) } - resp, err := HTTPClient().Do(req) + resp, err := this.client.Do(req) if err != nil { return nil, NewError("Failed to describe perkeep: "+err.Error(), 500) } diff --git a/server/plugin/plg_backend_url/index.go b/server/plugin/plg_backend_url/index.go index 51f0c0a2..3d0e3702 100644 --- a/server/plugin/plg_backend_url/index.go +++ b/server/plugin/plg_backend_url/index.go @@ -51,7 +51,7 @@ func (this Url) Init(params map[string]string, app *App) (IBackend, error) { } home := u.Path u.Path = "/" - opts := []HTTPClientOption{} + opts := []HTTPClientOption{WithTrace("url")} if params["insecure"] == "on" { opts = append(opts, WithInsecure) } diff --git a/server/plugin/plg_backend_webdav/index.go b/server/plugin/plg_backend_webdav/index.go index e5d79ac9..d895491f 100644 --- a/server/plugin/plg_backend_webdav/index.go +++ b/server/plugin/plg_backend_webdav/index.go @@ -16,6 +16,8 @@ import ( type WebDav struct { params *WebDavParams + client *http.Client + app *App } type WebDavParams struct { @@ -41,6 +43,8 @@ func (w WebDav) Init(params map[string]string, app *App) (IBackend, error) { password: params["password"], path: params["path"], }, + client: HTTPClient(WithTrace("webdav")), + app: app, } return backend, nil } @@ -265,6 +269,7 @@ func (w WebDav) request(method string, url string, body io.Reader, fn func(req * if err != nil { return nil, err } + req = req.WithContext(w.app.Context) if w.params.username != "" { req.SetBasicAuth(w.params.username, w.params.password) } @@ -284,7 +289,7 @@ func (w WebDav) request(method string, url string, body io.Reader, fn func(req * if fn != nil { fn(req) } - return HTTPClient().Do(req) + return w.client.Do(req) } type WebDavResp struct {