go.etcd.io/etcd@v3.3.27+incompatible/lease/leasehttp/http.go (about) 1 // Copyright 2016 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 leasehttp 16 17 import ( 18 "bytes" 19 "context" 20 "errors" 21 "fmt" 22 "io/ioutil" 23 "net/http" 24 "time" 25 26 pb "github.com/coreos/etcd/etcdserver/etcdserverpb" 27 "github.com/coreos/etcd/lease" 28 "github.com/coreos/etcd/lease/leasepb" 29 "github.com/coreos/etcd/pkg/httputil" 30 ) 31 32 var ( 33 LeasePrefix = "/leases" 34 LeaseInternalPrefix = "/leases/internal" 35 applyTimeout = time.Second 36 ErrLeaseHTTPTimeout = errors.New("waiting for node to catch up its applied index has timed out") 37 ) 38 39 // NewHandler returns an http Handler for lease renewals 40 func NewHandler(l lease.Lessor, waitch func() <-chan struct{}) http.Handler { 41 return &leaseHandler{l, waitch} 42 } 43 44 type leaseHandler struct { 45 l lease.Lessor 46 waitch func() <-chan struct{} 47 } 48 49 func (h *leaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 50 if r.Method != "POST" { 51 http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) 52 return 53 } 54 55 b, err := ioutil.ReadAll(r.Body) 56 if err != nil { 57 http.Error(w, "error reading body", http.StatusBadRequest) 58 return 59 } 60 61 var v []byte 62 switch r.URL.Path { 63 case LeasePrefix: 64 lreq := pb.LeaseKeepAliveRequest{} 65 if err := lreq.Unmarshal(b); err != nil { 66 http.Error(w, "error unmarshalling request", http.StatusBadRequest) 67 return 68 } 69 select { 70 case <-h.waitch(): 71 case <-time.After(applyTimeout): 72 http.Error(w, ErrLeaseHTTPTimeout.Error(), http.StatusRequestTimeout) 73 return 74 } 75 ttl, err := h.l.Renew(lease.LeaseID(lreq.ID)) 76 if err != nil { 77 if err == lease.ErrLeaseNotFound { 78 http.Error(w, err.Error(), http.StatusNotFound) 79 return 80 } 81 82 http.Error(w, err.Error(), http.StatusBadRequest) 83 return 84 } 85 // TODO: fill out ResponseHeader 86 resp := &pb.LeaseKeepAliveResponse{ID: lreq.ID, TTL: ttl} 87 v, err = resp.Marshal() 88 if err != nil { 89 http.Error(w, err.Error(), http.StatusInternalServerError) 90 return 91 } 92 93 case LeaseInternalPrefix: 94 lreq := leasepb.LeaseInternalRequest{} 95 if err := lreq.Unmarshal(b); err != nil { 96 http.Error(w, "error unmarshalling request", http.StatusBadRequest) 97 return 98 } 99 select { 100 case <-h.waitch(): 101 case <-time.After(applyTimeout): 102 http.Error(w, ErrLeaseHTTPTimeout.Error(), http.StatusRequestTimeout) 103 return 104 } 105 l := h.l.Lookup(lease.LeaseID(lreq.LeaseTimeToLiveRequest.ID)) 106 if l == nil { 107 http.Error(w, lease.ErrLeaseNotFound.Error(), http.StatusNotFound) 108 return 109 } 110 // TODO: fill out ResponseHeader 111 resp := &leasepb.LeaseInternalResponse{ 112 LeaseTimeToLiveResponse: &pb.LeaseTimeToLiveResponse{ 113 Header: &pb.ResponseHeader{}, 114 ID: lreq.LeaseTimeToLiveRequest.ID, 115 TTL: int64(l.Remaining().Seconds()), 116 GrantedTTL: l.TTL(), 117 }, 118 } 119 if lreq.LeaseTimeToLiveRequest.Keys { 120 ks := l.Keys() 121 kbs := make([][]byte, len(ks)) 122 for i := range ks { 123 kbs[i] = []byte(ks[i]) 124 } 125 resp.LeaseTimeToLiveResponse.Keys = kbs 126 } 127 128 v, err = resp.Marshal() 129 if err != nil { 130 http.Error(w, err.Error(), http.StatusInternalServerError) 131 return 132 } 133 134 default: 135 http.Error(w, fmt.Sprintf("unknown request path %q", r.URL.Path), http.StatusBadRequest) 136 return 137 } 138 139 w.Header().Set("Content-Type", "application/protobuf") 140 w.Write(v) 141 } 142 143 // RenewHTTP renews a lease at a given primary server. 144 // TODO: Batch request in future? 145 func RenewHTTP(ctx context.Context, id lease.LeaseID, url string, rt http.RoundTripper) (int64, error) { 146 // will post lreq protobuf to leader 147 lreq, err := (&pb.LeaseKeepAliveRequest{ID: int64(id)}).Marshal() 148 if err != nil { 149 return -1, err 150 } 151 152 cc := &http.Client{Transport: rt} 153 req, err := http.NewRequest("POST", url, bytes.NewReader(lreq)) 154 if err != nil { 155 return -1, err 156 } 157 req.Header.Set("Content-Type", "application/protobuf") 158 req.Cancel = ctx.Done() 159 160 resp, err := cc.Do(req) 161 if err != nil { 162 return -1, err 163 } 164 b, err := readResponse(resp) 165 if err != nil { 166 return -1, err 167 } 168 169 if resp.StatusCode == http.StatusRequestTimeout { 170 return -1, ErrLeaseHTTPTimeout 171 } 172 173 if resp.StatusCode == http.StatusNotFound { 174 return -1, lease.ErrLeaseNotFound 175 } 176 177 if resp.StatusCode != http.StatusOK { 178 return -1, fmt.Errorf("lease: unknown error(%s)", string(b)) 179 } 180 181 lresp := &pb.LeaseKeepAliveResponse{} 182 if err := lresp.Unmarshal(b); err != nil { 183 return -1, fmt.Errorf(`lease: %v. data = "%s"`, err, string(b)) 184 } 185 if lresp.ID != int64(id) { 186 return -1, fmt.Errorf("lease: renew id mismatch") 187 } 188 return lresp.TTL, nil 189 } 190 191 // TimeToLiveHTTP retrieves lease information of the given lease ID. 192 func TimeToLiveHTTP(ctx context.Context, id lease.LeaseID, keys bool, url string, rt http.RoundTripper) (*leasepb.LeaseInternalResponse, error) { 193 // will post lreq protobuf to leader 194 lreq, err := (&leasepb.LeaseInternalRequest{ 195 LeaseTimeToLiveRequest: &pb.LeaseTimeToLiveRequest{ 196 ID: int64(id), 197 Keys: keys, 198 }, 199 }).Marshal() 200 if err != nil { 201 return nil, err 202 } 203 204 req, err := http.NewRequest("POST", url, bytes.NewReader(lreq)) 205 if err != nil { 206 return nil, err 207 } 208 req.Header.Set("Content-Type", "application/protobuf") 209 210 req = req.WithContext(ctx) 211 212 cc := &http.Client{Transport: rt} 213 var b []byte 214 // buffer errc channel so that errc don't block inside the go routinue 215 resp, err := cc.Do(req) 216 if err != nil { 217 return nil, err 218 } 219 b, err = readResponse(resp) 220 if err != nil { 221 return nil, err 222 } 223 if resp.StatusCode == http.StatusRequestTimeout { 224 return nil, ErrLeaseHTTPTimeout 225 } 226 if resp.StatusCode == http.StatusNotFound { 227 return nil, lease.ErrLeaseNotFound 228 } 229 if resp.StatusCode != http.StatusOK { 230 return nil, fmt.Errorf("lease: unknown error(%s)", string(b)) 231 } 232 233 lresp := &leasepb.LeaseInternalResponse{} 234 if err := lresp.Unmarshal(b); err != nil { 235 return nil, fmt.Errorf(`lease: %v. data = "%s"`, err, string(b)) 236 } 237 if lresp.LeaseTimeToLiveResponse.ID != int64(id) { 238 return nil, fmt.Errorf("lease: renew id mismatch") 239 } 240 return lresp, nil 241 } 242 243 func readResponse(resp *http.Response) (b []byte, err error) { 244 b, err = ioutil.ReadAll(resp.Body) 245 httputil.GracefulClose(resp) 246 return 247 }