This commit is contained in:
Elad 2018-06-28 14:18:01 +00:00 committed by GitHub
commit ddf24c0816
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 501 additions and 266 deletions

View file

@ -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 <http://www.gnu.org/licenses/>.
package controllers

View file

@ -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 <http://www.gnu.org/licenses/>.
package controllers

View file

@ -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 <http://www.gnu.org/licenses/>.
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:/<manifest>/<path> and returns
// a list of all files contained in <manifest> under <path> 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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
package controllers

View file

@ -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 <http://www.gnu.org/licenses/>.
package controllers

View file

@ -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 <http://www.gnu.org/licenses/>.
package controllers

View file

@ -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)
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -42,6 +42,9 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/swarm/api" "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/log"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/swarm/storage/mru" "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 { func NewServer(api *api.API) *Server {
return &Server{api} return &Server{api: api}
} }
type Server struct { type Server struct {
controllers.Controller
api *api.API 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 // 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 // body in swarm and returns the resulting storage address as a text/plain response
func (s *Server) HandlePostRaw(w http.ResponseWriter, r *Request) { func (s *Server) HandlePostRaw(w http.ResponseWriter, r *messages.Request) {
log.Debug("handle.post.raw", "ruid", r.ruid) log.Debug("handle.post.raw", "ruid", r.Ruid)
postRawCount.Inc(1) postRawCount.Inc(1)
toEncrypt := false toEncrypt := false
if r.uri.Addr == "encrypt" { if r.Uri.Addr == "encrypt" {
toEncrypt = true toEncrypt = true
} }
if r.uri.Path != "" { if r.Uri.Path != "" {
postRawFail.Inc(1) 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 return
} }
if r.uri.Addr != "" && r.uri.Addr != "encrypt" { if r.Uri.Addr != "" && r.Uri.Addr != "encrypt" {
postRawFail.Inc(1) 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 return
} }
if r.Header.Get("Content-Length") == "" { if r.Header.Get("Content-Length") == "" {
postRawFail.Inc(1) 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 return
} }
addr, _, err := s.api.Store(r.Body, r.ContentLength, toEncrypt) addr, _, err := s.api.Store(r.Body, r.ContentLength, toEncrypt)
if err != nil { if err != nil {
postRawFail.Inc(1) postRawFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError) s.Respond(w, r, err.Error(), http.StatusInternalServerError)
return 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.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK) 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 // (either a tar archive or multipart form), adds those files either to an
// existing manifest or to a new manifest under <path> and returns the // existing manifest or to a new manifest under <path> and returns the
// resulting manifest hash as a text/plain response // resulting manifest hash as a text/plain response
func (s *Server) HandlePostFiles(w http.ResponseWriter, r *Request) { func (s *Server) HandlePostFiles(w http.ResponseWriter, r *messages.Request) {
log.Debug("handle.post.files", "ruid", r.ruid) log.Debug("handle.post.files", "ruid", r.Ruid)
postFilesCount.Inc(1) postFilesCount.Inc(1)
contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")) contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil { if err != nil {
postFilesFail.Inc(1) postFilesFail.Inc(1)
Respond(w, r, err.Error(), http.StatusBadRequest) s.Respond(w, r, err.Error(), http.StatusBadRequest)
return return
} }
toEncrypt := false toEncrypt := false
if r.uri.Addr == "encrypt" { if r.Uri.Addr == "encrypt" {
toEncrypt = true toEncrypt = true
} }
var addr storage.Address 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) addr, err = s.api.Resolve(r.Uri)
if err != nil { if err != nil {
postFilesFail.Inc(1) 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 return
} }
log.Debug("resolved key", "ruid", r.ruid, "key", addr) log.Debug("resolved key", "ruid", r.Ruid, "key", addr)
} else { } else {
addr, err = s.api.NewManifest(toEncrypt) addr, err = s.api.NewManifest(toEncrypt)
if err != nil { if err != nil {
postFilesFail.Inc(1) postFilesFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError) s.Respond(w, r, err.Error(), http.StatusInternalServerError)
return 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 { 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 { if err != nil {
postFilesFail.Inc(1) 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 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.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
fmt.Fprint(w, newAddr) fmt.Fprint(w, newAddr)
} }
func (s *Server) handleTarUpload(req *Request, mw *api.ManifestWriter) error { func (s *Server) handleTarUpload(req *messages.Request, mw *api.ManifestWriter) error {
log.Debug("handle.tar.upload", "ruid", req.ruid) log.Debug("handle.tar.upload", "ruid", req.Ruid)
tr := tar.NewReader(req.Body) tr := tar.NewReader(req.Body)
for { for {
hdr, err := tr.Next() 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 // 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{ entry := &api.ManifestEntry{
Path: path, Path: path,
ContentType: hdr.Xattrs["user.swarm.content-type"], ContentType: hdr.Xattrs["user.swarm.content-type"],
@ -252,17 +248,17 @@ func (s *Server) handleTarUpload(req *Request, mw *api.ManifestWriter) error {
Size: hdr.Size, Size: hdr.Size,
ModTime: hdr.ModTime, 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) contentKey, err := mw.AddEntry(tr, entry)
if err != nil { if err != nil {
return fmt.Errorf("error adding manifest entry from tar stream: %s", err) 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 { func (s *Server) handleMultipartUpload(req *messages.Request, boundary string, mw *api.ManifestWriter) error {
log.Debug("handle.multipart.upload", "ruid", req.ruid) log.Debug("handle.multipart.upload", "ruid", req.Ruid)
mr := multipart.NewReader(req.Body, boundary) mr := multipart.NewReader(req.Body, boundary)
for { for {
part, err := mr.NextPart() part, err := mr.NextPart()
@ -303,26 +299,26 @@ func (s *Server) handleMultipartUpload(req *Request, boundary string, mw *api.Ma
if name == "" { if name == "" {
name = part.FormName() name = part.FormName()
} }
path := path.Join(req.uri.Path, name) path := path.Join(req.Uri.Path, name)
entry := &api.ManifestEntry{ entry := &api.ManifestEntry{
Path: path, Path: path,
ContentType: part.Header.Get("Content-Type"), ContentType: part.Header.Get("Content-Type"),
Size: size, Size: size,
ModTime: time.Now(), 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) contentKey, err := mw.AddEntry(reader, entry)
if err != nil { if err != nil {
return fmt.Errorf("error adding manifest entry from multipart form: %s", err) 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 { func (s *Server) handleDirectUpload(req *messages.Request, mw *api.ManifestWriter) error {
log.Debug("handle.direct.upload", "ruid", req.ruid) log.Debug("handle.direct.upload", "ruid", req.Ruid)
key, err := mw.AddEntry(req.Body, &api.ManifestEntry{ key, err := mw.AddEntry(req.Body, &api.ManifestEntry{
Path: req.uri.Path, Path: req.Uri.Path,
ContentType: req.Header.Get("Content-Type"), ContentType: req.Header.Get("Content-Type"),
Mode: 0644, Mode: 0644,
Size: req.ContentLength, Size: req.ContentLength,
@ -331,31 +327,31 @@ func (s *Server) handleDirectUpload(req *Request, mw *api.ManifestWriter) error
if err != nil { if err != nil {
return err return err
} }
log.Debug("stored content", "ruid", req.ruid, "key", key) log.Debug("stored content", "ruid", req.Ruid, "key", key)
return nil return nil
} }
// HandleDelete handles a DELETE request to bzz:/<manifest>/<path>, removes // HandleDelete handles a DELETE request to bzz:/<manifest>/<path>, removes
// <path> from <manifest> and returns the resulting manifest hash as a // <path> from <manifest> and returns the resulting manifest hash as a
// text/plain response // text/plain response
func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) { func (s *Server) HandleDelete(w http.ResponseWriter, r *messages.Request) {
log.Debug("handle.delete", "ruid", r.ruid) log.Debug("handle.delete", "ruid", r.Ruid)
deleteCount.Inc(1) deleteCount.Inc(1)
key, err := s.api.Resolve(r.uri) key, err := s.api.Resolve(r.Uri)
if err != nil { if err != nil {
deleteFail.Inc(1) 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 return
} }
newKey, err := s.updateManifest(key, func(mw *api.ManifestWriter) error { 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) log.Debug(fmt.Sprintf("removing %s from manifest %s", r.Uri.Path, key.Log()), "ruid", r.Ruid)
return mw.RemoveEntry(r.uri.Path) return mw.RemoveEntry(r.Uri.Path)
}) })
if err != nil { if err != nil {
deleteFail.Inc(1) 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 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. // 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 // 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 // and name "foo.eth" will be created
func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) { func (s *Server) HandlePostResource(w http.ResponseWriter, r *messages.Request) {
log.Debug("handle.post.resource", "ruid", r.ruid) log.Debug("handle.post.resource", "ruid", r.Ruid)
var err error var err error
var addr storage.Address var addr storage.Address
var name string var name string
var outdata []byte var outdata []byte
isRaw, frequency, err := resourcePostMode(r.uri.Path) isRaw, frequency, err := resourcePostMode(r.Uri.Path)
if err != nil { if err != nil {
Respond(w, r, err.Error(), http.StatusBadRequest) s.Respond(w, r, err.Error(), http.StatusBadRequest)
return return
} }
// new mutable resource creation will always have a frequency field larger than 0 // new mutable resource creation will always have a frequency field larger than 0
if frequency > 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 // the key is the content addressed root chunk holding mutable resource metadata information
addr, err = s.api.ResourceCreate(r.Context(), name, frequency) addr, err = s.api.ResourceCreate(r.Context(), name, frequency)
if err != nil { if err != nil {
code, err2 := s.translateResourceError(w, r, "resource creation fail", err) code, err2 := s.translateResourceError(w, r, "resource creation fail", err)
Respond(w, r, err2.Error(), code) s.Respond(w, r, err2.Error(), code)
return return
} }
@ -430,7 +426,7 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
// root chunk // root chunk
m, err := s.api.NewResourceManifest(addr.Hex()) m, err := s.api.NewResourceManifest(addr.Hex())
if err != nil { 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 return
} }
@ -440,18 +436,18 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
// \TODO update manifest key automatically in ENS // \TODO update manifest key automatically in ENS
outdata, err = json.Marshal(m) outdata, err = json.Marshal(m)
if err != nil { 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 return
} }
} else { } else {
// to update the resource through http we need to retrieve the key for the mutable resource root chunk // 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. // that means that we retrieve the manifest and inspect its Hash member.
manifestAddr := r.uri.Address() manifestAddr := r.Uri.Address()
if manifestAddr == nil { if manifestAddr == nil {
manifestAddr, err = s.api.Resolve(r.uri) manifestAddr, err = s.api.Resolve(r.Uri)
if err != nil { if err != nil {
getFail.Inc(1) 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 return
} }
} else { } else {
@ -462,15 +458,15 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
addr, err = s.api.ResolveResourceManifest(manifestAddr) addr, err = s.api.ResolveResourceManifest(manifestAddr)
if err != nil { if err != nil {
getFail.Inc(1) 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 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{}) name, _, err = s.api.ResourceLookup(r.Context(), addr, 0, 0, &mru.LookupParams{})
if err != nil { if err != nil {
Respond(w, r, err.Error(), http.StatusNotFound) s.Respond(w, r, err.Error(), http.StatusNotFound)
return 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. // Creation and update must send data aswell. This data constitutes the update data itself.
data, err := ioutil.ReadAll(r.Body) data, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
Respond(w, r, err.Error(), http.StatusInternalServerError) s.Respond(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
@ -486,18 +482,18 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
if isRaw { if isRaw {
_, _, _, err = s.api.ResourceUpdate(r.Context(), name, data) _, _, _, err = s.api.ResourceUpdate(r.Context(), name, data)
if err != nil { if err != nil {
Respond(w, r, err.Error(), http.StatusBadRequest) s.Respond(w, r, err.Error(), http.StatusBadRequest)
return return
} }
} else { } else {
bytesdata, err := hexutil.Decode(string(data)) bytesdata, err := hexutil.Decode(string(data))
if err != nil { if err != nil {
Respond(w, r, err.Error(), http.StatusBadRequest) s.Respond(w, r, err.Error(), http.StatusBadRequest)
return return
} }
_, _, _, err = s.api.ResourceUpdateMultihash(r.Context(), name, bytesdata) _, _, _, err = s.api.ResourceUpdateMultihash(r.Context(), name, bytesdata)
if err != nil { if err != nil {
Respond(w, r, err.Error(), http.StatusBadRequest) s.Respond(w, r, err.Error(), http.StatusBadRequest)
return return
} }
} }
@ -518,22 +514,22 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
// bzz-resource://<id>/<n> - get latest update on period n // bzz-resource://<id>/<n> - get latest update on period n
// bzz-resource://<id>/<n>/<m> - get update version m of period n // bzz-resource://<id>/<n>/<m> - get update version m of period n
// <id> = ens name or hash // <id> = 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) s.handleGetResource(w, r)
} }
// TODO: Enable pass maxPeriod parameter // TODO: Enable pass maxPeriod parameter
func (s *Server) handleGetResource(w http.ResponseWriter, r *Request) { func (s *Server) handleGetResource(w http.ResponseWriter, r *messages.Request) {
log.Debug("handle.get.resource", "ruid", r.ruid) log.Debug("handle.get.resource", "ruid", r.Ruid)
var err error var err error
// resolve the content key. // resolve the content key.
manifestAddr := r.uri.Address() manifestAddr := r.Uri.Address()
if manifestAddr == nil { if manifestAddr == nil {
manifestAddr, err = s.api.Resolve(r.uri) manifestAddr, err = s.api.Resolve(r.Uri)
if err != nil { if err != nil {
getFail.Inc(1) 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 return
} }
} else { } else {
@ -544,16 +540,16 @@ func (s *Server) handleGetResource(w http.ResponseWriter, r *Request) {
key, err := s.api.ResolveResourceManifest(manifestAddr) key, err := s.api.ResolveResourceManifest(manifestAddr)
if err != nil { if err != nil {
getFail.Inc(1) 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 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 // determine if the query specifies period and version
var params []string var params []string
if len(r.uri.Path) > 0 { if len(r.Uri.Path) > 0 {
params = strings.Split(r.uri.Path, "/") params = strings.Split(r.Uri.Path, "/")
} }
var name string var name string
var period uint64 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 // any error from the switch statement will end up here
if err != nil { if err != nil {
code, err2 := s.translateResourceError(w, r, "mutable resource lookup fail", err) 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 return
} }
// All ok, serve the retrieved update // 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") w.Header().Set("Content-Type", "application/octet-stream")
http.ServeContent(w, &r.Request, "", now, bytes.NewReader(data)) 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 code := 0
defaultErr := fmt.Errorf("%s: %v", supErr, err) defaultErr := fmt.Errorf("%s: %v", supErr, err)
rsrcErr, ok := err.(*mru.Error) rsrcErr, ok := err.(*mru.Error)
@ -623,38 +619,38 @@ func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supEr
// given storage key // given storage key
// - bzz-hash://<key> and responds with the hash of the content stored // - bzz-hash://<key> and responds with the hash of the content stored
// at the given storage key as a text/plain response // at the given storage key as a text/plain response
func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { func (s *Server) HandleGet(w http.ResponseWriter, r *messages.Request) {
log.Debug("handle.get", "ruid", r.ruid, "uri", r.uri) log.Debug("handle.get", "ruid", r.Ruid, "uri", r.Uri)
getCount.Inc(1) getCount.Inc(1)
var err error var err error
addr := r.uri.Address() addr := r.Uri.Address()
if addr == nil { if addr == nil {
addr, err = s.api.Resolve(r.uri) addr, err = s.api.Resolve(r.Uri)
if err != nil { if err != nil {
getFail.Inc(1) 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 return
} }
} else { } else {
w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable. w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/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 <key> as a manifest and return the // if path is set, interpret <key> as a manifest and return the
// raw entry at the given path // raw entry at the given path
if r.uri.Path != "" { if r.Uri.Path != "" {
walker, err := s.api.NewManifestWalker(addr, nil) walker, err := s.api.NewManifestWalker(addr, nil)
if err != nil { if err != nil {
getFail.Inc(1) 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 return
} }
var entry *api.ManifestEntry var entry *api.ManifestEntry
walker.Walk(func(e *api.ManifestEntry) error { walker.Walk(func(e *api.ManifestEntry) error {
// if the entry matches the path, set entry and stop // if the entry matches the path, set entry and stop
// the walk // the walk
if e.Path == r.uri.Path { if e.Path == r.Uri.Path {
entry = e entry = e
// return an error to cancel the walk // return an error to cancel the walk
return errors.New("found") 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 // if the manifest's path is a prefix of the
// requested path, recurse into it by returning // requested path, recurse into it by returning
// nil and continuing the walk // nil and continuing the walk
if strings.HasPrefix(r.uri.Path, e.Path) { if strings.HasPrefix(r.Uri.Path, e.Path) {
return nil return nil
} }
@ -676,7 +672,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
}) })
if entry == nil { if entry == nil {
getFail.Inc(1) 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 return
} }
addr = storage.Address(common.Hex2Bytes(entry.Hash)) 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. w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key.
if noneMatchEtag != "" { if noneMatchEtag != "" {
if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), addr) { 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 return
} }
} }
@ -695,14 +691,14 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
reader, isEncrypted := s.api.Retrieve(addr) reader, isEncrypted := s.api.Retrieve(addr)
if _, err := reader.Size(nil); err != nil { if _, err := reader.Size(nil); err != nil {
getFail.Inc(1) 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 return
} }
w.Header().Set("X-Decrypted", fmt.Sprintf("%v", isEncrypted)) w.Header().Set("X-Decrypted", fmt.Sprintf("%v", isEncrypted))
switch { switch {
case r.uri.Raw(): case r.Uri.Raw():
// allow the request to overwrite the content type using a query // allow the request to overwrite the content type using a query
// parameter // parameter
contentType := "application/octet-stream" contentType := "application/octet-stream"
@ -711,7 +707,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
} }
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", contentType)
http.ServeContent(w, &r.Request, "", time.Now(), reader) http.ServeContent(w, &r.Request, "", time.Now(), reader)
case r.uri.Hash(): case r.Uri.Hash():
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
fmt.Fprint(w, addr) fmt.Fprint(w, addr)
@ -721,27 +717,27 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
// HandleGetFiles handles a GET request to bzz:/<manifest> with an Accept // HandleGetFiles handles a GET request to bzz:/<manifest> with an Accept
// header of "application/x-tar" and returns a tar stream of all files // header of "application/x-tar" and returns a tar stream of all files
// contained in the manifest // contained in the manifest
func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) { func (s *Server) HandleGetFiles(w http.ResponseWriter, r *messages.Request) {
log.Debug("handle.get.files", "ruid", r.ruid, "uri", r.uri) log.Debug("handle.get.files", "ruid", r.Ruid, "uri", r.Uri)
getFilesCount.Inc(1) getFilesCount.Inc(1)
if r.uri.Path != "" { if r.Uri.Path != "" {
getFilesFail.Inc(1) 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 return
} }
addr, err := s.api.Resolve(r.uri) addr, err := s.api.Resolve(r.Uri)
if err != nil { if err != nil {
getFilesFail.Inc(1) 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 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) walker, err := s.api.NewManifestWalker(addr, nil)
if err != nil { if err != nil {
getFilesFail.Inc(1) getFilesFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError) s.Respond(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
@ -794,147 +790,40 @@ func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) {
} }
} }
// HandleGetList handles a GET request to bzz-list:/<manifest>/<path> and returns
// a list of all files contained in <manifest> under <path> 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://<manifest>/<path> and responds // HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds
// with the content of the file at <path> from the given <manifest> // with the content of the file at <path> from the given <manifest>
func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) { func (s *Server) HandleGetFile(w http.ResponseWriter, r *messages.Request) {
log.Debug("handle.get.file", "ruid", r.ruid) log.Debug("handle.get.file", "ruid", r.Ruid)
getFileCount.Inc(1) getFileCount.Inc(1)
// ensure the root path has a trailing slash so that relative URLs work // 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) http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently)
return return
} }
var err error var err error
manifestAddr := r.uri.Address() manifestAddr := r.Uri.Address()
if manifestAddr == nil { if manifestAddr == nil {
manifestAddr, err = s.api.Resolve(r.uri) manifestAddr, err = s.api.Resolve(r.Uri)
if err != nil { if err != nil {
getFileFail.Inc(1) 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 return
} }
} else { } else {
w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable. w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/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) etag := common.Bytes2Hex(contentKey)
noneMatchEtag := r.Header.Get("If-None-Match") noneMatchEtag := r.Header.Get("If-None-Match")
w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to actual content key. w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to actual content key.
if noneMatchEtag != "" { if noneMatchEtag != "" {
if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), contentKey) { 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 return
} }
} }
@ -943,10 +832,10 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
switch status { switch status {
case http.StatusNotFound: case http.StatusNotFound:
getFileNotFound.Inc(1) getFileNotFound.Inc(1)
Respond(w, r, err.Error(), http.StatusNotFound) s.Respond(w, r, err.Error(), http.StatusNotFound)
default: default:
getFileFail.Inc(1) getFileFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError) s.Respond(w, r, err.Error(), http.StatusInternalServerError)
} }
return return
} }
@ -954,24 +843,26 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
//the request results in ambiguous files //the request results in ambiguous files
//e.g. /read with readme.md and readinglist.txt available in manifest //e.g. /read with readme.md and readinglist.txt available in manifest
if status == http.StatusMultipleChoices { 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 { if err != nil {
getFileFail.Inc(1) getFileFail.Inc(1)
Respond(w, r, err.Error(), http.StatusInternalServerError) s.Respond(w, r, err.Error(), http.StatusInternalServerError)
return 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 //show a nice page links to available entries
ShowMultipleChoices(w, r, list) views.ShowMultipleChoices(w, r, list)
return return
} }
// check the root chunk exists by retrieving the file's size // check the root chunk exists by retrieving the file's size
if _, err := reader.Size(nil); err != nil { if _, err := reader.Size(nil); err != nil {
getFileNotFound.Inc(1) 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 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) { 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()) 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) 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 // wrapping the ResponseWriter, so that we get the response code set by http.ServeContent
w := newLoggingResponseWriter(rw) w := newLoggingResponseWriter(rw)
if r.RequestURI == "/" && strings.Contains(r.Header.Get("Accept"), "text/html") { 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 { if err != nil {
log.Error(fmt.Sprintf("error rendering landing page: %s", err)) 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, "/")) uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/"))
if err != nil { 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 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 { switch r.Method {
case "POST": case "POST":
@ -1061,19 +952,19 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
s.HandlePostResource(w, req) s.HandlePostResource(w, req)
} else if uri.Immutable() || uri.List() || uri.Hash() { } else if uri.Immutable() || uri.List() || uri.Hash() {
log.Debug("POST not allowed on immutable, list or 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 { } else {
log.Debug("handlePostFiles") log.Debug("handlePostFiles")
s.HandlePostFiles(w, req) s.HandlePostFiles(w, req)
} }
case "PUT": 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 return
case "DELETE": case "DELETE":
if uri.Raw() { 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 return
} }
s.HandleDelete(w, req) s.HandleDelete(w, req)
@ -1091,7 +982,8 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
} }
if uri.List() { if uri.List() {
s.HandleGetList(w, req) list := &controllers.BzzListController{Api: s.api}
list.Get(w, req)
return return
} }
@ -1103,10 +995,10 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
s.HandleGetFile(w, req) s.HandleGetFile(w, req)
default: 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) { func (s *Server) updateManifest(addr storage.Address, update func(mw *api.ManifestWriter) error) (storage.Address, error) {

View file

@ -18,7 +18,7 @@
Show nicely (but simple) formatted HTML error pages (or respond with JSON Show nicely (but simple) formatted HTML error pages (or respond with JSON
if the appropriate `Accept` header is set)) for the http package. if the appropriate `Accept` header is set)) for the http package.
*/ */
package http package views
import ( import (
"encoding/json" "encoding/json"
@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
"github.com/ethereum/go-ethereum/swarm/api/http/messages"
l "github.com/ethereum/go-ethereum/swarm/log" 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 //a custom error case struct that would be used to store validators and
//additional error info to display with client responses. //additional error info to display with client responses.
type CaseError struct { type CaseError struct {
Validator func(*Request) bool Validator func(*messages.Request) bool
Msg func(*Request) string Msg func(*messages.Request) string
} }
//we init the error handling right on boot time, so lookup and http response is fast //we init the error handling right on boot time, so lookup and http response is fast
@ -86,9 +87,11 @@ func initErrHandling() {
caseErrors = []CaseError{ caseErrors = []CaseError{
{ {
Validator: func(r *Request) bool { return r.uri != nil && r.uri.Addr != "" && strings.HasPrefix(r.uri.Addr, "0x") }, Validator: func(r *messages.Request) bool {
Msg: func(r *Request) string { return r.Uri != nil && r.Uri.Addr != "" && strings.HasPrefix(r.Uri.Addr, "0x")
uriCopy := r.uri },
Msg: func(r *messages.Request) string {
uriCopy := r.Uri
uriCopy.Addr = strings.TrimPrefix(uriCopy.Addr, "0x") 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.<br/> return fmt.Sprintf(`The requested hash seems to be prefixed with '0x'. You will be redirected to the correct URL within 5 seconds.<br/>
Please click <a href='%[1]s'>here</a> if your browser does not redirect you.<script>setTimeout("location.href='%[1]s';",5000);</script>`, "/"+uriCopy.String()) Please click <a href='%[1]s'>here</a> if your browser does not redirect you.<script>setTimeout("location.href='%[1]s';",5000);</script>`, "/"+uriCopy.String())
@ -98,7 +101,7 @@ func initErrHandling() {
//ValidateCaseErrors is a method that process the request object through certain validators //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 //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 { for _, err := range caseErrors {
if err.Validator(r) { if err.Validator(r) {
return err.Msg(r) return err.Msg(r)
@ -114,7 +117,7 @@ func ValidateCaseErrors(r *Request) string {
//For example, if the user requests bzz:/<hash>/read and that manifest contains entries //For example, if the user requests bzz:/<hash>/read and that manifest contains entries
//"readme.md" and "readinglist.txt", a HTML page is returned with this two links. //"readme.md" and "readinglist.txt", a HTML page is returned with this two links.
//This only applies if the manifest has no default entry //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 := "" msg := ""
if list.Entries == nil { if list.Entries == nil {
Respond(w, req, "Could not resolve", http.StatusInternalServerError) 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 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 //The code is used to evaluate which template will be displayed
//(and return the correct HTTP status code) //(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) additionalMessage := ValidateCaseErrors(req)
switch code { switch code {
case http.StatusInternalServerError: 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: 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 { if code >= 400 {
@ -156,43 +159,43 @@ func Respond(w http.ResponseWriter, req *Request, msg string, code int) {
w.Header().Del("ETag") w.Header().Del("ETag")
} }
respond(w, &req.Request, &ResponseParams{ respond(w, &req.Request, &messages.ResponseParams{
Code: code, Code: code,
Msg: msg, Msg: msg,
Details: template.HTML(additionalMessage), Details: template.HTML(additionalMessage),
Timestamp: time.Now().Format(time.RFC1123), Timestamp: time.Now().Format(time.RFC1123),
template: getTemplate(code), Template: GetTemplate(code),
}) })
} }
//evaluate if client accepts html or json response //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) w.WriteHeader(params.Code)
if r.Header.Get("Accept") == "application/json" { if r.Header.Get("Accept") == "application/json" {
respondJSON(w, params) RespondJSON(w, params)
} else { } else {
respondHTML(w, params) RespondHTML(w, params)
} }
} }
//return a HTML page //return a HTML page
func respondHTML(w http.ResponseWriter, params *ResponseParams) { func RespondHTML(w http.ResponseWriter, params *messages.ResponseParams) {
htmlCounter.Inc(1) htmlCounter.Inc(1)
err := params.template.Execute(w, params) err := params.Template.Execute(w, params)
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
} }
} }
//return JSON //return JSON
func respondJSON(w http.ResponseWriter, params *ResponseParams) { func RespondJSON(w http.ResponseWriter, params *messages.ResponseParams) {
jsonCounter.Inc(1) jsonCounter.Inc(1)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(params) json.NewEncoder(w).Encode(params)
} }
//get the HTML template for a given code //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 { if val, tmpl := templateMap[code]; tmpl {
return val return val
} }

View file

View file

@ -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 For this reason we resort to save the HTML error pages as strings, which then can be
parsed by Go's html/template package parsed by Go's html/template package
*/ */
package http package views
//This returns the HTML for generic errors //This returns the HTML for generic errors
func GetGenericErrorPage() string { func GetGenericErrorPage() string {

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package http package views
import ( import (
"html/template" "html/template"
@ -23,12 +23,12 @@ import (
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
) )
type htmlListData struct { type HtmlListData struct {
URI *api.URI URI *api.URI
List *api.ManifestList 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(`
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -71,7 +71,7 @@ var htmlListTemplate = template.Must(template.New("html-list").Funcs(template.Fu
</body> </body>
`[1:])) `[1:]))
var landingPageTemplate = template.Must(template.New("landingPage").Parse(` var LandingPageTemplate = template.Must(template.New("landingPage").Parse(`
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">