github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/server/httputils/default.go (about)

     1  package httputils
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/gorilla/mux"
    13  	"github.com/hashicorp/go-multierror"
    14  	"github.com/pyroscope-io/pyroscope/pkg/model"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  type DefaultImpl struct {
    19  	logger logrus.FieldLogger
    20  }
    21  
    22  func NewDefaultHelper(logger logrus.FieldLogger) *DefaultImpl {
    23  	return &DefaultImpl{
    24  		logger: logger,
    25  	}
    26  }
    27  
    28  func (*DefaultImpl) MustJSON(_ *http.Request, w http.ResponseWriter, v interface{}) {
    29  	resp, err := json.Marshal(v)
    30  	if err != nil {
    31  		panic(err)
    32  	}
    33  	w.Header().Set("Content-Type", "application/json")
    34  	_, _ = w.Write(resp)
    35  }
    36  
    37  func (*DefaultImpl) mustJSONError(_ *http.Request, w http.ResponseWriter, code int, v interface{}) {
    38  	resp, err := json.Marshal(v)
    39  	if err != nil {
    40  		panic(err)
    41  	}
    42  	w.Header().Set("Content-Type", "application/json")
    43  	w.WriteHeader(code)
    44  	_, _ = w.Write(resp)
    45  }
    46  
    47  // HandleError replies to the request with an appropriate message as
    48  // JSON-encoded body and writes a corresponding message to the log
    49  // with debug log level.
    50  //
    51  // Any error of a type not defined in this package or pkg/model, will be
    52  // treated as an internal server error causing response code 500. Such
    53  // errors are not sent but only logged with error log level.
    54  func (d *DefaultImpl) HandleError(r *http.Request, w http.ResponseWriter, err error) {
    55  	d.ErrorCode(r, w, d.Logger(r), err, -1)
    56  }
    57  
    58  // ErrorCode replies to the request with the specified error message
    59  // as JSON-encoded body and writes corresponding message to the log.
    60  //
    61  // If HTTP code is less than or equal zero, it will be deduced based on
    62  // the error. If it fails, StatusInternalServerError will be returned
    63  // without the response body. The error can be of 'multierror.Error' type.
    64  //
    65  // The call writes messages with the debug log level except the case
    66  // when the code is StatusInternalServerError which is logged as an error.
    67  //
    68  // It does not end the HTTP request; the caller should ensure no further
    69  // writes are done to w.
    70  func (d *DefaultImpl) ErrorCode(r *http.Request, w http.ResponseWriter, logger logrus.FieldLogger, err error, code int) {
    71  	switch {
    72  	case err == nil:
    73  		return
    74  	case code > 0:
    75  	case model.IsAuthenticationError(err):
    76  		code = http.StatusUnauthorized
    77  		err = model.ErrCredentialsInvalid
    78  	case model.IsAuthorizationError(err):
    79  		code = http.StatusForbidden
    80  	case model.IsValidationError(err):
    81  		code = http.StatusBadRequest
    82  	case model.IsNotFoundError(err):
    83  		code = http.StatusNotFound
    84  	case IsJSONError(err):
    85  		code = http.StatusBadRequest
    86  		switch {
    87  		case errors.Is(err, io.EOF):
    88  			err = ErrRequestBodyRequired
    89  		case errors.Is(err, io.ErrUnexpectedEOF):
    90  			// https://github.com/golang/go/issues/25956
    91  			err = ErrRequestBodyJSONInvalid
    92  		}
    93  	default:
    94  		// No response code provided and it can't be determined.
    95  		code = http.StatusInternalServerError
    96  	}
    97  
    98  	var e Errors
    99  	if m := new(multierror.Error); errors.As(err, &m) {
   100  		m.ErrorFormat = listFormatFunc
   101  		for _, x := range m.Errors {
   102  			e.Errors = append(e.Errors, x.Error())
   103  		}
   104  	} else {
   105  		e.Errors = []string{err.Error()}
   106  	}
   107  
   108  	if logger != nil {
   109  		// Internal errors must not be shown to users but
   110  		// logged with error log level.
   111  		logger = logger.WithError(err).WithField("code", code)
   112  		msg := strings.ToLower(http.StatusText(code))
   113  		if code == http.StatusInternalServerError {
   114  			w.WriteHeader(code)
   115  			logger.Error(msg)
   116  			return
   117  		}
   118  		logger.Debug(msg)
   119  	}
   120  
   121  	d.mustJSONError(r, w, code, e)
   122  }
   123  
   124  var (
   125  	ErrParamIDRequired        = model.ValidationError{Err: errors.New("id parameter is required")}
   126  	ErrRequestBodyRequired    = model.ValidationError{Err: errors.New("request body required")}
   127  	ErrRequestBodyJSONInvalid = model.ValidationError{Err: errors.New("request body contains malformed JSON")}
   128  )
   129  
   130  type Errors struct {
   131  	Errors []string `json:"errors"`
   132  }
   133  
   134  func listFormatFunc(es []error) string {
   135  	if len(es) == 1 {
   136  		return es[0].Error()
   137  	}
   138  	points := make([]string, len(es))
   139  	for i, err := range es {
   140  		points[i] = err.Error()
   141  	}
   142  	return strings.Join(points, "; ")
   143  }
   144  
   145  func (*DefaultImpl) IDFromRequest(r *http.Request) (uint, error) {
   146  	v, ok := mux.Vars(r)["id"]
   147  	if !ok {
   148  		return 0, ErrParamIDRequired
   149  	}
   150  	id, err := strconv.ParseUint(v, 10, 0)
   151  	if err != nil {
   152  		return 0, model.ValidationError{Err: fmt.Errorf("id parameter is invalid: %w", err)}
   153  	}
   154  	return uint(id), nil
   155  }
   156  
   157  // Logger creates a new logger scoped to the request
   158  // and enriches it with the known fields.
   159  func (d *DefaultImpl) Logger(r *http.Request) logrus.FieldLogger {
   160  	fields := logrus.Fields{
   161  		"url":    r.URL.String(),
   162  		"method": r.Method,
   163  		"remote": r.RemoteAddr,
   164  	}
   165  	u, ok := model.UserFromContext(r.Context())
   166  	if ok {
   167  		fields["user"] = u.Name
   168  	}
   169  	var k model.APIKey
   170  	k, ok = model.APIKeyFromContext(r.Context())
   171  	if ok {
   172  		fields["api_key"] = k.Name
   173  	}
   174  	return d.logger.WithFields(fields)
   175  }
   176  
   177  func (d *DefaultImpl) WriteResponseJSON(r *http.Request, w http.ResponseWriter, res interface{}) {
   178  	w.Header().Set("Content-Type", "application/json")
   179  	if err := json.NewEncoder(w).Encode(res); err != nil {
   180  		d.WriteJSONEncodeError(r, w, err)
   181  	}
   182  }
   183  
   184  func (*DefaultImpl) WriteResponseFile(_ *http.Request, w http.ResponseWriter, filename string, content []byte) {
   185  	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%v", filename))
   186  	w.Header().Set("Content-Type", "application/octet-stream")
   187  	w.Write(content)
   188  	w.(http.Flusher).Flush()
   189  }
   190  
   191  func (d *DefaultImpl) WriteInvalidMethodError(r *http.Request, w http.ResponseWriter) {
   192  	d.writeErrorMessage(r, w, http.StatusMethodNotAllowed, "method not allowed")
   193  }
   194  
   195  func (d *DefaultImpl) WriteInvalidParameterError(r *http.Request, w http.ResponseWriter, err error) {
   196  	d.WriteError(r, w, http.StatusBadRequest, err, "invalid parameter")
   197  }
   198  
   199  func (d *DefaultImpl) WriteInternalServerError(r *http.Request, w http.ResponseWriter, err error, msg string) {
   200  	d.WriteError(r, w, http.StatusInternalServerError, err, msg)
   201  }
   202  
   203  func (d *DefaultImpl) WriteJSONEncodeError(r *http.Request, w http.ResponseWriter, err error) {
   204  	d.WriteInternalServerError(r, w, err, "encoding response body")
   205  }
   206  
   207  func (d *DefaultImpl) WriteError(r *http.Request, w http.ResponseWriter, code int, err error, msg string) {
   208  	d.logger.WithError(err).Error(msg)
   209  	d.writeMessage(r, w, code, "%s: %q", msg, err)
   210  }
   211  
   212  func (d *DefaultImpl) writeErrorMessage(r *http.Request, w http.ResponseWriter, code int, msg string) {
   213  	d.logger.Error(msg)
   214  	d.writeMessage(r, w, code, msg)
   215  }
   216  
   217  func (*DefaultImpl) writeMessage(_ *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
   218  	w.WriteHeader(code)
   219  	_, _ = fmt.Fprintf(w, format, args...)
   220  	_, _ = fmt.Fprintln(w)
   221  }