Skip to content

Commit a53d1f7

Browse files
committed
fix ipfs cat
1 parent 177e08b commit a53d1f7

File tree

5 files changed

+453
-38
lines changed

5 files changed

+453
-38
lines changed

flake.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ipfs_compatibility_test.go

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import (
1111
"io"
1212
"net/http"
1313
"net/http/httptest"
14+
"strconv"
1415
"strings"
1516
"testing"
1617

1718
tassert "github.com/stretchr/testify/assert"
1819
"github.com/stretchr/testify/require"
1920
)
2021

21-
func TestParseMultiAddr(t *testing.T) {
22+
func TestIPFSParseMultiAddr(t *testing.T) {
2223
tassert := tassert.New(t)
2324

2425
tests := []struct {
@@ -75,6 +76,9 @@ func TestParseMultiAddr(t *testing.T) {
7576

7677
// Mock IPFS HTTP API server for testing
7778
func setupMockIPFSServer() *httptest.Server {
79+
// Content store to maintain uploaded files
80+
contentStore := make(map[string][]byte)
81+
7882
mux := http.NewServeMux()
7983

8084
// Mock /api/v0/add endpoint
@@ -111,6 +115,9 @@ func setupMockIPFSServer() *httptest.Server {
111115
mockCID = mockCID[:46]
112116
}
113117

118+
// Store content for later retrieval
119+
contentStore[mockCID] = content
120+
114121
// Return IPFS add response format
115122
w.Header().Set("Content-Type", "application/json")
116123
fmt.Fprintf(w, `{"Name":"","Hash":"%s","Size":"%d"}`, mockCID, len(content))
@@ -129,13 +136,13 @@ func setupMockIPFSServer() *httptest.Server {
129136
return
130137
}
131138

132-
// Return mock content based on CID
133-
if strings.HasPrefix(cid, "Qm") {
134-
// Extract original content from mock CID (simplified)
139+
// Return stored content based on CID
140+
if content, exists := contentStore[cid]; exists {
135141
w.Header().Set("Content-Type", "application/octet-stream")
136-
fmt.Fprintf(w, "mock-content-for-%s", cid)
142+
w.Header().Set("Content-Length", strconv.Itoa(len(content)))
143+
w.Write(content)
137144
} else {
138-
http.Error(w, "Invalid CID", http.StatusBadRequest)
145+
http.Error(w, "Content not found", http.StatusNotFound)
139146
}
140147
})
141148

@@ -153,7 +160,7 @@ func setupMockIPFSServer() *httptest.Server {
153160
return httptest.NewServer(mux)
154161
}
155162

156-
func TestIPFSClientCompatibility(t *testing.T) {
163+
func TestIPFSHTTPClientCompatibility(t *testing.T) {
157164
// Setup mock IPFS server
158165
server := setupMockIPFSServer()
159166
defer server.Close()
@@ -178,15 +185,21 @@ func TestIPFSClientCompatibility(t *testing.T) {
178185
})
179186

180187
t.Run("Cat operation retrieves content", func(t *testing.T) {
181-
testCID := "QmTestCID12345"
188+
// First upload content to get a valid CID
189+
testData := []byte("test data for cat operation")
190+
reader := bytes.NewReader(testData)
191+
192+
uploadedCID, err := client.Add(ctx, reader)
193+
require.NoError(t, err)
182194

183-
reader, err := client.Cat(ctx, testCID)
195+
// Now retrieve it with Cat
196+
catReader, err := client.Cat(ctx, uploadedCID)
184197
require.NoError(t, err)
185-
defer reader.Close()
198+
defer catReader.Close()
186199

187-
content, err := io.ReadAll(reader)
200+
content, err := io.ReadAll(catReader)
188201
require.NoError(t, err)
189-
tassert.Contains(t, string(content), testCID)
202+
tassert.Equal(t, testData, content, "Cat should return original content")
190203
})
191204

192205
t.Run("SwarmPeers returns peer list", func(t *testing.T) {
@@ -198,7 +211,7 @@ func TestIPFSClientCompatibility(t *testing.T) {
198211
})
199212
}
200213

201-
func TestLightweightClientCompatibility(t *testing.T) {
214+
func TestIPFSLightweightClientCompatibility(t *testing.T) {
202215
// Setup mock IPFS server
203216
server := setupMockIPFSServer()
204217
defer server.Close()
@@ -223,18 +236,21 @@ func TestLightweightClientCompatibility(t *testing.T) {
223236
})
224237

225238
t.Run("Get with IPFSPath returns IPFSNode", func(t *testing.T) {
226-
testCID := "QmTestCID67890"
227-
path, err := NewIPFSPath(testCID)
239+
// First upload content to get a valid CID
240+
testData := []byte("test data for get operation")
241+
uploadFile := NewIPFSFileFromBytes(testData)
242+
uploadedPath, err := client.Unixfs().Add(ctx, uploadFile)
228243
require.NoError(t, err)
229244

230-
node, err := client.Unixfs().Get(ctx, path)
245+
// Now retrieve it
246+
node, err := client.Unixfs().Get(ctx, uploadedPath)
231247
require.NoError(t, err)
232248
defer node.Close()
233249

234250
var buf bytes.Buffer
235251
_, err = buf.ReadFrom(node)
236252
require.NoError(t, err)
237-
tassert.Contains(t, buf.String(), testCID)
253+
tassert.Equal(t, testData, buf.Bytes(), "Retrieved content should match uploaded content")
238254
})
239255

240256
t.Run("Swarm peers returns string slice", func(t *testing.T) {
@@ -245,7 +261,7 @@ func TestLightweightClientCompatibility(t *testing.T) {
245261
})
246262
}
247263

248-
func TestBlobUploadResponseFormat(t *testing.T) {
264+
func TestIPFSBlobUploadResponseFormat(t *testing.T) {
249265
// This test verifies the blob upload response format matches expectations
250266
// Based on the test failure: expected '/ipfs/CID' but got 'CID'
251267

@@ -329,7 +345,7 @@ func TestIPFSFileOperations(t *testing.T) {
329345
}
330346

331347
// Benchmark tests to ensure performance is acceptable
332-
func BenchmarkIPFSClientAdd(b *testing.B) {
348+
func BenchmarkIPFSHTTPClientAdd(b *testing.B) {
333349
server := setupMockIPFSServer()
334350
defer server.Close()
335351

@@ -348,7 +364,7 @@ func BenchmarkIPFSClientAdd(b *testing.B) {
348364
}
349365
}
350366

351-
func BenchmarkLightweightClientAdd(b *testing.B) {
367+
func BenchmarkIPFSLightweightClientAdd(b *testing.B) {
352368
server := setupMockIPFSServer()
353369
defer server.Close()
354370

ipfs_http_client.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"io"
1414
"mime/multipart"
1515
"net/http"
16+
"strconv"
1617
"strings"
1718
"time"
1819
)
@@ -159,6 +160,42 @@ func (c *IPFSHTTPClient) Cat(ctx context.Context, cid string) (io.ReadCloser, er
159160
return resp.Body, nil
160161
}
161162

163+
// CatWithSize retrieves data from IPFS by CID and attempts to get size from Content-Length header
164+
func (c *IPFSHTTPClient) CatWithSize(ctx context.Context, cid string) (io.ReadCloser, int64, error) {
165+
// Create request
166+
req, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+"/api/v0/cat", nil)
167+
if err != nil {
168+
return nil, 0, fmt.Errorf("failed to create request: %w", err)
169+
}
170+
171+
// Add CID as query parameter
172+
q := req.URL.Query()
173+
q.Add("arg", cid)
174+
req.URL.RawQuery = q.Encode()
175+
176+
// Execute request
177+
resp, err := c.httpClient.Do(req)
178+
if err != nil {
179+
return nil, 0, fmt.Errorf("failed to execute request: %w", err)
180+
}
181+
182+
if resp.StatusCode != http.StatusOK {
183+
body, _ := io.ReadAll(resp.Body)
184+
resp.Body.Close()
185+
return nil, 0, fmt.Errorf("IPFS cat failed with status %d: %s", resp.StatusCode, string(body))
186+
}
187+
188+
// Try to get size from Content-Length header
189+
var size int64 = -1 // -1 indicates unknown size
190+
if contentLength := resp.Header.Get("Content-Length"); contentLength != "" {
191+
if parsedSize, err := strconv.ParseInt(contentLength, 10, 64); err == nil {
192+
size = parsedSize
193+
}
194+
}
195+
196+
return resp.Body, size, nil
197+
}
198+
162199
// SwarmPeersResponse represents a peer in the swarm
163200
type SwarmPeersResponse struct {
164201
Peers []struct {
@@ -308,11 +345,11 @@ func (n *IPFSNode) Close() error {
308345

309346
// Get retrieves a file from IPFS
310347
func (u *UnixfsAPI) Get(ctx context.Context, path *IPFSPath) (*IPFSNode, error) {
311-
reader, err := u.client.Cat(ctx, path.cid)
348+
reader, size, err := u.client.CatWithSize(ctx, path.cid)
312349
if err != nil {
313350
return nil, err
314351
}
315-
return &IPFSNode{reader: reader}, nil
352+
return &IPFSNode{reader: reader, size: size}, nil
316353
}
317354

318355
// SwarmAPI provides swarm operations

ipfs_lightweight.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,9 @@ func uploadBlobHandleFunc(_ uint, r *Relay) func(http.ResponseWriter, *http.Requ
152152
}
153153
}
154154

155-
func ipfsCatHandleFunc() func(http.ResponseWriter, *http.Request) {
155+
func ipfsCatHandlerWithClient(client *LightweightIPFSClient) func(http.ResponseWriter, *http.Request) {
156156
return func(w http.ResponseWriter, req *http.Request) {
157157
ctx := req.Context()
158-
client, err := getIpfsClient(ctx, 0, nil)
159-
if err != nil {
160-
http.Error(w, err.Error(), http.StatusInternalServerError)
161-
return
162-
}
163158

164159
ipfsPath, err := NewIPFSPath(req.URL.Path)
165160
if err != nil {
@@ -184,14 +179,31 @@ func ipfsCatHandleFunc() func(http.ResponseWriter, *http.Request) {
184179
}
185180

186181
headers := w.Header()
187-
headers.Set("Content-Length", strconv.Itoa(int(sz)))
182+
// Only set Content-Length if size is known (>= 0)
183+
if sz >= 0 {
184+
headers.Set("Content-Length", strconv.Itoa(int(sz)))
185+
}
188186
// ipfs blobs never change
189187
headers.Set("Cache-Control", "public, max-age=29030400, immutable")
190188
w.WriteHeader(http.StatusOK)
191189
_, _ = io.Copy(w, node)
192190
}
193191
}
194192

193+
func ipfsCatHandleFunc() func(http.ResponseWriter, *http.Request) {
194+
return func(w http.ResponseWriter, req *http.Request) {
195+
ctx := req.Context()
196+
client, err := getIpfsClient(ctx, 0, nil)
197+
if err != nil {
198+
http.Error(w, err.Error(), http.StatusInternalServerError)
199+
return
200+
}
201+
202+
handler := ipfsCatHandlerWithClient(client)
203+
handler(w, req)
204+
}
205+
}
206+
195207
type savedItem struct {
196208
cid combinedID
197209
cborHash [32]byte

0 commit comments

Comments
 (0)