package runtime
import (
"context"
"encoding/base64"
"fmt"
"net"
"net/http"
"net/textproto"
"strconv"
"strings"
"sync"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
const MetadataHeaderPrefix = "Grpc-Metadata-"
const MetadataPrefix = "grpcgateway-"
const MetadataTrailerPrefix = "Grpc-Trailer-"
const metadataGrpcTimeout = "Grpc-Timeout"
const metadataHeaderBinarySuffix = "-Bin"
const xForwardedFor = "X-Forwarded-For"
const xForwardedHost = "X-Forwarded-Host"
var DefaultContextTimeout = 0 * time .Second
var malformedHTTPHeaders = map [string ]struct {}{
"connection" : {},
}
type (
rpcMethodKey struct {}
httpPathPatternKey struct {}
httpPatternKey struct {}
AnnotateContextOption func (ctx context .Context ) context .Context
)
func WithHTTPPathPattern (pattern string ) AnnotateContextOption {
return func (ctx context .Context ) context .Context {
return withHTTPPathPattern (ctx , pattern )
}
}
func decodeBinHeader(v string ) ([]byte , error ) {
if len (v )%4 == 0 {
return base64 .StdEncoding .DecodeString (v )
}
return base64 .RawStdEncoding .DecodeString (v )
}
func AnnotateContext (ctx context .Context , mux *ServeMux , req *http .Request , rpcMethodName string , options ...AnnotateContextOption ) (context .Context , error ) {
ctx , md , err := annotateContext (ctx , mux , req , rpcMethodName , options ...)
if err != nil {
return nil , err
}
if md == nil {
return ctx , nil
}
return metadata .NewOutgoingContext (ctx , md ), nil
}
func AnnotateIncomingContext (ctx context .Context , mux *ServeMux , req *http .Request , rpcMethodName string , options ...AnnotateContextOption ) (context .Context , error ) {
ctx , md , err := annotateContext (ctx , mux , req , rpcMethodName , options ...)
if err != nil {
return nil , err
}
if md == nil {
return ctx , nil
}
return metadata .NewIncomingContext (ctx , md ), nil
}
func isValidGRPCMetadataKey(key string ) bool {
bytes := []byte (key )
for _ , ch := range bytes {
validLowercaseLetter := ch >= 'a' && ch <= 'z'
validUppercaseLetter := ch >= 'A' && ch <= 'Z'
validDigit := ch >= '0' && ch <= '9'
validOther := ch == '.' || ch == '-' || ch == '_'
if !validLowercaseLetter && !validUppercaseLetter && !validDigit && !validOther {
return false
}
}
return true
}
func isValidGRPCMetadataTextValue(textValue string ) bool {
bytes := []byte (textValue )
for _ , ch := range bytes {
if ch < 0x20 || ch > 0x7E {
return false
}
}
return true
}
func annotateContext(ctx context .Context , mux *ServeMux , req *http .Request , rpcMethodName string , options ...AnnotateContextOption ) (context .Context , metadata .MD , error ) {
ctx = withRPCMethod (ctx , rpcMethodName )
for _ , o := range options {
ctx = o (ctx )
}
timeout := DefaultContextTimeout
if tm := req .Header .Get (metadataGrpcTimeout ); tm != "" {
var err error
timeout , err = timeoutDecode (tm )
if err != nil {
return nil , nil , status .Errorf (codes .InvalidArgument , "invalid grpc-timeout: %s" , tm )
}
}
var pairs []string
for key , vals := range req .Header {
key = textproto .CanonicalMIMEHeaderKey (key )
switch key {
case xForwardedFor , xForwardedHost :
continue
}
for _ , val := range vals {
if key == "Authorization" {
pairs = append (pairs , "authorization" , val )
}
if h , ok := mux .incomingHeaderMatcher (key ); ok {
if !isValidGRPCMetadataKey (h ) {
grpclog .Errorf ("HTTP header name %q is not valid as gRPC metadata key; skipping" , h )
continue
}
if strings .HasSuffix (key , metadataHeaderBinarySuffix ) {
b , err := decodeBinHeader (val )
if err != nil {
return nil , nil , status .Errorf (codes .InvalidArgument , "invalid binary header %s: %s" , key , err )
}
val = string (b )
} else if !isValidGRPCMetadataTextValue (val ) {
grpclog .Errorf ("Value of HTTP header %q contains non-ASCII value (not valid as gRPC metadata): skipping" , h )
continue
}
pairs = append (pairs , h , val )
}
}
}
if host := req .Header .Get (xForwardedHost ); host != "" {
pairs = append (pairs , strings .ToLower (xForwardedHost ), host )
} else if req .Host != "" {
pairs = append (pairs , strings .ToLower (xForwardedHost ), req .Host )
}
xff := req .Header .Values (xForwardedFor )
if addr := req .RemoteAddr ; addr != "" {
if remoteIP , _ , err := net .SplitHostPort (addr ); err == nil {
xff = append (xff , remoteIP )
}
}
if len (xff ) > 0 {
pairs = append (pairs , strings .ToLower (xForwardedFor ), strings .Join (xff , ", " ))
}
if timeout != 0 {
ctx , _ = context .WithTimeout (ctx , timeout )
}
if len (pairs ) == 0 {
return ctx , nil , nil
}
md := metadata .Pairs (pairs ...)
for _ , mda := range mux .metadataAnnotators {
md = metadata .Join (md , mda (ctx , req ))
}
return ctx , md , nil
}
type ServerMetadata struct {
HeaderMD metadata .MD
TrailerMD metadata .MD
}
type serverMetadataKey struct {}
func NewServerMetadataContext (ctx context .Context , md ServerMetadata ) context .Context {
if ctx == nil {
ctx = context .Background ()
}
return context .WithValue (ctx , serverMetadataKey {}, md )
}
func ServerMetadataFromContext (ctx context .Context ) (md ServerMetadata , ok bool ) {
if ctx == nil {
return md , false
}
md , ok = ctx .Value (serverMetadataKey {}).(ServerMetadata )
return
}
type ServerTransportStream struct {
mu sync .Mutex
header metadata .MD
trailer metadata .MD
}
func (s *ServerTransportStream ) Method () string {
return ""
}
func (s *ServerTransportStream ) Header () metadata .MD {
s .mu .Lock ()
defer s .mu .Unlock ()
return s .header .Copy ()
}
func (s *ServerTransportStream ) SetHeader (md metadata .MD ) error {
if md .Len () == 0 {
return nil
}
s .mu .Lock ()
s .header = metadata .Join (s .header , md )
s .mu .Unlock ()
return nil
}
func (s *ServerTransportStream ) SendHeader (md metadata .MD ) error {
return s .SetHeader (md )
}
func (s *ServerTransportStream ) Trailer () metadata .MD {
s .mu .Lock ()
defer s .mu .Unlock ()
return s .trailer .Copy ()
}
func (s *ServerTransportStream ) SetTrailer (md metadata .MD ) error {
if md .Len () == 0 {
return nil
}
s .mu .Lock ()
s .trailer = metadata .Join (s .trailer , md )
s .mu .Unlock ()
return nil
}
func timeoutDecode(s string ) (time .Duration , error ) {
size := len (s )
if size < 2 {
return 0 , fmt .Errorf ("timeout string is too short: %q" , s )
}
d , ok := timeoutUnitToDuration (s [size -1 ])
if !ok {
return 0 , fmt .Errorf ("timeout unit is not recognized: %q" , s )
}
t , err := strconv .ParseInt (s [:size -1 ], 10 , 64 )
if err != nil {
return 0 , err
}
return d * time .Duration (t ), nil
}
func timeoutUnitToDuration(u uint8 ) (d time .Duration , ok bool ) {
switch u {
case 'H' :
return time .Hour , true
case 'M' :
return time .Minute , true
case 'S' :
return time .Second , true
case 'm' :
return time .Millisecond , true
case 'u' :
return time .Microsecond , true
case 'n' :
return time .Nanosecond , true
default :
return
}
}
func isPermanentHTTPHeader(hdr string ) bool {
switch hdr {
case
"Accept" ,
"Accept-Charset" ,
"Accept-Language" ,
"Accept-Ranges" ,
"Authorization" ,
"Cache-Control" ,
"Content-Type" ,
"Cookie" ,
"Date" ,
"Expect" ,
"From" ,
"Host" ,
"If-Match" ,
"If-Modified-Since" ,
"If-None-Match" ,
"If-Schedule-Tag-Match" ,
"If-Unmodified-Since" ,
"Max-Forwards" ,
"Origin" ,
"Pragma" ,
"Referer" ,
"User-Agent" ,
"Via" ,
"Warning" :
return true
}
return false
}
func isMalformedHTTPHeader(header string ) bool {
_ , isMalformed := malformedHTTPHeaders [strings .ToLower (header )]
return isMalformed
}
func RPCMethod (ctx context .Context ) (string , bool ) {
m := ctx .Value (rpcMethodKey {})
if m == nil {
return "" , false
}
ms , ok := m .(string )
if !ok {
return "" , false
}
return ms , true
}
func withRPCMethod(ctx context .Context , rpcMethodName string ) context .Context {
return context .WithValue (ctx , rpcMethodKey {}, rpcMethodName )
}
func HTTPPathPattern (ctx context .Context ) (string , bool ) {
m := ctx .Value (httpPathPatternKey {})
if m == nil {
return "" , false
}
ms , ok := m .(string )
if !ok {
return "" , false
}
return ms , true
}
func withHTTPPathPattern(ctx context .Context , httpPathPattern string ) context .Context {
return context .WithValue (ctx , httpPathPatternKey {}, httpPathPattern )
}
func HTTPPattern (ctx context .Context ) (Pattern , bool ) {
v , ok := ctx .Value (httpPatternKey {}).(Pattern )
return v , ok
}
func withHTTPPattern(ctx context .Context , httpPattern Pattern ) context .Context {
return context .WithValue (ctx , httpPatternKey {}, httpPattern )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .