forked from yarpc/yarpc-go
/
headers.go
230 lines (213 loc) · 7.91 KB
/
headers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// Copyright (c) 2018 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package grpc
import (
"strings"
"go.uber.org/multierr"
"go.uber.org/yarpc/api/transport"
"go.uber.org/yarpc/yarpcerrors"
"google.golang.org/grpc/metadata"
)
const (
// CallerHeader is the header key for the name of the service sending the
// request. This corresponds to the Request.Caller attribute.
// This header is required.
CallerHeader = "rpc-caller"
// ServiceHeader is the header key for the name of the service to which
// the request is being sent. This corresponds to the Request.Service attribute.
// This header is required.
ServiceHeader = "rpc-service"
// ShardKeyHeader is the header key for the shard key used by the destined service
// to shard the request. This corresponds to the Request.ShardKey attribute.
// This header is optional.
ShardKeyHeader = "rpc-shard-key"
// RoutingKeyHeader is the header key for the traffic group responsible for
// handling the request. This corresponds to the Request.RoutingKey attribute.
// This header is optional.
RoutingKeyHeader = "rpc-routing-key"
// RoutingDelegateHeader is the header key for a service that can proxy the
// destined service. This corresponds to the Request.RoutingDelegate attribute.
// This header is optional.
RoutingDelegateHeader = "rpc-routing-delegate"
// EncodingHeader is the header key for the encoding used for the request body.
// This corresponds to the Request.Encoding attribute.
// If this is not set, content-type will attempt to be read for the encoding per
// the gRPC wire format http://www.grpc.io/docs/guides/wire.html
// For example, a content-type of "application/grpc+proto" will be intepreted
// as the proto encoding.
// This header is required unless content-type is set properly.
EncodingHeader = "rpc-encoding"
// ErrorNameHeader is the header key for the error name.
ErrorNameHeader = "rpc-error-name"
// ApplicationErrorHeader is the header key that will contain a non-empty value
// if there was an application error.
ApplicationErrorHeader = "rpc-application-error"
// ApplicationErrorHeaderValue is the value that will be set for
// ApplicationErrorHeader is there was an application error.
//
// The definition says any non-empty value is valid, however this is
// the specific value that will be used for now.
ApplicationErrorHeaderValue = "error"
baseContentType = "application/grpc"
contentTypeHeader = "content-type"
)
// TODO: there are way too many repeat calls to strings.ToLower
// Note that these calls are done indirectly, primarily through
// transport.CanonicalizeHeaderKey
func isReserved(header string) bool {
return strings.HasPrefix(strings.ToLower(header), "rpc-")
}
// transportRequestToMetadata will populate all reserved and application headers
// from the Request into a new MD.
func transportRequestToMetadata(request *transport.Request) (metadata.MD, error) {
md := metadata.New(nil)
if err := multierr.Combine(
addToMetadata(md, CallerHeader, request.Caller),
addToMetadata(md, ServiceHeader, request.Service),
addToMetadata(md, ShardKeyHeader, request.ShardKey),
addToMetadata(md, RoutingKeyHeader, request.RoutingKey),
addToMetadata(md, RoutingDelegateHeader, request.RoutingDelegate),
addToMetadata(md, EncodingHeader, string(request.Encoding)),
); err != nil {
return md, err
}
return md, addApplicationHeaders(md, request.Headers)
}
// metadataToTransportRequest will populate the Request with all reserved and application
// headers into a new Request, only not setting the Body field.
func metadataToTransportRequest(md metadata.MD) (*transport.Request, error) {
request := &transport.Request{
Headers: transport.NewHeadersWithCapacity(md.Len()),
}
for header, values := range md {
var value string
switch len(values) {
case 0:
continue
case 1:
value = values[0]
default:
return nil, yarpcerrors.InvalidArgumentErrorf("header has more than one value: %s", header)
}
header = transport.CanonicalizeHeaderKey(header)
switch header {
case CallerHeader:
request.Caller = value
case ServiceHeader:
request.Service = value
case ShardKeyHeader:
request.ShardKey = value
case RoutingKeyHeader:
request.RoutingKey = value
case RoutingDelegateHeader:
request.RoutingDelegate = value
case EncodingHeader:
request.Encoding = transport.Encoding(value)
case contentTypeHeader:
// if request.Encoding was set, do not parse content-type
// this results in EncodingHeader overriding content-type
if request.Encoding == "" {
request.Encoding = transport.Encoding(getContentSubtype(value))
}
default:
request.Headers = request.Headers.With(header, value)
}
}
return request, nil
}
// addApplicationHeaders adds the headers to md.
func addApplicationHeaders(md metadata.MD, headers transport.Headers) error {
for header, value := range headers.Items() {
header = transport.CanonicalizeHeaderKey(header)
if isReserved(header) {
return yarpcerrors.InvalidArgumentErrorf("cannot use reserved header in application headers: %s", header)
}
if err := addToMetadata(md, header, value); err != nil {
return err
}
}
return nil
}
// getApplicationHeaders returns the headers from md without any reserved headers.
func getApplicationHeaders(md metadata.MD) (transport.Headers, error) {
if len(md) == 0 {
return transport.Headers{}, nil
}
headers := transport.NewHeadersWithCapacity(md.Len())
for header, values := range md {
header = transport.CanonicalizeHeaderKey(header)
if isReserved(header) {
continue
}
var value string
switch len(values) {
case 0:
continue
case 1:
value = values[0]
default:
return headers, yarpcerrors.InvalidArgumentErrorf("header has more than one value: %s", header)
}
headers = headers.With(header, value)
}
return headers, nil
}
// add to md
// return error if key already in md
func addToMetadata(md metadata.MD, key string, value string) error {
if value == "" {
return nil
}
if _, ok := md[key]; ok {
return yarpcerrors.InvalidArgumentErrorf("duplicate key: %s", key)
}
md[key] = []string{value}
return nil
}
// getContentSubtype attempts to get the content subtype.
// returns "" if no content subtype can be parsed.
func getContentSubtype(contentType string) string {
if !strings.HasPrefix(contentType, baseContentType) || len(contentType) == len(baseContentType) {
return ""
}
switch contentType[len(baseContentType)] {
case '+', ';':
return contentType[len(baseContentType)+1:]
default:
return ""
}
}
type mdReadWriter metadata.MD
// ForeachKey implements opentracing.TextMapReader.
func (md mdReadWriter) ForeachKey(handler func(string, string) error) error {
for key, values := range md {
for _, value := range values {
if err := handler(key, value); err != nil {
return err
}
}
}
return nil
}
// Set implements opentracing.TextMapWriter.
func (md mdReadWriter) Set(key string, value string) {
key = strings.ToLower(key)
md[key] = append(md[key], value)
}