github.com/jincm/wesharechain@v0.0.0-20210122032815-1537409ce26a/chain/swarm/storage/mock/explorer/explorer.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package explorer 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "io" 23 "net/http" 24 "net/url" 25 "strconv" 26 "strings" 27 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/swarm/log" 30 "github.com/ethereum/go-ethereum/swarm/storage/mock" 31 "github.com/rs/cors" 32 ) 33 34 const jsonContentType = "application/json; charset=utf-8" 35 36 // NewHandler constructs an http.Handler with router 37 // that servers requests required by chunk explorer. 38 // 39 // /api/has-key/{node}/{key} 40 // /api/keys?start={key}&node={node}&limit={int[0..1000]} 41 // /api/nodes?start={node}&key={key}&limit={int[0..1000]} 42 // 43 // Data from global store will be served and appropriate 44 // CORS headers will be sent if allowed origins are provided. 45 func NewHandler(store mock.GlobalStorer, corsOrigins []string) (handler http.Handler) { 46 mux := http.NewServeMux() 47 mux.Handle("/api/has-key/", newHasKeyHandler(store)) 48 mux.Handle("/api/keys", newKeysHandler(store)) 49 mux.Handle("/api/nodes", newNodesHandler(store)) 50 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 51 jsonStatusResponse(w, http.StatusNotFound) 52 }) 53 handler = noCacheHandler(mux) 54 if corsOrigins != nil { 55 handler = cors.New(cors.Options{ 56 AllowedOrigins: corsOrigins, 57 AllowedMethods: []string{"GET"}, 58 MaxAge: 600, 59 }).Handler(handler) 60 } 61 return handler 62 } 63 64 // newHasKeyHandler returns a new handler that serves 65 // requests for HasKey global store method. 66 // Possible responses are StatusResponse with 67 // status codes 200 or 404 if the chunk is found or not. 68 func newHasKeyHandler(store mock.GlobalStorer) http.HandlerFunc { 69 return func(w http.ResponseWriter, r *http.Request) { 70 addr, key, ok := parseHasKeyPath(r.URL.Path) 71 if !ok { 72 jsonStatusResponse(w, http.StatusNotFound) 73 return 74 } 75 found := store.HasKey(addr, key) 76 if !found { 77 jsonStatusResponse(w, http.StatusNotFound) 78 return 79 } 80 jsonStatusResponse(w, http.StatusOK) 81 } 82 } 83 84 // KeysResponse is a JSON-encoded response for global store 85 // Keys and NodeKeys methods. 86 type KeysResponse struct { 87 Keys []string `json:"keys"` 88 Next string `json:"next,omitempty"` 89 } 90 91 // newKeysHandler returns a new handler that serves 92 // requests for Key global store method. 93 // HTTP response body will be JSON-encoded KeysResponse. 94 func newKeysHandler(store mock.GlobalStorer) http.HandlerFunc { 95 return func(w http.ResponseWriter, r *http.Request) { 96 q := r.URL.Query() 97 node := q.Get("node") 98 start, limit := listingPage(q) 99 100 var keys mock.Keys 101 if node == "" { 102 var err error 103 keys, err = store.Keys(common.Hex2Bytes(start), limit) 104 if err != nil { 105 log.Error("chunk explorer: keys handler: get keys", "start", start, "err", err) 106 jsonStatusResponse(w, http.StatusInternalServerError) 107 return 108 } 109 } else { 110 var err error 111 keys, err = store.NodeKeys(common.HexToAddress(node), common.Hex2Bytes(start), limit) 112 if err != nil { 113 log.Error("chunk explorer: keys handler: get node keys", "node", node, "start", start, "err", err) 114 jsonStatusResponse(w, http.StatusInternalServerError) 115 return 116 } 117 } 118 ks := make([]string, len(keys.Keys)) 119 for i, k := range keys.Keys { 120 ks[i] = common.Bytes2Hex(k) 121 } 122 data, err := json.Marshal(KeysResponse{ 123 Keys: ks, 124 Next: common.Bytes2Hex(keys.Next), 125 }) 126 if err != nil { 127 log.Error("chunk explorer: keys handler: json marshal", "err", err) 128 jsonStatusResponse(w, http.StatusInternalServerError) 129 return 130 } 131 w.Header().Set("Content-Type", jsonContentType) 132 _, err = io.Copy(w, bytes.NewReader(data)) 133 if err != nil { 134 log.Error("chunk explorer: keys handler: write response", "err", err) 135 } 136 } 137 } 138 139 // NodesResponse is a JSON-encoded response for global store 140 // Nodes and KeyNodes methods. 141 type NodesResponse struct { 142 Nodes []string `json:"nodes"` 143 Next string `json:"next,omitempty"` 144 } 145 146 // newNodesHandler returns a new handler that serves 147 // requests for Nodes global store method. 148 // HTTP response body will be JSON-encoded NodesResponse. 149 func newNodesHandler(store mock.GlobalStorer) http.HandlerFunc { 150 return func(w http.ResponseWriter, r *http.Request) { 151 q := r.URL.Query() 152 key := q.Get("key") 153 var start *common.Address 154 queryStart, limit := listingPage(q) 155 if queryStart != "" { 156 s := common.HexToAddress(queryStart) 157 start = &s 158 } 159 160 var nodes mock.Nodes 161 if key == "" { 162 var err error 163 nodes, err = store.Nodes(start, limit) 164 if err != nil { 165 log.Error("chunk explorer: nodes handler: get nodes", "start", queryStart, "err", err) 166 jsonStatusResponse(w, http.StatusInternalServerError) 167 return 168 } 169 } else { 170 var err error 171 nodes, err = store.KeyNodes(common.Hex2Bytes(key), start, limit) 172 if err != nil { 173 log.Error("chunk explorer: nodes handler: get key nodes", "key", key, "start", queryStart, "err", err) 174 jsonStatusResponse(w, http.StatusInternalServerError) 175 return 176 } 177 } 178 ns := make([]string, len(nodes.Addrs)) 179 for i, n := range nodes.Addrs { 180 ns[i] = n.Hex() 181 } 182 var next string 183 if nodes.Next != nil { 184 next = nodes.Next.Hex() 185 } 186 data, err := json.Marshal(NodesResponse{ 187 Nodes: ns, 188 Next: next, 189 }) 190 if err != nil { 191 log.Error("chunk explorer: nodes handler", "err", err) 192 jsonStatusResponse(w, http.StatusInternalServerError) 193 return 194 } 195 w.Header().Set("Content-Type", jsonContentType) 196 _, err = io.Copy(w, bytes.NewReader(data)) 197 if err != nil { 198 log.Error("chunk explorer: nodes handler: write response", "err", err) 199 } 200 } 201 } 202 203 // parseHasKeyPath extracts address and key from HTTP request 204 // path for HasKey route: /api/has-key/{node}/{key}. 205 // If ok is false, the provided path is not matched. 206 func parseHasKeyPath(p string) (addr common.Address, key []byte, ok bool) { 207 p = strings.TrimPrefix(p, "/api/has-key/") 208 parts := strings.SplitN(p, "/", 2) 209 if len(parts) != 2 || parts[0] == "" || parts[1] == "" { 210 return addr, nil, false 211 } 212 addr = common.HexToAddress(parts[0]) 213 key = common.Hex2Bytes(parts[1]) 214 return addr, key, true 215 } 216 217 // listingPage returns start value and listing limit 218 // from url query values. 219 func listingPage(q url.Values) (start string, limit int) { 220 // if limit is not a valid integer (or blank string), 221 // ignore the error and use the returned 0 value 222 limit, _ = strconv.Atoi(q.Get("limit")) 223 return q.Get("start"), limit 224 } 225 226 // StatusResponse is a standardized JSON-encoded response 227 // that contains information about HTTP response code 228 // for easier status identification. 229 type StatusResponse struct { 230 Message string `json:"message"` 231 Code int `json:"code"` 232 } 233 234 // jsonStatusResponse writes to the response writer 235 // JSON-encoded StatusResponse based on the provided status code. 236 func jsonStatusResponse(w http.ResponseWriter, code int) { 237 w.Header().Set("Content-Type", jsonContentType) 238 w.WriteHeader(code) 239 err := json.NewEncoder(w).Encode(StatusResponse{ 240 Message: http.StatusText(code), 241 Code: code, 242 }) 243 if err != nil { 244 log.Error("chunk explorer: json status response", "err", err) 245 } 246 } 247 248 // noCacheHandler sets required HTTP headers to prevent 249 // response caching at the client side. 250 func noCacheHandler(h http.Handler) http.Handler { 251 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 252 w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") 253 w.Header().Set("Pragma", "no-cache") 254 w.Header().Set("Expires", "0") 255 h.ServeHTTP(w, r) 256 }) 257 }