mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			217 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			217 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2015 Matthew Holt and The Caddy Authors
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
package reverseproxy
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"net"
 | 
						|
	"strings"
 | 
						|
	"sync/atomic"
 | 
						|
 | 
						|
	"github.com/caddyserver/caddy/v2"
 | 
						|
)
 | 
						|
 | 
						|
// Host represents a remote host which can be proxied to.
 | 
						|
// Its methods must be safe for concurrent use.
 | 
						|
type Host interface {
 | 
						|
	// NumRequests returns the numnber of requests
 | 
						|
	// currently in process with the host.
 | 
						|
	NumRequests() int
 | 
						|
 | 
						|
	// Fails returns the count of recent failures.
 | 
						|
	Fails() int
 | 
						|
 | 
						|
	// Unhealthy returns true if the backend is unhealthy.
 | 
						|
	Unhealthy() bool
 | 
						|
 | 
						|
	// CountRequest atomically counts the given number of
 | 
						|
	// requests as currently in process with the host. The
 | 
						|
	// count should not go below 0.
 | 
						|
	CountRequest(int) error
 | 
						|
 | 
						|
	// CountFail atomically counts the given number of
 | 
						|
	// failures with the host. The count should not go
 | 
						|
	// below 0.
 | 
						|
	CountFail(int) error
 | 
						|
 | 
						|
	// SetHealthy atomically marks the host as either
 | 
						|
	// healthy (true) or unhealthy (false). If the given
 | 
						|
	// status is the same, this should be a no-op and
 | 
						|
	// return false. It returns true if the status was
 | 
						|
	// changed; i.e. if it is now different from before.
 | 
						|
	SetHealthy(bool) (bool, error)
 | 
						|
}
 | 
						|
 | 
						|
// UpstreamPool is a collection of upstreams.
 | 
						|
type UpstreamPool []*Upstream
 | 
						|
 | 
						|
// Upstream bridges this proxy's configuration to the
 | 
						|
// state of the backend host it is correlated with.
 | 
						|
type Upstream struct {
 | 
						|
	Host `json:"-"`
 | 
						|
 | 
						|
	Dial        string `json:"dial,omitempty"`
 | 
						|
	MaxRequests int    `json:"max_requests,omitempty"`
 | 
						|
 | 
						|
	// TODO: This could be really useful, to bind requests
 | 
						|
	// with certain properties to specific backends
 | 
						|
	// HeaderAffinity string
 | 
						|
	// IPAffinity     string
 | 
						|
 | 
						|
	healthCheckPolicy *PassiveHealthChecks
 | 
						|
	cb                CircuitBreaker
 | 
						|
	dialInfo          DialInfo
 | 
						|
}
 | 
						|
 | 
						|
// Available returns true if the remote host
 | 
						|
// is available to receive requests. This is
 | 
						|
// the method that should be used by selection
 | 
						|
// policies, etc. to determine if a backend
 | 
						|
// should be able to be sent a request.
 | 
						|
func (u *Upstream) Available() bool {
 | 
						|
	return u.Healthy() && !u.Full()
 | 
						|
}
 | 
						|
 | 
						|
// Healthy returns true if the remote host
 | 
						|
// is currently known to be healthy or "up".
 | 
						|
// It consults the circuit breaker, if any.
 | 
						|
func (u *Upstream) Healthy() bool {
 | 
						|
	healthy := !u.Host.Unhealthy()
 | 
						|
	if healthy && u.healthCheckPolicy != nil {
 | 
						|
		healthy = u.Host.Fails() < u.healthCheckPolicy.MaxFails
 | 
						|
	}
 | 
						|
	if healthy && u.cb != nil {
 | 
						|
		healthy = u.cb.OK()
 | 
						|
	}
 | 
						|
	return healthy
 | 
						|
}
 | 
						|
 | 
						|
// Full returns true if the remote host
 | 
						|
// cannot receive more requests at this time.
 | 
						|
func (u *Upstream) Full() bool {
 | 
						|
	return u.MaxRequests > 0 && u.Host.NumRequests() >= u.MaxRequests
 | 
						|
}
 | 
						|
 | 
						|
// upstreamHost is the basic, in-memory representation
 | 
						|
// of the state of a remote host. It implements the
 | 
						|
// Host interface.
 | 
						|
type upstreamHost struct {
 | 
						|
	numRequests int64 // must be first field to be 64-bit aligned on 32-bit systems (see https://golang.org/pkg/sync/atomic/#pkg-note-BUG)
 | 
						|
	fails       int64
 | 
						|
	unhealthy   int32
 | 
						|
}
 | 
						|
 | 
						|
