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