From 77e5d7e8e2ad6473eb76058ae768e334ccf1bbf8 Mon Sep 17 00:00:00 2001 From: Janos Guljas Date: Thu, 14 Dec 2017 18:27:07 +0100 Subject: [PATCH] swarm/api: return hash of the content for bzz-hash:// requests --- swarm/api/api.go | 15 ++++-- swarm/api/http/server.go | 52 ++++++++++---------- swarm/api/http/server_test.go | 91 +++++++++++++++++++++++------------ 3 files changed, 99 insertions(+), 59 deletions(-) diff --git a/swarm/api/api.go b/swarm/api/api.go index 79de29a1cf..ca58d5936d 100644 --- a/swarm/api/api.go +++ b/swarm/api/api.go @@ -130,6 +130,15 @@ func (self *Api) Put(content, contentType string) (storage.Key, error) { // to resolve basePath to content using dpa retrieve // it returns a section reader, mimeType, status and an error func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionReader, mimeType string, status int, err error) { + reader, _, mimeType, status, err = self.GetHash(key, path) + return +} + +// GetHash extends the Get method to return the hash of the content, +// it uses iterative manifest retrieval and prefix matching +// to resolve basePath to content using dpa retrieve +// it returns a section reader, hash, mimeType, status and an error +func (self *Api) GetHash(key storage.Key, path string) (reader storage.LazySectionReader, hash storage.Key, mimeType string, status int, err error) { trie, err := loadManifest(self.dpa, key, nil) if err != nil { status = http.StatusNotFound @@ -142,14 +151,14 @@ func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionRe entry, _ := trie.getEntry(path) if entry != nil { - key = common.Hex2Bytes(entry.Hash) + hash = common.Hex2Bytes(entry.Hash) status = entry.Status if status == http.StatusMultipleChoices { return } else { mimeType = entry.ContentType - log.Trace(fmt.Sprintf("content lookup key: '%v' (%v)", key, mimeType)) - reader = self.dpa.Retrieve(key) + log.Trace(fmt.Sprintf("content lookup key: '%v' (%v)", hash, mimeType)) + reader = self.dpa.Retrieve(hash) } } else { status = http.StatusNotFound diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go index 86450ed6f6..17164a0b71 100644 --- a/swarm/api/http/server.go +++ b/swarm/api/http/server.go @@ -290,12 +290,9 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) { fmt.Fprint(w, newKey) } -// HandleGet handles a GET request to -// - bzzr:// and responds with the raw content stored at the -// given storage key -// - bzz-hash:// and responds with the hash of the content stored -// at the given storage key as a text/plain response -func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { +// HandleGetRaw handles a GET request to bzzr:// and responds with +// the raw content stored at the given storage key +func (s *Server) HandleGetRaw(w http.ResponseWriter, r *Request) { key, err := s.api.Resolve(r.uri) if err != nil { s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) @@ -348,22 +345,15 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { return } - switch { - case r.uri.Raw(): - // allow the request to overwrite the content type using a query - // parameter - contentType := "application/octet-stream" - if typ := r.URL.Query().Get("content_type"); typ != "" { - contentType = typ - } - w.Header().Set("Content-Type", contentType) - - http.ServeContent(w, &r.Request, "", time.Now(), reader) - case r.uri.Hash(): - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, key) + // allow the request to overwrite the content type using a query + // parameter + contentType := "application/octet-stream" + if typ := r.URL.Query().Get("content_type"); typ != "" { + contentType = typ } + w.Header().Set("Content-Type", contentType) + + http.ServeContent(w, &r.Request, "", time.Now(), reader) } // HandleGetFiles handles a GET request to bzz:/ with an Accept @@ -532,8 +522,11 @@ func (s *Server) getManifestList(key storage.Key, prefix string) (list api.Manif return list, nil } -// HandleGetFile handles a GET request to bzz:/// and responds -// with the content of the file at from the given +// HandleGetFile handles a GET request to +// - bzz:/// and responds with the content of the file at +// from the given +// - bzz-hash:/// and responds with the hash of the content stored +// at the given storage key as a text/plain response func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) { // ensure the root path has a trailing slash so that relative URLs work if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { @@ -547,7 +540,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) { return } - reader, contentType, status, err := s.api.Get(key, r.uri.Path) + reader, hash, contentType, status, err := s.api.GetHash(key, r.uri.Path) if err != nil { switch status { case http.StatusNotFound: @@ -580,6 +573,13 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) { return } + if r.uri.Hash() { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, hash) + return + } + w.Header().Set("Content-Type", contentType) http.ServeContent(w, &r.Request, "", time.Now(), reader) @@ -626,8 +626,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.HandleDelete(w, req) case "GET": - if uri.Raw() || uri.Hash() { - s.HandleGet(w, req) + if uri.Raw() { + s.HandleGetRaw(w, req) return } diff --git a/swarm/api/http/server_test.go b/swarm/api/http/server_test.go index 9acfd40979..583b1fe5e8 100644 --- a/swarm/api/http/server_test.go +++ b/swarm/api/http/server_test.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/swarm/testutil" ) -func TestBzzGetPath(t *testing.T) { +func TestBzzrGetPath(t *testing.T) { var err error @@ -104,35 +104,6 @@ func TestBzzGetPath(t *testing.T) { } } - for k, v := range testrequests { - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz-hash:/" - if k[:] != "" { - url += common.ToHex(key[0])[2:] + "/" + k[1:] - } - resp, err = http.Get(url) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if string(respbody) != key[v].String() { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k[:] == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Fatalf("Response body does not match, expected: %v, got %v", key[v], string(respbody)) - } - } - } - nonhashtests := []string{ srv.URL + "/bzz:/name", srv.URL + "/bzzi:/nonhash", @@ -225,3 +196,63 @@ func TestBzzRootRedirect(t *testing.T) { t.Fatalf("expected response to equal %q, got %q", data, gotData) } } + +// TestBzzHash tests if requests with bzz-hash:// scheme +// return the hash of the swarm content. +func TestBzzHash(t *testing.T) { + srv := testutil.NewTestSwarmServer(t) + defer srv.Close() + + client := swarm.NewClient(srv.URL) + + for _, c := range []struct { + data string + path string + }{ + { + data: "test root", + path: "", + }, + { + data: "test /a", + path: "a", + }, + { + data: "test /a/b", + path: "a/b", + }, + } { + t.Run("path "+c.path, func(t *testing.T) { + hash, err := client.Upload(&swarm.File{ + ReadCloser: ioutil.NopCloser(strings.NewReader(c.data)), + ManifestEntry: api.ManifestEntry{ + Path: "", + ContentType: "text/plain", + Size: int64(len(c.data)), + }, + }, "") + if err != nil { + t.Fatal(err) + } + + manifest, err := client.DownloadManifest(hash) + if err != nil { + t.Fatal(err) + } + + res, err := http.Get(srv.URL + "/bzz-hash:/" + hash + "/") + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + if string(body) != manifest.Entries[0].Hash { + t.Fatalf("expected response to equal %q, got %q", manifest.Entries[0].Hash, string(body)) + } + }) + } +}