blob: 35b12ca1c4ed7b594430974a8b06ce77c40a31d9 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 triple_protocol
import (
"context"
"encoding/base64"
"fmt"
"net/http"
)
// EncodeBinaryHeader base64-encodes the data. It always emits unpadded values.
//
// In the Triple, gRPC, and gRPC-Web protocols, binary headers must have keys
// ending in "-Bin".
func EncodeBinaryHeader(data []byte) string {
// gRPC specification says that implementations should emit unpadded values.
return base64.RawStdEncoding.EncodeToString(data)
}
// DecodeBinaryHeader base64-decodes the data. It can decode padded or unpadded
// values. Following usual HTTP semantics, multiple base64-encoded values may
// be joined with a comma. When receiving such comma-separated values, split
// them with [strings.Split] before calling DecodeBinaryHeader.
//
// Binary headers sent using the Triple, gRPC, and gRPC-Web protocols have
// keys ending in "-Bin".
func DecodeBinaryHeader(data string) ([]byte, error) {
if len(data)%4 != 0 {
// Data definitely isn't padded.
return base64.RawStdEncoding.DecodeString(data)
}
// Either the data was padded, or padding wasn't necessary. In both cases,
// the padding-aware decoder works.
return base64.StdEncoding.DecodeString(data)
}
func mergeHeaders(into, from http.Header) {
for k, vals := range from {
into[k] = append(into[k], vals...)
}
}
// getCanonicalHeader is a shortcut for Header.Get() which
// bypasses the CanonicalMIMEHeaderKey operation when we
// know the key is already in canonical form.
func getHeaderCanonical(h http.Header, key string) string {
if h == nil {
return ""
}
v := h[key]
if len(v) == 0 {
return ""
}
return v[0]
}
// setHeaderCanonical is a shortcut for Header.Set() which
// bypasses the CanonicalMIMEHeaderKey operation when we
// know the key is already in canonical form.
func setHeaderCanonical(h http.Header, key, value string) {
h[key] = []string{value}
}
// delHeaderCanonical is a shortcut for Header.Del() which
// bypasses the CanonicalMIMEHeaderKey operation when we
// know the key is already in canonical form.
func delHeaderCanonical(h http.Header, key string) {
delete(h, key)
}
// addHeaderCanonical is a shortcut for Header.Add() which
// bypasses the CanonicalMIMEHeaderKey operation when we
// know the key is already in canonical form.
func addHeaderCanonical(h http.Header, key, value string) {
h[key] = append(h[key], value)
}
type extraDataKey struct{}
const headerIncomingKey string = "headerIncomingKey"
const headerOutgoingKey string = "headerOutgoingKey"
type handlerOutgoingKey struct{}
func newIncomingContext(ctx context.Context, data http.Header) context.Context {
var header = http.Header{}
extraData, ok := ctx.Value(extraDataKey{}).(map[string]http.Header)
if !ok {
extraData = map[string]http.Header{}
} else {
extraData = cloneExtraData(extraData)
}
for key, vals := range data {
// Context headers use canonical keys so http.Header.Get/Values work as expected.
header[http.CanonicalHeaderKey(key)] = append([]string(nil), vals...)
}
extraData[headerIncomingKey] = header
return context.WithValue(ctx, extraDataKey{}, extraData)
}
// NewOutgoingContext sets outgoing request headers on ctx. If ctx already has
// outgoing headers, they are replaced.
//
// For example:
//
// ctx := NewOutgoingContext(context.Background(), http.Header{
// "hello": []string{"triple"},
// })
// resp, err := client.Greet(ctx, &greet.GreetRequest{})
//
// Use AppendToOutgoingContext to add more values without replacing existing
// outgoing headers. These APIs follow the same pattern as grpc metadata:
// https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md#sending-metadata.
func NewOutgoingContext(ctx context.Context, data http.Header) context.Context {
var header = http.Header{}
for key, vals := range data {
// Context headers use canonical keys so http.Header.Get/Values work as expected.
header[http.CanonicalHeaderKey(key)] = append([]string(nil), vals...)
}
extraData, ok := ctx.Value(extraDataKey{}).(map[string]http.Header)
if !ok {
extraData = map[string]http.Header{}
} else {
extraData = cloneExtraData(extraData)
}
extraData[headerOutgoingKey] = header
return context.WithValue(ctx, extraDataKey{}, extraData)
}
func cloneExtraData(data map[string]http.Header) map[string]http.Header {
cloned := make(map[string]http.Header, len(data))
for extraKey, header := range data {
headerClone := make(http.Header, len(header))
for headerKey, vals := range header {
headerClone[headerKey] = append([]string(nil), vals...)
}
cloned[extraKey] = headerClone
}
return cloned
}
// AppendToOutgoingContext appends key-value pairs to outgoing request headers.
// It panics when kv contains an odd number of strings.
//
// For example:
//
// ctx := NewOutgoingContext(context.Background(), http.Header{
// "hello": []string{"triple"},
// })
// ctx = AppendToOutgoingContext(ctx, "hello", "dubbo", "hey", "hessian")
// resp, err := client.Greet(ctx, &greet.GreetRequest{})
//
// The example sends "hello: triple", "hello: dubbo", and "hey: hessian" to the
// server. This API follows the same pattern as grpc metadata:
// https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md#sending-metadata.
func AppendToOutgoingContext(ctx context.Context, kv ...string) context.Context {
if len(kv)%2 == 1 {
panic(fmt.Sprintf("AppendToOutgoingContext got an odd number of input pairs for header: %d", len(kv)))
}
extraData, ok := ctx.Value(extraDataKey{}).(map[string]http.Header)
if !ok {
extraData = map[string]http.Header{}
ctx = context.WithValue(ctx, extraDataKey{}, extraData)
}
header, ok := extraData[headerOutgoingKey]
if !ok {
header = make(http.Header)
extraData[headerOutgoingKey] = header
}
for i := 0; i < len(kv); i += 2 {
key := http.CanonicalHeaderKey(kv[i])
header[key] = append(header[key], kv[i+1])
}
return ctx
}
func ExtractFromOutgoingContext(ctx context.Context) http.Header {
extraData, ok := ctx.Value(extraDataKey{}).(map[string]http.Header)
if !ok {
return nil
}
if outGoingDataHeader, ok := extraData[headerOutgoingKey]; !ok {
return nil
} else {
return outGoingDataHeader
}
}
// FromIncomingContext retrieves request headers received by a server handler.
//
// For example:
//
// func (srv *Server) Greet(ctx context.Context, req *greet.GreetRequest) (*greet.GreetResponse, error) {
// headers, ok := FromIncomingContext(ctx)
// if ok {
// values := headers.Values("hello")
// // Use values.
// }
// return &greet.GreetResponse{}, nil
// }
//
// This API follows the same pattern as grpc metadata:
// https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md#receiving-metadata-1.
func FromIncomingContext(ctx context.Context) (http.Header, bool) {
data, ok := ctx.Value(extraDataKey{}).(map[string]http.Header)
if !ok {
return nil, false
} else if incomingDataHeader, ok := data[headerIncomingKey]; !ok {
return nil, false
} else {
return incomingDataHeader, true
}
}
// SetHeader appends response headers from a server handler. The headers are
// buffered and sent with the response instead of being sent immediately.
//
// For example:
//
// func (srv *Server) Greet(ctx context.Context, req *greet.GreetRequest) (*greet.GreetResponse, error) {
// if err := SetHeader(ctx, http.Header{"hi": []string{"triple"}}); err != nil {
// return nil, err
// }
// return &greet.GreetResponse{}, nil
// }
//
// This API follows the same pattern as grpc metadata:
// https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md#unary-call-2.
func SetHeader(ctx context.Context, header http.Header) error {
conn, ok := ctx.Value(handlerOutgoingKey{}).(StreamingHandlerConn)
if !ok {
// todo(DMwangnima): return standard error
return fmt.Errorf("triple: failed to fetch the connection from the context %v", ctx)
}
mergeHeaders(conn.ResponseHeader(), header)
return nil
}
// SetTrailer appends response trailers from a server handler.
//
// For example:
//
// func (srv *Server) Greet(ctx context.Context, req *greet.GreetRequest) (*greet.GreetResponse, error) {
// if err := SetTrailer(ctx, http.Header{"end": []string{"end"}}); err != nil {
// return nil, err
// }
// return &greet.GreetResponse{}, nil
// }
//
// This API follows the same pattern as grpc metadata:
// https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md#unary-call-2.
func SetTrailer(ctx context.Context, trailer http.Header) error {
conn, ok := ctx.Value(handlerOutgoingKey{}).(StreamingHandlerConn)
if !ok {
// todo(DMwangnima): return standard error
return fmt.Errorf("triple: failed to fetch the connection from the context %v", ctx)
}
mergeHeaders(conn.ResponseTrailer(), trailer)
return nil
}
// SendHeader appends response headers from a server handler and sends them
// immediately. This is useful for streaming handlers that need to flush headers
// before the first message.
//
// For example:
//
// func (srv *Server) GreetStream(ctx context.Context, stream greet.GreetService_GreetStreamServer) error {
// if err := SendHeader(ctx, http.Header{"hi": []string{"triple"}}); err != nil {
// return err
// }
// return stream.Send(&greet.GreetResponse{})
// }
//
// This API follows the same pattern as grpc metadata:
// https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md#unary-call-2.
func SendHeader(ctx context.Context, header http.Header) error {
conn, ok := ctx.Value(handlerOutgoingKey{}).(StreamingHandlerConn)
if !ok {
// todo(DMwangnima): return standard error
return fmt.Errorf("triple: failed to fetch the connection from the context %v", ctx)
}
mergeHeaders(conn.RequestHeader(), header)
return conn.Send(nil)
}