github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/swarm/api/api.go (about)

     1  // This file is part of the go-sberex library. The go-sberex library is 
     2  // free software: you can redistribute it and/or modify it under the terms 
     3  // of the GNU Lesser General Public License as published by the Free 
     4  // Software Foundation, either version 3 of the License, or (at your option)
     5  // any later version.
     6  //
     7  // The go-sberex library is distributed in the hope that it will be useful, 
     8  // but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
    10  // General Public License <http://www.gnu.org/licenses/> for more details.
    11  
    12  package api
    13  
    14  import (
    15  	"fmt"
    16  	"io"
    17  	"net/http"
    18  	"path"
    19  	"regexp"
    20  	"strings"
    21  	"sync"
    22  
    23  	"bytes"
    24  	"mime"
    25  	"path/filepath"
    26  	"time"
    27  
    28  	"github.com/Sberex/go-sberex/common"
    29  	"github.com/Sberex/go-sberex/log"
    30  	"github.com/Sberex/go-sberex/metrics"
    31  	"github.com/Sberex/go-sberex/swarm/storage"
    32  )
    33  
    34  var hashMatcher = regexp.MustCompile("^[0-9A-Fa-f]{64}")
    35  
    36  //setup metrics
    37  var (
    38  	apiResolveCount    = metrics.NewRegisteredCounter("api.resolve.count", nil)
    39  	apiResolveFail     = metrics.NewRegisteredCounter("api.resolve.fail", nil)
    40  	apiPutCount        = metrics.NewRegisteredCounter("api.put.count", nil)
    41  	apiPutFail         = metrics.NewRegisteredCounter("api.put.fail", nil)
    42  	apiGetCount        = metrics.NewRegisteredCounter("api.get.count", nil)
    43  	apiGetNotFound     = metrics.NewRegisteredCounter("api.get.notfound", nil)
    44  	apiGetHttp300      = metrics.NewRegisteredCounter("api.get.http.300", nil)
    45  	apiModifyCount     = metrics.NewRegisteredCounter("api.modify.count", nil)
    46  	apiModifyFail      = metrics.NewRegisteredCounter("api.modify.fail", nil)
    47  	apiAddFileCount    = metrics.NewRegisteredCounter("api.addfile.count", nil)
    48  	apiAddFileFail     = metrics.NewRegisteredCounter("api.addfile.fail", nil)
    49  	apiRmFileCount     = metrics.NewRegisteredCounter("api.removefile.count", nil)
    50  	apiRmFileFail      = metrics.NewRegisteredCounter("api.removefile.fail", nil)
    51  	apiAppendFileCount = metrics.NewRegisteredCounter("api.appendfile.count", nil)
    52  	apiAppendFileFail  = metrics.NewRegisteredCounter("api.appendfile.fail", nil)
    53  )
    54  
    55  type Resolver interface {
    56  	Resolve(string) (common.Hash, error)
    57  }
    58  
    59  // NoResolverError is returned by MultiResolver.Resolve if no resolver
    60  // can be found for the address.
    61  type NoResolverError struct {
    62  	TLD string
    63  }
    64  
    65  func NewNoResolverError(tld string) *NoResolverError {
    66  	return &NoResolverError{TLD: tld}
    67  }
    68  
    69  func (e *NoResolverError) Error() string {
    70  	if e.TLD == "" {
    71  		return "no ENS resolver"
    72  	}
    73  	return fmt.Sprintf("no ENS endpoint configured to resolve .%s TLD names", e.TLD)
    74  }
    75  
    76  // MultiResolver is used to resolve URL addresses based on their TLDs.
    77  // Each TLD can have multiple resolvers, and the resoluton from the
    78  // first one in the sequence will be returned.
    79  type MultiResolver struct {
    80  	resolvers map[string][]Resolver
    81  }
    82  
    83  // MultiResolverOption sets options for MultiResolver and is used as
    84  // arguments for its constructor.
    85  type MultiResolverOption func(*MultiResolver)
    86  
    87  // MultiResolverOptionWithResolver adds a Resolver to a list of resolvers
    88  // for a specific TLD. If TLD is an empty string, the resolver will be added
    89  // to the list of default resolver, the ones that will be used for resolution
    90  // of addresses which do not have their TLD resolver specified.
    91  func MultiResolverOptionWithResolver(r Resolver, tld string) MultiResolverOption {
    92  	return func(m *MultiResolver) {
    93  		m.resolvers[tld] = append(m.resolvers[tld], r)
    94  	}
    95  }
    96  
    97  // NewMultiResolver creates a new instance of MultiResolver.
    98  func NewMultiResolver(opts ...MultiResolverOption) (m *MultiResolver) {
    99  	m = &MultiResolver{
   100  		resolvers: make(map[string][]Resolver),
   101  	}
   102  	for _, o := range opts {
   103  		o(m)
   104  	}
   105  	return m
   106  }
   107  
   108  // Resolve resolves address by choosing a Resolver by TLD.
   109  // If there are more default Resolvers, or for a specific TLD,
   110  // the Hash from the the first one which does not return error
   111  // will be returned.
   112  func (m MultiResolver) Resolve(addr string) (h common.Hash, err error) {
   113  	rs := m.resolvers[""]
   114  	tld := path.Ext(addr)
   115  	if tld != "" {
   116  		tld = tld[1:]
   117  		rstld, ok := m.resolvers[tld]
   118  		if ok {
   119  			rs = rstld
   120  		}
   121  	}
   122  	if rs == nil {
   123  		return h, NewNoResolverError(tld)
   124  	}
   125  	for _, r := range rs {
   126  		h, err = r.Resolve(addr)
   127  		if err == nil {
   128  			return
   129  		}
   130  	}
   131  	return
   132  }
   133  
   134  /*
   135  Api implements webserver/file system related content storage and retrieval
   136  on top of the dpa
   137  it is the public interface of the dpa which is included in the sberex stack
   138  */
   139  type Api struct {
   140  	dpa *storage.DPA
   141  	dns Resolver
   142  }
   143  
   144  //the api constructor initialises
   145  func NewApi(dpa *storage.DPA, dns Resolver) (self *Api) {
   146  	self = &Api{
   147  		dpa: dpa,
   148  		dns: dns,
   149  	}
   150  	return
   151  }
   152  
   153  // to be used only in TEST
   154  func (self *Api) Upload(uploadDir, index string) (hash string, err error) {
   155  	fs := NewFileSystem(self)
   156  	hash, err = fs.Upload(uploadDir, index)
   157  	return hash, err
   158  }
   159  
   160  // DPA reader API
   161  func (self *Api) Retrieve(key storage.Key) storage.LazySectionReader {
   162  	return self.dpa.Retrieve(key)
   163  }
   164  
   165  func (self *Api) Store(data io.Reader, size int64, wg *sync.WaitGroup) (key storage.Key, err error) {
   166  	return self.dpa.Store(data, size, wg, nil)
   167  }
   168  
   169  type ErrResolve error
   170  
   171  // DNS Resolver
   172  func (self *Api) Resolve(uri *URI) (storage.Key, error) {
   173  	apiResolveCount.Inc(1)
   174  	log.Trace(fmt.Sprintf("Resolving : %v", uri.Addr))
   175  
   176  	// if the URI is immutable, check if the address is a hash
   177  	isHash := hashMatcher.MatchString(uri.Addr)
   178  	if uri.Immutable() || uri.DeprecatedImmutable() {
   179  		if !isHash {
   180  			return nil, fmt.Errorf("immutable address not a content hash: %q", uri.Addr)
   181  		}
   182  		return common.Hex2Bytes(uri.Addr), nil
   183  	}
   184  
   185  	// if DNS is not configured, check if the address is a hash
   186  	if self.dns == nil {
   187  		if !isHash {
   188  			apiResolveFail.Inc(1)
   189  			return nil, fmt.Errorf("no DNS to resolve name: %q", uri.Addr)
   190  		}
   191  		return common.Hex2Bytes(uri.Addr), nil
   192  	}
   193  
   194  	// try and resolve the address
   195  	resolved, err := self.dns.Resolve(uri.Addr)
   196  	if err == nil {
   197  		return resolved[:], nil
   198  	} else if !isHash {
   199  		apiResolveFail.Inc(1)
   200  		return nil, err
   201  	}
   202  	return common.Hex2Bytes(uri.Addr), nil
   203  }
   204  
   205  // Put provides singleton manifest creation on top of dpa store
   206  func (self *Api) Put(content, contentType string) (storage.Key, error) {
   207  	apiPutCount.Inc(1)
   208  	r := strings.NewReader(content)
   209  	wg := &sync.WaitGroup{}
   210  	key, err := self.dpa.Store(r, int64(len(content)), wg, nil)
   211  	if err != nil {
   212  		apiPutFail.Inc(1)
   213  		return nil, err
   214  	}
   215  	manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType)
   216  	r = strings.NewReader(manifest)
   217  	key, err = self.dpa.Store(r, int64(len(manifest)), wg, nil)
   218  	if err != nil {
   219  		apiPutFail.Inc(1)
   220  		return nil, err
   221  	}
   222  	wg.Wait()
   223  	return key, nil
   224  }
   225  
   226  // Get uses iterative manifest retrieval and prefix matching
   227  // to resolve basePath to content using dpa retrieve
   228  // it returns a section reader, mimeType, status and an error
   229  func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionReader, mimeType string, status int, err error) {
   230  	apiGetCount.Inc(1)
   231  	trie, err := loadManifest(self.dpa, key, nil)
   232  	if err != nil {
   233  		apiGetNotFound.Inc(1)
   234  		status = http.StatusNotFound
   235  		log.Warn(fmt.Sprintf("loadManifestTrie error: %v", err))
   236  		return
   237  	}
   238  
   239  	log.Trace(fmt.Sprintf("getEntry(%s)", path))
   240  
   241  	entry, _ := trie.getEntry(path)
   242  
   243  	if entry != nil {
   244  		key = common.Hex2Bytes(entry.Hash)
   245  		status = entry.Status
   246  		if status == http.StatusMultipleChoices {
   247  			apiGetHttp300.Inc(1)
   248  			return
   249  		} else {
   250  			mimeType = entry.ContentType
   251  			log.Trace(fmt.Sprintf("content lookup key: '%v' (%v)", key, mimeType))
   252  			reader = self.dpa.Retrieve(key)
   253  		}
   254  	} else {
   255  		status = http.StatusNotFound
   256  		apiGetNotFound.Inc(1)
   257  		err = fmt.Errorf("manifest entry for '%s' not found", path)
   258  		log.Warn(fmt.Sprintf("%v", err))
   259  	}
   260  	return
   261  }
   262  
   263  func (self *Api) Modify(key storage.Key, path, contentHash, contentType string) (storage.Key, error) {
   264  	apiModifyCount.Inc(1)
   265  	quitC := make(chan bool)
   266  	trie, err := loadManifest(self.dpa, key, quitC)
   267  	if err != nil {
   268  		apiModifyFail.Inc(1)
   269  		return nil, err
   270  	}
   271  	if contentHash != "" {
   272  		entry := newManifestTrieEntry(&ManifestEntry{
   273  			Path:        path,
   274  			ContentType: contentType,
   275  		}, nil)
   276  		entry.Hash = contentHash
   277  		trie.addEntry(entry, quitC)
   278  	} else {
   279  		trie.deleteEntry(path, quitC)
   280  	}
   281  
   282  	if err := trie.recalcAndStore(); err != nil {
   283  		apiModifyFail.Inc(1)
   284  		return nil, err
   285  	}
   286  	return trie.hash, nil
   287  }
   288  
   289  func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver bool) (storage.Key, string, error) {
   290  	apiAddFileCount.Inc(1)
   291  
   292  	uri, err := Parse("bzz:/" + mhash)
   293  	if err != nil {
   294  		apiAddFileFail.Inc(1)
   295  		return nil, "", err
   296  	}
   297  	mkey, err := self.Resolve(uri)
   298  	if err != nil {
   299  		apiAddFileFail.Inc(1)
   300  		return nil, "", err
   301  	}
   302  
   303  	// trim the root dir we added
   304  	if path[:1] == "/" {
   305  		path = path[1:]
   306  	}
   307  
   308  	entry := &ManifestEntry{
   309  		Path:        filepath.Join(path, fname),
   310  		ContentType: mime.TypeByExtension(filepath.Ext(fname)),
   311  		Mode:        0700,
   312  		Size:        int64(len(content)),
   313  		ModTime:     time.Now(),
   314  	}
   315  
   316  	mw, err := self.NewManifestWriter(mkey, nil)
   317  	if err != nil {
   318  		apiAddFileFail.Inc(1)
   319  		return nil, "", err
   320  	}
   321  
   322  	fkey, err := mw.AddEntry(bytes.NewReader(content), entry)
   323  	if err != nil {
   324  		apiAddFileFail.Inc(1)
   325  		return nil, "", err
   326  	}
   327  
   328  	newMkey, err := mw.Store()
   329  	if err != nil {
   330  		apiAddFileFail.Inc(1)
   331  		return nil, "", err
   332  
   333  	}
   334  
   335  	return fkey, newMkey.String(), nil
   336  
   337  }
   338  
   339  func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (string, error) {
   340  	apiRmFileCount.Inc(1)
   341  
   342  	uri, err := Parse("bzz:/" + mhash)
   343  	if err != nil {
   344  		apiRmFileFail.Inc(1)
   345  		return "", err
   346  	}
   347  	mkey, err := self.Resolve(uri)
   348  	if err != nil {
   349  		apiRmFileFail.Inc(1)
   350  		return "", err
   351  	}
   352  
   353  	// trim the root dir we added
   354  	if path[:1] == "/" {
   355  		path = path[1:]
   356  	}
   357  
   358  	mw, err := self.NewManifestWriter(mkey, nil)
   359  	if err != nil {
   360  		apiRmFileFail.Inc(1)
   361  		return "", err
   362  	}
   363  
   364  	err = mw.RemoveEntry(filepath.Join(path, fname))
   365  	if err != nil {
   366  		apiRmFileFail.Inc(1)
   367  		return "", err
   368  	}
   369  
   370  	newMkey, err := mw.Store()
   371  	if err != nil {
   372  		apiRmFileFail.Inc(1)
   373  		return "", err
   374  
   375  	}
   376  
   377  	return newMkey.String(), nil
   378  }
   379  
   380  func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, content []byte, oldKey storage.Key, offset int64, addSize int64, nameresolver bool) (storage.Key, string, error) {
   381  	apiAppendFileCount.Inc(1)
   382  
   383  	buffSize := offset + addSize
   384  	if buffSize < existingSize {
   385  		buffSize = existingSize
   386  	}
   387  
   388  	buf := make([]byte, buffSize)
   389  
   390  	oldReader := self.Retrieve(oldKey)
   391  	io.ReadAtLeast(oldReader, buf, int(offset))
   392  
   393  	newReader := bytes.NewReader(content)
   394  	io.ReadAtLeast(newReader, buf[offset:], int(addSize))
   395  
   396  	if buffSize < existingSize {
   397  		io.ReadAtLeast(oldReader, buf[addSize:], int(buffSize))
   398  	}
   399  
   400  	combinedReader := bytes.NewReader(buf)
   401  	totalSize := int64(len(buf))
   402  
   403  	// TODO(jmozah): to append using pyramid chunker when it is ready
   404  	//oldReader := self.Retrieve(oldKey)
   405  	//newReader := bytes.NewReader(content)
   406  	//combinedReader := io.MultiReader(oldReader, newReader)
   407  
   408  	uri, err := Parse("bzz:/" + mhash)
   409  	if err != nil {
   410  		apiAppendFileFail.Inc(1)
   411  		return nil, "", err
   412  	}
   413  	mkey, err := self.Resolve(uri)
   414  	if err != nil {
   415  		apiAppendFileFail.Inc(1)
   416  		return nil, "", err
   417  	}
   418  
   419  	// trim the root dir we added
   420  	if path[:1] == "/" {
   421  		path = path[1:]
   422  	}
   423  
   424  	mw, err := self.NewManifestWriter(mkey, nil)
   425  	if err != nil {
   426  		apiAppendFileFail.Inc(1)
   427  		return nil, "", err
   428  	}
   429  
   430  	err = mw.RemoveEntry(filepath.Join(path, fname))
   431  	if err != nil {
   432  		apiAppendFileFail.Inc(1)
   433  		return nil, "", err
   434  	}
   435  
   436  	entry := &ManifestEntry{
   437  		Path:        filepath.Join(path, fname),
   438  		ContentType: mime.TypeByExtension(filepath.Ext(fname)),
   439  		Mode:        0700,
   440  		Size:        totalSize,
   441  		ModTime:     time.Now(),
   442  	}
   443  
   444  	fkey, err := mw.AddEntry(io.Reader(combinedReader), entry)
   445  	if err != nil {
   446  		apiAppendFileFail.Inc(1)
   447  		return nil, "", err
   448  	}
   449  
   450  	newMkey, err := mw.Store()
   451  	if err != nil {
   452  		apiAppendFileFail.Inc(1)
   453  		return nil, "", err
   454  
   455  	}
   456  
   457  	return fkey, newMkey.String(), nil
   458  
   459  }
   460  
   461  func (self *Api) BuildDirectoryTree(mhash string, nameresolver bool) (key storage.Key, manifestEntryMap map[string]*manifestTrieEntry, err error) {
   462  
   463  	uri, err := Parse("bzz:/" + mhash)
   464  	if err != nil {
   465  		return nil, nil, err
   466  	}
   467  	key, err = self.Resolve(uri)
   468  	if err != nil {
   469  		return nil, nil, err
   470  	}
   471  
   472  	quitC := make(chan bool)
   473  	rootTrie, err := loadManifest(self.dpa, key, quitC)
   474  	if err != nil {
   475  		return nil, nil, fmt.Errorf("can't load manifest %v: %v", key.String(), err)
   476  	}
   477  
   478  	manifestEntryMap = map[string]*manifestTrieEntry{}
   479  	err = rootTrie.listWithPrefix(uri.Path, quitC, func(entry *manifestTrieEntry, suffix string) {
   480  		manifestEntryMap[suffix] = entry
   481  	})
   482  
   483  	if err != nil {
   484  		return nil, nil, fmt.Errorf("list with prefix failed %v: %v", key.String(), err)
   485  	}
   486  	return key, manifestEntryMap, nil
   487  }