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" |
29 » headerGRPCCode = "X-Prpc-Grpc-Code" | |
dnj
2016/01/21 07:44:28
Make this exported and reuse it in client? No poin
nodir
2016/01/22 00:47:24
Done, but vice-versa: common cannot import server
| |
26 ) | 30 ) |
27 | 31 |
28 // responseFormat returns the format to be used in a response. | 32 // responseFormat returns the format to be used in a response. |
29 // Can return only formatBinary (preferred), formatJSONPB or formatText. | 33 // Can return only formatBinary (preferred), formatJSONPB or formatText. |
30 // In case of an error, format is undefined and the error has an HTTP status. | 34 // In case of an error, format is undefined and the error has an HTTP status. |
31 func responseFormat(acceptHeader string) (format, *httpError) { | 35 func responseFormat(acceptHeader string) (format, *httpError) { |
32 if acceptHeader == "" { | 36 if acceptHeader == "" { |
33 return formatBinary, nil | 37 return formatBinary, nil |
34 } | 38 } |
35 | 39 |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
107 res = buf.Bytes() | 111 res = buf.Bytes() |
108 } | 112 } |
109 if err != nil { | 113 if err != nil { |
110 return err | 114 return err |
111 } | 115 } |
112 w.Header().Set(headerContentType, contentType) | 116 w.Header().Set(headerContentType, contentType) |
113 _, err = w.Write(res) | 117 _, err = w.Write(res) |
114 return err | 118 return err |
115 } | 119 } |
116 | 120 |
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. | 121 // ErrorStatus returns HTTP status for an error. |
142 // In particular, it maps gRPC codes to HTTP statuses. | 122 // In particular, it maps gRPC codes to HTTP statuses. |
143 // Status of nil is 200. | 123 // Status of nil is 200. |
144 // | 124 // |
145 // See also grpc.Code. | 125 // See also grpc.Code. |
146 func ErrorStatus(err error) int { | 126 func ErrorStatus(err error) int { |
147 if err, ok := err.(*httpError); ok { | 127 if err, ok := err.(*httpError); ok { |
148 return err.status | 128 return err.status |
149 } | 129 } |
150 | 130 |
151 » status, ok := codeToStatus[grpc.Code(err)] | 131 » status, ok := prpc.CodeStatus(grpc.Code(err)) |
152 if !ok { | 132 if !ok { |
153 status = http.StatusInternalServerError | 133 status = http.StatusInternalServerError |
154 } | 134 } |
155 return status | 135 return status |
156 } | 136 } |
157 | 137 |
158 // ErrorDesc returns the error description of err if it was produced by pRPC or gRPC. | 138 // 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. | 139 // Otherwise, it returns err.Error() or empty string when err is nil. |
160 // | 140 // |
161 // See also grpc.ErrorDesc. | 141 // See also grpc.ErrorDesc. |
(...skipping 17 matching lines...) Expand all Loading... | |
179 func writeError(c context.Context, w http.ResponseWriter, err error) { | 159 func writeError(c context.Context, w http.ResponseWriter, err error) { |
180 if err == nil { | 160 if err == nil { |
181 panic("err is nil") | 161 panic("err is nil") |
182 } | 162 } |
183 | 163 |
184 status := ErrorStatus(err) | 164 status := ErrorStatus(err) |
185 if status >= 500 { | 165 if status >= 500 { |
186 logging.Errorf(c, "HTTP %d: %s", status, ErrorDesc(err)) | 166 logging.Errorf(c, "HTTP %d: %s", status, ErrorDesc(err)) |
187 } | 167 } |
188 | 168 |
169 w.Header().Set(headerGRPCCode, strconv.Itoa(int(grpcCode(err)))) | |
189 w.Header().Set(headerContentType, "text/plain") | 170 w.Header().Set(headerContentType, "text/plain") |
190 w.WriteHeader(status) | 171 w.WriteHeader(status) |
191 | 172 |
192 var body string | 173 var body string |
193 if status == http.StatusInternalServerError { | 174 if status == http.StatusInternalServerError { |
194 body = "Internal server error" | 175 body = "Internal server error" |
195 } else { | 176 } else { |
196 body = ErrorDesc(err) | 177 body = ErrorDesc(err) |
197 } | 178 } |
198 if _, err := io.WriteString(w, body+"\n"); err != nil { | 179 if _, err := io.WriteString(w, body+"\n"); err != nil { |
199 logging.Errorf(c, "could not write error: %s", err) | 180 logging.Errorf(c, "could not write error: %s", err) |
200 } | 181 } |
201 } | 182 } |
202 | 183 |
184 // returns the most appropriate gRPC code for an error. | |
185 func grpcCode(err error) codes.Code { | |
186 if err == nil { | |
187 return codes.OK | |
188 } | |
189 | |
190 for ; err != nil; err = errors.Unwrap(err) { | |
191 if code := grpc.Code(err); code != codes.Unknown { | |
192 return code | |
193 } | |
194 | |
195 switch err { | |
196 | |
197 case context.Canceled: | |
198 return codes.Canceled | |
199 | |
200 case context.DeadlineExceeded: | |
201 return codes.DeadlineExceeded | |
202 | |
203 } | |
204 } | |
205 return codes.Unknown | |
206 } | |
207 | |
203 func assert(condition bool) { | 208 func assert(condition bool) { |
204 if !condition { | 209 if !condition { |
205 panicf("assertion failed") | 210 panicf("assertion failed") |
206 } | 211 } |
207 } | 212 } |
OLD | NEW |