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 }