Skip to content

Commit 20c11e2

Browse files
hacdiaslidel
andcommitted
gateway: add support for car-* query parameters (#604)
Co-authored-by: Marcin Rataj <lidel@lidel.org>
1 parent 07fd7e8 commit 20c11e2

5 files changed

Lines changed: 42 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ The following emojis are used to highlight certain changes:
2020
* `NewRemoteBlocksBackend` allows you to create a gateway backend that uses one or multiple other gateways as backend. These gateways must support RAW block requests (`application/vnd.ipld.raw`), as well as IPNS Record requests (`application/vnd.ipfs.ipns-record`). With this, we also introduced `NewCacheBlockStore`, `NewRemoteBlockstore` and `NewRemoteValueStore`.
2121
* `NewRemoteCarBackend` allows you to create a gateway backend that uses one or multiple Trustless Gateways as backend. These gateways must support CAR requests (`application/vnd.ipld.car`), as well as the extensions describe in [IPIP-402](https://specs.ipfs.tech/ipips/ipip-0402/). With this, we also introduced `NewCarBackend`, `NewRemoteCarFetcher` and `NewRetryCarFetcher`.
2222
* `gateway` now sets the [`Content-Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Location) header for requests with non-default content format, as a result of content negotiation. This allows generic and misconfigured HTTP caches to store Deserialized, CAR and Block responses separately, under distinct cache keys.
23+
* `gateway` now supports `car-dups`, `car-order` and `car-version` as query parameters in addition to the `application/vnd.ipld.car` parameters sent via `Accept` header. The parameters in the `Accept` header have always priority, but including them in URL simplifies HTTP caching and allows use in `Content-Location` header on CAR responses to maximize interoperability with wide array of HTTP caches.
2324

2425
### Changed
2526

gateway/gateway_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ func TestHeaders(t *testing.T) {
470470
dnslinkGatewayHost := "dnslink-gateway.com"
471471

472472
runTest("Regular gateway with default format", contentPath, "", "", "")
473-
runTest("Regular gateway with Accept: application/vnd.ipld.car has no Content-Location", contentPath, "application/vnd.ipld.car;version=1;order=dfs;dups=n", "", "")
473+
runTest("Regular gateway with Accept: application/vnd.ipld.car;version=1;order=dfs;dups=n sets correct Content-Location", contentPath, "application/vnd.ipld.car;version=1;order=dfs;dups=n", "", contentPath+"?car-dups=n&car-order=dfs&car-version=1&format=car")
474474
runTest("Regular gateway with ?dag-scope=entity&format=car", contentPath+"?dag-scope=entity&format=car", "", "", "")
475475
runTest("Regular gateway preserves query parameters", contentPath+"?a=b&c=d", dagCborResponseFormat, "", contentPath+"?a=b&c=d&format=dag-cbor")
476476
runTest("Subdomain gateway with default format", "/empty-dir/", "", subdomainGatewayHost, "")

gateway/handler.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -658,14 +658,7 @@ func addContentLocation(r *http.Request, w http.ResponseWriter, rq *requestData)
658658
return
659659
}
660660

661-
// Response format parameters, such as 'dups' and 'order' for CAR requests
662-
// cannot be translated into the URL. Therefore, we cannot add a 'Content-Location'
663-
// header.
664-
if len(rq.responseParams) != 0 {
665-
return
666-
}
667-
668-
param := responseFormatToFormatParam[rq.responseFormat]
661+
format := responseFormatToFormatParam[rq.responseFormat]
669662
path := r.URL.Path
670663
if p, ok := r.Context().Value(OriginalPathKey).(string); ok {
671664
path = p
@@ -676,7 +669,12 @@ func addContentLocation(r *http.Request, w http.ResponseWriter, rq *requestData)
676669
for k, v := range r.URL.Query() {
677670
query[k] = v
678671
}
679-
query.Set("format", param)
672+
query.Set("format", format)
673+
674+
// Set response params as query elements.
675+
for k, v := range rq.responseParams {
676+
query.Set(format+"-"+k, v)
677+
}
680678

681679
w.Header().Set("Content-Location", path+"?"+query.Encode())
682680
}

gateway/handler_car.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import (
2323
const (
2424
carRangeBytesKey = "entity-bytes"
2525
carTerminalElementTypeKey = "dag-scope"
26+
carVersionKey = "car-version"
27+
carDuplicatesKey = "car-dups"
28+
carOrderKey = "car-order"
2629
)
2730

2831
// serveCAR returns a CAR stream for specific DAG+selector
@@ -144,16 +147,31 @@ func buildCarParams(r *http.Request, contentTypeParams map[string]string) (CarPa
144147

145148
// application/vnd.ipld.car content type parameters from Accept header
146149

150+
// Get CAR version, duplicates and order from the query parameters and override
151+
// with parameters from Accept header if they exist, since they have priority.
152+
versionStr := queryParams.Get(carVersionKey)
153+
duplicatesStr := queryParams.Get(carDuplicatesKey)
154+
orderStr := queryParams.Get(carOrderKey)
155+
if v, ok := contentTypeParams["version"]; ok {
156+
versionStr = v
157+
}
158+
if v, ok := contentTypeParams["order"]; ok {
159+
orderStr = v
160+
}
161+
if v, ok := contentTypeParams["dups"]; ok {
162+
duplicatesStr = v
163+
}
164+
147165
// version of CAR format
148-
switch contentTypeParams["version"] {
166+
switch versionStr {
149167
case "": // noop, client does not care about version
150168
case "1": // noop, we support this
151169
default:
152170
return CarParams{}, errors.New("unsupported application/vnd.ipld.car version: only version=1 is supported")
153171
}
154172

155173
// optional order from IPIP-412
156-
if order := DagOrder(contentTypeParams["order"]); order != DagOrderUnspecified {
174+
if order := DagOrder(orderStr); order != DagOrderUnspecified {
157175
switch order {
158176
case DagOrderUnknown, DagOrderDFS:
159177
params.Order = order
@@ -168,7 +186,7 @@ func buildCarParams(r *http.Request, contentTypeParams map[string]string) (CarPa
168186
}
169187

170188
// optional dups from IPIP-412
171-
dups, err := NewDuplicateBlocksPolicy(contentTypeParams["dups"])
189+
dups, err := NewDuplicateBlocksPolicy(duplicatesStr)
172190
if err != nil {
173191
return CarParams{}, err
174192
}

gateway/handler_car_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package gateway
22

33
import (
44
"net/http"
5+
"net/url"
56
"testing"
67

78
"github.com/ipfs/boxo/path"
@@ -81,19 +82,22 @@ func TestCarParams(t *testing.T) {
8182
// from the value read from Accept header
8283
tests := []struct {
8384
acceptHeader string
85+
params url.Values
8486
expectedOrder DagOrder
8587
expectedDuplicates DuplicateBlocksPolicy
8688
}{
87-
{"application/vnd.ipld.car; order=dfs; dups=y", DagOrderDFS, DuplicateBlocksIncluded},
88-
{"application/vnd.ipld.car; order=unk; dups=n", DagOrderUnknown, DuplicateBlocksExcluded},
89-
{"application/vnd.ipld.car; order=unk", DagOrderUnknown, DuplicateBlocksExcluded},
90-
{"application/vnd.ipld.car; dups=y", DagOrderDFS, DuplicateBlocksIncluded},
91-
{"application/vnd.ipld.car; dups=n", DagOrderDFS, DuplicateBlocksExcluded},
92-
{"application/vnd.ipld.car", DagOrderDFS, DuplicateBlocksExcluded},
93-
{"application/vnd.ipld.car;version=1;order=dfs;dups=y", DagOrderDFS, DuplicateBlocksIncluded},
89+
{"application/vnd.ipld.car; order=dfs; dups=y", nil, DagOrderDFS, DuplicateBlocksIncluded},
90+
{"application/vnd.ipld.car; order=unk; dups=n", nil, DagOrderUnknown, DuplicateBlocksExcluded},
91+
{"application/vnd.ipld.car; order=unk", nil, DagOrderUnknown, DuplicateBlocksExcluded},
92+
{"application/vnd.ipld.car; dups=y", nil, DagOrderDFS, DuplicateBlocksIncluded},
93+
{"application/vnd.ipld.car; dups=n", nil, DagOrderDFS, DuplicateBlocksExcluded},
94+
{"application/vnd.ipld.car", nil, DagOrderDFS, DuplicateBlocksExcluded},
95+
{"application/vnd.ipld.car;version=1;order=dfs;dups=y", nil, DagOrderDFS, DuplicateBlocksIncluded},
96+
{"application/vnd.ipld.car;version=1;order=dfs;dups=y", url.Values{"car-order": []string{"unk"}}, DagOrderDFS, DuplicateBlocksIncluded},
97+
{"application/vnd.ipld.car;version=1;dups=y", url.Values{"car-order": []string{"unk"}}, DagOrderUnknown, DuplicateBlocksIncluded},
9498
}
9599
for _, test := range tests {
96-
r := mustNewRequest(t, http.MethodGet, "http://example.com/", nil)
100+
r := mustNewRequest(t, http.MethodGet, "http://example.com/?"+test.params.Encode(), nil)
97101
r.Header.Set("Accept", test.acceptHeader)
98102

99103
mediaType, formatParams, err := customResponseFormat(r)

0 commit comments

Comments
 (0)