Skip to content

Commit

Permalink
Configurable idle conn counts for connection pooling
Browse files Browse the repository at this point in the history
  • Loading branch information
francislavoie committed Jan 15, 2025
1 parent 02be81e commit 4b5025f
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 38 deletions.
80 changes: 49 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ $ xcaddy build --with github.com/caddyserver/forwardproxy
Most people prefer the [Caddyfile](https://caddyserver.com/docs/caddyfile) for configuration. You can stand up a simple, wide-open unauthenticated forward proxy like this:

```
example.com
route {
# UNAUTHENTICATED! USE ONLY FOR TESTING
forward_proxy
example.com {
route {
# UNAUTHENTICATED! USE ONLY FOR TESTING
forward_proxy
}
}
```

Expand All @@ -58,9 +59,11 @@ Because `forward_proxy` is not a standard directive, its ordering relative to ot
{
order forward_proxy before file_server
}
example.com
# UNAUTHENTICATED! USE ONLY FOR TESTING
forward_proxy
example.com {
# UNAUTHENTICATED! USE ONLY FOR TESTING
forward_proxy
}
```

to define its position globally; then you don't need `route` blocks. The correct order is up to you and depends on your config.
Expand All @@ -82,29 +85,32 @@ The `forward_proxy` directive has no default order and must be used within a `ro
Here's an example of all properties in use (note that the syntax is subject to change):

```
:443, example.com
route {
forward_proxy {
basic_auth user1 0NtCL2JPJBgPPMmlPcJ
basic_auth user2 密码
ports 80 443
hide_ip
hide_via
probe_resistance secret-link-kWWL9Q.com # alternatively you can use a real domain, such as caddyserver.com
serve_pac /secret-proxy.pac
dial_timeout 30
upstream https://user:[email protected]
acl {
allow *.caddyserver.com
deny 192.168.1.1/32 192.168.0.0/16 *.prohibitedsite.com *.localhost
allow ::1/128 8.8.8.8 github.com *.github.io
allow_file /path/to/whitelist.txt
deny_file /path/to/blacklist.txt
allow all
deny all # unreachable rule, remaining requests are matched by `allow all` above
}
}
file_server
forward_proxy {
basic_auth user1 0NtCL2JPJBgPPMmlPcJ
basic_auth user2 密码
ports 80 443
hide_ip
hide_via
probe_resistance secret-link-kWWL9Q.com # alternatively you can use a real domain, such as caddyserver.com
serve_pac /secret-proxy.pac
dial_timeout 30
max_idle_conns 50
max_idle_conns_per_host 2
upstream https://user:[email protected]
acl {
allow *.caddyserver.com
deny 192.168.1.1/32 192.168.0.0/16 *.prohibitedsite.com *.localhost
allow ::1/128 8.8.8.8 github.com *.github.io
allow_file /path/to/whitelist.txt
deny_file /path/to/blacklist.txt
allow all
deny all # unreachable rule, remaining requests are matched by `allow all` above
}
}
```

Expand Down Expand Up @@ -200,7 +206,19 @@ route {
- `dial_timeout [integer]`
Sets timeout (in seconds) for establishing TCP connection to target website. Affects all requests.

Default: 20 seconds.
Default: 30 seconds.

### Pooling

By default, forwardproxy will reuse connections by using Go's built-in connection pooling mechanism. You can adjust the maximum number of idle connections to keep open:

- `max_idle_conns [integer]`
Sets the maximum number of idle connections to keep open, globally. Set to -1 for no global limit. See https://pkg.go.dev/net/http#Transport.MaxIdleConns
Default: 50.

- `max_idle_conns_per_host [integer]`
Sets the maximum number of idle connections to keep open _per host_. See https://pkg.go.dev/net/http#Transport.MaxIdleConnsPerHost
Default: 0, which uses Go's default of 2.

### Other

Expand Down
50 changes: 44 additions & 6 deletions caddyfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,16 @@ func EncodeAuthCredentials(user, pass string) (result []byte) {

// UnmarshalCaddyfile unmarshals Caddyfile tokens into h.
func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if !d.Next() {
return d.ArgErr()
}
d.Next() // consume directive name

args := d.RemainingArgs()
if len(args) > 0 {
return d.ArgErr()
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
subdirective := d.Val()
args := d.RemainingArgs()
switch subdirective {
switch d.Val() {
case "basic_auth":
args := d.RemainingArgs()
if len(args) != 2 {
return d.ArgErr()
}
Expand All @@ -58,15 +56,19 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
h.AuthCredentials = [][]byte{}
}
h.AuthCredentials = append(h.AuthCredentials, EncodeAuthCredentials(args[0], args[1]))

case "hosts":
args := d.RemainingArgs()
if len(args) == 0 {
return d.ArgErr()
}
if len(h.Hosts) != 0 {
return d.Err("hosts subdirective specified twice")
}
h.Hosts = caddyhttp.MatchHost(args)

case "ports":
args := d.RemainingArgs()
if len(args) == 0 {
return d.ArgErr()
}
Expand All @@ -81,17 +83,23 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
h.AllowedPorts[i] = intPort
}

case "hide_ip":
args := d.RemainingArgs()
if len(args) != 0 {
return d.ArgErr()
}
h.HideIP = true

case "hide_via":
args := d.RemainingArgs()
if len(args) != 0 {
return d.ArgErr()
}
h.HideVia = true

case "probe_resistance":
args := d.RemainingArgs()
if len(args) > 1 {
return d.ArgErr()
}
Expand All @@ -104,7 +112,9 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
} else {
h.ProbeResistance = &ProbeResistance{}
}

case "serve_pac":
args := d.RemainingArgs()
if len(args) > 1 {
return d.ArgErr()
}
Expand All @@ -119,7 +129,9 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
} else {
h.PACPath = "/proxy.pac"
}

case "dial_timeout":
args := d.RemainingArgs()
if len(args) != 1 {
return d.ArgErr()
}
Expand All @@ -131,14 +143,39 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.Err("dial_timeout cannot be negative.")
}
h.DialTimeout = caddy.Duration(timeout)

case "max_idle_conns":
args := d.RemainingArgs()
if len(args) != 1 {
return d.ArgErr()
}
val, err := strconv.Atoi(args[0])
if err != nil {
return d.ArgErr()
}
h.MaxIdleConns = val

case "max_idle_conns_per_host":
args := d.RemainingArgs()
if len(args) != 1 {
return d.ArgErr()
}
val, err := strconv.Atoi(args[0])
if err != nil {
return d.ArgErr()
}
h.MaxIdleConnsPerHost = val

case "upstream":
args := d.RemainingArgs()
if len(args) != 1 {
return d.ArgErr()
}
if h.Upstream != "" {
return d.Err("upstream directive specified more than once")
}
h.Upstream = args[0]

case "acl":
for nesting := d.Nesting(); d.NextBlock(nesting); {
aclDirective := d.Val()
Expand Down Expand Up @@ -179,6 +216,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
ar := ACLRule{Subjects: ruleSubjects, Allow: aclAllow}
h.ACL = append(h.ACL, ar)
}

default:
return d.ArgErr()
}
Expand Down
23 changes: 22 additions & 1 deletion forwardproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ type Handler struct {
// How long to wait before timing out initial TCP connections.
DialTimeout caddy.Duration `json:"dial_timeout,omitempty"`

// Maximum number of idle connections to keep open, globally.
// Default: 50. Set to -1 for no limit.
// See https://pkg.go.dev/net/http#Transport.MaxIdleConns
MaxIdleConns int `json:"max_idle_conns,omitempty"`

// Maximum number of idle connections to keep open per host.
// Default: 0, which uses Go's default of 2.
// See https://pkg.go.dev/net/http#Transport.MaxIdleConnsPerHost
MaxIdleConnsPerHost int `json:"max_idle_conns_per_host,omitempty"`

// Optionally configure an upstream proxy to use.
Upstream string `json:"upstream,omitempty"`

Expand Down Expand Up @@ -111,9 +121,20 @@ func (h *Handler) Provision(ctx caddy.Context) error {
h.DialTimeout = caddy.Duration(30 * time.Second)
}

// Default to 50 max idle connections if not specified,
// or no limit if -1 is specified.
maxIdleConns := h.MaxIdleConns
if maxIdleConns == 0 {
maxIdleConns = 50
}
if maxIdleConns < 0 {
maxIdleConns = 0
}

h.httpTransport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
MaxIdleConns: 50,
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: h.MaxIdleConnsPerHost,
IdleConnTimeout: 60 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
}
Expand Down

0 comments on commit 4b5025f

Please sign in to comment.