Mercurial > gemma
view pkg/middleware/jsonhandler.go @ 5625:39c1698fb0ff
Bumped dev version
author | Sascha Wilde <wilde@sha-bang.de> |
---|---|
date | Thu, 22 Dec 2022 18:43:46 +0100 |
parents | 5f47eeea988d |
children | 6270951dda28 |
line wrap: on
line source
// This is Free Software under GNU Affero General Public License v >= 3.0 // without warranty, see README.md and license for details. // // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // // Copyright (C) 2018 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann <sascha.teichmann@intevation.de> package middleware import ( "context" "database/sql" "encoding/json" "fmt" "io" "net/http" "github.com/jackc/pgx" "gemma.intevation.de/gemma/pkg/auth" "gemma.intevation.de/gemma/pkg/log" ) // JSONResult defines the return type of JSONHandler handler function. type JSONResult struct { // Code is the HTTP status code to be set which defaults to http.StatusOK (200). Code int // Result is serialized to JSON. // If the type is an io.Reader its copied through. Result interface{} } // JSONDefaultLimit is default size limit in bytes of an accepted // input document. const JSONDefaultLimit = 2048 // JSONHandler implements a middleware to ease the handing JSON input // streams and return JSON documents as output. type JSONHandler struct { // Input (if not nil) is called to fill a data structure // returned by this function. Input func(*http.Request) interface{} // Handle is called to handle the incoming HTTP request. // in is the data structure returned by Input. Its nil if Input is nil. Handle func(rep *http.Request) (JSONResult, error) // NoConn if set to true no database connection is established and // the conn parameter of the Handle call is nil. NoConn bool // Limit overides the default size of accepted input documents. // Set to a negative value to allow an arbitrary size. // Handle with care! Limit int64 } // JSONError is an error if returned by the JSONHandler.Handle function // which ends up encoded as a JSON document. type JSONError struct { // Code is the HTTP status code of the result defaults // to http.StatusInternalServerError if not set. Code int // The message of the error. Message string } // Error implements the error interface. func (je JSONError) Error() string { return fmt.Sprintf("%d: %s", je.Code, je.Message) } type jsonHandlerType int const ( jsonHandlerConnKey jsonHandlerType = iota jsonHandlerInputKey ) // JSONConn extracts the impersonated sql.Conn from the context of the request. func JSONConn(req *http.Request) *sql.Conn { if conn, ok := req.Context().Value(jsonHandlerConnKey).(*sql.Conn); ok { return conn } return nil } // JSONInput extracts the de-serialized input from the context of the request. func JSONInput(req *http.Request) interface{} { return req.Context().Value(jsonHandlerInputKey) } // ServeHTTP makes the JSONHandler a middleware. func (j *JSONHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if j.Input != nil { input := j.Input(req) defer req.Body.Close() var r io.Reader switch { case j.Limit == 0: r = io.LimitReader(req.Body, JSONDefaultLimit) case j.Limit > 0: r = io.LimitReader(req.Body, j.Limit) default: r = req.Body } if err := json.NewDecoder(r).Decode(input); err != nil { http.Error(rw, "error: "+err.Error(), http.StatusBadRequest) return } parent := req.Context() ctx := context.WithValue(parent, jsonHandlerInputKey, input) req = req.WithContext(ctx) } var jr JSONResult var err error if token, ok := auth.GetToken(req); ok && !j.NoConn { if session := auth.Sessions.Session(token); session != nil { parent := req.Context() err = auth.RunAs(parent, session.User, func(conn *sql.Conn) error { ctx := context.WithValue(parent, jsonHandlerConnKey, conn) req = req.WithContext(ctx) jr, err = j.Handle(req) return err }) } else { err = auth.ErrNoSuchToken } } else { jr, err = j.Handle(req) } if err != nil { log.Errorf("%v\n", err) switch e := err.(type) { case pgx.PgError: var res = struct { Result string `json:"result"` Code string `json:"code"` Message string `json:"message"` }{ Result: "failure", Code: e.Code, Message: e.Message, } rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(http.StatusInternalServerError) if err := json.NewEncoder(rw).Encode(&res); err != nil { log.Errorf("%v\n", err) } case JSONError: rw.Header().Set("Content-Type", "application/json") if e.Code == 0 { e.Code = http.StatusInternalServerError } rw.WriteHeader(e.Code) var res = struct { Message string `json:"message"` }{ Message: e.Message, } if err := json.NewEncoder(rw).Encode(&res); err != nil { log.Errorf("%v\n", err) } default: http.Error(rw, "error: "+err.Error(), http.StatusInternalServerError) } return } if jr.Code == 0 { jr.Code = http.StatusOK } if jr.Code != http.StatusNoContent { rw.Header().Set("Content-Type", "application/json") } rw.Header().Set("X-Content-Type-Options", "nosniff") rw.WriteHeader(jr.Code) if jr.Code != http.StatusNoContent { var err error if r, ok := jr.Result.(io.Reader); ok { _, err = io.Copy(rw, r) } else { err = json.NewEncoder(rw).Encode(jr.Result) } if err != nil { log.Errorf("%v\n", err) } } } // SendJSON sends data JSON encoded to the response writer // with a given HTTP status code. func SendJSON(rw http.ResponseWriter, code int, data interface{}) { rw.Header().Set("Content-Type", "application/json") rw.Header().Set("X-Content-Type-Options", "nosniff") rw.WriteHeader(code) if err := json.NewEncoder(rw).Encode(data); err != nil { log.Errorf("%v\n", err) } }