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 }