FastCGI implementation in OCaml
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.
4
5// Package fcgi implements the FastCGI protocol.
6//
7// See https://fast-cgi.github.io/ for an unofficial mirror of the
8// original documentation.
9//
10// Currently only the responder role is supported.
11package fcgi
12
13// This file defines the raw protocol and some utilities used by the child and
14// the host.
15
16import (
17 "bufio"
18 "bytes"
19 "encoding/binary"
20 "errors"
21 "io"
22 "sync"
23)
24
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
27type recType uint8
28
29const (
30 typeBeginRequest recType = 1
31 typeAbortRequest recType = 2
32 typeEndRequest recType = 3
33 typeParams recType = 4
34 typeStdin recType = 5
35 typeStdout recType = 6
36 typeStderr recType = 7
37 typeData recType = 8
38 typeGetValues recType = 9
39 typeGetValuesResult recType = 10
40 typeUnknownType recType = 11
41)
42
43// keep the connection between web-server and responder open after request
44const flagKeepConn = 1
45
46const (
47 maxWrite = 65535 // maximum record body
48 maxPad = 255
49)
50
51const (
52 roleResponder = iota + 1 // only Responders are implemented.
53 roleAuthorizer
54 roleFilter
55)
56
57const (
58 statusRequestComplete = iota
59 statusCantMultiplex
60 statusOverloaded
61 statusUnknownRole
62)
63
64type header struct {
65 Version uint8
66 Type recType
67 Id uint16
68 ContentLength uint16
69 PaddingLength uint8
70 Reserved uint8
71}
72
73type beginRequest struct {
74 role uint16
75 flags uint8
76 reserved [5]uint8
77}
78
79func (br *beginRequest) read(content []byte) error {
80 if len(content) != 8 {
81 return errors.New("fcgi: invalid begin request record")
82 }
83 br.role = binary.BigEndian.Uint16(content)
84 br.flags = content[2]
85 return nil
86}
87
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
90var pad [maxPad]byte
91
92func (h *header) init(recType recType, reqId uint16, contentLength int) {
93 h.Version = 1
94 h.Type = recType
95 h.Id = reqId
96 h.ContentLength = uint16(contentLength)
97 h.PaddingLength = uint8(-contentLength & 7)
98}
99
100// conn sends records over rwc
101type conn struct {
102 mutex sync.Mutex
103 rwc io.ReadWriteCloser
104 closeErr error
105 closed bool
106
107 // to avoid allocations
108 buf bytes.Buffer
109 h header
110}
111
112func newConn(rwc io.ReadWriteCloser) *conn {
113 return &conn{rwc: rwc}
114}
115
116// Close closes the conn if it is not already closed.
117func (c *conn) Close() error {
118 c.mutex.Lock()
119 defer c.mutex.Unlock()
120 if !c.closed {
121 c.closeErr = c.rwc.Close()
122 c.closed = true
123 }
124 return c.closeErr
125}
126
127type record struct {
128 h header
129 buf [maxWrite + maxPad]byte
130}
131
132func (rec *record) read(r io.Reader) (err error) {
133 if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil {
134 return err
135 }
136 if rec.h.Version != 1 {
137 return errors.New("fcgi: invalid header version")
138 }
139 n := int(rec.h.ContentLength) + int(rec.h.PaddingLength)
140 if _, err = io.ReadFull(r, rec.buf[:n]); err != nil {
141 return err
142 }
143 return nil
144}
145
146func (r *record) content() []byte {
147 return r.buf[:r.h.ContentLength]
148}
149
150// writeRecord writes and sends a single record.
151func (c *conn) writeRecord(recType recType, reqId uint16, b []byte) error {
152 c.mutex.Lock()
153 defer c.mutex.Unlock()
154 c.buf.Reset()
155 c.h.init(recType, reqId, len(b))
156 if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil {
157 return err
158 }
159 if _, err := c.buf.Write(b); err != nil {
160 return err
161 }
162 if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil {
163 return err
164 }
165 _, err := c.rwc.Write(c.buf.Bytes())
166 return err
167}
168
169func (c *conn) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error {
170 b := make([]byte, 8)
171 binary.BigEndian.PutUint32(b, uint32(appStatus))
172 b[4] = protocolStatus
173 return c.writeRecord(typeEndRequest, reqId, b)
174}
175
176func (c *conn) writePairs(recType recType, reqId uint16, pairs map[string]string) error {
177 w := newWriter(c, recType, reqId)
178 b := make([]byte, 8)
179 for k, v := range pairs {
180 n := encodeSize(b, uint32(len(k)))
181 n += encodeSize(b[n:], uint32(len(v)))
182 if _, err := w.Write(b[:n]); err != nil {
183 return err
184 }
185 if _, err := w.WriteString(k); err != nil {
186 return err
187 }
188 if _, err := w.WriteString(v); err != nil {
189 return err
190 }
191 }
192 w.Close()
193 return nil
194}
195
196func readSize(s []byte) (uint32, int) {
197 if len(s) == 0 {
198 return 0, 0
199 }
200 size, n := uint32(s[0]), 1
201 if size&(1<<7) != 0 {
202 if len(s) < 4 {
203 return 0, 0
204 }
205 n = 4
206 size = binary.BigEndian.Uint32(s)
207 size &^= 1 << 31
208 }
209 return size, n
210}
211
212func readString(s []byte, size uint32) string {
213 if size > uint32(len(s)) {
214 return ""
215 }
216 return string(s[:size])
217}
218
219func encodeSize(b []byte, size uint32) int {
220 if size > 127 {
221 size |= 1 << 31
222 binary.BigEndian.PutUint32(b, size)
223 return 4
224 }
225 b[0] = byte(size)
226 return 1
227}
228
229// bufWriter encapsulates bufio.Writer but also closes the underlying stream when
230// Closed.
231type bufWriter struct {
232 closer io.Closer
233 *bufio.Writer
234}
235
236func (w *bufWriter) Close() error {
237 if err := w.Writer.Flush(); err != nil {
238 w.closer.Close()
239 return err
240 }
241 return w.closer.Close()
242}
243
244func newWriter(c *conn, recType recType, reqId uint16) *bufWriter {
245 s := &streamWriter{c: c, recType: recType, reqId: reqId}
246 w := bufio.NewWriterSize(s, maxWrite)
247 return &bufWriter{s, w}
248}
249
250// streamWriter abstracts out the separation of a stream into discrete records.
251// It only writes maxWrite bytes at a time.
252type streamWriter struct {
253 c *conn
254 recType recType
255 reqId uint16
256}
257
258func (w *streamWriter) Write(p []byte) (int, error) {
259 nn := 0
260 for len(p) > 0 {
261 n := len(p)
262 if n > maxWrite {
263 n = maxWrite
264 }
265 if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil {
266 return nn, err
267 }
268 nn += n
269 p = p[n:]
270 }
271 return nn, nil
272}
273
274func (w *streamWriter) Close() error {
275 // send empty record to close the stream
276 return w.c.writeRecord(w.recType, w.reqId, nil)
277}