client.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. // Copyright 2017 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package client
  17. import (
  18. "archive/tar"
  19. "bytes"
  20. "context"
  21. "encoding/json"
  22. "errors"
  23. "fmt"
  24. "io"
  25. "io/ioutil"
  26. "mime/multipart"
  27. "net/http"
  28. "net/http/httptrace"
  29. "net/textproto"
  30. "net/url"
  31. "os"
  32. "path/filepath"
  33. "regexp"
  34. "strconv"
  35. "strings"
  36. "time"
  37. "github.com/ethereum/go-ethereum/log"
  38. "github.com/ethereum/go-ethereum/metrics"
  39. "github.com/ethereum/go-ethereum/swarm/api"
  40. swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
  41. "github.com/ethereum/go-ethereum/swarm/spancontext"
  42. "github.com/ethereum/go-ethereum/swarm/storage/feed"
  43. "github.com/pborman/uuid"
  44. )
  45. var (
  46. ErrUnauthorized = errors.New("unauthorized")
  47. )
  48. func NewClient(gateway string) *Client {
  49. return &Client{
  50. Gateway: gateway,
  51. }
  52. }
  53. // Client wraps interaction with a swarm HTTP gateway.
  54. type Client struct {
  55. Gateway string
  56. }
  57. // UploadRaw uploads raw data to swarm and returns the resulting hash. If toEncrypt is true it
  58. // uploads encrypted data
  59. func (c *Client) UploadRaw(r io.Reader, size int64, toEncrypt bool) (string, error) {
  60. if size <= 0 {
  61. return "", errors.New("data size must be greater than zero")
  62. }
  63. addr := ""
  64. if toEncrypt {
  65. addr = "encrypt"
  66. }
  67. req, err := http.NewRequest("POST", c.Gateway+"/bzz-raw:/"+addr, r)
  68. if err != nil {
  69. return "", err
  70. }
  71. req.ContentLength = size
  72. req.Header.Set(swarmhttp.SwarmTagHeaderName, fmt.Sprintf("raw_upload_%d", time.Now().Unix()))
  73. res, err := http.DefaultClient.Do(req)
  74. if err != nil {
  75. return "", err
  76. }
  77. defer res.Body.Close()
  78. if res.StatusCode != http.StatusOK {
  79. return "", fmt.Errorf("unexpected HTTP status: %s", res.Status)
  80. }
  81. data, err := ioutil.ReadAll(res.Body)
  82. if err != nil {
  83. return "", err
  84. }
  85. return string(data), nil
  86. }
  87. // DownloadRaw downloads raw data from swarm and it returns a ReadCloser and a bool whether the
  88. // content was encrypted
  89. func (c *Client) DownloadRaw(hash string) (io.ReadCloser, bool, error) {
  90. uri := c.Gateway + "/bzz-raw:/" + hash
  91. res, err := http.DefaultClient.Get(uri)
  92. if err != nil {
  93. return nil, false, err
  94. }
  95. if res.StatusCode != http.StatusOK {
  96. res.Body.Close()
  97. return nil, false, fmt.Errorf("unexpected HTTP status: %s", res.Status)
  98. }
  99. isEncrypted := (res.Header.Get("X-Decrypted") == "true")
  100. return res.Body, isEncrypted, nil
  101. }
  102. // File represents a file in a swarm manifest and is used for uploading and
  103. // downloading content to and from swarm
  104. type File struct {
  105. io.ReadCloser
  106. api.ManifestEntry
  107. Tag string
  108. }
  109. // Open opens a local file which can then be passed to client.Upload to upload
  110. // it to swarm
  111. func Open(path string) (*File, error) {
  112. f, err := os.Open(path)
  113. if err != nil {
  114. return nil, err
  115. }
  116. stat, err := f.Stat()
  117. if err != nil {
  118. f.Close()
  119. return nil, err
  120. }
  121. contentType, err := api.DetectContentType(f.Name(), f)
  122. if err != nil {
  123. return nil, err
  124. }
  125. return &File{
  126. ReadCloser: f,
  127. ManifestEntry: api.ManifestEntry{
  128. ContentType: contentType,
  129. Mode: int64(stat.Mode()),
  130. Size: stat.Size(),
  131. ModTime: stat.ModTime(),
  132. },
  133. Tag: filepath.Base(path),
  134. }, nil
  135. }
  136. // Upload uploads a file to swarm and either adds it to an existing manifest
  137. // (if the manifest argument is non-empty) or creates a new manifest containing
  138. // the file, returning the resulting manifest hash (the file will then be
  139. // available at bzz:/<hash>/<path>)
  140. func (c *Client) Upload(file *File, manifest string, toEncrypt bool) (string, error) {
  141. if file.Size <= 0 {
  142. return "", errors.New("file size must be greater than zero")
  143. }
  144. return c.TarUpload(manifest, &FileUploader{file}, "", toEncrypt)
  145. }
  146. // Download downloads a file with the given path from the swarm manifest with
  147. // the given hash (i.e. it gets bzz:/<hash>/<path>)
  148. func (c *Client) Download(hash, path string) (*File, error) {
  149. uri := c.Gateway + "/bzz:/" + hash + "/" + path
  150. res, err := http.DefaultClient.Get(uri)
  151. if err != nil {
  152. return nil, err
  153. }
  154. if res.StatusCode != http.StatusOK {
  155. res.Body.Close()
  156. return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status)
  157. }
  158. return &File{
  159. ReadCloser: res.Body,
  160. ManifestEntry: api.ManifestEntry{
  161. ContentType: res.Header.Get("Content-Type"),
  162. Size: res.ContentLength,
  163. },
  164. }, nil
  165. }
  166. // UploadDirectory uploads a directory tree to swarm and either adds the files
  167. // to an existing manifest (if the manifest argument is non-empty) or creates a
  168. // new manifest, returning the resulting manifest hash (files from the
  169. // directory will then be available at bzz:/<hash>/path/to/file), with
  170. // the file specified in defaultPath being uploaded to the root of the manifest
  171. // (i.e. bzz:/<hash>/)
  172. func (c *Client) UploadDirectory(dir, defaultPath, manifest string, toEncrypt bool) (string, error) {
  173. stat, err := os.Stat(dir)
  174. if err != nil {
  175. return "", err
  176. } else if !stat.IsDir() {
  177. return "", fmt.Errorf("not a directory: %s", dir)
  178. }
  179. if defaultPath != "" {
  180. if _, err := os.Stat(filepath.Join(dir, defaultPath)); err != nil {
  181. if os.IsNotExist(err) {
  182. return "", fmt.Errorf("the default path %q was not found in the upload directory %q", defaultPath, dir)
  183. }
  184. return "", fmt.Errorf("default path: %v", err)
  185. }
  186. }
  187. return c.TarUpload(manifest, &DirectoryUploader{dir}, defaultPath, toEncrypt)
  188. }
  189. // DownloadDirectory downloads the files contained in a swarm manifest under
  190. // the given path into a local directory (existing files will be overwritten)
  191. func (c *Client) DownloadDirectory(hash, path, destDir, credentials string) error {
  192. stat, err := os.Stat(destDir)
  193. if err != nil {
  194. return err
  195. } else if !stat.IsDir() {
  196. return fmt.Errorf("not a directory: %s", destDir)
  197. }
  198. uri := c.Gateway + "/bzz:/" + hash + "/" + path
  199. req, err := http.NewRequest("GET", uri, nil)
  200. if err != nil {
  201. return err
  202. }
  203. if credentials != "" {
  204. req.SetBasicAuth("", credentials)
  205. }
  206. req.Header.Set("Accept", "application/x-tar")
  207. res, err := http.DefaultClient.Do(req)
  208. if err != nil {
  209. return err
  210. }
  211. defer res.Body.Close()
  212. switch res.StatusCode {
  213. case http.StatusOK:
  214. case http.StatusUnauthorized:
  215. return ErrUnauthorized
  216. default:
  217. return fmt.Errorf("unexpected HTTP status: %s", res.Status)
  218. }
  219. tr := tar.NewReader(res.Body)
  220. for {
  221. hdr, err := tr.Next()
  222. if err == io.EOF {
  223. return nil
  224. } else if err != nil {
  225. return err
  226. }
  227. // ignore the default path file
  228. if hdr.Name == "" {
  229. continue
  230. }
  231. dstPath := filepath.Join(destDir, filepath.Clean(strings.TrimPrefix(hdr.Name, path)))
  232. if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
  233. return err
  234. }
  235. var mode os.FileMode = 0644
  236. if hdr.Mode > 0 {
  237. mode = os.FileMode(hdr.Mode)
  238. }
  239. dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
  240. if err != nil {
  241. return err
  242. }
  243. n, err := io.Copy(dst, tr)
  244. dst.Close()
  245. if err != nil {
  246. return err
  247. } else if n != hdr.Size {
  248. return fmt.Errorf("expected %s to be %d bytes but got %d", hdr.Name, hdr.Size, n)
  249. }
  250. }
  251. }
  252. // DownloadFile downloads a single file into the destination directory
  253. // if the manifest entry does not specify a file name - it will fallback
  254. // to the hash of the file as a filename
  255. func (c *Client) DownloadFile(hash, path, dest, credentials string) error {
  256. hasDestinationFilename := false
  257. if stat, err := os.Stat(dest); err == nil {
  258. hasDestinationFilename = !stat.IsDir()
  259. } else {
  260. if os.IsNotExist(err) {
  261. // does not exist - should be created
  262. hasDestinationFilename = true
  263. } else {
  264. return fmt.Errorf("could not stat path: %v", err)
  265. }
  266. }
  267. manifestList, err := c.List(hash, path, credentials)
  268. if err != nil {
  269. return err
  270. }
  271. switch len(manifestList.Entries) {
  272. case 0:
  273. return fmt.Errorf("could not find path requested at manifest address. make sure the path you've specified is correct")
  274. case 1:
  275. //continue
  276. default:
  277. return fmt.Errorf("got too many matches for this path")
  278. }
  279. uri := c.Gateway + "/bzz:/" + hash + "/" + path
  280. req, err := http.NewRequest("GET", uri, nil)
  281. if err != nil {
  282. return err
  283. }
  284. if credentials != "" {
  285. req.SetBasicAuth("", credentials)
  286. }
  287. res, err := http.DefaultClient.Do(req)
  288. if err != nil {
  289. return err
  290. }
  291. defer res.Body.Close()
  292. switch res.StatusCode {
  293. case http.StatusOK:
  294. case http.StatusUnauthorized:
  295. return ErrUnauthorized
  296. default:
  297. return fmt.Errorf("unexpected HTTP status: expected 200 OK, got %d", res.StatusCode)
  298. }
  299. filename := ""
  300. if hasDestinationFilename {
  301. filename = dest
  302. } else {
  303. // try to assert
  304. re := regexp.MustCompile("[^/]+$") //everything after last slash
  305. if results := re.FindAllString(path, -1); len(results) > 0 {
  306. filename = results[len(results)-1]
  307. } else {
  308. if entry := manifestList.Entries[0]; entry.Path != "" && entry.Path != "/" {
  309. filename = entry.Path
  310. } else {
  311. // assume hash as name if there's nothing from the command line
  312. filename = hash
  313. }
  314. }
  315. filename = filepath.Join(dest, filename)
  316. }
  317. filePath, err := filepath.Abs(filename)
  318. if err != nil {
  319. return err
  320. }
  321. if err := os.MkdirAll(filepath.Dir(filePath), 0777); err != nil {
  322. return err
  323. }
  324. dst, err := os.Create(filename)
  325. if err != nil {
  326. return err
  327. }
  328. defer dst.Close()
  329. _, err = io.Copy(dst, res.Body)
  330. return err
  331. }
  332. // UploadManifest uploads the given manifest to swarm
  333. func (c *Client) UploadManifest(m *api.Manifest, toEncrypt bool) (string, error) {
  334. data, err := json.Marshal(m)
  335. if err != nil {
  336. return "", err
  337. }
  338. return c.UploadRaw(bytes.NewReader(data), int64(len(data)), toEncrypt)
  339. }
  340. // DownloadManifest downloads a swarm manifest
  341. func (c *Client) DownloadManifest(hash string) (*api.Manifest, bool, error) {
  342. res, isEncrypted, err := c.DownloadRaw(hash)
  343. if err != nil {
  344. return nil, isEncrypted, err
  345. }
  346. defer res.Close()
  347. var manifest api.Manifest
  348. if err := json.NewDecoder(res).Decode(&manifest); err != nil {
  349. return nil, isEncrypted, err
  350. }
  351. return &manifest, isEncrypted, nil
  352. }
  353. // List list files in a swarm manifest which have the given prefix, grouping
  354. // common prefixes using "/" as a delimiter.
  355. //
  356. // For example, if the manifest represents the following directory structure:
  357. //
  358. // file1.txt
  359. // file2.txt
  360. // dir1/file3.txt
  361. // dir1/dir2/file4.txt
  362. //
  363. // Then:
  364. //
  365. // - a prefix of "" would return [dir1/, file1.txt, file2.txt]
  366. // - a prefix of "file" would return [file1.txt, file2.txt]
  367. // - a prefix of "dir1/" would return [dir1/dir2/, dir1/file3.txt]
  368. //
  369. // where entries ending with "/" are common prefixes.
  370. func (c *Client) List(hash, prefix, credentials string) (*api.ManifestList, error) {
  371. req, err := http.NewRequest(http.MethodGet, c.Gateway+"/bzz-list:/"+hash+"/"+prefix, nil)
  372. if err != nil {
  373. return nil, err
  374. }
  375. if credentials != "" {
  376. req.SetBasicAuth("", credentials)
  377. }
  378. res, err := http.DefaultClient.Do(req)
  379. if err != nil {
  380. return nil, err
  381. }
  382. defer res.Body.Close()
  383. switch res.StatusCode {
  384. case http.StatusOK:
  385. case http.StatusUnauthorized:
  386. return nil, ErrUnauthorized
  387. default:
  388. return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status)
  389. }
  390. var list api.ManifestList
  391. if err := json.NewDecoder(res.Body).Decode(&list); err != nil {
  392. return nil, err
  393. }
  394. return &list, nil
  395. }
  396. // Uploader uploads files to swarm using a provided UploadFn
  397. type Uploader interface {
  398. Upload(UploadFn) error
  399. Tag() string
  400. }
  401. type UploaderFunc func(UploadFn) error
  402. func (u UploaderFunc) Upload(upload UploadFn) error {
  403. return u(upload)
  404. }
  405. func (u UploaderFunc) Tag() string {
  406. return fmt.Sprintf("multipart_upload_%d", time.Now().Unix())
  407. }
  408. // DirectoryUploader implements Uploader
  409. var _ Uploader = &DirectoryUploader{}
  410. // DirectoryUploader uploads all files in a directory, optionally uploading
  411. // a file to the default path
  412. type DirectoryUploader struct {
  413. Dir string
  414. }
  415. func (d *DirectoryUploader) Tag() string {
  416. return filepath.Base(d.Dir)
  417. }
  418. // Upload performs the upload of the directory and default path
  419. func (d *DirectoryUploader) Upload(upload UploadFn) error {
  420. return filepath.Walk(d.Dir, func(path string, f os.FileInfo, err error) error {
  421. if err != nil {
  422. return err
  423. }
  424. if f.IsDir() {
  425. return nil
  426. }
  427. file, err := Open(path)
  428. if err != nil {
  429. return err
  430. }
  431. relPath, err := filepath.Rel(d.Dir, path)
  432. if err != nil {
  433. return err
  434. }
  435. file.Path = filepath.ToSlash(relPath)
  436. return upload(file)
  437. })
  438. }
  439. var _ Uploader = &FileUploader{}
  440. // FileUploader uploads a single file
  441. type FileUploader struct {
  442. File *File
  443. }
  444. func (f *FileUploader) Tag() string {
  445. return f.File.Tag
  446. }
  447. // Upload performs the upload of the file
  448. func (f *FileUploader) Upload(upload UploadFn) error {
  449. return upload(f.File)
  450. }
  451. // UploadFn is the type of function passed to an Uploader to perform the upload
  452. // of a single file (for example, a directory uploader would call a provided
  453. // UploadFn for each file in the directory tree)
  454. type UploadFn func(file *File) error
  455. // TarUpload uses the given Uploader to upload files to swarm as a tar stream,
  456. // returning the resulting manifest hash
  457. func (c *Client) TarUpload(hash string, uploader Uploader, defaultPath string, toEncrypt bool) (string, error) {
  458. ctx, sp := spancontext.StartSpan(context.Background(), "api.client.tarupload")
  459. defer sp.Finish()
  460. var tn time.Time
  461. reqR, reqW := io.Pipe()
  462. defer reqR.Close()
  463. addr := hash
  464. // If there is a hash already (a manifest), then that manifest will determine if the upload has
  465. // to be encrypted or not. If there is no manifest then the toEncrypt parameter decides if
  466. // there is encryption or not.
  467. if hash == "" && toEncrypt {
  468. // This is the built-in address for the encrypted upload endpoint
  469. addr = "encrypt"
  470. }
  471. req, err := http.NewRequest("POST", c.Gateway+"/bzz:/"+addr, reqR)
  472. if err != nil {
  473. return "", err
  474. }
  475. trace := GetClientTrace("swarm api client - upload tar", "api.client.uploadtar", uuid.New()[:8], &tn)
  476. req = req.WithContext(httptrace.WithClientTrace(ctx, trace))
  477. transport := http.DefaultTransport
  478. req.Header.Set("Content-Type", "application/x-tar")
  479. if defaultPath != "" {
  480. q := req.URL.Query()
  481. q.Set("defaultpath", defaultPath)
  482. req.URL.RawQuery = q.Encode()
  483. }
  484. tag := uploader.Tag()
  485. if tag == "" {
  486. tag = "unnamed_tag_" + fmt.Sprintf("%d", time.Now().Unix())
  487. }
  488. log.Trace("setting upload tag", "tag", tag)
  489. req.Header.Set(swarmhttp.SwarmTagHeaderName, tag)
  490. // use 'Expect: 100-continue' so we don't send the request body if
  491. // the server refuses the request
  492. req.Header.Set("Expect", "100-continue")
  493. tw := tar.NewWriter(reqW)
  494. // define an UploadFn which adds files to the tar stream
  495. uploadFn := func(file *File) error {
  496. hdr := &tar.Header{
  497. Name: file.Path,
  498. Mode: file.Mode,
  499. Size: file.Size,
  500. ModTime: file.ModTime,
  501. Xattrs: map[string]string{
  502. "user.swarm.content-type": file.ContentType,
  503. },
  504. }
  505. if err := tw.WriteHeader(hdr); err != nil {
  506. return err
  507. }
  508. _, err = io.Copy(tw, file)
  509. return err
  510. }
  511. // run the upload in a goroutine so we can send the request headers and
  512. // wait for a '100 Continue' response before sending the tar stream
  513. go func() {
  514. err := uploader.Upload(uploadFn)
  515. if err == nil {
  516. err = tw.Close()
  517. }
  518. reqW.CloseWithError(err)
  519. }()
  520. tn = time.Now()
  521. res, err := transport.RoundTrip(req)
  522. if err != nil {
  523. return "", err
  524. }
  525. defer res.Body.Close()
  526. if res.StatusCode != http.StatusOK {
  527. return "", fmt.Errorf("unexpected HTTP status: %s", res.Status)
  528. }
  529. data, err := ioutil.ReadAll(res.Body)
  530. if err != nil {
  531. return "", err
  532. }
  533. return string(data), nil
  534. }
  535. // MultipartUpload uses the given Uploader to upload files to swarm as a
  536. // multipart form, returning the resulting manifest hash
  537. func (c *Client) MultipartUpload(hash string, uploader Uploader) (string, error) {
  538. reqR, reqW := io.Pipe()
  539. defer reqR.Close()
  540. req, err := http.NewRequest("POST", c.Gateway+"/bzz:/"+hash, reqR)
  541. if err != nil {
  542. return "", err
  543. }
  544. // use 'Expect: 100-continue' so we don't send the request body if
  545. // the server refuses the request
  546. req.Header.Set("Expect", "100-continue")
  547. mw := multipart.NewWriter(reqW)
  548. req.Header.Set("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%q", mw.Boundary()))
  549. req.Header.Set(swarmhttp.SwarmTagHeaderName, fmt.Sprintf("multipart_upload_%d", time.Now().Unix()))
  550. // define an UploadFn which adds files to the multipart form
  551. uploadFn := func(file *File) error {
  552. hdr := make(textproto.MIMEHeader)
  553. hdr.Set("Content-Disposition", fmt.Sprintf("form-data; name=%q", file.Path))
  554. hdr.Set("Content-Type", file.ContentType)
  555. hdr.Set("Content-Length", strconv.FormatInt(file.Size, 10))
  556. w, err := mw.CreatePart(hdr)
  557. if err != nil {
  558. return err
  559. }
  560. _, err = io.Copy(w, file)
  561. return err
  562. }
  563. // run the upload in a goroutine so we can send the request headers and
  564. // wait for a '100 Continue' response before sending the multipart form
  565. go func() {
  566. err := uploader.Upload(uploadFn)
  567. if err == nil {
  568. err = mw.Close()
  569. }
  570. reqW.CloseWithError(err)
  571. }()
  572. res, err := http.DefaultClient.Do(req)
  573. if err != nil {
  574. return "", err
  575. }
  576. defer res.Body.Close()
  577. if res.StatusCode != http.StatusOK {
  578. return "", fmt.Errorf("unexpected HTTP status: %s", res.Status)
  579. }
  580. data, err := ioutil.ReadAll(res.Body)
  581. if err != nil {
  582. return "", err
  583. }
  584. return string(data), nil
  585. }
  586. // ErrNoFeedUpdatesFound is returned when Swarm cannot find updates of the given feed
  587. var ErrNoFeedUpdatesFound = errors.New("No updates found for this feed")
  588. // CreateFeedWithManifest creates a feed manifest, initializing it with the provided
  589. // data
  590. // Returns the resulting feed manifest address that you can use to include in an ENS Resolver (setContent)
  591. // or reference future updates (Client.UpdateFeed)
  592. func (c *Client) CreateFeedWithManifest(request *feed.Request) (string, error) {
  593. responseStream, err := c.updateFeed(request, true)
  594. if err != nil {
  595. return "", err
  596. }
  597. defer responseStream.Close()
  598. body, err := ioutil.ReadAll(responseStream)
  599. if err != nil {
  600. return "", err
  601. }
  602. var manifestAddress string
  603. if err = json.Unmarshal(body, &manifestAddress); err != nil {
  604. return "", err
  605. }
  606. return manifestAddress, nil
  607. }
  608. // UpdateFeed allows you to set a new version of your content
  609. func (c *Client) UpdateFeed(request *feed.Request) error {
  610. _, err := c.updateFeed(request, false)
  611. return err
  612. }
  613. func (c *Client) updateFeed(request *feed.Request, createManifest bool) (io.ReadCloser, error) {
  614. URL, err := url.Parse(c.Gateway)
  615. if err != nil {
  616. return nil, err
  617. }
  618. URL.Path = "/bzz-feed:/"
  619. values := URL.Query()
  620. body := request.AppendValues(values)
  621. if createManifest {
  622. values.Set("manifest", "1")
  623. }
  624. URL.RawQuery = values.Encode()
  625. req, err := http.NewRequest("POST", URL.String(), bytes.NewBuffer(body))
  626. if err != nil {
  627. return nil, err
  628. }
  629. res, err := http.DefaultClient.Do(req)
  630. if err != nil {
  631. return nil, err
  632. }
  633. return res.Body, nil
  634. }
  635. // QueryFeed returns a byte stream with the raw content of the feed update
  636. // manifestAddressOrDomain is the address you obtained in CreateFeedWithManifest or an ENS domain whose Resolver
  637. // points to that address
  638. func (c *Client) QueryFeed(query *feed.Query, manifestAddressOrDomain string) (io.ReadCloser, error) {
  639. return c.queryFeed(query, manifestAddressOrDomain, false)
  640. }
  641. // queryFeed returns a byte stream with the raw content of the feed update
  642. // manifestAddressOrDomain is the address you obtained in CreateFeedWithManifest or an ENS domain whose Resolver
  643. // points to that address
  644. // meta set to true will instruct the node return feed metainformation instead
  645. func (c *Client) queryFeed(query *feed.Query, manifestAddressOrDomain string, meta bool) (io.ReadCloser, error) {
  646. URL, err := url.Parse(c.Gateway)
  647. if err != nil {
  648. return nil, err
  649. }
  650. URL.Path = "/bzz-feed:/" + manifestAddressOrDomain
  651. values := URL.Query()
  652. if query != nil {
  653. query.AppendValues(values) //adds query parameters
  654. }
  655. if meta {
  656. values.Set("meta", "1")
  657. }
  658. URL.RawQuery = values.Encode()
  659. res, err := http.Get(URL.String())
  660. if err != nil {
  661. return nil, err
  662. }
  663. if res.StatusCode != http.StatusOK {
  664. if res.StatusCode == http.StatusNotFound {
  665. return nil, ErrNoFeedUpdatesFound
  666. }
  667. errorMessageBytes, err := ioutil.ReadAll(res.Body)
  668. var errorMessage string
  669. if err != nil {
  670. errorMessage = "cannot retrieve error message: " + err.Error()
  671. } else {
  672. errorMessage = string(errorMessageBytes)
  673. }
  674. return nil, fmt.Errorf("Error retrieving feed updates: %s", errorMessage)
  675. }
  676. return res.Body, nil
  677. }
  678. // GetFeedRequest returns a structure that describes the referenced feed status
  679. // manifestAddressOrDomain is the address you obtained in CreateFeedWithManifest or an ENS domain whose Resolver
  680. // points to that address
  681. func (c *Client) GetFeedRequest(query *feed.Query, manifestAddressOrDomain string) (*feed.Request, error) {
  682. responseStream, err := c.queryFeed(query, manifestAddressOrDomain, true)
  683. if err != nil {
  684. return nil, err
  685. }
  686. defer responseStream.Close()
  687. body, err := ioutil.ReadAll(responseStream)
  688. if err != nil {
  689. return nil, err
  690. }
  691. var metadata feed.Request
  692. if err := metadata.UnmarshalJSON(body); err != nil {
  693. return nil, err
  694. }
  695. return &metadata, nil
  696. }
  697. func GetClientTrace(traceMsg, metricPrefix, ruid string, tn *time.Time) *httptrace.ClientTrace {
  698. trace := &httptrace.ClientTrace{
  699. GetConn: func(_ string) {
  700. log.Trace(traceMsg+" - http get", "event", "GetConn", "ruid", ruid)
  701. metrics.GetOrRegisterResettingTimer(metricPrefix+".getconn", nil).Update(time.Since(*tn))
  702. },
  703. GotConn: func(_ httptrace.GotConnInfo) {
  704. log.Trace(traceMsg+" - http get", "event", "GotConn", "ruid", ruid)
  705. metrics.GetOrRegisterResettingTimer(metricPrefix+".gotconn", nil).Update(time.Since(*tn))
  706. },
  707. PutIdleConn: func(err error) {
  708. log.Trace(traceMsg+" - http get", "event", "PutIdleConn", "ruid", ruid, "err", err)
  709. metrics.GetOrRegisterResettingTimer(metricPrefix+".putidle", nil).Update(time.Since(*tn))
  710. },
  711. GotFirstResponseByte: func() {
  712. log.Trace(traceMsg+" - http get", "event", "GotFirstResponseByte", "ruid", ruid)
  713. metrics.GetOrRegisterResettingTimer(metricPrefix+".firstbyte", nil).Update(time.Since(*tn))
  714. },
  715. Got100Continue: func() {
  716. log.Trace(traceMsg, "event", "Got100Continue", "ruid", ruid)
  717. metrics.GetOrRegisterResettingTimer(metricPrefix+".got100continue", nil).Update(time.Since(*tn))
  718. },
  719. DNSStart: func(_ httptrace.DNSStartInfo) {
  720. log.Trace(traceMsg, "event", "DNSStart", "ruid", ruid)
  721. metrics.GetOrRegisterResettingTimer(metricPrefix+".dnsstart", nil).Update(time.Since(*tn))
  722. },
  723. DNSDone: func(_ httptrace.DNSDoneInfo) {
  724. log.Trace(traceMsg, "event", "DNSDone", "ruid", ruid)
  725. metrics.GetOrRegisterResettingTimer(metricPrefix+".dnsdone", nil).Update(time.Since(*tn))
  726. },
  727. ConnectStart: func(network, addr string) {
  728. log.Trace(traceMsg, "event", "ConnectStart", "ruid", ruid, "network", network, "addr", addr)
  729. metrics.GetOrRegisterResettingTimer(metricPrefix+".connectstart", nil).Update(time.Since(*tn))
  730. },
  731. ConnectDone: func(network, addr string, err error) {
  732. log.Trace(traceMsg, "event", "ConnectDone", "ruid", ruid, "network", network, "addr", addr, "err", err)
  733. metrics.GetOrRegisterResettingTimer(metricPrefix+".connectdone", nil).Update(time.Since(*tn))
  734. },
  735. WroteHeaders: func() {
  736. log.Trace(traceMsg, "event", "WroteHeaders(request)", "ruid", ruid)
  737. metrics.GetOrRegisterResettingTimer(metricPrefix+".wroteheaders", nil).Update(time.Since(*tn))
  738. },
  739. Wait100Continue: func() {
  740. log.Trace(traceMsg, "event", "Wait100Continue", "ruid", ruid)
  741. metrics.GetOrRegisterResettingTimer(metricPrefix+".wait100continue", nil).Update(time.Since(*tn))
  742. },
  743. WroteRequest: func(_ httptrace.WroteRequestInfo) {
  744. log.Trace(traceMsg, "event", "WroteRequest", "ruid", ruid)
  745. metrics.GetOrRegisterResettingTimer(metricPrefix+".wroterequest", nil).Update(time.Since(*tn))
  746. },
  747. }
  748. return trace
  749. }