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