Skip to content

Commit

Permalink
feat: add HTTP APIs for cloud/metainfo (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
lsjbd authored May 11, 2021
1 parent 24980f0 commit 6a6cce9
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 0 deletions.
108 changes: 108 additions & 0 deletions cloud/metainfo/http.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
101 changes: 101 additions & 0 deletions cloud/metainfo/http_test.go
Original file line number Diff line number Diff line change
@@ -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")
}

0 comments on commit 6a6cce9

Please sign in to comment.