diff --git a/caddyhttp/fastcgi/fastcgi.go b/caddyhttp/fastcgi/fastcgi.go index 30426b33..3a562891 100644 --- a/caddyhttp/fastcgi/fastcgi.go +++ b/caddyhttp/fastcgi/fastcgi.go @@ -12,10 +12,29 @@ import ( "path/filepath" "strconv" "strings" + "sync" "github.com/mholt/caddy/caddyhttp/httpserver" ) +// persistent fastcgi connections + +type serialClient struct { + // for read/write serialisation + sync.Mutex + backend *FCGIClient +} +type concurrentPersistentConnectionsMap struct { + // for thread safe acces to the map + sync.Mutex + clientMap map[string]*serialClient +} + +var persistentConnections = &(concurrentPersistentConnectionsMap{clientMap: make(map[string]*serialClient)}) + +// UsePersistentFcgiConnections TODO: add an option in Caddyfile and pass it down to here +var UsePersistentFcgiConnections = true + // Handler is a middleware type that can handle requests as a FastCGI client. type Handler struct { Next httpserver.Handler @@ -73,9 +92,25 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) // Connect to FastCGI gateway network, address := rule.parseAddress() - fcgiBackend, err := Dial(network, address) - if err != nil { - return http.StatusBadGateway, err + + var fcgiBackend *FCGIClient + var client *serialClient + reuse := false + if UsePersistentFcgiConnections { + persistentConnections.Lock() + client, reuse = persistentConnections.clientMap[network+address] + persistentConnections.Unlock() + } + + if reuse { + client.Lock() + defer client.Unlock() + fcgiBackend = client.backend + } else { + fcgiBackend, err = Dial(network, address) + if err != nil { + return http.StatusBadGateway, err + } } var resp *http.Response @@ -91,14 +126,18 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) resp, err = fcgiBackend.Post(env, r.Method, r.Header.Get("Content-Type"), r.Body, contentLength) } - if resp.Body != nil { - defer resp.Body.Close() - } - if err != nil && err != io.EOF { return http.StatusBadGateway, err } + if UsePersistentFcgiConnections { + persistentConnections.Lock() + persistentConnections.clientMap[network+address] = &(serialClient{backend: fcgiBackend}) + persistentConnections.Unlock() + } else { + defer fcgiBackend.Close() + } + // Write response header writeHeader(w, resp) @@ -112,6 +151,10 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) if fcgiBackend.stderr.Len() != 0 { // Remove trailing newline, error logger already does this. err = LogError(strings.TrimSuffix(fcgiBackend.stderr.String(), "\n")) + persistentConnections.Lock() + delete(persistentConnections.clientMap, network+address) + persistentConnections.Unlock() + fcgiBackend.Close() } // Normally we would return the status code if it is an error status (>= 400), diff --git a/caddyhttp/fastcgi/fcgiclient.go b/caddyhttp/fastcgi/fcgiclient.go index 9ee66092..667ce101 100644 --- a/caddyhttp/fastcgi/fcgiclient.go +++ b/caddyhttp/fastcgi/fcgiclient.go @@ -385,7 +385,7 @@ func (w *streamReader) Read(p []byte) (n int, err error) { // Do made the request and returns a io.Reader that translates the data read // from fcgi responder out of fcgi packet before returning it. func (c *FCGIClient) Do(p map[string]string, req io.Reader) (r io.Reader, err error) { - err = c.writeBeginRequest(uint16(Responder), 0) + err = c.writeBeginRequest(uint16(Responder), FCGIKeepConn) if err != nil { return }