mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05:00 
			
		
		
		
	Add send_timeout property to fastcgi directive.
* Convert rwc field on FCGIClient from type io.ReadWriteCloser to net.Conn. * Return HTTP 504 to the client when a timeout occurs. * In Handler.ServeHTTP(), close the connection before returning an HTTP 502/504. * Refactor tests and add coverage.
This commit is contained in:
		
							parent
							
								
									17e7e6076a
								
							
						
					
					
						commit
						5874fbeb7e
					
				@ -19,7 +19,10 @@ type basicDialer struct {
 | 
				
			|||||||
	timeout time.Duration
 | 
						timeout time.Duration
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (b basicDialer) Dial() (Client, error) { return Dial(b.network, b.address, b.timeout) }
 | 
					func (b basicDialer) Dial() (Client, error) {
 | 
				
			||||||
 | 
						return DialTimeout(b.network, b.address, b.timeout)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (b basicDialer) Close(c Client) error { return c.Close() }
 | 
					func (b basicDialer) Close(c Client) error { return c.Close() }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// persistentDialer keeps a pool of fcgi connections.
 | 
					// persistentDialer keeps a pool of fcgi connections.
 | 
				
			||||||
@ -47,7 +50,7 @@ func (p *persistentDialer) Dial() (Client, error) {
 | 
				
			|||||||
	p.Unlock()
 | 
						p.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// no connection available, create new one
 | 
						// no connection available, create new one
 | 
				
			||||||
	return Dial(p.network, p.address, p.timeout)
 | 
						return DialTimeout(p.network, p.address, p.timeout)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (p *persistentDialer) Close(client Client) error {
 | 
					func (p *persistentDialer) Close(client Client) error {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ package fastcgi
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
@ -82,7 +83,9 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
 | 
				
			|||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return http.StatusBadGateway, err
 | 
									return http.StatusBadGateway, err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								defer fcgiBackend.Close()
 | 
				
			||||||
			fcgiBackend.SetReadTimeout(rule.ReadTimeout)
 | 
								fcgiBackend.SetReadTimeout(rule.ReadTimeout)
 | 
				
			||||||
 | 
								fcgiBackend.SetSendTimeout(rule.SendTimeout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			var resp *http.Response
 | 
								var resp *http.Response
 | 
				
			||||||
			contentLength, _ := strconv.Atoi(r.Header.Get("Content-Length"))
 | 
								contentLength, _ := strconv.Atoi(r.Header.Get("Content-Length"))
 | 
				
			||||||
@ -97,9 +100,13 @@ 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)
 | 
									resp, err = fcgiBackend.Post(env, r.Method, r.Header.Get("Content-Type"), r.Body, contentLength)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err != nil && err != io.EOF {
 | 
								if err != nil {
 | 
				
			||||||
 | 
									if err, ok := err.(net.Error); ok && err.Timeout() {
 | 
				
			||||||
 | 
										return http.StatusGatewayTimeout, err
 | 
				
			||||||
 | 
									} else if err != io.EOF {
 | 
				
			||||||
					return http.StatusBadGateway, err
 | 
										return http.StatusBadGateway, err
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Write response header
 | 
								// Write response header
 | 
				
			||||||
			writeHeader(w, resp)
 | 
								writeHeader(w, resp)
 | 
				
			||||||
@ -110,8 +117,6 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
 | 
				
			|||||||
				return http.StatusBadGateway, err
 | 
									return http.StatusBadGateway, err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			defer rule.dialer.Close(fcgiBackend)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Log any stderr output from upstream
 | 
								// Log any stderr output from upstream
 | 
				
			||||||
			if stderr := fcgiBackend.StdErr(); stderr.Len() != 0 {
 | 
								if stderr := fcgiBackend.StdErr(); stderr.Len() != 0 {
 | 
				
			||||||
				// Remove trailing newline, error logger already does this.
 | 
									// Remove trailing newline, error logger already does this.
 | 
				
			||||||
@ -306,6 +311,9 @@ type Rule struct {
 | 
				
			|||||||
	// The duration used to set a deadline when reading from the FastCGI server.
 | 
						// The duration used to set a deadline when reading from the FastCGI server.
 | 
				
			||||||
	ReadTimeout time.Duration
 | 
						ReadTimeout time.Duration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The duration used to set a deadline when sending to the FastCGI server.
 | 
				
			||||||
 | 
						SendTimeout time.Duration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// FCGI dialer
 | 
						// FCGI dialer
 | 
				
			||||||
	dialer dialer
 | 
						dialer dialer
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -327,9 +327,22 @@ func TestBuildEnv(t *testing.T) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestReadTimeout(t *testing.T) {
 | 
					func TestReadTimeout(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							sleep       time.Duration
 | 
				
			||||||
 | 
							readTimeout time.Duration
 | 
				
			||||||
 | 
							shouldErr   bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{75 * time.Millisecond, 50 * time.Millisecond, true},
 | 
				
			||||||
 | 
							{0, -1 * time.Second, true},
 | 
				
			||||||
 | 
							{0, time.Minute, false},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var wg sync.WaitGroup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i, test := range tests {
 | 
				
			||||||
		listener, err := net.Listen("tcp", "127.0.0.1:0")
 | 
							listener, err := net.Listen("tcp", "127.0.0.1:0")
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
		t.Fatalf("Unable to create listener for test: %v", err)
 | 
								t.Fatalf("Test %d: Unable to create listener for test: %v", i, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		defer listener.Close()
 | 
							defer listener.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -341,24 +354,97 @@ func TestReadTimeout(t *testing.T) {
 | 
				
			|||||||
					Path:        "/",
 | 
										Path:        "/",
 | 
				
			||||||
					Address:     listener.Addr().String(),
 | 
										Address:     listener.Addr().String(),
 | 
				
			||||||
					dialer:      basicDialer{network: network, address: address},
 | 
										dialer:      basicDialer{network: network, address: address},
 | 
				
			||||||
				ReadTimeout: time.Millisecond * 100,
 | 
										ReadTimeout: test.readTimeout,
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		r, err := http.NewRequest("GET", "/", nil)
 | 
							r, err := http.NewRequest("GET", "/", nil)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
		t.Fatalf("Unable to create request: %v", err)
 | 
								t.Fatalf("Test %d: Unable to create request: %v", i, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							w := httptest.NewRecorder()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							wg.Add(1)
 | 
				
			||||||
 | 
							go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
								time.Sleep(test.sleep)
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusOK)
 | 
				
			||||||
 | 
								wg.Done()
 | 
				
			||||||
 | 
							}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							got, err := handler.ServeHTTP(w, r)
 | 
				
			||||||
 | 
							if test.shouldErr {
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									t.Errorf("Test %d: Expected i/o timeout error but had none", i)
 | 
				
			||||||
 | 
								} else if err, ok := err.(net.Error); !ok || !err.Timeout() {
 | 
				
			||||||
 | 
									t.Errorf("Test %d: Expected i/o timeout error, got: '%s'", i, err.Error())
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								want := http.StatusGatewayTimeout
 | 
				
			||||||
 | 
								if got != want {
 | 
				
			||||||
 | 
									t.Errorf("Test %d: Expected returned status code to be %d, got: %d",
 | 
				
			||||||
 | 
										i, want, got)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else if err != nil {
 | 
				
			||||||
 | 
								t.Errorf("Test %d: Expected nil error, got: %v", i, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							wg.Wait()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSendTimeout(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							sendTimeout time.Duration
 | 
				
			||||||
 | 
							shouldErr   bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{-1 * time.Second, true},
 | 
				
			||||||
 | 
							{time.Minute, false},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i, test := range tests {
 | 
				
			||||||
 | 
							listener, err := net.Listen("tcp", "127.0.0.1:0")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("Test %d: Unable to create listener for test: %v", i, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer listener.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							network, address := parseAddress(listener.Addr().String())
 | 
				
			||||||
 | 
							handler := Handler{
 | 
				
			||||||
 | 
								Next: nil,
 | 
				
			||||||
 | 
								Rules: []Rule{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Path:        "/",
 | 
				
			||||||
 | 
										Address:     listener.Addr().String(),
 | 
				
			||||||
 | 
										dialer:      basicDialer{network: network, address: address},
 | 
				
			||||||
 | 
										SendTimeout: test.sendTimeout,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							r, err := http.NewRequest("GET", "/", nil)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("Test %d: Unable to create request: %v", i, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		w := httptest.NewRecorder()
 | 
							w := httptest.NewRecorder()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
							go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
		time.Sleep(time.Millisecond * 130)
 | 
								w.WriteHeader(http.StatusOK)
 | 
				
			||||||
		}))
 | 
							}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = handler.ServeHTTP(w, r)
 | 
							got, err := handler.ServeHTTP(w, r)
 | 
				
			||||||
 | 
							if test.shouldErr {
 | 
				
			||||||
			if err == nil {
 | 
								if err == nil {
 | 
				
			||||||
		t.Error("Expected i/o timeout error but had none")
 | 
									t.Errorf("Test %d: Expected i/o timeout error but had none", i)
 | 
				
			||||||
			} else if err, ok := err.(net.Error); !ok || !err.Timeout() {
 | 
								} else if err, ok := err.(net.Error); !ok || !err.Timeout() {
 | 
				
			||||||
		t.Errorf("Expected i/o timeout error, got: '%s'", err.Error())
 | 
									t.Errorf("Test %d: Expected i/o timeout error, got: '%s'", i, err.Error())
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								want := http.StatusGatewayTimeout
 | 
				
			||||||
 | 
								if got != want {
 | 
				
			||||||
 | 
									t.Errorf("Test %d: Expected returned status code to be %d, got: %d",
 | 
				
			||||||
 | 
										i, want, got)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else if err != nil {
 | 
				
			||||||
 | 
								t.Errorf("Test %d: Expected nil error, got: %v", i, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,6 @@ import (
 | 
				
			|||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"encoding/binary"
 | 
						"encoding/binary"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"mime/multipart"
 | 
						"mime/multipart"
 | 
				
			||||||
@ -116,8 +115,8 @@ type Client interface {
 | 
				
			|||||||
	Post(pairs map[string]string, method string, bodyType string, body io.Reader, contentLength int) (response *http.Response, err error)
 | 
						Post(pairs map[string]string, method string, bodyType string, body io.Reader, contentLength int) (response *http.Response, err error)
 | 
				
			||||||
	Close() error
 | 
						Close() error
 | 
				
			||||||
	StdErr() bytes.Buffer
 | 
						StdErr() bytes.Buffer
 | 
				
			||||||
	ReadTimeout() time.Duration
 | 
					 | 
				
			||||||
	SetReadTimeout(time.Duration) error
 | 
						SetReadTimeout(time.Duration) error
 | 
				
			||||||
 | 
						SetSendTimeout(time.Duration) error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type header struct {
 | 
					type header struct {
 | 
				
			||||||
@ -174,57 +173,32 @@ func (rec *record) read(r io.Reader) (buf []byte, err error) {
 | 
				
			|||||||
// interfacing external applications with Web servers.
 | 
					// interfacing external applications with Web servers.
 | 
				
			||||||
type FCGIClient struct {
 | 
					type FCGIClient struct {
 | 
				
			||||||
	mutex       sync.Mutex
 | 
						mutex       sync.Mutex
 | 
				
			||||||
	rwc         io.ReadWriteCloser
 | 
						conn        net.Conn
 | 
				
			||||||
	h           header
 | 
						h           header
 | 
				
			||||||
	buf         bytes.Buffer
 | 
						buf         bytes.Buffer
 | 
				
			||||||
	stderr      bytes.Buffer
 | 
						stderr      bytes.Buffer
 | 
				
			||||||
	keepAlive   bool
 | 
						keepAlive   bool
 | 
				
			||||||
	reqID       uint16
 | 
						reqID       uint16
 | 
				
			||||||
	readTimeout time.Duration
 | 
						readTimeout time.Duration
 | 
				
			||||||
 | 
						sendTimeout time.Duration
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DialWithDialer connects to the fcgi responder at the specified network address, using custom net.Dialer.
 | 
					// DialTimeout connects to the fcgi responder at the specified network address, using default net.Dialer.
 | 
				
			||||||
// See func net.Dial for a description of the network and address parameters.
 | 
					// See func net.Dial for a description of the network and address parameters.
 | 
				
			||||||
func DialWithDialer(network, address string, dialer net.Dialer) (fcgi *FCGIClient, err error) {
 | 
					func DialTimeout(network string, address string, timeout time.Duration) (fcgi *FCGIClient, err error) {
 | 
				
			||||||
	var conn net.Conn
 | 
						conn, err := net.DialTimeout(network, address, timeout)
 | 
				
			||||||
	conn, err = dialer.Dial(network, address)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fcgi = &FCGIClient{
 | 
						fcgi = &FCGIClient{conn: conn, keepAlive: false, reqID: 1}
 | 
				
			||||||
		rwc:       conn,
 | 
					 | 
				
			||||||
		keepAlive: false,
 | 
					 | 
				
			||||||
		reqID:     1,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return
 | 
						return fcgi, nil
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Dial connects to the fcgi responder at the specified network address, using default net.Dialer.
 | 
					 | 
				
			||||||
// See func net.Dial for a description of the network and address parameters.
 | 
					 | 
				
			||||||
func Dial(network string, address string, timeout time.Duration) (fcgi *FCGIClient, err error) {
 | 
					 | 
				
			||||||
	return DialWithDialer(network, address, net.Dialer{Timeout: timeout})
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Close closes fcgi connnection.
 | 
					// Close closes fcgi connnection.
 | 
				
			||||||
func (c *FCGIClient) Close() error {
 | 
					func (c *FCGIClient) Close() error {
 | 
				
			||||||
	return c.rwc.Close()
 | 
						return c.conn.Close()
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// setReadDeadline sets a read deadline on FCGIClient based on the configured
 | 
					 | 
				
			||||||
// readTimeout. A zero value for readTimeout means no deadline will be set.
 | 
					 | 
				
			||||||
func (c *FCGIClient) setReadDeadline() error {
 | 
					 | 
				
			||||||
	if c.readTimeout > 0 {
 | 
					 | 
				
			||||||
		conn, ok := c.rwc.(net.Conn)
 | 
					 | 
				
			||||||
		if ok {
 | 
					 | 
				
			||||||
			conn.SetReadDeadline(time.Now().Add(c.readTimeout))
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return fmt.Errorf("Could not set Client ReadTimeout")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *FCGIClient) writeRecord(recType uint8, content []byte) error {
 | 
					func (c *FCGIClient) writeRecord(recType uint8, content []byte) error {
 | 
				
			||||||
@ -245,7 +219,13 @@ func (c *FCGIClient) writeRecord(recType uint8, content []byte) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := c.rwc.Write(c.buf.Bytes()); err != nil {
 | 
						if c.sendTimeout != 0 {
 | 
				
			||||||
 | 
							if err := c.conn.SetWriteDeadline(time.Now().Add(c.sendTimeout)); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := c.conn.Write(c.buf.Bytes()); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -369,7 +349,7 @@ func (w *streamReader) Read(p []byte) (n int, err error) {
 | 
				
			|||||||
			for {
 | 
								for {
 | 
				
			||||||
				rec := &record{}
 | 
									rec := &record{}
 | 
				
			||||||
				var buf []byte
 | 
									var buf []byte
 | 
				
			||||||
				buf, err = rec.read(w.c.rwc)
 | 
									buf, err = rec.read(w.c.conn)
 | 
				
			||||||
				if err == errInvalidHeaderVersion {
 | 
									if err == errInvalidHeaderVersion {
 | 
				
			||||||
					continue
 | 
										continue
 | 
				
			||||||
				} else if err != nil {
 | 
									} else if err != nil {
 | 
				
			||||||
@ -436,7 +416,6 @@ func (c clientCloser) Close() error { return c.f.Close() }
 | 
				
			|||||||
// Request returns a HTTP Response with Header and Body
 | 
					// Request returns a HTTP Response with Header and Body
 | 
				
			||||||
// from fcgi responder
 | 
					// from fcgi responder
 | 
				
			||||||
func (c *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) {
 | 
					func (c *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
	r, err := c.Do(p, req)
 | 
						r, err := c.Do(p, req)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@ -446,9 +425,11 @@ func (c *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Res
 | 
				
			|||||||
	tp := textproto.NewReader(rb)
 | 
						tp := textproto.NewReader(rb)
 | 
				
			||||||
	resp = new(http.Response)
 | 
						resp = new(http.Response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = c.setReadDeadline(); err != nil {
 | 
						if c.readTimeout != 0 {
 | 
				
			||||||
 | 
							if err = c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)); err != nil {
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Parse the response headers.
 | 
						// Parse the response headers.
 | 
				
			||||||
	mimeHeader, err := tp.ReadMIMEHeader()
 | 
						mimeHeader, err := tp.ReadMIMEHeader()
 | 
				
			||||||
@ -582,10 +563,6 @@ func (c *FCGIClient) PostFile(p map[string]string, data url.Values, file map[str
 | 
				
			|||||||
	return c.Post(p, "POST", bodyType, buf, buf.Len())
 | 
						return c.Post(p, "POST", bodyType, buf, buf.Len())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ReadTimeout returns the read timeout for future calls that read from the
 | 
					 | 
				
			||||||
// fcgi responder.
 | 
					 | 
				
			||||||
func (c *FCGIClient) ReadTimeout() time.Duration { return c.readTimeout }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// SetReadTimeout sets the read timeout for future calls that read from the
 | 
					// SetReadTimeout sets the read timeout for future calls that read from the
 | 
				
			||||||
// fcgi responder. A zero value for t means no timeout will be set.
 | 
					// fcgi responder. A zero value for t means no timeout will be set.
 | 
				
			||||||
func (c *FCGIClient) SetReadTimeout(t time.Duration) error {
 | 
					func (c *FCGIClient) SetReadTimeout(t time.Duration) error {
 | 
				
			||||||
@ -593,6 +570,13 @@ func (c *FCGIClient) SetReadTimeout(t time.Duration) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetSendTimeout sets the read timeout for future calls that send data to
 | 
				
			||||||
 | 
					// the fcgi responder. A zero value for t means no timeout will be set.
 | 
				
			||||||
 | 
					func (c *FCGIClient) SetSendTimeout(t time.Duration) error {
 | 
				
			||||||
 | 
						c.sendTimeout = t
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Checks whether chunked is part of the encodings stack
 | 
					// Checks whether chunked is part of the encodings stack
 | 
				
			||||||
func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }
 | 
					func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -103,7 +103,7 @@ func (s FastCGIServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func sendFcgi(reqType int, fcgiParams map[string]string, data []byte, posts map[string]string, files map[string]string) (content []byte) {
 | 
					func sendFcgi(reqType int, fcgiParams map[string]string, data []byte, posts map[string]string, files map[string]string) (content []byte) {
 | 
				
			||||||
	fcgi, err := Dial("tcp", ipPort, 0)
 | 
						fcgi, err := DialTimeout("tcp", ipPort, 0)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Println("err:", err)
 | 
							log.Println("err:", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
 | 
				
			|||||||
@ -59,7 +59,7 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
 | 
				
			|||||||
			return rules, c.ArgErr()
 | 
								return rules, c.ArgErr()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		rule := Rule{Path: args[0], ReadTimeout: 60 * time.Second}
 | 
							rule := Rule{Path: args[0], ReadTimeout: 60 * time.Second, SendTimeout: 60 * time.Second}
 | 
				
			||||||
		upstreams := []string{args[1]}
 | 
							upstreams := []string{args[1]}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if len(args) == 3 {
 | 
							if len(args) == 3 {
 | 
				
			||||||
@ -144,6 +144,15 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
 | 
				
			|||||||
					return rules, err
 | 
										return rules, err
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				rule.ReadTimeout = readTimeout
 | 
									rule.ReadTimeout = readTimeout
 | 
				
			||||||
 | 
								case "send_timeout":
 | 
				
			||||||
 | 
									if !c.NextArg() {
 | 
				
			||||||
 | 
										return rules, c.ArgErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									sendTimeout, err := time.ParseDuration(c.Val())
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return rules, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									rule.SendTimeout = sendTimeout
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -80,6 +80,7 @@ func TestFastcgiParse(t *testing.T) {
 | 
				
			|||||||
				dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: "tcp", address: "127.0.0.1:9000", timeout: 60 * time.Second}}},
 | 
									dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: "tcp", address: "127.0.0.1:9000", timeout: 60 * time.Second}}},
 | 
				
			||||||
				IndexFiles:  []string{"index.php"},
 | 
									IndexFiles:  []string{"index.php"},
 | 
				
			||||||
				ReadTimeout: 60 * time.Second,
 | 
									ReadTimeout: 60 * time.Second,
 | 
				
			||||||
 | 
									SendTimeout: 60 * time.Second,
 | 
				
			||||||
			}}},
 | 
								}}},
 | 
				
			||||||
		{`fastcgi /blog 127.0.0.1:9000 php {
 | 
							{`fastcgi /blog 127.0.0.1:9000 php {
 | 
				
			||||||
			upstream 127.0.0.1:9001
 | 
								upstream 127.0.0.1:9001
 | 
				
			||||||
@ -92,6 +93,7 @@ func TestFastcgiParse(t *testing.T) {
 | 
				
			|||||||
				dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: "tcp", address: "127.0.0.1:9000", timeout: 60 * time.Second}, basicDialer{network: "tcp", address: "127.0.0.1:9001", timeout: 60 * time.Second}}},
 | 
									dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: "tcp", address: "127.0.0.1:9000", timeout: 60 * time.Second}, basicDialer{network: "tcp", address: "127.0.0.1:9001", timeout: 60 * time.Second}}},
 | 
				
			||||||
				IndexFiles:  []string{"index.php"},
 | 
									IndexFiles:  []string{"index.php"},
 | 
				
			||||||
				ReadTimeout: 60 * time.Second,
 | 
									ReadTimeout: 60 * time.Second,
 | 
				
			||||||
 | 
									SendTimeout: 60 * time.Second,
 | 
				
			||||||
			}}},
 | 
								}}},
 | 
				
			||||||
		{`fastcgi /blog 127.0.0.1:9000 {
 | 
							{`fastcgi /blog 127.0.0.1:9000 {
 | 
				
			||||||
			upstream 127.0.0.1:9001 
 | 
								upstream 127.0.0.1:9001 
 | 
				
			||||||
@ -104,6 +106,7 @@ func TestFastcgiParse(t *testing.T) {
 | 
				
			|||||||
				dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: "tcp", address: "127.0.0.1:9000", timeout: 60 * time.Second}, basicDialer{network: "tcp", address: "127.0.0.1:9001", timeout: 60 * time.Second}}},
 | 
									dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: "tcp", address: "127.0.0.1:9000", timeout: 60 * time.Second}, basicDialer{network: "tcp", address: "127.0.0.1:9001", timeout: 60 * time.Second}}},
 | 
				
			||||||
				IndexFiles:  []string{},
 | 
									IndexFiles:  []string{},
 | 
				
			||||||
				ReadTimeout: 60 * time.Second,
 | 
									ReadTimeout: 60 * time.Second,
 | 
				
			||||||
 | 
									SendTimeout: 60 * time.Second,
 | 
				
			||||||
			}}},
 | 
								}}},
 | 
				
			||||||
		{`fastcgi / ` + defaultAddress + ` {
 | 
							{`fastcgi / ` + defaultAddress + ` {
 | 
				
			||||||
	              split .html
 | 
						              split .html
 | 
				
			||||||
@ -116,6 +119,7 @@ func TestFastcgiParse(t *testing.T) {
 | 
				
			|||||||
				dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 60 * time.Second}}},
 | 
									dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 60 * time.Second}}},
 | 
				
			||||||
				IndexFiles:  []string{},
 | 
									IndexFiles:  []string{},
 | 
				
			||||||
				ReadTimeout: 60 * time.Second,
 | 
									ReadTimeout: 60 * time.Second,
 | 
				
			||||||
 | 
									SendTimeout: 60 * time.Second,
 | 
				
			||||||
			}}},
 | 
								}}},
 | 
				
			||||||
		{`fastcgi / ` + defaultAddress + ` {
 | 
							{`fastcgi / ` + defaultAddress + ` {
 | 
				
			||||||
	              split .html
 | 
						              split .html
 | 
				
			||||||
@ -130,6 +134,7 @@ func TestFastcgiParse(t *testing.T) {
 | 
				
			|||||||
				IndexFiles:      []string{},
 | 
									IndexFiles:      []string{},
 | 
				
			||||||
				IgnoredSubPaths: []string{"/admin", "/user"},
 | 
									IgnoredSubPaths: []string{"/admin", "/user"},
 | 
				
			||||||
				ReadTimeout:     60 * time.Second,
 | 
									ReadTimeout:     60 * time.Second,
 | 
				
			||||||
 | 
									SendTimeout:     60 * time.Second,
 | 
				
			||||||
			}}},
 | 
								}}},
 | 
				
			||||||
		{`fastcgi / ` + defaultAddress + ` {
 | 
							{`fastcgi / ` + defaultAddress + ` {
 | 
				
			||||||
	              pool 0
 | 
						              pool 0
 | 
				
			||||||
@ -142,6 +147,7 @@ func TestFastcgiParse(t *testing.T) {
 | 
				
			|||||||
				dialer:      &loadBalancingDialer{dialers: []dialer{&persistentDialer{size: 0, network: network, address: address, timeout: 60 * time.Second}}},
 | 
									dialer:      &loadBalancingDialer{dialers: []dialer{&persistentDialer{size: 0, network: network, address: address, timeout: 60 * time.Second}}},
 | 
				
			||||||
				IndexFiles:  []string{},
 | 
									IndexFiles:  []string{},
 | 
				
			||||||
				ReadTimeout: 60 * time.Second,
 | 
									ReadTimeout: 60 * time.Second,
 | 
				
			||||||
 | 
									SendTimeout: 60 * time.Second,
 | 
				
			||||||
			}}},
 | 
								}}},
 | 
				
			||||||
		{`fastcgi / 127.0.0.1:8080  {
 | 
							{`fastcgi / 127.0.0.1:8080  {
 | 
				
			||||||
			upstream 127.0.0.1:9000
 | 
								upstream 127.0.0.1:9000
 | 
				
			||||||
@ -155,6 +161,7 @@ func TestFastcgiParse(t *testing.T) {
 | 
				
			|||||||
				dialer:      &loadBalancingDialer{dialers: []dialer{&persistentDialer{size: 5, network: "tcp", address: "127.0.0.1:8080", timeout: 60 * time.Second}, &persistentDialer{size: 5, network: "tcp", address: "127.0.0.1:9000", timeout: 60 * time.Second}}},
 | 
									dialer:      &loadBalancingDialer{dialers: []dialer{&persistentDialer{size: 5, network: "tcp", address: "127.0.0.1:8080", timeout: 60 * time.Second}, &persistentDialer{size: 5, network: "tcp", address: "127.0.0.1:9000", timeout: 60 * time.Second}}},
 | 
				
			||||||
				IndexFiles:  []string{},
 | 
									IndexFiles:  []string{},
 | 
				
			||||||
				ReadTimeout: 60 * time.Second,
 | 
									ReadTimeout: 60 * time.Second,
 | 
				
			||||||
 | 
									SendTimeout: 60 * time.Second,
 | 
				
			||||||
			}}},
 | 
								}}},
 | 
				
			||||||
		{`fastcgi / ` + defaultAddress + ` {
 | 
							{`fastcgi / ` + defaultAddress + ` {
 | 
				
			||||||
	              split .php
 | 
						              split .php
 | 
				
			||||||
@ -167,6 +174,7 @@ func TestFastcgiParse(t *testing.T) {
 | 
				
			|||||||
				dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 60 * time.Second}}},
 | 
									dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 60 * time.Second}}},
 | 
				
			||||||
				IndexFiles:  []string{},
 | 
									IndexFiles:  []string{},
 | 
				
			||||||
				ReadTimeout: 60 * time.Second,
 | 
									ReadTimeout: 60 * time.Second,
 | 
				
			||||||
 | 
									SendTimeout: 60 * time.Second,
 | 
				
			||||||
			}}},
 | 
								}}},
 | 
				
			||||||
		{`fastcgi / ` + defaultAddress + ` {
 | 
							{`fastcgi / ` + defaultAddress + ` {
 | 
				
			||||||
	              connect_timeout 5s
 | 
						              connect_timeout 5s
 | 
				
			||||||
@ -179,7 +187,13 @@ func TestFastcgiParse(t *testing.T) {
 | 
				
			|||||||
				dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 5 * time.Second}}},
 | 
									dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 5 * time.Second}}},
 | 
				
			||||||
				IndexFiles:  []string{},
 | 
									IndexFiles:  []string{},
 | 
				
			||||||
				ReadTimeout: 60 * time.Second,
 | 
									ReadTimeout: 60 * time.Second,
 | 
				
			||||||
 | 
									SendTimeout: 60 * time.Second,
 | 
				
			||||||
			}}},
 | 
								}}},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								`fastcgi / ` + defaultAddress + ` { connect_timeout BADVALUE }`,
 | 
				
			||||||
 | 
								true,
 | 
				
			||||||
 | 
								[]Rule{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{`fastcgi / ` + defaultAddress + ` {
 | 
							{`fastcgi / ` + defaultAddress + ` {
 | 
				
			||||||
	              read_timeout 5s
 | 
						              read_timeout 5s
 | 
				
			||||||
	              }`,
 | 
						              }`,
 | 
				
			||||||
@ -191,7 +205,31 @@ func TestFastcgiParse(t *testing.T) {
 | 
				
			|||||||
				dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 60 * time.Second}}},
 | 
									dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 60 * time.Second}}},
 | 
				
			||||||
				IndexFiles:  []string{},
 | 
									IndexFiles:  []string{},
 | 
				
			||||||
				ReadTimeout: 5 * time.Second,
 | 
									ReadTimeout: 5 * time.Second,
 | 
				
			||||||
 | 
									SendTimeout: 60 * time.Second,
 | 
				
			||||||
			}}},
 | 
								}}},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								`fastcgi / ` + defaultAddress + ` { read_timeout BADVALUE }`,
 | 
				
			||||||
 | 
								true,
 | 
				
			||||||
 | 
								[]Rule{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{`fastcgi / ` + defaultAddress + ` {
 | 
				
			||||||
 | 
						              send_timeout 5s
 | 
				
			||||||
 | 
						              }`,
 | 
				
			||||||
 | 
								false, []Rule{{
 | 
				
			||||||
 | 
									Path:        "/",
 | 
				
			||||||
 | 
									Address:     defaultAddress,
 | 
				
			||||||
 | 
									Ext:         "",
 | 
				
			||||||
 | 
									SplitPath:   "",
 | 
				
			||||||
 | 
									dialer:      &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 60 * time.Second}}},
 | 
				
			||||||
 | 
									IndexFiles:  []string{},
 | 
				
			||||||
 | 
									ReadTimeout: 60 * time.Second,
 | 
				
			||||||
 | 
									SendTimeout: 5 * time.Second,
 | 
				
			||||||
 | 
								}}},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								`fastcgi / ` + defaultAddress + ` { send_timeout BADVALUE }`,
 | 
				
			||||||
 | 
								true,
 | 
				
			||||||
 | 
								[]Rule{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{`fastcgi / {
 | 
							{`fastcgi / {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		              }`,
 | 
							              }`,
 | 
				
			||||||
@ -251,6 +289,16 @@ func TestFastcgiParse(t *testing.T) {
 | 
				
			|||||||
				t.Errorf("Test %d expected %dth FastCGI IgnoredSubPaths to be  %s  , but got %s",
 | 
									t.Errorf("Test %d expected %dth FastCGI IgnoredSubPaths to be  %s  , but got %s",
 | 
				
			||||||
					i, j, test.expectedFastcgiConfig[j].IgnoredSubPaths, actualFastcgiConfig.IgnoredSubPaths)
 | 
										i, j, test.expectedFastcgiConfig[j].IgnoredSubPaths, actualFastcgiConfig.IgnoredSubPaths)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if fmt.Sprint(actualFastcgiConfig.ReadTimeout) != fmt.Sprint(test.expectedFastcgiConfig[j].ReadTimeout) {
 | 
				
			||||||
 | 
									t.Errorf("Test %d expected %dth FastCGI ReadTimeout to be  %s  , but got %s",
 | 
				
			||||||
 | 
										i, j, test.expectedFastcgiConfig[j].ReadTimeout, actualFastcgiConfig.ReadTimeout)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if fmt.Sprint(actualFastcgiConfig.SendTimeout) != fmt.Sprint(test.expectedFastcgiConfig[j].SendTimeout) {
 | 
				
			||||||
 | 
									t.Errorf("Test %d expected %dth FastCGI SendTimeout to be  %s  , but got %s",
 | 
				
			||||||
 | 
										i, j, test.expectedFastcgiConfig[j].SendTimeout, actualFastcgiConfig.SendTimeout)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user