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 }