diff --git a/cmd/booster-http/gateway_handler.go b/cmd/booster-http/gateway_handler.go index f15663e5f..f21063893 100644 --- a/cmd/booster-http/gateway_handler.go +++ b/cmd/booster-http/gateway_handler.go @@ -1,105 +1,37 @@ package main import ( - "fmt" - "mime" + "net/http" - "strings" "github.com/ipfs/boxo/gateway" ) type gatewayHandler struct { gwh http.Handler - supportedFormats map[string]struct{} } -func newGatewayHandler(gw *gateway.BlocksBackend, supportedFormats []string) http.Handler { +func newGatewayHandler(gw *gateway.BlocksBackend, serveGateway string) http.Handler { headers := map[string][]string{} + var deserializedResponse bool gateway.AddAccessControlHeaders(headers) - fmtsMap := make(map[string]struct{}, len(supportedFormats)) - for _, f := range supportedFormats { - fmtsMap[f] = struct{}{} + if serveGateway == "none" { + return &gatewayHandler{} + } + if serveGateway == "all" { + deserializedResponse = true + } else if serveGateway == "verifiable" { + deserializedResponse = false } - return &gatewayHandler{ gwh: gateway.NewHandler(gateway.Config{ Headers: headers, - DeserializedResponses: true, + DeserializedResponses: deserializedResponse, }, gw), - supportedFormats: fmtsMap, } } func (h *gatewayHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - responseFormat, _, err := customResponseFormat(r) - if err != nil { - webError(w, fmt.Errorf("error while processing the Accept header: %w", err), http.StatusBadRequest) - return - } - - if _, ok := h.supportedFormats[responseFormat]; !ok { - if responseFormat == "" { - responseFormat = "unixfs" - } - webError(w, fmt.Errorf("unsupported response format: %s", responseFormat), http.StatusBadRequest) - return - } - h.gwh.ServeHTTP(w, r) } - -func webError(w http.ResponseWriter, err error, code int) { - http.Error(w, err.Error(), code) -} - -// Unfortunately this function is not exported from go-libipfs so we need to copy it here. -// return explicit response format if specified in request as query parameter or via Accept HTTP header -func customResponseFormat(r *http.Request) (mediaType string, params map[string]string, err error) { - if formatParam := r.URL.Query().Get("format"); formatParam != "" { - // translate query param to a content type - switch formatParam { - case "raw": - return "application/vnd.ipld.raw", nil, nil - case "car": - return "application/vnd.ipld.car", nil, nil - case "tar": - return "application/x-tar", nil, nil - case "json": - return "application/json", nil, nil - case "cbor": - return "application/cbor", nil, nil - case "dag-json": - return "application/vnd.ipld.dag-json", nil, nil - case "dag-cbor": - return "application/vnd.ipld.dag-cbor", nil, nil - case "ipns-record": - return "application/vnd.ipfs.ipns-record", nil, nil - } - } - // Browsers and other user agents will send Accept header with generic types like: - // Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 - // We only care about explicit, vendor-specific content-types and respond to the first match (in order). - // TODO: make this RFC compliant and respect weights (eg. return CAR for Accept:application/vnd.ipld.dag-json;q=0.1,application/vnd.ipld.car;q=0.2) - for _, header := range r.Header.Values("Accept") { - for _, value := range strings.Split(header, ",") { - accept := strings.TrimSpace(value) - // respond to the very first matching content type - if strings.HasPrefix(accept, "application/vnd.ipld") || - strings.HasPrefix(accept, "application/x-tar") || - strings.HasPrefix(accept, "application/json") || - strings.HasPrefix(accept, "application/cbor") || - strings.HasPrefix(accept, "application/vnd.ipfs") { - mediatype, params, err := mime.ParseMediaType(accept) - if err != nil { - return "", nil, err - } - return mediatype, params, nil - } - } - } - // If none of special-cased content types is found, return empty string - // to indicate default, implicit UnixFS response should be prepared - return "", nil, nil -} diff --git a/cmd/booster-http/run.go b/cmd/booster-http/run.go index 5eed671d4..749b645a0 100644 --- a/cmd/booster-http/run.go +++ b/cmd/booster-http/run.go @@ -2,7 +2,6 @@ package main import ( "context" - "errors" "fmt" "net/http" _ "net/http/pprof" @@ -76,20 +75,11 @@ var runCmd = &cli.Command{ Usage: "enables serving raw pieces", Value: true, }, - &cli.BoolFlag{ - Name: "serve-blocks", - Usage: "serve blocks with the ipfs gateway API", - Value: true, - }, - &cli.BoolFlag{ - Name: "serve-cars", - Usage: "serve CAR files with the ipfs gateway API", - Value: true, - }, - &cli.BoolFlag{ - Name: "serve-files", - Usage: "serve original files (eg jpg, mov) with the ipfs gateway API", - Value: false, + &cli.StringFlag{ + Name: "serve-gateway", + Usage: "This flag enables serving deserialized responses with the ipfs gateway API. " + + "Values: 'verifiable' (default) - only serve verifiable responses, 'all' - serve all responses, 'none' - disable serving responses with the ipfs gateway API", + Value: "verifiable", }, &cli.BoolFlag{ Name: "tracing", @@ -117,10 +107,9 @@ var runCmd = &cli.Command{ }, Action: func(cctx *cli.Context) error { servePieces := cctx.Bool("serve-pieces") - responseFormats := parseSupportedResponseFormats(cctx) - enableIpfsGateway := len(responseFormats) > 0 - if !servePieces && !enableIpfsGateway { - return errors.New("one of --serve-pieces, --serve-blocks, etc must be enabled") + enableIpfsGateway, err := shouldEnableIPFSGateway(cctx) + if err != nil { + return err } if cctx.Bool("pprof") { @@ -136,7 +125,7 @@ var runCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) cl := bdclient.NewStore() defer cl.Close(ctx) - err := cl.Dial(ctx, cctx.String("api-lid")) + err = cl.Dial(ctx, cctx.String("api-lid")) if err != nil { return fmt.Errorf("connecting to local index directory service: %w", err) } @@ -177,9 +166,10 @@ var runCmd = &cli.Command{ pr := &piecedirectory.SectorAccessorAsPieceReader{SectorAccessor: sa} pd := piecedirectory.NewPieceDirectory(cl, pr, cctx.Int("add-index-throttle")) + serveGateway := cctx.String("serve-gateway") opts := &HttpServerOptions{ ServePieces: servePieces, - SupportedResponseFormats: responseFormats, + ServeGateway: serveGateway, } if enableIpfsGateway { repoDir, err := createRepoDir(cctx.String(FlagRepo.Name)) @@ -262,32 +252,27 @@ var runCmd = &cli.Command{ }, } -func parseSupportedResponseFormats(cctx *cli.Context) []string { - fmts := []string{} - if cctx.Bool("serve-blocks") { - fmts = append(fmts, "application/vnd.ipld.raw") - } - if cctx.Bool("serve-cars") { - fmts = append(fmts, "application/vnd.ipld.car") +func shouldEnableIPFSGateway(cctx *cli.Context) (bool, error) { + switch gatewayType := cctx.String("serve-gateway"); gatewayType { + case "verifiable": + return true, nil + case "all": + return true, nil + case "none": + return false, nil + default: + return false, fmt.Errorf("invalid value for serve-gateway: %s", gatewayType) } - if cctx.Bool("serve-files") { - // Allow the user to not specify a specific response format. - // In that case the gateway will respond with any kind of file - // (eg jpg, mov etc) - fmts = append(fmts, "") - } - return fmts } + func ipfsGatewayMsg(cctx *cli.Context, ipfsBasePath string) string { fmts := []string{} - if cctx.Bool("serve-blocks") { + switch cctx.String("serve-gateway") { + case "verifiable": fmts = append(fmts, "blocks") - } - if cctx.Bool("serve-cars") { fmts = append(fmts, "CARs") - } - if cctx.Bool("serve-files") { + case "all": fmts = append(fmts, "files") } diff --git a/cmd/booster-http/server.go b/cmd/booster-http/server.go index 2c6344424..e84b1c9b1 100644 --- a/cmd/booster-http/server.go +++ b/cmd/booster-http/server.go @@ -65,7 +65,7 @@ type HttpServerApi interface { type HttpServerOptions struct { Blockstore blockstore.Blockstore ServePieces bool - SupportedResponseFormats []string + ServeGateway string } func NewHttpServer(path string, listenAddr string, port int, api HttpServerApi, opts *HttpServerOptions) *HttpServer { @@ -106,7 +106,7 @@ func (s *HttpServer) Start(ctx context.Context) error { if err != nil { return fmt.Errorf("creating blocks gateway: %w", err) } - handler.Handle(s.ipfsBasePath(), newGatewayHandler(gw, s.opts.SupportedResponseFormats)) + handler.Handle(s.ipfsBasePath(), newGatewayHandler(gw, s.opts.ServeGateway)) } handler.HandleFunc("/", s.handleIndex) diff --git a/docker/devnet/booster-http/entrypoint.sh b/docker/devnet/booster-http/entrypoint.sh index 353b9d33c..7240c76e8 100755 --- a/docker/devnet/booster-http/entrypoint.sh +++ b/docker/devnet/booster-http/entrypoint.sh @@ -9,4 +9,4 @@ echo $MINER_API_INFO echo $LID_API_INFO echo Starting booster-http... -exec booster-http run --serve-files=true --api-lid=$LID_API_INFO --api-fullnode=$FULLNODE_API_INFO --api-storage=$MINER_API_INFO --tracing +exec booster-http run --serve-gateway=verifiable --api-lid=$LID_API_INFO --api-fullnode=$FULLNODE_API_INFO --api-storage=$MINER_API_INFO --tracing