OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package prpc | 5 package prpc |
6 | 6 |
7 // This file implements encoding of RPC results to HTTP responses. | 7 // This file implements encoding of RPC results to HTTP responses. |
8 | 8 |
9 import ( | 9 import ( |
10 "bytes" | 10 "bytes" |
11 "io" | 11 "io" |
12 "net/http" | 12 "net/http" |
13 "sort" | 13 "sort" |
| 14 "strconv" |
14 | 15 |
15 "github.com/golang/protobuf/jsonpb" | 16 "github.com/golang/protobuf/jsonpb" |
16 "github.com/golang/protobuf/proto" | 17 "github.com/golang/protobuf/proto" |
17 "golang.org/x/net/context" | 18 "golang.org/x/net/context" |
18 "google.golang.org/grpc" | 19 "google.golang.org/grpc" |
19 "google.golang.org/grpc/codes" | 20 "google.golang.org/grpc/codes" |
20 | 21 |
| 22 "github.com/luci/luci-go/common/errors" |
21 "github.com/luci/luci-go/common/logging" | 23 "github.com/luci/luci-go/common/logging" |
| 24 "github.com/luci/luci-go/common/prpc" |
22 ) | 25 ) |
23 | 26 |
24 const ( | 27 const ( |
25 headerAccept = "Accept" | 28 headerAccept = "Accept" |
26 ) | 29 ) |
27 | 30 |
28 // responseFormat returns the format to be used in a response. | 31 // responseFormat returns the format to be used in a response. |
29 // Can return only formatBinary (preferred), formatJSONPB or formatText. | 32 // Can return only formatBinary (preferred), formatJSONPB or formatText. |
30 // In case of an error, format is undefined and the error has an HTTP status. | 33 // In case of an error, format is undefined and the error has an HTTP status. |
31 func responseFormat(acceptHeader string) (format, *httpError) { | 34 func responseFormat(acceptHeader string) (format, *httpError) { |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
107 res = buf.Bytes() | 110 res = buf.Bytes() |
108 } | 111 } |
109 if err != nil { | 112 if err != nil { |
110 return err | 113 return err |
111 } | 114 } |
112 w.Header().Set(headerContentType, contentType) | 115 w.Header().Set(headerContentType, contentType) |
113 _, err = w.Write(res) | 116 _, err = w.Write(res) |
114 return err | 117 return err |
115 } | 118 } |
116 | 119 |
117 // codeToStatus maps gRPC codes to HTTP statuses. | |
118 // This map may need to be corrected when | |
119 // https://github.com/grpc/grpc-common/issues/210 | |
120 // is closed. | |
121 var codeToStatus = map[codes.Code]int{ | |
122 codes.OK: http.StatusOK, | |
123 codes.Canceled: http.StatusNoContent, | |
124 codes.Unknown: http.StatusInternalServerError, | |
125 codes.InvalidArgument: http.StatusBadRequest, | |
126 codes.DeadlineExceeded: http.StatusServiceUnavailable, | |
127 codes.NotFound: http.StatusNotFound, | |
128 codes.AlreadyExists: http.StatusConflict, | |
129 codes.PermissionDenied: http.StatusForbidden, | |
130 codes.Unauthenticated: http.StatusUnauthorized, | |
131 codes.ResourceExhausted: http.StatusServiceUnavailable, | |
132 codes.FailedPrecondition: http.StatusPreconditionFailed, | |
133 codes.Aborted: http.StatusInternalServerError, | |
134 codes.OutOfRange: http.StatusBadRequest, | |
135 codes.Unimplemented: http.StatusNotImplemented, | |
136 codes.Internal: http.StatusInternalServerError, | |
137 codes.Unavailable: http.StatusServiceUnavailable, | |
138 codes.DataLoss: http.StatusInternalServerError, | |
139 } | |
140 | |
141 // ErrorStatus returns HTTP status for an error. | 120 // ErrorStatus returns HTTP status for an error. |
142 // In particular, it maps gRPC codes to HTTP statuses. | 121 // In particular, it maps gRPC codes to HTTP statuses. |
143 // Status of nil is 200. | 122 // Status of nil is 200. |
144 // | 123 // |
145 // See also grpc.Code. | 124 // See also grpc.Code. |
146 func ErrorStatus(err error) int { | 125 func ErrorStatus(err error) int { |
147 if err, ok := err.(*httpError); ok { | 126 if err, ok := err.(*httpError); ok { |
148 return err.status | 127 return err.status |
149 } | 128 } |
150 | 129 |
151 » status, ok := codeToStatus[grpc.Code(err)] | 130 » status, ok := prpc.CodeStatus(grpc.Code(err)) |
152 if !ok { | 131 if !ok { |
153 status = http.StatusInternalServerError | 132 status = http.StatusInternalServerError |
154 } | 133 } |
155 return status | 134 return status |
156 } | 135 } |
157 | 136 |
158 // ErrorDesc returns the error description of err if it was produced by pRPC or
gRPC. | 137 // ErrorDesc returns the error description of err if it was produced by pRPC or
gRPC. |
159 // Otherwise, it returns err.Error() or empty string when err is nil. | 138 // Otherwise, it returns err.Error() or empty string when err is nil. |
160 // | 139 // |
161 // See also grpc.ErrorDesc. | 140 // See also grpc.ErrorDesc. |
(...skipping 17 matching lines...) Expand all Loading... |
179 func writeError(c context.Context, w http.ResponseWriter, err error) { | 158 func writeError(c context.Context, w http.ResponseWriter, err error) { |
180 if err == nil { | 159 if err == nil { |
181 panic("err is nil") | 160 panic("err is nil") |
182 } | 161 } |
183 | 162 |
184 status := ErrorStatus(err) | 163 status := ErrorStatus(err) |
185 if status >= 500 { | 164 if status >= 500 { |
186 logging.Errorf(c, "HTTP %d: %s", status, ErrorDesc(err)) | 165 logging.Errorf(c, "HTTP %d: %s", status, ErrorDesc(err)) |
187 } | 166 } |
188 | 167 |
| 168 w.Header().Set(prpc.HeaderGrpcCode, strconv.Itoa(int(grpcCode(err)))) |
189 w.Header().Set(headerContentType, "text/plain") | 169 w.Header().Set(headerContentType, "text/plain") |
190 w.WriteHeader(status) | 170 w.WriteHeader(status) |
191 | 171 |
192 var body string | 172 var body string |
193 if status == http.StatusInternalServerError { | 173 if status == http.StatusInternalServerError { |
194 body = "Internal server error" | 174 body = "Internal server error" |
195 } else { | 175 } else { |
196 body = ErrorDesc(err) | 176 body = ErrorDesc(err) |
197 } | 177 } |
198 if _, err := io.WriteString(w, body+"\n"); err != nil { | 178 if _, err := io.WriteString(w, body+"\n"); err != nil { |
199 logging.Errorf(c, "could not write error: %s", err) | 179 logging.Errorf(c, "could not write error: %s", err) |
200 } | 180 } |
201 } | 181 } |
202 | 182 |
| 183 // returns the most appropriate gRPC code for an error. |
| 184 func grpcCode(err error) codes.Code { |
| 185 if err == nil { |
| 186 return codes.OK |
| 187 } |
| 188 |
| 189 for ; err != nil; err = errors.Unwrap(err) { |
| 190 if code := grpc.Code(err); code != codes.Unknown { |
| 191 return code |
| 192 } |
| 193 |
| 194 switch err { |
| 195 |
| 196 case context.Canceled: |
| 197 return codes.Canceled |
| 198 |
| 199 case context.DeadlineExceeded: |
| 200 return codes.DeadlineExceeded |
| 201 |
| 202 } |
| 203 } |
| 204 return codes.Unknown |
| 205 } |
| 206 |
203 func assert(condition bool) { | 207 func assert(condition bool) { |
204 if !condition { | 208 if !condition { |
205 panicf("assertion failed") | 209 panicf("assertion failed") |
206 } | 210 } |
207 } | 211 } |
OLD | NEW |