From 6a6cce9da51e7b502a5ea82cb6ae4448a8722ea4 Mon Sep 17 00:00:00 2001 From: lsjbd <44455379+lsjbd@users.noreply.github.com> Date: Tue, 11 May 2021 17:52:55 +0800 Subject: [PATCH] feat: add HTTP APIs for cloud/metainfo (#31) --- cloud/metainfo/http.go | 108 ++++++++++++++++++++++++++++++++++++ cloud/metainfo/http_test.go | 101 +++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 cloud/metainfo/http.go create mode 100644 cloud/metainfo/http_test.go diff --git a/cloud/metainfo/http.go b/cloud/metainfo/http.go new file mode 100644 index 00000000..fe278b6e --- /dev/null +++ b/cloud/metainfo/http.go @@ -0,0 +1,108 @@ +// Copyright 2021 ByteDance Inc. +// +// 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 metainfo + +import ( + "context" + "strings" +) + +// HTTP header prefixes. +const ( + HTTPPrefixTransient = "rpc-transit-" + HTTPPrefixPersistent = "rpc-persist-" + + lenHPT = len(HTTPPrefixTransient) + lenHPP = len(HTTPPrefixPersistent) +) + +// HTTPHeaderToCGIVariable performs an CGI variable conversion. +// For example, an HTTP header key `abc-def` will result in `ABC_DEF`. +func HTTPHeaderToCGIVariable(key string) string { + return strings.ToUpper(strings.ReplaceAll(key, "-", "_")) +} + +// CGIVariableToHTTPHeader converts a CGI variable into an HTTP header key. +// For example, `ABC_DEF` will be converted to `abc-def`. +func CGIVariableToHTTPHeader(key string) string { + return strings.ToLower(strings.ReplaceAll(key, "_", "-")) +} + +// HTTPHeaderSetter sets a key with a value into a HTTP header. +type HTTPHeaderSetter interface { + Set(key, value string) +} + +// HTTPHeaderCarrier accepts a visitor to access all key value pairs in an HTTP header. +type HTTPHeaderCarrier interface { + Visit(func(k, v string)) +} + +// HTTPHeader is provided to wrap an http.Header into an HTTPHeaderCarrier. +type HTTPHeader map[string][]string + +// Visit implements the HTTPHeaderCarrier interface. +func (h HTTPHeader) Visit(v func(k, v string)) { + for k, vs := range h { + v(k, vs[0]) + } +} + +// Set sets the header entries associated with key to the single element value. +// The key will converted into lowercase as the HTTP/2 protocol requires. +func (h HTTPHeader) Set(key, value string) { + h[strings.ToLower(key)] = []string{value} +} + +// FromHTTPHeader reads metainfo from a given HTTP header and sets them into the context. +// Note that this function does not call TransferForward inside. +func FromHTTPHeader(ctx context.Context, h HTTPHeaderCarrier) context.Context { + if ctx == nil || h == nil { + return ctx + } + + h.Visit(func(k, v string) { + kk := strings.ToLower(k) + ln := len(kk) + + if ln > lenHPT && strings.HasPrefix(kk, HTTPPrefixTransient) { + kk = HTTPHeaderToCGIVariable(kk[lenHPT:]) + ctx = WithValue(ctx, kk, v) + } else if ln > lenHPP && strings.HasPrefix(kk, HTTPPrefixPersistent) { + kk = HTTPHeaderToCGIVariable(kk[lenHPP:]) + ctx = WithPersistentValue(ctx, kk, v) + } + }) + + return ctx +} + +// ToHTTPHeader writes all metainfo into the given HTTP header. +// Note that this function does not call TransferForward inside. +func ToHTTPHeader(ctx context.Context, h HTTPHeaderSetter) { + if ctx == nil || h == nil { + return + } + + for k, v := range GetAllValues(ctx) { + k := HTTPPrefixTransient + CGIVariableToHTTPHeader(k) + h.Set(k, v) + } + + for k, v := range GetAllPersistentValues(ctx) { + k := HTTPPrefixPersistent + CGIVariableToHTTPHeader(k) + h.Set(k, v) + } +} diff --git a/cloud/metainfo/http_test.go b/cloud/metainfo/http_test.go new file mode 100644 index 00000000..5c4e706b --- /dev/null +++ b/cloud/metainfo/http_test.go @@ -0,0 +1,101 @@ +// Copyright 2021 ByteDance Inc. +// +// 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 metainfo_test + +import ( + "context" + "net/http" + "testing" + + "github.com/bytedance/gopkg/cloud/metainfo" +) + +func TestHTTPHeaderToCGIVariable(t *testing.T) { + for k, v := range map[string]string{ + "a": "A", + "aBc": "ABC", + "a1z": "A1Z", + "ab-": "AB_", + "-cd": "_CD", + "abc-def": "ABC_DEF", + "Abc-def_ghi": "ABC_DEF_GHI", + } { + assert(t, metainfo.HTTPHeaderToCGIVariable(k) == v) + } +} + +func TestCGIVariableToHTTPHeader(t *testing.T) { + for k, v := range map[string]string{ + "a": "a", + "aBc": "abc", + "a1z": "a1z", + "AB_": "ab-", + "_CD": "-cd", + "ABC_DEF": "abc-def", + "ABC-def_GHI": "abc-def-ghi", + } { + assert(t, metainfo.CGIVariableToHTTPHeader(k) == v) + } +} + +func TestFromHTTPHeader(t *testing.T) { + assert(t, metainfo.FromHTTPHeader(nil, nil) == nil) + + h := make(http.Header) + c := context.Background() + c1 := metainfo.FromHTTPHeader(c, metainfo.HTTPHeader(h)) + assert(t, c == c1) + + h.Set("abc", "def") + h.Set(metainfo.HTTPPrefixTransient+"123", "456") + h.Set(metainfo.HTTPPrefixTransient+"abc-def", "ghi") + h.Set(metainfo.HTTPPrefixPersistent+"xyz", "000") + c1 = metainfo.FromHTTPHeader(c, metainfo.HTTPHeader(h)) + assert(t, c != c1) + vs := metainfo.GetAllValues(c1) + assert(t, len(vs) == 2, vs) + assert(t, vs["ABC_DEF"] == "ghi" && vs["123"] == "456", vs) + vs = metainfo.GetAllPersistentValues(c1) + assert(t, len(vs) == 1 && vs["XYZ"] == "000") +} + +func TestToHTTPHeader(t *testing.T) { + metainfo.ToHTTPHeader(nil, nil) + + h := make(http.Header) + c := context.Background() + metainfo.ToHTTPHeader(c, h) + assert(t, len(h) == 0) + + c = metainfo.WithValue(c, "123", "456") + c = metainfo.WithPersistentValue(c, "abc", "def") + metainfo.ToHTTPHeader(c, h) + assert(t, len(h) == 2) + assert(t, h.Get(metainfo.HTTPPrefixTransient+"123") == "456") + assert(t, h.Get(metainfo.HTTPPrefixPersistent+"abc") == "def") +} + +func TestHTTPHeader(t *testing.T) { + h := make(metainfo.HTTPHeader) + h.Set("Hello", "halo") + h.Set("hello", "world") + + kvs := make(map[string]string) + h.Visit(func(k, v string) { + kvs[k] = v + }) + assert(t, len(kvs) == 1) + assert(t, kvs["hello"] == "world") +}