Commit f7d3678c authored by Anton Evangelatov's avatar Anton Evangelatov Committed by Balint Gabor

swarm/api/http: http package refactoring 1/5 and 2/5 (#17164)

parent facf1bc9
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package api package api
import ( import (
"archive/tar"
"context" "context"
"fmt" "fmt"
"io" "io"
...@@ -48,6 +49,16 @@ var ( ...@@ -48,6 +49,16 @@ var (
apiGetCount = metrics.NewRegisteredCounter("api.get.count", nil) apiGetCount = metrics.NewRegisteredCounter("api.get.count", nil)
apiGetNotFound = metrics.NewRegisteredCounter("api.get.notfound", nil) apiGetNotFound = metrics.NewRegisteredCounter("api.get.notfound", nil)
apiGetHTTP300 = metrics.NewRegisteredCounter("api.get.http.300", nil) apiGetHTTP300 = metrics.NewRegisteredCounter("api.get.http.300", nil)
apiManifestUpdateCount = metrics.NewRegisteredCounter("api.manifestupdate.count", nil)
apiManifestUpdateFail = metrics.NewRegisteredCounter("api.manifestupdate.fail", nil)
apiManifestListCount = metrics.NewRegisteredCounter("api.manifestlist.count", nil)
apiManifestListFail = metrics.NewRegisteredCounter("api.manifestlist.fail", nil)
apiDeleteCount = metrics.NewRegisteredCounter("api.delete.count", nil)
apiDeleteFail = metrics.NewRegisteredCounter("api.delete.fail", nil)
apiGetTarCount = metrics.NewRegisteredCounter("api.gettar.count", nil)
apiGetTarFail = metrics.NewRegisteredCounter("api.gettar.fail", nil)
apiUploadTarCount = metrics.NewRegisteredCounter("api.uploadtar.count", nil)
apiUploadTarFail = metrics.NewRegisteredCounter("api.uploadtar.fail", nil)
apiModifyCount = metrics.NewRegisteredCounter("api.modify.count", nil) apiModifyCount = metrics.NewRegisteredCounter("api.modify.count", nil)
apiModifyFail = metrics.NewRegisteredCounter("api.modify.fail", nil) apiModifyFail = metrics.NewRegisteredCounter("api.modify.fail", nil)
apiAddFileCount = metrics.NewRegisteredCounter("api.addfile.count", nil) apiAddFileCount = metrics.NewRegisteredCounter("api.addfile.count", nil)
...@@ -424,6 +435,185 @@ func (a *API) Get(ctx context.Context, manifestAddr storage.Address, path string ...@@ -424,6 +435,185 @@ func (a *API) Get(ctx context.Context, manifestAddr storage.Address, path string
return return
} }
func (a *API) Delete(ctx context.Context, addr string, path string) (storage.Address, error) {
apiDeleteCount.Inc(1)
uri, err := Parse("bzz:/" + addr)
if err != nil {
apiDeleteFail.Inc(1)
return nil, err
}
key, err := a.Resolve(ctx, uri)
if err != nil {
return nil, err
}
newKey, err := a.UpdateManifest(ctx, key, func(mw *ManifestWriter) error {
log.Debug(fmt.Sprintf("removing %s from manifest %s", path, key.Log()))
return mw.RemoveEntry(path)
})
if err != nil {
apiDeleteFail.Inc(1)
return nil, err
}
return newKey, nil
}
// GetDirectoryTar fetches a requested directory as a tarstream
// it returns an io.Reader and an error. Do not forget to Close() the returned ReadCloser
func (a *API) GetDirectoryTar(ctx context.Context, uri *URI) (io.ReadCloser, error) {
apiGetTarCount.Inc(1)
addr, err := a.Resolve(ctx, uri)
if err != nil {
return nil, err
}
walker, err := a.NewManifestWalker(ctx, addr, nil)
if err != nil {
apiGetTarFail.Inc(1)
return nil, err
}
piper, pipew := io.Pipe()
tw := tar.NewWriter(pipew)
go func() {
err := walker.Walk(func(entry *ManifestEntry) error {
// ignore manifests (walk will recurse into them)
if entry.ContentType == ManifestType {
return nil
}
// retrieve the entry's key and size
reader, _ := a.Retrieve(ctx, storage.Address(common.Hex2Bytes(entry.Hash)))
size, err := reader.Size(nil)
if err != nil {
return err
}
// write a tar header for the entry
hdr := &tar.Header{
Name: entry.Path,
Mode: entry.Mode,
Size: size,
ModTime: entry.ModTime,
Xattrs: map[string]string{
"user.swarm.content-type": entry.ContentType,
},
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
// copy the file into the tar stream
n, err := io.Copy(tw, io.LimitReader(reader, hdr.Size))
if err != nil {
return err
} else if n != size {
return fmt.Errorf("error writing %s: expected %d bytes but sent %d", entry.Path, size, n)
}
return nil
})
if err != nil {
apiGetTarFail.Inc(1)
pipew.CloseWithError(err)
} else {
pipew.Close()
}
}()
return piper, nil
}
// GetManifestList lists the manifest entries for the specified address and prefix
// and returns it as a ManifestList
func (a *API) GetManifestList(ctx context.Context, addr storage.Address, prefix string) (list ManifestList, err error) {
apiManifestListCount.Inc(1)
walker, err := a.NewManifestWalker(ctx, addr, nil)
if err != nil {
apiManifestListFail.Inc(1)
return ManifestList{}, err
}
err = walker.Walk(func(entry *ManifestEntry) error {
// handle non-manifest files
if entry.ContentType != 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 ErrSkipManifest
}
return nil
}
// the manifest neither has the prefix or needs recursing in to
// so just skip it
return ErrSkipManifest
})
if err != nil {
apiManifestListFail.Inc(1)
return ManifestList{}, err
}
return list, nil
}
func (a *API) UpdateManifest(ctx context.Context, addr storage.Address, update func(mw *ManifestWriter) error) (storage.Address, error) {
apiManifestUpdateCount.Inc(1)
mw, err := a.NewManifestWriter(ctx, addr, nil)
if err != nil {
apiManifestUpdateFail.Inc(1)
return nil, err
}
if err := update(mw); err != nil {
apiManifestUpdateFail.Inc(1)
return nil, err
}
addr, err = mw.Store()
if err != nil {
apiManifestUpdateFail.Inc(1)
return nil, err
}
log.Debug(fmt.Sprintf("generated manifest %s", addr))
return addr, nil
}
// Modify loads manifest and checks the content hash before recalculating and storing the manifest. // Modify loads manifest and checks the content hash before recalculating and storing the manifest.
func (a *API) Modify(ctx context.Context, addr storage.Address, path, contentHash, contentType string) (storage.Address, error) { func (a *API) Modify(ctx context.Context, addr storage.Address, path, contentHash, contentType string) (storage.Address, error) {
apiModifyCount.Inc(1) apiModifyCount.Inc(1)
...@@ -501,6 +691,43 @@ func (a *API) AddFile(ctx context.Context, mhash, path, fname string, content [] ...@@ -501,6 +691,43 @@ func (a *API) AddFile(ctx context.Context, mhash, path, fname string, content []
return fkey, newMkey.String(), nil return fkey, newMkey.String(), nil
} }
func (a *API) UploadTar(ctx context.Context, bodyReader io.ReadCloser, manifestPath string, mw *ManifestWriter) (storage.Address, error) {
apiUploadTarCount.Inc(1)
var contentKey storage.Address
tr := tar.NewReader(bodyReader)
defer bodyReader.Close()
for {
hdr, err := tr.Next()
if err == io.EOF {
break
} else if err != nil {
apiUploadTarFail.Inc(1)
return nil, fmt.Errorf("error reading tar stream: %s", err)
}
// only store regular files
if !hdr.FileInfo().Mode().IsRegular() {
continue
}
// add the entry under the path from the request
manifestPath := path.Join(manifestPath, hdr.Name)
entry := &ManifestEntry{
Path: manifestPath,
ContentType: hdr.Xattrs["user.swarm.content-type"],
Mode: hdr.Mode,
Size: hdr.Size,
ModTime: hdr.ModTime,
}
contentKey, err = mw.AddEntry(ctx, tr, entry)
if err != nil {
apiUploadTarFail.Inc(1)
return nil, fmt.Errorf("error adding manifest entry from tar stream: %s", err)
}
}
return contentKey, nil
}
// RemoveFile removes a file entry in a manifest. // RemoveFile removes a file entry in a manifest.
func (a *API) RemoveFile(ctx context.Context, mhash string, path string, fname string, nameresolver bool) (string, error) { func (a *API) RemoveFile(ctx context.Context, mhash string, path string, fname string, nameresolver bool) (string, error) {
apiRmFileCount.Inc(1) apiRmFileCount.Inc(1)
......
...@@ -31,7 +31,7 @@ import ( ...@@ -31,7 +31,7 @@ import (
) )
func serverFunc(api *api.API) testutil.TestServer { func serverFunc(api *api.API) testutil.TestServer {
return swarmhttp.NewServer(api) return swarmhttp.NewServer(api, "")
} }
// TestClientUploadDownloadRaw test uploading and downloading raw data to swarm // TestClientUploadDownloadRaw test uploading and downloading raw data to swarm
......
...@@ -29,7 +29,6 @@ import ( ...@@ -29,7 +29,6 @@ import (
) )
func TestError(t *testing.T) { func TestError(t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc) srv := testutil.NewTestSwarmServer(t, serverFunc)
defer srv.Close() defer srv.Close()
......
This diff is collapsed.
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package http package http
import ( import (
"archive/tar"
"bytes" "bytes"
"context" "context"
"crypto/rand" "crypto/rand"
...@@ -24,11 +25,13 @@ import ( ...@@ -24,11 +25,13 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"strings" "strings"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -88,7 +91,7 @@ func TestResourcePostMode(t *testing.T) { ...@@ -88,7 +91,7 @@ func TestResourcePostMode(t *testing.T) {
} }
func serverFunc(api *api.API) testutil.TestServer { func serverFunc(api *api.API) testutil.TestServer {
return NewServer(api) return NewServer(api, "")
} }
// test the transparent resolving of multihash resource types with bzz:// scheme // test the transparent resolving of multihash resource types with bzz:// scheme
...@@ -356,6 +359,11 @@ func TestBzzGetPath(t *testing.T) { ...@@ -356,6 +359,11 @@ func TestBzzGetPath(t *testing.T) {
testBzzGetPath(true, t) testBzzGetPath(true, t)
} }
func TestBzzTar(t *testing.T) {
testBzzTar(false, t)
testBzzTar(true, t)
}
func testBzzGetPath(encrypted bool, t *testing.T) { func testBzzGetPath(encrypted bool, t *testing.T) {
var err error var err error
...@@ -592,6 +600,122 @@ func testBzzGetPath(encrypted bool, t *testing.T) { ...@@ -592,6 +600,122 @@ func testBzzGetPath(encrypted bool, t *testing.T) {
} }
} }
func testBzzTar(encrypted bool, t *testing.T) {
srv := testutil.NewTestSwarmServer(t, serverFunc)
defer srv.Close()
fileNames := []string{"tmp1.txt", "tmp2.lock", "tmp3.rtf"}
fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"}
buf := &bytes.Buffer{}
tw := tar.NewWriter(buf)
defer tw.Close()
for i, v := range fileNames {
size := int64(len(fileContents[i]))
hdr := &tar.Header{
Name: v,
Mode: 0644,
Size: size,
ModTime: time.Now(),
Xattrs: map[string]string{
"user.swarm.content-type": "text/plain",
},
}
if err := tw.WriteHeader(hdr); err != nil {
t.Fatal(err)
}
// copy the file into the tar stream
n, err := io.Copy(tw, bytes.NewBufferString(fileContents[i]))
if err != nil {
t.Fatal(err)
} else if n != size {
t.Fatal("size mismatch")
}
}
//post tar stream
url := srv.URL + "/bzz:/"
if encrypted {
url = url + "encrypt"
}
req, err := http.NewRequest("POST", url, buf)
if err != nil {
t.Fatal(err)
}
req.Header.Add("Content-Type", "application/x-tar")
client := &http.Client{}
resp2, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
if resp2.StatusCode != http.StatusOK {
t.Fatalf("err %s", resp2.Status)
}
swarmHash, err := ioutil.ReadAll(resp2.Body)
resp2.Body.Close()
t.Logf("uploaded tarball successfully and got manifest address at %s", string(swarmHash))
if err != nil {
t.Fatal(err)
}
// now do a GET to get a tarball back
req, err = http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s", string(swarmHash)), nil)
if err != nil {
t.Fatal(err)
}
req.Header.Add("Accept", "application/x-tar")
resp2, err = client.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp2.Body.Close()
file, err := ioutil.TempFile("", "swarm-downloaded-tarball")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
_, err = io.Copy(file, resp2.Body)
if err != nil {
t.Fatalf("error getting tarball: %v", err)
}
file.Sync()
file.Close()
tarFileHandle, err := os.Open(file.Name())
if err != nil {
t.Fatal(err)
}
tr := tar.NewReader(tarFileHandle)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
} else if err != nil {
t.Fatalf("error reading tar stream: %s", err)
}
bb := make([]byte, hdr.Size)
_, err = tr.Read(bb)
if err != nil && err != io.EOF {
t.Fatal(err)
}
passed := false
for i, v := range fileNames {
if v == hdr.Name {
if string(bb) == fileContents[i] {
passed = true
break
}
}
}
if !passed {
t.Fatalf("file %s did not pass content assertion", hdr.Name)
}
}
}
// TestBzzRootRedirect tests that getting the root path of a manifest without // TestBzzRootRedirect tests that getting the root path of a manifest without
// a trailing slash gets redirected to include the trailing slash so that // a trailing slash gets redirected to include the trailing slash so that
// relative URLs work as expected. // relative URLs work as expected.
......
...@@ -388,10 +388,9 @@ func (self *Swarm) Start(srv *p2p.Server) error { ...@@ -388,10 +388,9 @@ func (self *Swarm) Start(srv *p2p.Server) error {
// start swarm http proxy server // start swarm http proxy server
if self.config.Port != "" { if self.config.Port != "" {
addr := net.JoinHostPort(self.config.ListenAddr, self.config.Port) addr := net.JoinHostPort(self.config.ListenAddr, self.config.Port)
go httpapi.StartHTTPServer(self.api, &httpapi.ServerConfig{ server := httpapi.NewServer(self.api, self.config.Cors)
Addr: addr,
CorsString: self.config.Cors, go server.ListenAndServe(addr)
})
} }
log.Debug(fmt.Sprintf("Swarm http proxy started on port: %v", self.config.Port)) log.Debug(fmt.Sprintf("Swarm http proxy started on port: %v", self.config.Port))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment