Mercurial > gemma
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 } |