vitess.io/vitess@v0.16.2/go/vt/vtgate/api.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vtgate 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "net/http" 23 "strings" 24 25 "vitess.io/vitess/go/vt/discovery" 26 "vitess.io/vitess/go/vt/log" 27 ) 28 29 // This file implements a REST-style API for the vtgate web interface. 30 31 const ( 32 apiPrefix = "/api/" 33 34 jsonContentType = "application/json; charset=utf-8" 35 ) 36 37 func httpErrorf(w http.ResponseWriter, r *http.Request, format string, args ...any) { 38 errMsg := fmt.Sprintf(format, args...) 39 log.Errorf("HTTP error on %v: %v, request: %#v", r.URL.Path, errMsg, r) 40 http.Error(w, errMsg, http.StatusInternalServerError) 41 } 42 43 func handleAPI(apiPath string, handlerFunc func(w http.ResponseWriter, r *http.Request) error) { 44 http.HandleFunc(apiPrefix+apiPath, func(w http.ResponseWriter, r *http.Request) { 45 defer func() { 46 if x := recover(); x != nil { 47 httpErrorf(w, r, "uncaught panic: %v", x) 48 } 49 }() 50 if err := handlerFunc(w, r); err != nil { 51 httpErrorf(w, r, "%v", err) 52 } 53 }) 54 } 55 56 func handleCollection(collection string, getFunc func(*http.Request) (any, error)) { 57 handleAPI(collection+"/", func(w http.ResponseWriter, r *http.Request) error { 58 // Get the requested object. 59 obj, err := getFunc(r) 60 if err != nil { 61 return fmt.Errorf("can't get %v: %v", collection, err) 62 } 63 64 // JSON encode response. 65 data, err := json.MarshalIndent(obj, "", " ") 66 if err != nil { 67 return fmt.Errorf("cannot marshal data: %v", err) 68 } 69 w.Header().Set("Content-Type", jsonContentType) 70 w.Write(data) 71 return nil 72 }) 73 } 74 75 func getItemPath(url string) string { 76 // Strip API prefix. 77 if !strings.HasPrefix(url, apiPrefix) { 78 return "" 79 } 80 url = url[len(apiPrefix):] 81 82 // Strip collection name. 83 parts := strings.SplitN(url, "/", 2) 84 if len(parts) != 2 { 85 return "" 86 } 87 return parts[1] 88 } 89 90 func initAPI(hc discovery.HealthCheck) { 91 // Healthcheck real time status per (cell, keyspace, tablet type, metric). 92 handleCollection("health-check", func(r *http.Request) (any, error) { 93 cacheStatus := hc.CacheStatus() 94 95 itemPath := getItemPath(r.URL.Path) 96 if itemPath == "" { 97 return cacheStatus, nil 98 } 99 parts := strings.SplitN(itemPath, "/", 2) 100 collectionFilter := parts[0] 101 if collectionFilter == "" { 102 return cacheStatus, nil 103 } 104 if len(parts) != 2 { 105 return nil, fmt.Errorf("invalid health-check path: %q expected path: / or /cell/<cell> or /keyspace/<keyspace> or /tablet/mysql_hostname", itemPath) 106 } 107 value := parts[1] 108 109 switch collectionFilter { 110 case "cell": 111 { 112 filteredStatus := make(discovery.TabletsCacheStatusList, 0) 113 for _, tabletCacheStatus := range cacheStatus { 114 if tabletCacheStatus.Cell == value { 115 filteredStatus = append(filteredStatus, tabletCacheStatus) 116 } 117 } 118 return filteredStatus, nil 119 } 120 case "keyspace": 121 { 122 filteredStatus := make(discovery.TabletsCacheStatusList, 0) 123 for _, tabletCacheStatus := range cacheStatus { 124 if tabletCacheStatus.Target.Keyspace == value { 125 filteredStatus = append(filteredStatus, tabletCacheStatus) 126 } 127 } 128 return filteredStatus, nil 129 } 130 case "tablet": 131 { 132 // Return a _specific tablet_ 133 for _, tabletCacheStatus := range cacheStatus { 134 for _, tabletStats := range tabletCacheStatus.TabletsStats { 135 if tabletStats.Tablet.MysqlHostname == value { 136 return tabletStats, nil 137 } 138 } 139 } 140 } 141 } 142 return nil, fmt.Errorf("cannot find health for: %s", itemPath) 143 }) 144 }