@@ -317,28 +317,12 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
317
317
w .Header ().Set ("X-IPFS-Path" , urlPath )
318
318
w .Header ().Set ("Etag" , responseEtag )
319
319
320
- // X-Ipfs-Roots array for efficient HTTP cache invalidation
321
- // These are logical roots where each CID represent one path segment
322
- // and resolves to either a directory or the root block of a file
323
- var sp strings.Builder
324
- var pathRoots []string
325
- pathSegments := strings .Split (urlPath [6 :], "/" )
326
- sp .WriteString (urlPath [:5 ]) // /ipfs or /ipns
327
- for _ , root := range pathSegments {
328
- if root == "" {
329
- continue
330
- }
331
- sp .WriteString ("/" )
332
- sp .WriteString (root )
333
- resolvedSubPath , err := i .api .ResolvePath (r .Context (), ipath .New (sp .String ()))
334
- if err != nil {
335
- // this should never happen, as we resolved the full path already
336
- webError (w , "error while resolving X-Ipfs-Roots" , err , http .StatusInternalServerError )
337
- return
338
- }
339
- pathRoots = append (pathRoots , resolvedSubPath .Cid ().String ())
320
+ if rootCids , err := i .buildIpfsRootsHeader (urlPath , r ); err == nil {
321
+ w .Header ().Set ("X-Ipfs-Roots" , rootCids )
322
+ } else { // this should never happen, as we resolved the urlPath already
323
+ webError (w , "error while resolving X-Ipfs-Roots" , err , http .StatusInternalServerError )
324
+ return
340
325
}
341
- w .Header ().Set ("X-Ipfs-Roots" , strings .Join (pathRoots , ", " ))
342
326
343
327
// set these headers _after_ the error, for we may just not have it
344
328
// and don't want the client to cache a 500 response...
@@ -781,6 +765,50 @@ func (i *gatewayHandler) addUserHeaders(w http.ResponseWriter) {
781
765
}
782
766
}
783
767
768
+ // Set X-Ipfs-Roots with logical CID array for efficient HTTP cache invalidation.
769
+ func (i * gatewayHandler ) buildIpfsRootsHeader (contentPath string , r * http.Request ) (string , error ) {
770
+ /*
771
+ These are logical roots where each CID represent one path segment
772
+ and resolves to either a directory or the root block of a file.
773
+ The main purpose of this header is allow HTTP caches to do smarter decisions
774
+ around cache invalidation (eg. keep specific subdirectory/file if it did not change)
775
+
776
+ A good example is Wikipedia, which is HAMT-sharded, but we only care about
777
+ logical roots that represent each segment of the human-readable content
778
+ path:
779
+
780
+ Given contentPath = /ipns/en.wikipedia-on-ipfs.org/wiki/Block_of_Wikipedia_in_Turkey
781
+ rootCidList is a generated by doing `ipfs resolve -r` on each sub path:
782
+ /ipns/en.wikipedia-on-ipfs.org → bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze
783
+ /ipns/en.wikipedia-on-ipfs.org/wiki/ → bafybeihn2f7lhumh4grizksi2fl233cyszqadkn424ptjajfenykpsaiw4
784
+ /ipns/en.wikipedia-on-ipfs.org/wiki/Block_of_Wikipedia_in_Turkey → bafkreibn6euazfvoghepcm4efzqx5l3hieof2frhp254hio5y7n3hv5rma
785
+
786
+ The result is an ordered array of values:
787
+ X-Ipfs-Roots: bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze,bafybeihn2f7lhumh4grizksi2fl233cyszqadkn424ptjajfenykpsaiw4,bafkreibn6euazfvoghepcm4efzqx5l3hieof2frhp254hio5y7n3hv5rma
788
+
789
+ Note that while the top one will change every time any article is changed,
790
+ the last root (responsible for specific article) may not change at all.
791
+ */
792
+ var sp strings.Builder
793
+ var pathRoots []string
794
+ pathSegments := strings .Split (contentPath [6 :], "/" )
795
+ sp .WriteString (contentPath [:5 ]) // /ipfs or /ipns
796
+ for _ , root := range pathSegments {
797
+ if root == "" {
798
+ continue
799
+ }
800
+ sp .WriteString ("/" )
801
+ sp .WriteString (root )
802
+ resolvedSubPath , err := i .api .ResolvePath (r .Context (), ipath .New (sp .String ()))
803
+ if err != nil {
804
+ return "" , err
805
+ }
806
+ pathRoots = append (pathRoots , resolvedSubPath .Cid ().String ())
807
+ }
808
+ rootCidList := strings .Join (pathRoots , "," ) // convention from rfc2616#sec4.2
809
+ return rootCidList , nil
810
+ }
811
+
784
812
func webError (w http.ResponseWriter , message string , err error , defaultCode int ) {
785
813
if _ , ok := err .(resolver.ErrNoLink ); ok {
786
814
webErrorWithCode (w , message , err , http .StatusNotFound )
0 commit comments