comparison fcgi/fcgi.go @ 3:eb705d4cdcd7

fix fcgi
author Atarwn Gard <a@qwa.su>
date Mon, 09 Mar 2026 02:16:06 +0500
parents 48bdab3eec8a
children
comparison
equal deleted inserted replaced
2:d19133be91ba 3:eb705d4cdcd7
1 // Package fcgi implements a minimal FastCGI client. 1 // Copyright 2012 Junqing Tan <ivan@mysqlab.net> and The Go Authors
2 // 2 // Use of this source code is governed by a BSD-style
3 // Supports sending a single request over a pre-dialed net.Conn and streaming 3 // Part of source code is from Go fcgi package
4 // the CGI response back to an http.ResponseWriter. 4
5 package fcgi 5 package fcgi
6 6
7 import ( 7 import (
8 "bufio" 8 "bufio"
9 "bytes"
9 "encoding/binary" 10 "encoding/binary"
11 "errors"
12 "fmt"
10 "io" 13 "io"
14 "io/ioutil"
15 "mime/multipart"
16 "net"
11 "net/http" 17 "net/http"
18 "net/http/httputil"
12 "net/textproto" 19 "net/textproto"
20 "net/url"
21 "os"
22 "path/filepath"
23 "strconv"
13 "strings" 24 "strings"
25 "sync"
26 "time"
14 ) 27 )
15 28
16 // Reference: https://fastcgi-archives.github.io/FastCGI_Specification.html 29 const FCGI_LISTENSOCK_FILENO uint8 = 0
30 const FCGI_HEADER_LEN uint8 = 8
31 const VERSION_1 uint8 = 1
32 const FCGI_NULL_REQUEST_ID uint8 = 0
33 const FCGI_KEEP_CONN uint8 = 1
34 const doubleCRLF = "\r\n\r\n"
17 35
18 const ( 36 const (
19 fcgiVersion = 1 37 FCGI_BEGIN_REQUEST uint8 = iota + 1
20 fcgiBeginRequest = 1 38 FCGI_ABORT_REQUEST
21 fcgiParams = 4 39 FCGI_END_REQUEST
22 fcgiStdin = 5 40 FCGI_PARAMS
23 fcgiStdout = 6 41 FCGI_STDIN
24 fcgiEndRequest = 3 42 FCGI_STDOUT
25 fcgiRoleResponder = 1 43 FCGI_STDERR
26 fcgiRequestID = 1 44 FCGI_DATA
45 FCGI_GET_VALUES
46 FCGI_GET_VALUES_RESULT
47 FCGI_UNKNOWN_TYPE
48 FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
27 ) 49 )
28 50
29 // Do sends params and the request body over conn as a FastCGI request, then 51 const (
30 // parses the response and writes it to w. conn is closed by the caller. 52 FCGI_RESPONDER uint8 = iota + 1
31 func Do(w http.ResponseWriter, r *http.Request, conn io.ReadWriter, params map[string]string) error { 53 FCGI_AUTHORIZER
32 bw := bufio.NewWriter(conn) 54 FCGI_FILTER
33 55 )
34 if err := writeRecord(bw, fcgiBeginRequest, fcgiRequestID, 56
35 []byte{0, fcgiRoleResponder, 0, 0, 0, 0, 0, 0}); err != nil { 57 const (
58 FCGI_REQUEST_COMPLETE uint8 = iota
59 FCGI_CANT_MPX_CONN
60 FCGI_OVERLOADED
61 FCGI_UNKNOWN_ROLE
62 )
63
64 const (
65 FCGI_MAX_CONNS string = "MAX_CONNS"
66 FCGI_MAX_REQS string = "MAX_REQS"
67 FCGI_MPXS_CONNS string = "MPXS_CONNS"
68 )
69
70 const (
71 maxWrite = 65500 // 65530 may work, but for compatibility
72 maxPad = 255
73 )
74
75 type header struct {
76 Version uint8
77 Type uint8
78 Id uint16
79 ContentLength uint16
80 PaddingLength uint8
81 Reserved uint8
82 }
83
84 // for padding so we don't have to allocate all the time
85 // not synchronized because we don't care what the contents are
86 var pad [maxPad]byte
87
88 func (h *header) init(recType uint8, reqId uint16, contentLength int) {
89 h.Version = 1
90 h.Type = recType
91 h.Id = reqId
92 h.ContentLength = uint16(contentLength)
93 h.PaddingLength = uint8(-contentLength & 7)
94 }
95
96 type record struct {
97 h header
98 rbuf []byte
99 }
100
101 func (rec *record) read(r io.Reader) (buf []byte, err error) {
102 if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil {
103 return
104 }
105 if rec.h.Version != 1 {
106 err = errors.New("fcgi: invalid header version")
107 return
108 }
109 if rec.h.Type == FCGI_END_REQUEST {
110 err = io.EOF
111 return
112 }
113 n := int(rec.h.ContentLength) + int(rec.h.PaddingLength)
114 if len(rec.rbuf) < n {
115 rec.rbuf = make([]byte, n)
116 }
117 if n, err = io.ReadFull(r, rec.rbuf[:n]); err != nil {
118 return
119 }
120 buf = rec.rbuf[:int(rec.h.ContentLength)]
121
122 return
123 }
124
125 type FCGIClient struct {
126 mutex sync.Mutex
127 rwc io.ReadWriteCloser
128 h header
129 buf bytes.Buffer
130 keepAlive bool
131 reqId uint16
132 }
133
134 // Connects to the fcgi responder at the specified network address.
135 // See func net.Dial for a description of the network and address parameters.
136 func Dial(network, address string) (fcgi *FCGIClient, err error) {
137 var conn net.Conn
138
139 conn, err = net.Dial(network, address)
140 if err != nil {
141 return
142 }
143
144 fcgi = &FCGIClient{
145 rwc: conn,
146 keepAlive: false,
147 reqId: 1,
148 }
149
150 return
151 }
152
153 // Connects to the fcgi responder at the specified network address with timeout
154 // See func net.DialTimeout for a description of the network, address and timeout parameters.
155 func DialTimeout(network, address string, timeout time.Duration) (fcgi *FCGIClient, err error) {
156
157 var conn net.Conn
158
159 conn, err = net.DialTimeout(network, address, timeout)
160 if err != nil {
161 return
162 }
163
164 fcgi = &FCGIClient{
165 rwc: conn,
166 keepAlive: false,
167 reqId: 1,
168 }
169
170 return
171 }
172
173 // Close fcgi connnection
174 func (this *FCGIClient) Close() {
175 this.rwc.Close()
176 }
177
178 func (this *FCGIClient) writeRecord(recType uint8, content []byte) (err error) {
179 this.mutex.Lock()
180 defer this.mutex.Unlock()
181 this.buf.Reset()
182 this.h.init(recType, this.reqId, len(content))
183 if err := binary.Write(&this.buf, binary.BigEndian, this.h); err != nil {
36 return err 184 return err
37 } 185 }
38 186 if _, err := this.buf.Write(content); err != nil {
39 if err := writeRecord(bw, fcgiParams, fcgiRequestID, encodeParams(params)); err != nil {
40 return err 187 return err
41 } 188 }
42 if err := writeRecord(bw, fcgiParams, fcgiRequestID, nil); err != nil { 189 if _, err := this.buf.Write(pad[:this.h.PaddingLength]); err != nil {
43 return err 190 return err
44 } 191 }
45 192 _, err = this.rwc.Write(this.buf.Bytes())
46 if r.Body != nil { 193 return err
47 buf := make([]byte, 4096) 194 }
48 for { 195
49 n, err := r.Body.Read(buf) 196 func (this *FCGIClient) writeBeginRequest(role uint16, flags uint8) error {
50 if n > 0 { 197 b := [8]byte{byte(role >> 8), byte(role), flags}
51 if werr := writeRecord(bw, fcgiStdin, fcgiRequestID, buf[:n]); werr != nil { 198 return this.writeRecord(FCGI_BEGIN_REQUEST, b[:])
52 return werr 199 }
53 } 200
201 func (this *FCGIClient) writeEndRequest(appStatus int, protocolStatus uint8) error {
202 b := make([]byte, 8)
203 binary.BigEndian.PutUint32(b, uint32(appStatus))
204 b[4] = protocolStatus
205 return this.writeRecord(FCGI_END_REQUEST, b)
206 }
207
208 func (this *FCGIClient) writePairs(recType uint8, pairs map[string]string) error {
209 w := newWriter(this, recType)
210 b := make([]byte, 8)
211 nn := 0
212 for k, v := range pairs {
213 m := 8 + len(k) + len(v)
214 if m > maxWrite {
215 // param data size exceed 65535 bytes"
216 vl := maxWrite - 8 - len(k)
217 v = v[:vl]
218 }
219 n := encodeSize(b, uint32(len(k)))
220 n += encodeSize(b[n:], uint32(len(v)))
221 m = n + len(k) + len(v)
222 if (nn + m) > maxWrite {
223 w.Flush()
224 nn = 0
225 }
226 nn += m
227 if _, err := w.Write(b[:n]); err != nil {
228 return err
229 }
230 if _, err := w.WriteString(k); err != nil {
231 return err
232 }
233 if _, err := w.WriteString(v); err != nil {
234 return err
235 }
236 }
237 w.Close()
238 return nil
239 }
240
241 func readSize(s []byte) (uint32, int) {
242 if len(s) == 0 {
243 return 0, 0
244 }
245 size, n := uint32(s[0]), 1
246 if size&(1<<7) != 0 {
247 if len(s) < 4 {
248 return 0, 0
249 }
250 n = 4
251 size = binary.BigEndian.Uint32(s)
252 size &^= 1 << 31
253 }
254 return size, n
255 }
256
257 func readString(s []byte, size uint32) string {
258 if size > uint32(len(s)) {
259 return ""
260 }
261 return string(s[:size])
262 }
263
264 func encodeSize(b []byte, size uint32) int {
265 if size > 127 {
266 size |= 1 << 31
267 binary.BigEndian.PutUint32(b, size)
268 return 4
269 }
270 b[0] = byte(size)
271 return 1
272 }
273
274 // bufWriter encapsulates bufio.Writer but also closes the underlying stream when
275 // Closed.
276 type bufWriter struct {
277 closer io.Closer
278 *bufio.Writer
279 }
280
281 func (w *bufWriter) Close() error {
282 if err := w.Writer.Flush(); err != nil {
283 w.closer.Close()
284 return err
285 }
286 return w.closer.Close()
287 }
288
289 func newWriter(c *FCGIClient, recType uint8) *bufWriter {
290 s := &streamWriter{c: c, recType: recType}
291 w := bufio.NewWriterSize(s, maxWrite)
292 return &bufWriter{s, w}
293 }
294
295 // streamWriter abstracts out the separation of a stream into discrete records.
296 // It only writes maxWrite bytes at a time.
297 type streamWriter struct {
298 c *FCGIClient
299 recType uint8
300 }
301
302 func (w *streamWriter) Write(p []byte) (int, error) {
303 nn := 0
304 for len(p) > 0 {
305 n := len(p)
306 if n > maxWrite {
307 n = maxWrite
308 }
309 if err := w.c.writeRecord(w.recType, p[:n]); err != nil {
310 return nn, err
311 }
312 nn += n
313 p = p[n:]
314 }
315 return nn, nil
316 }
317
318 func (w *streamWriter) Close() error {
319 // send empty record to close the stream
320 return w.c.writeRecord(w.recType, nil)
321 }
322
323 type streamReader struct {
324 c *FCGIClient
325 buf []byte
326 }
327
328 func (w *streamReader) Read(p []byte) (n int, err error) {
329
330 if len(p) > 0 {
331 if len(w.buf) == 0 {
332 rec := &record{}
333 w.buf, err = rec.read(w.c.rwc)
334 if err != nil {
335 return
54 } 336 }
55 if err == io.EOF { 337 }
56 break 338
339 n = len(p)
340 if n > len(w.buf) {
341 n = len(w.buf)
342 }
343 copy(p, w.buf[:n])
344 w.buf = w.buf[n:]
345 }
346
347 return
348 }
349
350 // Do made the request and returns a io.Reader that translates the data read
351 // from fcgi responder out of fcgi packet before returning it.
352 func (this *FCGIClient) Do(p map[string]string, req io.Reader) (r io.Reader, err error) {
353 err = this.writeBeginRequest(uint16(FCGI_RESPONDER), 0)
354 if err != nil {
355 return
356 }
357
358 err = this.writePairs(FCGI_PARAMS, p)
359 if err != nil {
360 return
361 }
362
363 body := newWriter(this, FCGI_STDIN)
364 if req != nil {
365 io.Copy(body, req)
366 }
367 body.Close()
368
369 r = &streamReader{c: this}
370 return
371 }
372
373 type badStringError struct {
374 what string
375 str string
376 }
377
378 func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) }
379
380 // Request returns a HTTP Response with Header and Body
381 // from fcgi responder
382 func (this *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) {
383
384 r, err := this.Do(p, req)
385 if err != nil {
386 return
387 }
388
389 rb := bufio.NewReader(r)
390 tp := textproto.NewReader(rb)
391 resp = new(http.Response)
392 // Parse the first line of the response.
393 line, err := tp.ReadLine()
394 if err != nil {
395 if err == io.EOF {
396 err = io.ErrUnexpectedEOF
397 }
398 return nil, err
399 }
400 if i := strings.IndexByte(line, ' '); i == -1 {
401 err = &badStringError{"malformed HTTP response", line}
402 } else {
403 resp.Proto = line[:i]
404 resp.Status = strings.TrimLeft(line[i+1:], " ")
405 }
406 statusCode := resp.Status
407 if i := strings.IndexByte(resp.Status, ' '); i != -1 {
408 statusCode = resp.Status[:i]
409 }
410 if len(statusCode) != 3 {
411 err = &badStringError{"malformed HTTP status code", statusCode}
412 }
413 resp.StatusCode, err = strconv.Atoi(statusCode)
414 if err != nil || resp.StatusCode < 0 {
415 err = &badStringError{"malformed HTTP status code", statusCode}
416 }
417 var ok bool
418 if resp.ProtoMajor, resp.ProtoMinor, ok = http.ParseHTTPVersion(resp.Proto); !ok {
419 err = &badStringError{"malformed HTTP version", resp.Proto}
420 }
421 // Parse the response headers.
422 mimeHeader, err := tp.ReadMIMEHeader()
423 if err != nil {
424 if err == io.EOF {
425 err = io.ErrUnexpectedEOF
426 }
427 return nil, err
428 }
429 resp.Header = http.Header(mimeHeader)
430 // TODO: fixTransferEncoding ?
431 resp.TransferEncoding = resp.Header["Transfer-Encoding"]
432 resp.ContentLength, _ = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
433
434 if chunked(resp.TransferEncoding) {
435 resp.Body = ioutil.NopCloser(httputil.NewChunkedReader(rb))
436 } else {
437 resp.Body = ioutil.NopCloser(rb)
438 }
439 return
440 }
441
442 // Get issues a GET request to the fcgi responder.
443 func (this *FCGIClient) Get(p map[string]string) (resp *http.Response, err error) {
444
445 p["REQUEST_METHOD"] = "GET"
446 p["CONTENT_LENGTH"] = "0"
447
448 return this.Request(p, nil)
449 }
450
451 // Get issues a Post request to the fcgi responder. with request body
452 // in the format that bodyType specified
453 func (this *FCGIClient) Post(p map[string]string, bodyType string, body io.Reader, l int) (resp *http.Response, err error) {
454
455 if len(p["REQUEST_METHOD"]) == 0 || p["REQUEST_METHOD"] == "GET" {
456 p["REQUEST_METHOD"] = "POST"
457 }
458 p["CONTENT_LENGTH"] = strconv.Itoa(l)
459 if len(bodyType) > 0 {
460 p["CONTENT_TYPE"] = bodyType
461 } else {
462 p["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
463 }
464
465 return this.Request(p, body)
466 }
467
468 // PostForm issues a POST to the fcgi responder, with form
469 // as a string key to a list values (url.Values)
470 func (this *FCGIClient) PostForm(p map[string]string, data url.Values) (resp *http.Response, err error) {
471 body := bytes.NewReader([]byte(data.Encode()))
472 return this.Post(p, "application/x-www-form-urlencoded", body, body.Len())
473 }
474
475 // PostFile issues a POST to the fcgi responder in multipart(RFC 2046) standard,
476 // with form as a string key to a list values (url.Values),
477 // and/or with file as a string key to a list file path.
478 func (this *FCGIClient) PostFile(p map[string]string, data url.Values, file map[string]string) (resp *http.Response, err error) {
479 buf := &bytes.Buffer{}
480 writer := multipart.NewWriter(buf)
481 bodyType := writer.FormDataContentType()
482
483 for key, val := range data {
484 for _, v0 := range val {
485 err = writer.WriteField(key, v0)
486 if err != nil {
487 return
57 } 488 }
58 if err != nil { 489 }
59 return err 490 }
60 } 491
61 } 492 for key, val := range file {
62 } 493 fd, e := os.Open(val)
63 if err := writeRecord(bw, fcgiStdin, fcgiRequestID, nil); err != nil { 494 if e != nil {
64 return err 495 return nil, e
65 } 496 }
66 if err := bw.Flush(); err != nil { 497 defer fd.Close()
67 return err 498
68 } 499 part, e := writer.CreateFormFile(key, filepath.Base(val))
69 500 if e != nil {
70 // Read response 501 return nil, e
71 var stdout strings.Builder 502 }
72 br := bufio.NewReader(conn) 503 _, err = io.Copy(part, fd)
73 504 }
74 for { 505
75 hdr := make([]byte, 8) 506 err = writer.Close()
76 if _, err := io.ReadFull(br, hdr); err != nil { 507 if err != nil {
77 return err 508 return
78 } 509 }
79 recType := hdr[1] 510
80 contentLen := int(binary.BigEndian.Uint16(hdr[4:6])) 511 return this.Post(p, bodyType, buf, buf.Len())
81 paddingLen := int(hdr[6]) 512 }
82 513
83 content := make([]byte, contentLen) 514 // Checks whether chunked is part of the encodings stack
84 if _, err := io.ReadFull(br, content); err != nil { 515 func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }
85 return err
86 }
87 if paddingLen > 0 {
88 if _, err := io.ReadFull(br, make([]byte, paddingLen)); err != nil {
89 return err
90 }
91 }
92
93 switch recType {
94 case fcgiStdout:
95 stdout.Write(content)
96 case fcgiEndRequest:
97 return forwardResponse(w, stdout.String())
98 }
99 // fcgiStderr is silently discarded
100 }
101 }
102
103 func forwardResponse(w http.ResponseWriter, raw string) error {
104 sep := "\r\n\r\n"
105 idx := strings.Index(raw, sep)
106 advance := 4
107 if idx == -1 {
108 sep = "\n\n"
109 idx = strings.Index(raw, sep)
110 advance = 2
111 }
112 if idx == -1 {
113 w.Write([]byte(raw))
114 return nil
115 }
116
117 headerPart := raw[:idx]
118 body := raw[idx+advance:]
119
120 tp := textproto.NewReader(bufio.NewReader(strings.NewReader(headerPart + "\r\n\r\n")))
121 mime, _ := tp.ReadMIMEHeader()
122
123 status := 200
124 if s := mime.Get("Status"); s != "" {
125 code, _, _ := strings.Cut(s, " ")
126 if n := parseIntFast(code); n > 0 {
127 status = n
128 }
129 mime.Del("Status")
130 }
131
132 for k, vs := range mime {
133 for _, v := range vs {
134 w.Header().Add(k, v)
135 }
136 }
137 w.WriteHeader(status)
138 w.Write([]byte(body))
139 return nil
140 }
141
142 func writeRecord(w io.Writer, recType uint8, reqID uint16, content []byte) error {
143 length := len(content)
144 padding := (8 - (length % 8)) % 8
145 hdr := []byte{
146 fcgiVersion, recType,
147 byte(reqID >> 8), byte(reqID),
148 byte(length >> 8), byte(length),
149 byte(padding), 0,
150 }
151 if _, err := w.Write(hdr); err != nil {
152 return err
153 }
154 if len(content) > 0 {
155 if _, err := w.Write(content); err != nil {
156 return err
157 }
158 }
159 if padding > 0 {
160 if _, err := w.Write(make([]byte, padding)); err != nil {
161 return err
162 }
163 }
164 return nil
165 }
166
167 func encodeParams(params map[string]string) []byte {
168 var buf []byte
169 for k, v := range params {
170 buf = appendLen(buf, len(k))
171 buf = appendLen(buf, len(v))
172 buf = append(buf, k...)
173 buf = append(buf, v...)
174 }
175 return buf
176 }
177
178 func appendLen(buf []byte, n int) []byte {
179 if n <= 127 {
180 return append(buf, byte(n))
181 }
182 return append(buf, byte((n>>24)|0x80), byte(n>>16), byte(n>>8), byte(n))
183 }
184
185 func parseIntFast(s string) int {
186 n := 0
187 for i := 0; i < len(s); i++ {
188 c := s[i]
189 if c < '0' || c > '9' {
190 return 0
191 }
192 n = n*10 + int(c-'0')
193 }
194 return n
195 }