comparison pkg/middleware/jsonhandler.go @ 4244:4394daeea96a json-handler-middleware

Moved JSONHandler into middleware package.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Thu, 22 Aug 2019 11:26:48 +0200
parents pkg/controllers/json.go@d776110b4db0
children f4ec3558460e
comparison
equal deleted inserted replaced
4243:d776110b4db0 4244:4394daeea96a
1 // This is Free Software under GNU Affero General Public License v >= 3.0
2 // without warranty, see README.md and license for details.
3 //
4 // SPDX-License-Identifier: AGPL-3.0-or-later
5 // License-Filename: LICENSES/AGPL-3.0.txt
6 //
7 // Copyright (C) 2018 by via donau
8 // – Österreichische Wasserstraßen-Gesellschaft mbH
9 // Software engineering by Intevation GmbH
10 //
11 // Author(s):
12 // * Sascha L. Teichmann <sascha.teichmann@intevation.de>
13
14 package middleware
15
16 import (
17 "context"
18 "database/sql"
19 "encoding/json"
20 "fmt"
21 "io"
22 "log"
23 "net/http"
24
25 "github.com/jackc/pgx"
26
27 "gemma.intevation.de/gemma/pkg/auth"
28 )
29
30 // JSONResult defines the return type of JSONHandler handler function.
31 type JSONResult struct {
32 // Code is the HTTP status code to be set which defaults to http.StatusOK (200).
33 Code int
34 // Result is serialized to JSON.
35 // If the type is an io.Reader its copied through.
36 Result interface{}
37 }
38
39 // JSONDefaultLimit is default size limit in bytes of an accepted
40 // input document.
41 const JSONDefaultLimit = 2048
42
43 // JSONHandler implements a middleware to ease the handing JSON input
44 // streams and return JSON documents as output.
45 type JSONHandler struct {
46 // Input (if not nil) is called to fill a data structure
47 // returned by this function.
48 Input func(*http.Request) interface{}
49 // Handle is called to handle the incoming HTTP request.
50 // in is the data structure returned by Input. Its nil if Input is nil.
51 Handle func(rep *http.Request) (JSONResult, error)
52 // NoConn if set to true no database connection is established and
53 // the conn parameter of the Handle call is nil.
54 NoConn bool
55 // Limit overides the default size of accepted input documents.
56 // Set to a negative value to allow an arbitrary size.
57 // Handle with care!
58 Limit int64
59 }
60
61 // JSONError is an error if returned by the JSONHandler.Handle function
62 // which ends up encoded as a JSON document.
63 type JSONError struct {
64 // Code is the HTTP status code of the result defaults
65 // to http.StatusInternalServerError if not set.
66 Code int
67 // The message of the error.
68 Message string
69 }
70
71 // Error implements the error interface.
72 func (je JSONError) Error() string {
73 return fmt.Sprintf("%d: %s", je.Code, je.Message)
74 }
75
76 type jsonHandlerType int
77
78 const (
79 jsonHandlerConnKey jsonHandlerType = iota
80 jsonHandlerInputKey
81 )
82
83 // JSONConn extracts the impersonated sql.Conn from the context of the request.
84 func JSONConn(req *http.Request) *sql.Conn {
85 if conn, ok := req.Context().Value(jsonHandlerConnKey).(*sql.Conn); ok {
86 return conn
87 }
88 return nil
89 }
90
91 // JSONInput extracts the de-serialized input from the context of the request.
92 func JSONInput(req *http.Request) interface{} {
93 return req.Context().Value(jsonHandlerInputKey)
94 }
95
96 // ServeHTTP makes the JSONHandler a middleware.
97 func (j *JSONHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
98
99 if j.Input != nil {
100 input := j.Input(req)
101 defer req.Body.Close()
102 var r io.Reader
103 switch {
104 case j.Limit == 0:
105 r = io.LimitReader(req.Body, JSONDefaultLimit)
106 case j.Limit > 0:
107 r = io.LimitReader(req.Body, j.Limit)
108 default:
109 r = req.Body
110 }
111 if err := json.NewDecoder(r).Decode(input); err != nil {
112 http.Error(rw, "error: "+err.Error(), http.StatusBadRequest)
113 return
114 }
115 parent := req.Context()
116 ctx := context.WithValue(parent, jsonHandlerInputKey, input)
117 req = req.WithContext(ctx)
118 }
119
120 var jr JSONResult
121 var err error
122
123 if token, ok := auth.GetToken(req); ok && !j.NoConn {
124 if session := auth.Sessions.Session(token); session != nil {
125 parent := req.Context()
126 err = auth.RunAs(parent, session.User, func(conn *sql.Conn) error {
127 ctx := context.WithValue(parent, jsonHandlerConnKey, conn)
128 req = req.WithContext(ctx)
129 jr, err = j.Handle(req)
130 return err
131 })
132 } else {
133 err = auth.ErrNoSuchToken
134 }
135 } else {
136 jr, err = j.Handle(req)
137 }
138
139 if err != nil {
140 log.Printf("error: %v\n", err)
141 switch e := err.(type) {
142 case pgx.PgError:
143 var res = struct {
144 Result string `json:"result"`
145 Code string `json:"code"`
146 Message string `json:"message"`
147 }{
148 Result: "failure",
149 Code: e.Code,
150 Message: e.Message,
151 }
152 rw.Header().Set("Content-Type", "application/json")
153 rw.WriteHeader(http.StatusInternalServerError)
154 if err := json.NewEncoder(rw).Encode(&res); err != nil {
155 log.Printf("error: %v\n", err)
156 }
157 case JSONError:
158 rw.Header().Set("Content-Type", "application/json")
159 if e.Code == 0 {
160 e.Code = http.StatusInternalServerError
161 }
162 rw.WriteHeader(e.Code)
163 var res = struct {
164 Message string `json:"message"`
165 }{
166 Message: e.Message,
167 }
168 if err := json.NewEncoder(rw).Encode(&res); err != nil {
169 log.Printf("error: %v\n", err)
170 }
171 default:
172 http.Error(rw,
173 "error: "+err.Error(),
174 http.StatusInternalServerError)
175 }
176 return
177 }
178
179 if jr.Code == 0 {
180 jr.Code = http.StatusOK
181 }
182
183 if jr.Code != http.StatusNoContent {
184 rw.Header().Set("Content-Type", "application/json")
185 }
186 rw.WriteHeader(jr.Code)
187 if jr.Code != http.StatusNoContent {
188 var err error
189 if r, ok := jr.Result.(io.Reader); ok {
190 _, err = io.Copy(rw, r)
191 } else {
192 err = json.NewEncoder(rw).Encode(jr.Result)
193 }
194 if err != nil {
195 log.Printf("error: %v\n", err)
196 }
197 }
198 }
199
200 // SendJSON sends data JSON encoded to the response writer
201 // with a given HTTP status code.
202 func SendJSON(rw http.ResponseWriter, code int, data interface{}) {
203 rw.Header().Set("Content-Type", "application/json")
204 rw.WriteHeader(code)
205 if err := json.NewEncoder(rw).Encode(data); err != nil {
206 log.Printf("error: %v\n", err)
207 }
208 }