1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package fcgi implements the FastCGI protocol.
7 // See https://fast-cgi.github.io/ for an unofficial mirror of the
8 // original documentation.
10 // Currently only the responder role is supported.
13 // This file defines the raw protocol and some utilities used by the child and
25 // recType is a record type, as defined by
26 // https://web.archive.org/web/20150420080736/http://www.fastcgi.com/drupal/node/6?q=node/22#S8
30 typeBeginRequest recType
= 1
31 typeAbortRequest recType
= 2
32 typeEndRequest recType
= 3
33 typeParams recType
= 4
35 typeStdout recType
= 6
36 typeStderr recType
= 7
38 typeGetValues recType
= 9
39 typeGetValuesResult recType
= 10
40 typeUnknownType recType
= 11
43 // keep the connection between web-server and responder open after request
44 const flagKeepConn
= 1
47 maxWrite
= 65535 // maximum record body
52 roleResponder
= iota + 1 // only Responders are implemented.
58 statusRequestComplete
= iota
73 type beginRequest
struct {
79 func (br
*beginRequest
) read(content
[]byte) error
{
80 if len(content
) != 8 {
81 return errors
.New("fcgi: invalid begin request record")
83 br
.role
= binary
.BigEndian
.Uint16(content
)
88 // for padding so we don't have to allocate all the time
89 // not synchronized because we don't care what the contents are
92 func (h
*header
) init(recType recType
, reqId
uint16, contentLength
int) {
96 h
.ContentLength
= uint16(contentLength
)
97 h
.PaddingLength
= uint8(-contentLength
& 7)
100 // conn sends records over rwc
103 rwc io
.ReadWriteCloser
105 // to avoid allocations
110 func newConn(rwc io
.ReadWriteCloser
) *conn
{
111 return &conn
{rwc
: rwc
}
114 func (c
*conn
) Close() error
{
116 defer c
.mutex
.Unlock()
122 buf
[maxWrite
+ maxPad
]byte
125 func (rec
*record
) read(r io
.Reader
) (err error
) {
126 if err
= binary
.Read(r
, binary
.BigEndian
, &rec
.h
); err
!= nil {
129 if rec
.h
.Version
!= 1 {
130 return errors
.New("fcgi: invalid header version")
132 n
:= int(rec
.h
.ContentLength
) + int(rec
.h
.PaddingLength
)
133 if _
, err
= io
.ReadFull(r
, rec
.buf
[:n
]); err
!= nil {
139 func (r
*record
) content() []byte {
140 return r
.buf
[:r
.h
.ContentLength
]
143 // writeRecord writes and sends a single record.
144 func (c
*conn
) writeRecord(recType recType
, reqId
uint16, b
[]byte) error
{
146 defer c
.mutex
.Unlock()
148 c
.h
.init(recType
, reqId
, len(b
))
149 if err
:= binary
.Write(&c
.buf
, binary
.BigEndian
, c
.h
); err
!= nil {
152 if _
, err
:= c
.buf
.Write(b
); err
!= nil {
155 if _
, err
:= c
.buf
.Write(pad
[:c
.h
.PaddingLength
]); err
!= nil {
158 _
, err
:= c
.rwc
.Write(c
.buf
.Bytes())
162 func (c
*conn
) writeEndRequest(reqId
uint16, appStatus
int, protocolStatus
uint8) error
{
164 binary
.BigEndian
.PutUint32(b
, uint32(appStatus
))
165 b
[4] = protocolStatus
166 return c
.writeRecord(typeEndRequest
, reqId
, b
)
169 func (c
*conn
) writePairs(recType recType
, reqId
uint16, pairs
map[string]string) error
{
170 w
:= newWriter(c
, recType
, reqId
)
172 for k
, v
:= range pairs
{
173 n
:= encodeSize(b
, uint32(len(k
)))
174 n
+= encodeSize(b
[n
:], uint32(len(v
)))
175 if _
, err
:= w
.Write(b
[:n
]); err
!= nil {
178 if _
, err
:= w
.WriteString(k
); err
!= nil {
181 if _
, err
:= w
.WriteString(v
); err
!= nil {
189 func readSize(s
[]byte) (uint32, int) {
193 size
, n
:= uint32(s
[0]), 1
194 if size
&(1<<7) != 0 {
199 size
= binary
.BigEndian
.Uint32(s
)
205 func readString(s
[]byte, size
uint32) string {
206 if size
> uint32(len(s
)) {
209 return string(s
[:size
])
212 func encodeSize(b
[]byte, size
uint32) int {
215 binary
.BigEndian
.PutUint32(b
, size
)
222 // bufWriter encapsulates bufio.Writer but also closes the underlying stream when
224 type bufWriter
struct {
229 func (w
*bufWriter
) Close() error
{
230 if err
:= w
.Writer
.Flush(); err
!= nil {
234 return w
.closer
.Close()
237 func newWriter(c
*conn
, recType recType
, reqId
uint16) *bufWriter
{
238 s
:= &streamWriter
{c
: c
, recType
: recType
, reqId
: reqId
}
239 w
:= bufio
.NewWriterSize(s
, maxWrite
)
240 return &bufWriter
{s
, w
}
243 // streamWriter abstracts out the separation of a stream into discrete records.
244 // It only writes maxWrite bytes at a time.
245 type streamWriter
struct {
251 func (w
*streamWriter
) Write(p
[]byte) (int, error
) {
258 if err
:= w
.c
.writeRecord(w
.recType
, w
.reqId
, p
[:n
]); err
!= nil {
267 func (w
*streamWriter
) Close() error
{
268 // send empty record to close the stream
269 return w
.c
.writeRecord(w
.recType
, w
.reqId
, nil)