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  }