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 }