|
0
|
1 // Package fcgi implements a minimal FastCGI client.
|
|
|
2 //
|
|
|
3 // Supports sending a single request over a pre-dialed net.Conn and streaming
|
|
|
4 // the CGI response back to an http.ResponseWriter.
|
|
|
5 package fcgi
|
|
|
6
|
|
|
7 import (
|
|
|
8 "bufio"
|
|
|
9 "encoding/binary"
|
|
|
10 "io"
|
|
|
11 "net/http"
|
|
|
12 "net/textproto"
|
|
|
13 "strings"
|
|
|
14 )
|
|
|
15
|
|
|
16 // Reference: https://fastcgi-archives.github.io/FastCGI_Specification.html
|
|
|
17
|
|
|
18 const (
|
|
|
19 fcgiVersion = 1
|
|
|
20 fcgiBeginRequest = 1
|
|
|
21 fcgiParams = 4
|
|
|
22 fcgiStdin = 5
|
|
|
23 fcgiStdout = 6
|
|
|
24 fcgiEndRequest = 3
|
|
|
25 fcgiRoleResponder = 1
|
|
|
26 fcgiRequestID = 1
|
|
|
27 )
|
|
|
28
|
|
|
29 // Do sends params and the request body over conn as a FastCGI request, then
|
|
|
30 // parses the response and writes it to w. conn is closed by the caller.
|
|
|
31 func Do(w http.ResponseWriter, r *http.Request, conn io.ReadWriter, params map[string]string) error {
|
|
|
32 bw := bufio.NewWriter(conn)
|
|
|
33
|
|
|
34 if err := writeRecord(bw, fcgiBeginRequest, fcgiRequestID,
|
|
|
35 []byte{0, fcgiRoleResponder, 0, 0, 0, 0, 0, 0}); err != nil {
|
|
|
36 return err
|
|
|
37 }
|
|
|
38
|
|
|
39 if err := writeRecord(bw, fcgiParams, fcgiRequestID, encodeParams(params)); err != nil {
|
|
|
40 return err
|
|
|
41 }
|
|
|
42 if err := writeRecord(bw, fcgiParams, fcgiRequestID, nil); err != nil {
|
|
|
43 return err
|
|
|
44 }
|
|
|
45
|
|
|
46 if r.Body != nil {
|
|
|
47 buf := make([]byte, 4096)
|
|
|
48 for {
|
|
|
49 n, err := r.Body.Read(buf)
|
|
|
50 if n > 0 {
|
|
|
51 if werr := writeRecord(bw, fcgiStdin, fcgiRequestID, buf[:n]); werr != nil {
|
|
|
52 return werr
|
|
|
53 }
|
|
|
54 }
|
|
|
55 if err == io.EOF {
|
|
|
56 break
|
|
|
57 }
|
|
|
58 if err != nil {
|
|
|
59 return err
|
|
|
60 }
|
|
|
61 }
|
|
|
62 }
|
|
|
63 if err := writeRecord(bw, fcgiStdin, fcgiRequestID, nil); err != nil {
|
|
|
64 return err
|
|
|
65 }
|
|
|
66 if err := bw.Flush(); err != nil {
|
|
|
67 return err
|
|
|
68 }
|
|
|
69
|
|
|
70 // Read response
|
|
|
71 var stdout strings.Builder
|
|
|
72 br := bufio.NewReader(conn)
|
|
|
73
|
|
|
74 for {
|
|
|
75 hdr := make([]byte, 8)
|
|
|
76 if _, err := io.ReadFull(br, hdr); err != nil {
|
|
|
77 return err
|
|
|
78 }
|
|
|
79 recType := hdr[1]
|
|
|
80 contentLen := int(binary.BigEndian.Uint16(hdr[4:6]))
|
|
|
81 paddingLen := int(hdr[6])
|
|
|
82
|
|
|
83 content := make([]byte, contentLen)
|
|
|
84 if _, err := io.ReadFull(br, content); err != nil {
|
|
|
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 }
|