1
0
Fork 0
telegraf/plugins/inputs/http/http.go

267 lines
5.8 KiB
Go
Raw Normal View History

//go:generate ../../../tools/config_includer/generator
//go:generate ../../../tools/readme_config_includer/generator
package http
import (
"context"
_ "embed"
"errors"
"fmt"
"io"
"net/http"
"os"
"strings"
"sync"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/internal"
common_http "github.com/influxdata/telegraf/plugins/common/http"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
var once sync.Once
type HTTP struct {
URLs []string `toml:"urls"`
Method string `toml:"method"`
Body string `toml:"body"`
ContentEncoding string `toml:"content_encoding"`
// Basic authentication
Username config.Secret `toml:"username"`
Password config.Secret `toml:"password"`
// Bearer authentication
BearerToken string `toml:"bearer_token" deprecated:"1.28.0;1.35.0;use 'token_file' instead"`
Token config.Secret `toml:"token"`
TokenFile string `toml:"token_file"`
Headers map[string]*config.Secret `toml:"headers"`
SuccessStatusCodes []int `toml:"success_status_codes"`
Log telegraf.Logger `toml:"-"`
common_http.HTTPClientConfig
client *http.Client
parserFunc telegraf.ParserFunc
}
func (*HTTP) SampleConfig() string {
return sampleConfig
}
func (h *HTTP) Init() error {
// For backward compatibility
if h.TokenFile != "" && h.BearerToken != "" && h.TokenFile != h.BearerToken {
return errors.New("conflicting settings for 'bearer_token' and 'token_file'")
} else if h.TokenFile == "" && h.BearerToken != "" {
h.TokenFile = h.BearerToken
}
// We cannot use multiple sources for tokens
if h.TokenFile != "" && !h.Token.Empty() {
return errors.New("either use 'token_file' or 'token' not both")
}
// Create the client
ctx := context.Background()
client, err := h.HTTPClientConfig.CreateClient(ctx, h.Log)
if err != nil {
return err
}
h.client = client
// Set default as [200]
if len(h.SuccessStatusCodes) == 0 {
h.SuccessStatusCodes = []int{200}
}
return nil
}
func (h *HTTP) SetParserFunc(fn telegraf.ParserFunc) {
h.parserFunc = fn
}
func (*HTTP) Start(telegraf.Accumulator) error {
return nil
}
func (h *HTTP) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
for _, u := range h.URLs {
wg.Add(1)
go func(url string) {
defer wg.Done()
if err := h.gatherURL(acc, url); err != nil {
acc.AddError(fmt.Errorf("[url=%s]: %w", url, err))
}
}(u)
}
wg.Wait()
return nil
}
func (h *HTTP) Stop() {
if h.client != nil {
h.client.CloseIdleConnections()
}
}
// Gathers data from a particular URL
// Parameters:
//
// acc : The telegraf Accumulator to use
// url : endpoint to send request to
//
// Returns:
//
// error: Any error that may have occurred
func (h *HTTP) gatherURL(acc telegraf.Accumulator, url string) error {
body := makeRequestBodyReader(h.ContentEncoding, h.Body)
request, err := http.NewRequest(h.Method, url, body)
if err != nil {
return err
}
if !h.Token.Empty() {
token, err := h.Token.Get()
if err != nil {
return err
}
bearer := "Bearer " + strings.TrimSpace(token.String())
token.Destroy()
request.Header.Set("Authorization", bearer)
} else if h.TokenFile != "" {
token, err := os.ReadFile(h.TokenFile)
if err != nil {
return err
}
bearer := "Bearer " + strings.Trim(string(token), "\n")
request.Header.Set("Authorization", bearer)
}
if h.ContentEncoding == "gzip" {
request.Header.Set("Content-Encoding", "gzip")
}
for k, v := range h.Headers {
secret, err := v.Get()
if err != nil {
return err
}
headerVal := secret.String()
if strings.EqualFold(k, "host") {
request.Host = headerVal
} else {
request.Header.Add(k, headerVal)
}
secret.Destroy()
}
if err := h.setRequestAuth(request); err != nil {
return err
}
resp, err := h.client.Do(request)
if err != nil {
return err
}
defer resp.Body.Close()
responseHasSuccessCode := false
for _, statusCode := range h.SuccessStatusCodes {
if resp.StatusCode == statusCode {
responseHasSuccessCode = true
break
}
}
if !responseHasSuccessCode {
return fmt.Errorf("received status code %d (%s), expected any value out of %v",
resp.StatusCode,
http.StatusText(resp.StatusCode),
h.SuccessStatusCodes)
}
b, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("reading body failed: %w", err)
}
// Instantiate a new parser for the new data to avoid trouble with stateful parsers
parser, err := h.parserFunc()
if err != nil {
return fmt.Errorf("instantiating parser failed: %w", err)
}
metrics, err := parser.Parse(b)
if err != nil {
return fmt.Errorf("parsing metrics failed: %w", err)
}
if len(metrics) == 0 {
once.Do(func() {
h.Log.Debug(internal.NoMetricsCreatedMsg)
})
}
for _, metric := range metrics {
if !metric.HasTag("url") {
metric.AddTag("url", url)
}
acc.AddFields(metric.Name(), metric.Fields(), metric.Tags(), metric.Time())
}
return nil
}
func (h *HTTP) setRequestAuth(request *http.Request) error {
if h.Username.Empty() && h.Password.Empty() {
return nil
}
username, err := h.Username.Get()
if err != nil {
return fmt.Errorf("getting username failed: %w", err)
}
defer username.Destroy()
password, err := h.Password.Get()
if err != nil {
return fmt.Errorf("getting password failed: %w", err)
}
defer password.Destroy()
request.SetBasicAuth(username.String(), password.String())
return nil
}
func makeRequestBodyReader(contentEncoding, body string) io.Reader {
if body == "" {
return nil
}
var reader io.Reader = strings.NewReader(body)
if contentEncoding == "gzip" {
return internal.CompressWithGzip(reader)
}
return reader
}
func init() {
inputs.Add("http", func() telegraf.Input {
return &HTTP{
Method: "GET",
}
})
}