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" | |
| 12 "net/http" | 11 "net/http" |
| 13 "sort" | 12 "sort" |
| 14 | 13 |
| 15 "github.com/golang/protobuf/jsonpb" | 14 "github.com/golang/protobuf/jsonpb" |
| 16 "github.com/golang/protobuf/proto" | 15 "github.com/golang/protobuf/proto" |
| 17 "golang.org/x/net/context" | 16 "golang.org/x/net/context" |
| 18 "google.golang.org/grpc" | 17 "google.golang.org/grpc" |
| 19 "google.golang.org/grpc/codes" | 18 "google.golang.org/grpc/codes" |
| 20 | |
| 21 "github.com/luci/luci-go/common/logging" | |
| 22 ) | 19 ) |
| 23 | 20 |
| 24 const ( | 21 const ( |
| 25 headerAccept = "Accept" | 22 headerAccept = "Accept" |
| 23 csrfPrefix = ")]}'\n" | |
| 26 ) | 24 ) |
| 27 | 25 |
| 28 // responseFormat returns the format to be used in a response. | 26 // responseFormat returns the format to be used in a response. |
| 29 // Can return only formatBinary (preferred), formatJSONPB or formatText. | 27 // Can return only formatBinary (preferred), formatJSONPB or formatText. |
| 30 // In case of an error, format is undefined and the error has an HTTP status. | 28 // In case of an error, format is undefined. |
| 31 func responseFormat(acceptHeader string) (format, *httpError) { | 29 func responseFormat(acceptHeader string) (format, *protocolError) { |
| 32 if acceptHeader == "" { | 30 if acceptHeader == "" { |
| 33 return formatBinary, nil | 31 return formatBinary, nil |
| 34 } | 32 } |
| 35 | 33 |
| 36 parsed, err := parseAccept(acceptHeader) | 34 parsed, err := parseAccept(acceptHeader) |
| 37 if err != nil { | 35 if err != nil { |
| 38 return formatBinary, errorf(http.StatusBadRequest, "Accept heade r: %s", err) | 36 return formatBinary, errorf(http.StatusBadRequest, "Accept heade r: %s", err) |
| 39 } | 37 } |
| 40 assert(len(parsed) > 0) | |
| 41 formats := make(acceptFormatSlice, 0, len(parsed)) | 38 formats := make(acceptFormatSlice, 0, len(parsed)) |
| 42 for _, at := range parsed { | 39 for _, at := range parsed { |
| 43 f, err := parseFormat(at.MediaType, at.MediaTypeParams) | 40 f, err := parseFormat(at.MediaType, at.MediaTypeParams) |
| 44 if err != nil { | 41 if err != nil { |
| 45 // Ignore invalid format. Check further. | 42 // Ignore invalid format. Check further. |
| 46 continue | 43 continue |
| 47 } | 44 } |
| 48 switch f { | 45 switch f { |
| 49 | 46 |
| 50 case formatBinary, formatJSONPB, formatText: | 47 case formatBinary, formatJSONPB, formatText: |
| 51 // fine | 48 // fine |
| 52 | 49 |
| 53 case formatUnspecified: | 50 case formatUnspecified: |
| 54 f = formatBinary // prefer binary | 51 f = formatBinary // prefer binary |
| 55 | 52 |
| 56 case formatUnrecognized: | 53 case formatUnrecognized: |
| 57 continue | 54 continue |
| 58 | 55 |
| 59 default: | 56 default: |
| 60 » » » panicf("cannot happen") | 57 » » » panic("cannot happen") |
|
dnj
2016/01/26 18:50:27
nit: if this cannot happen, get rid of the default
nodir
2016/01/26 19:06:09
just default: continue is enough I guess
| |
| 61 } | 58 } |
| 62 | 59 |
| 63 assert(f == formatBinary || f == formatJSONPB || f == formatText ) | |
| 64 formats = append(formats, acceptFormat{f, at.QualityFactor}) | 60 formats = append(formats, acceptFormat{f, at.QualityFactor}) |
| 65 } | 61 } |
| 66 if len(formats) == 0 { | 62 if len(formats) == 0 { |
| 67 return formatBinary, errorf( | 63 return formatBinary, errorf( |
| 68 http.StatusNotAcceptable, | 64 http.StatusNotAcceptable, |
| 69 "Accept header: specified media types are not not suppor ted. Supported types: %q, %q, %q, %q.", | 65 "Accept header: specified media types are not not suppor ted. Supported types: %q, %q, %q, %q.", |
| 70 mtPRPCBinary, | 66 mtPRPCBinary, |
| 71 mtPRPCJSNOPB, | 67 mtPRPCJSNOPB, |
| 72 mtPRPCText, | 68 mtPRPCText, |
| 73 mtJSON, | 69 mtJSON, |
| 74 ) | 70 ) |
| 75 } | 71 } |
| 76 sort.Sort(formats) // order by quality factor and format preference. | 72 sort.Sort(formats) // order by quality factor and format preference. |
| 77 return formats[0].Format, nil | 73 return formats[0].Format, nil |
| 78 } | 74 } |
| 79 | 75 |
| 80 // writeMessage writes a protobuf message to response in the specified format. | 76 // respondMessage encodes msg to a response in the specified format. |
| 81 func writeMessage(w http.ResponseWriter, msg proto.Message, format format) error { | 77 func respondMessage(msg proto.Message, format format) *response { |
| 82 if msg == nil { | 78 if msg == nil { |
| 83 » » panic("msg is nil") | 79 » » return errResponse(codes.Internal, 0, "pRPC: responseMessage: ms g is nil") |
| 84 } | 80 } |
| 85 » var ( | 81 » res := response{header: http.Header{}} |
| 86 » » contentType string | 82 » var err error |
| 87 » » res []byte | |
| 88 » » err error | |
| 89 » ) | |
| 90 switch format { | 83 switch format { |
| 91 case formatBinary: | 84 case formatBinary: |
| 92 » » contentType = mtPRPCBinary | 85 » » res.header.Set(headerContentType, mtPRPCBinary) |
| 93 » » res, err = proto.Marshal(msg) | 86 » » res.body, err = proto.Marshal(msg) |
| 94 | 87 |
| 95 case formatJSONPB: | 88 case formatJSONPB: |
| 96 » » contentType = mtPRPCJSNOPB | 89 » » res.header.Set(headerContentType, mtPRPCJSNOPB) |
| 97 » » m := jsonpb.Marshaler{Indent: "\t"} | |
| 98 var buf bytes.Buffer | 90 var buf bytes.Buffer |
| 91 buf.WriteString(csrfPrefix) | |
| 92 m := jsonpb.Marshaler{} | |
| 99 err = m.Marshal(&buf, msg) | 93 err = m.Marshal(&buf, msg) |
| 100 » » buf.WriteString("\n") | 94 » » res.body = buf.Bytes() |
| 101 » » res = buf.Bytes() | 95 » » res.newLine = true |
| 102 | 96 |
| 103 case formatText: | 97 case formatText: |
| 104 » » contentType = mtPRPCText | 98 » » res.header.Set(headerContentType, mtPRPCText) |
| 105 var buf bytes.Buffer | 99 var buf bytes.Buffer |
| 106 err = proto.MarshalText(&buf, msg) | 100 err = proto.MarshalText(&buf, msg) |
| 107 » » res = buf.Bytes() | 101 » » res.body = buf.Bytes() |
| 102 | |
| 103 » default: | |
| 104 » » return errResponse(codes.Internal, 0, "pRPC: responseMessage: in valid format %s", format) | |
| 105 | |
| 108 } | 106 } |
| 109 if err != nil { | 107 if err != nil { |
| 110 » » return err | 108 » » return errResponse(codes.Internal, 0, err.Error()) |
| 111 } | 109 } |
| 112 » w.Header().Set(headerContentType, contentType) | 110 |
| 113 » _, err = w.Write(res) | 111 » return &res |
| 114 » return err | 112 } |
| 113 | |
| 114 // respondProtocolError creates a response for a pRPC protocol error. | |
| 115 func respondProtocolError(err *protocolError) *response { | |
| 116 » return errResponse(codes.InvalidArgument, err.status, err.err.Error()) | |
| 117 } | |
| 118 | |
| 119 // errorCode returns a most appropriate gRPC code for an error | |
| 120 func errorCode(err error) codes.Code { | |
| 121 » switch err { | |
| 122 » case context.DeadlineExceeded: | |
| 123 » » return codes.DeadlineExceeded | |
| 124 | |
| 125 » case context.Canceled: | |
| 126 » » return codes.Canceled | |
| 127 | |
| 128 » default: | |
| 129 » » return grpc.Code(err) | |
| 130 » } | |
| 115 } | 131 } |
| 116 | 132 |
| 117 // codeToStatus maps gRPC codes to HTTP statuses. | 133 // codeToStatus maps gRPC codes to HTTP statuses. |
| 118 // This map may need to be corrected when | 134 // This map may need to be corrected when |
| 119 // https://github.com/grpc/grpc-common/issues/210 | 135 // https://github.com/grpc/grpc-common/issues/210 |
| 120 // is closed. | 136 // is closed. |
| 121 var codeToStatus = map[codes.Code]int{ | 137 var codeToStatus = map[codes.Code]int{ |
| 122 codes.OK: http.StatusOK, | 138 codes.OK: http.StatusOK, |
| 123 codes.Canceled: http.StatusNoContent, | 139 codes.Canceled: http.StatusNoContent, |
| 124 codes.Unknown: http.StatusInternalServerError, | |
| 125 codes.InvalidArgument: http.StatusBadRequest, | 140 codes.InvalidArgument: http.StatusBadRequest, |
| 126 codes.DeadlineExceeded: http.StatusServiceUnavailable, | 141 codes.DeadlineExceeded: http.StatusServiceUnavailable, |
| 127 codes.NotFound: http.StatusNotFound, | 142 codes.NotFound: http.StatusNotFound, |
| 128 codes.AlreadyExists: http.StatusConflict, | 143 codes.AlreadyExists: http.StatusConflict, |
| 129 codes.PermissionDenied: http.StatusForbidden, | 144 codes.PermissionDenied: http.StatusForbidden, |
| 130 codes.Unauthenticated: http.StatusUnauthorized, | 145 codes.Unauthenticated: http.StatusUnauthorized, |
| 131 codes.ResourceExhausted: http.StatusServiceUnavailable, | 146 codes.ResourceExhausted: http.StatusServiceUnavailable, |
| 132 codes.FailedPrecondition: http.StatusPreconditionFailed, | 147 codes.FailedPrecondition: http.StatusPreconditionFailed, |
| 133 codes.Aborted: http.StatusInternalServerError, | |
| 134 codes.OutOfRange: http.StatusBadRequest, | 148 codes.OutOfRange: http.StatusBadRequest, |
| 135 codes.Unimplemented: http.StatusNotImplemented, | 149 codes.Unimplemented: http.StatusNotImplemented, |
| 136 codes.Internal: http.StatusInternalServerError, | |
| 137 codes.Unavailable: http.StatusServiceUnavailable, | 150 codes.Unavailable: http.StatusServiceUnavailable, |
| 138 codes.DataLoss: http.StatusInternalServerError, | |
| 139 } | 151 } |
| 140 | 152 |
| 141 // ErrorStatus returns HTTP status for an error. | 153 // codeStatus maps gRPC codes to HTTP status codes. |
| 142 // In particular, it maps gRPC codes to HTTP statuses. | 154 // Falls back to http.StatusInternalServerError. |
| 143 // Status of nil is 200. | 155 func codeStatus(code codes.Code) int { |
| 144 // | 156 » if status, ok := codeToStatus[code]; ok { |
| 145 // See also grpc.Code. | 157 » » return status |
| 146 func ErrorStatus(err error) int { | |
| 147 » if err, ok := err.(*httpError); ok { | |
| 148 » » return err.status | |
| 149 } | 158 } |
| 150 | 159 » return http.StatusInternalServerError |
| 151 » status, ok := codeToStatus[grpc.Code(err)] | |
| 152 » if !ok { | |
| 153 » » status = http.StatusInternalServerError | |
| 154 » } | |
| 155 » return status | |
| 156 } | 160 } |
| 157 | |
| 158 // 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. | |
| 160 // | |
| 161 // See also grpc.ErrorDesc. | |
| 162 func ErrorDesc(err error) string { | |
| 163 if err == nil { | |
| 164 return "" | |
| 165 } | |
| 166 if e, ok := err.(*httpError); ok { | |
| 167 err = e.err | |
| 168 } | |
| 169 return grpc.ErrorDesc(err) | |
| 170 } | |
| 171 | |
| 172 // writeError writes an error to an HTTP response. | |
| 173 // | |
| 174 // HTTP status is determined by ErrorStatus. | |
| 175 // If it is http.StatusInternalServerError, prints only "Internal server error", | |
| 176 // otherwise uses ErrorDesc. | |
| 177 // | |
| 178 // Logs all errors with status >= 500. | |
| 179 func writeError(c context.Context, w http.ResponseWriter, err error) { | |
| 180 if err == nil { | |
| 181 panic("err is nil") | |
| 182 } | |
| 183 | |
| 184 status := ErrorStatus(err) | |
| 185 if status >= 500 { | |
| 186 logging.Errorf(c, "HTTP %d: %s", status, ErrorDesc(err)) | |
| 187 } | |
| 188 | |
| 189 w.Header().Set(headerContentType, "text/plain") | |
| 190 w.WriteHeader(status) | |
| 191 | |
| 192 var body string | |
| 193 if status == http.StatusInternalServerError { | |
| 194 body = "Internal server error" | |
| 195 } else { | |
| 196 body = ErrorDesc(err) | |
| 197 } | |
| 198 if _, err := io.WriteString(w, body+"\n"); err != nil { | |
| 199 logging.Errorf(c, "could not write error: %s", err) | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 func assert(condition bool) { | |
| 204 if !condition { | |
| 205 panicf("assertion failed") | |
| 206 } | |
| 207 } | |
| OLD | NEW |