package transport
import (
"bufio"
"encoding/base64"
"errors"
"fmt"
"io"
"math"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"unicode/utf8"
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
"google.golang.org/grpc/codes"
)
const (
http2MaxFrameLen = 16384
http2InitHeaderTableSize = 4096
)
var (
clientPreface = []byte (http2 .ClientPreface )
http2ErrConvTab = map [http2 .ErrCode ]codes .Code {
http2 .ErrCodeNo : codes .Internal ,
http2 .ErrCodeProtocol : codes .Internal ,
http2 .ErrCodeInternal : codes .Internal ,
http2 .ErrCodeFlowControl : codes .ResourceExhausted ,
http2 .ErrCodeSettingsTimeout : codes .Internal ,
http2 .ErrCodeStreamClosed : codes .Internal ,
http2 .ErrCodeFrameSize : codes .Internal ,
http2 .ErrCodeRefusedStream : codes .Unavailable ,
http2 .ErrCodeCancel : codes .Canceled ,
http2 .ErrCodeCompression : codes .Internal ,
http2 .ErrCodeConnect : codes .Internal ,
http2 .ErrCodeEnhanceYourCalm : codes .ResourceExhausted ,
http2 .ErrCodeInadequateSecurity : codes .PermissionDenied ,
http2 .ErrCodeHTTP11Required : codes .Internal ,
}
HTTPStatusConvTab = map [int ]codes .Code {
http .StatusBadRequest : codes .Internal ,
http .StatusUnauthorized : codes .Unauthenticated ,
http .StatusForbidden : codes .PermissionDenied ,
http .StatusNotFound : codes .Unimplemented ,
http .StatusTooManyRequests : codes .Unavailable ,
http .StatusBadGateway : codes .Unavailable ,
http .StatusServiceUnavailable : codes .Unavailable ,
http .StatusGatewayTimeout : codes .Unavailable ,
}
)
var grpcStatusDetailsBinHeader = "grpc-status-details-bin"
func isReservedHeader(hdr string ) bool {
if hdr != "" && hdr [0 ] == ':' {
return true
}
switch hdr {
case "content-type" ,
"user-agent" ,
"grpc-message-type" ,
"grpc-encoding" ,
"grpc-message" ,
"grpc-status" ,
"grpc-timeout" ,
"te" :
return true
default :
return false
}
}
func isWhitelistedHeader(hdr string ) bool {
switch hdr {
case ":authority" , "user-agent" :
return true
default :
return false
}
}
const binHdrSuffix = "-bin"
func encodeBinHeader(v []byte ) string {
return base64 .RawStdEncoding .EncodeToString (v )
}
func decodeBinHeader(v string ) ([]byte , error ) {
if len (v )%4 == 0 {
return base64 .StdEncoding .DecodeString (v )
}
return base64 .RawStdEncoding .DecodeString (v )
}
func encodeMetadataHeader(k , v string ) string {
if strings .HasSuffix (k , binHdrSuffix ) {
return encodeBinHeader (([]byte )(v ))
}
return v
}
func decodeMetadataHeader(k , v string ) (string , error ) {
if strings .HasSuffix (k , binHdrSuffix ) {
b , err := decodeBinHeader (v )
return string (b ), err
}
return v , nil
}
type timeoutUnit uint8
const (
hour timeoutUnit = 'H'
minute timeoutUnit = 'M'
second timeoutUnit = 'S'
millisecond timeoutUnit = 'm'
microsecond timeoutUnit = 'u'
nanosecond timeoutUnit = 'n'
)
func timeoutUnitToDuration(u timeoutUnit ) (d time .Duration , ok bool ) {
switch u {
case hour :
return time .Hour , true
case minute :
return time .Minute , true
case second :
return time .Second , true
case millisecond :
return time .Millisecond , true
case microsecond :
return time .Microsecond , true
case nanosecond :
return time .Nanosecond , true
default :
}
return
}
func decodeTimeout(s string ) (time .Duration , error ) {
size := len (s )
if size < 2 {
return 0 , fmt .Errorf ("transport: timeout string is too short: %q" , s )
}
if size > 9 {
return 0 , fmt .Errorf ("transport: timeout string is too long: %q" , s )
}
unit := timeoutUnit (s [size -1 ])
d , ok := timeoutUnitToDuration (unit )
if !ok {
return 0 , fmt .Errorf ("transport: timeout unit is not recognized: %q" , s )
}
t , err := strconv .ParseUint (s [:size -1 ], 10 , 64 )
if err != nil {
return 0 , err
}
const maxHours = math .MaxInt64 / uint64 (time .Hour )
if d == time .Hour && t > maxHours {
return time .Duration (math .MaxInt64 ), nil
}
return d * time .Duration (t ), nil
}
const (
spaceByte = ' '
tildeByte = '~'
percentByte = '%'
)
func encodeGrpcMessage(msg string ) string {
if msg == "" {
return ""
}
lenMsg := len (msg )
for i := 0 ; i < lenMsg ; i ++ {
c := msg [i ]
if !(c >= spaceByte && c <= tildeByte && c != percentByte ) {
return encodeGrpcMessageUnchecked (msg )
}
}
return msg
}
func encodeGrpcMessageUnchecked(msg string ) string {
var sb strings .Builder
for len (msg ) > 0 {
r , size := utf8 .DecodeRuneInString (msg )
for _ , b := range []byte (string (r )) {
if size > 1 {
fmt .Fprintf (&sb , "%%%02X" , b )
continue
}
if b >= spaceByte && b <= tildeByte && b != percentByte {
sb .WriteByte (b )
} else {
fmt .Fprintf (&sb , "%%%02X" , b )
}
}
msg = msg [size :]
}
return sb .String ()
}
func decodeGrpcMessage(msg string ) string {
if msg == "" {
return ""
}
lenMsg := len (msg )
for i := 0 ; i < lenMsg ; i ++ {
if msg [i ] == percentByte && i +2 < lenMsg {
return decodeGrpcMessageUnchecked (msg )
}
}
return msg
}
func decodeGrpcMessageUnchecked(msg string ) string {
var sb strings .Builder
lenMsg := len (msg )
for i := 0 ; i < lenMsg ; i ++ {
c := msg [i ]
if c == percentByte && i +2 < lenMsg {
parsed , err := strconv .ParseUint (msg [i +1 :i +3 ], 16 , 8 )
if err != nil {
sb .WriteByte (c )
} else {
sb .WriteByte (byte (parsed ))
i += 2
}
} else {
sb .WriteByte (c )
}
}
return sb .String ()
}
type bufWriter struct {
pool *sync .Pool
buf []byte
offset int
batchSize int
conn net .Conn
err error
}
func newBufWriter(conn net .Conn , batchSize int , pool *sync .Pool ) *bufWriter {
w := &bufWriter {
batchSize : batchSize ,
conn : conn ,
pool : pool ,
}
if pool == nil {
w .buf = make ([]byte , batchSize )
}
return w
}
func (w *bufWriter ) Write (b []byte ) (int , error ) {
if w .err != nil {
return 0 , w .err
}
if w .batchSize == 0 {
n , err := w .conn .Write (b )
return n , toIOError (err )
}
if w .buf == nil {
b := w .pool .Get ().(*[]byte )
w .buf = *b
}
written := 0
for len (b ) > 0 {
copied := copy (w .buf [w .offset :], b )
b = b [copied :]
written += copied
w .offset += copied
if w .offset < w .batchSize {
continue
}
if err := w .flushKeepBuffer (); err != nil {
return written , err
}
}
return written , nil
}
func (w *bufWriter ) Flush () error {
err := w .flushKeepBuffer ()
if w .buf != nil && w .pool != nil {
b := w .buf
w .pool .Put (&b )
w .buf = nil
}
return err
}
func (w *bufWriter ) flushKeepBuffer () error {
if w .err != nil {
return w .err
}
if w .offset == 0 {
return nil
}
_, w .err = w .conn .Write (w .buf [:w .offset ])
w .err = toIOError (w .err )
w .offset = 0
return w .err
}
type ioError struct {
error
}
func (i ioError ) Unwrap () error {
return i .error
}
func isIOError(err error ) bool {
return errors .As (err , &ioError {})
}
func toIOError(err error ) error {
if err == nil {
return nil
}
return ioError {error : err }
}
type framer struct {
writer *bufWriter
fr *http2 .Framer
}
var writeBufferPoolMap = make (map [int ]*sync .Pool )
var writeBufferMutex sync .Mutex
func newFramer(conn net .Conn , writeBufferSize , readBufferSize int , sharedWriteBuffer bool , maxHeaderListSize uint32 ) *framer {
if writeBufferSize < 0 {
writeBufferSize = 0
}
var r io .Reader = conn
if readBufferSize > 0 {
r = bufio .NewReaderSize (r , readBufferSize )
}
var pool *sync .Pool
if sharedWriteBuffer {
pool = getWriteBufferPool (writeBufferSize )
}
w := newBufWriter (conn , writeBufferSize , pool )
f := &framer {
writer : w ,
fr : http2 .NewFramer (w , r ),
}
f .fr .SetMaxReadFrameSize (http2MaxFrameLen )
f .fr .SetReuseFrames ()
f .fr .MaxHeaderListSize = maxHeaderListSize
f .fr .ReadMetaHeaders = hpack .NewDecoder (http2InitHeaderTableSize , nil )
return f
}
func getWriteBufferPool(size int ) *sync .Pool {
writeBufferMutex .Lock ()
defer writeBufferMutex .Unlock ()
pool , ok := writeBufferPoolMap [size ]
if ok {
return pool
}
pool = &sync .Pool {
New : func () any {
b := make ([]byte , size )
return &b
},
}
writeBufferPoolMap [size ] = pool
return pool
}
func ParseDialTarget (target string ) (string , string ) {
net := "tcp"
m1 := strings .Index (target , ":" )
m2 := strings .Index (target , ":/" )
if m1 >= 0 && m2 < 0 {
if n := target [0 :m1 ]; n == "unix" {
return n , target [m1 +1 :]
}
}
if m2 >= 0 {
t , err := url .Parse (target )
if err != nil {
return net , target
}
scheme := t .Scheme
addr := t .Path
if scheme == "unix" {
if addr == "" {
addr = t .Host
}
return scheme , addr
}
}
return net , target
}
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 .