1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
7 // This file implements FastCGI from the perspective of a child process.
23 // request holds the state for an in-progress request. As soon as it's complete,
24 // it's converted to an http.Request.
28 params
map[string]string
34 func newRequest(reqId
uint16, flags
uint8) *request
{
37 params
: map[string]string{},
38 keepConn
: flags
&flagKeepConn
!= 0,
40 r
.rawParams
= r
.buf
[:0]
44 // parseParams reads an encoded []byte into Params.
45 func (r
*request
) parseParams() {
49 keyLen
, n
:= readSize(text
)
54 valLen
, n
:= readSize(text
)
59 key
:= readString(text
, keyLen
)
61 val
:= readString(text
, valLen
)
67 // response implements http.ResponseWriter.
68 type response
struct {
75 func newResponse(c
*child
, req
*request
) *response
{
78 header
: http
.Header
{},
79 w
: newWriter(c
.conn
, typeStdout
, req
.reqId
),
83 func (r
*response
) Header() http
.Header
{
87 func (r
*response
) Write(data
[]byte) (int, error
) {
89 r
.WriteHeader(http
.StatusOK
)
91 return r
.w
.Write(data
)
94 func (r
*response
) WriteHeader(code
int) {
99 if code
== http
.StatusNotModified
{
100 // Must not have body.
101 r
.header
.Del("Content-Type")
102 r
.header
.Del("Content-Length")
103 r
.header
.Del("Transfer-Encoding")
104 } else if r
.header
.Get("Content-Type") == "" {
105 r
.header
.Set("Content-Type", "text/html; charset=utf-8")
108 if r
.header
.Get("Date") == "" {
109 r
.header
.Set("Date", time
.Now().UTC().Format(http
.TimeFormat
))
112 fmt
.Fprintf(r
.w
, "Status: %d %s\r\n", code
, http
.StatusText(code
))
114 r
.w
.WriteString("\r\n")
117 func (r
*response
) Flush() {
119 r
.WriteHeader(http
.StatusOK
)
124 func (r
*response
) Close() error
{
133 mu sync
.Mutex
// protects requests:
134 requests
map[uint16]*request
// keyed by request ID
137 func newChild(rwc io
.ReadWriteCloser
, handler http
.Handler
) *child
{
141 requests
: make(map[uint16]*request
),
145 func (c
*child
) serve() {
149 if err
:= rec
.read(c
.conn
.rwc
); err
!= nil {
152 if err
:= c
.handleRecord(&rec
); err
!= nil {
158 var errCloseConn
= errors
.New("fcgi: connection should be closed")
160 var emptyBody
= ioutil
.NopCloser(strings
.NewReader(""))
162 func (c
*child
) handleRecord(rec
*record
) error
{
164 req
, ok
:= c
.requests
[rec
.h
.Id
]
166 if !ok
&& rec
.h
.Type
!= typeBeginRequest
&& rec
.h
.Type
!= typeGetValues
{
167 // The spec says to ignore unknown request IDs.
172 case typeBeginRequest
:
174 // The server is trying to begin a request with the same ID
175 // as an in-progress request. This is an error.
176 return errors
.New("fcgi: received ID that is already in-flight")
180 if err
:= br
.read(rec
.content()); err
!= nil {
183 if br
.role
!= roleResponder
{
184 c
.conn
.writeEndRequest(rec
.h
.Id
, 0, statusUnknownRole
)
187 req
= newRequest(rec
.h
.Id
, br
.flags
)
189 c
.requests
[rec
.h
.Id
] = req
193 // NOTE(eds): Technically a key-value pair can straddle the boundary
194 // between two packets. We buffer until we've received all parameters.
195 if len(rec
.content()) > 0 {
196 req
.rawParams
= append(req
.rawParams
, rec
.content()...)
202 content
:= rec
.content()
204 var body io
.ReadCloser
205 if len(content
) > 0 {
206 // body could be an io.LimitReader, but it shouldn't matter
207 // as long as both sides are behaving.
208 body
, req
.pw
= io
.Pipe()
212 go c
.serveRequest(req
, body
)
214 if len(content
) > 0 {
215 // TODO(eds): This blocks until the handler reads from the pipe.
216 // If the handler takes a long time, it might be a problem.
217 req
.pw
.Write(content
)
218 } else if req
.pw
!= nil {
223 values
:= map[string]string{"FCGI_MPXS_CONNS": "1"}
224 c
.conn
.writePairs(typeGetValuesResult
, 0, values
)
227 // If the filter role is implemented, read the data stream here.
229 case typeAbortRequest
:
232 delete(c
.requests
, rec
.h
.Id
)
234 c
.conn
.writeEndRequest(rec
.h
.Id
, 0, statusRequestComplete
)
236 // connection will close upon return
242 b
[0] = byte(rec
.h
.Type
)
243 c
.conn
.writeRecord(typeUnknownType
, 0, b
)
248 func (c
*child
) serveRequest(req
*request
, body io
.ReadCloser
) {
249 r
:= newResponse(c
, req
)
250 httpReq
, err
:= cgi
.RequestFromMap(req
.params
)
252 // there was an error reading the request
253 r
.WriteHeader(http
.StatusInternalServerError
)
254 c
.conn
.writeRecord(typeStderr
, req
.reqId
, []byte(err
.Error()))
257 c
.handler
.ServeHTTP(r
, httpReq
)
261 delete(c
.requests
, req
.reqId
)
263 c
.conn
.writeEndRequest(req
.reqId
, 0, statusRequestComplete
)
265 // Consume the entire body, so the host isn't still writing to
266 // us when we close the socket below in the !keepConn case,
267 // otherwise we'd send a RST. (golang.org/issue/4183)
268 // TODO(bradfitz): also bound this copy in time. Or send
269 // some sort of abort request to the host, so the host
270 // can properly cut off the client sending all the data.
271 // For now just bound it a little and
272 io
.CopyN(ioutil
.Discard
, body
, 100<<20)
280 // Serve accepts incoming FastCGI connections on the listener l, creating a new
281 // goroutine for each. The goroutine reads requests and then calls handler
283 // If l is nil, Serve accepts connections from os.Stdin.
284 // If handler is nil, http.DefaultServeMux is used.
285 func Serve(l net
.Listener
, handler http
.Handler
) error
{
288 l
, err
= net
.FileListener(os
.Stdin
)
295 handler
= http
.DefaultServeMux
298 rw
, err
:= l
.Accept()
302 c
:= newChild(rw
, handler
)