package fcgi
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"io"
"sync"
)
type recType uint8
const (
typeBeginRequest recType = 1
typeAbortRequest recType = 2
typeEndRequest recType = 3
typeParams recType = 4
typeStdin recType = 5
typeStdout recType = 6
typeStderr recType = 7
typeData recType = 8
typeGetValues recType = 9
typeGetValuesResult recType = 10
typeUnknownType recType = 11
)
const flagKeepConn = 1
const (
maxWrite = 65535
maxPad = 255
)
const (
roleResponder = iota + 1
roleAuthorizer
roleFilter
)
const (
statusRequestComplete = iota
statusCantMultiplex
statusOverloaded
statusUnknownRole
)
type header struct {
Version uint8
Type recType
Id uint16
ContentLength uint16
PaddingLength uint8
Reserved uint8
}
type beginRequest struct {
role uint16
flags uint8
reserved [5 ]uint8
}
func (br *beginRequest ) read (content []byte ) error {
if len (content ) != 8 {
return errors .New ("fcgi: invalid begin request record" )
}
br .role = binary .BigEndian .Uint16 (content )
br .flags = content [2 ]
return nil
}
var pad [maxPad ]byte
func (h *header ) init (recType recType , reqId uint16 , contentLength int ) {
h .Version = 1
h .Type = recType
h .Id = reqId
h .ContentLength = uint16 (contentLength )
h .PaddingLength = uint8 (-contentLength & 7 )
}
type conn struct {
mutex sync .Mutex
rwc io .ReadWriteCloser
closeErr error
closed bool
buf bytes .Buffer
h header
}
func newConn(rwc io .ReadWriteCloser ) *conn {
return &conn {rwc : rwc }
}
func (c *conn ) Close () error {
c .mutex .Lock ()
defer c .mutex .Unlock ()
if !c .closed {
c .closeErr = c .rwc .Close ()
c .closed = true
}
return c .closeErr
}
type record struct {
h header
buf [maxWrite + maxPad ]byte
}
func (rec *record ) read (r io .Reader ) (err error ) {
if err = binary .Read (r , binary .BigEndian , &rec .h ); err != nil {
return err
}
if rec .h .Version != 1 {
return errors .New ("fcgi: invalid header version" )
}
n := int (rec .h .ContentLength ) + int (rec .h .PaddingLength )
if _, err = io .ReadFull (r , rec .buf [:n ]); err != nil {
return err
}
return nil
}
func (r *record ) content () []byte {
return r .buf [:r .h .ContentLength ]
}
func (c *conn ) writeRecord (recType recType , reqId uint16 , b []byte ) error {
c .mutex .Lock ()
defer c .mutex .Unlock ()
c .buf .Reset ()
c .h .init (recType , reqId , len (b ))
if err := binary .Write (&c .buf , binary .BigEndian , c .h ); err != nil {
return err
}
if _ , err := c .buf .Write (b ); err != nil {
return err
}
if _ , err := c .buf .Write (pad [:c .h .PaddingLength ]); err != nil {
return err
}
_ , err := c .rwc .Write (c .buf .Bytes ())
return err
}
func (c *conn ) writeEndRequest (reqId uint16 , appStatus int , protocolStatus uint8 ) error {
b := make ([]byte , 8 )
binary .BigEndian .PutUint32 (b , uint32 (appStatus ))
b [4 ] = protocolStatus
return c .writeRecord (typeEndRequest , reqId , b )
}
func (c *conn ) writePairs (recType recType , reqId uint16 , pairs map [string ]string ) error {
w := newWriter (c , recType , reqId )
b := make ([]byte , 8 )
for k , v := range pairs {
n := encodeSize (b , uint32 (len (k )))
n += encodeSize (b [n :], uint32 (len (v )))
if _ , err := w .Write (b [:n ]); err != nil {
return err
}
if _ , err := w .WriteString (k ); err != nil {
return err
}
if _ , err := w .WriteString (v ); err != nil {
return err
}
}
w .Close ()
return nil
}
func readSize(s []byte ) (uint32 , int ) {
if len (s ) == 0 {
return 0 , 0
}
size , n := uint32 (s [0 ]), 1
if size &(1 <<7 ) != 0 {
if len (s ) < 4 {
return 0 , 0
}
n = 4
size = binary .BigEndian .Uint32 (s )
size &^= 1 << 31
}
return size , n
}
func readString(s []byte , size uint32 ) string {
if size > uint32 (len (s )) {
return ""
}
return string (s [:size ])
}
func encodeSize(b []byte , size uint32 ) int {
if size > 127 {
size |= 1 << 31
binary .BigEndian .PutUint32 (b , size )
return 4
}
b [0 ] = byte (size )
return 1
}
type bufWriter struct {
closer io .Closer
*bufio .Writer
}
func (w *bufWriter ) Close () error {
if err := w .Writer .Flush (); err != nil {
w .closer .Close ()
return err
}
return w .closer .Close ()
}
func newWriter(c *conn , recType recType , reqId uint16 ) *bufWriter {
s := &streamWriter {c : c , recType : recType , reqId : reqId }
w := bufio .NewWriterSize (s , maxWrite )
return &bufWriter {s , w }
}
type streamWriter struct {
c *conn
recType recType
reqId uint16
}
func (w *streamWriter ) Write (p []byte ) (int , error ) {
nn := 0
for len (p ) > 0 {
n := len (p )
if n > maxWrite {
n = maxWrite
}
if err := w .c .writeRecord (w .recType , w .reqId , p [:n ]); err != nil {
return nn , err
}
nn += n
p = p [n :]
}
return nn , nil
}
func (w *streamWriter ) Close () error {
return w .c .writeRecord (w .recType , w .reqId , nil )
}
The pages are generated with Golds v0.7.0-preview . (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 .