mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
swarm/api: get hash of swarm content with swarm.hash=true query param (#15238)
The response to request bzz request with swarm.hash=true query parameter will return the hash of the content that will be otherwise returned without the query parameter. Response Content-Type is application/bzz-hash. All possible error responses are the same with or without the query parameter.
This commit is contained in:
parent
d82ecbac22
commit
e9f38d726e
2 changed files with 96 additions and 20 deletions
|
|
@ -293,10 +293,48 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) {
|
||||||
// HandleGetRaw handles a GET request to bzzr://<key> and responds with
|
// HandleGetRaw handles a GET request to bzzr://<key> and responds with
|
||||||
// the raw content stored at the given storage key
|
// the raw content stored at the given storage key
|
||||||
func (s *Server) HandleGetRaw(w http.ResponseWriter, r *Request) {
|
func (s *Server) HandleGetRaw(w http.ResponseWriter, r *Request) {
|
||||||
|
_, reader, ok := s.handleGet(w, r)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleGetHash handles a GET request to bzz://<key> with query parameter
|
||||||
|
// hash=true, and responds with the hash of the content stored
|
||||||
|
// at the given storage key as a application/bzz-hash response
|
||||||
|
func (s *Server) HandleGetHash(w http.ResponseWriter, r *Request) {
|
||||||
|
key, _, ok := s.handleGet(w, r)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/bzz-hash")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleGet is a handler that is used in HandleGetRaw and HandleGetHash methods
|
||||||
|
// to provide storage Key and LazySectionReader for the requested path.
|
||||||
|
//
|
||||||
|
// This method accepts http.ResponseWriter to respond errors and in case of
|
||||||
|
// errors, the third returned value will be false, indicating that the request
|
||||||
|
// is not valid, error is written to the response and nothing more should be
|
||||||
|
// written.
|
||||||
|
func (s *Server) handleGet(w http.ResponseWriter, r *Request) (key storage.Key, reader storage.LazySectionReader, ok bool) {
|
||||||
key, err := s.api.Resolve(r.uri)
|
key, err := s.api.Resolve(r.uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
|
s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
|
||||||
return
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// if path is set, interpret <key> as a manifest and return the
|
// if path is set, interpret <key> as a manifest and return the
|
||||||
|
|
@ -305,7 +343,7 @@ func (s *Server) HandleGetRaw(w http.ResponseWriter, r *Request) {
|
||||||
walker, err := s.api.NewManifestWalker(key, nil)
|
walker, err := s.api.NewManifestWalker(key, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.BadRequest(w, r, fmt.Sprintf("%s is not a manifest", key))
|
s.BadRequest(w, r, fmt.Sprintf("%s is not a manifest", key))
|
||||||
return
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
var entry *api.ManifestEntry
|
var entry *api.ManifestEntry
|
||||||
walker.Walk(func(e *api.ManifestEntry) error {
|
walker.Walk(func(e *api.ManifestEntry) error {
|
||||||
|
|
@ -333,27 +371,18 @@ func (s *Server) HandleGetRaw(w http.ResponseWriter, r *Request) {
|
||||||
})
|
})
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
s.NotFound(w, r, fmt.Errorf("Manifest entry could not be loaded"))
|
s.NotFound(w, r, fmt.Errorf("Manifest entry could not be loaded"))
|
||||||
return
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
key = storage.Key(common.Hex2Bytes(entry.Hash))
|
key = storage.Key(common.Hex2Bytes(entry.Hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the root chunk exists by retrieving the file's size
|
// check the root chunk exists by retrieving the file's size
|
||||||
reader := s.api.Retrieve(key)
|
reader = s.api.Retrieve(key)
|
||||||
if _, err := reader.Size(nil); err != nil {
|
if _, err := reader.Size(nil); err != nil {
|
||||||
s.NotFound(w, r, fmt.Errorf("Root chunk not found %s: %s", key, err))
|
s.NotFound(w, r, fmt.Errorf("Root chunk not found %s: %s", key, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
return key, reader, true
|
||||||
// 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:/<manifest> with an Accept
|
// HandleGetFiles handles a GET request to bzz:/<manifest> with an Accept
|
||||||
|
|
@ -626,11 +655,24 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.URL.Query().Get("list") == "true" {
|
getList := r.URL.Query().Get("list") == "true"
|
||||||
|
getHash := r.URL.Query().Get("swarm.hash") == "true"
|
||||||
|
|
||||||
|
if getList && getHash {
|
||||||
|
s.BadRequest(w, req, "query parameters list and hash can not be requested at the same time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if getList {
|
||||||
s.HandleGetList(w, req)
|
s.HandleGetList(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if getHash {
|
||||||
|
s.HandleGetHash(w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
s.HandleGetFile(w, req)
|
s.HandleGetFile(w, req)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -104,19 +104,53 @@ func TestBzzrGetPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nonhashtests := []string{
|
// test hash requests
|
||||||
|
for k, v := range testrequests {
|
||||||
|
var resp *http.Response
|
||||||
|
var respbody []byte
|
||||||
|
|
||||||
|
url := srv.URL + "/bzz:/"
|
||||||
|
if k[:] != "" {
|
||||||
|
url += common.ToHex(key[0])[2:] + "/" + k[1:] + "?swarm.hash=true"
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorTests := []string{
|
||||||
srv.URL + "/bzz:/name",
|
srv.URL + "/bzz:/name",
|
||||||
srv.URL + "/bzzi:/nonhash",
|
srv.URL + "/bzzi:/nonhash",
|
||||||
srv.URL + "/bzzr:/nonhash",
|
srv.URL + "/bzzr:/nonhash",
|
||||||
|
srv.URL + "/bzz:/nonhash?swarm.hash=true",
|
||||||
|
srv.URL + "/bzz:/a?swarm.hash=true&list=true",
|
||||||
}
|
}
|
||||||
|
|
||||||
nonhashresponses := []string{
|
errorResponses := []string{
|
||||||
"error resolving name: no DNS to resolve name: "name"",
|
"error resolving name: no DNS to resolve name: "name"",
|
||||||
"error resolving nonhash: immutable address not a content hash: "nonhash"",
|
"error resolving nonhash: immutable address not a content hash: "nonhash"",
|
||||||
"error resolving nonhash: no DNS to resolve name: "nonhash"",
|
"error resolving nonhash: no DNS to resolve name: "nonhash"",
|
||||||
|
"error resolving nonhash: no DNS to resolve name: "nonhash"",
|
||||||
|
"query parameters list and hash can not be requested at the same time",
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, url := range nonhashtests {
|
for i, url := range errorTests {
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
var respbody []byte
|
var respbody []byte
|
||||||
|
|
||||||
|
|
@ -130,8 +164,8 @@ func TestBzzrGetPath(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ReadAll failed: %v", err)
|
t.Fatalf("ReadAll failed: %v", err)
|
||||||
}
|
}
|
||||||
if !strings.Contains(string(respbody), nonhashresponses[i]) {
|
if !strings.Contains(string(respbody), errorResponses[i]) {
|
||||||
t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", nonhashresponses[i], string(respbody))
|
t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", errorResponses[i], string(respbody))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue