1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package etcdhttp
16
17 import (
18 "encoding/json"
19 "expvar"
20 "fmt"
21 "net/http"
22
23 "go.etcd.io/etcd/api/v3/version"
24 "go.etcd.io/etcd/server/v3/etcdserver"
25 "go.etcd.io/etcd/server/v3/etcdserver/api"
26 "go.etcd.io/etcd/server/v3/etcdserver/api/v2error"
27 "go.etcd.io/etcd/server/v3/etcdserver/api/v2http/httptypes"
28 "go.uber.org/zap"
29 )
30
31 const (
32 configPath = "/config"
33 varsPath = "/debug/vars"
34 versionPath = "/version"
35 )
36
37
38
39 func HandleBasic(lg *zap.Logger, mux *http.ServeMux, server etcdserver.ServerPeer) {
40 mux.HandleFunc(varsPath, serveVars)
41 mux.HandleFunc(versionPath, versionHandler(server.Cluster(), serveVersion))
42 }
43
44 func versionHandler(c api.Cluster, fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
45 return func(w http.ResponseWriter, r *http.Request) {
46 v := c.Version()
47 if v != nil {
48 fn(w, r, v.String())
49 } else {
50 fn(w, r, "not_decided")
51 }
52 }
53 }
54
55 func serveVersion(w http.ResponseWriter, r *http.Request, clusterV string) {
56 if !allowMethod(w, r, "GET") {
57 return
58 }
59 vs := version.Versions{
60 Server: version.Version,
61 Cluster: clusterV,
62 }
63
64 w.Header().Set("Content-Type", "application/json")
65 b, err := json.Marshal(&vs)
66 if err != nil {
67 panic(fmt.Sprintf("cannot marshal versions to json (%v)", err))
68 }
69 w.Write(b)
70 }
71
72 func serveVars(w http.ResponseWriter, r *http.Request) {
73 if !allowMethod(w, r, "GET") {
74 return
75 }
76
77 w.Header().Set("Content-Type", "application/json; charset=utf-8")
78 fmt.Fprintf(w, "{\n")
79 first := true
80 expvar.Do(func(kv expvar.KeyValue) {
81 if !first {
82 fmt.Fprintf(w, ",\n")
83 }
84 first = false
85 fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
86 })
87 fmt.Fprintf(w, "\n}\n")
88 }
89
90 func allowMethod(w http.ResponseWriter, r *http.Request, m string) bool {
91 if m == r.Method {
92 return true
93 }
94 w.Header().Set("Allow", m)
95 http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
96 return false
97 }
98
99
100
101
102 func WriteError(lg *zap.Logger, w http.ResponseWriter, r *http.Request, err error) {
103 if err == nil {
104 return
105 }
106 switch e := err.(type) {
107 case *v2error.Error:
108 e.WriteTo(w)
109
110 case *httptypes.HTTPError:
111 if et := e.WriteTo(w); et != nil {
112 if lg != nil {
113 lg.Debug(
114 "failed to write v2 HTTP error",
115 zap.String("remote-addr", r.RemoteAddr),
116 zap.String("internal-server-error", e.Error()),
117 zap.Error(et),
118 )
119 }
120 }
121
122 default:
123 switch err {
124 case etcdserver.ErrTimeoutDueToLeaderFail, etcdserver.ErrTimeoutDueToConnectionLost, etcdserver.ErrNotEnoughStartedMembers,
125 etcdserver.ErrUnhealthy:
126 if lg != nil {
127 lg.Warn(
128 "v2 response error",
129 zap.String("remote-addr", r.RemoteAddr),
130 zap.String("internal-server-error", err.Error()),
131 )
132 }
133
134 default:
135 if lg != nil {
136 lg.Warn(
137 "unexpected v2 response error",
138 zap.String("remote-addr", r.RemoteAddr),
139 zap.String("internal-server-error", err.Error()),
140 )
141 }
142 }
143
144 herr := httptypes.NewHTTPError(http.StatusInternalServerError, "Internal Server Error")
145 if et := herr.WriteTo(w); et != nil {
146 if lg != nil {
147 lg.Debug(
148 "failed to write v2 HTTP error",
149 zap.String("remote-addr", r.RemoteAddr),
150 zap.String("internal-server-error", err.Error()),
151 zap.Error(et),
152 )
153 }
154 }
155 }
156 }
157
View as plain text