github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/src/cmd/pprof/internal/fetch/fetch.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package fetch provides an extensible mechanism to fetch a profile 6 // from a data source. 7 package fetch 8 9 import ( 10 "crypto/tls" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "net/http" 15 "net/url" 16 "os" 17 "strings" 18 "time" 19 20 "cmd/pprof/internal/plugin" 21 "internal/pprof/profile" 22 ) 23 24 // FetchProfile reads from a data source (network, file) and generates a 25 // profile. 26 func FetchProfile(source string, timeout time.Duration) (*profile.Profile, error) { 27 return Fetcher(source, timeout, plugin.StandardUI()) 28 } 29 30 // Fetcher is the plugin.Fetcher version of FetchProfile. 31 func Fetcher(source string, timeout time.Duration, ui plugin.UI) (*profile.Profile, error) { 32 var f io.ReadCloser 33 var err error 34 35 url, err := url.Parse(source) 36 if err == nil && url.Host != "" { 37 f, err = FetchURL(source, timeout) 38 } else { 39 f, err = os.Open(source) 40 } 41 if err != nil { 42 return nil, err 43 } 44 defer f.Close() 45 return profile.Parse(f) 46 } 47 48 // FetchURL fetches a profile from a URL using HTTP. 49 func FetchURL(source string, timeout time.Duration) (io.ReadCloser, error) { 50 resp, err := httpGet(source, timeout) 51 if err != nil { 52 return nil, fmt.Errorf("http fetch: %v", err) 53 } 54 if resp.StatusCode != http.StatusOK { 55 defer resp.Body.Close() 56 return nil, statusCodeError(resp) 57 } 58 59 return resp.Body, nil 60 } 61 62 // PostURL issues a POST to a URL over HTTP. 63 func PostURL(source, post string) ([]byte, error) { 64 resp, err := http.Post(source, "application/octet-stream", strings.NewReader(post)) 65 if err != nil { 66 return nil, fmt.Errorf("http post %s: %v", source, err) 67 } 68 defer resp.Body.Close() 69 if resp.StatusCode != http.StatusOK { 70 return nil, statusCodeError(resp) 71 } 72 return ioutil.ReadAll(resp.Body) 73 } 74 75 func statusCodeError(resp *http.Response) error { 76 if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") { 77 // error is from pprof endpoint 78 body, err := ioutil.ReadAll(resp.Body) 79 if err == nil { 80 return fmt.Errorf("server response: %s - %s", resp.Status, body) 81 } 82 } 83 return fmt.Errorf("server response: %s", resp.Status) 84 } 85 86 // httpGet is a wrapper around http.Get; it is defined as a variable 87 // so it can be redefined during for testing. 88 var httpGet = func(source string, timeout time.Duration) (*http.Response, error) { 89 url, err := url.Parse(source) 90 if err != nil { 91 return nil, err 92 } 93 94 var tlsConfig *tls.Config 95 if url.Scheme == "https+insecure" { 96 tlsConfig = &tls.Config{ 97 InsecureSkipVerify: true, 98 } 99 url.Scheme = "https" 100 source = url.String() 101 } 102 103 client := &http.Client{ 104 Transport: &http.Transport{ 105 ResponseHeaderTimeout: timeout + 5*time.Second, 106 TLSClientConfig: tlsConfig, 107 }, 108 } 109 return client.Get(source) 110 }