mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
Fastcgi upstreams (#1264)
* Make fastcgi load balanceable too * Address one more corner case - invalid configuration fastcgi / * After review fixes * Simplify conditions * Error message * New fastcgi syntax * golint will be happy * Change syntax
This commit is contained in:
parent
12fd349916
commit
c972ea39c8
6 changed files with 295 additions and 53 deletions
|
@ -1,10 +1,14 @@
|
||||||
package fastcgi
|
package fastcgi
|
||||||
|
|
||||||
import "sync"
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
type dialer interface {
|
type dialer interface {
|
||||||
Dial() (*FCGIClient, error)
|
Dial() (Client, error)
|
||||||
Close(*FCGIClient) error
|
Close(Client) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// basicDialer is a basic dialer that wraps default fcgi functions.
|
// basicDialer is a basic dialer that wraps default fcgi functions.
|
||||||
|
@ -12,8 +16,8 @@ type basicDialer struct {
|
||||||
network, address string
|
network, address string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b basicDialer) Dial() (*FCGIClient, error) { return Dial(b.network, b.address) }
|
func (b basicDialer) Dial() (Client, error) { return Dial(b.network, b.address) }
|
||||||
func (b basicDialer) Close(c *FCGIClient) 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.
|
||||||
// connections are not closed after use, rather added back to the pool for reuse.
|
// connections are not closed after use, rather added back to the pool for reuse.
|
||||||
|
@ -21,11 +25,11 @@ type persistentDialer struct {
|
||||||
size int
|
size int
|
||||||
network string
|
network string
|
||||||
address string
|
address string
|
||||||
pool []*FCGIClient
|
pool []Client
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *persistentDialer) Dial() (*FCGIClient, error) {
|
func (p *persistentDialer) Dial() (Client, error) {
|
||||||
p.Lock()
|
p.Lock()
|
||||||
// connection is available, return first one.
|
// connection is available, return first one.
|
||||||
if len(p.pool) > 0 {
|
if len(p.pool) > 0 {
|
||||||
|
@ -42,7 +46,7 @@ func (p *persistentDialer) Dial() (*FCGIClient, error) {
|
||||||
return Dial(p.network, p.address)
|
return Dial(p.network, p.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *persistentDialer) Close(client *FCGIClient) error {
|
func (p *persistentDialer) Close(client Client) error {
|
||||||
p.Lock()
|
p.Lock()
|
||||||
if len(p.pool) < p.size {
|
if len(p.pool) < p.size {
|
||||||
// pool is not full yet, add connection for reuse
|
// pool is not full yet, add connection for reuse
|
||||||
|
@ -57,3 +61,35 @@ func (p *persistentDialer) Close(client *FCGIClient) error {
|
||||||
// otherwise, close the connection.
|
// otherwise, close the connection.
|
||||||
return client.Close()
|
return client.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type loadBalancingDialer struct {
|
||||||
|
dialers []dialer
|
||||||
|
current int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *loadBalancingDialer) Dial() (Client, error) {
|
||||||
|
nextDialerIndex := atomic.AddInt64(&m.current, 1) % int64(len(m.dialers))
|
||||||
|
currentDialer := m.dialers[nextDialerIndex]
|
||||||
|
|
||||||
|
client, err := currentDialer.Dial()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dialerAwareClient{Client: client, dialer: currentDialer}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *loadBalancingDialer) Close(c Client) error {
|
||||||
|
// Close the client according to dialer behaviour
|
||||||
|
if da, ok := c.(*dialerAwareClient); ok {
|
||||||
|
return da.dialer.Close(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("Cannot close client")
|
||||||
|
}
|
||||||
|
|
||||||
|
type dialerAwareClient struct {
|
||||||
|
Client
|
||||||
|
dialer dialer
|
||||||
|
}
|
||||||
|
|
126
caddyhttp/fastcgi/dialer_test.go
Normal file
126
caddyhttp/fastcgi/dialer_test.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
package fastcgi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadbalancingDialer(t *testing.T) {
|
||||||
|
// given
|
||||||
|
runs := 100
|
||||||
|
mockDialer1 := new(mockDialer)
|
||||||
|
mockDialer2 := new(mockDialer)
|
||||||
|
|
||||||
|
dialer := &loadBalancingDialer{dialers: []dialer{mockDialer1, mockDialer2}}
|
||||||
|
|
||||||
|
// when
|
||||||
|
for i := 0; i < runs; i++ {
|
||||||
|
client, err := dialer.Dial()
|
||||||
|
dialer.Close(client)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected error to be nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
if mockDialer1.dialCalled != mockDialer2.dialCalled && mockDialer1.dialCalled != 50 {
|
||||||
|
t.Errorf("Expected dialer to call Dial() on multiple backend dialers %d times [actual: %d, %d]", 50, mockDialer1.dialCalled, mockDialer2.dialCalled)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mockDialer1.closeCalled != mockDialer2.closeCalled && mockDialer1.closeCalled != 50 {
|
||||||
|
t.Errorf("Expected dialer to call Close() on multiple backend dialers %d times [actual: %d, %d]", 50, mockDialer1.closeCalled, mockDialer2.closeCalled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadBalancingDialerShouldReturnDialerAwareClient(t *testing.T) {
|
||||||
|
// given
|
||||||
|
mockDialer1 := new(mockDialer)
|
||||||
|
dialer := &loadBalancingDialer{dialers: []dialer{mockDialer1}}
|
||||||
|
|
||||||
|
// when
|
||||||
|
client, err := dialer.Dial()
|
||||||
|
|
||||||
|
// then
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected error to be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if awareClient, ok := client.(*dialerAwareClient); !ok {
|
||||||
|
t.Error("Expected dialer to wrap client")
|
||||||
|
} else {
|
||||||
|
if awareClient.dialer != mockDialer1 {
|
||||||
|
t.Error("Expected wrapped client to have reference to dialer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadBalancingDialerShouldUnderlyingReturnDialerError(t *testing.T) {
|
||||||
|
// given
|
||||||
|
mockDialer1 := new(errorReturningDialer)
|
||||||
|
dialer := &loadBalancingDialer{dialers: []dialer{mockDialer1}}
|
||||||
|
|
||||||
|
// when
|
||||||
|
_, err := dialer.Dial()
|
||||||
|
|
||||||
|
// then
|
||||||
|
if err.Error() != "Error during dial" {
|
||||||
|
t.Errorf("Expected 'Error during dial', got: '%s'", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadBalancingDialerShouldCloseClient(t *testing.T) {
|
||||||
|
// given
|
||||||
|
mockDialer1 := new(mockDialer)
|
||||||
|
mockDialer2 := new(mockDialer)
|
||||||
|
|
||||||
|
dialer := &loadBalancingDialer{dialers: []dialer{mockDialer1, mockDialer2}}
|
||||||
|
client, _ := dialer.Dial()
|
||||||
|
|
||||||
|
// when
|
||||||
|
err := dialer.Close(client)
|
||||||
|
|
||||||
|
// then
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Expected error not to occur")
|
||||||
|
}
|
||||||
|
|
||||||
|
// load balancing starts from index 1
|
||||||
|
if mockDialer2.client != client {
|
||||||
|
t.Errorf("Expected Close() to be called on referenced dialer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockDialer struct {
|
||||||
|
dialCalled int
|
||||||
|
closeCalled int
|
||||||
|
client Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockClient struct {
|
||||||
|
Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDialer) Dial() (Client, error) {
|
||||||
|
m.dialCalled++
|
||||||
|
return mockClient{Client: &FCGIClient{}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDialer) Close(c Client) error {
|
||||||
|
m.client = c
|
||||||
|
m.closeCalled++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorReturningDialer struct {
|
||||||
|
client Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *errorReturningDialer) Dial() (Client, error) {
|
||||||
|
return mockClient{Client: &FCGIClient{}}, errors.New("Error during dial")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *errorReturningDialer) Close(c Client) error {
|
||||||
|
m.client = c
|
||||||
|
return errors.New("Error during close")
|
||||||
|
}
|
|
@ -111,9 +111,9 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
|
||||||
defer rule.dialer.Close(fcgiBackend)
|
defer rule.dialer.Close(fcgiBackend)
|
||||||
|
|
||||||
// Log any stderr output from upstream
|
// Log any stderr output from upstream
|
||||||
if fcgiBackend.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.
|
||||||
err = LogError(strings.TrimSuffix(fcgiBackend.stderr.String(), "\n"))
|
err = LogError(strings.TrimSuffix(stderr.String(), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normally we would return the status code if it is an error status (>= 400),
|
// Normally we would return the status code if it is an error status (>= 400),
|
||||||
|
|
|
@ -106,6 +106,16 @@ const (
|
||||||
maxPad = 255
|
maxPad = 255
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Client interface
|
||||||
|
type Client interface {
|
||||||
|
Get(pair map[string]string) (response *http.Response, err error)
|
||||||
|
Head(pair map[string]string) (response *http.Response, err error)
|
||||||
|
Options(pairs map[string]string) (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
|
||||||
|
StdErr() bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
type header struct {
|
type header struct {
|
||||||
Version uint8
|
Version uint8
|
||||||
Type uint8
|
Type uint8
|
||||||
|
@ -197,24 +207,31 @@ func (c *FCGIClient) Close() error {
|
||||||
return c.rwc.Close()
|
return c.rwc.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *FCGIClient) writeRecord(recType uint8, content []byte) (err error) {
|
func (c *FCGIClient) writeRecord(recType uint8, content []byte) error {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
c.buf.Reset()
|
c.buf.Reset()
|
||||||
c.h.init(recType, c.reqID, len(content))
|
c.h.init(recType, c.reqID, len(content))
|
||||||
|
|
||||||
if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil {
|
if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := c.buf.Write(content); err != nil {
|
if _, err := c.buf.Write(content); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil {
|
if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = c.rwc.Write(c.buf.Bytes())
|
|
||||||
|
if _, err := c.rwc.Write(c.buf.Bytes()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *FCGIClient) writeBeginRequest(role uint16, flags uint8) error {
|
func (c *FCGIClient) writeBeginRequest(role uint16, flags uint8) error {
|
||||||
b := [8]byte{byte(role >> 8), byte(role), flags}
|
b := [8]byte{byte(role >> 8), byte(role), flags}
|
||||||
return c.writeRecord(BeginRequest, b[:])
|
return c.writeRecord(BeginRequest, b[:])
|
||||||
|
@ -360,6 +377,11 @@ func (w *streamReader) Read(p []byte) (n int, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StdErr returns stderr stream
|
||||||
|
func (c *FCGIClient) StdErr() bytes.Buffer {
|
||||||
|
return c.stderr
|
||||||
|
}
|
||||||
|
|
||||||
// Do made the request and returns a io.Reader that translates the data read
|
// Do made the request and returns a io.Reader that translates the data read
|
||||||
// from fcgi responder out of fcgi packet before returning it.
|
// 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) {
|
func (c *FCGIClient) Do(p map[string]string, req io.Reader) (r io.Reader, err error) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
|
@ -55,26 +56,21 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
|
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
switch len(args) {
|
if len(args) < 2 || len(args) > 3 {
|
||||||
case 0:
|
|
||||||
return rules, c.ArgErr()
|
return rules, c.ArgErr()
|
||||||
case 1:
|
}
|
||||||
rule.Path = "/"
|
|
||||||
rule.Address = args[0]
|
|
||||||
case 2:
|
|
||||||
rule.Path = args[0]
|
rule.Path = args[0]
|
||||||
rule.Address = args[1]
|
upstreams := []string{args[1]}
|
||||||
case 3:
|
|
||||||
rule.Path = args[0]
|
if len(args) == 3 {
|
||||||
rule.Address = args[1]
|
if err := fastcgiPreset(args[2], &rule); err != nil {
|
||||||
err := fastcgiPreset(args[2], &rule)
|
return rules, err
|
||||||
if err != nil {
|
|
||||||
return rules, c.Err("Invalid fastcgi rule preset '" + args[2] + "'")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
network, address := parseAddress(rule.Address)
|
var dialers []dialer
|
||||||
rule.dialer = basicDialer{network: network, address: address}
|
var poolSize = -1
|
||||||
|
|
||||||
for c.NextBlock() {
|
for c.NextBlock() {
|
||||||
switch c.Val() {
|
switch c.Val() {
|
||||||
|
@ -94,6 +90,15 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
return rules, c.ArgErr()
|
return rules, c.ArgErr()
|
||||||
}
|
}
|
||||||
rule.IndexFiles = args
|
rule.IndexFiles = args
|
||||||
|
|
||||||
|
case "upstream":
|
||||||
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
|
if len(args) != 1 {
|
||||||
|
return rules, c.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
upstreams = append(upstreams, args[0])
|
||||||
case "env":
|
case "env":
|
||||||
envArgs := c.RemainingArgs()
|
envArgs := c.RemainingArgs()
|
||||||
if len(envArgs) < 2 {
|
if len(envArgs) < 2 {
|
||||||
|
@ -106,6 +111,7 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
return rules, c.ArgErr()
|
return rules, c.ArgErr()
|
||||||
}
|
}
|
||||||
rule.IgnoredSubPaths = ignoredPaths
|
rule.IgnoredSubPaths = ignoredPaths
|
||||||
|
|
||||||
case "pool":
|
case "pool":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return rules, c.ArgErr()
|
return rules, c.ArgErr()
|
||||||
|
@ -115,13 +121,24 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
return rules, err
|
return rules, err
|
||||||
}
|
}
|
||||||
if pool >= 0 {
|
if pool >= 0 {
|
||||||
rule.dialer = &persistentDialer{size: pool, network: network, address: address}
|
poolSize = pool
|
||||||
} else {
|
} else {
|
||||||
return rules, c.Errf("positive integer expected, found %d", pool)
|
return rules, c.Errf("positive integer expected, found %d", pool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, rawAddress := range upstreams {
|
||||||
|
network, address := parseAddress(rawAddress)
|
||||||
|
if poolSize >= 0 {
|
||||||
|
dialers = append(dialers, &persistentDialer{size: poolSize, network: network, address: address})
|
||||||
|
} else {
|
||||||
|
dialers = append(dialers, basicDialer{network: network, address: address})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.dialer = &loadBalancingDialer{dialers: dialers}
|
||||||
|
rule.Address = strings.Join(upstreams, ",")
|
||||||
rules = append(rules, rule)
|
rules = append(rules, rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,9 +76,31 @@ func TestFastcgiParse(t *testing.T) {
|
||||||
Address: "127.0.0.1:9000",
|
Address: "127.0.0.1:9000",
|
||||||
Ext: ".php",
|
Ext: ".php",
|
||||||
SplitPath: ".php",
|
SplitPath: ".php",
|
||||||
dialer: basicDialer{network: "tcp", address: "127.0.0.1:9000"},
|
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: "tcp", address: "127.0.0.1:9000"}}},
|
||||||
IndexFiles: []string{"index.php"},
|
IndexFiles: []string{"index.php"},
|
||||||
}}},
|
}}},
|
||||||
|
{`fastcgi /blog 127.0.0.1:9000 php {
|
||||||
|
upstream 127.0.0.1:9001
|
||||||
|
}`,
|
||||||
|
false, []Rule{{
|
||||||
|
Path: "/blog",
|
||||||
|
Address: "127.0.0.1:9000,127.0.0.1:9001",
|
||||||
|
Ext: ".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"}}},
|
||||||
|
IndexFiles: []string{"index.php"},
|
||||||
|
}}},
|
||||||
|
{`fastcgi /blog 127.0.0.1:9000 {
|
||||||
|
upstream 127.0.0.1:9001
|
||||||
|
}`,
|
||||||
|
false, []Rule{{
|
||||||
|
Path: "/blog",
|
||||||
|
Address: "127.0.0.1:9000,127.0.0.1:9001",
|
||||||
|
Ext: "",
|
||||||
|
SplitPath: "",
|
||||||
|
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: "tcp", address: "127.0.0.1:9000"}, basicDialer{network: "tcp", address: "127.0.0.1:9001"}}},
|
||||||
|
IndexFiles: []string{},
|
||||||
|
}}},
|
||||||
{`fastcgi / ` + defaultAddress + ` {
|
{`fastcgi / ` + defaultAddress + ` {
|
||||||
split .html
|
split .html
|
||||||
}`,
|
}`,
|
||||||
|
@ -87,7 +109,7 @@ func TestFastcgiParse(t *testing.T) {
|
||||||
Address: defaultAddress,
|
Address: defaultAddress,
|
||||||
Ext: "",
|
Ext: "",
|
||||||
SplitPath: ".html",
|
SplitPath: ".html",
|
||||||
dialer: basicDialer{network: network, address: address},
|
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address}}},
|
||||||
IndexFiles: []string{},
|
IndexFiles: []string{},
|
||||||
}}},
|
}}},
|
||||||
{`fastcgi / ` + defaultAddress + ` {
|
{`fastcgi / ` + defaultAddress + ` {
|
||||||
|
@ -99,7 +121,7 @@ func TestFastcgiParse(t *testing.T) {
|
||||||
Address: "127.0.0.1:9001",
|
Address: "127.0.0.1:9001",
|
||||||
Ext: "",
|
Ext: "",
|
||||||
SplitPath: ".html",
|
SplitPath: ".html",
|
||||||
dialer: basicDialer{network: network, address: address},
|
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address}}},
|
||||||
IndexFiles: []string{},
|
IndexFiles: []string{},
|
||||||
IgnoredSubPaths: []string{"/admin", "/user"},
|
IgnoredSubPaths: []string{"/admin", "/user"},
|
||||||
}}},
|
}}},
|
||||||
|
@ -111,18 +133,19 @@ func TestFastcgiParse(t *testing.T) {
|
||||||
Address: defaultAddress,
|
Address: defaultAddress,
|
||||||
Ext: "",
|
Ext: "",
|
||||||
SplitPath: "",
|
SplitPath: "",
|
||||||
dialer: &persistentDialer{size: 0, network: network, address: address},
|
dialer: &loadBalancingDialer{dialers: []dialer{&persistentDialer{size: 0, network: network, address: address}}},
|
||||||
IndexFiles: []string{},
|
IndexFiles: []string{},
|
||||||
}}},
|
}}},
|
||||||
{`fastcgi / ` + defaultAddress + ` {
|
{`fastcgi / 127.0.0.1:8080 {
|
||||||
|
upstream 127.0.0.1:9000
|
||||||
pool 5
|
pool 5
|
||||||
}`,
|
}`,
|
||||||
false, []Rule{{
|
false, []Rule{{
|
||||||
Path: "/",
|
Path: "/",
|
||||||
Address: defaultAddress,
|
Address: "127.0.0.1:8080,127.0.0.1:9000",
|
||||||
Ext: "",
|
Ext: "",
|
||||||
SplitPath: "",
|
SplitPath: "",
|
||||||
dialer: &persistentDialer{size: 5, network: network, address: address},
|
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"}}},
|
||||||
IndexFiles: []string{},
|
IndexFiles: []string{},
|
||||||
}}},
|
}}},
|
||||||
{`fastcgi / ` + defaultAddress + ` {
|
{`fastcgi / ` + defaultAddress + ` {
|
||||||
|
@ -133,9 +156,14 @@ func TestFastcgiParse(t *testing.T) {
|
||||||
Address: defaultAddress,
|
Address: defaultAddress,
|
||||||
Ext: "",
|
Ext: "",
|
||||||
SplitPath: ".php",
|
SplitPath: ".php",
|
||||||
dialer: basicDialer{network: network, address: address},
|
dialer: &loadBalancingDialer{dialers: []dialer{basicDialer{network: network, address: address}}},
|
||||||
IndexFiles: []string{},
|
IndexFiles: []string{},
|
||||||
}}},
|
}}},
|
||||||
|
{`fastcgi / {
|
||||||
|
|
||||||
|
}`,
|
||||||
|
true, []Rule{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
actualFastcgiConfigs, err := fastcgiParse(caddy.NewTestController("http", test.inputFastcgiConfig))
|
actualFastcgiConfigs, err := fastcgiParse(caddy.NewTestController("http", test.inputFastcgiConfig))
|
||||||
|
@ -175,20 +203,7 @@ func TestFastcgiParse(t *testing.T) {
|
||||||
t.Errorf("Test %d expected %dth FastCGI dialer to be of type %T, but got %T",
|
t.Errorf("Test %d expected %dth FastCGI dialer to be of type %T, but got %T",
|
||||||
i, j, test.expectedFastcgiConfig[j].dialer, actualFastcgiConfig.dialer)
|
i, j, test.expectedFastcgiConfig[j].dialer, actualFastcgiConfig.dialer)
|
||||||
} else {
|
} else {
|
||||||
equal := true
|
if !areDialersEqual(actualFastcgiConfig.dialer, test.expectedFastcgiConfig[j].dialer, t) {
|
||||||
switch actual := actualFastcgiConfig.dialer.(type) {
|
|
||||||
case basicDialer:
|
|
||||||
equal = actualFastcgiConfig.dialer == test.expectedFastcgiConfig[j].dialer
|
|
||||||
case *persistentDialer:
|
|
||||||
if expected, ok := test.expectedFastcgiConfig[j].dialer.(*persistentDialer); ok {
|
|
||||||
equal = actual.Equals(expected)
|
|
||||||
} else {
|
|
||||||
equal = false
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
t.Errorf("Unkonw dialer type %T", actualFastcgiConfig.dialer)
|
|
||||||
}
|
|
||||||
if !equal {
|
|
||||||
t.Errorf("Test %d expected %dth FastCGI dialer to be %v, but got %v",
|
t.Errorf("Test %d expected %dth FastCGI dialer to be %v, but got %v",
|
||||||
i, j, test.expectedFastcgiConfig[j].dialer, actualFastcgiConfig.dialer)
|
i, j, test.expectedFastcgiConfig[j].dialer, actualFastcgiConfig.dialer)
|
||||||
}
|
}
|
||||||
|
@ -205,5 +220,31 @@ func TestFastcgiParse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func areDialersEqual(current, expected dialer, t *testing.T) bool {
|
||||||
|
|
||||||
|
switch actual := current.(type) {
|
||||||
|
case *loadBalancingDialer:
|
||||||
|
if expected, ok := expected.(*loadBalancingDialer); ok {
|
||||||
|
for i := 0; i < len(actual.dialers); i++ {
|
||||||
|
if !areDialersEqual(actual.dialers[i], expected.dialers[i], t) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case basicDialer:
|
||||||
|
return current == expected
|
||||||
|
case *persistentDialer:
|
||||||
|
if expected, ok := expected.(*persistentDialer); ok {
|
||||||
|
return actual.Equals(expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Errorf("Unknown dialer type %T", current)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue