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