211 lines
5.2 KiB
Go
211 lines
5.2 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you 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.
|
|
|
|
package helper
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/elastic/beats/libbeat/common/transport/tlscommon"
|
|
"github.com/elastic/beats/libbeat/outputs/transport"
|
|
"github.com/elastic/beats/metricbeat/mb"
|
|
)
|
|
|
|
type HTTP struct {
|
|
base mb.BaseMetricSet
|
|
client *http.Client // HTTP client that is reused across requests.
|
|
headers map[string]string
|
|
uri string
|
|
method string
|
|
body []byte
|
|
}
|
|
|
|
// NewHTTP creates new http helper
|
|
func NewHTTP(base mb.BaseMetricSet) (*HTTP, error) {
|
|
config := struct {
|
|
TLS *tlscommon.Config `config:"ssl"`
|
|
Timeout time.Duration `config:"timeout"`
|
|
Headers map[string]string `config:"headers"`
|
|
BearerTokenFile string `config:"bearer_token_file"`
|
|
}{}
|
|
if err := base.Module().UnpackConfig(&config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if config.Headers == nil {
|
|
config.Headers = map[string]string{}
|
|
}
|
|
|
|
if config.BearerTokenFile != "" {
|
|
header, err := getAuthHeaderFromToken(config.BearerTokenFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.Headers["Authorization"] = header
|
|
}
|
|
|
|
tlsConfig, err := tlscommon.LoadTLSConfig(config.TLS)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var dialer, tlsDialer transport.Dialer
|
|
|
|
dialer = transport.NetDialer(config.Timeout)
|
|
tlsDialer, err = transport.TLSDialer(dialer, tlsConfig, config.Timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &HTTP{
|
|
base: base,
|
|
client: &http.Client{
|
|
Transport: &http.Transport{
|
|
Dial: dialer.Dial,
|
|
DialTLS: tlsDialer.Dial,
|
|
},
|
|
Timeout: config.Timeout,
|
|
},
|
|
headers: config.Headers,
|
|
method: "GET",
|
|
uri: base.HostData().SanitizedURI,
|
|
body: nil,
|
|
}, nil
|
|
}
|
|
|
|
// FetchResponse fetches a response for the http metricset.
|
|
// It's important that resp.Body has to be closed if this method is used. Before using this method
|
|
// check if one of the other Fetch* methods could be used as they ensure that the Body is properly closed.
|
|
func (h *HTTP) FetchResponse() (*http.Response, error) {
|
|
// Create a fresh reader every time
|
|
var reader io.Reader
|
|
if h.body != nil {
|
|
reader = bytes.NewReader(h.body)
|
|
}
|
|
|
|
req, err := http.NewRequest(h.method, h.uri, reader)
|
|
if h.base.HostData().User != "" || h.base.HostData().Password != "" {
|
|
req.SetBasicAuth(h.base.HostData().User, h.base.HostData().Password)
|
|
}
|
|
|
|
for k, v := range h.headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
|
|
resp, err := h.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error making http request: %v", err)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// SetHeader sets HTTP headers to use in requests
|
|
func (h *HTTP) SetHeader(key, value string) {
|
|
h.headers[key] = value
|
|
}
|
|
|
|
// SetMethod sets HTTP method to use in requests
|
|
func (h *HTTP) SetMethod(method string) {
|
|
h.method = method
|
|
}
|
|
|
|
// GetURI gets the URI used in requests
|
|
func (h *HTTP) GetURI() string {
|
|
return h.uri
|
|
}
|
|
|
|
// SetURI sets URI to use in requests
|
|
func (h *HTTP) SetURI(uri string) {
|
|
h.uri = uri
|
|
}
|
|
|
|
// SetBody sets the body of the requests
|
|
func (h *HTTP) SetBody(body []byte) {
|
|
h.body = body
|
|
}
|
|
|
|
// FetchContent makes an HTTP request to the configured url and returns the body content.
|
|
func (h *HTTP) FetchContent() ([]byte, error) {
|
|
resp, err := h.FetchResponse()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, fmt.Errorf("HTTP error %d in %s: %s", resp.StatusCode, h.base.Name(), resp.Status)
|
|
}
|
|
|
|
return ioutil.ReadAll(resp.Body)
|
|
}
|
|
|
|
// FetchScanner returns a Scanner for the content.
|
|
func (h *HTTP) FetchScanner() (*bufio.Scanner, error) {
|
|
content, err := h.FetchContent()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return bufio.NewScanner(bytes.NewReader(content)), nil
|
|
}
|
|
|
|
// FetchJSON makes an HTTP request to the configured url and returns the JSON content.
|
|
// This only works if the JSON output needed is in map[string]interface format.
|
|
func (h *HTTP) FetchJSON() (map[string]interface{}, error) {
|
|
body, err := h.FetchContent()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var data map[string]interface{}
|
|
|
|
err = json.Unmarshal(body, &data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// getAuthHeaderFromToken reads a bearer authorizaiton token from the given file
|
|
func getAuthHeaderFromToken(path string) (string, error) {
|
|
var token string
|
|
|
|
b, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "reading bearer token file")
|
|
}
|
|
|
|
if len(b) != 0 {
|
|
if b[len(b)-1] == '\n' {
|
|
b = b[0 : len(b)-1]
|
|
}
|
|
token = fmt.Sprintf("Bearer %s", string(b))
|
|
}
|
|
|
|
return token, nil
|
|
}
|