github.com/jincm/wesharechain@v0.0.0-20210122032815-1537409ce26a/chain/swarm/api/client/client.go (about)

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