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

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: addressed comments 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 = ")]}'\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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698