Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1033)

Side by Side Diff: server/prpc/encoding.go

Issue 1638493004: server/prpc: updated server according to protocol (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-go@master
Patch Set: Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 = ")]}'"
dnj (Google) 2016/01/26 16:13:56 I believe this should end in a newline, both for c
nodir 2016/01/26 18:01:06 Done.
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) 38 assert(len(parsed) > 0)
dnj (Google) 2016/01/26 16:13:56 This is just correctness of "parseAccept". No valu
nodir 2016/01/26 18:01:06 Done.
41 formats := make(acceptFormatSlice, 0, len(parsed)) 39 formats := make(acceptFormatSlice, 0, len(parsed))
42 for _, at := range parsed { 40 for _, at := range parsed {
43 f, err := parseFormat(at.MediaType, at.MediaTypeParams) 41 f, err := parseFormat(at.MediaType, at.MediaTypeParams)
44 if err != nil { 42 if err != nil {
45 // Ignore invalid format. Check further. 43 // Ignore invalid format. Check further.
46 continue 44 continue
47 } 45 }
48 switch f { 46 switch f {
49 47
50 case formatBinary, formatJSONPB, formatText: 48 case formatBinary, formatJSONPB, formatText:
51 // fine 49 // fine
52 50
53 case formatUnspecified: 51 case formatUnspecified:
54 f = formatBinary // prefer binary 52 f = formatBinary // prefer binary
55 53
56 case formatUnrecognized: 54 case formatUnrecognized:
57 continue 55 continue
58 56
59 default: 57 default:
60 panicf("cannot happen") 58 panicf("cannot happen")
61 } 59 }
62 60
63 assert(f == formatBinary || f == formatJSONPB || f == formatText ) 61 assert(f == formatBinary || f == formatJSONPB || f == formatText )
dnj (Google) 2016/01/26 16:13:56 This is just correctness. No value using panic to
nodir 2016/01/26 18:01:06 Done.
64 formats = append(formats, acceptFormat{f, at.QualityFactor}) 62 formats = append(formats, acceptFormat{f, at.QualityFactor})
65 } 63 }
66 if len(formats) == 0 { 64 if len(formats) == 0 {
67 return formatBinary, errorf( 65 return formatBinary, errorf(
68 http.StatusNotAcceptable, 66 http.StatusNotAcceptable,
69 "Accept header: specified media types are not not suppor ted. Supported types: %q, %q, %q, %q.", 67 "Accept header: specified media types are not not suppor ted. Supported types: %q, %q, %q, %q.",
70 mtPRPCBinary, 68 mtPRPCBinary,
71 mtPRPCJSNOPB, 69 mtPRPCJSNOPB,
72 mtPRPCText, 70 mtPRPCText,
73 mtJSON, 71 mtJSON,
74 ) 72 )
75 } 73 }
76 sort.Sort(formats) // order by quality factor and format preference. 74 sort.Sort(formats) // order by quality factor and format preference.
77 return formats[0].Format, nil 75 return formats[0].Format, nil
78 } 76 }
79 77
80 // writeMessage writes a protobuf message to response in the specified format. 78 // respondMessage encodes msg to a response in the specified format.
81 func writeMessage(w http.ResponseWriter, msg proto.Message, format format) error { 79 func respondMessage(msg proto.Message, format format) *response {
82 if msg == nil { 80 if msg == nil {
83 » » panic("msg is nil") 81 » » panicf("msg is nil")
dnj (Google) 2016/01/26 16:13:56 nit: panic would work here, no need for "panicf".
nodir 2016/01/26 18:01:06 err, in another CL you said that we panic with err
dnj 2016/01/26 18:50:27 Ah sorry I meant something like: panic(errors.New(
84 } 82 }
85 var ( 83 var (
86 » » contentType string 84 » » res response
87 » » res []byte 85 » » err error
88 » » err error
89 ) 86 )
90 switch format { 87 switch format {
91 case formatBinary: 88 case formatBinary:
92 » » contentType = mtPRPCBinary 89 » » res.contentType = mtPRPCBinary
93 » » res, err = proto.Marshal(msg) 90 » » res.body, err = proto.Marshal(msg)
94 91
95 case formatJSONPB: 92 case formatJSONPB:
96 » » contentType = mtPRPCJSNOPB 93 » » res.contentType = mtPRPCJSNOPB
94 » » var buf bytes.Buffer
95 » » buf.WriteString(csrfPrefix)
97 m := jsonpb.Marshaler{Indent: "\t"} 96 m := jsonpb.Marshaler{Indent: "\t"}
dnj (Google) 2016/01/26 16:13:56 1) Don't we normally use spaces to indent JSON? 2)
nodir 2016/01/26 18:01:07 1) \t is less bytes 2) I used to curl in the past.
98 var buf bytes.Buffer
99 err = m.Marshal(&buf, msg) 97 err = m.Marshal(&buf, msg)
100 » » buf.WriteString("\n") 98 » » res.body = buf.Bytes()
101 » » res = buf.Bytes() 99 » » res.newLine = true
102 100
103 case formatText: 101 case formatText:
104 » » contentType = mtPRPCText 102 » » res.contentType = mtPRPCText
105 var buf bytes.Buffer 103 var buf bytes.Buffer
106 err = proto.MarshalText(&buf, msg) 104 err = proto.MarshalText(&buf, msg)
107 » » res = buf.Bytes() 105 » » res.body = buf.Bytes()
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 nil:
123 » » panicf("err is nil")
dnj (Google) 2016/01/26 16:13:56 Default to something like Unknown if nil, rather t
nodir 2016/01/26 18:01:06 returned ok
124 » » fallthrough
125
126 » case context.DeadlineExceeded:
127 » » return codes.DeadlineExceeded
128
129 » case context.Canceled:
130 » » return codes.Canceled
131
132 » default:
133 » » return grpc.Code(err)
134 » }
115 } 135 }
116 136
117 // codeToStatus maps gRPC codes to HTTP statuses. 137 // codeToStatus maps gRPC codes to HTTP statuses.
118 // This map may need to be corrected when 138 // This map may need to be corrected when
119 // https://github.com/grpc/grpc-common/issues/210 139 // https://github.com/grpc/grpc-common/issues/210
120 // is closed. 140 // is closed.
121 var codeToStatus = map[codes.Code]int{ 141 var codeToStatus = map[codes.Code]int{
122 codes.OK: http.StatusOK, 142 codes.OK: http.StatusOK,
123 codes.Canceled: http.StatusNoContent, 143 codes.Canceled: http.StatusNoContent,
124 codes.Unknown: http.StatusInternalServerError, 144 codes.Unknown: http.StatusInternalServerError,
125 codes.InvalidArgument: http.StatusBadRequest, 145 codes.InvalidArgument: http.StatusBadRequest,
126 codes.DeadlineExceeded: http.StatusServiceUnavailable, 146 codes.DeadlineExceeded: http.StatusServiceUnavailable,
127 codes.NotFound: http.StatusNotFound, 147 codes.NotFound: http.StatusNotFound,
128 codes.AlreadyExists: http.StatusConflict, 148 codes.AlreadyExists: http.StatusConflict,
129 codes.PermissionDenied: http.StatusForbidden, 149 codes.PermissionDenied: http.StatusForbidden,
130 codes.Unauthenticated: http.StatusUnauthorized, 150 codes.Unauthenticated: http.StatusUnauthorized,
131 codes.ResourceExhausted: http.StatusServiceUnavailable, 151 codes.ResourceExhausted: http.StatusServiceUnavailable,
132 codes.FailedPrecondition: http.StatusPreconditionFailed, 152 codes.FailedPrecondition: http.StatusPreconditionFailed,
133 codes.Aborted: http.StatusInternalServerError, 153 codes.Aborted: http.StatusInternalServerError,
134 codes.OutOfRange: http.StatusBadRequest, 154 codes.OutOfRange: http.StatusBadRequest,
135 codes.Unimplemented: http.StatusNotImplemented, 155 codes.Unimplemented: http.StatusNotImplemented,
136 codes.Internal: http.StatusInternalServerError, 156 codes.Internal: http.StatusInternalServerError,
137 codes.Unavailable: http.StatusServiceUnavailable, 157 codes.Unavailable: http.StatusServiceUnavailable,
138 codes.DataLoss: http.StatusInternalServerError, 158 codes.DataLoss: http.StatusInternalServerError,
139 } 159 }
140 160
141 // ErrorStatus returns HTTP status for an error. 161 // codeStatus maps gRPC codes to HTTP status codes.
142 // In particular, it maps gRPC codes to HTTP statuses. 162 // Falls back to http.StatusInternalServerError.
143 // Status of nil is 200. 163 func codeStatus(code codes.Code) int {
dnj (Google) 2016/01/26 16:13:56 If it falls back to internal server error, you sho
nodir 2016/01/26 18:01:07 Done.
144 // 164 » if status, ok := codeToStatus[code]; ok {
145 // See also grpc.Code. 165 » » return status
146 func ErrorStatus(err error) int {
147 » if err, ok := err.(*httpError); ok {
148 » » return err.status
149 } 166 }
150 167 » return http.StatusInternalServerError
151 » status, ok := codeToStatus[grpc.Code(err)]
152 » if !ok {
153 » » status = http.StatusInternalServerError
154 » }
155 » return status
156 } 168 }
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 }
OLDNEW
« no previous file with comments | « server/prpc/decoding_test.go ('k') | server/prpc/encoding_test.go » ('j') | server/prpc/error.go » ('J')

Powered by Google App Engine
This is Rietveld 408576698