Commit b8722d9a authored by ericdreeves's avatar ericdreeves

Fix read timeout and add default timeout values.

By setting the read deadline in streamReader.Read(), the deadline was
extended by the read timeout on each subsequent call. To avoid this, the
deadline is set in FCGIClient.Request(), before the first read occurs.

See #1094.
parent 7dc23b18
...@@ -332,9 +332,6 @@ func TestReadTimeout(t *testing.T) { ...@@ -332,9 +332,6 @@ func TestReadTimeout(t *testing.T) {
t.Fatalf("Unable to create listener for test: %v", err) t.Fatalf("Unable to create listener for test: %v", err)
} }
defer listener.Close() defer listener.Close()
go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Second * 1)
}))
network, address := parseAddress(listener.Addr().String()) network, address := parseAddress(listener.Addr().String())
handler := Handler{ handler := Handler{
...@@ -354,6 +351,10 @@ func TestReadTimeout(t *testing.T) { ...@@ -354,6 +351,10 @@ func TestReadTimeout(t *testing.T) {
} }
w := httptest.NewRecorder() w := httptest.NewRecorder()
go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Millisecond * 130)
}))
_, err = handler.ServeHTTP(w, r) _, err = handler.ServeHTTP(w, r)
if err == nil { if err == nil {
t.Error("Expected i/o timeout error but had none") t.Error("Expected i/o timeout error but had none")
......
...@@ -212,6 +212,21 @@ func (c *FCGIClient) Close() error { ...@@ -212,6 +212,21 @@ func (c *FCGIClient) Close() error {
return c.rwc.Close() return c.rwc.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 {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
...@@ -350,20 +365,10 @@ func (w *streamReader) Read(p []byte) (n int, err error) { ...@@ -350,20 +365,10 @@ func (w *streamReader) Read(p []byte) (n int, err error) {
if len(p) > 0 { if len(p) > 0 {
if len(w.buf) == 0 { if len(w.buf) == 0 {
// filter outputs for error log // filter outputs for error log
for { for {
rec := &record{} rec := &record{}
var buf []byte var buf []byte
if readTimeout := w.c.ReadTimeout(); readTimeout > 0 {
conn, ok := w.c.rwc.(net.Conn)
if ok {
conn.SetReadDeadline(time.Now().Add(readTimeout))
} else {
err = fmt.Errorf("Could not set Client ReadTimeout")
return
}
}
buf, err = rec.read(w.c.rwc) buf, err = rec.read(w.c.rwc)
if err == errInvalidHeaderVersion { if err == errInvalidHeaderVersion {
continue continue
...@@ -441,6 +446,10 @@ func (c *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Res ...@@ -441,6 +446,10 @@ 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 {
return
}
// Parse the response headers. // Parse the response headers.
mimeHeader, err := tp.ReadMIMEHeader() mimeHeader, err := tp.ReadMIMEHeader()
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
......
...@@ -53,15 +53,13 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) { ...@@ -53,15 +53,13 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
var rules []Rule var rules []Rule
for c.Next() { for c.Next() {
var rule Rule
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) < 2 || len(args) > 3 { if len(args) < 2 || len(args) > 3 {
return rules, c.ArgErr() return rules, c.ArgErr()
} }
rule.Path = args[0] rule := Rule{Path: args[0], ReadTimeout: 60 * time.Second}
upstreams := []string{args[1]} upstreams := []string{args[1]}
if len(args) == 3 { if len(args) == 3 {
...@@ -72,7 +70,7 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) { ...@@ -72,7 +70,7 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
var err error var err error
var pool int var pool int
var timeout time.Duration var connectTimeout = 60 * time.Second
var dialers []dialer var dialers []dialer
var poolSize = -1 var poolSize = -1
...@@ -133,7 +131,7 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) { ...@@ -133,7 +131,7 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
if !c.NextArg() { if !c.NextArg() {
return rules, c.ArgErr() return rules, c.ArgErr()
} }
timeout, err = time.ParseDuration(c.Val()) connectTimeout, err = time.ParseDuration(c.Val())
if err != nil { if err != nil {
return rules, err return rules, err
} }
...@@ -152,9 +150,18 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) { ...@@ -152,9 +150,18 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
for _, rawAddress := range upstreams { for _, rawAddress := range upstreams {
network, address := parseAddress(rawAddress) network, address := parseAddress(rawAddress)
if poolSize >= 0 { if poolSize >= 0 {
dialers = append(dialers, &persistentDialer{size: poolSize, network: network, address: address, timeout: timeout}) dialers = append(dialers, &persistentDialer{
size: poolSize,
network: network,
address: address,
timeout: connectTimeout,
})
} else { } else {
dialers = append(dialers, basicDialer{network: network, address: address, timeout: timeout}) dialers = append(dialers, basicDialer{
network: network,
address: address,
timeout: connectTimeout,
})
} }
} }
......
...@@ -73,45 +73,49 @@ func TestFastcgiParse(t *testing.T) { ...@@ -73,45 +73,49 @@ func TestFastcgiParse(t *testing.T) {
{`fastcgi /blog 127.0.0.1:9000 php`, {`fastcgi /blog 127.0.0.1:9000 php`,
false, []Rule{{ false, []Rule{{
Path: "/blog", Path: "/blog",
Address: "127.0.0.1:9000", Address: "127.0.0.1:9000",
Ext: ".php", Ext: ".php",
SplitPath: ".php", SplitPath: ".php",
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: "tcp", address: "127.0.0.1:9000"}}}, 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,
}}}, }}},
{`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
}`, }`,
false, []Rule{{ false, []Rule{{
Path: "/blog", Path: "/blog",
Address: "127.0.0.1:9000,127.0.0.1:9001", Address: "127.0.0.1:9000,127.0.0.1:9001",
Ext: ".php", Ext: ".php",
SplitPath: ".php", SplitPath: ".php",
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: "tcp", address: "127.0.0.1:9000"}, basicDialer{network: "tcp", address: "127.0.0.1:9001"}}}, 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,
}}}, }}},
{`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
}`, }`,
false, []Rule{{ false, []Rule{{
Path: "/blog", Path: "/blog",
Address: "127.0.0.1:9000,127.0.0.1:9001", Address: "127.0.0.1:9000,127.0.0.1:9001",
Ext: "", Ext: "",
SplitPath: "", SplitPath: "",
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: "tcp", address: "127.0.0.1:9000"}, basicDialer{network: "tcp", address: "127.0.0.1:9001"}}}, 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,
}}}, }}},
{`fastcgi / ` + defaultAddress + ` { {`fastcgi / ` + defaultAddress + ` {
split .html split .html
}`, }`,
false, []Rule{{ false, []Rule{{
Path: "/", Path: "/",
Address: defaultAddress, Address: defaultAddress,
Ext: "", Ext: "",
SplitPath: ".html", SplitPath: ".html",
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address}}}, dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 60 * time.Second}}},
IndexFiles: []string{}, IndexFiles: []string{},
ReadTimeout: 60 * time.Second,
}}}, }}},
{`fastcgi / ` + defaultAddress + ` { {`fastcgi / ` + defaultAddress + ` {
split .html split .html
...@@ -122,54 +126,59 @@ func TestFastcgiParse(t *testing.T) { ...@@ -122,54 +126,59 @@ func TestFastcgiParse(t *testing.T) {
Address: "127.0.0.1:9001", Address: "127.0.0.1:9001",
Ext: "", Ext: "",
SplitPath: ".html", SplitPath: ".html",
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address}}}, dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 60 * time.Second}}},
IndexFiles: []string{}, IndexFiles: []string{},
IgnoredSubPaths: []string{"/admin", "/user"}, IgnoredSubPaths: []string{"/admin", "/user"},
ReadTimeout: 60 * time.Second,
}}}, }}},
{`fastcgi / ` + defaultAddress + ` { {`fastcgi / ` + defaultAddress + ` {
pool 0 pool 0
}`, }`,
false, []Rule{{ false, []Rule{{
Path: "/", Path: "/",
Address: defaultAddress, Address: defaultAddress,
Ext: "", Ext: "",
SplitPath: "", SplitPath: "",
dialer: &loadBalancingDialer{dialers: []dialer{&persistentDialer{size: 0, network: network, address: address}}}, dialer: &loadBalancingDialer{dialers: []dialer{&persistentDialer{size: 0, network: network, address: address, timeout: 60 * time.Second}}},
IndexFiles: []string{}, IndexFiles: []string{},
ReadTimeout: 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
pool 5 pool 5
}`, }`,
false, []Rule{{ false, []Rule{{
Path: "/", Path: "/",
Address: "127.0.0.1:8080,127.0.0.1:9000", Address: "127.0.0.1:8080,127.0.0.1:9000",
Ext: "", Ext: "",
SplitPath: "", SplitPath: "",
dialer: &loadBalancingDialer{dialers: []dialer{&persistentDialer{size: 5, network: "tcp", address: "127.0.0.1:8080"}, &persistentDialer{size: 5, network: "tcp", address: "127.0.0.1:9000"}}}, 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,
}}}, }}},
{`fastcgi / ` + defaultAddress + ` { {`fastcgi / ` + defaultAddress + ` {
split .php split .php
}`, }`,
false, []Rule{{ false, []Rule{{
Path: "/", Path: "/",
Address: defaultAddress, Address: defaultAddress,
Ext: "", Ext: "",
SplitPath: ".php", SplitPath: ".php",
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address}}}, dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address, timeout: 60 * time.Second}}},
IndexFiles: []string{}, IndexFiles: []string{},
ReadTimeout: 60 * time.Second,
}}}, }}},
{`fastcgi / ` + defaultAddress + ` { {`fastcgi / ` + defaultAddress + ` {
connect_timeout 5s connect_timeout 5s
}`, }`,
false, []Rule{{ false, []Rule{{
Path: "/", Path: "/",
Address: defaultAddress, Address: defaultAddress,
Ext: "", Ext: "",
SplitPath: "", SplitPath: "",
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,
}}}, }}},
{`fastcgi / ` + defaultAddress + ` { {`fastcgi / ` + defaultAddress + ` {
read_timeout 5s read_timeout 5s
...@@ -179,7 +188,7 @@ func TestFastcgiParse(t *testing.T) { ...@@ -179,7 +188,7 @@ func TestFastcgiParse(t *testing.T) {
Address: defaultAddress, Address: defaultAddress,
Ext: "", Ext: "",
SplitPath: "", SplitPath: "",
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address}}}, 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,
}}}, }}},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment