go.etcd.io/etcd@v3.3.27+incompatible/contrib/raftexample/httpapi.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 main 16 17 import ( 18 "io/ioutil" 19 "log" 20 "net/http" 21 "strconv" 22 23 "github.com/coreos/etcd/raft/raftpb" 24 ) 25 26 // Handler for a http based key-value store backed by raft 27 type httpKVAPI struct { 28 store *kvstore 29 confChangeC chan<- raftpb.ConfChange 30 } 31 32 func (h *httpKVAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) { 33 key := r.RequestURI 34 switch { 35 case r.Method == "PUT": 36 v, err := ioutil.ReadAll(r.Body) 37 if err != nil { 38 log.Printf("Failed to read on PUT (%v)\n", err) 39 http.Error(w, "Failed on PUT", http.StatusBadRequest) 40 return 41 } 42 43 h.store.Propose(key, string(v)) 44 45 // Optimistic-- no waiting for ack from raft. Value is not yet 46 // committed so a subsequent GET on the key may return old value 47 w.WriteHeader(http.StatusNoContent) 48 case r.Method == "GET": 49 if v, ok := h.store.Lookup(key); ok { 50 w.Write([]byte(v)) 51 } else { 52 http.Error(w, "Failed to GET", http.StatusNotFound) 53 } 54 case r.Method == "POST": 55 url, err := ioutil.ReadAll(r.Body) 56 if err != nil { 57 log.Printf("Failed to read on POST (%v)\n", err) 58 http.Error(w, "Failed on POST", http.StatusBadRequest) 59 return 60 } 61 62 nodeId, err := strconv.ParseUint(key[1:], 0, 64) 63 if err != nil { 64 log.Printf("Failed to convert ID for conf change (%v)\n", err) 65 http.Error(w, "Failed on POST", http.StatusBadRequest) 66 return 67 } 68 69 cc := raftpb.ConfChange{ 70 Type: raftpb.ConfChangeAddNode, 71 NodeID: nodeId, 72 Context: url, 73 } 74 h.confChangeC <- cc 75 76 // As above, optimistic that raft will apply the conf change 77 w.WriteHeader(http.StatusNoContent) 78 case r.Method == "DELETE": 79 nodeId, err := strconv.ParseUint(key[1:], 0, 64) 80 if err != nil { 81 log.Printf("Failed to convert ID for conf change (%v)\n", err) 82 http.Error(w, "Failed on DELETE", http.StatusBadRequest) 83 return 84 } 85 86 cc := raftpb.ConfChange{ 87 Type: raftpb.ConfChangeRemoveNode, 88 NodeID: nodeId, 89 } 90 h.confChangeC <- cc 91 92 // As above, optimistic that raft will apply the conf change 93 w.WriteHeader(http.StatusNoContent) 94 default: 95 w.Header().Set("Allow", "PUT") 96 w.Header().Add("Allow", "GET") 97 w.Header().Add("Allow", "POST") 98 w.Header().Add("Allow", "DELETE") 99 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 100 } 101 } 102 103 // serveHttpKVAPI starts a key-value server with a GET/PUT API and listens. 104 func serveHttpKVAPI(kv *kvstore, port int, confChangeC chan<- raftpb.ConfChange, errorC <-chan error) { 105 srv := http.Server{ 106 Addr: ":" + strconv.Itoa(port), 107 Handler: &httpKVAPI{ 108 store: kv, 109 confChangeC: confChangeC, 110 }, 111 } 112 go func() { 113 if err := srv.ListenAndServe(); err != nil { 114 log.Fatal(err) 115 } 116 }() 117 118 // exit when raft goes down 119 if err, ok := <-errorC; ok { 120 log.Fatal(err) 121 } 122 }