aboutsummaryrefslogtreecommitdiffstats
path: root/swarm/api/http/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'swarm/api/http/server.go')
-rw-r--r--swarm/api/http/server.go188
1 files changed, 80 insertions, 108 deletions
diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go
index 5a7c9e93e..76a57625b 100644
--- a/swarm/api/http/server.go
+++ b/swarm/api/http/server.go
@@ -38,7 +38,6 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/swarm/api"
"github.com/ethereum/go-ethereum/swarm/log"
@@ -101,9 +100,11 @@ func NewServer(api *api.API, corsString string) *Server {
server.Handler = c.Handler(mux)
return server
}
+
func (s *Server) ListenAndServe(addr string) error {
return http.ListenAndServe(addr, s)
}
+
func (s *Server) HandleRootPaths(w http.ResponseWriter, r *Request) {
switch r.Method {
case http.MethodGet:
@@ -133,6 +134,7 @@ func (s *Server) HandleRootPaths(w http.ResponseWriter, r *Request) {
Respond(w, r, "Not Found", http.StatusNotFound)
}
}
+
func (s *Server) HandleBzz(w http.ResponseWriter, r *Request) {
switch r.Method {
case http.MethodGet:
@@ -240,12 +242,6 @@ func (s *Server) WrapHandler(parseBzzUri bool, h func(http.ResponseWriter, *Requ
// https://developer.mozilla.org/en/docs/Web-based_protocol_handlers
// electron (chromium) api for registering bzz url scheme handlers:
// https://github.com/atom/electron/blob/master/docs/api/protocol.md
-
-// browser API for registering bzz url scheme handlers:
-// https://developer.mozilla.org/en/docs/Web-based_protocol_handlers
-// electron (chromium) api for registering bzz url scheme handlers:
-// https://github.com/atom/electron/blob/master/docs/api/protocol.md
-
type Server struct {
http.Handler
api *api.API
@@ -340,7 +336,7 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *Request) {
var addr storage.Address
if r.uri.Addr != "" && r.uri.Addr != "encrypt" {
- addr, err = s.api.Resolve(ctx, r.uri)
+ addr, err = s.api.Resolve(r.Context(), r.uri)
if err != nil {
postFilesFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError)
@@ -348,7 +344,7 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *Request) {
}
log.Debug("resolved key", "ruid", r.ruid, "key", addr)
} else {
- addr, err = s.api.NewManifest(ctx, toEncrypt)
+ addr, err = s.api.NewManifest(r.Context(), toEncrypt)
if err != nil {
postFilesFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError)
@@ -521,9 +517,8 @@ func resourcePostMode(path string) (isRaw bool, frequency uint64, err error) {
// If the latter is used, a subsequent bzz:// GET call to the manifest of the resource will return
// the page that the multihash is pointing to, as if it held a normal swarm content manifest
//
-// The resource name will be verbatim what is passed as the address part of the url.
-// For example, if a POST is made to /bzz-resource:/foo.eth/raw/13 a new resource with frequency 13
-// and name "foo.eth" will be created
+// The POST request admits a JSON structure as defined in the mru package: `mru.updateRequestJSON`
+// The requests can be to a) create a resource, b) update a resource or c) both a+b: create a resource and set the initial content
func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
log.Debug("handle.post.resource", "ruid", r.ruid)
@@ -535,33 +530,54 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
defer sp.Finish()
var err error
- var addr storage.Address
- var name string
- var outdata []byte
- isRaw, frequency, err := resourcePostMode(r.uri.Path)
+
+ // Creation and update must send mru.updateRequestJSON JSON structure
+ body, err := ioutil.ReadAll(r.Body)
if err != nil {
- Respond(w, r, err.Error(), http.StatusBadRequest)
+ Respond(w, r, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ var updateRequest mru.Request
+ if err := updateRequest.UnmarshalJSON(body); err != nil { // decodes request JSON
+ Respond(w, r, err.Error(), http.StatusBadRequest) //TODO: send different status response depending on error
return
}
- // new mutable resource creation will always have a frequency field larger than 0
- if frequency > 0 {
-
- name = r.uri.Addr
+ if updateRequest.IsUpdate() {
+ // Verify that the signature is intact and that the signer is authorized
+ // to update this resource
+ // Check this early, to avoid creating a resource and then not being able to set its first update.
+ if err = updateRequest.Verify(); err != nil {
+ Respond(w, r, err.Error(), http.StatusForbidden)
+ return
+ }
+ }
- // the key is the content addressed root chunk holding mutable resource metadata information
- addr, err = s.api.ResourceCreate(ctx, name, frequency)
+ if updateRequest.IsNew() {
+ err = s.api.ResourceCreate(r.Context(), &updateRequest)
if err != nil {
code, err2 := s.translateResourceError(w, r, "resource creation fail", err)
-
Respond(w, r, err2.Error(), code)
return
}
+ }
+ if updateRequest.IsUpdate() {
+ _, err = s.api.ResourceUpdate(r.Context(), &updateRequest.SignedResourceUpdate)
+ if err != nil {
+ Respond(w, r, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+
+ // at this point both possible operations (create, update or both) were successful
+ // so in case it was a new resource, then create a manifest and send it over.
+
+ if updateRequest.IsNew() {
// we create a manifest so we can retrieve the resource with bzz:// later
// this manifest has a special "resource type" manifest, and its hash is the key of the mutable resource
- // root chunk
- m, err := s.api.NewResourceManifest(ctx, addr.Hex())
+ // metadata chunk (rootAddr)
+ m, err := s.api.NewResourceManifest(r.Context(), updateRequest.RootAddr().Hex())
if err != nil {
Respond(w, r, fmt.Sprintf("failed to create resource manifest: %v", err), http.StatusInternalServerError)
return
@@ -571,85 +587,21 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
// the client can access the root chunk key directly through its Hash member
// the manifest key should be set as content in the resolver of the ENS name
// \TODO update manifest key automatically in ENS
- outdata, err = json.Marshal(m)
+ outdata, err := json.Marshal(m)
if err != nil {
Respond(w, r, fmt.Sprintf("failed to create json response: %s", err), http.StatusInternalServerError)
return
}
- } else {
- // to update the resource through http we need to retrieve the key for the mutable resource root chunk
- // that means that we retrieve the manifest and inspect its Hash member.
- manifestAddr := r.uri.Address()
- if manifestAddr == nil {
- manifestAddr, err = s.api.Resolve(ctx, r.uri)
- if err != nil {
- getFail.Inc(1)
- Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
- return
- }
- } else {
- w.Header().Set("Cache-Control", "max-age=2147483648")
- }
-
- // get the root chunk key from the manifest
- addr, err = s.api.ResolveResourceManifest(ctx, manifestAddr)
- if err != nil {
- getFail.Inc(1)
- Respond(w, r, fmt.Sprintf("error resolving resource root chunk for %s: %s", r.uri.Addr, err), http.StatusNotFound)
- return
- }
-
- log.Debug("handle.post.resource: resolved", "ruid", r.ruid, "manifestkey", manifestAddr, "rootchunkkey", addr)
-
- name, _, err = s.api.ResourceLookup(ctx, addr, 0, 0, &mru.LookupParams{})
- if err != nil {
- Respond(w, r, err.Error(), http.StatusNotFound)
- return
- }
- }
-
- // Creation and update must send data aswell. This data constitutes the update data itself.
- data, err := ioutil.ReadAll(r.Body)
- if err != nil {
- Respond(w, r, err.Error(), http.StatusInternalServerError)
- return
- }
-
- // Multihash will be passed as hex-encoded data, so we need to parse this to bytes
- if isRaw {
- _, _, _, err = s.api.ResourceUpdate(ctx, name, data)
- if err != nil {
- Respond(w, r, err.Error(), http.StatusBadRequest)
- return
- }
- } else {
- bytesdata, err := hexutil.Decode(string(data))
- if err != nil {
- Respond(w, r, err.Error(), http.StatusBadRequest)
- return
- }
- _, _, _, err = s.api.ResourceUpdateMultihash(ctx, name, bytesdata)
- if err != nil {
- Respond(w, r, err.Error(), http.StatusBadRequest)
- return
- }
- }
-
- // If we have data to return, write this now
- // \TODO there should always be data to return here
- if len(outdata) > 0 {
- w.Header().Add("Content-type", "text/plain")
- w.WriteHeader(http.StatusOK)
fmt.Fprint(w, string(outdata))
- return
}
- w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-type", "application/json")
}
// Retrieve mutable resource updates:
// bzz-resource://<id> - get latest update
// bzz-resource://<id>/<n> - get latest update on period n
// bzz-resource://<id>/<n>/<m> - get update version m of period n
+// bzz-resource://<id>/meta - get metadata and next version information
// <id> = ens name or hash
// TODO: Enable pass maxPeriod parameter
func (s *Server) HandleGetResource(w http.ResponseWriter, r *Request) {
@@ -669,31 +621,51 @@ func (s *Server) HandleGetResource(w http.ResponseWriter, r *Request) {
w.Header().Set("Cache-Control", "max-age=2147483648")
}
- // get the root chunk key from the manifest
- key, err := s.api.ResolveResourceManifest(r.Context(), manifestAddr)
+ // get the root chunk rootAddr from the manifest
+ rootAddr, err := s.api.ResolveResourceManifest(r.Context(), manifestAddr)
if err != nil {
getFail.Inc(1)
Respond(w, r, fmt.Sprintf("error resolving resource root chunk for %s: %s", r.uri.Addr, err), http.StatusNotFound)
return
}
- log.Debug("handle.get.resource: resolved", "ruid", r.ruid, "manifestkey", manifestAddr, "rootchunk key", key)
+ log.Debug("handle.get.resource: resolved", "ruid", r.ruid, "manifestkey", manifestAddr, "rootchunk addr", rootAddr)
- // determine if the query specifies period and version
+ // determine if the query specifies period and version or it is a metadata query
var params []string
if len(r.uri.Path) > 0 {
+ if r.uri.Path == "meta" {
+ unsignedUpdateRequest, err := s.api.ResourceNewRequest(r.Context(), rootAddr)
+ if err != nil {
+ getFail.Inc(1)
+ Respond(w, r, fmt.Sprintf("cannot retrieve resource metadata for rootAddr=%s: %s", rootAddr.Hex(), err), http.StatusNotFound)
+ return
+ }
+ rawResponse, err := unsignedUpdateRequest.MarshalJSON()
+ if err != nil {
+ Respond(w, r, fmt.Sprintf("cannot encode unsigned UpdateRequest: %v", err), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Add("Content-type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, string(rawResponse))
+ return
+
+ }
+
params = strings.Split(r.uri.Path, "/")
+
}
var name string
- var period uint64
- var version uint64
var data []byte
now := time.Now()
switch len(params) {
case 0: // latest only
- name, data, err = s.api.ResourceLookup(r.Context(), key, 0, 0, nil)
+ name, data, err = s.api.ResourceLookup(r.Context(), mru.LookupLatest(rootAddr))
case 2: // specific period and version
+ var version uint64
+ var period uint64
version, err = strconv.ParseUint(params[1], 10, 32)
if err != nil {
break
@@ -702,13 +674,14 @@ func (s *Server) HandleGetResource(w http.ResponseWriter, r *Request) {
if err != nil {
break
}
- name, data, err = s.api.ResourceLookup(r.Context(), key, uint32(period), uint32(version), nil)
+ name, data, err = s.api.ResourceLookup(r.Context(), mru.LookupVersion(rootAddr, uint32(period), uint32(version)))
case 1: // last version of specific period
+ var period uint64
period, err = strconv.ParseUint(params[0], 10, 32)
if err != nil {
break
}
- name, data, err = s.api.ResourceLookup(r.Context(), key, uint32(period), uint32(version), nil)
+ name, data, err = s.api.ResourceLookup(r.Context(), mru.LookupLatestVersionInPeriod(rootAddr, uint32(period)))
default: // bogus
err = mru.NewError(storage.ErrInvalidValue, "invalid mutable resource request")
}
@@ -766,7 +739,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
var err error
addr := r.uri.Address()
if addr == nil {
- addr, err = s.api.Resolve(ctx, r.uri)
+ addr, err = s.api.Resolve(r.Context(), r.uri)
if err != nil {
getFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
@@ -781,7 +754,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
// if path is set, interpret <key> as a manifest and return the
// raw entry at the given path
if r.uri.Path != "" {
- walker, err := s.api.NewManifestWalker(ctx, addr, nil)
+ walker, err := s.api.NewManifestWalker(r.Context(), addr, nil)
if err != nil {
getFail.Inc(1)
Respond(w, r, fmt.Sprintf("%s is not a manifest", addr), http.StatusBadRequest)
@@ -875,7 +848,7 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) {
return
}
- addr, err := s.api.Resolve(ctx, r.uri)
+ addr, err := s.api.Resolve(r.Context(), r.uri)
if err != nil {
getListFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
@@ -935,7 +908,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
manifestAddr := r.uri.Address()
if manifestAddr == nil {
- manifestAddr, err = s.api.Resolve(ctx, r.uri)
+ manifestAddr, err = s.api.Resolve(r.Context(), r.uri)
if err != nil {
getFileFail.Inc(1)
Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
@@ -947,8 +920,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
}
log.Debug("handle.get.file: resolved", "ruid", r.ruid, "key", manifestAddr)
-
- reader, contentType, status, contentKey, err := s.api.Get(ctx, manifestAddr, r.uri.Path)
+ reader, contentType, status, contentKey, err := s.api.Get(r.Context(), manifestAddr, r.uri.Path)
etag := common.Bytes2Hex(contentKey)
noneMatchEtag := r.Header.Get("If-None-Match")