go.etcd.io/etcd@v3.3.27+incompatible/proxy/httpproxy/reverse.go (about)

     1  // Copyright 2015 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package httpproxy
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  	"net"
    24  	"net/http"
    25  	"net/url"
    26  	"strings"
    27  	"sync/atomic"
    28  	"time"
    29  
    30  	"github.com/coreos/etcd/etcdserver/api/v2http/httptypes"
    31  	"github.com/coreos/pkg/capnslog"
    32  )
    33  
    34  var (
    35  	plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "proxy/httpproxy")
    36  
    37  	// Hop-by-hop headers. These are removed when sent to the backend.
    38  	// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
    39  	// This list of headers borrowed from stdlib httputil.ReverseProxy
    40  	singleHopHeaders = []string{
    41  		"Connection",
    42  		"Keep-Alive",
    43  		"Proxy-Authenticate",
    44  		"Proxy-Authorization",
    45  		"Te", // canonicalized version of "TE"
    46  		"Trailers",
    47  		"Transfer-Encoding",
    48  		"Upgrade",
    49  	}
    50  )
    51  
    52  func removeSingleHopHeaders(hdrs *http.Header) {
    53  	for _, h := range singleHopHeaders {
    54  		hdrs.Del(h)
    55  	}
    56  }
    57  
    58  type reverseProxy struct {
    59  	director  *director
    60  	transport http.RoundTripper
    61  }
    62  
    63  func (p *reverseProxy) ServeHTTP(rw http.ResponseWriter, clientreq *http.Request) {
    64  	reportIncomingRequest(clientreq)
    65  	proxyreq := new(http.Request)
    66  	*proxyreq = *clientreq
    67  	startTime := time.Now()
    68  
    69  	var (
    70  		proxybody []byte
    71  		err       error
    72  	)
    73  
    74  	if clientreq.Body != nil {
    75  		proxybody, err = ioutil.ReadAll(clientreq.Body)
    76  		if err != nil {
    77  			msg := fmt.Sprintf("failed to read request body: %v", err)
    78  			plog.Println(msg)
    79  			e := httptypes.NewHTTPError(http.StatusInternalServerError, "httpproxy: "+msg)
    80  			if we := e.WriteTo(rw); we != nil {
    81  				plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr)
    82  			}
    83  			return
    84  		}
    85  	}
    86  
    87  	// deep-copy the headers, as these will be modified below
    88  	proxyreq.Header = make(http.Header)
    89  	copyHeader(proxyreq.Header, clientreq.Header)
    90  
    91  	normalizeRequest(proxyreq)
    92  	removeSingleHopHeaders(&proxyreq.Header)
    93  	maybeSetForwardedFor(proxyreq)
    94  
    95  	endpoints := p.director.endpoints()
    96  	if len(endpoints) == 0 {
    97  		msg := "zero endpoints currently available"
    98  		reportRequestDropped(clientreq, zeroEndpoints)
    99  
   100  		// TODO: limit the rate of the error logging.
   101  		plog.Println(msg)
   102  		e := httptypes.NewHTTPError(http.StatusServiceUnavailable, "httpproxy: "+msg)
   103  		if we := e.WriteTo(rw); we != nil {
   104  			plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr)
   105  		}
   106  		return
   107  	}
   108  
   109  	var requestClosed int32
   110  	completeCh := make(chan bool, 1)
   111  	closeNotifier, ok := rw.(http.CloseNotifier)
   112  	ctx, cancel := context.WithCancel(context.Background())
   113  	proxyreq = proxyreq.WithContext(ctx)
   114  	defer cancel()
   115  	if ok {
   116  		closeCh := closeNotifier.CloseNotify()
   117  		go func() {
   118  			select {
   119  			case <-closeCh:
   120  				atomic.StoreInt32(&requestClosed, 1)
   121  				plog.Printf("client %v closed request prematurely", clientreq.RemoteAddr)
   122  				cancel()
   123  			case <-completeCh:
   124  			}
   125  		}()
   126  
   127  		defer func() {
   128  			completeCh <- true
   129  		}()
   130  	}
   131  
   132  	var res *http.Response
   133  
   134  	for _, ep := range endpoints {
   135  		if proxybody != nil {
   136  			proxyreq.Body = ioutil.NopCloser(bytes.NewBuffer(proxybody))
   137  		}
   138  		redirectRequest(proxyreq, ep.URL)
   139  
   140  		res, err = p.transport.RoundTrip(proxyreq)
   141  		if atomic.LoadInt32(&requestClosed) == 1 {
   142  			return
   143  		}
   144  		if err != nil {
   145  			reportRequestDropped(clientreq, failedSendingRequest)
   146  			plog.Printf("failed to direct request to %s: %v", ep.URL.String(), err)
   147  			ep.Failed()
   148  			continue
   149  		}
   150  
   151  		break
   152  	}
   153  
   154  	if res == nil {
   155  		// TODO: limit the rate of the error logging.
   156  		msg := fmt.Sprintf("unable to get response from %d endpoint(s)", len(endpoints))
   157  		reportRequestDropped(clientreq, failedGettingResponse)
   158  		plog.Println(msg)
   159  		e := httptypes.NewHTTPError(http.StatusBadGateway, "httpproxy: "+msg)
   160  		if we := e.WriteTo(rw); we != nil {
   161  			plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr)
   162  		}
   163  		return
   164  	}
   165  
   166  	defer res.Body.Close()
   167  	reportRequestHandled(clientreq, res, startTime)
   168  	removeSingleHopHeaders(&res.Header)
   169  	copyHeader(rw.Header(), res.Header)
   170  
   171  	rw.WriteHeader(res.StatusCode)
   172  	io.Copy(rw, res.Body)
   173  }
   174  
   175  func copyHeader(dst, src http.Header) {
   176  	for k, vv := range src {
   177  		for _, v := range vv {
   178  			dst.Add(k, v)
   179  		}
   180  	}
   181  }
   182  
   183  func redirectRequest(req *http.Request, loc url.URL) {
   184  	req.URL.Scheme = loc.Scheme
   185  	req.URL.Host = loc.Host
   186  }
   187  
   188  func normalizeRequest(req *http.Request) {
   189  	req.Proto = "HTTP/1.1"
   190  	req.ProtoMajor = 1
   191  	req.ProtoMinor = 1
   192  	req.Close = false
   193  }
   194  
   195  func maybeSetForwardedFor(req *http.Request) {
   196  	clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
   197  	if err != nil {
   198  		return
   199  	}
   200  
   201  	// If we aren't the first proxy retain prior
   202  	// X-Forwarded-For information as a comma+space
   203  	// separated list and fold multiple headers into one.
   204  	if prior, ok := req.Header["X-Forwarded-For"]; ok {
   205  		clientIP = strings.Join(prior, ", ") + ", " + clientIP
   206  	}
   207  	req.Header.Set("X-Forwarded-For", clientIP)
   208  }