-
Notifications
You must be signed in to change notification settings - Fork 5
/
fcgi.go
168 lines (143 loc) · 3.46 KB
/
fcgi.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package fcgi
import (
"encoding/binary"
"io"
"net"
"net/http"
"strconv"
"strings"
)
type recType uint8
const (
typeBeginRequest recType = iota + 1
typeAbortRequest
typeEndRequest
typeParams
typeStdin
typeStdout
typeStderr
typeData
typeGetValues
typeGetValuesResult
typeUnknownType
)
type header struct {
Version uint8
Type recType
ID uint16
ContentLength uint16
PaddingLength uint8
Reserved uint8
}
const (
maxWrite = 65535
maxPad = 0
fcgiVersion uint8 = 1
flagNone uint8 = 0
flagKeepConn uint8 = 1
requestID uint16 = 1
)
const (
roleResponder uint16 = iota + 1
roleAuthorizer
roleFilter
)
const (
statusRequestComplete = iota
statusCantMultiplex
statusOverloaded
statusUnknownRole
)
// Write the beginning of a request into the given connection.
func writeBeginReq(c net.Conn, w *buffer, id uint16) error {
binary.Write(w, binary.BigEndian, roleResponder) // role
binary.Write(w, binary.BigEndian, flagNone) // flags
w.Write([]byte{0, 0, 0, 0, 0}) // reserved
return w.WriteRecord(c, id, typeBeginRequest)
}
// Write an abort request into the given connection.
func writeAbortReq(c net.Conn, w *buffer, id uint16) error {
return w.WriteRecord(c, id, typeAbortRequest)
}
// Encode the length of a key or value using FCGIs compressed length
// scheme. The encoded length is placed in b and the number of bytes
// that were required to encode the length is returned.
func encodeLength(b []byte, n uint32) int {
if n > 127 {
n |= 1 << 31
binary.BigEndian.PutUint32(b, n)
return 4
}
b[0] = byte(n)
return 1
}
// Encode and write the given parameters into the connection. Note that the headers
// may be fragmented into several writes if they will not fit into a single write.
func writeParams(c net.Conn, w *buffer, id uint16, params map[string][]string) error {
var b [8]byte
for key, vals := range params {
for _, val := range vals {
// encode the key's length
n := encodeLength(b[:], uint32(len(key)))
// encode the value's length
n += encodeLength(b[n:], uint32(len(val)))
// the total lenth of this param
t := n + len(key) + len(val)
// this header itself is so big, it cannot fit into a
// write so we just discard it.
if t > w.Cap() {
continue
}
// if this param would overflow the current buffer, go ahead
// and send it.
if t > w.Free() {
if err := w.WriteRecord(c, id, typeParams); err != nil {
return err
}
}
w.Write(b[:n])
w.Write([]byte(key))
w.Write([]byte(val))
}
}
if w.Len() > 0 {
if err := w.WriteRecord(c, id, typeParams); err != nil {
return err
}
}
// send the empty params message
return w.WriteRecord(c, id, typeParams)
}
// Copy the data from the given reader into the connection as stdin. Note that
// this may fragment the data into multiple writes.
func writeStdin(c net.Conn, w *buffer, id uint16, r io.Reader) error {
if r != nil {
for {
err := w.CopyFrom(r)
if err == io.EOF {
break
} else if err != nil {
return err
}
if err := w.WriteRecord(c, id, typeStdin); err != nil {
return err
}
}
}
return w.WriteRecord(c, id, typeStdin)
}
func statusFromHeaders(h http.Header) (int, error) {
text := h.Get("Status")
if text == "" {
return http.StatusOK, nil
}
ix := strings.Index(text, " ")
if ix >= 0 {
text = text[:ix]
}
s, err := strconv.ParseInt(text, 10, 64)
if err != nil {
return 0, err
}
return int(s), nil
}