@ -20,7 +20,6 @@ import ( 
			
		
	
		
			
				
						"fmt"  
			
		
	
		
			
				
						"net"  
			
		
	
		
			
				
						"net/http"  
			
		
	
		
			
				
						"net/url"  
			
		
	
		
			
				
						"regexp"  
			
		
	
		
			
				
						"strings"  
			
		
	
		
			
				
						"time"  
			
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
				@ -86,7 +85,18 @@ func (h *Handler) Provision(ctx caddy.Context) error { 
			
		
	
		
			
				
						}  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
						if  h . Transport  ==  nil  {  
			
		
	
		
			
				
							h . Transport  =  defaultTransport  
			
		
	
		
			
				
							t  :=  & HTTPTransport {  
			
		
	
		
			
				
								KeepAlive :  & KeepAlive {  
			
		
	
		
			
				
									ProbeInterval :    caddy . Duration ( 30  *  time . Second ) ,  
			
		
	
		
			
				
									IdleConnTimeout :  caddy . Duration ( 2  *  time . Minute ) ,  
			
		
	
		
			
				
								} ,  
			
		
	
		
			
				
								DialTimeout :  caddy . Duration ( 10  *  time . Second ) ,  
			
		
	
		
			
				
							}  
			
		
	
		
			
				
							err  :=  t . Provision ( ctx )  
			
		
	
		
			
				
							if  err  !=  nil  {  
			
		
	
		
			
				
								return  fmt . Errorf ( "provisioning default transport: %v" ,  err )  
			
		
	
		
			
				
							}  
			
		
	
		
			
				
							h . Transport  =  t  
			
		
	
		
			
				
						}  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
						if  h . LoadBalancing  ==  nil  {  
			
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
				@ -133,51 +143,65 @@ func (h *Handler) Provision(ctx caddy.Context) error { 
			
		
	
		
			
				
							go  h . activeHealthChecker ( )  
			
		
	
		
			
				
						}  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
						var  allUpstreams  [ ] * Upstream  
			
		
	
		
			
				
						for  _ ,  upstream  :=  range  h . Upstreams  {  
			
		
	
		
			
				
							upstream . cb  =  h . CB  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
							// url parser requires a scheme
  
			
		
	
		
			
				
							if  ! strings . Contains ( upstream . Address ,  "://" )  {  
			
		
	
		
			
				
								upstream . Address  =  "http://"  +  upstream . Address  
			
		
	
		
			
				
							}  
			
		
	
		
			
				
							u ,  err  :=  url . Parse ( upstream . Address )  
			
		
	
		
			
				
							// upstreams are allowed to map to only a single host,
  
			
		
	
		
			
				
							// but an upstream's address may semantically represent
  
			
		
	
		
			
				
							// multiple addresses, so make sure to handle each
  
			
		
	
		
			
				
							// one in turn based on this one upstream config
  
			
		
	
		
			
				
							network ,  addresses ,  err  :=  caddy . ParseNetworkAddress ( upstream . Dial )  
			
		
	
		
			
				
							if  err  !=  nil  {  
			
		
	
		
			
				
								return  fmt . Errorf ( "invalid upstream address %s: %v" ,  upstream . Address  ,  err )  
			
		
	
		
			
				
								return  fmt . Errorf ( "parsing dial address: %v" ,  err )  
			
		
	
		
			
				
							}  
			
		
	
		
			
				
							upstream . hostURL  =  u  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
							for  _ ,  addr  :=  range  addresses  {  
			
		
	
		
			
				
								// make a new upstream based on the original
  
			
		
	
		
			
				
								// that has a singular dial address
  
			
		
	
		
			
				
								upstreamCopy  :=  * upstream  
			
		
	
		
			
				
								upstreamCopy . dialInfo  =  DialInfo { network ,  addr }  
			
		
	
		
			
				
								upstreamCopy . Dial  =  upstreamCopy . dialInfo . String ( )  
			
		
	
		
			
				
								upstreamCopy . cb  =  h . CB  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
								// if host already exists from a current config,
  
			
		
	
		
			
				
								// use that instead; otherwise, add it
  
			
		
	
		
			
				
								// TODO: make hosts modular, so that their state can be distributed in enterprise for example
  
			
		
	
		
			
				
								// TODO: If distributed, the pool should be stored in storage...
  
			
		
	
		
			
				
								var  host  Host  =  new ( upstreamHost )  
			
		
	
		
			
				
							activeHost ,  loaded  :=  hosts . LoadOrStore ( u . String ( ) ,  host )  
			
		
	
		
			
				
								 activeHost ,  loaded  :=  hosts . LoadOrStore ( upstreamCopy . Dial  ,  host )  
			
		
	
		
			
				
								if  loaded  {  
			
		
	
		
			
				
									host  =  activeHost . ( Host )  
			
		
	
		
			
				
								}  
			
		
	
		
			
				
							upstream . Host  =  host  
			
		
	
		
			
				
								 upstreamCopy  . Host  =  host  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
								// if the passive health checker has a non-zero "unhealthy
  
			
		
	
		
			
				
								// request count" but the upstream has no MaxRequests set
  
			
		
	
		
			
				
								// (they are the same thing, but one is a default value for
  
			
		
	
		
			
				
								// for upstreams with a zero MaxRequests), copy the default
  
			
		
	
		
			
				
								// value into this upstream, since the value in the upstream
  
			
		
	
		
			
				
							// is what is used during availability checks
  
			
		
	
		
			
				
								 // (MaxRequests)  is what is used during availability checks
  
			
		
	
		
			
				
								if  h . HealthChecks  !=  nil  &&  
			
		
	
		
			
				
									h . HealthChecks . Passive  !=  nil  &&  
			
		
	
		
			
				
									h . HealthChecks . Passive . UnhealthyRequestCount  >  0  &&  
			
		
	
		
			
				
								upstream . MaxRequests  ==  0  {  
			
		
	
		
			
				
								upstream . MaxRequests  =  h . HealthChecks . Passive . UnhealthyRequestCount  
			
		
	
		
			
				
									 upstreamCopy  . MaxRequests  ==  0  {  
			
		
	
		
			
				
									 upstreamCopy  . MaxRequests  =  h . HealthChecks . Passive . UnhealthyRequestCount  
			
		
	
		
			
				
								}  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
							if  h . HealthChecks  !=  nil  {  
			
		
	
		
			
				
								// upstreams need independent access to the passive
  
			
		
	
		
			
				
								// health check policy so they can, you know, passively
  
			
		
	
		
			
				
								// do health checks
  
			
		
	
		
			
				
								upstream . healthCheckPolicy  =  h . HealthChecks . Passive  
			
		
	
		
			
				
								// health check policy because they run outside of the
  
			
		
	
		
			
				
								// scope of a request handler
  
			
		
	
		
			
				
								if  h . HealthChecks  !=  nil  {  
			
		
	
		
			
				
									upstreamCopy . healthCheckPolicy  =  h . HealthChecks . Passive  
			
		
	
		
			
				
								}  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
								allUpstreams  =  append ( allUpstreams ,  & upstreamCopy )  
			
		
	
		
			
				
							}  
			
		
	
		
			
				
						}  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
						// replace the unmarshaled upstreams (possible 1:many
  
			
		
	
		
			
				
						// address mapping) with our list, which is mapped 1:1,
  
			
		
	
		
			
				
						// thus may have expanded the original list
  
			
		
	
		
			
				
						h . Upstreams  =  allUpstreams  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
						return  nil  
			
		
	
		
			
				
					}  
			
		
	
		
			
				
					
 
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -192,7 +216,7 @@ func (h *Handler) Cleanup() error { 
			
		
	
		
			
				
					
 
			
		
	
		
			
				
						// remove hosts from our config from the pool
  
			
		
	
		
			
				
						for  _ ,  upstream  :=  range  h . Upstreams  {  
			
		
	
		
			
				
							hosts . Delete ( upstream . hostURL . String ( ) )  
			
		
	
		
			
				
							hosts . Delete ( upstream . dialInfo . String ( ) )  
			
		
	
		
			
				
						}  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
						return  nil  
			
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
				@ -222,6 +246,12 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht 
			
		
	
		
			
				
								continue  
			
		
	
		
			
				
							}  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
							// attach to the request information about how to dial the upstream;
  
			
		
	
		
			
				
							// this is necessary because the information cannot be sufficiently
  
			
		
	
		
			
				
							// or satisfactorily represented in a URL
  
			
		
	
		
			
				
							ctx  :=  context . WithValue ( r . Context ( ) ,  DialInfoCtxKey ,  upstream . dialInfo )  
			
		
	
		
			
				
							r  =  r . WithContext ( ctx )  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
							// proxy the request to that upstream
  
			
		
	
		
			
				
							proxyErr  =  h . reverseProxy ( w ,  r ,  upstream )  
			
		
	
		
			
				
							if  proxyErr  ==  nil  ||  proxyErr  ==  context . Canceled  {  
			
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
				@ -249,6 +279,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht 
			
		
	
		
			
				
					// This assumes that no mutations of the request are performed
  
			
		
	
		
			
				
					// by h during or after proxying.
  
			
		
	
		
			
				
					func  ( h  Handler )  prepareRequest ( req  * http . Request )  error  {  
			
		
	
		
			
				
						// as a special (but very common) case, if the transport
  
			
		
	
		
			
				
						// is HTTP, then ensure the request has the proper scheme
  
			
		
	
		
			
				
						// because incoming requests by default are lacking it
  
			
		
	
		
			
				
						if  req . URL . Scheme  ==  ""  {  
			
		
	
		
			
				
							req . URL . Scheme  =  "http"  
			
		
	
		
			
				
							if  ht ,  ok  :=  h . Transport . ( * HTTPTransport ) ;  ok  &&  ht . TLS  !=  nil  {  
			
		
	
		
			
				
								req . URL . Scheme  =  "https"  
			
		
	
		
			
				
							}  
			
		
	
		
			
				
						}  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
						if  req . ContentLength  ==  0  {  
			
		
	
		
			
				
							req . Body  =  nil  // Issue golang/go#16036: nil Body for http.Transport retries
  
			
		
	
		
			
				
						}  
			
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
				@ -433,14 +473,8 @@ func (h Handler) tryAgain(start time.Time, proxyErr error) bool { 
			
		
	
		
			
				
					// directRequest modifies only req.URL so that it points to the
  
			
		
	
		
			
				
					// given upstream host. It must modify ONLY the request URL.
  
			
		
	
		
			
				
					func  ( h  Handler )  directRequest ( req  * http . Request ,  upstream  * Upstream )  {  
			
		
	
		
			
				
						target  :=  upstream . hostURL  
			
		
	
		
			
				
						req . URL . Scheme  =  target . Scheme  
			
		
	
		
			
				
						req . URL . Host  =  target . Host  
			
		
	
		
			
				
						req . URL . Path  =  singleJoiningSlash ( target . Path ,  req . URL . Path )  // TODO: This might be a bug (if any part of the path was augmented from a previously-tried upstream; need to start from clean original path of request, same for query string!)
  
			
		
	
		
			
				
						if  target . RawQuery  ==  ""  ||  req . URL . RawQuery  ==  ""  {  
			
		
	
		
			
				
							req . URL . RawQuery  =  target . RawQuery  +  req . URL . RawQuery  
			
		
	
		
			
				
						}  else  {  
			
		
	
		
			
				
							req . URL . RawQuery  =  target . RawQuery  +  "&"  +  req . URL . RawQuery  
			
		
	
		
			
				
						if  req . URL . Host  ==  ""  {  
			
		
	
		
			
				
							req . URL . Host  =  upstream . dialInfo . Address  
			
		
	
		
			
				
						}  
			
		
	
		
			
				
					}