Skip to content

Commit 3d1f1f8

Browse files
committed
add fcgiclient library.
1 parent e24f841 commit 3d1f1f8

File tree

1 file changed

+319
-0
lines changed

1 file changed

+319
-0
lines changed

php/fcgiclient.go

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
// Copyright 2012 Junqing Tan <ivan@mysqlab.net> and The Go Authors
2+
// Use of this source code is governed by a BSD-style
3+
// Part of source code is from Go fcgi package
4+
5+
// Fix bug: Can't recive more than 1 record untill FCGI_END_REQUEST 2012-09-15
6+
// By: wofeiwo
7+
8+
package fcgiclient
9+
10+
import (
11+
"bufio"
12+
"bytes"
13+
"encoding/binary"
14+
"errors"
15+
"io"
16+
"net"
17+
"strconv"
18+
"sync"
19+
)
20+
21+
const FCGI_LISTENSOCK_FILENO uint8 = 0
22+
const FCGI_HEADER_LEN uint8 = 8
23+
const VERSION_1 uint8 = 1
24+
const FCGI_NULL_REQUEST_ID uint8 = 0
25+
const FCGI_KEEP_CONN uint8 = 1
26+
27+
const (
28+
FCGI_BEGIN_REQUEST uint8 = iota + 1
29+
FCGI_ABORT_REQUEST
30+
FCGI_END_REQUEST
31+
FCGI_PARAMS
32+
FCGI_STDIN
33+
FCGI_STDOUT
34+
FCGI_STDERR
35+
FCGI_DATA
36+
FCGI_GET_VALUES
37+
FCGI_GET_VALUES_RESULT
38+
FCGI_UNKNOWN_TYPE
39+
FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
40+
)
41+
42+
const (
43+
FCGI_RESPONDER uint8 = iota + 1
44+
FCGI_AUTHORIZER
45+
FCGI_FILTER
46+
)
47+
48+
const (
49+
FCGI_REQUEST_COMPLETE uint8 = iota
50+
FCGI_CANT_MPX_CONN
51+
FCGI_OVERLOADED
52+
FCGI_UNKNOWN_ROLE
53+
)
54+
55+
const (
56+
FCGI_MAX_CONNS string = "MAX_CONNS"
57+
FCGI_MAX_REQS string = "MAX_REQS"
58+
FCGI_MPXS_CONNS string = "MPXS_CONNS"
59+
)
60+
61+
const (
62+
maxWrite = 6553500 // maximum record body
63+
maxPad = 255
64+
)
65+
66+
type header struct {
67+
Version uint8
68+
Type uint8
69+
Id uint16
70+
ContentLength uint16
71+
PaddingLength uint8
72+
Reserved uint8
73+
}
74+
75+
// for padding so we don't have to allocate all the time
76+
// not synchronized because we don't care what the contents are
77+
var pad [maxPad]byte
78+
79+
func (h *header) init(recType uint8, reqId uint16, contentLength int) {
80+
h.Version = 1
81+
h.Type = recType
82+
h.Id = reqId
83+
h.ContentLength = uint16(contentLength)
84+
h.PaddingLength = uint8(-contentLength & 7)
85+
}
86+
87+
type record struct {
88+
h header
89+
buf [maxWrite + maxPad]byte
90+
}
91+
92+
func (rec *record) read(r io.Reader) (err error) {
93+
if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil {
94+
return err
95+
}
96+
if rec.h.Version != 1 {
97+
return errors.New("fcgi: invalid header version")
98+
}
99+
n := int(rec.h.ContentLength) + int(rec.h.PaddingLength)
100+
if _, err = io.ReadFull(r, rec.buf[:n]); err != nil {
101+
return err
102+
}
103+
return nil
104+
}
105+
106+
func (r *record) content() []byte {
107+
return r.buf[:r.h.ContentLength]
108+
}
109+
110+
type FCGIClient struct {
111+
mutex sync.Mutex
112+
rwc io.ReadWriteCloser
113+
h header
114+
buf bytes.Buffer
115+
keepAlive bool
116+
}
117+
118+
func New(h string, args ...interface{}) (fcgi *FCGIClient, err error) {
119+
var conn net.Conn
120+
if len(args) != 1 {
121+
err = errors.New("fcgi: not enough params")
122+
return
123+
}
124+
switch args[0].(type) {
125+
case int:
126+
addr := h + ":" + strconv.FormatInt(int64(args[0].(int)), 10)
127+
conn, err = net.Dial("tcp", addr)
128+
case string:
129+
addr := h + ":" + args[0].(string)
130+
conn, err = net.Dial("unix", addr)
131+
default:
132+
err = errors.New("fcgi: we only accept int (port) or string (socket) params.")
133+
}
134+
fcgi = &FCGIClient{
135+
rwc: conn,
136+
keepAlive: false,
137+
}
138+
return
139+
}
140+
141+
func (this *FCGIClient) writeRecord(recType uint8, reqId uint16, content []byte) (err error) {
142+
this.mutex.Lock()
143+
defer this.mutex.Unlock()
144+
this.buf.Reset()
145+
this.h.init(recType, reqId, len(content))
146+
if err := binary.Write(&this.buf, binary.BigEndian, this.h); err != nil {
147+
return err
148+
}
149+
if _, err := this.buf.Write(content); err != nil {
150+
return err
151+
}
152+
if _, err := this.buf.Write(pad[:this.h.PaddingLength]); err != nil {
153+
return err
154+
}
155+
_, err = this.rwc.Write(this.buf.Bytes())
156+
return err
157+
}
158+
159+
func (this *FCGIClient) writeBeginRequest(reqId uint16, role uint16, flags uint8) error {
160+
b := [8]byte{byte(role >> 8), byte(role), flags}
161+
return this.writeRecord(FCGI_BEGIN_REQUEST, reqId, b[:])
162+
}
163+
164+
func (this *FCGIClient) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error {
165+
b := make([]byte, 8)
166+
binary.BigEndian.PutUint32(b, uint32(appStatus))
167+
b[4] = protocolStatus
168+
return this.writeRecord(FCGI_END_REQUEST, reqId, b)
169+
}
170+
171+
func (this *FCGIClient) writePairs(recType uint8, reqId uint16, pairs map[string]string) error {
172+
w := newWriter(this, recType, reqId)
173+
b := make([]byte, 8)
174+
for k, v := range pairs {
175+
n := encodeSize(b, uint32(len(k)))
176+
n += encodeSize(b[n:], uint32(len(v)))
177+
if _, err := w.Write(b[:n]); err != nil {
178+
return err
179+
}
180+
if _, err := w.WriteString(k); err != nil {
181+
return err
182+
}
183+
if _, err := w.WriteString(v); err != nil {
184+
return err
185+
}
186+
}
187+
w.Close()
188+
return nil
189+
}
190+
191+
func readSize(s []byte) (uint32, int) {
192+
if len(s) == 0 {
193+
return 0, 0
194+
}
195+
size, n := uint32(s[0]), 1
196+
if size&(1<<7) != 0 {
197+
if len(s) < 4 {
198+
return 0, 0
199+
}
200+
n = 4
201+
size = binary.BigEndian.Uint32(s)
202+
size &^= 1 << 31
203+
}
204+
return size, n
205+
}
206+
207+
func readString(s []byte, size uint32) string {
208+
if size > uint32(len(s)) {
209+
return ""
210+
}
211+
return string(s[:size])
212+
}
213+
214+
func encodeSize(b []byte, size uint32) int {
215+
if size > 127 {
216+
size |= 1 << 31
217+
binary.BigEndian.PutUint32(b, size)
218+
return 4
219+
}
220+
b[0] = byte(size)
221+
return 1
222+
}
223+
224+
// bufWriter encapsulates bufio.Writer but also closes the underlying stream when
225+
// Closed.
226+
type bufWriter struct {
227+
closer io.Closer
228+
*bufio.Writer
229+
}
230+
231+
func (w *bufWriter) Close() error {
232+
if err := w.Writer.Flush(); err != nil {
233+
w.closer.Close()
234+
return err
235+
}
236+
return w.closer.Close()
237+
}
238+
239+
func newWriter(c *FCGIClient, recType uint8, reqId uint16) *bufWriter {
240+
s := &streamWriter{c: c, recType: recType, reqId: reqId}
241+
w := bufio.NewWriterSize(s, maxWrite)
242+
return &bufWriter{s, w}
243+
}
244+
245+
// streamWriter abstracts out the separation of a stream into discrete records.
246+
// It only writes maxWrite bytes at a time.
247+
type streamWriter struct {
248+
c *FCGIClient
249+
recType uint8
250+
reqId uint16
251+
}
252+
253+
func (w *streamWriter) Write(p []byte) (int, error) {
254+
nn := 0
255+
for len(p) > 0 {
256+
n := len(p)
257+
if n > maxWrite {
258+
n = maxWrite
259+
}
260+
if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil {
261+
return nn, err
262+
}
263+
nn += n
264+
p = p[n:]
265+
}
266+
return nn, nil
267+
}
268+
269+
func (w *streamWriter) Close() error {
270+
// send empty record to close the stream
271+
return w.c.writeRecord(w.recType, w.reqId, nil)
272+
}
273+
274+
func (this *FCGIClient) Request(env map[string]string, reqStr string) (retout []byte, reterr []byte, err error) {
275+
276+
var reqId uint16 = 1
277+
defer this.rwc.Close()
278+
279+
err = this.writeBeginRequest(reqId, uint16(FCGI_RESPONDER), 0)
280+
if err != nil {
281+
return
282+
}
283+
err = this.writePairs(FCGI_PARAMS, reqId, env)
284+
if err != nil {
285+
return
286+
}
287+
if len(reqStr) > 0 {
288+
err = this.writeRecord(FCGI_STDIN, reqId, []byte(reqStr))
289+
if err != nil {
290+
return
291+
}
292+
}
293+
294+
rec := &record{}
295+
var err1 error
296+
297+
// recive untill EOF or FCGI_END_REQUEST
298+
for {
299+
err1 = rec.read(this.rwc)
300+
if err1 != nil {
301+
if err1 != io.EOF {
302+
err = err1
303+
}
304+
break
305+
}
306+
switch {
307+
case rec.h.Type == FCGI_STDOUT:
308+
retout = append(retout, rec.content()...)
309+
case rec.h.Type == FCGI_STDERR:
310+
reterr = append(reterr, rec.content()...)
311+
case rec.h.Type == FCGI_END_REQUEST:
312+
fallthrough
313+
default:
314+
break
315+
}
316+
}
317+
318+
return
319+
}

0 commit comments

Comments
 (0)