github.com/thanos-io/thanos@v0.32.5/internal/cortex/util/http.go (about)

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