runtime/server_header.go (150 lines of code) (raw):
// Copyright (c) 2023 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 zanzibar
import (
"net/http"
"net/textproto"
"strings"
"context"
"github.com/pkg/errors"
"go.uber.org/zap"
)
// Header defines methods on ServerHeaders
type Header interface {
// Get mirrors the implementation of http.header and returns a single header value.
// When a key contains multiple values, -only- the first one is returned.
// Ref: https://golang.org/pkg/net/http/#Header.Get
Get(key string) (string, bool)
// Values mirrors the implementation of http.header and returns a slice of header values.
// When a key contains multiple values, the entire collection is returned.
// Ref: https://golang.org/pkg/net/http/#Header.Values
Values(key string) ([]string, bool)
Add(key string, value string)
Set(key string, value string)
// Unset unsets the value for a given header. Can be safely called multiple times
Unset(key string)
Keys() []string
// Deprecated: Use EnsureContext instead
Ensure(keys []string, logger *zap.Logger) error
EnsureContext(ctx context.Context, keys []string, logger ContextLogger) error
}
// ServerHTTPHeader wrapper to implement zanzibar Header interface
// on http.Header
type ServerHTTPHeader http.Header
// NewServerHTTPHeader creates a server http header
func NewServerHTTPHeader(h http.Header) ServerHTTPHeader {
return ServerHTTPHeader(h)
}
// Get retrieves the first string stored on a given header. Bool
// return value is used to distinguish between the presence of a
// header with golang's zerovalue string and the absence of the header.
func (zh ServerHTTPHeader) Get(key string) (string, bool) {
httpKey := textproto.CanonicalMIMEHeaderKey(key)
h := zh[httpKey]
if len(h) > 0 {
return h[0], true
}
return "", false
}
// Values retrieves the entire collection of values stored on a given header.
// Bool return value is used to distinguish between the presence of a
// header with golang's zerovalue slice and the absence of the header.
func (zh ServerHTTPHeader) Values(key string) ([]string, bool) {
httpKey := textproto.CanonicalMIMEHeaderKey(key)
if _, ok := zh[httpKey]; ok {
return textproto.MIMEHeader(zh).Values(key), true
}
return []string{}, false
}
// GetOrEmptyStr retrieves the first string stored on a given header or
// the empty string (golang's zero vlaue for string types)
func (zh ServerHTTPHeader) GetOrEmptyStr(key string) string {
value, ok := zh.Get(key)
if ok {
return value
}
return ""
}
// GetAll retries all strings stored for this header.
func (zh ServerHTTPHeader) GetAll(key string) []string {
httpKey := textproto.CanonicalMIMEHeaderKey(key)
return zh[httpKey]
}
// Add appends a value for a given header.
func (zh ServerHTTPHeader) Add(key string, value string) {
httpKey := textproto.CanonicalMIMEHeaderKey(key)
zh[httpKey] = append(zh[httpKey], value)
}
// Set sets a value for a given header, overwriting all previous values.
func (zh ServerHTTPHeader) Set(key string, value string) {
httpKey := textproto.CanonicalMIMEHeaderKey(key)
zh[httpKey] = []string{value}
}
// Unset unsets the value for a given header. Can be safely called multiple times
func (zh ServerHTTPHeader) Unset(key string) {
httpKey := textproto.CanonicalMIMEHeaderKey(key)
delete(zh, httpKey)
}
// Keys returns a slice of header keys.
func (zh ServerHTTPHeader) Keys() []string {
keys := make([]string, len(zh))
i := 0
for k := range zh {
keys[i] = k
i++
}
return keys
}
// Ensure returns error if the headers do not have the given keys
//
// Deprecated: Use EnsureContext instead
func (zh ServerHTTPHeader) Ensure(keys []string, logger *zap.Logger) error {
loggerCtx := NewContextLogger(logger)
ctx := context.Background()
return zh.EnsureContext(ctx, keys, loggerCtx)
}
// EnsureContext returns error if the headers do not have the given keys
func (zh ServerHTTPHeader) EnsureContext(ctx context.Context, keys []string, logger ContextLogger) error {
missing := make([]string, 0, len(keys))
for _, header := range keys {
h := textproto.CanonicalMIMEHeaderKey(header)
if _, ok := zh[h]; !ok {
missing = append(missing, header)
}
}
if len(missing) == 0 {
return nil
}
err := errors.Errorf(
"Missing mandatory headers: %s",
strings.Join(missing, ", "),
)
logger.WarnZ(ctx, "Missing mandatory headers",
zap.Error(err),
zap.Strings("headers", missing),
)
return err
}
// ServerTChannelHeader wrapper to implement zanzibar Header interface
// on map[string]string
// Unlike http.Header, tchannel headers are case sensitive and should be
// keyed with lower case. TChannel protocol does not mention header case
// sensitivity, so it is up to implementation.
type ServerTChannelHeader map[string]string
// Get retrieves the string value stored on a given header. Bool
// return value is used to distinguish between the presence of a
// header with golang's zerovalue string and the absence of the string.
func (th ServerTChannelHeader) Get(key string) (string, bool) {
value, ok := th[key]
return value, ok
}
// Values retrieves the entire collection of values stored on a given header.
// Bool return value is used to distinguish between the presence of a
// header with golang's zerovalue slice and the absence of the header.
func (th ServerTChannelHeader) Values(key string) ([]string, bool) {
if value, ok := th.Get(key); ok {
// In the case of TChannel, ServerTChannelHeader does not support
// multiple, disparate values so we defer to Get and package it
// in a slice to meet the interface's requirement.
return []string{value}, ok
}
return []string{}, false
}
// Add is an alias to Set.
func (th ServerTChannelHeader) Add(key string, value string) {
th.Set(key, value)
}
// Set sets a value for a given header, overwriting the previous value.
func (th ServerTChannelHeader) Set(key string, value string) {
th[key] = value
}
// Unset unsets the value for a given header. Can be safely called multiple times
func (th ServerTChannelHeader) Unset(key string) {
delete(th, key)
}
// Keys returns a slice of header keys.
func (th ServerTChannelHeader) Keys() []string {
keys := make([]string, len(th))
i := 0
for k := range th {
keys[i] = k
i++
}
return keys
}
// Ensure returns error if the headers do not have the given keys
//
// Deprecated: Use EnsureContext instead
func (th ServerTChannelHeader) Ensure(keys []string, logger *zap.Logger) error {
loggerCtx := NewContextLogger(logger)
ctx := context.Background()
return th.EnsureContext(ctx, keys, loggerCtx)
}
// EnsureContext returns error if the headers do not have the given keys
func (th ServerTChannelHeader) EnsureContext(ctx context.Context, keys []string, logger ContextLogger) error {
missing := make([]string, 0, len(keys))
for _, header := range keys {
if _, ok := th[header]; !ok {
missing = append(missing, header)
}
}
if len(missing) == 0 {
return nil
}
err := errors.Errorf(
"Missing mandatory headers: %s",
strings.Join(missing, ", "),
)
logger.WarnZ(ctx, "Missing mandatory headers",
zap.Error(err),
zap.Strings("headers", missing),
)
return err
}