github.com/soulinfo/etcd@v0.1.2-0.20130922053317-3a0a8c89e859/etcd_handlers.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "net/http" 6 "strconv" 7 "strings" 8 9 etcdErr "github.com/coreos/etcd/error" 10 "github.com/coreos/etcd/store" 11 "github.com/coreos/go-raft" 12 ) 13 14 //------------------------------------------------------------------- 15 // Handlers to handle etcd-store related request via etcd url 16 //------------------------------------------------------------------- 17 18 func NewEtcdMuxer() *http.ServeMux { 19 // external commands 20 etcdMux := http.NewServeMux() 21 etcdMux.Handle("/"+version+"/keys/", errorHandler(Multiplexer)) 22 etcdMux.Handle("/"+version+"/watch/", errorHandler(WatchHttpHandler)) 23 etcdMux.Handle("/"+version+"/leader", errorHandler(LeaderHttpHandler)) 24 etcdMux.Handle("/"+version+"/machines", errorHandler(MachinesHttpHandler)) 25 etcdMux.Handle("/"+version+"/stats", errorHandler(StatsHttpHandler)) 26 etcdMux.Handle("/version", errorHandler(VersionHttpHandler)) 27 etcdMux.HandleFunc("/test/", TestHttpHandler) 28 return etcdMux 29 } 30 31 type errorHandler func(http.ResponseWriter, *http.Request) error 32 33 // addCorsHeader parses the request Origin header and loops through the user 34 // provided allowed origins and sets the Access-Control-Allow-Origin header if 35 // there is a match. 36 func addCorsHeader(w http.ResponseWriter, r *http.Request) { 37 val, ok := corsList["*"] 38 if val && ok { 39 w.Header().Add("Access-Control-Allow-Origin", "*") 40 return 41 } 42 43 requestOrigin := r.Header.Get("Origin") 44 val, ok = corsList[requestOrigin] 45 if val && ok { 46 w.Header().Add("Access-Control-Allow-Origin", requestOrigin) 47 return 48 } 49 } 50 51 func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 52 addCorsHeader(w, r) 53 if e := fn(w, r); e != nil { 54 if etcdErr, ok := e.(etcdErr.Error); ok { 55 debug("Return error: ", etcdErr.Error()) 56 etcdErr.Write(w) 57 } else { 58 http.Error(w, e.Error(), http.StatusInternalServerError) 59 } 60 } 61 } 62 63 // Multiplex GET/POST/DELETE request to corresponding handlers 64 func Multiplexer(w http.ResponseWriter, req *http.Request) error { 65 66 switch req.Method { 67 case "GET": 68 return GetHttpHandler(w, req) 69 case "POST": 70 return SetHttpHandler(w, req) 71 case "PUT": 72 return SetHttpHandler(w, req) 73 case "DELETE": 74 return DeleteHttpHandler(w, req) 75 default: 76 w.WriteHeader(http.StatusMethodNotAllowed) 77 return nil 78 } 79 } 80 81 //-------------------------------------- 82 // State sensitive handlers 83 // Set/Delete will dispatch to leader 84 //-------------------------------------- 85 86 // Set Command Handler 87 func SetHttpHandler(w http.ResponseWriter, req *http.Request) error { 88 key := req.URL.Path[len("/v1/keys/"):] 89 90 if store.CheckKeyword(key) { 91 return etcdErr.NewError(400, "Set") 92 } 93 94 debugf("[recv] POST %v/v1/keys/%s [%s]", e.url, key, req.RemoteAddr) 95 96 req.ParseForm() 97 98 value := req.Form.Get("value") 99 100 if len(value) == 0 { 101 return etcdErr.NewError(200, "Set") 102 } 103 104 strDuration := req.Form.Get("ttl") 105 106 expireTime, err := durationToExpireTime(strDuration) 107 108 if err != nil { 109 return etcdErr.NewError(202, "Set") 110 } 111 112 if prevValueArr, ok := req.Form["prevValue"]; ok && len(prevValueArr) > 0 { 113 command := &TestAndSetCommand{ 114 Key: key, 115 Value: value, 116 PrevValue: prevValueArr[0], 117 ExpireTime: expireTime, 118 } 119 120 return dispatch(command, w, req, true) 121 122 } else { 123 command := &SetCommand{ 124 Key: key, 125 Value: value, 126 ExpireTime: expireTime, 127 } 128 129 return dispatch(command, w, req, true) 130 } 131 } 132 133 // Delete Handler 134 func DeleteHttpHandler(w http.ResponseWriter, req *http.Request) error { 135 key := req.URL.Path[len("/v1/keys/"):] 136 137 debugf("[recv] DELETE %v/v1/keys/%s [%s]", e.url, key, req.RemoteAddr) 138 139 command := &DeleteCommand{ 140 Key: key, 141 } 142 143 return dispatch(command, w, req, true) 144 } 145 146 // Dispatch the command to leader 147 func dispatch(c Command, w http.ResponseWriter, req *http.Request, etcd bool) error { 148 149 if r.State() == raft.Leader { 150 if body, err := r.Do(c); err != nil { 151 return err 152 } else { 153 if body == nil { 154 return etcdErr.NewError(300, "Empty result from raft") 155 } else { 156 body, _ := body.([]byte) 157 w.WriteHeader(http.StatusOK) 158 w.Write(body) 159 return nil 160 } 161 } 162 163 } else { 164 leader := r.Leader() 165 // current no leader 166 if leader == "" { 167 return etcdErr.NewError(300, "") 168 } 169 170 // tell the client where is the leader 171 path := req.URL.Path 172 173 var url string 174 175 if etcd { 176 etcdAddr, _ := nameToEtcdURL(leader) 177 url = etcdAddr + path 178 } else { 179 raftAddr, _ := nameToRaftURL(leader) 180 url = raftAddr + path 181 } 182 183 debugf("Redirect to %s", url) 184 185 http.Redirect(w, req, url, http.StatusTemporaryRedirect) 186 return nil 187 } 188 return etcdErr.NewError(300, "") 189 } 190 191 //-------------------------------------- 192 // State non-sensitive handlers 193 // will not dispatch to leader 194 // TODO: add sensitive version for these 195 // command? 196 //-------------------------------------- 197 198 // Handler to return the current leader's raft address 199 func LeaderHttpHandler(w http.ResponseWriter, req *http.Request) error { 200 leader := r.Leader() 201 202 if leader != "" { 203 w.WriteHeader(http.StatusOK) 204 raftURL, _ := nameToRaftURL(leader) 205 w.Write([]byte(raftURL)) 206 return nil 207 } else { 208 return etcdErr.NewError(301, "") 209 } 210 } 211 212 // Handler to return all the known machines in the current cluster 213 func MachinesHttpHandler(w http.ResponseWriter, req *http.Request) error { 214 machines := getMachines(nameToEtcdURL) 215 216 w.WriteHeader(http.StatusOK) 217 w.Write([]byte(strings.Join(machines, ", "))) 218 return nil 219 } 220 221 // Handler to return the current version of etcd 222 func VersionHttpHandler(w http.ResponseWriter, req *http.Request) error { 223 w.WriteHeader(http.StatusOK) 224 fmt.Fprintf(w, "etcd %s", releaseVersion) 225 return nil 226 } 227 228 // Handler to return the basic stats of etcd 229 func StatsHttpHandler(w http.ResponseWriter, req *http.Request) error { 230 w.WriteHeader(http.StatusOK) 231 w.Write(etcdStore.Stats()) 232 w.Write(r.Stats()) 233 return nil 234 } 235 236 // Get Handler 237 func GetHttpHandler(w http.ResponseWriter, req *http.Request) error { 238 key := req.URL.Path[len("/v1/keys/"):] 239 240 debugf("[recv] GET %s/v1/keys/%s [%s]", e.url, key, req.RemoteAddr) 241 242 command := &GetCommand{ 243 Key: key, 244 } 245 246 if body, err := command.Apply(r.Server); err != nil { 247 return err 248 } else { 249 body, _ := body.([]byte) 250 w.WriteHeader(http.StatusOK) 251 w.Write(body) 252 253 return nil 254 } 255 256 } 257 258 // Watch handler 259 func WatchHttpHandler(w http.ResponseWriter, req *http.Request) error { 260 key := req.URL.Path[len("/v1/watch/"):] 261 262 command := &WatchCommand{ 263 Key: key, 264 } 265 266 if req.Method == "GET" { 267 debugf("[recv] GET %s/watch/%s [%s]", e.url, key, req.RemoteAddr) 268 command.SinceIndex = 0 269 270 } else if req.Method == "POST" { 271 // watch from a specific index 272 273 debugf("[recv] POST %s/watch/%s [%s]", e.url, key, req.RemoteAddr) 274 content := req.FormValue("index") 275 276 sinceIndex, err := strconv.ParseUint(string(content), 10, 64) 277 if err != nil { 278 return etcdErr.NewError(203, "Watch From Index") 279 } 280 command.SinceIndex = sinceIndex 281 282 } else { 283 w.WriteHeader(http.StatusMethodNotAllowed) 284 return nil 285 } 286 287 if body, err := command.Apply(r.Server); err != nil { 288 return etcdErr.NewError(500, key) 289 } else { 290 w.WriteHeader(http.StatusOK) 291 292 body, _ := body.([]byte) 293 w.Write(body) 294 return nil 295 } 296 297 } 298 299 // TestHandler 300 func TestHttpHandler(w http.ResponseWriter, req *http.Request) { 301 testType := req.URL.Path[len("/test/"):] 302 303 if testType == "speed" { 304 directSet() 305 w.WriteHeader(http.StatusOK) 306 w.Write([]byte("speed test success")) 307 return 308 } 309 310 w.WriteHeader(http.StatusBadRequest) 311 }