Skip to content
This repository has been archived by the owner on Jun 20, 2024. It is now read-only.

Commit

Permalink
fix: use more precise gateway errors
Browse files Browse the repository at this point in the history
  • Loading branch information
aschmahmann committed Jul 27, 2023
1 parent c37bb09 commit 0228fe1
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 79 deletions.
36 changes: 2 additions & 34 deletions blockstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ package main
import (
"context"
"errors"
"fmt"
"strings"
"time"

"github.com/filecoin-saturn/caboose"
"github.com/ipfs/bifrost-gateway/lib"
blockstore "github.com/ipfs/boxo/blockstore"
exchange "github.com/ipfs/boxo/exchange"
"github.com/ipfs/boxo/gateway"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
"go.uber.org/zap/zapcore"
Expand Down Expand Up @@ -38,7 +35,7 @@ func (e *exchangeBsWrapper) GetBlock(ctx context.Context, c cid.Cid) (blocks.Blo

blk, err := e.bstore.Get(ctx, c)
if err != nil {
return nil, gatewayError(err)
return nil, lib.GatewayError(err)
}
return blk, nil
}
Expand Down Expand Up @@ -67,33 +64,4 @@ func (e *exchangeBsWrapper) Close() error {
return nil
}

// gatewayError translates underlying blockstore error into one that gateway code will return as HTTP 502 or 504
// it also makes sure Retry-After hint from remote blockstore will be passed to HTTP client, if present.
func gatewayError(err error) error {
if errors.Is(err, &gateway.ErrorStatusCode{}) ||
errors.Is(err, &gateway.ErrorRetryAfter{}) {
// already correct error
return err
}

// All timeouts should produce 504 Gateway Timeout
if errors.Is(err, context.DeadlineExceeded) ||
errors.Is(err, caboose.ErrSaturnTimeout) ||
// Unfortunately this is not an exported type so we have to check for the content.
strings.Contains(err.Error(), "Client.Timeout exceeded") {
return fmt.Errorf("%w: %s", gateway.ErrGatewayTimeout, err.Error())
}

// (Saturn) errors that support the RetryAfter interface need to be converted
// to the correct gateway error, such that the HTTP header is set.
for v := err; v != nil; v = errors.Unwrap(v) {
if r, ok := v.(interface{ RetryAfter() time.Duration }); ok {
return gateway.NewErrorRetryAfter(err, r.RetryAfter())
}
}

// everything else returns 502 Bad Gateway
return fmt.Errorf("%w: %s", gateway.ErrBadGateway, err.Error())
}

var _ exchange.Interface = (*exchangeBsWrapper)(nil)
44 changes: 0 additions & 44 deletions blockstore_test.go

This file was deleted.

2 changes: 1 addition & 1 deletion lib/gateway_traversal.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func carToLinearBlockGetter(ctx context.Context, reader io.Reader, metrics *Grap
if !ok || errors.Is(blkRead.err, io.EOF) {
return nil, io.ErrUnexpectedEOF
}
return nil, blkRead.err
return nil, GatewayError(blkRead.err)
}
if blkRead.block != nil {
metrics.carBlocksFetchedMetric.Inc()
Expand Down
34 changes: 34 additions & 0 deletions lib/gateway_utils.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package lib

import (
"context"
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"time"

"github.com/filecoin-saturn/caboose"
"github.com/ipfs/boxo/gateway"
)

Expand Down Expand Up @@ -37,3 +42,32 @@ func carParamsToString(params gateway.CarParams) string {
}
return paramsBuilder.String()
}

// GatewayError translates underlying blockstore error into one that gateway code will return as HTTP 502 or 504
// it also makes sure Retry-After hint from remote blockstore will be passed to HTTP client, if present.
func GatewayError(err error) error {
if errors.Is(err, &gateway.ErrorStatusCode{}) ||
errors.Is(err, &gateway.ErrorRetryAfter{}) {
// already correct error
return err
}

// All timeouts should produce 504 Gateway Timeout
if errors.Is(err, context.DeadlineExceeded) ||
errors.Is(err, caboose.ErrSaturnTimeout) ||
// Unfortunately this is not an exported type so we have to check for the content.
strings.Contains(err.Error(), "Client.Timeout exceeded") {
return fmt.Errorf("%w: %s", gateway.ErrGatewayTimeout, err.Error())
}

// (Saturn) errors that support the RetryAfter interface need to be converted
// to the correct gateway error, such that the HTTP header is set.
for v := err; v != nil; v = errors.Unwrap(v) {
if r, ok := v.(interface{ RetryAfter() time.Duration }); ok {
return gateway.NewErrorRetryAfter(err, r.RetryAfter())
}
}

// everything else returns 502 Bad Gateway
return fmt.Errorf("%w: %s", gateway.ErrBadGateway, err.Error())
}
38 changes: 38 additions & 0 deletions lib/gateway_utils_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package lib

import (
"errors"
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"

ifacepath "github.com/ipfs/boxo/coreiface/path"
"github.com/ipfs/boxo/gateway"
Expand Down Expand Up @@ -55,3 +60,36 @@ func TestContentPathToCarUrl(t *testing.T) {
})
}
}

type testErr struct {
message string
retryAfter time.Duration
}

func (e *testErr) Error() string {
return e.message
}

func (e *testErr) RetryAfter() time.Duration {
return e.retryAfter
}

func TestGatewayErrorRetryAfter(t *testing.T) {
originalErr := &testErr{message: "test", retryAfter: time.Minute}
var (
convertedErr error
gatewayErr *gateway.ErrorRetryAfter
)

// Test unwrapped
convertedErr = GatewayError(originalErr)
ok := errors.As(convertedErr, &gatewayErr)
assert.True(t, ok)
assert.EqualValues(t, originalErr.retryAfter, gatewayErr.RetryAfter)

// Test wrapped.
convertedErr = GatewayError(fmt.Errorf("wrapped error: %w", originalErr))
ok = errors.As(convertedErr, &gatewayErr)
assert.True(t, ok)
assert.EqualValues(t, originalErr.retryAfter, gatewayErr.RetryAfter)
}

0 comments on commit 0228fe1

Please sign in to comment.