diff --git a/go/vt/vtctld/api.go b/go/vt/vtctld/api.go index 3bb7912a60f..ede03e98815 100644 --- a/go/vt/vtctld/api.go +++ b/go/vt/vtctld/api.go @@ -29,6 +29,7 @@ import ( "golang.org/x/net/context" "vitess.io/vitess/go/acl" + "vitess.io/vitess/go/netutil" "vitess.io/vitess/go/vt/log" "vitess.io/vitess/go/vt/logutil" "vitess.io/vitess/go/vt/schemamanager" @@ -48,6 +49,7 @@ import ( var ( localCell = flag.String("cell", "", "cell to use") showTopologyCRUD = flag.Bool("vtctld_show_topology_crud", true, "Controls the display of the CRUD topology actions in the vtctld UI.") + proxyTablets = flag.Bool("proxy_tablets", false, "Setting this true will make vtctld proxy the tablet status instead of redirecting to them") ) // This file implements a REST-style API for the vtctld web interface. @@ -406,13 +408,10 @@ func initAPI(ctx context.Context, ts *topo.Server, actions *ActionRepository, re MysqlPort: t.MysqlPort, MasterTermStartTime: t.MasterTermStartTime, } - tabletPath = fmt.Sprintf("http://%s:%d", t.Tablet.Hostname, t.PortMap["vt"]) if *proxyTablets { - tabletID := fmt.Sprintf("%s-%d", t.Tablet.Alias.Cell, t.Tablet.Alias.Uid) - addRemote(tabletID, tabletPath) - tab.URL = fmt.Sprintf("/vttablet/%s-%d", t.Tablet.Alias.Cell, t.Tablet.Alias.Uid) + tab.URL = fmt.Sprintf("/vttablet/%s-%d", t.Alias.Cell, t.Alias.Uid) } else { - tab.URL = tabletPath + tab.URL = "http://" + netutil.JoinHostPort(t.Hostname, t.PortMap["vt"]) } return tab, nil }) diff --git a/go/vt/vtctld/redirection.go b/go/vt/vtctld/redirection.go new file mode 100644 index 00000000000..02701e91f07 --- /dev/null +++ b/go/vt/vtctld/redirection.go @@ -0,0 +1,87 @@ +/* +Copyright 2020 The Vitess 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 vtctld + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "net/http/httputil" + "strings" + + "vitess.io/vitess/go/netutil" + "vitess.io/vitess/go/vt/log" + "vitess.io/vitess/go/vt/topo" + "vitess.io/vitess/go/vt/topo/topoproto" +) + +func initVTTabletRedirection(ts *topo.Server) { + http.HandleFunc("/vttablet/", func(w http.ResponseWriter, r *http.Request) { + splits := strings.SplitN(r.URL.Path, "/", 4) + if len(splits) < 4 { + log.Errorf("Invalid URL: %v", r.URL) + http.NotFound(w, r) + return + } + tabletID := splits[2] + tabletAlias, err := topoproto.ParseTabletAlias(tabletID) + if err != nil { + log.Errorf("Error parsting tablet alias %v: %v", tabletID, err) + http.NotFound(w, r) + return + } + tablet, err := ts.GetTablet(r.Context(), tabletAlias) + if err != nil { + log.Errorf("Error fetching tablet %v: %v", splits[2], err) + http.NotFound(w, r) + return + } + if tablet.Hostname == "" || tablet.PortMap["vt"] == 0 { + log.Errorf("Invalid host/port: %s %d", tablet.Hostname, tablet.PortMap["vt"]) + http.NotFound(w, r) + return + } + + rp := &httputil.ReverseProxy{} + rp.Director = func(req *http.Request) { + req.URL.Scheme = "http" + req.URL.Host = netutil.JoinHostPort(tablet.Hostname, tablet.PortMap["vt"]) + req.URL.Path = "/" + splits[3] + } + + prefixPath := fmt.Sprintf("/vttablet/%s/", tabletID) + rp.ModifyResponse = func(r *http.Response) error { + b, _ := ioutil.ReadAll(r.Body) + b = bytes.ReplaceAll(b, []byte(`href="/`), []byte(fmt.Sprintf(`href="%s`, prefixPath))) + b = bytes.ReplaceAll(b, []byte(`href=/`), []byte(fmt.Sprintf(`href=%s`, prefixPath))) + r.Body = ioutil.NopCloser(bytes.NewBuffer(b)) + r.Header["Content-Length"] = []string{fmt.Sprint(len(b))} + + // Don't forget redirects + loc := r.Header["Location"] + for i, v := range loc { + if strings.HasPrefix(v, "/") { + loc[i] = strings.Replace(v, "/", prefixPath, 1) + } + } + return nil + } + + rp.ServeHTTP(w, r) + }) +} diff --git a/go/vt/vtctld/vtctld.go b/go/vt/vtctld/vtctld.go index 4913ed5faa6..fe6c2347372 100644 --- a/go/vt/vtctld/vtctld.go +++ b/go/vt/vtctld/vtctld.go @@ -178,4 +178,7 @@ func InitVtctld(ts *topo.Server) { // Init workflow manager. initWorkflowManager(ts) + + // Setup reverse proxy for all vttablets through /vttablet/. + initVTTabletRedirection(ts) } diff --git a/go/vt/vtctld/vttablet_proxy.go b/go/vt/vtctld/vttablet_proxy.go deleted file mode 100644 index b9f4a6e8f1d..00000000000 --- a/go/vt/vtctld/vttablet_proxy.go +++ /dev/null @@ -1,81 +0,0 @@ -/* -Copyright 2020 The Vitess 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 vtctld - -import ( - "bytes" - "flag" - "fmt" - "io/ioutil" - "net/http" - "net/http/httputil" - "net/url" - "strings" - "sync" - - "vitess.io/vitess/go/vt/log" -) - -var ( - proxyTablets = flag.Bool("proxy_tablets", false, "Setting this true will make vtctld proxy the tablet status instead of redirecting to them") - - proxyMu sync.Mutex - remotes = make(map[string]*httputil.ReverseProxy) -) - -func addRemote(tabletID, path string) { - u, err := url.Parse(path) - if err != nil { - log.Errorf("Error parsing URL %v: %v", path, err) - return - } - - proxyMu.Lock() - defer proxyMu.Unlock() - if _, ok := remotes[tabletID]; ok { - return - } - - rp := &httputil.ReverseProxy{} - rp.Director = func(req *http.Request) { - splits := strings.SplitN(req.URL.Path, "/", 4) - if len(splits) < 4 { - return - } - req.URL.Scheme = u.Scheme - req.URL.Host = u.Host - req.URL.Path = "/" + splits[3] - } - - prefixPath := fmt.Sprintf("/vttablet/%s/", tabletID) - - rp.ModifyResponse = func(r *http.Response) error { - b, _ := ioutil.ReadAll(r.Body) - b = bytes.ReplaceAll(b, []byte(`href="/`), []byte(fmt.Sprintf(`href="%s`, prefixPath))) - b = bytes.ReplaceAll(b, []byte(`href=/`), []byte(fmt.Sprintf(`href=%s`, prefixPath))) - r.Body = ioutil.NopCloser(bytes.NewBuffer(b)) - r.Header["Content-Length"] = []string{fmt.Sprint(len(b))} - // Don't forget redirects - loc := r.Header["Location"] - for i, v := range loc { - loc[i] = strings.Replace(v, "/", prefixPath, 1) - } - return nil - } - http.Handle(prefixPath, rp) - remotes[tabletID] = rp -}