OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package epfrontend | |
6 | |
7 import ( | |
8 "bytes" | |
9 "encoding/json" | |
10 "fmt" | |
11 "net/http" | |
12 | |
13 "github.com/GoogleCloudPlatform/go-endpoints/endpoints" | |
14 ) | |
15 | |
16 type outerError struct { | |
17 Error *innerError `json:"error"` | |
18 } | |
19 | |
20 type innerError struct { | |
21 Code int `json:"code"` | |
22 Errors []*errorInstance `json:"errors"` | |
23 Message string `json:"message"` | |
24 } | |
25 | |
26 type errorInstance struct { | |
27 Domain string `json:"domain"` | |
28 Message string `json:"message"` | |
29 Reason string `json:"reason"` | |
30 } | |
31 | |
32 // endpointsErrorResponse is a copy of go-endpoints' errorResponse struct. | |
33 type endpointsErrorResponse struct { | |
34 // Currently always "APPLICATION_ERROR" | |
35 State string `json:"state"` | |
36 Name string `json:"error_name"` | |
37 Msg string `json:"error_message,omitempty"` | |
38 Code int `json:"-"` | |
39 } | |
40 | |
41 // errorResponseWriter is an http.ResponseWriter implementation that captures | |
42 // endpoints errors that are written to it and emits them as error JSON. | |
43 // | |
44 // This fulfills the purpose of collecting an error response, generated either | |
45 // by the frontend processing code (e.g., could not find method) or the backend | |
46 // handling code (e.g., endpoint returned an error, JSON unmarshal, etc.) and | |
47 // boxing it into a frontend error response structure. | |
48 // | |
49 // If the frontend handler encounters an error, it will be set via setError. | |
50 // | |
51 // If the backend code generates an error, it will be captured as follows: | |
52 // 1) The backend will invoke WriteHeader with an error status. That will enable | |
53 // response buffering. | |
54 // 2) Write will be called to write out the backend error response JSON. This | |
55 // will be captured for deconstruction. | |
56 // | |
57 // In either case, the error state will be forwarded to the real underlying | |
58 // ResponseWriter via forwardError. If no error is assigned, all operations | |
59 // essentially pass through to the underlying ResponseWriter, making this very | |
60 // low overhead in the standard case. | |
61 type errorResponseWriter struct { | |
62 http.ResponseWriter | |
63 | |
64 status int | |
65 inst *errorInstance | |
66 buf bytes.Buffer | |
67 } | |
68 | |
69 func (w *errorResponseWriter) WriteHeader(status int) { | |
70 if w.status != 0 { | |
71 return | |
72 } | |
73 | |
74 // If we have an error status (>= 400) | |
75 w.status = status | |
76 if w.status >= http.StatusBadRequest { | |
77 w.status, w.inst = w.translateReason(status) | |
78 } | |
79 w.ResponseWriter.WriteHeader(w.status) | |
80 } | |
81 | |
82 func (w *errorResponseWriter) Write(d []byte) (int, error) { | |
83 // If we have an error status. | |
84 if w.inst != nil { | |
85 return w.buf.Write(d) | |
86 } | |
87 return w.ResponseWriter.Write(d) | |
88 } | |
89 | |
90 // setError explicitly configures an error response. | |
91 // | |
92 // This is used by the frontend ServeHTTP code when an error is encountered | |
93 // prior to calling into the backend. If the backend is invoked, setError will | |
94 // not be called. | |
95 func (w *errorResponseWriter) setError(err error) { | |
96 e, ok := err.(*endpoints.APIError) | |
97 if !ok { | |
98 e = &endpoints.APIError{ | |
99 Msg: err.Error(), | |
100 Code: http.StatusInternalServerError, | |
101 } | |
102 } | |
103 w.WriteHeader(e.Code) | |
104 w.inst.Message = err.Error() | |
105 } | |
106 | |
107 func (w *errorResponseWriter) forwardError() bool { | |
108 if w.inst == nil { | |
109 // No buffered error; leave things be. | |
110 return false | |
111 } | |
112 | |
113 ierr := innerError{ | |
114 Code: w.status, | |
115 Message: "unspecified error", | |
116 Errors: []*errorInstance{w.inst}, | |
117 } | |
118 if w.inst.Message == "" { | |
119 // Attempt to load the message from the backend endpoints error
JSON. | |
120 if w.buf.Len() > 0 { | |
121 resp := endpointsErrorResponse{} | |
122 if err := json.NewDecoder(&w.buf).Decode(&resp); err !=
nil { | |
123 w.inst.Message = fmt.Sprintf("Failed to decode e
rror JSON (%s): %s", err, w.buf.String()) | |
124 } else { | |
125 w.inst.Message = resp.Msg | |
126 ierr.Message = resp.Msg | |
127 } | |
128 } | |
129 } | |
130 | |
131 feErr := outerError{ | |
132 Error: &ierr, | |
133 } | |
134 data, err := json.MarshalIndent(feErr, "", " ") | |
135 if err != nil { | |
136 return true | |
137 } | |
138 w.ResponseWriter.Write(data) | |
139 return true | |
140 } | |
141 | |
142 // translateReason returns an errorInstance populated from an HTTP status. | |
143 // | |
144 // The mappings here are copied from Python AppEngine: | |
145 // /google/appengine/tools/devappserver2/endpoints/generated_error_info.py | |
146 func (*errorResponseWriter) translateReason(status int) (int, *errorInstance) { | |
147 code := status | |
148 r := errorInstance{ | |
149 Domain: "global", | |
150 } | |
151 switch status { | |
152 case 400: | |
153 r.Reason = "badRequest" | |
154 case 401: | |
155 r.Reason = "required" | |
156 case 402: | |
157 r.Reason = "unsupportedProtocol" | |
158 case 403: | |
159 r.Reason = "forbidden" | |
160 case 404: | |
161 r.Reason = "notFound" | |
162 case 405: | |
163 r.Reason = "unsupportedMethod" | |
164 case 409: | |
165 r.Reason = "conflict" | |
166 case 410: | |
167 r.Reason = "deleted" | |
168 case 412: | |
169 r.Reason = "conditionNotMet" | |
170 case 413: | |
171 r.Reason = "uploadTooLarge" | |
172 | |
173 case 406, 407, 411, 414, 415, 416, 417: | |
174 code = 404 | |
175 r.Reason = "unsupportedProtocol" | |
176 | |
177 case 408: | |
178 fallthrough | |
179 default: | |
180 code = 503 | |
181 r.Reason = "backendError" | |
182 } | |
183 return code, &r | |
184 } | |
OLD | NEW |