| // Copyright 2015 The Prometheus Authors |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // +build go1.7 |
| |
| // Package api provides clients for the HTTP APIs. |
| package api |
| |
| import ( |
| "context" |
| "io/ioutil" |
| "net" |
| "net/http" |
| "net/url" |
| "path" |
| "strings" |
| "time" |
| ) |
| |
| // DefaultRoundTripper is used if no RoundTripper is set in Config. |
| var DefaultRoundTripper http.RoundTripper = &http.Transport{ |
| Proxy: http.ProxyFromEnvironment, |
| DialContext: (&net.Dialer{ |
| Timeout: 30 * time.Second, |
| KeepAlive: 30 * time.Second, |
| }).DialContext, |
| TLSHandshakeTimeout: 10 * time.Second, |
| } |
| |
| // Config defines configuration parameters for a new client. |
| type Config struct { |
| // The address of the Prometheus to connect to. |
| Address string |
| |
| // RoundTripper is used by the Client to drive HTTP requests. If not |
| // provided, DefaultRoundTripper will be used. |
| RoundTripper http.RoundTripper |
| } |
| |
| func (cfg *Config) roundTripper() http.RoundTripper { |
| if cfg.RoundTripper == nil { |
| return DefaultRoundTripper |
| } |
| return cfg.RoundTripper |
| } |
| |
| // Client is the interface for an API client. |
| type Client interface { |
| URL(ep string, args map[string]string) *url.URL |
| Do(context.Context, *http.Request) (*http.Response, []byte, error) |
| } |
| |
| // NewClient returns a new Client. |
| // |
| // It is safe to use the returned Client from multiple goroutines. |
| func NewClient(cfg Config) (Client, error) { |
| u, err := url.Parse(cfg.Address) |
| if err != nil { |
| return nil, err |
| } |
| u.Path = strings.TrimRight(u.Path, "/") |
| |
| return &httpClient{ |
| endpoint: u, |
| client: http.Client{Transport: cfg.roundTripper()}, |
| }, nil |
| } |
| |
| type httpClient struct { |
| endpoint *url.URL |
| client http.Client |
| } |
| |
| func (c *httpClient) URL(ep string, args map[string]string) *url.URL { |
| p := path.Join(c.endpoint.Path, ep) |
| |
| for arg, val := range args { |
| arg = ":" + arg |
| p = strings.Replace(p, arg, val, -1) |
| } |
| |
| u := *c.endpoint |
| u.Path = p |
| |
| return &u |
| } |
| |
| func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { |
| if ctx != nil { |
| req = req.WithContext(ctx) |
| } |
| resp, err := c.client.Do(req) |
| defer func() { |
| if resp != nil { |
| resp.Body.Close() |
| } |
| }() |
| |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| var body []byte |
| done := make(chan struct{}) |
| go func() { |
| body, err = ioutil.ReadAll(resp.Body) |
| close(done) |
| }() |
| |
| select { |
| case <-ctx.Done(): |
| err = resp.Body.Close() |
| <-done |
| if err == nil { |
| err = ctx.Err() |
| } |
| case <-done: |
| } |
| |
| return resp, body, err |
| } |