mirror of
https://github.com/octoleo/restic.git
synced 2024-11-24 13:47:42 +00:00
Merge pull request #4616 from MichaelEischer/fix-rest-connection-close
rest: fix and cleanup closing of http response body
This commit is contained in:
commit
54c5c72e5a
@ -35,6 +35,9 @@ linters:
|
|||||||
# parse and typecheck code
|
# parse and typecheck code
|
||||||
- typecheck
|
- typecheck
|
||||||
|
|
||||||
|
# ensure that http response bodies are closed
|
||||||
|
- bodyclose
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
# don't use the default exclude rules, this hides (among others) ignored
|
# don't use the default exclude rules, this hides (among others) ignored
|
||||||
# errors from Close() calls
|
# errors from Close() calls
|
||||||
|
@ -118,6 +118,7 @@ func TestRoundTripperReader(t *testing.T) {
|
|||||||
test.Assert(t, bytes.Equal(data, out.Bytes()), "data ping-pong failed")
|
test.Assert(t, bytes.Equal(data, out.Bytes()), "data ping-pong failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint:bodyclose // the http response is just a mock
|
||||||
func TestRoundTripperCornerCases(t *testing.T) {
|
func TestRoundTripperCornerCases(t *testing.T) {
|
||||||
limiter := NewStaticLimiter(Limits{42 * 1024, 42 * 1024})
|
limiter := NewStaticLimiter(Limits{42 * 1024, 42 * 1024})
|
||||||
|
|
||||||
|
@ -252,6 +252,7 @@ func newBackend(ctx context.Context, cfg Config, lim limiter.Limiter) (*Backend,
|
|||||||
return nil, fmt.Errorf("error talking HTTP to rclone: %w", err)
|
return nil, fmt.Errorf("error talking HTTP to rclone: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = res.Body.Close()
|
||||||
debug.Log("HTTP status %q returned, moving instance to background", res.Status)
|
debug.Log("HTTP status %q returned, moving instance to background", res.Status)
|
||||||
err = bg()
|
err = bg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -58,6 +58,17 @@ func Open(_ context.Context, cfg Config, rt http.RoundTripper) (*Backend, error)
|
|||||||
return be, nil
|
return be, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func drainAndClose(resp *http.Response) error {
|
||||||
|
_, err := io.Copy(io.Discard, resp.Body)
|
||||||
|
cerr := resp.Body.Close()
|
||||||
|
|
||||||
|
// return first error
|
||||||
|
if err != nil {
|
||||||
|
return errors.Errorf("drain: %w", err)
|
||||||
|
}
|
||||||
|
return cerr
|
||||||
|
}
|
||||||
|
|
||||||
// Create creates a new REST on server configured in config.
|
// Create creates a new REST on server configured in config.
|
||||||
func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, error) {
|
func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, error) {
|
||||||
be, err := Open(ctx, cfg, rt)
|
be, err := Open(ctx, cfg, rt)
|
||||||
@ -80,20 +91,14 @@ func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := drainAndClose(resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("server response unexpected: %v (%v)", resp.Status, resp.StatusCode)
|
return nil, fmt.Errorf("server response unexpected: %v (%v)", resp.Status, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = io.Copy(io.Discard, resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = resp.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return be, nil
|
return be, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,22 +141,19 @@ func (b *Backend) Save(ctx context.Context, h backend.Handle, rd backend.RewindR
|
|||||||
req.ContentLength = rd.Length()
|
req.ContentLength = rd.Length()
|
||||||
|
|
||||||
resp, err := b.client.Do(req)
|
resp, err := b.client.Do(req)
|
||||||
|
|
||||||
var cerr error
|
|
||||||
if resp != nil {
|
|
||||||
_, _ = io.Copy(io.Discard, resp.Body)
|
|
||||||
cerr = resp.Body.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := drainAndClose(resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return errors.Errorf("server response unexpected: %v (%v)", resp.Status, resp.StatusCode)
|
return errors.Errorf("server response unexpected: %v (%v)", resp.Status, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Wrap(cerr, "Close")
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// notExistError is returned whenever the requested file does not exist on the
|
// notExistError is returned whenever the requested file does not exist on the
|
||||||
@ -215,22 +217,17 @@ func (b *Backend) openReader(ctx context.Context, h backend.Handle, length int,
|
|||||||
req.Header.Set("Accept", ContentTypeV2)
|
req.Header.Set("Accept", ContentTypeV2)
|
||||||
|
|
||||||
resp, err := b.client.Do(req)
|
resp, err := b.client.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if resp != nil {
|
|
||||||
_, _ = io.Copy(io.Discard, resp.Body)
|
|
||||||
_ = resp.Body.Close()
|
|
||||||
}
|
|
||||||
return nil, errors.Wrap(err, "client.Do")
|
return nil, errors.Wrap(err, "client.Do")
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
_ = resp.Body.Close()
|
_ = drainAndClose(resp)
|
||||||
return nil, ¬ExistError{h}
|
return nil, ¬ExistError{h}
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
|
||||||
_ = resp.Body.Close()
|
_ = drainAndClose(resp)
|
||||||
return nil, errors.Errorf("unexpected HTTP response (%v): %v", resp.StatusCode, resp.Status)
|
return nil, errors.Errorf("unexpected HTTP response (%v): %v", resp.StatusCode, resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,13 +247,11 @@ func (b *Backend) Stat(ctx context.Context, h backend.Handle) (backend.FileInfo,
|
|||||||
return backend.FileInfo{}, errors.WithStack(err)
|
return backend.FileInfo{}, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = io.Copy(io.Discard, resp.Body)
|
if err = drainAndClose(resp); err != nil {
|
||||||
if err = resp.Body.Close(); err != nil {
|
return backend.FileInfo{}, err
|
||||||
return backend.FileInfo{}, errors.Wrap(err, "Close")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
_ = resp.Body.Close()
|
|
||||||
return backend.FileInfo{}, ¬ExistError{h}
|
return backend.FileInfo{}, ¬ExistError{h}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,13 +280,15 @@ func (b *Backend) Remove(ctx context.Context, h backend.Handle) error {
|
|||||||
req.Header.Set("Accept", ContentTypeV2)
|
req.Header.Set("Accept", ContentTypeV2)
|
||||||
|
|
||||||
resp, err := b.client.Do(req)
|
resp, err := b.client.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "client.Do")
|
return errors.Wrap(err, "client.Do")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = drainAndClose(resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
_ = resp.Body.Close()
|
|
||||||
return ¬ExistError{h}
|
return ¬ExistError{h}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,12 +296,7 @@ func (b *Backend) Remove(ctx context.Context, h backend.Handle) error {
|
|||||||
return errors.Errorf("blob not removed, server response: %v (%v)", resp.Status, resp.StatusCode)
|
return errors.Errorf("blob not removed, server response: %v (%v)", resp.Status, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = io.Copy(io.Discard, resp.Body)
|
return nil
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "Copy")
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Wrap(resp.Body.Close(), "Close")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List runs fn for each file in the backend which has the type t. When an
|
// List runs fn for each file in the backend which has the type t. When an
|
||||||
@ -322,7 +314,6 @@ func (b *Backend) List(ctx context.Context, t backend.FileType, fn func(backend.
|
|||||||
req.Header.Set("Accept", ContentTypeV2)
|
req.Header.Set("Accept", ContentTypeV2)
|
||||||
|
|
||||||
resp, err := b.client.Do(req)
|
resp, err := b.client.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "List")
|
return errors.Wrap(err, "List")
|
||||||
}
|
}
|
||||||
@ -333,19 +324,25 @@ func (b *Backend) List(ctx context.Context, t backend.FileType, fn func(backend.
|
|||||||
// already ignores missing directories, but misuses "not found" to
|
// already ignores missing directories, but misuses "not found" to
|
||||||
// report certain internal errors, see
|
// report certain internal errors, see
|
||||||
// https://github.com/rclone/rclone/pull/7550 for details.
|
// https://github.com/rclone/rclone/pull/7550 for details.
|
||||||
return nil
|
return drainAndClose(resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
_ = drainAndClose(resp)
|
||||||
return errors.Errorf("List failed, server response: %v (%v)", resp.Status, resp.StatusCode)
|
return errors.Errorf("List failed, server response: %v (%v)", resp.Status, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.Header.Get("Content-Type") == ContentTypeV2 {
|
if resp.Header.Get("Content-Type") == ContentTypeV2 {
|
||||||
return b.listv2(ctx, resp, fn)
|
err = b.listv2(ctx, resp, fn)
|
||||||
|
} else {
|
||||||
|
err = b.listv1(ctx, t, resp, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.listv1(ctx, t, resp, fn)
|
if cerr := drainAndClose(resp); cerr != nil && err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// listv1 uses the REST protocol v1, where a list HTTP request (e.g. `GET
|
// listv1 uses the REST protocol v1, where a list HTTP request (e.g. `GET
|
||||||
|
Loading…
Reference in New Issue
Block a user