mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
swarm/api: Serve Resource Updates from bzz: scheme
This commit is contained in:
parent
e61c739107
commit
e4991168b9
5 changed files with 164 additions and 35 deletions
|
|
@ -37,6 +37,18 @@ import (
|
|||
|
||||
var hashMatcher = regexp.MustCompile("^[0-9A-Fa-f]{64}")
|
||||
|
||||
type ErrResourceReturn struct {
|
||||
key string
|
||||
}
|
||||
|
||||
func (e *ErrResourceReturn) Error() string {
|
||||
return "resourceupdate"
|
||||
}
|
||||
|
||||
func (e *ErrResourceReturn) Key() string {
|
||||
return e.key
|
||||
}
|
||||
|
||||
type Resolver interface {
|
||||
Resolve(string) (common.Hash, error)
|
||||
}
|
||||
|
|
@ -145,6 +157,18 @@ func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionRe
|
|||
entry, _ := trie.getEntry(path)
|
||||
|
||||
if entry != nil {
|
||||
// we want to be able to serve Mutable Resource Updates transparently using the bzz:// scheme
|
||||
//
|
||||
// we use a special manifest hack for this purpose, which is pathless and where the resource root key
|
||||
// is set as the hash of the manifest (see swarm/api/manifest.go:NewResourceManifest)
|
||||
//
|
||||
// to avoid taking a performance hit hacking a storage.LazySectionReader to wrap the resource key,
|
||||
// we return a typed error instead. Since for all other purposes this is an invalid manifest,
|
||||
// any normal interfacing code will just see an error fail accordingly.
|
||||
if entry.ContentType == ResourceContentType {
|
||||
log.Warn("resource type", "hash", entry.Hash)
|
||||
return nil, entry.ContentType, http.StatusOK, &ErrResourceReturn{entry.Hash}
|
||||
}
|
||||
key = common.Hex2Bytes(entry.Hash)
|
||||
status = entry.Status
|
||||
if status == http.StatusMultipleChoices {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ import (
|
|||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
type resourceResponse struct {
|
||||
Manifest storage.Key `json:"manifest"`
|
||||
Resource string `json:"resource"`
|
||||
Update storage.Key `json:"update"`
|
||||
}
|
||||
|
||||
// ServerConfig is the basic configuration needed for the HTTP server and also
|
||||
// includes CORS settings.
|
||||
type ServerConfig struct {
|
||||
|
|
@ -292,7 +298,7 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) {
|
|||
}
|
||||
|
||||
func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
|
||||
var outdata string
|
||||
var outdata []byte
|
||||
if r.uri.Path != "" {
|
||||
frequency, err := strconv.ParseUint(r.uri.Path, 10, 64)
|
||||
if err != nil {
|
||||
|
|
@ -304,7 +310,21 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
|
|||
s.translateResourceError(w, r, "Resource creation fail", err)
|
||||
return
|
||||
}
|
||||
outdata = key.Hex()
|
||||
m, err := s.api.NewResourceManifest(r.uri.Addr)
|
||||
if err != nil {
|
||||
s.Error(w, r, fmt.Errorf("Failed to create resource manifest: %v", err))
|
||||
return
|
||||
}
|
||||
rsrcResponse := &resourceResponse{
|
||||
Manifest: m,
|
||||
Resource: r.uri.Addr,
|
||||
Update: key,
|
||||
}
|
||||
outdata, err = json.Marshal(rsrcResponse)
|
||||
if err != nil {
|
||||
s.Error(w, r, fmt.Errorf("Failed to create json response for %v: error was: %v", r, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
|
|
@ -318,40 +338,15 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if outdata != "" {
|
||||
if len(outdata) > 0 {
|
||||
w.Header().Add("Content-type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, outdata)
|
||||
fmt.Fprint(w, string(outdata))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supErr string, err error) {
|
||||
code := 0
|
||||
defaulterr := fmt.Errorf("%s: %v", supErr, err)
|
||||
rsrcerr, ok := err.(*storage.ResourceError)
|
||||
if !ok {
|
||||
code = rsrcerr.Code()
|
||||
}
|
||||
switch code {
|
||||
case storage.ErrInval:
|
||||
s.BadRequest(w, r, defaulterr.Error())
|
||||
case storage.ErrNoent, storage.ErrSync, storage.ErrNodata:
|
||||
s.NotFound(w, r, defaulterr)
|
||||
return
|
||||
case storage.ErrAcces, storage.ErrNokey:
|
||||
ShowError(w, &r.Request, defaulterr.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
case storage.ErrFbig:
|
||||
ShowError(w, &r.Request, defaulterr.Error(), http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
s.Error(w, r, defaulterr)
|
||||
return
|
||||
}
|
||||
|
||||
// Retrieve mutable resource updates:
|
||||
// bzz-resource://<id> - get latest update
|
||||
// bzz-resource://<id>/<n> - get latest update on period n
|
||||
|
|
@ -405,6 +400,31 @@ func (s *Server) handleGetResource(w http.ResponseWriter, r *Request, name strin
|
|||
http.ServeContent(w, &r.Request, "", now, bytes.NewReader(data))
|
||||
}
|
||||
|
||||
func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supErr string, err error) {
|
||||
code := 0
|
||||
defaulterr := fmt.Errorf("%s: %v", supErr, err)
|
||||
rsrcerr, ok := err.(*storage.ResourceError)
|
||||
if !ok {
|
||||
code = rsrcerr.Code()
|
||||
}
|
||||
switch code {
|
||||
case storage.ErrInval:
|
||||
s.BadRequest(w, r, defaulterr.Error())
|
||||
case storage.ErrNoent, storage.ErrSync, storage.ErrNodata:
|
||||
s.NotFound(w, r, defaulterr)
|
||||
return
|
||||
case storage.ErrAcces, storage.ErrNokey:
|
||||
ShowError(w, &r.Request, defaulterr.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
case storage.ErrFbig:
|
||||
ShowError(w, &r.Request, defaulterr.Error(), http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
s.Error(w, r, defaulterr)
|
||||
return
|
||||
}
|
||||
|
||||
// HandleGet handles a GET request to
|
||||
// - bzz-raw://<key> and responds with the raw content stored at the
|
||||
// given storage key
|
||||
|
|
@ -665,7 +685,14 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
|
|||
}
|
||||
|
||||
reader, contentType, status, err := s.api.Get(key, r.uri.Path)
|
||||
|
||||
if err != nil {
|
||||
// cheeky, cheeky hack. See swarm/api/api.go:Api.Get() for an explanation
|
||||
if rsrcErr, ok := err.(*api.ErrResourceReturn); ok {
|
||||
log.Trace("getting resource proxy", "err", rsrcErr.Key())
|
||||
s.handleGetResource(w, r, rsrcErr.Key())
|
||||
return
|
||||
}
|
||||
switch status {
|
||||
case http.StatusNotFound:
|
||||
s.NotFound(w, r, err)
|
||||
|
|
|
|||
|
|
@ -19,28 +19,45 @@ package http_test
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
verbose := flag.Bool("v", false, "verbose")
|
||||
flag.Parse()
|
||||
if *verbose {
|
||||
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
||||
}
|
||||
}
|
||||
|
||||
type resourceResponse struct {
|
||||
Manifest storage.Key `json:"manifest"`
|
||||
Resource string `json:"resource"`
|
||||
Update storage.Key `json:"update"`
|
||||
}
|
||||
|
||||
func TestBzzResource(t *testing.T) {
|
||||
srv := testutil.NewTestSwarmServer(t)
|
||||
defer srv.Close()
|
||||
|
||||
// our mutable resource "name"
|
||||
keybytes := make([]byte, common.HashLength)
|
||||
copy(keybytes, []byte{42})
|
||||
keybytes := []byte("foo")
|
||||
srv.Hasher.Reset()
|
||||
srv.Hasher.Write([]byte(fmt.Sprintf("%x", keybytes)))
|
||||
keybyteshash := fmt.Sprintf("%x", srv.Hasher.Sum(nil))
|
||||
|
|
@ -66,10 +83,55 @@ func TestBzzResource(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(b, []byte(keybyteshash)) {
|
||||
t.Fatalf("resource update hash mismatch, expected '%s' got '%s'", keybyteshash, b)
|
||||
rsrcResp := &resourceResponse{}
|
||||
err = json.Unmarshal(b, rsrcResp)
|
||||
if err != nil {
|
||||
t.Fatalf("data %s could not be unmarshaled: %v", b, err)
|
||||
}
|
||||
if rsrcResp.Update.Hex() != keybyteshash {
|
||||
t.Fatalf("Response resource key mismatch, expected '%s', got '%s'", keybyteshash, rsrcResp.Resource)
|
||||
}
|
||||
|
||||
// get manifest
|
||||
url = fmt.Sprintf("%s/bzz-raw:/%s", srv.URL, rsrcResp.Manifest)
|
||||
resp, err = http.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("err %s", resp.Status)
|
||||
}
|
||||
b, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
manifest := &api.Manifest{}
|
||||
err = json.Unmarshal(b, manifest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(manifest.Entries) != 1 {
|
||||
t.Fatalf("Manifest has %d entries", len(manifest.Entries))
|
||||
}
|
||||
if manifest.Entries[0].Hash != rsrcResp.Resource {
|
||||
t.Fatalf("Expected manifest path '%s', got '%s'", keybyteshash, manifest.Entries[0].Hash)
|
||||
}
|
||||
|
||||
// get bzz manifest transparent resource resolve
|
||||
url = fmt.Sprintf("%s/bzz:/%s", srv.URL, rsrcResp.Manifest)
|
||||
resp, err = http.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("err %s", resp.Status)
|
||||
}
|
||||
b, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("creatreturn %v / %v", keybyteshash, b)
|
||||
|
||||
// get latest update (1.1) through resource directly
|
||||
url = fmt.Sprintf("%s/bzz-resource:/%x", srv.URL, keybytes)
|
||||
|
|
|
|||
|
|
@ -69,6 +69,22 @@ func (a *Api) NewManifest() (storage.Key, error) {
|
|||
return a.Store(bytes.NewReader(data), int64(len(data)), &sync.WaitGroup{})
|
||||
}
|
||||
|
||||
// Manifest hack for supporting Mutable Resource Updates from the bzz: scheme
|
||||
// see swarm/api/api.go:Api.Get() for more information
|
||||
func (a *Api) NewResourceManifest(resourceKey string) (storage.Key, error) {
|
||||
var manifest Manifest
|
||||
entry := ManifestEntry{
|
||||
Hash: resourceKey,
|
||||
ContentType: ResourceContentType,
|
||||
}
|
||||
manifest.Entries = append(manifest.Entries, entry)
|
||||
data, err := json.Marshal(&manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.Store(bytes.NewReader(data), int64(len(data)), &sync.WaitGroup{})
|
||||
}
|
||||
|
||||
// ManifestWriter is used to add and remove entries from an underlying manifest
|
||||
type ManifestWriter struct {
|
||||
api *Api
|
||||
|
|
|
|||
|
|
@ -571,7 +571,7 @@ func (self *ResourceHandler) Update(ctx context.Context, name string, data []byt
|
|||
}
|
||||
|
||||
// get our blockheight at this time and the next block of the update period
|
||||
currentblock, err := self.GetBlock(ctx)
|
||||
currentblock, err := self.getBlock(ctx)
|
||||
if err != nil {
|
||||
return nil, NewResourceError(ErrIO, fmt.Sprintf("Could not get block height: %v", err))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue