diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 3649609e1f1..a918129693c 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -432,12 +432,14 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request i.serveCAR(r.Context(), w, r, resolvedPath, contentPath, carVersion, begin) return case "application/vnd.ipld.dag-json": + case "application/json": logger.Debugw("serving dag-json", "path", contentPath) - i.serveJSON(r.Context(), w, r, resolvedPath, begin, "application/vnd.ipld.dag-json", uint64(mc.DagJson)) + i.serveCodec(r.Context(), w, r, resolvedPath, contentPath, begin, responseFormat, uint64(mc.DagJson)) return case "application/vnd.ipld.dag-cbor": + case "application/cbor": logger.Debugw("serving dag-cbor", "path", contentPath) - i.serveJSON(r.Context(), w, r, resolvedPath, begin, "application/vnd.ipld.dag-cbor", uint64(mc.DagCbor)) + i.serveCodec(r.Context(), w, r, resolvedPath, contentPath, begin, responseFormat, uint64(mc.DagCbor)) return default: // catch-all for unsuported application/vnd.* err := fmt.Errorf("unsupported format %q", responseFormat) @@ -870,8 +872,12 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string] return "application/vnd.ipld.car", nil, nil case "dag-json": return "application/vnd.ipld.dag-json", nil, nil + case "json": + return "application/json", nil, nil case "dag-cbor": return "application/vnd.ipld.dag-cbor", nil, nil + case "cbor": + return "application/cbor", nil, nil } } // Browsers and other user agents will send Accept header with generic types like: @@ -879,7 +885,9 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string] // We only care about explciit, vendor-specific content-types. for _, accept := range r.Header.Values("Accept") { // respond to the very first ipld content type - if strings.HasPrefix(accept, "application/vnd.ipld") { + if strings.HasPrefix(accept, "application/vnd.ipld") || + strings.HasPrefix(accept, "application/json") || + strings.HasPrefix(accept, "application/cbor") { mediatype, params, err := mime.ParseMediaType(accept) if err != nil { return "", nil, err diff --git a/core/corehttp/gateway_handler_codec.go b/core/corehttp/gateway_handler_codec.go new file mode 100644 index 00000000000..c21ccedab11 --- /dev/null +++ b/core/corehttp/gateway_handler_codec.go @@ -0,0 +1,58 @@ +package corehttp + +import ( + "context" + "fmt" + "html" + "net/http" + "time" + + ipldlegacy "github.com/ipfs/go-ipld-legacy" + ipath "github.com/ipfs/interface-go-ipfs-core/path" + "github.com/ipfs/kubo/tracing" + "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/multicodec" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +var unixEpochTime = time.Unix(0, 0) + +func (i *gatewayHandler) serveCodec(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, ctype string, codec uint64) { + ctx, span := tracing.Span(ctx, "Gateway", "ServeCodec", trace.WithAttributes(attribute.String("path", resolvedPath.String()), attribute.String("ctype", ctype))) + defer span.End() + + // Set Cache-Control and read optional Last-Modified time + modtime := addCacheControlHeaders(w, r, contentPath, resolvedPath.Cid()) + + // Sets correct Last-Modified header. This code is borrowed from the standard + // library (net/http/server.go) as we cannot use serveFile. + if !(modtime.IsZero() || modtime.Equal(unixEpochTime)) { + w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) + } + + addContentDispositionHeader(w, r, contentPath) + w.Header().Set("Content-Type", ctype) + w.Header().Set("X-Content-Type-Options", "nosniff") + + obj, err := i.api.Dag().Get(ctx, resolvedPath.Cid()) + if err != nil { + webError(w, "ipfs dag get "+html.EscapeString(resolvedPath.String()), err, http.StatusInternalServerError) + return + } + + universal, ok := obj.(ipldlegacy.UniversalNode) + if !ok { + webError(w, "todo", fmt.Errorf("%T is not a valid IPLD node", obj), http.StatusInternalServerError) + return + } + finalNode := universal.(ipld.Node) + + encoder, err := multicodec.LookupEncoder(codec) + if err != nil { + webError(w, "todo", err, http.StatusInternalServerError) + return + } + + _ = encoder(finalNode, w) +} diff --git a/core/corehttp/gateway_handler_json.go b/core/corehttp/gateway_handler_json.go deleted file mode 100644 index dc0959393c8..00000000000 --- a/core/corehttp/gateway_handler_json.go +++ /dev/null @@ -1,44 +0,0 @@ -package corehttp - -import ( - "context" - "fmt" - "html" - "net/http" - "time" - - ipldlegacy "github.com/ipfs/go-ipld-legacy" - ipath "github.com/ipfs/interface-go-ipfs-core/path" - "github.com/ipld/go-ipld-prime" - "github.com/ipld/go-ipld-prime/multicodec" -) - -// serveCAR returns a CAR stream for specific DAG+selector -func (i *gatewayHandler) serveJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, begin time.Time, ctype string, codec uint64) { - // ctx, span := tracing.Span(ctx, "Gateway", "ServeCAR", trace.WithAttributes(attribute.String("path", resolvedPath.String()))) - // defer span.End() - // ctx, cancel := context.WithCancel(ctx) - // defer cancel() - - obj, err := i.api.Dag().Get(r.Context(), resolvedPath.Cid()) - if err != nil { - webError(w, "ipfs dag get "+html.EscapeString(resolvedPath.String()), err, http.StatusInternalServerError) - return - } - - universal, ok := obj.(ipldlegacy.UniversalNode) - if !ok { - webError(w, "todo", fmt.Errorf("%T is not a valid IPLD node", obj), http.StatusInternalServerError) - return - } - finalNode := universal.(ipld.Node) - - encoder, err := multicodec.LookupEncoder(codec) - if err != nil { - webError(w, "todo", err, http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", ctype) - _ = encoder(finalNode, w) -}