| // Copyright 2015 The etcd 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 httpproxy |
| |
| import ( |
| "encoding/json" |
| "net/http" |
| "strings" |
| "time" |
| |
| "golang.org/x/net/http2" |
| ) |
| |
| const ( |
| // DefaultMaxIdleConnsPerHost indicates the default maximum idle connection |
| // count maintained between proxy and each member. We set it to 128 to |
| // let proxy handle 128 concurrent requests in long term smoothly. |
| // If the number of concurrent requests is bigger than this value, |
| // proxy needs to create one new connection when handling each request in |
| // the delta, which is bad because the creation consumes resource and |
| // may eat up ephemeral ports. |
| DefaultMaxIdleConnsPerHost = 128 |
| ) |
| |
| // GetProxyURLs is a function which should return the current set of URLs to |
| // which client requests should be proxied. This function will be queried |
| // periodically by the proxy Handler to refresh the set of available |
| // backends. |
| type GetProxyURLs func() []string |
| |
| // NewHandler creates a new HTTP handler, listening on the given transport, |
| // which will proxy requests to an etcd cluster. |
| // The handler will periodically update its view of the cluster. |
| func NewHandler(t *http.Transport, urlsFunc GetProxyURLs, failureWait time.Duration, refreshInterval time.Duration) http.Handler { |
| if t.TLSClientConfig != nil { |
| // Enable http2, see Issue 5033. |
| err := http2.ConfigureTransport(t) |
| if err != nil { |
| plog.Infof("Error enabling Transport HTTP/2 support: %v", err) |
| } |
| } |
| |
| p := &reverseProxy{ |
| director: newDirector(urlsFunc, failureWait, refreshInterval), |
| transport: t, |
| } |
| |
| mux := http.NewServeMux() |
| mux.Handle("/", p) |
| mux.HandleFunc("/v2/config/local/proxy", p.configHandler) |
| |
| return mux |
| } |
| |
| // NewReadonlyHandler wraps the given HTTP handler to allow only GET requests |
| func NewReadonlyHandler(hdlr http.Handler) http.Handler { |
| readonly := readonlyHandlerFunc(hdlr) |
| return http.HandlerFunc(readonly) |
| } |
| |
| func readonlyHandlerFunc(next http.Handler) func(http.ResponseWriter, *http.Request) { |
| return func(w http.ResponseWriter, req *http.Request) { |
| if req.Method != "GET" { |
| w.WriteHeader(http.StatusNotImplemented) |
| return |
| } |
| |
| next.ServeHTTP(w, req) |
| } |
| } |
| |
| func (p *reverseProxy) configHandler(w http.ResponseWriter, r *http.Request) { |
| if !allowMethod(w, r.Method, "GET") { |
| return |
| } |
| |
| eps := p.director.endpoints() |
| epstr := make([]string, len(eps)) |
| for i, e := range eps { |
| epstr[i] = e.URL.String() |
| } |
| |
| proxyConfig := struct { |
| Endpoints []string `json:"endpoints"` |
| }{ |
| Endpoints: epstr, |
| } |
| |
| json.NewEncoder(w).Encode(proxyConfig) |
| } |
| |
| // allowMethod verifies that the given method is one of the allowed methods, |
| // and if not, it writes an error to w. A boolean is returned indicating |
| // whether or not the method is allowed. |
| func allowMethod(w http.ResponseWriter, m string, ms ...string) bool { |
| for _, meth := range ms { |
| if m == meth { |
| return true |
| } |
| } |
| w.Header().Set("Allow", strings.Join(ms, ",")) |
| http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |
| return false |
| } |