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  }