While inspecting Pyroscope, the function api.ReturnJSON was seen to take some chunk of the overall memory allocations. Looking deeper, we can see that the entire object is json marshalled into bytes, and then wrote to the network. Instead, we can just do a streaming write to the network which avoids pre-allocating the entire object. A similar approach is taken in the server channels codebase as well. ```release-note NONE ```
115 строки
3.4 KiB
Go
115 строки
3.4 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/gorilla/mux"
|
|
"github.com/mattermost/mattermost-server/v6/server/playbooks/server/config"
|
|
)
|
|
|
|
// MaxRequestSize is the size limit for any incoming request
|
|
// The default limit set by mattermost-server is the configured max file size, and
|
|
// it sometimes isn't small enough to prevent some scenarios.
|
|
//
|
|
// This is important to prevent huge payloads from being sent
|
|
// that could end in a bigger problem.
|
|
//
|
|
// If an endpoint needs a smaller limit than this one, it could be solved by adding their
|
|
// own limit BEFORE reading the request body `r.Body = http.MaxBytesReader(w, r.Body, MaxRequestSize)`
|
|
const MaxRequestSize = 5 * 1024 * 1024 // 5MB
|
|
|
|
// Handler Root API handler.
|
|
type Handler struct {
|
|
*ErrorHandler
|
|
APIRouter *mux.Router
|
|
root *mux.Router
|
|
config config.Service
|
|
}
|
|
|
|
// NewHandler constructs a new handler.
|
|
func NewHandler(config config.Service) *Handler {
|
|
handler := &Handler{
|
|
ErrorHandler: &ErrorHandler{},
|
|
config: config,
|
|
}
|
|
|
|
root := mux.NewRouter()
|
|
api := root.PathPrefix("/api/v0").Subrouter()
|
|
api.Use(LogRequest)
|
|
api.Use(MattermostAuthorizationRequired)
|
|
|
|
api.Handle("{anything:.*}", http.NotFoundHandler())
|
|
api.NotFoundHandler = http.NotFoundHandler()
|
|
|
|
handler.APIRouter = api
|
|
handler.root = root
|
|
handler.config = config
|
|
|
|
return handler
|
|
}
|
|
|
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
r.Body = http.MaxBytesReader(w, r.Body, MaxRequestSize)
|
|
h.root.ServeHTTP(w, r)
|
|
}
|
|
|
|
// handleResponseWithCode logs the internal error and sends the public facing error
|
|
// message as JSON in a response with the provided code.
|
|
func handleResponseWithCode(w http.ResponseWriter, code int, publicMsg string) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(code)
|
|
|
|
responseMsg, _ := json.Marshal(struct {
|
|
Error string `json:"error"` // A public facing message providing details about the error.
|
|
}{
|
|
Error: publicMsg,
|
|
})
|
|
_, _ = w.Write(responseMsg)
|
|
}
|
|
|
|
// HandleErrorWithCode logs the internal error and sends the public facing error
|
|
// message as JSON in a response with the provided code.
|
|
func HandleErrorWithCode(logger logrus.FieldLogger, w http.ResponseWriter, code int, publicErrorMsg string, internalErr error) {
|
|
if internalErr != nil {
|
|
logger = logger.WithError(internalErr)
|
|
}
|
|
|
|
if code >= http.StatusInternalServerError {
|
|
logger.Error(publicErrorMsg)
|
|
} else {
|
|
logger.Warn(publicErrorMsg)
|
|
}
|
|
|
|
handleResponseWithCode(w, code, publicErrorMsg)
|
|
}
|
|
|
|
// ReturnJSON writes the given pointerToObject as json with the provided httpStatus
|
|
func ReturnJSON(w http.ResponseWriter, pointerToObject interface{}, httpStatus int) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(httpStatus)
|
|
|
|
if err := json.NewEncoder(w).Encode(pointerToObject); err != nil {
|
|
logrus.WithError(err).Warn("Unable to write to http.ResponseWriter")
|
|
return
|
|
}
|
|
}
|
|
|
|
// MattermostAuthorizationRequired checks if request is authorized.
|
|
func MattermostAuthorizationRequired(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
userID := r.Header.Get("Mattermost-User-Id")
|
|
if userID != "" {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
|
})
|
|
}
|