diff --git a/swarm/api/http/controllers/bzz-hash.go b/swarm/api/http/controllers/bzz-hash.go new file mode 100644 index 0000000000..0cc3cc4673 --- /dev/null +++ b/swarm/api/http/controllers/bzz-hash.go @@ -0,0 +1,16 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . +package controllers diff --git a/swarm/api/http/controllers/bzz-immutable.go b/swarm/api/http/controllers/bzz-immutable.go new file mode 100644 index 0000000000..0cc3cc4673 --- /dev/null +++ b/swarm/api/http/controllers/bzz-immutable.go @@ -0,0 +1,16 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . +package controllers diff --git a/swarm/api/http/controllers/bzz-list.go b/swarm/api/http/controllers/bzz-list.go new file mode 100644 index 0000000000..dbaec57f2f --- /dev/null +++ b/swarm/api/http/controllers/bzz-list.go @@ -0,0 +1,142 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . +package controllers + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "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" + "github.com/ethereum/go-ethereum/swarm/log" + "github.com/ethereum/go-ethereum/swarm/storage" +) + +type BzzListController struct { + Api *api.API + + *Controller +} + +// 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 *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, "/") { + http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) + return + } + + addr, err := controller.Api.Resolve(r.Uri) + if err != nil { + //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) + + list, err := controller.GetManifestList(addr, r.Uri.Path) + + if err != nil { + // getListFail.Inc(1) + controller.Respond(w, r, err.Error(), http.StatusInternalServerError) + return + } + + // if the client wants HTML (e.g. a browser) then render the list as a + // HTML index with relative URLs + if strings.Contains(r.Header.Get("Accept"), "text/html") { + w.Header().Set("Content-Type", "text/html") + err := views.HtmlListTemplate.Execute(w, &views.HtmlListData{ + URI: &api.URI{ + Scheme: "bzz", + Addr: r.Uri.Addr, + Path: r.Uri.Path, + }, + List: &list, + }) + if err != nil { + // getListFail.Inc(1) + log.Error(fmt.Sprintf("error rendering list HTML: %s", err)) + } + return + } + + 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/bzz-raw.go b/swarm/api/http/controllers/bzz-raw.go new file mode 100644 index 0000000000..0cc3cc4673 --- /dev/null +++ b/swarm/api/http/controllers/bzz-raw.go @@ -0,0 +1,16 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . +package controllers diff --git a/swarm/api/http/controllers/bzz-resource.go b/swarm/api/http/controllers/bzz-resource.go new file mode 100644 index 0000000000..0cc3cc4673 --- /dev/null +++ b/swarm/api/http/controllers/bzz-resource.go @@ -0,0 +1,16 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . +package controllers diff --git a/swarm/api/http/controllers/bzz.go b/swarm/api/http/controllers/bzz.go new file mode 100644 index 0000000000..0cc3cc4673 --- /dev/null +++ b/swarm/api/http/controllers/bzz.go @@ -0,0 +1,16 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . +package controllers diff --git a/swarm/api/http/controllers/controller.go b/swarm/api/http/controllers/controller.go new file mode 100644 index 0000000000..e1172ae78a --- /dev/null +++ b/swarm/api/http/controllers/controller.go @@ -0,0 +1,61 @@ +package controllers + +import ( + "html/template" + "net/http" + "time" + + "github.com/ethereum/go-ethereum/log" + "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 { + ControllerHandler +} + +type ControllerHandler interface { + 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" { + views.RespondJSON(w, params) + } else { + views.RespondHTML(w, params) + } +} diff --git a/swarm/api/http/messages/request.go b/swarm/api/http/messages/request.go new file mode 100644 index 0000000000..5ed954c759 --- /dev/null +++ b/swarm/api/http/messages/request.go @@ -0,0 +1,30 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// 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 ( + "net/http" + + "github.com/ethereum/go-ethereum/swarm/api" +) + +// Request wraps http.Request and also includes the parsed bzz URI +type Request struct { + http.Request + + Uri *api.URI + Ruid string // request unique id +} diff --git a/swarm/api/http/messages/response.go b/swarm/api/http/messages/response.go new file mode 100644 index 0000000000..c54af76315 --- /dev/null +++ b/swarm/api/http/messages/response.go @@ -0,0 +1,27 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// 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 ba8b2b7ba9..6e6c5e212c 100644 --- a/swarm/api/http/server.go +++ b/swarm/api/http/server.go @@ -42,6 +42,9 @@ 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/controllers" + "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" "github.com/ethereum/go-ethereum/swarm/storage/mru" @@ -103,58 +106,51 @@ func StartHTTPServer(api *api.API, config *ServerConfig) { } func NewServer(api *api.API) *Server { - return &Server{api} + return &Server{api: api} } type Server struct { + controllers.Controller api *api.API } -// Request wraps http.Request and also includes the parsed bzz URI -type Request struct { - http.Request - - uri *api.URI - ruid string // request unique id -} - // 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) { - 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) + s.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) + s.Respond(w, r, "raw POST request addr can only be empty or \"encrypt\"", http.StatusBadRequest) return } if r.Header.Get("Content-Length") == "" { postRawFail.Inc(1) - Respond(w, r, "missing Content-Length header in request", http.StatusBadRequest) + s.Respond(w, r, "missing Content-Length header in request", http.StatusBadRequest) return } addr, _, err := s.api.Store(r.Body, r.ContentLength, toEncrypt) if err != nil { postRawFail.Inc(1) - Respond(w, r, err.Error(), http.StatusInternalServerError) + s.Respond(w, r, err.Error(), http.StatusInternalServerError) 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) @@ -166,39 +162,39 @@ func (s *Server) HandlePostRaw(w http.ResponseWriter, r *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) { - 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")) if err != nil { postFilesFail.Inc(1) - Respond(w, r, err.Error(), http.StatusBadRequest) + s.Respond(w, r, err.Error(), http.StatusBadRequest) return } 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" { - addr, err = s.api.Resolve(r.uri) + 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) + s.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 { postFilesFail.Inc(1) - Respond(w, r, err.Error(), http.StatusInternalServerError) + s.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 { @@ -216,19 +212,19 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *Request) { }) if err != nil { postFilesFail.Inc(1) - Respond(w, r, fmt.Sprintf("cannot create manifest: %s", err), http.StatusInternalServerError) + s.Respond(w, r, fmt.Sprintf("cannot create manifest: %s", err), http.StatusInternalServerError) 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, 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() @@ -244,7 +240,7 @@ func (s *Server) handleTarUpload(req *Request, mw *api.ManifestWriter) error { } // 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"], @@ -252,17 +248,17 @@ func (s *Server) handleTarUpload(req *Request, mw *api.ManifestWriter) error { 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, 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() @@ -303,26 +299,26 @@ func (s *Server) handleMultipartUpload(req *Request, boundary string, mw *api.Ma 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, 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, @@ -331,31 +327,31 @@ func (s *Server) handleDirectUpload(req *Request, mw *api.ManifestWriter) error 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) { - 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) + 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) + s.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) - Respond(w, r, fmt.Sprintf("cannot update manifest: %s", err), http.StatusInternalServerError) + s.Respond(w, r, fmt.Sprintf("cannot update manifest: %s", err), http.StatusInternalServerError) return } @@ -399,29 +395,29 @@ 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) { - 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) + s.Respond(w, r, err.Error(), http.StatusBadRequest) return } // 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) if err != nil { code, err2 := s.translateResourceError(w, r, "resource creation fail", err) - Respond(w, r, err2.Error(), code) + s.Respond(w, r, err2.Error(), code) return } @@ -430,7 +426,7 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) { // root chunk m, err := s.api.NewResourceManifest(addr.Hex()) if err != nil { - Respond(w, r, fmt.Sprintf("failed to create resource manifest: %v", err), http.StatusInternalServerError) + s.Respond(w, r, fmt.Sprintf("failed to create resource manifest: %v", err), http.StatusInternalServerError) return } @@ -440,18 +436,18 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) { // \TODO update manifest key automatically in ENS outdata, err = json.Marshal(m) if err != nil { - Respond(w, r, fmt.Sprintf("failed to create json response: %s", err), http.StatusInternalServerError) + s.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() + manifestAddr := r.Uri.Address() if manifestAddr == nil { - manifestAddr, err = s.api.Resolve(r.uri) + 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) + s.Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.Uri.Addr, err), http.StatusNotFound) return } } else { @@ -462,15 +458,15 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *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) + s.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 { - Respond(w, r, err.Error(), http.StatusNotFound) + s.Respond(w, r, err.Error(), http.StatusNotFound) return } } @@ -478,7 +474,7 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) { // 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) + s.Respond(w, r, err.Error(), http.StatusInternalServerError) return } @@ -486,18 +482,18 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) { if isRaw { _, _, _, err = s.api.ResourceUpdate(r.Context(), name, data) if err != nil { - Respond(w, r, err.Error(), http.StatusBadRequest) + s.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) + s.Respond(w, r, err.Error(), http.StatusBadRequest) return } _, _, _, err = s.api.ResourceUpdateMultihash(r.Context(), name, bytesdata) if err != nil { - Respond(w, r, err.Error(), http.StatusBadRequest) + s.Respond(w, r, err.Error(), http.StatusBadRequest) return } } @@ -518,22 +514,22 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *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) { +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) { - 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) + 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) + s.Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.Uri.Addr, err), http.StatusNotFound) return } } else { @@ -544,16 +540,16 @@ func (s *Server) handleGetResource(w http.ResponseWriter, r *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) + s.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 @@ -587,17 +583,17 @@ func (s *Server) handleGetResource(w http.ResponseWriter, r *Request) { // any error from the switch statement will end up here if err != nil { code, err2 := s.translateResourceError(w, r, "mutable resource lookup fail", err) - Respond(w, r, err2.Error(), code) + s.Respond(w, r, err2.Error(), code) return } // 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, 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) @@ -623,38 +619,38 @@ func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supEr // 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) { - 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) + 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) + s.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) - Respond(w, r, fmt.Sprintf("%s is not a manifest", addr), http.StatusBadRequest) + s.Respond(w, r, fmt.Sprintf("%s is not a manifest", addr), http.StatusBadRequest) return } var entry *api.ManifestEntry 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") @@ -668,7 +664,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *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 } @@ -676,7 +672,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { }) if entry == nil { getFail.Inc(1) - Respond(w, r, fmt.Sprintf("manifest entry could not be loaded"), http.StatusNotFound) + s.Respond(w, r, fmt.Sprintf("manifest entry could not be loaded"), http.StatusNotFound) return } addr = storage.Address(common.Hex2Bytes(entry.Hash)) @@ -686,7 +682,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key. if noneMatchEtag != "" { if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), addr) { - Respond(w, r, "Not Modified", http.StatusNotModified) + s.Respond(w, r, "Not Modified", http.StatusNotModified) return } } @@ -695,14 +691,14 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { reader, isEncrypted := s.api.Retrieve(addr) if _, err := reader.Size(nil); err != nil { getFail.Inc(1) - Respond(w, r, fmt.Sprintf("root chunk not found %s: %s", addr, err), http.StatusNotFound) + s.Respond(w, r, fmt.Sprintf("root chunk not found %s: %s", addr, err), http.StatusNotFound) return } 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" @@ -711,7 +707,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *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) @@ -721,27 +717,27 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *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) { - 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) + s.Respond(w, r, "files request cannot contain a path", http.StatusBadRequest) return } - addr, err := s.api.Resolve(r.uri) + 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) + s.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 { getFilesFail.Inc(1) - Respond(w, r, err.Error(), http.StatusInternalServerError) + s.Respond(w, r, err.Error(), http.StatusInternalServerError) return } @@ -794,147 +790,40 @@ func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) { } } -// HandleGetList 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 (s *Server) HandleGetList(w http.ResponseWriter, r *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, "/") { - http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) - return - } - - addr, err := s.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) - return - } - log.Debug("handle.get.list: resolved", "ruid", r.ruid, "key", addr) - - list, err := s.getManifestList(addr, r.uri.Path) - - if err != nil { - getListFail.Inc(1) - Respond(w, r, err.Error(), http.StatusInternalServerError) - return - } - - // if the client wants HTML (e.g. a browser) then render the list as a - // 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{ - Scheme: "bzz", - Addr: r.uri.Addr, - Path: r.uri.Path, - }, - List: &list, - }) - if err != nil { - getListFail.Inc(1) - log.Error(fmt.Sprintf("error rendering list HTML: %s", err)) - } - return - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(&list) -} - -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) { - 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) + 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) + s.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") w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to actual content key. if noneMatchEtag != "" { if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), contentKey) { - Respond(w, r, "Not Modified", http.StatusNotModified) + s.Respond(w, r, "Not Modified", http.StatusNotModified) return } } @@ -943,10 +832,10 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) { switch status { case http.StatusNotFound: getFileNotFound.Inc(1) - Respond(w, r, err.Error(), http.StatusNotFound) + s.Respond(w, r, err.Error(), http.StatusNotFound) default: getFileFail.Inc(1) - Respond(w, r, err.Error(), http.StatusInternalServerError) + s.Respond(w, r, err.Error(), http.StatusInternalServerError) } return } @@ -954,24 +843,26 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *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) + listCtrl := controllers.BzzListController{Api: s.api} + + list, err := listCtrl.GetManifestList(manifestAddr, r.Uri.Path) if err != nil { getFileFail.Inc(1) - Respond(w, r, err.Error(), http.StatusInternalServerError) + s.Respond(w, r, err.Error(), http.StatusInternalServerError) 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) + views.ShowMultipleChoices(w, r, list) return } // check the root chunk exists by retrieving the file's size if _, err := reader.Size(nil); err != nil { getFileNotFound.Inc(1) - Respond(w, r, fmt.Sprintf("file not found %s: %s", r.uri, err), http.StatusNotFound) + s.Respond(w, r, fmt.Sprintf("file not found %s: %s", r.Uri, err), http.StatusNotFound) return } @@ -1012,16 +903,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)) } @@ -1043,13 +934,13 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/")) if err != nil { - Respond(w, req, fmt.Sprintf("invalid URI %q", r.URL.Path), http.StatusBadRequest) + s.Respond(w, req, fmt.Sprintf("invalid URI %q", r.URL.Path), http.StatusBadRequest) return } - req.uri = uri + 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": @@ -1061,19 +952,19 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { s.HandlePostResource(w, req) } else if uri.Immutable() || uri.List() || uri.Hash() { log.Debug("POST not allowed on immutable, list or hash") - Respond(w, req, fmt.Sprintf("POST method on scheme %s not allowed", uri.Scheme), http.StatusMethodNotAllowed) + s.Respond(w, req, fmt.Sprintf("POST method on scheme %s not allowed", uri.Scheme), http.StatusMethodNotAllowed) } else { log.Debug("handlePostFiles") s.HandlePostFiles(w, req) } case "PUT": - Respond(w, req, fmt.Sprintf("PUT method to %s not allowed", uri), http.StatusBadRequest) + s.Respond(w, req, fmt.Sprintf("PUT method to %s not allowed", uri), http.StatusBadRequest) return case "DELETE": if uri.Raw() { - Respond(w, req, fmt.Sprintf("DELETE method to %s not allowed", uri), http.StatusBadRequest) + s.Respond(w, req, fmt.Sprintf("DELETE method to %s not allowed", uri), http.StatusBadRequest) return } s.HandleDelete(w, req) @@ -1091,7 +982,8 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { } if uri.List() { - s.HandleGetList(w, req) + list := &controllers.BzzListController{Api: s.api} + list.Get(w, req) return } @@ -1103,10 +995,10 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { s.HandleGetFile(w, req) default: - Respond(w, req, fmt.Sprintf("%s method is not supported", r.Method), http.StatusMethodNotAllowed) + s.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 83% rename from swarm/api/http/error.go rename to swarm/api/http/views/error.go index 5fff7575e8..b1e33f8220 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 { @@ -156,43 +159,43 @@ func Respond(w http.ResponseWriter, req *Request, msg string, code int) { w.Header().Del("ETag") } - respond(w, &req.Request, &ResponseParams{ + respond(w, &req.Request, &messages.ResponseParams{ Code: code, Msg: msg, Details: template.HTML(additionalMessage), Timestamp: time.Now().Format(time.RFC1123), - template: getTemplate(code), + Template: GetTemplate(code), }) } //evaluate if client accepts html or json response -func respond(w http.ResponseWriter, r *http.Request, params *ResponseParams) { +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) + RespondJSON(w, params) } else { - respondHTML(w, params) + RespondHTML(w, params) } } //return a HTML page -func respondHTML(w http.ResponseWriter, params *ResponseParams) { +func RespondHTML(w http.ResponseWriter, params *messages.ResponseParams) { htmlCounter.Inc(1) - err := params.template.Execute(w, params) + err := params.Template.Execute(w, params) if err != nil { log.Error(err.Error()) } } //return JSON -func respondJSON(w http.ResponseWriter, params *ResponseParams) { +func RespondJSON(w http.ResponseWriter, params *messages.ResponseParams) { jsonCounter.Inc(1) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(params) } //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/views/error.tmpl b/swarm/api/http/views/error.tmpl new file mode 100644 index 0000000000..e69de29bb2 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(`