github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/util/http.go (about)

     1  package util
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"flag"
     8  	"fmt"
     9  	"html/template"
    10  	"io"
    11  	"net/http"
    12  	"net/url"
    13  	"strings"
    14  
    15  	"github.com/go-kit/log"
    16  	"github.com/go-kit/log/level"
    17  	"github.com/gogo/protobuf/proto"
    18  	"github.com/golang/snappy"
    19  	"github.com/opentracing/opentracing-go"
    20  	otlog "github.com/opentracing/opentracing-go/log"
    21  	"gopkg.in/yaml.v2"
    22  )
    23  
    24  const messageSizeLargerErrFmt = "received message larger than max (%d vs %d)"
    25  
    26  // IsRequestBodyTooLarge returns true if the error is "http: request body too large".
    27  func IsRequestBodyTooLarge(err error) bool {
    28  	return err != nil && strings.Contains(err.Error(), "http: request body too large")
    29  }
    30  
    31  // BasicAuth configures basic authentication for HTTP clients.
    32  type BasicAuth struct {
    33  	Username string `yaml:"basic_auth_username"`
    34  	Password string `yaml:"basic_auth_password"`
    35  }
    36  
    37  func (b *BasicAuth) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
    38  	f.StringVar(&b.Username, prefix+"basic-auth-username", "", "HTTP Basic authentication username. It overrides the username set in the URL (if any).")
    39  	f.StringVar(&b.Password, prefix+"basic-auth-password", "", "HTTP Basic authentication password. It overrides the password set in the URL (if any).")
    40  }
    41  
    42  // IsEnabled returns false if basic authentication isn't enabled.
    43  func (b BasicAuth) IsEnabled() bool {
    44  	return b.Username != "" || b.Password != ""
    45  }
    46  
    47  // HeaderAuth condigures header based authorization for HTTP clients.
    48  type HeaderAuth struct {
    49  	Type            string `yaml:"type,omitempty"`
    50  	Credentials     string `yaml:"credentials,omitempty"`
    51  	CredentialsFile string `yaml:"credentials_file,omitempty"`
    52  }
    53  
    54  func (h *HeaderAuth) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
    55  	f.StringVar(&h.Type, prefix+"type", "Bearer", "HTTP Header authorization type (default: Bearer).")
    56  	f.StringVar(&h.Credentials, prefix+"credentials", "", "HTTP Header authorization credentials.")
    57  	f.StringVar(&h.CredentialsFile, prefix+"credentials-file", "", "HTTP Header authorization credentials file.")
    58  }
    59  
    60  // IsEnabled returns false if header authorization isn't enabled.
    61  func (h HeaderAuth) IsEnabled() bool {
    62  	return h.Credentials != "" || h.CredentialsFile != ""
    63  }
    64  
    65  // WriteJSONResponse writes some JSON as a HTTP response.
    66  func WriteJSONResponse(w http.ResponseWriter, v interface{}) {
    67  	w.Header().Set("Content-Type", "application/json")
    68  
    69  	data, err := json.Marshal(v)
    70  	if err != nil {
    71  		http.Error(w, err.Error(), http.StatusInternalServerError)
    72  		return
    73  	}
    74  
    75  	// We ignore errors here, because we cannot do anything about them.
    76  	// Write will trigger sending Status code, so we cannot send a different status code afterwards.
    77  	// Also this isn't internal error, but error communicating with client.
    78  	_, _ = w.Write(data)
    79  }
    80  
    81  // WriteYAMLResponse writes some YAML as a HTTP response.
    82  func WriteYAMLResponse(w http.ResponseWriter, v interface{}) {
    83  	// There is not standardised content-type for YAML, text/plain ensures the
    84  	// YAML is displayed in the browser instead of offered as a download
    85  	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    86  
    87  	data, err := yaml.Marshal(v)
    88  	if err != nil {
    89  		http.Error(w, err.Error(), http.StatusInternalServerError)
    90  		return
    91  	}
    92  
    93  	// We ignore errors here, because we cannot do anything about them.
    94  	// Write will trigger sending Status code, so we cannot send a different status code afterwards.
    95  	// Also this isn't internal error, but error communicating with client.
    96  	_, _ = w.Write(data)
    97  }
    98  
    99  // Sends message as text/plain response with 200 status code.
   100  func WriteTextResponse(w http.ResponseWriter, message string) {
   101  	w.Header().Set("Content-Type", "text/plain")
   102  
   103  	// Ignore inactionable errors.
   104  	_, _ = w.Write([]byte(message))
   105  }
   106  
   107  // Sends message as text/html response with 200 status code.
   108  func WriteHTMLResponse(w http.ResponseWriter, message string) {
   109  	w.Header().Set("Content-Type", "text/html")
   110  
   111  	// Ignore inactionable errors.
   112  	_, _ = w.Write([]byte(message))
   113  }
   114  
   115  // RenderHTTPResponse either responds with json or a rendered html page using the passed in template
   116  // by checking the Accepts header
   117  func RenderHTTPResponse(w http.ResponseWriter, v interface{}, t *template.Template, r *http.Request) {
   118  	accept := r.Header.Get("Accept")
   119  	if strings.Contains(accept, "application/json") {
   120  		WriteJSONResponse(w, v)
   121  		return
   122  	}
   123  
   124  	err := t.Execute(w, v)
   125  	if err != nil {
   126  		http.Error(w, err.Error(), http.StatusInternalServerError)
   127  	}
   128  }
   129  
   130  // StreamWriteYAMLResponse stream writes data as http response
   131  func StreamWriteYAMLResponse(w http.ResponseWriter, iter chan interface{}, logger log.Logger) {
   132  	w.Header().Set("Content-Type", "application/yaml")
   133  	for v := range iter {
   134  		data, err := yaml.Marshal(v)
   135  		if err != nil {
   136  			level.Error(logger).Log("msg", "yaml marshal failed", "err", err)
   137  			continue
   138  		}
   139  		_, err = w.Write(data)
   140  		if err != nil {
   141  			level.Error(logger).Log("msg", "write http response failed", "err", err)
   142  			return
   143  		}
   144  	}
   145  }
   146  
   147  // CompressionType for encoding and decoding requests and responses.
   148  type CompressionType int
   149  
   150  // Values for CompressionType
   151  const (
   152  	NoCompression CompressionType = iota
   153  	RawSnappy
   154  )
   155  
   156  // ParseProtoReader parses a compressed proto from an io.Reader.
   157  func ParseProtoReader(ctx context.Context, reader io.Reader, expectedSize, maxSize int, req proto.Message, compression CompressionType) error {
   158  	sp := opentracing.SpanFromContext(ctx)
   159  	if sp != nil {
   160  		sp.LogFields(otlog.String("event", "util.ParseProtoRequest[start reading]"))
   161  	}
   162  	body, err := decompressRequest(reader, expectedSize, maxSize, compression, sp)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	if sp != nil {
   168  		sp.LogFields(otlog.String("event", "util.ParseProtoRequest[unmarshal]"), otlog.Int("size", len(body)))
   169  	}
   170  
   171  	// We re-implement proto.Unmarshal here as it calls XXX_Unmarshal first,
   172  	// which we can't override without upsetting golint.
   173  	req.Reset()
   174  	if u, ok := req.(proto.Unmarshaler); ok {
   175  		err = u.Unmarshal(body)
   176  	} else {
   177  		err = proto.NewBuffer(body).Unmarshal(req)
   178  	}
   179  	if err != nil {
   180  		return err
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  func decompressRequest(reader io.Reader, expectedSize, maxSize int, compression CompressionType, sp opentracing.Span) (body []byte, err error) {
   187  	defer func() {
   188  		if err != nil && len(body) > maxSize {
   189  			err = fmt.Errorf(messageSizeLargerErrFmt, len(body), maxSize)
   190  		}
   191  	}()
   192  	if expectedSize > maxSize {
   193  		return nil, fmt.Errorf(messageSizeLargerErrFmt, expectedSize, maxSize)
   194  	}
   195  	buffer, ok := tryBufferFromReader(reader)
   196  	if ok {
   197  		body, err = decompressFromBuffer(buffer, maxSize, compression, sp)
   198  		return
   199  	}
   200  	body, err = decompressFromReader(reader, expectedSize, maxSize, compression, sp)
   201  	return
   202  }
   203  
   204  func decompressFromReader(reader io.Reader, expectedSize, maxSize int, compression CompressionType, sp opentracing.Span) ([]byte, error) {
   205  	var (
   206  		buf  bytes.Buffer
   207  		body []byte
   208  		err  error
   209  	)
   210  	if expectedSize > 0 {
   211  		buf.Grow(expectedSize + bytes.MinRead) // extra space guarantees no reallocation
   212  	}
   213  	// Read from LimitReader with limit max+1. So if the underlying
   214  	// reader is over limit, the result will be bigger than max.
   215  	reader = io.LimitReader(reader, int64(maxSize)+1)
   216  	switch compression {
   217  	case NoCompression:
   218  		_, err = buf.ReadFrom(reader)
   219  		body = buf.Bytes()
   220  	case RawSnappy:
   221  		_, err = buf.ReadFrom(reader)
   222  		if err != nil {
   223  			return nil, err
   224  		}
   225  		body, err = decompressFromBuffer(&buf, maxSize, RawSnappy, sp)
   226  	}
   227  	return body, err
   228  }
   229  
   230  func decompressFromBuffer(buffer *bytes.Buffer, maxSize int, compression CompressionType, sp opentracing.Span) ([]byte, error) {
   231  	if len(buffer.Bytes()) > maxSize {
   232  		return nil, fmt.Errorf(messageSizeLargerErrFmt, len(buffer.Bytes()), maxSize)
   233  	}
   234  	switch compression {
   235  	case NoCompression:
   236  		return buffer.Bytes(), nil
   237  	case RawSnappy:
   238  		if sp != nil {
   239  			sp.LogFields(otlog.String("event", "util.ParseProtoRequest[decompress]"),
   240  				otlog.Int("size", len(buffer.Bytes())))
   241  		}
   242  		size, err := snappy.DecodedLen(buffer.Bytes())
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  		if size > maxSize {
   247  			return nil, fmt.Errorf(messageSizeLargerErrFmt, size, maxSize)
   248  		}
   249  		body, err := snappy.Decode(nil, buffer.Bytes())
   250  		if err != nil {
   251  			return nil, err
   252  		}
   253  		return body, nil
   254  	}
   255  	return nil, nil
   256  }
   257  
   258  // tryBufferFromReader attempts to cast the reader to a `*bytes.Buffer` this is possible when using httpgrpc.
   259  // If it fails it will return nil and false.
   260  func tryBufferFromReader(reader io.Reader) (*bytes.Buffer, bool) {
   261  	if bufReader, ok := reader.(interface {
   262  		BytesBuffer() *bytes.Buffer
   263  	}); ok && bufReader != nil {
   264  		return bufReader.BytesBuffer(), true
   265  	}
   266  	return nil, false
   267  }
   268  
   269  // SerializeProtoResponse serializes a protobuf response into an HTTP response.
   270  func SerializeProtoResponse(w http.ResponseWriter, resp proto.Message, compression CompressionType) error {
   271  	data, err := proto.Marshal(resp)
   272  	if err != nil {
   273  		http.Error(w, err.Error(), http.StatusInternalServerError)
   274  		return fmt.Errorf("error marshaling proto response: %v", err)
   275  	}
   276  
   277  	switch compression {
   278  	case NoCompression:
   279  	case RawSnappy:
   280  		data = snappy.Encode(nil, data)
   281  	}
   282  
   283  	if _, err := w.Write(data); err != nil {
   284  		http.Error(w, err.Error(), http.StatusInternalServerError)
   285  		return fmt.Errorf("error sending proto response: %v", err)
   286  	}
   287  	return nil
   288  }
   289  
   290  func FlagFromValues(values url.Values, key string, d bool) bool {
   291  	switch strings.ToLower(values.Get(key)) {
   292  	case "t", "true", "1":
   293  		return true
   294  	case "f", "false", "0":
   295  		return false
   296  	default:
   297  		return d
   298  	}
   299  }