swarm/api: refactor and improve HTTP API (#3773)

This PR deprecates the file related RPC calls in favour of an improved HTTP API.

The main aim is to expose a simple to use API which can be consumed by thin
clients (e.g. curl and HTML forms) without the need for complex logic (e.g.
manipulating prefix trie manifests).
This commit is contained in:
Lewis Marshall
2017-04-06 23:22:22 +01:00
committed by Felix Lange
parent 9aca9e6deb
commit 71fdaa4238
20 changed files with 1822 additions and 706 deletions

View File

@ -17,6 +17,7 @@
package api
import (
"errors"
"fmt"
"io"
"net/http"
@ -70,86 +71,50 @@ func (self *Api) Store(data io.Reader, size int64, wg *sync.WaitGroup) (key stor
type ErrResolve error
// DNS Resolver
func (self *Api) Resolve(hostPort string, nameresolver bool) (storage.Key, error) {
log.Trace(fmt.Sprintf("Resolving : %v", hostPort))
if hashMatcher.MatchString(hostPort) || self.dns == nil {
log.Trace(fmt.Sprintf("host is a contentHash: '%v'", hostPort))
return storage.Key(common.Hex2Bytes(hostPort)), nil
func (self *Api) Resolve(uri *URI) (storage.Key, error) {
log.Trace(fmt.Sprintf("Resolving : %v", uri.Addr))
if hashMatcher.MatchString(uri.Addr) {
log.Trace(fmt.Sprintf("addr is a hash: %q", uri.Addr))
return storage.Key(common.Hex2Bytes(uri.Addr)), nil
}
if !nameresolver {
return nil, fmt.Errorf("'%s' is not a content hash value.", hostPort)
if uri.Immutable() {
return nil, errors.New("refusing to resolve immutable address")
}
contentHash, err := self.dns.Resolve(hostPort)
if self.dns == nil {
return nil, fmt.Errorf("unable to resolve addr %q, resolver not configured", uri.Addr)
}
hash, err := self.dns.Resolve(uri.Addr)
if err != nil {
err = ErrResolve(err)
log.Warn(fmt.Sprintf("DNS error : %v", err))
log.Warn(fmt.Sprintf("DNS error resolving addr %q: %s", uri.Addr, err))
return nil, ErrResolve(err)
}
log.Trace(fmt.Sprintf("host lookup: %v -> %v", hostPort, contentHash))
return contentHash[:], err
}
func Parse(uri string) (hostPort, path string) {
if uri == "" {
return
}
parts := slashes.Split(uri, 3)
var i int
if len(parts) == 0 {
return
}
// beginning with slash is now optional
for len(parts[i]) == 0 {
i++
}
hostPort = parts[i]
for i < len(parts)-1 {
i++
if len(path) > 0 {
path = path + "/" + parts[i]
} else {
path = parts[i]
}
}
log.Debug(fmt.Sprintf("host: '%s', path '%s' requested.", hostPort, path))
return
}
func (self *Api) parseAndResolve(uri string, nameresolver bool) (key storage.Key, hostPort, path string, err error) {
hostPort, path = Parse(uri)
//resolving host and port
contentHash, err := self.Resolve(hostPort, nameresolver)
log.Debug(fmt.Sprintf("Resolved '%s' to contentHash: '%s', path: '%s'", uri, contentHash, path))
return contentHash[:], hostPort, path, err
log.Trace(fmt.Sprintf("addr lookup: %v -> %v", uri.Addr, hash))
return hash[:], nil
}
// Put provides singleton manifest creation on top of dpa store
func (self *Api) Put(content, contentType string) (string, error) {
func (self *Api) Put(content, contentType string) (storage.Key, error) {
r := strings.NewReader(content)
wg := &sync.WaitGroup{}
key, err := self.dpa.Store(r, int64(len(content)), wg, nil)
if err != nil {
return "", err
return nil, err
}
manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType)
r = strings.NewReader(manifest)
key, err = self.dpa.Store(r, int64(len(manifest)), wg, nil)
if err != nil {
return "", err
return nil, err
}
wg.Wait()
return key.String(), nil
return key, nil
}
// Get uses iterative manifest retrieval and prefix matching
// to resolve path to content using dpa retrieve
// it returns a section reader, mimeType, status and an error
func (self *Api) Get(uri string, nameresolver bool) (reader storage.LazySectionReader, mimeType string, status int, err error) {
key, _, path, err := self.parseAndResolve(uri, nameresolver)
if err != nil {
return nil, "", 500, fmt.Errorf("can't resolve: %v", err)
}
quitC := make(chan bool)
trie, err := loadManifest(self.dpa, key, quitC)
func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionReader, mimeType string, status int, err error) {
trie, err := loadManifest(self.dpa, key, nil)
if err != nil {
log.Warn(fmt.Sprintf("loadManifestTrie error: %v", err))
return
@ -173,32 +138,25 @@ func (self *Api) Get(uri string, nameresolver bool) (reader storage.LazySectionR
return
}
func (self *Api) Modify(uri, contentHash, contentType string, nameresolver bool) (newRootHash string, err error) {
root, _, path, err := self.parseAndResolve(uri, nameresolver)
if err != nil {
return "", fmt.Errorf("can't resolve: %v", err)
}
func (self *Api) Modify(key storage.Key, path, contentHash, contentType string) (storage.Key, error) {
quitC := make(chan bool)
trie, err := loadManifest(self.dpa, root, quitC)
trie, err := loadManifest(self.dpa, key, quitC)
if err != nil {
return
return nil, err
}
if contentHash != "" {
entry := &manifestTrieEntry{
entry := newManifestTrieEntry(&ManifestEntry{
Path: path,
Hash: contentHash,
ContentType: contentType,
}
}, nil)
entry.Hash = contentHash
trie.addEntry(entry, quitC)
} else {
trie.deleteEntry(path, quitC)
}
err = trie.recalcAndStore()
if err != nil {
return
if err := trie.recalcAndStore(); err != nil {
return nil, err
}
return trie.hash.String(), nil
return trie.hash, nil
}