diff --git a/client.go b/client.go index 40b3c45..748a007 100644 --- a/client.go +++ b/client.go @@ -99,8 +99,8 @@ func (c *Client) Call(ctx context.Context, service, method string, req, resp int cresp = &Response{} ) - if headers, ok := GetHeaders(ctx); ok { - creq.Headers = headers + if metadata, ok := GetMetadata(ctx); ok { + creq.Metadata = metadata } if dl, ok := ctx.Deadline(); ok { diff --git a/header.go b/header.go deleted file mode 100644 index f27e8d3..0000000 --- a/header.go +++ /dev/null @@ -1,86 +0,0 @@ -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ttrpc - -import "context" - -// Headers represents the key-value pairs (similar to http.Header) to be passed to ttrpc server from a client. -type Headers map[string]StringList - -// Get returns the headers for a given key when they exist. -// If there are no headers, a nil slice and false are returned. -func (h Headers) Get(key string) ([]string, bool) { - list, ok := h[key] - if !ok || len(list.List) == 0 { - return nil, false - } - - return list.List, true -} - -// Set sets the provided values for a given key. -// The values will overwrite any existing values. -// If no values provided, a key will be deleted. -func (h Headers) Set(key string, values ...string) { - if len(values) == 0 { - delete(h, key) - return - } - - h[key] = StringList{List: values} -} - -// Append appends additional values to the given key. -func (h Headers) Append(key string, values ...string) { - if len(values) == 0 { - return - } - - list, ok := h[key] - if ok { - h.Set(key, append(list.List, values...)...) - } else { - h.Set(key, values...) - } -} - -type headerKey struct{} - -// GetHeaders retrieves headers from context.Context (previously attached with WithHeaders) -func GetHeaders(ctx context.Context) (Headers, bool) { - headers, ok := ctx.Value(headerKey{}).(Headers) - return headers, ok -} - -// GetHeader gets a specific header value by name from context.Context -func GetHeader(ctx context.Context, name string) (string, bool) { - headers, ok := GetHeaders(ctx) - if !ok { - return "", false - } - - if list, ok := headers.Get(name); ok { - return list[0], true - } - - return "", false -} - -// WithHeaders attaches headers map to a context.Context -func WithHeaders(ctx context.Context, headers Headers) context.Context { - return context.WithValue(ctx, headerKey{}, headers) -} diff --git a/header_test.go b/header_test.go deleted file mode 100644 index 31e947a..0000000 --- a/header_test.go +++ /dev/null @@ -1,108 +0,0 @@ -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ttrpc - -import ( - "context" - "testing" -) - -func TestHeaders_Get(t *testing.T) { - hdr := make(Headers) - hdr.Set("foo", "1", "2") - - if list, ok := hdr.Get("foo"); !ok { - t.Error("key not found") - } else if len(list) != 2 { - t.Errorf("unexpected number of values: %d", len(list)) - } else if list[0] != "1" { - t.Errorf("invalid header value at 0: %s", list[0]) - } else if list[1] != "2" { - t.Errorf("invalid header value at 1: %s", list[1]) - } -} - -func TestHeaders_GetInvalidKey(t *testing.T) { - hdr := make(Headers) - hdr.Set("foo", "1", "2") - - if _, ok := hdr.Get("invalid"); ok { - t.Error("found invalid key") - } -} - -func TestHeaders_Unset(t *testing.T) { - hdr := make(Headers) - hdr.Set("foo", "1", "2") - hdr.Set("foo") - - if _, ok := hdr.Get("foo"); ok { - t.Error("key not deleted") - } -} - -func TestHeader_Replace(t *testing.T) { - hdr := make(Headers) - hdr.Set("foo", "1", "2") - hdr.Set("foo", "3", "4") - - if list, ok := hdr.Get("foo"); !ok { - t.Error("key not found") - } else if len(list) != 2 { - t.Errorf("unexpected number of values: %d", len(list)) - } else if list[0] != "3" { - t.Errorf("invalid header value at 0: %s", list[0]) - } else if list[1] != "4" { - t.Errorf("invalid header value at 1: %s", list[1]) - } -} - -func TestHeaders_Append(t *testing.T) { - hdr := make(Headers) - hdr.Set("foo", "1") - hdr.Append("foo", "2") - hdr.Append("bar", "3") - - if list, ok := hdr.Get("foo"); !ok { - t.Error("key not found") - } else if len(list) != 2 { - t.Errorf("unexpected number of values: %d", len(list)) - } else if list[0] != "1" { - t.Errorf("invalid header value at 0: %s", list[0]) - } else if list[1] != "2" { - t.Errorf("invalid header value at 1: %s", list[1]) - } - - if list, ok := hdr.Get("bar"); !ok { - t.Error("key not found") - } else if list[0] != "3" { - t.Errorf("invalid value: %s", list[0]) - } -} - -func TestHeaders_Context(t *testing.T) { - hdr := make(Headers) - hdr.Set("foo", "bar") - - ctx := WithHeaders(context.Background(), hdr) - - if bar, ok := GetHeader(ctx, "foo"); !ok { - t.Error("header not found") - } else if bar != "bar" { - t.Errorf("invalid header value: %q", bar) - } -} diff --git a/metadata.go b/metadata.go new file mode 100644 index 0000000..d036263 --- /dev/null +++ b/metadata.go @@ -0,0 +1,86 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import "context" + +// Metadata represents the key-value pairs (similar to http.Header) to be passed to ttrpc server from a client. +type Metadata map[string]StringList + +// Get returns the metadata for a given key when they exist. +// If there is no metadata, a nil slice and false are returned. +func (m Metadata) Get(key string) ([]string, bool) { + list, ok := m[key] + if !ok || len(list.List) == 0 { + return nil, false + } + + return list.List, true +} + +// Set sets the provided values for a given key. +// The values will overwrite any existing values. +// If no values provided, a key will be deleted. +func (m Metadata) Set(key string, values ...string) { + if len(values) == 0 { + delete(m, key) + return + } + + m[key] = StringList{List: values} +} + +// Append appends additional values to the given key. +func (m Metadata) Append(key string, values ...string) { + if len(values) == 0 { + return + } + + list, ok := m[key] + if ok { + m.Set(key, append(list.List, values...)...) + } else { + m.Set(key, values...) + } +} + +type metadataKey struct{} + +// GetMetadata retrieves metadata from context.Context (previously attached with WithMetadata) +func GetMetadata(ctx context.Context) (Metadata, bool) { + metadata, ok := ctx.Value(metadataKey{}).(Metadata) + return metadata, ok +} + +// GetMetadataValue gets a specific metadata value by name from context.Context +func GetMetadataValue(ctx context.Context, name string) (string, bool) { + metadata, ok := GetMetadata(ctx) + if !ok { + return "", false + } + + if list, ok := metadata.Get(name); ok { + return list[0], true + } + + return "", false +} + +// WithMetadata attaches metadata map to a context.Context +func WithMetadata(ctx context.Context, headers Metadata) context.Context { + return context.WithValue(ctx, metadataKey{}, headers) +} diff --git a/metadata_test.go b/metadata_test.go new file mode 100644 index 0000000..c334e05 --- /dev/null +++ b/metadata_test.go @@ -0,0 +1,108 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ttrpc + +import ( + "context" + "testing" +) + +func TestMetadata_Get(t *testing.T) { + metadata := make(Metadata) + metadata.Set("foo", "1", "2") + + if list, ok := metadata.Get("foo"); !ok { + t.Error("key not found") + } else if len(list) != 2 { + t.Errorf("unexpected number of values: %d", len(list)) + } else if list[0] != "1" { + t.Errorf("invalid metadata value at 0: %s", list[0]) + } else if list[1] != "2" { + t.Errorf("invalid metadata value at 1: %s", list[1]) + } +} + +func TestMetadata_GetInvalidKey(t *testing.T) { + metadata := make(Metadata) + metadata.Set("foo", "1", "2") + + if _, ok := metadata.Get("invalid"); ok { + t.Error("found invalid key") + } +} + +func TestMetadata_Unset(t *testing.T) { + metadata := make(Metadata) + metadata.Set("foo", "1", "2") + metadata.Set("foo") + + if _, ok := metadata.Get("foo"); ok { + t.Error("key not deleted") + } +} + +func TestMetadata_Replace(t *testing.T) { + metadata := make(Metadata) + metadata.Set("foo", "1", "2") + metadata.Set("foo", "3", "4") + + if list, ok := metadata.Get("foo"); !ok { + t.Error("key not found") + } else if len(list) != 2 { + t.Errorf("unexpected number of values: %d", len(list)) + } else if list[0] != "3" { + t.Errorf("invalid metadata value at 0: %s", list[0]) + } else if list[1] != "4" { + t.Errorf("invalid metadata value at 1: %s", list[1]) + } +} + +func TestMetadata_Append(t *testing.T) { + metadata := make(Metadata) + metadata.Set("foo", "1") + metadata.Append("foo", "2") + metadata.Append("bar", "3") + + if list, ok := metadata.Get("foo"); !ok { + t.Error("key not found") + } else if len(list) != 2 { + t.Errorf("unexpected number of values: %d", len(list)) + } else if list[0] != "1" { + t.Errorf("invalid metadata value at 0: %s", list[0]) + } else if list[1] != "2" { + t.Errorf("invalid metadata value at 1: %s", list[1]) + } + + if list, ok := metadata.Get("bar"); !ok { + t.Error("key not found") + } else if list[0] != "3" { + t.Errorf("invalid value: %s", list[0]) + } +} + +func TestMetadata_Context(t *testing.T) { + metadata := make(Metadata) + metadata.Set("foo", "bar") + + ctx := WithMetadata(context.Background(), metadata) + + if bar, ok := GetMetadataValue(ctx, "foo"); !ok { + t.Error("metadata not found") + } else if bar != "bar" { + t.Errorf("invalid metadata value: %q", bar) + } +} diff --git a/server.go b/server.go index a6d9ef0..595a69a 100644 --- a/server.go +++ b/server.go @@ -466,8 +466,8 @@ func (c *serverConn) run(sctx context.Context) { var noopFunc = func() {} func getRequestContext(ctx context.Context, req *Request) (retCtx context.Context, cancel func()) { - if req.Headers != nil { - ctx = WithHeaders(ctx, req.Headers) + if req.Metadata != nil { + ctx = WithMetadata(ctx, req.Metadata) } cancel = noopFunc diff --git a/server_test.go b/server_test.go index 88e9919..d76ece1 100644 --- a/server_test.go +++ b/server_test.go @@ -61,7 +61,7 @@ func (tc *testingClient) Test(ctx context.Context, req *testPayload) (*testPaylo type testPayload struct { Foo string `protobuf:"bytes,1,opt,name=foo,proto3"` Deadline int64 `protobuf:"varint,2,opt,name=deadline,proto3"` - Hdr string `protobuf:"bytes,3,opt,name=hdr,proto3"` + Metadata string `protobuf:"bytes,3,opt,name=metadata,proto3"` } func (r *testPayload) Reset() { *r = testPayload{} } @@ -77,8 +77,8 @@ func (s *testingServer) Test(ctx context.Context, req *testPayload) (*testPayloa tp.Deadline = dl.UnixNano() } - if v, ok := GetHeader(ctx, "foo"); ok { - tp.Hdr = v + if v, ok := GetMetadataValue(ctx, "foo"); ok { + tp.Metadata = v } return tp, nil @@ -546,7 +546,7 @@ func roundTrip(ctx context.Context, t *testing.T, client *testingClient, value s } ) - ctx = WithHeaders(ctx, Headers{"foo": makeStringList("bar")}) + ctx = WithMetadata(ctx, Metadata{"foo": makeStringList("bar")}) resp, err := client.Test(ctx, tp) if err != nil { @@ -555,7 +555,7 @@ func roundTrip(ctx context.Context, t *testing.T, client *testingClient, value s results <- callResult{ input: tp, - expected: &testPayload{Foo: strings.Repeat(tp.Foo, 2), Hdr: "bar"}, + expected: &testPayload{Foo: strings.Repeat(tp.Foo, 2), Metadata: "bar"}, received: resp, } } diff --git a/types.go b/types.go index bebc123..c8ecb38 100644 --- a/types.go +++ b/types.go @@ -23,11 +23,11 @@ import ( ) type Request struct { - Service string `protobuf:"bytes,1,opt,name=service,proto3"` - Method string `protobuf:"bytes,2,opt,name=method,proto3"` - Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3"` - TimeoutNano int64 `protobuf:"varint,4,opt,name=timeout_nano,proto3"` - Headers Headers `protobuf:"bytes,5,opt,name=headers,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Service string `protobuf:"bytes,1,opt,name=service,proto3"` + Method string `protobuf:"bytes,2,opt,name=method,proto3"` + Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3"` + TimeoutNano int64 `protobuf:"varint,4,opt,name=timeout_nano,proto3"` + Metadata Metadata `protobuf:"bytes,5,opt,name=metadata,proto3" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (r *Request) Reset() { *r = Request{} }