go.etcd.io/etcd@v3.3.27+incompatible/proxy/httpproxy/proxy.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  	"encoding/json"
    19  	"net/http"
    20  	"strings"
    21  	"time"
    22  
    23  	"golang.org/x/net/http2"
    24  )
    25  
    26  const (
    27  	// DefaultMaxIdleConnsPerHost indicates the default maximum idle connection
    28  	// count maintained between proxy and each member. We set it to 128 to
    29  	// let proxy handle 128 concurrent requests in long term smoothly.
    30  	// If the number of concurrent requests is bigger than this value,
    31  	// proxy needs to create one new connection when handling each request in
    32  	// the delta, which is bad because the creation consumes resource and
    33  	// may eat up ephemeral ports.
    34  	DefaultMaxIdleConnsPerHost = 128
    35  )
    36  
    37  // GetProxyURLs is a function which should return the current set of URLs to
    38  // which client requests should be proxied. This function will be queried
    39  // periodically by the proxy Handler to refresh the set of available
    40  // backends.
    41  type GetProxyURLs func() []string
    42  
    43  // NewHandler creates a new HTTP handler, listening on the given transport,
    44  // which will proxy requests to an etcd cluster.
    45  // The handler will periodically update its view of the cluster.
    46  func NewHandler(t *http.Transport, urlsFunc GetProxyURLs, failureWait time.Duration, refreshInterval time.Duration) http.Handler {
    47  	if t.TLSClientConfig != nil {
    48  		// Enable http2, see Issue 5033.
    49  		err := http2.ConfigureTransport(t)
    50  		if err != nil {
    51  			plog.Infof("Error enabling Transport HTTP/2 support: %v", err)
    52  		}
    53  	}
    54  
    55  	p := &reverseProxy{
    56  		director:  newDirector(urlsFunc, failureWait, refreshInterval),
    57  		transport: t,
    58  	}
    59  
    60  	mux := http.NewServeMux()
    61  	mux.Handle("/", p)
    62  	mux.HandleFunc("/v2/config/local/proxy", p.configHandler)
    63  
    64  	return mux
    65  }
    66  
    67  // NewReadonlyHandler wraps the given HTTP handler to allow only GET requests
    68  func NewReadonlyHandler(hdlr http.Handler) http.Handler {
    69  	readonly := readonlyHandlerFunc(hdlr)
    70  	return http.HandlerFunc(readonly)
    71  }
    72  
    73  func readonlyHandlerFunc(next http.Handler) func(http.ResponseWriter, *http.Request) {
    74  	return func(w http.ResponseWriter, req *http.Request) {
    75  		if req.Method != "GET" {
    76  			w.WriteHeader(http.StatusNotImplemented)
    77  			return
    78  		}
    79  
    80  		next.ServeHTTP(w, req)
    81  	}
    82  }
    83  
    84  func (p *reverseProxy) configHandler(w http.ResponseWriter, r *http.Request) {
    85  	if !allowMethod(w, r.Method, "GET") {
    86  		return
    87  	}
    88  
    89  	eps := p.director.endpoints()
    90  	epstr := make([]string, len(eps))
    91  	for i, e := range eps {
    92  		epstr[i] = e.URL.String()
    93  	}
    94  
    95  	proxyConfig := struct {
    96  		Endpoints []string `json:"endpoints"`
    97  	}{
    98  		Endpoints: epstr,
    99  	}
   100  
   101  	json.NewEncoder(w).Encode(proxyConfig)
   102  }
   103  
   104  // allowMethod verifies that the given method is one of the allowed methods,
   105  // and if not, it writes an error to w.  A boolean is returned indicating
   106  // whether or not the method is allowed.
   107  func allowMethod(w http.ResponseWriter, m string, ms ...string) bool {
   108  	for _, meth := range ms {
   109  		if m == meth {
   110  			return true
   111  		}
   112  	}
   113  	w.Header().Set("Allow", strings.Join(ms, ","))
   114  	http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
   115  	return false
   116  }