// NumRequests returns the number of active requests to the upstream.
 | 
						|
func (uh *upstreamHost) NumRequests() int {
 | 
						|
	return int(atomic.LoadInt64(&uh.numRequests))
 | 
						|
}
 | 
						|
 | 
						|
// Fails returns the number of recent failures with the upstream.
 | 
						|
func (uh *upstreamHost) Fails() int {
 | 
						|
	return int(atomic.LoadInt64(&uh.fails))
 | 
						|
}
 | 
						|
 | 
						|
// Unhealthy returns whether the upstream is healthy.
 | 
						|
func (uh *upstreamHost) Unhealthy() bool {
 | 
						|
	return atomic.LoadInt32(&uh.unhealthy) == 1
 | 
						|
}
 | 
						|
 | 
						|
// CountRequest mutates the active request count by
 | 
						|
// delta. It returns an error if the adjustment fails.
 | 
						|
func (uh *upstreamHost) CountRequest(delta int) error {
 | 
						|
	result := atomic.AddInt64(&uh.numRequests, int64(delta))
 | 
						|
	if result < 0 {
 | 
						|
		return fmt.Errorf("count below 0: %d", result)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// CountFail mutates the recent failures count by
 | 
						|
// delta. It returns an error if the adjustment fails.
 | 
						|
func (uh *upstreamHost) CountFail(delta int) error {
 | 
						|
	result := atomic.AddInt64(&uh.fails, int64(delta))
 | 
						|
	if result < 0 {
 | 
						|
		return fmt.Errorf("count below 0: %d", result)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// SetHealthy sets the upstream has healthy or unhealthy
 | 
						|
// and returns true if the value was different from before,
 | 
						|
// or an error if the adjustment failed.
 | 
						|
func (uh *upstreamHost) SetHealthy(healthy bool) (bool, error) {
 | 
						|
	var unhealthy, compare int32 = 1, 0
 | 
						|
	if healthy {
 | 
						|
		unhealthy, compare = 0, 1
 | 
						|
	}
 | 
						|
	swapped := atomic.CompareAndSwapInt32(&uh.unhealthy, compare, unhealthy)
 | 
						|
	return swapped, nil
 | 
						|
}
 | 
						|
 | 
						|
// DialInfo contains information needed to dial a
 | 
						|
// connection to an upstream host. This information
 | 
						|
// may be different than that which is represented
 | 
						|
// in a URL (for example, unix sockets don't have
 | 
						|
// a host that can be represented in a URL, but
 | 
						|
// they certainly have a network name and address).
 | 
						|
type DialInfo struct {
 | 
						|
	// The network to use. This should be one of the
 | 
						|
	// values that is accepted by net.Dial:
 | 
						|
	// https://golang.org/pkg/net/#Dial
 | 
						|
	Network string
 | 
						|
 | 
						|
	// The address to dial. Follows the same
 | 
						|
	// semantics and rules as net.Dial.
 | 
						|
	Address string
 | 
						|
 | 
						|
	// Host and Port are components of Address,
 | 
						|
	// pre-split for convenience.
 | 
						|
	Host, Port string
 | 
						|
}
 | 
						|
 | 
						|
// NewDialInfo creates and populates a DialInfo
 | 
						|
// for the given network and address. It splits
 | 
						|
// the address into host and port values if the
 | 
						|
// network type supports them, or uses the whole
 | 
						|
// address as the port if splitting fails.
 | 
						|
func NewDialInfo(network, address string) DialInfo {
 | 
						|
	var addrHost, addrPort string
 | 
						|
	if !strings.Contains(network, "unix") {
 | 
						|
		var err error
 | 
						|
		addrHost, addrPort, err = net.SplitHostPort(address)
 | 
						|
		if err != nil {
 | 
						|
			addrHost = address // assume there was no port
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return DialInfo{network, address, addrHost, addrPort}
 | 
						|
}
 | 
						|
 | 
						|
// String returns the Caddy network address form
 | 
						|
// by joining the network and address with a
 | 
						|
// forward slash.
 | 
						|
func (di DialInfo) String() string {
 | 
						|
	return di.Network + "/" + di.Address
 | 
						|
}
 | 
						|
 | 
						|
// DialInfoCtxKey is used to store a DialInfo
 | 
						|
// in a context.Context.
 | 
						|
const DialInfoCtxKey = caddy.CtxKey("dial_info")
 | 
						|
 | 
						|
// hosts is the global repository for hosts that are
 | 
						|
// currently in use by active configuration(s). This
 | 
						|
// allows the state of remote hosts to be preserved
 | 
						|
// through config reloads.
 | 
						|
var hosts = caddy.NewUsagePool()
 |