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 }