Files
mostlymatter/server/playbooks/server/api/api.go
Agniva De Sarker e09d5690a7 Optimize JSON marshalling in playbooks (#22735)
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
```
2023-03-30 23:40:36 +05:30

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)
})
}