Chromium Code Reviews| 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 |