Skip to content

Commit 0b82c6b

Browse files
committed
Use native net/http/fcgi package to serve FastCGI requests
This package didn't exist when I first wrote fcgi.go, and it doesn't make sense to have a separate implementation.
1 parent 4b4df91 commit 0b82c6b

File tree

2 files changed

+3
-489
lines changed

2 files changed

+3
-489
lines changed

fcgi.go

Lines changed: 3 additions & 294 deletions
Original file line numberDiff line numberDiff line change
@@ -1,299 +1,16 @@
11
package web
22

33
import (
4-
"bufio"
5-
"bytes"
6-
"encoding/binary"
7-
"errors"
8-
"fmt"
9-
"io"
10-
"io/ioutil"
114
"net"
12-
"net/http"
13-
"net/http/cgi"
14-
"strings"
5+
"net/http/fcgi"
156
)
167

17-
const (
18-
fcgiBeginRequest = iota + 1
19-
fcgiAbortRequest
20-
fcgiEndRequest
21-
fcgiParams
22-
fcgiStdin
23-
fcgiStdout
24-
fcgiStderr
25-
fcgiData
26-
fcgiGetValues
27-
fcgiGetValuesResult
28-
fcgiUnknownType
29-
fcgiMaxType = fcgiUnknownType
30-
)
31-
32-
const (
33-
fcgiRequestComplete = iota
34-
fcgiCantMpxConn
35-
fcgiOverloaded
36-
fcgiUnknownRole
37-
)
38-
39-
type fcgiHeader struct {
40-
Version uint8
41-
Type uint8
42-
RequestId uint16
43-
ContentLength uint16
44-
PaddingLength uint8
45-
Reserved uint8
46-
}
47-
48-
func (h fcgiHeader) bytes() []byte {
49-
order := binary.BigEndian
50-
buf := make([]byte, 8)
51-
buf[0] = h.Version
52-
buf[1] = h.Type
53-
order.PutUint16(buf[2:4], h.RequestId)
54-
order.PutUint16(buf[4:6], h.ContentLength)
55-
buf[6] = h.PaddingLength
56-
buf[7] = h.Reserved
57-
return buf
58-
}
59-
60-
func newFcgiRecord(typ int, requestId int, data []byte) []byte {
61-
var record bytes.Buffer
62-
l := len(data)
63-
// round to the nearest 8
64-
padding := make([]byte, uint8(-l&7))
65-
hdr := fcgiHeader{
66-
Version: 1,
67-
Type: uint8(typ),
68-
RequestId: uint16(requestId),
69-
ContentLength: uint16(l),
70-
PaddingLength: uint8(len(padding)),
71-
}
72-
73-
//write the header
74-
record.Write(hdr.bytes())
75-
record.Write(data)
76-
record.Write(padding)
77-
78-
return record.Bytes()
79-
}
80-
81-
type fcgiEndReq struct {
82-
appStatus uint32
83-
protocolStatus uint8
84-
reserved [3]uint8
85-
}
86-
87-
func (er fcgiEndReq) bytes() []byte {
88-
buf := make([]byte, 8)
89-
binary.BigEndian.PutUint32(buf, er.appStatus)
90-
buf[4] = er.protocolStatus
91-
return buf
92-
}
93-
94-
type fcgiConn struct {
95-
requestId uint16
96-
req *http.Request
97-
fd io.ReadWriteCloser
98-
headers http.Header
99-
wroteHeaders bool
100-
}
101-
102-
func (conn *fcgiConn) fcgiWrite(data []byte) (err error) {
103-
l := len(data)
104-
// round to the nearest 8
105-
padding := make([]byte, uint8(-l&7))
106-
hdr := fcgiHeader{
107-
Version: 1,
108-
Type: fcgiStdout,
109-
RequestId: conn.requestId,
110-
ContentLength: uint16(l),
111-
PaddingLength: uint8(len(padding)),
112-
}
113-
114-
//write the header
115-
hdrBytes := hdr.bytes()
116-
_, err = conn.fd.Write(hdrBytes)
117-
118-
if err != nil {
119-
return err
120-
}
121-
122-
_, err = conn.fd.Write(data)
123-
if err != nil {
124-
return err
125-
}
126-
127-
_, err = conn.fd.Write(padding)
128-
if err != nil {
129-
return err
130-
}
131-
132-
return err
133-
}
134-
135-
func (conn *fcgiConn) Write(data []byte) (n int, err error) {
136-
if !conn.wroteHeaders {
137-
conn.WriteHeader(200)
138-
}
139-
140-
if conn.req.Method == "HEAD" {
141-
return 0, errors.New("Body Not Allowed")
142-
}
143-
err = conn.fcgiWrite(data)
144-
if err != nil {
145-
return 0, err
146-
}
147-
148-
return len(data), nil
149-
}
150-
151-
func (conn *fcgiConn) WriteHeader(status int) {
152-
if !conn.wroteHeaders {
153-
conn.wroteHeaders = true
154-
155-
var buf bytes.Buffer
156-
text := statusText[status]
157-
fmt.Fprintf(&buf, "HTTP/1.1 %d %s\r\n", status, text)
158-
159-
for k, v := range conn.headers {
160-
for _, i := range v {
161-
buf.WriteString(k + ": " + i + "\r\n")
162-
}
163-
}
164-
buf.WriteString("\r\n")
165-
conn.fcgiWrite(buf.Bytes())
166-
}
167-
}
168-
169-
func (conn *fcgiConn) Header() http.Header {
170-
return conn.headers
171-
}
172-
173-
func (conn *fcgiConn) complete() {
174-
content := fcgiEndReq{appStatus: 200, protocolStatus: fcgiRequestComplete}.bytes()
175-
l := len(content)
176-
177-
hdr := fcgiHeader{
178-
Version: 1,
179-
Type: fcgiEndRequest,
180-
RequestId: uint16(conn.requestId),
181-
ContentLength: uint16(l),
182-
PaddingLength: 0,
183-
}
184-
185-
conn.fd.Write(hdr.bytes())
186-
conn.fd.Write(content)
187-
conn.fd.Close()
188-
}
189-
190-
func (conn *fcgiConn) Close() {}
191-
192-
func readFcgiParamSize(data []byte, index int) (int, int) {
193-
194-
var size int
195-
var shift = 0
196-
197-
if data[index]>>7 == 0 {
198-
size = int(data[index])
199-
shift = 1
200-
} else {
201-
var s uint32
202-
binary.Read(bytes.NewBuffer(data[index:index+4]), binary.BigEndian, &s)
203-
s ^= 1 << 31
204-
size = int(s)
205-
shift = 4
206-
}
207-
return size, shift
208-
209-
}
210-
211-
//read the fcgi parameters contained in data, and store them in storage
212-
func readFcgiParams(data []byte, storage map[string]string) {
213-
for idx := 0; len(data) > idx; {
214-
keySize, shift := readFcgiParamSize(data, idx)
215-
idx += shift
216-
valSize, shift := readFcgiParamSize(data, idx)
217-
idx += shift
218-
key := data[idx : idx+keySize]
219-
idx += keySize
220-
val := data[idx : idx+valSize]
221-
idx += valSize
222-
storage[string(key)] = string(val)
223-
}
224-
}
225-
226-
func (s *Server) handleFcgiConnection(fd io.ReadWriteCloser) {
227-
br := bufio.NewReader(fd)
228-
var req *http.Request
229-
var fc *fcgiConn
230-
var body bytes.Buffer
231-
headers := map[string]string{}
232-
233-
for {
234-
var h fcgiHeader
235-
err := binary.Read(br, binary.BigEndian, &h)
236-
if err == io.EOF {
237-
break
238-
}
239-
if err != nil {
240-
s.Logger.Println("FCGI Error", err.Error())
241-
break
242-
}
243-
content := make([]byte, h.ContentLength)
244-
_, err = io.ReadFull(br, content)
245-
if err != nil {
246-
s.Logger.Println("FCGI Error", err.Error())
247-
break
248-
}
249-
250-
//read padding
251-
if h.PaddingLength > 0 {
252-
padding := make([]byte, h.PaddingLength)
253-
_, err = io.ReadFull(br, padding)
254-
if err != nil {
255-
s.Logger.Println("FCGI Error", err.Error())
256-
break
257-
}
258-
}
259-
260-
switch h.Type {
261-
case fcgiBeginRequest:
262-
fc = &fcgiConn{h.RequestId, req, fd, make(map[string][]string), false}
263-
264-
case fcgiParams:
265-
if h.ContentLength > 0 {
266-
readFcgiParams(content, headers)
267-
}
268-
case fcgiStdin:
269-
if h.ContentLength > 0 {
270-
body.Write(content)
271-
} else if h.ContentLength == 0 {
272-
273-
req, _ = cgi.RequestFromMap(headers)
274-
req.Body = ioutil.NopCloser(&body)
275-
fc.req = req
276-
s.routeHandler(req, fc)
277-
//we close the connection after processing
278-
//TODO: is there a way to keep it open for future requests?
279-
fc.complete()
280-
return
281-
}
282-
case fcgiData:
283-
if h.ContentLength > 0 {
284-
body.Write(content)
285-
}
286-
case fcgiAbortRequest:
287-
}
288-
}
289-
}
290-
2918
func (s *Server) listenAndServeFcgi(addr string) error {
2929
var l net.Listener
29310
var err error
29411

29512
//if the path begins with a "/", assume it's a unix address
296-
if strings.HasPrefix(addr, "/") {
13+
if addr[0] == '/' {
29714
l, err = net.Listen("unix", addr)
29815
} else {
29916
l, err = net.Listen("tcp", addr)
@@ -306,13 +23,5 @@ func (s *Server) listenAndServeFcgi(addr string) error {
30623
s.Logger.Println("FCGI listen error", err.Error())
30724
return err
30825
}
309-
for {
310-
fd, err := l.Accept()
311-
if err != nil {
312-
s.Logger.Println("FCGI accept error", err.Error())
313-
break
314-
}
315-
go s.handleFcgiConnection(fd)
316-
}
317-
return nil
26+
return fcgi.Serve(s.l, s)
31827
}

0 commit comments

Comments
 (0)