diff --git a/swarm/api/http/controllers/bzz-list.go b/swarm/api/http/controllers/bzz-list.go index 99f4ce14b4..a7c7c22d38 100644 --- a/swarm/api/http/controllers/bzz-list.go +++ b/swarm/api/http/controllers/bzz-list.go @@ -22,41 +22,41 @@ import ( "strings" "github.com/ethereum/go-ethereum/swarm/api" - "github.com/ethereum/go-ethereum/swarm/api/http" + "github.com/ethereum/go-ethereum/swarm/api/http/messages" + "github.com/ethereum/go-ethereum/swarm/api/http/views" "github.com/ethereum/go-ethereum/swarm/log" + "github.com/ethereum/go-ethereum/swarm/storage" ) type BzzListController struct { *Controller } -//var BzzListController BzzListController - -// HandleGetList handles a GET request to bzz-list:// and returns +// Get handles a GET request to bzz-list:// and returns // a list of all files contained in under grouped into // common prefixes using "/" as a delimiter -func (controller *BzzListController) Get(w http.ResponseWriter, r *Request) { - log.Debug("handle.get.list", "ruid", r.ruid, "uri", r.uri) - getListCount.Inc(1) +func (controller *BzzListController) Get(w http.ResponseWriter, r *messages.Request) { + log.Debug("handle.get.list", "ruid", r.Ruid, "uri", r.Uri) + //getListCount.Inc(1) // ensure the root path has a trailing slash so that relative URLs work - if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { + if r.Uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) return } - addr, err := s.api.Resolve(r.uri) + addr, err := controller.api.Resolve(r.Uri) if err != nil { - getListFail.Inc(1) - Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) + //getListFail.Inc(1) + controller.Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.Uri.Addr, err), http.StatusNotFound) return } - log.Debug("handle.get.list: resolved", "ruid", r.ruid, "key", addr) + log.Debug("handle.get.list: resolved", "ruid", r.Ruid, "key", addr) - list, err := s.getManifestList(addr, r.uri.Path) + list, err := controller.getManifestList(addr, r.Uri.Path) if err != nil { - getListFail.Inc(1) - Respond(w, r, err.Error(), http.StatusInternalServerError) + // getListFail.Inc(1) + controller.Respond(w, r, err.Error(), http.StatusInternalServerError) return } @@ -64,16 +64,16 @@ func (controller *BzzListController) Get(w http.ResponseWriter, r *Request) { // HTML index with relative URLs if strings.Contains(r.Header.Get("Accept"), "text/html") { w.Header().Set("Content-Type", "text/html") - err := htmlListTemplate.Execute(w, &htmlListData{ - URI: &api.URI{ + err := views.HtmlListTemplate.Execute(w, &views.HtmlListData{ + URI: &api.Uri{ Scheme: "bzz", - Addr: r.uri.Addr, - Path: r.uri.Path, + Addr: r.Uri.Addr, + Path: r.Uri.Path, }, List: &list, }) if err != nil { - getListFail.Inc(1) + // getListFail.Inc(1) log.Error(fmt.Sprintf("error rendering list HTML: %s", err)) } return @@ -82,3 +82,59 @@ func (controller *BzzListController) Get(w http.ResponseWriter, r *Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(&list) } + +func (controller *BzzListController) getManifestList(addr storage.Address, prefix string) (list api.ManifestList, err error) { + walker, err := controller.api.NewManifestWalker(addr, nil) + if err != nil { + return + } + + err = walker.Walk(func(entry *api.ManifestEntry) error { + // handle non-manifest files + if entry.ContentType != api.ManifestType { + // ignore the file if it doesn't have the specified prefix + if !strings.HasPrefix(entry.Path, prefix) { + return nil + } + + // if the path after the prefix contains a slash, add a + // common prefix to the list, otherwise add the entry + suffix := strings.TrimPrefix(entry.Path, prefix) + if index := strings.Index(suffix, "/"); index > -1 { + list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) + return nil + } + if entry.Path == "" { + entry.Path = "/" + } + list.Entries = append(list.Entries, entry) + return nil + } + + // if the manifest's path is a prefix of the specified prefix + // then just recurse into the manifest by returning nil and + // continuing the walk + if strings.HasPrefix(prefix, entry.Path) { + return nil + } + + // if the manifest's path has the specified prefix, then if the + // path after the prefix contains a slash, add a common prefix + // to the list and skip the manifest, otherwise recurse into + // the manifest by returning nil and continuing the walk + if strings.HasPrefix(entry.Path, prefix) { + suffix := strings.TrimPrefix(entry.Path, prefix) + if index := strings.Index(suffix, "/"); index > -1 { + list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) + return api.ErrSkipManifest + } + return nil + } + + // the manifest neither has the prefix or needs recursing in to + // so just skip it + return api.ErrSkipManifest + }) + + return list, nil +} diff --git a/swarm/api/http/controllers/controller.go b/swarm/api/http/controllers/controller.go index 937b9dd81a..c6b95814fd 100644 --- a/swarm/api/http/controllers/controller.go +++ b/swarm/api/http/controllers/controller.go @@ -1,15 +1,64 @@ package controllers import ( + "html/template" "net/http" + "time" - "github.com/ethereum/go-ethereum/swarm/api/http/request" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/swarm/api" + "github.com/ethereum/go-ethereum/swarm/api/http/messages" + "github.com/ethereum/go-ethereum/swarm/api/http/views" + l "github.com/ethereum/go-ethereum/swarm/log" ) type Controller struct { + api *api.API + ControllerHandler } type ControllerHandler interface { - Get(w http.ResponseWriter, r *request.Request) + Get(w http.ResponseWriter, r *messages.Request) + Respond(w http.ResponseWriter, req *messages.Request, msg string, code int) +} + +//Respond is used to show an HTML page to a client. +//If there is an `Accept` header of `application/json`, JSON will be returned instead +//The function just takes a string message which will be displayed in the error page. +//The code is used to evaluate which template will be displayed +//(and return the correct HTTP status code) +func (controller *Controller) Respond(w http.ResponseWriter, req *messages.Request, msg string, code int) { + //additionalMessage := ValidateCaseErrors(req) + //additionalMessage := ValidateCaseErrors(req) + additionalMessage := "" + switch code { + case http.StatusInternalServerError: + log.Output(msg, log.LvlError, l.CallDepth, "ruid", req.Ruid, "code", code) + default: + log.Output(msg, log.LvlDebug, l.CallDepth, "ruid", req.Ruid, "code", code) + } + + if code >= 400 { + w.Header().Del("Cache-Control") //avoid sending cache headers for errors! + w.Header().Del("ETag") + } + + respond(w, &req.Request, &messages.ResponseParams{ + Code: code, + Msg: msg, + Details: template.HTML(additionalMessage), + Timestamp: time.Now().Format(time.RFC1123), + template: views.GetTemplate(code), + }) +} + +//evaluate if client accepts html or json response +func respond(w http.ResponseWriter, r *http.Request, params *messages.ResponseParams) { + w.WriteHeader(params.Code) + if r.Header.Get("Accept") == "application/json" { + // respondJSON(w, params) + } else { + // respondHTML(w, params) + } } diff --git a/swarm/api/http/messages/request.go b/swarm/api/http/messages/request.go index 0769147961..5ed954c759 100644 --- a/swarm/api/http/messages/request.go +++ b/swarm/api/http/messages/request.go @@ -25,6 +25,6 @@ import ( type Request struct { http.Request - uri *api.URI - ruid string // request unique id + Uri *api.URI + Ruid string // request unique id } diff --git a/swarm/api/http/messages/response.go b/swarm/api/http/messages/response.go index 435903426e..b04be0971d 100644 --- a/swarm/api/http/messages/response.go +++ b/swarm/api/http/messages/response.go @@ -14,3 +14,14 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package messages + +import "html/template" + +//parameters needed for formatting the correct HTML page +type ResponseParams struct { + Msg string + Code int + Timestamp string + template *template.Template + Details template.HTML +} diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go index d0f460ee3f..c85fde6a2b 100644 --- a/swarm/api/http/server.go +++ b/swarm/api/http/server.go @@ -42,7 +42,8 @@ import ( "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/api/http/request" + "github.com/ethereum/go-ethereum/swarm/api/http/controllers" + "github.com/ethereum/go-ethereum/swarm/api/http/messages" "github.com/ethereum/go-ethereum/swarm/log" "github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage/mru" @@ -113,23 +114,23 @@ type Server struct { // HandlePostRaw handles a POST request to a raw bzz-raw:/ URI, stores the request // body in swarm and returns the resulting storage address as a text/plain response -func (s *Server) HandlePostRaw(w http.ResponseWriter, r *request.Request) { - log.Debug("handle.post.raw", "ruid", r.ruid) +func (s *Server) HandlePostRaw(w http.ResponseWriter, r *messages.Request) { + log.Debug("handle.post.raw", "ruid", r.Ruid) postRawCount.Inc(1) toEncrypt := false - if r.uri.Addr == "encrypt" { + if r.Uri.Addr == "encrypt" { toEncrypt = true } - if r.uri.Path != "" { + if r.Uri.Path != "" { postRawFail.Inc(1) Respond(w, r, "raw POST request cannot contain a path", http.StatusBadRequest) return } - if r.uri.Addr != "" && r.uri.Addr != "encrypt" { + if r.Uri.Addr != "" && r.Uri.Addr != "encrypt" { postRawFail.Inc(1) Respond(w, r, "raw POST request addr can only be empty or \"encrypt\"", http.StatusBadRequest) return @@ -147,7 +148,7 @@ func (s *Server) HandlePostRaw(w http.ResponseWriter, r *request.Request) { return } - log.Debug("stored content", "ruid", r.ruid, "key", addr) + log.Debug("stored content", "ruid", r.Ruid, "key", addr) w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) @@ -159,8 +160,8 @@ func (s *Server) HandlePostRaw(w http.ResponseWriter, r *request.Request) { // (either a tar archive or multipart form), adds those files either to an // existing manifest or to a new manifest under and returns the // resulting manifest hash as a text/plain response -func (s *Server) HandlePostFiles(w http.ResponseWriter, r *request.Request) { - log.Debug("handle.post.files", "ruid", r.ruid) +func (s *Server) HandlePostFiles(w http.ResponseWriter, r *messages.Request) { + log.Debug("handle.post.files", "ruid", r.Ruid) postFilesCount.Inc(1) contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")) @@ -171,19 +172,19 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *request.Request) { } toEncrypt := false - if r.uri.Addr == "encrypt" { + if r.Uri.Addr == "encrypt" { toEncrypt = true } var addr storage.Address - if r.uri.Addr != "" && r.uri.Addr != "encrypt" { + if r.Uri.Addr != "" && r.Uri.Addr != "encrypt" { addr, err = s.api.Resolve(r.uri) if err != nil { postFilesFail.Inc(1) - Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError) + Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.Uri.Addr, err), http.StatusInternalServerError) return } - log.Debug("resolved key", "ruid", r.ruid, "key", addr) + log.Debug("resolved key", "ruid", r.Ruid, "key", addr) } else { addr, err = s.api.NewManifest(toEncrypt) if err != nil { @@ -191,7 +192,7 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *request.Request) { Respond(w, r, err.Error(), http.StatusInternalServerError) return } - log.Debug("new manifest", "ruid", r.ruid, "key", addr) + log.Debug("new manifest", "ruid", r.Ruid, "key", addr) } newAddr, err := s.updateManifest(addr, func(mw *api.ManifestWriter) error { @@ -213,15 +214,15 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *request.Request) { return } - log.Debug("stored content", "ruid", r.ruid, "key", newAddr) + log.Debug("stored content", "ruid", r.Ruid, "key", newAddr) w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) fmt.Fprint(w, newAddr) } -func (s *Server) handleTarUpload(req *request.Request, mw *api.ManifestWriter) error { - log.Debug("handle.tar.upload", "ruid", req.ruid) +func (s *Server) handleTarUpload(req *messages.Request, mw *api.ManifestWriter) error { + log.Debug("handle.tar.upload", "ruid", req.Ruid) tr := tar.NewReader(req.Body) for { hdr, err := tr.Next() @@ -237,7 +238,7 @@ func (s *Server) handleTarUpload(req *request.Request, mw *api.ManifestWriter) e } // add the entry under the path from the request - path := path.Join(req.uri.Path, hdr.Name) + path := path.Join(req.Uri.Path, hdr.Name) entry := &api.ManifestEntry{ Path: path, ContentType: hdr.Xattrs["user.swarm.content-type"], @@ -245,17 +246,17 @@ func (s *Server) handleTarUpload(req *request.Request, mw *api.ManifestWriter) e Size: hdr.Size, ModTime: hdr.ModTime, } - log.Debug("adding path to new manifest", "ruid", req.ruid, "bytes", entry.Size, "path", entry.Path) + log.Debug("adding path to new manifest", "ruid", req.Ruid, "bytes", entry.Size, "path", entry.Path) contentKey, err := mw.AddEntry(tr, entry) if err != nil { return fmt.Errorf("error adding manifest entry from tar stream: %s", err) } - log.Debug("stored content", "ruid", req.ruid, "key", contentKey) + log.Debug("stored content", "ruid", req.Ruid, "key", contentKey) } } -func (s *Server) handleMultipartUpload(req *request.Request, boundary string, mw *api.ManifestWriter) error { - log.Debug("handle.multipart.upload", "ruid", req.ruid) +func (s *Server) handleMultipartUpload(req *messages.Request, boundary string, mw *api.ManifestWriter) error { + log.Debug("handle.multipart.upload", "ruid", req.Ruid) mr := multipart.NewReader(req.Body, boundary) for { part, err := mr.NextPart() @@ -296,26 +297,26 @@ func (s *Server) handleMultipartUpload(req *request.Request, boundary string, mw if name == "" { name = part.FormName() } - path := path.Join(req.uri.Path, name) + path := path.Join(req.Uri.Path, name) entry := &api.ManifestEntry{ Path: path, ContentType: part.Header.Get("Content-Type"), Size: size, ModTime: time.Now(), } - log.Debug("adding path to new manifest", "ruid", req.ruid, "bytes", entry.Size, "path", entry.Path) + log.Debug("adding path to new manifest", "ruid", req.Ruid, "bytes", entry.Size, "path", entry.Path) contentKey, err := mw.AddEntry(reader, entry) if err != nil { return fmt.Errorf("error adding manifest entry from multipart form: %s", err) } - log.Debug("stored content", "ruid", req.ruid, "key", contentKey) + log.Debug("stored content", "ruid", req.Ruid, "key", contentKey) } } -func (s *Server) handleDirectUpload(req *request.Request, mw *api.ManifestWriter) error { - log.Debug("handle.direct.upload", "ruid", req.ruid) +func (s *Server) handleDirectUpload(req *messages.Request, mw *api.ManifestWriter) error { + log.Debug("handle.direct.upload", "ruid", req.Ruid) key, err := mw.AddEntry(req.Body, &api.ManifestEntry{ - Path: req.uri.Path, + Path: req.Uri.Path, ContentType: req.Header.Get("Content-Type"), Mode: 0644, Size: req.ContentLength, @@ -324,27 +325,27 @@ func (s *Server) handleDirectUpload(req *request.Request, mw *api.ManifestWriter if err != nil { return err } - log.Debug("stored content", "ruid", req.ruid, "key", key) + log.Debug("stored content", "ruid", req.Ruid, "key", key) return nil } // HandleDelete handles a DELETE request to bzz://, removes // from and returns the resulting manifest hash as a // text/plain response -func (s *Server) HandleDelete(w http.ResponseWriter, r *request.Request) { - log.Debug("handle.delete", "ruid", r.ruid) +func (s *Server) HandleDelete(w http.ResponseWriter, r *messages.Request) { + log.Debug("handle.delete", "ruid", r.Ruid) deleteCount.Inc(1) key, err := s.api.Resolve(r.uri) if err != nil { deleteFail.Inc(1) - Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError) + Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.Uri.Addr, err), http.StatusInternalServerError) return } newKey, err := s.updateManifest(key, func(mw *api.ManifestWriter) error { - log.Debug(fmt.Sprintf("removing %s from manifest %s", r.uri.Path, key.Log()), "ruid", r.ruid) - return mw.RemoveEntry(r.uri.Path) + log.Debug(fmt.Sprintf("removing %s from manifest %s", r.Uri.Path, key.Log()), "ruid", r.Ruid) + return mw.RemoveEntry(r.Uri.Path) }) if err != nil { deleteFail.Inc(1) @@ -392,13 +393,13 @@ func resourcePostMode(path string) (isRaw bool, frequency uint64, err error) { // 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 -func (s *Server) HandlePostResource(w http.ResponseWriter, r *request.Request) { - log.Debug("handle.post.resource", "ruid", r.ruid) +func (s *Server) HandlePostResource(w http.ResponseWriter, r *messages.Request) { + log.Debug("handle.post.resource", "ruid", r.Ruid) var err error var addr storage.Address var name string var outdata []byte - isRaw, frequency, err := resourcePostMode(r.uri.Path) + isRaw, frequency, err := resourcePostMode(r.Uri.Path) if err != nil { Respond(w, r, err.Error(), http.StatusBadRequest) return @@ -407,7 +408,7 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *request.Request) { // new mutable resource creation will always have a frequency field larger than 0 if frequency > 0 { - name = r.uri.Addr + name = r.Uri.Addr // the key is the content addressed root chunk holding mutable resource metadata information addr, err = s.api.ResourceCreate(r.Context(), name, frequency) @@ -439,12 +440,12 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *request.Request) { } 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() + manifestAddr := r.Uri.Address() if manifestAddr == nil { manifestAddr, err = s.api.Resolve(r.uri) if err != nil { getFail.Inc(1) - Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) + Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.Uri.Addr, err), http.StatusNotFound) return } } else { @@ -455,11 +456,11 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *request.Request) { addr, err = s.api.ResolveResourceManifest(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) + 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) + log.Debug("handle.post.resource: resolved", "ruid", r.Ruid, "manifestkey", manifestAddr, "rootchunkkey", addr) name, _, err = s.api.ResourceLookup(r.Context(), addr, 0, 0, &mru.LookupParams{}) if err != nil { @@ -511,22 +512,22 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *request.Request) { // bzz-resource:/// - get latest update on period n // bzz-resource://// - get update version m of period n // = ens name or hash -func (s *Server) HandleGetResource(w http.ResponseWriter, r *request.Request) { +func (s *Server) HandleGetResource(w http.ResponseWriter, r *messages.Request) { s.handleGetResource(w, r) } // TODO: Enable pass maxPeriod parameter -func (s *Server) handleGetResource(w http.ResponseWriter, r *request.Request) { - log.Debug("handle.get.resource", "ruid", r.ruid) +func (s *Server) handleGetResource(w http.ResponseWriter, r *messages.Request) { + log.Debug("handle.get.resource", "ruid", r.Ruid) var err error // resolve the content key. - manifestAddr := r.uri.Address() + manifestAddr := r.Uri.Address() if manifestAddr == nil { manifestAddr, err = s.api.Resolve(r.uri) if err != nil { getFail.Inc(1) - Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) + Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.Uri.Addr, err), http.StatusNotFound) return } } else { @@ -537,16 +538,16 @@ func (s *Server) handleGetResource(w http.ResponseWriter, r *request.Request) { key, err := s.api.ResolveResourceManifest(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) + 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 key", key) // determine if the query specifies period and version var params []string - if len(r.uri.Path) > 0 { - params = strings.Split(r.uri.Path, "/") + if len(r.Uri.Path) > 0 { + params = strings.Split(r.Uri.Path, "/") } var name string var period uint64 @@ -585,12 +586,12 @@ func (s *Server) handleGetResource(w http.ResponseWriter, r *request.Request) { } // All ok, serve the retrieved update - log.Debug("Found update", "name", name, "ruid", r.ruid) + log.Debug("Found update", "name", name, "ruid", r.Ruid) w.Header().Set("Content-Type", "application/octet-stream") http.ServeContent(w, &r.Request, "", now, bytes.NewReader(data)) } -func (s *Server) translateResourceError(w http.ResponseWriter, r *request.Request, supErr string, err error) (int, error) { +func (s *Server) translateResourceError(w http.ResponseWriter, r *messages.Request, supErr string, err error) (int, error) { code := 0 defaultErr := fmt.Errorf("%s: %v", supErr, err) rsrcErr, ok := err.(*mru.Error) @@ -616,27 +617,27 @@ func (s *Server) translateResourceError(w http.ResponseWriter, r *request.Reques // 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.Request) { - log.Debug("handle.get", "ruid", r.ruid, "uri", r.uri) +func (s *Server) HandleGet(w http.ResponseWriter, r *messages.Request) { + log.Debug("handle.get", "ruid", r.Ruid, "uri", r.uri) getCount.Inc(1) var err error - addr := r.uri.Address() + addr := r.Uri.Address() if addr == nil { addr, err = s.api.Resolve(r.uri) if err != nil { getFail.Inc(1) - Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) + 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, immutable") // url was of type bzz:///path, so we are sure it is immutable. } - log.Debug("handle.get: resolved", "ruid", r.ruid, "key", addr) + log.Debug("handle.get: resolved", "ruid", r.Ruid, "key", addr) // if path is set, interpret as a manifest and return the // raw entry at the given path - if r.uri.Path != "" { + if r.Uri.Path != "" { walker, err := s.api.NewManifestWalker(addr, nil) if err != nil { getFail.Inc(1) @@ -647,7 +648,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *request.Request) { walker.Walk(func(e *api.ManifestEntry) error { // if the entry matches the path, set entry and stop // the walk - if e.Path == r.uri.Path { + if e.Path == r.Uri.Path { entry = e // return an error to cancel the walk return errors.New("found") @@ -661,7 +662,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *request.Request) { // if the manifest's path is a prefix of the // requested path, recurse into it by returning // nil and continuing the walk - if strings.HasPrefix(r.uri.Path, e.Path) { + if strings.HasPrefix(r.Uri.Path, e.Path) { return nil } @@ -695,7 +696,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *request.Request) { w.Header().Set("X-Decrypted", fmt.Sprintf("%v", isEncrypted)) switch { - case r.uri.Raw(): + case r.Uri.Raw(): // allow the request to overwrite the content type using a query // parameter contentType := "application/octet-stream" @@ -704,7 +705,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *request.Request) { } w.Header().Set("Content-Type", contentType) http.ServeContent(w, &r.Request, "", time.Now(), reader) - case r.uri.Hash(): + case r.Uri.Hash(): w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) fmt.Fprint(w, addr) @@ -714,10 +715,10 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *request.Request) { // HandleGetFiles handles a GET request to bzz:/ with an Accept // header of "application/x-tar" and returns a tar stream of all files // contained in the manifest -func (s *Server) HandleGetFiles(w http.ResponseWriter, r *request.Request) { - log.Debug("handle.get.files", "ruid", r.ruid, "uri", r.uri) +func (s *Server) HandleGetFiles(w http.ResponseWriter, r *messages.Request) { + log.Debug("handle.get.files", "ruid", r.Ruid, "uri", r.uri) getFilesCount.Inc(1) - if r.uri.Path != "" { + if r.Uri.Path != "" { getFilesFail.Inc(1) Respond(w, r, "files request cannot contain a path", http.StatusBadRequest) return @@ -726,10 +727,10 @@ func (s *Server) HandleGetFiles(w http.ResponseWriter, r *request.Request) { addr, err := s.api.Resolve(r.uri) if err != nil { getFilesFail.Inc(1) - Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) + Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.Uri.Addr, err), http.StatusNotFound) return } - log.Debug("handle.get.files: resolved", "ruid", r.ruid, "key", addr) + log.Debug("handle.get.files: resolved", "ruid", r.Ruid, "key", addr) walker, err := s.api.NewManifestWalker(addr, nil) if err != nil { @@ -787,89 +788,33 @@ func (s *Server) HandleGetFiles(w http.ResponseWriter, r *request.Request) { } } -func (s *Server) getManifestList(addr storage.Address, prefix string) (list api.ManifestList, err error) { - walker, err := s.api.NewManifestWalker(addr, nil) - if err != nil { - return - } - - err = walker.Walk(func(entry *api.ManifestEntry) error { - // handle non-manifest files - if entry.ContentType != api.ManifestType { - // ignore the file if it doesn't have the specified prefix - if !strings.HasPrefix(entry.Path, prefix) { - return nil - } - - // if the path after the prefix contains a slash, add a - // common prefix to the list, otherwise add the entry - suffix := strings.TrimPrefix(entry.Path, prefix) - if index := strings.Index(suffix, "/"); index > -1 { - list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) - return nil - } - if entry.Path == "" { - entry.Path = "/" - } - list.Entries = append(list.Entries, entry) - return nil - } - - // if the manifest's path is a prefix of the specified prefix - // then just recurse into the manifest by returning nil and - // continuing the walk - if strings.HasPrefix(prefix, entry.Path) { - return nil - } - - // if the manifest's path has the specified prefix, then if the - // path after the prefix contains a slash, add a common prefix - // to the list and skip the manifest, otherwise recurse into - // the manifest by returning nil and continuing the walk - if strings.HasPrefix(entry.Path, prefix) { - suffix := strings.TrimPrefix(entry.Path, prefix) - if index := strings.Index(suffix, "/"); index > -1 { - list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) - return api.ErrSkipManifest - } - return nil - } - - // the manifest neither has the prefix or needs recursing in to - // so just skip it - return api.ErrSkipManifest - }) - - return list, nil -} - // HandleGetFile handles a GET request to bzz:/// and responds // with the content of the file at from the given -func (s *Server) HandleGetFile(w http.ResponseWriter, r *request.Request) { - log.Debug("handle.get.file", "ruid", r.ruid) +func (s *Server) HandleGetFile(w http.ResponseWriter, r *messages.Request) { + log.Debug("handle.get.file", "ruid", r.Ruid) getFileCount.Inc(1) // ensure the root path has a trailing slash so that relative URLs work - if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { + if r.Uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) return } var err error - manifestAddr := r.uri.Address() + manifestAddr := r.Uri.Address() if manifestAddr == nil { manifestAddr, err = s.api.Resolve(r.uri) if err != nil { getFileFail.Inc(1) - Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) + 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, immutable") // url was of type bzz:///path, so we are sure it is immutable. } - log.Debug("handle.get.file: resolved", "ruid", r.ruid, "key", manifestAddr) + log.Debug("handle.get.file: resolved", "ruid", r.Ruid, "key", manifestAddr) - reader, contentType, status, contentKey, err := s.api.Get(manifestAddr, r.uri.Path) + reader, contentType, status, contentKey, err := s.api.Get(manifestAddr, r.Uri.Path) etag := common.Bytes2Hex(contentKey) noneMatchEtag := r.Header.Get("If-None-Match") @@ -896,7 +841,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *request.Request) { //the request results in ambiguous files //e.g. /read with readme.md and readinglist.txt available in manifest if status == http.StatusMultipleChoices { - list, err := s.getManifestList(manifestAddr, r.uri.Path) + list, err := s.getManifestList(manifestAddr, r.Uri.Path) if err != nil { getFileFail.Inc(1) @@ -904,7 +849,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *request.Request) { return } - log.Debug(fmt.Sprintf("Multiple choices! --> %v", list), "ruid", r.ruid) + log.Debug(fmt.Sprintf("Multiple choices! --> %v", list), "ruid", r.Ruid) //show a nice page links to available entries ShowMultipleChoices(w, r, list) return @@ -954,16 +899,16 @@ func (b bufferedReadSeeker) Seek(offset int64, whence int) (int64, error) { func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { defer metrics.GetOrRegisterResettingTimer(fmt.Sprintf("http.request.%s.time", r.Method), nil).UpdateSince(time.Now()) - req := &Request{Request: *r, ruid: uuid.New()[:8]} + req := &messages.Request{Request: *r, Ruid: uuid.New()[:8]} metrics.GetOrRegisterCounter(fmt.Sprintf("http.request.%s", r.Method), nil).Inc(1) - log.Info("serving request", "ruid", req.ruid, "method", r.Method, "url", r.RequestURI) + log.Info("serving request", "ruid", req.Ruid, "method", r.Method, "url", r.RequestURI) // wrapping the ResponseWriter, so that we get the response code set by http.ServeContent w := newLoggingResponseWriter(rw) if r.RequestURI == "/" && strings.Contains(r.Header.Get("Accept"), "text/html") { - err := landingPageTemplate.Execute(w, nil) + err := views.LandingPageTemplate.Execute(w, nil) if err != nil { log.Error(fmt.Sprintf("error rendering landing page: %s", err)) } @@ -991,7 +936,7 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { req.uri = uri - log.Debug("parsed request path", "ruid", req.ruid, "method", req.Method, "uri.Addr", req.uri.Addr, "uri.Path", req.uri.Path, "uri.Scheme", req.uri.Scheme) + log.Debug("parsed request path", "ruid", req.Ruid, "method", req.Method, "uri.Addr", req.Uri.Addr, "uri.Path", req.Uri.Path, "uri.Scheme", req.Uri.Scheme) switch r.Method { case "POST": @@ -1033,8 +978,8 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { } if uri.List() { - - //ctrl.(w, req) + list := &controllers.BzzListController{api: s.api} + list.Get(w, req) return } @@ -1049,7 +994,7 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { Respond(w, req, fmt.Sprintf("%s method is not supported", r.Method), http.StatusMethodNotAllowed) } - log.Info("served response", "ruid", req.ruid, "code", w.statusCode) + log.Info("served response", "ruid", req.Ruid, "code", w.statusCode) } func (s *Server) updateManifest(addr storage.Address, update func(mw *api.ManifestWriter) error) (storage.Address, error) { diff --git a/swarm/api/http/error.go b/swarm/api/http/views/error.go similarity index 88% rename from swarm/api/http/error.go rename to swarm/api/http/views/error.go index 5fff7575e8..684d3a2d8b 100644 --- a/swarm/api/http/error.go +++ b/swarm/api/http/views/error.go @@ -18,7 +18,7 @@ Show nicely (but simple) formatted HTML error pages (or respond with JSON if the appropriate `Accept` header is set)) for the http package. */ -package http +package views import ( "encoding/json" @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/swarm/api" + "github.com/ethereum/go-ethereum/swarm/api/http/messages" l "github.com/ethereum/go-ethereum/swarm/log" ) @@ -56,8 +57,8 @@ type ResponseParams struct { //a custom error case struct that would be used to store validators and //additional error info to display with client responses. type CaseError struct { - Validator func(*Request) bool - Msg func(*Request) string + Validator func(*messages.Request) bool + Msg func(*messages.Request) string } //we init the error handling right on boot time, so lookup and http response is fast @@ -86,9 +87,11 @@ func initErrHandling() { caseErrors = []CaseError{ { - Validator: func(r *Request) bool { return r.uri != nil && r.uri.Addr != "" && strings.HasPrefix(r.uri.Addr, "0x") }, - Msg: func(r *Request) string { - uriCopy := r.uri + Validator: func(r *messages.Request) bool { + return r.Uri != nil && r.Uri.Addr != "" && strings.HasPrefix(r.Uri.Addr, "0x") + }, + Msg: func(r *messages.Request) string { + uriCopy := r.Uri uriCopy.Addr = strings.TrimPrefix(uriCopy.Addr, "0x") return fmt.Sprintf(`The requested hash seems to be prefixed with '0x'. You will be redirected to the correct URL within 5 seconds.
Please click here if your browser does not redirect you.`, "/"+uriCopy.String()) @@ -98,7 +101,7 @@ func initErrHandling() { //ValidateCaseErrors is a method that process the request object through certain validators //that assert if certain conditions are met for further information to log as an error -func ValidateCaseErrors(r *Request) string { +func ValidateCaseErrors(r *messages.Request) string { for _, err := range caseErrors { if err.Validator(r) { return err.Msg(r) @@ -114,7 +117,7 @@ func ValidateCaseErrors(r *Request) string { //For example, if the user requests bzz://read and that manifest contains entries //"readme.md" and "readinglist.txt", a HTML page is returned with this two links. //This only applies if the manifest has no default entry -func ShowMultipleChoices(w http.ResponseWriter, req *Request, list api.ManifestList) { +func ShowMultipleChoices(w http.ResponseWriter, req *messages.Request, list api.ManifestList) { msg := "" if list.Entries == nil { Respond(w, req, "Could not resolve", http.StatusInternalServerError) @@ -142,13 +145,13 @@ func ShowMultipleChoices(w http.ResponseWriter, req *Request, list api.ManifestL //The function just takes a string message which will be displayed in the error page. //The code is used to evaluate which template will be displayed //(and return the correct HTTP status code) -func Respond(w http.ResponseWriter, req *Request, msg string, code int) { +func Respond(w http.ResponseWriter, req *messages.Request, msg string, code int) { additionalMessage := ValidateCaseErrors(req) switch code { case http.StatusInternalServerError: - log.Output(msg, log.LvlError, l.CallDepth, "ruid", req.ruid, "code", code) + log.Output(msg, log.LvlError, l.CallDepth, "ruid", req.Ruid, "code", code) default: - log.Output(msg, log.LvlDebug, l.CallDepth, "ruid", req.ruid, "code", code) + log.Output(msg, log.LvlDebug, l.CallDepth, "ruid", req.Ruid, "code", code) } if code >= 400 { @@ -161,7 +164,7 @@ func Respond(w http.ResponseWriter, req *Request, msg string, code int) { Msg: msg, Details: template.HTML(additionalMessage), Timestamp: time.Now().Format(time.RFC1123), - template: getTemplate(code), + template: GetTemplate(code), }) } @@ -192,7 +195,7 @@ func respondJSON(w http.ResponseWriter, params *ResponseParams) { } //get the HTML template for a given code -func getTemplate(code int) *template.Template { +func GetTemplate(code int) *template.Template { if val, tmpl := templateMap[code]; tmpl { return val } diff --git a/swarm/api/http/error_templates.go b/swarm/api/http/views/error_templates.go similarity index 99% rename from swarm/api/http/error_templates.go rename to swarm/api/http/views/error_templates.go index f3c643c90d..52739683c6 100644 --- a/swarm/api/http/error_templates.go +++ b/swarm/api/http/views/error_templates.go @@ -24,7 +24,7 @@ they won't be found. For this reason we resort to save the HTML error pages as strings, which then can be parsed by Go's html/template package */ -package http +package views //This returns the HTML for generic errors func GetGenericErrorPage() string { diff --git a/swarm/api/http/templates.go b/swarm/api/http/views/templates.go similarity index 99% rename from swarm/api/http/templates.go rename to swarm/api/http/views/templates.go index ffd8164930..6ad0ab2714 100644 --- a/swarm/api/http/templates.go +++ b/swarm/api/http/views/templates.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package http +package views import ( "html/template" @@ -23,12 +23,12 @@ import ( "github.com/ethereum/go-ethereum/swarm/api" ) -type htmlListData struct { +type HtmlListData struct { URI *api.URI List *api.ManifestList } -var htmlListTemplate = template.Must(template.New("html-list").Funcs(template.FuncMap{"basename": path.Base}).Parse(` +var HtmlListTemplate = template.Must(template.New("html-list").Funcs(template.FuncMap{"basename": path.Base}).Parse(` @@ -71,7 +71,7 @@ var htmlListTemplate = template.Must(template.New("html-list").Funcs(template.Fu `[1:])) -var landingPageTemplate = template.Must(template.New("landingPage").Parse(` +var LandingPageTemplate = template.Must(template.New("landingPage").Parse(`