github.com/luckypickle/go-ethereum-vet@v1.14.2/p2p/simulations/http.go (about)

     1  // Copyright 2017 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 simulations
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"net/http"
    28  	"strconv"
    29  	"strings"
    30  	"sync"
    31  
    32  	"github.com/julienschmidt/httprouter"
    33  	"github.com/luckypickle/go-ethereum-vet/event"
    34  	"github.com/luckypickle/go-ethereum-vet/p2p"
    35  	"github.com/luckypickle/go-ethereum-vet/p2p/discover"
    36  	"github.com/luckypickle/go-ethereum-vet/p2p/simulations/adapters"
    37  	"github.com/luckypickle/go-ethereum-vet/rpc"
    38  	"golang.org/x/net/websocket"
    39  )
    40  
    41  // DefaultClient is the default simulation API client which expects the API
    42  // to be running at http://localhost:8888
    43  var DefaultClient = NewClient("http://localhost:8888")
    44  
    45  // Client is a client for the simulation HTTP API which supports creating
    46  // and managing simulation networks
    47  type Client struct {
    48  	URL string
    49  
    50  	client *http.Client
    51  }
    52  
    53  // NewClient returns a new simulation API client
    54  func NewClient(url string) *Client {
    55  	return &Client{
    56  		URL:    url,
    57  		client: http.DefaultClient,
    58  	}
    59  }
    60  
    61  // GetNetwork returns details of the network
    62  func (c *Client) GetNetwork() (*Network, error) {
    63  	network := &Network{}
    64  	return network, c.Get("/", network)
    65  }
    66  
    67  // StartNetwork starts all existing nodes in the simulation network
    68  func (c *Client) StartNetwork() error {
    69  	return c.Post("/start", nil, nil)
    70  }
    71  
    72  // StopNetwork stops all existing nodes in a simulation network
    73  func (c *Client) StopNetwork() error {
    74  	return c.Post("/stop", nil, nil)
    75  }
    76  
    77  // CreateSnapshot creates a network snapshot
    78  func (c *Client) CreateSnapshot() (*Snapshot, error) {
    79  	snap := &Snapshot{}
    80  	return snap, c.Get("/snapshot", snap)
    81  }
    82  
    83  // LoadSnapshot loads a snapshot into the network
    84  func (c *Client) LoadSnapshot(snap *Snapshot) error {
    85  	return c.Post("/snapshot", snap, nil)
    86  }
    87  
    88  // SubscribeOpts is a collection of options to use when subscribing to network
    89  // events
    90  type SubscribeOpts struct {
    91  	// Current instructs the server to send events for existing nodes and
    92  	// connections first
    93  	Current bool
    94  
    95  	// Filter instructs the server to only send a subset of message events
    96  	Filter string
    97  }
    98  
    99  // SubscribeNetwork subscribes to network events which are sent from the server
   100  // as a server-sent-events stream, optionally receiving events for existing
   101  // nodes and connections and filtering message events
   102  func (c *Client) SubscribeNetwork(events chan *Event, opts SubscribeOpts) (event.Subscription, error) {
   103  	url := fmt.Sprintf("%s/events?current=%t&filter=%s", c.URL, opts.Current, opts.Filter)
   104  	req, err := http.NewRequest("GET", url, nil)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	req.Header.Set("Accept", "text/event-stream")
   109  	res, err := c.client.Do(req)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	if res.StatusCode != http.StatusOK {
   114  		response, _ := ioutil.ReadAll(res.Body)
   115  		res.Body.Close()
   116  		return nil, fmt.Errorf("unexpected HTTP status: %s: %s", res.Status, response)
   117  	}
   118  
   119  	// define a producer function to pass to event.Subscription
   120  	// which reads server-sent events from res.Body and sends
   121  	// them to the events channel
   122  	producer := func(stop <-chan struct{}) error {
   123  		defer res.Body.Close()
   124  
   125  		// read lines from res.Body in a goroutine so that we are
   126  		// always reading from the stop channel
   127  		lines := make(chan string)
   128  		errC := make(chan error, 1)
   129  		go func() {
   130  			s := bufio.NewScanner(res.Body)
   131  			for s.Scan() {
   132  				select {
   133  				case lines <- s.Text():
   134  				case <-stop:
   135  					return
   136  				}
   137  			}
   138  			errC <- s.Err()
   139  		}()
   140  
   141  		// detect any lines which start with "data:", decode the data
   142  		// into an event and send it to the events channel
   143  		for {
   144  			select {
   145  			case line := <-lines:
   146  				if !strings.HasPrefix(line, "data:") {
   147  					continue
   148  				}
   149  				data := strings.TrimSpace(strings.TrimPrefix(line, "data:"))
   150  				event := &Event{}
   151  				if err := json.Unmarshal([]byte(data), event); err != nil {
   152  					return fmt.Errorf("error decoding SSE event: %s", err)
   153  				}
   154  				select {
   155  				case events <- event:
   156  				case <-stop:
   157  					return nil
   158  				}
   159  			case err := <-errC:
   160  				return err
   161  			case <-stop:
   162  				return nil
   163  			}
   164  		}
   165  	}
   166  
   167  	return event.NewSubscription(producer), nil
   168  }
   169  
   170  // GetNodes returns all nodes which exist in the network
   171  func (c *Client) GetNodes() ([]*p2p.NodeInfo, error) {
   172  	var nodes []*p2p.NodeInfo
   173  	return nodes, c.Get("/nodes", &nodes)
   174  }
   175  
   176  // CreateNode creates a node in the network using the given configuration
   177  func (c *Client) CreateNode(config *adapters.NodeConfig) (*p2p.NodeInfo, error) {
   178  	node := &p2p.NodeInfo{}
   179  	return node, c.Post("/nodes", config, node)
   180  }
   181  
   182  // GetNode returns details of a node
   183  func (c *Client) GetNode(nodeID string) (*p2p.NodeInfo, error) {
   184  	node := &p2p.NodeInfo{}
   185  	return node, c.Get(fmt.Sprintf("/nodes/%s", nodeID), node)
   186  }
   187  
   188  // StartNode starts a node
   189  func (c *Client) StartNode(nodeID string) error {
   190  	return c.Post(fmt.Sprintf("/nodes/%s/start", nodeID), nil, nil)
   191  }
   192  
   193  // StopNode stops a node
   194  func (c *Client) StopNode(nodeID string) error {
   195  	return c.Post(fmt.Sprintf("/nodes/%s/stop", nodeID), nil, nil)
   196  }
   197  
   198  // ConnectNode connects a node to a peer node
   199  func (c *Client) ConnectNode(nodeID, peerID string) error {
   200  	return c.Post(fmt.Sprintf("/nodes/%s/conn/%s", nodeID, peerID), nil, nil)
   201  }
   202  
   203  // DisconnectNode disconnects a node from a peer node
   204  func (c *Client) DisconnectNode(nodeID, peerID string) error {
   205  	return c.Delete(fmt.Sprintf("/nodes/%s/conn/%s", nodeID, peerID))
   206  }
   207  
   208  // RPCClient returns an RPC client connected to a node
   209  func (c *Client) RPCClient(ctx context.Context, nodeID string) (*rpc.Client, error) {
   210  	baseURL := strings.Replace(c.URL, "http", "ws", 1)
   211  	return rpc.DialWebsocket(ctx, fmt.Sprintf("%s/nodes/%s/rpc", baseURL, nodeID), "")
   212  }
   213  
   214  // Get performs a HTTP GET request decoding the resulting JSON response
   215  // into "out"
   216  func (c *Client) Get(path string, out interface{}) error {
   217  	return c.Send("GET", path, nil, out)
   218  }
   219  
   220  // Post performs a HTTP POST request sending "in" as the JSON body and
   221  // decoding the resulting JSON response into "out"
   222  func (c *Client) Post(path string, in, out interface{}) error {
   223  	return c.Send("POST", path, in, out)
   224  }
   225  
   226  // Delete performs a HTTP DELETE request
   227  func (c *Client) Delete(path string) error {
   228  	return c.Send("DELETE", path, nil, nil)
   229  }
   230  
   231  // Send performs a HTTP request, sending "in" as the JSON request body and
   232  // decoding the JSON response into "out"
   233  func (c *Client) Send(method, path string, in, out interface{}) error {
   234  	var body []byte
   235  	if in != nil {
   236  		var err error
   237  		body, err = json.Marshal(in)
   238  		if err != nil {
   239  			return err
   240  		}
   241  	}
   242  	req, err := http.NewRequest(method, c.URL+path, bytes.NewReader(body))
   243  	if err != nil {
   244  		return err
   245  	}
   246  	req.Header.Set("Content-Type", "application/json")
   247  	req.Header.Set("Accept", "application/json")
   248  	res, err := c.client.Do(req)
   249  	if err != nil {
   250  		return err
   251  	}
   252  	defer res.Body.Close()
   253  	if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated {
   254  		response, _ := ioutil.ReadAll(res.Body)
   255  		return fmt.Errorf("unexpected HTTP status: %s: %s", res.Status, response)
   256  	}
   257  	if out != nil {
   258  		if err := json.NewDecoder(res.Body).Decode(out); err != nil {
   259  			return err
   260  		}
   261  	}
   262  	return nil
   263  }
   264  
   265  // Server is an HTTP server providing an API to manage a simulation network
   266  type Server struct {
   267  	router     *httprouter.Router
   268  	network    *Network
   269  	mockerStop chan struct{} // when set, stops the current mocker
   270  	mockerMtx  sync.Mutex    // synchronises access to the mockerStop field
   271  }
   272  
   273  // NewServer returns a new simulation API server
   274  func NewServer(network *Network) *Server {
   275  	s := &Server{
   276  		router:  httprouter.New(),
   277  		network: network,
   278  	}
   279  
   280  	s.OPTIONS("/", s.Options)
   281  	s.GET("/", s.GetNetwork)
   282  	s.POST("/start", s.StartNetwork)
   283  	s.POST("/stop", s.StopNetwork)
   284  	s.POST("/mocker/start", s.StartMocker)
   285  	s.POST("/mocker/stop", s.StopMocker)
   286  	s.GET("/mocker", s.GetMockers)
   287  	s.POST("/reset", s.ResetNetwork)
   288  	s.GET("/events", s.StreamNetworkEvents)
   289  	s.GET("/snapshot", s.CreateSnapshot)
   290  	s.POST("/snapshot", s.LoadSnapshot)
   291  	s.POST("/nodes", s.CreateNode)
   292  	s.GET("/nodes", s.GetNodes)
   293  	s.GET("/nodes/:nodeid", s.GetNode)
   294  	s.POST("/nodes/:nodeid/start", s.StartNode)
   295  	s.POST("/nodes/:nodeid/stop", s.StopNode)
   296  	s.POST("/nodes/:nodeid/conn/:peerid", s.ConnectNode)
   297  	s.DELETE("/nodes/:nodeid/conn/:peerid", s.DisconnectNode)
   298  	s.GET("/nodes/:nodeid/rpc", s.NodeRPC)
   299  
   300  	return s
   301  }
   302  
   303  // GetNetwork returns details of the network
   304  func (s *Server) GetNetwork(w http.ResponseWriter, req *http.Request) {
   305  	s.JSON(w, http.StatusOK, s.network)
   306  }
   307  
   308  // StartNetwork starts all nodes in the network
   309  func (s *Server) StartNetwork(w http.ResponseWriter, req *http.Request) {
   310  	if err := s.network.StartAll(); err != nil {
   311  		http.Error(w, err.Error(), http.StatusInternalServerError)
   312  		return
   313  	}
   314  
   315  	w.WriteHeader(http.StatusOK)
   316  }
   317  
   318  // StopNetwork stops all nodes in the network
   319  func (s *Server) StopNetwork(w http.ResponseWriter, req *http.Request) {
   320  	if err := s.network.StopAll(); err != nil {
   321  		http.Error(w, err.Error(), http.StatusInternalServerError)
   322  		return
   323  	}
   324  
   325  	w.WriteHeader(http.StatusOK)
   326  }
   327  
   328  // StartMocker starts the mocker node simulation
   329  func (s *Server) StartMocker(w http.ResponseWriter, req *http.Request) {
   330  	s.mockerMtx.Lock()
   331  	defer s.mockerMtx.Unlock()
   332  	if s.mockerStop != nil {
   333  		http.Error(w, "mocker already running", http.StatusInternalServerError)
   334  		return
   335  	}
   336  	mockerType := req.FormValue("mocker-type")
   337  	mockerFn := LookupMocker(mockerType)
   338  	if mockerFn == nil {
   339  		http.Error(w, fmt.Sprintf("unknown mocker type %q", mockerType), http.StatusBadRequest)
   340  		return
   341  	}
   342  	nodeCount, err := strconv.Atoi(req.FormValue("node-count"))
   343  	if err != nil {
   344  		http.Error(w, "invalid node-count provided", http.StatusBadRequest)
   345  		return
   346  	}
   347  	s.mockerStop = make(chan struct{})
   348  	go mockerFn(s.network, s.mockerStop, nodeCount)
   349  
   350  	w.WriteHeader(http.StatusOK)
   351  }
   352  
   353  // StopMocker stops the mocker node simulation
   354  func (s *Server) StopMocker(w http.ResponseWriter, req *http.Request) {
   355  	s.mockerMtx.Lock()
   356  	defer s.mockerMtx.Unlock()
   357  	if s.mockerStop == nil {
   358  		http.Error(w, "stop channel not initialized", http.StatusInternalServerError)
   359  		return
   360  	}
   361  	close(s.mockerStop)
   362  	s.mockerStop = nil
   363  
   364  	w.WriteHeader(http.StatusOK)
   365  }
   366  
   367  // GetMockerList returns a list of available mockers
   368  func (s *Server) GetMockers(w http.ResponseWriter, req *http.Request) {
   369  
   370  	list := GetMockerList()
   371  	s.JSON(w, http.StatusOK, list)
   372  }
   373  
   374  // ResetNetwork resets all properties of a network to its initial (empty) state
   375  func (s *Server) ResetNetwork(w http.ResponseWriter, req *http.Request) {
   376  	s.network.Reset()
   377  
   378  	w.WriteHeader(http.StatusOK)
   379  }
   380  
   381  // StreamNetworkEvents streams network events as a server-sent-events stream
   382  func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) {
   383  	events := make(chan *Event)
   384  	sub := s.network.events.Subscribe(events)
   385  	defer sub.Unsubscribe()
   386  
   387  	// stop the stream if the client goes away
   388  	var clientGone <-chan bool
   389  	if cn, ok := w.(http.CloseNotifier); ok {
   390  		clientGone = cn.CloseNotify()
   391  	}
   392  
   393  	// write writes the given event and data to the stream like:
   394  	//
   395  	// event: <event>
   396  	// data: <data>
   397  	//
   398  	write := func(event, data string) {
   399  		fmt.Fprintf(w, "event: %s\n", event)
   400  		fmt.Fprintf(w, "data: %s\n\n", data)
   401  		if fw, ok := w.(http.Flusher); ok {
   402  			fw.Flush()
   403  		}
   404  	}
   405  	writeEvent := func(event *Event) error {
   406  		data, err := json.Marshal(event)
   407  		if err != nil {
   408  			return err
   409  		}
   410  		write("network", string(data))
   411  		return nil
   412  	}
   413  	writeErr := func(err error) {
   414  		write("error", err.Error())
   415  	}
   416  
   417  	// check if filtering has been requested
   418  	var filters MsgFilters
   419  	if filterParam := req.URL.Query().Get("filter"); filterParam != "" {
   420  		var err error
   421  		filters, err = NewMsgFilters(filterParam)
   422  		if err != nil {
   423  			http.Error(w, err.Error(), http.StatusBadRequest)
   424  			return
   425  		}
   426  	}
   427  
   428  	w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
   429  	w.WriteHeader(http.StatusOK)
   430  	fmt.Fprintf(w, "\n\n")
   431  	if fw, ok := w.(http.Flusher); ok {
   432  		fw.Flush()
   433  	}
   434  
   435  	// optionally send the existing nodes and connections
   436  	if req.URL.Query().Get("current") == "true" {
   437  		snap, err := s.network.Snapshot()
   438  		if err != nil {
   439  			writeErr(err)
   440  			return
   441  		}
   442  		for _, node := range snap.Nodes {
   443  			event := NewEvent(&node.Node)
   444  			if err := writeEvent(event); err != nil {
   445  				writeErr(err)
   446  				return
   447  			}
   448  		}
   449  		for _, conn := range snap.Conns {
   450  			event := NewEvent(&conn)
   451  			if err := writeEvent(event); err != nil {
   452  				writeErr(err)
   453  				return
   454  			}
   455  		}
   456  	}
   457  
   458  	for {
   459  		select {
   460  		case event := <-events:
   461  			// only send message events which match the filters
   462  			if event.Msg != nil && !filters.Match(event.Msg) {
   463  				continue
   464  			}
   465  			if err := writeEvent(event); err != nil {
   466  				writeErr(err)
   467  				return
   468  			}
   469  		case <-clientGone:
   470  			return
   471  		}
   472  	}
   473  }
   474  
   475  // NewMsgFilters constructs a collection of message filters from a URL query
   476  // parameter.
   477  //
   478  // The parameter is expected to be a dash-separated list of individual filters,
   479  // each having the format '<proto>:<codes>', where <proto> is the name of a
   480  // protocol and <codes> is a comma-separated list of message codes.
   481  //
   482  // A message code of '*' or '-1' is considered a wildcard and matches any code.
   483  func NewMsgFilters(filterParam string) (MsgFilters, error) {
   484  	filters := make(MsgFilters)
   485  	for _, filter := range strings.Split(filterParam, "-") {
   486  		protoCodes := strings.SplitN(filter, ":", 2)
   487  		if len(protoCodes) != 2 || protoCodes[0] == "" || protoCodes[1] == "" {
   488  			return nil, fmt.Errorf("invalid message filter: %s", filter)
   489  		}
   490  		proto := protoCodes[0]
   491  		for _, code := range strings.Split(protoCodes[1], ",") {
   492  			if code == "*" || code == "-1" {
   493  				filters[MsgFilter{Proto: proto, Code: -1}] = struct{}{}
   494  				continue
   495  			}
   496  			n, err := strconv.ParseUint(code, 10, 64)
   497  			if err != nil {
   498  				return nil, fmt.Errorf("invalid message code: %s", code)
   499  			}
   500  			filters[MsgFilter{Proto: proto, Code: int64(n)}] = struct{}{}
   501  		}
   502  	}
   503  	return filters, nil
   504  }
   505  
   506  // MsgFilters is a collection of filters which are used to filter message
   507  // events
   508  type MsgFilters map[MsgFilter]struct{}
   509  
   510  // Match checks if the given message matches any of the filters
   511  func (m MsgFilters) Match(msg *Msg) bool {
   512  	// check if there is a wildcard filter for the message's protocol
   513  	if _, ok := m[MsgFilter{Proto: msg.Protocol, Code: -1}]; ok {
   514  		return true
   515  	}
   516  
   517  	// check if there is a filter for the message's protocol and code
   518  	if _, ok := m[MsgFilter{Proto: msg.Protocol, Code: int64(msg.Code)}]; ok {
   519  		return true
   520  	}
   521  
   522  	return false
   523  }
   524  
   525  // MsgFilter is used to filter message events based on protocol and message
   526  // code
   527  type MsgFilter struct {
   528  	// Proto is matched against a message's protocol
   529  	Proto string
   530  
   531  	// Code is matched against a message's code, with -1 matching all codes
   532  	Code int64
   533  }
   534  
   535  // CreateSnapshot creates a network snapshot
   536  func (s *Server) CreateSnapshot(w http.ResponseWriter, req *http.Request) {
   537  	snap, err := s.network.Snapshot()
   538  	if err != nil {
   539  		http.Error(w, err.Error(), http.StatusInternalServerError)
   540  		return
   541  	}
   542  
   543  	s.JSON(w, http.StatusOK, snap)
   544  }
   545  
   546  // LoadSnapshot loads a snapshot into the network
   547  func (s *Server) LoadSnapshot(w http.ResponseWriter, req *http.Request) {
   548  	snap := &Snapshot{}
   549  	if err := json.NewDecoder(req.Body).Decode(snap); err != nil {
   550  		http.Error(w, err.Error(), http.StatusBadRequest)
   551  		return
   552  	}
   553  
   554  	if err := s.network.Load(snap); err != nil {
   555  		http.Error(w, err.Error(), http.StatusInternalServerError)
   556  		return
   557  	}
   558  
   559  	s.JSON(w, http.StatusOK, s.network)
   560  }
   561  
   562  // CreateNode creates a node in the network using the given configuration
   563  func (s *Server) CreateNode(w http.ResponseWriter, req *http.Request) {
   564  	config := &adapters.NodeConfig{}
   565  
   566  	err := json.NewDecoder(req.Body).Decode(config)
   567  	if err != nil && err != io.EOF {
   568  		http.Error(w, err.Error(), http.StatusBadRequest)
   569  		return
   570  	}
   571  
   572  	node, err := s.network.NewNodeWithConfig(config)
   573  	if err != nil {
   574  		http.Error(w, err.Error(), http.StatusInternalServerError)
   575  		return
   576  	}
   577  
   578  	s.JSON(w, http.StatusCreated, node.NodeInfo())
   579  }
   580  
   581  // GetNodes returns all nodes which exist in the network
   582  func (s *Server) GetNodes(w http.ResponseWriter, req *http.Request) {
   583  	nodes := s.network.GetNodes()
   584  
   585  	infos := make([]*p2p.NodeInfo, len(nodes))
   586  	for i, node := range nodes {
   587  		infos[i] = node.NodeInfo()
   588  	}
   589  
   590  	s.JSON(w, http.StatusOK, infos)
   591  }
   592  
   593  // GetNode returns details of a node
   594  func (s *Server) GetNode(w http.ResponseWriter, req *http.Request) {
   595  	node := req.Context().Value("node").(*Node)
   596  
   597  	s.JSON(w, http.StatusOK, node.NodeInfo())
   598  }
   599  
   600  // StartNode starts a node
   601  func (s *Server) StartNode(w http.ResponseWriter, req *http.Request) {
   602  	node := req.Context().Value("node").(*Node)
   603  
   604  	if err := s.network.Start(node.ID()); err != nil {
   605  		http.Error(w, err.Error(), http.StatusInternalServerError)
   606  		return
   607  	}
   608  
   609  	s.JSON(w, http.StatusOK, node.NodeInfo())
   610  }
   611  
   612  // StopNode stops a node
   613  func (s *Server) StopNode(w http.ResponseWriter, req *http.Request) {
   614  	node := req.Context().Value("node").(*Node)
   615  
   616  	if err := s.network.Stop(node.ID()); err != nil {
   617  		http.Error(w, err.Error(), http.StatusInternalServerError)
   618  		return
   619  	}
   620  
   621  	s.JSON(w, http.StatusOK, node.NodeInfo())
   622  }
   623  
   624  // ConnectNode connects a node to a peer node
   625  func (s *Server) ConnectNode(w http.ResponseWriter, req *http.Request) {
   626  	node := req.Context().Value("node").(*Node)
   627  	peer := req.Context().Value("peer").(*Node)
   628  
   629  	if err := s.network.Connect(node.ID(), peer.ID()); err != nil {
   630  		http.Error(w, err.Error(), http.StatusInternalServerError)
   631  		return
   632  	}
   633  
   634  	s.JSON(w, http.StatusOK, node.NodeInfo())
   635  }
   636  
   637  // DisconnectNode disconnects a node from a peer node
   638  func (s *Server) DisconnectNode(w http.ResponseWriter, req *http.Request) {
   639  	node := req.Context().Value("node").(*Node)
   640  	peer := req.Context().Value("peer").(*Node)
   641  
   642  	if err := s.network.Disconnect(node.ID(), peer.ID()); err != nil {
   643  		http.Error(w, err.Error(), http.StatusInternalServerError)
   644  		return
   645  	}
   646  
   647  	s.JSON(w, http.StatusOK, node.NodeInfo())
   648  }
   649  
   650  // Options responds to the OPTIONS HTTP method by returning a 200 OK response
   651  // with the "Access-Control-Allow-Headers" header set to "Content-Type"
   652  func (s *Server) Options(w http.ResponseWriter, req *http.Request) {
   653  	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
   654  	w.WriteHeader(http.StatusOK)
   655  }
   656  
   657  // NodeRPC forwards RPC requests to a node in the network via a WebSocket
   658  // connection
   659  func (s *Server) NodeRPC(w http.ResponseWriter, req *http.Request) {
   660  	node := req.Context().Value("node").(*Node)
   661  
   662  	handler := func(conn *websocket.Conn) {
   663  		node.ServeRPC(conn)
   664  	}
   665  
   666  	websocket.Server{Handler: handler}.ServeHTTP(w, req)
   667  }
   668  
   669  // ServeHTTP implements the http.Handler interface by delegating to the
   670  // underlying httprouter.Router
   671  func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   672  	s.router.ServeHTTP(w, req)
   673  }
   674  
   675  // GET registers a handler for GET requests to a particular path
   676  func (s *Server) GET(path string, handle http.HandlerFunc) {
   677  	s.router.GET(path, s.wrapHandler(handle))
   678  }
   679  
   680  // POST registers a handler for POST requests to a particular path
   681  func (s *Server) POST(path string, handle http.HandlerFunc) {
   682  	s.router.POST(path, s.wrapHandler(handle))
   683  }
   684  
   685  // DELETE registers a handler for DELETE requests to a particular path
   686  func (s *Server) DELETE(path string, handle http.HandlerFunc) {
   687  	s.router.DELETE(path, s.wrapHandler(handle))
   688  }
   689  
   690  // OPTIONS registers a handler for OPTIONS requests to a particular path
   691  func (s *Server) OPTIONS(path string, handle http.HandlerFunc) {
   692  	s.router.OPTIONS("/*path", s.wrapHandler(handle))
   693  }
   694  
   695  // JSON sends "data" as a JSON HTTP response
   696  func (s *Server) JSON(w http.ResponseWriter, status int, data interface{}) {
   697  	w.Header().Set("Content-Type", "application/json")
   698  	w.WriteHeader(status)
   699  	json.NewEncoder(w).Encode(data)
   700  }
   701  
   702  // wrapHandler returns a httprouter.Handle which wraps a http.HandlerFunc by
   703  // populating request.Context with any objects from the URL params
   704  func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle {
   705  	return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
   706  		w.Header().Set("Access-Control-Allow-Origin", "*")
   707  		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
   708  
   709  		ctx := context.Background()
   710  
   711  		if id := params.ByName("nodeid"); id != "" {
   712  			var node *Node
   713  			if nodeID, err := discover.HexID(id); err == nil {
   714  				node = s.network.GetNode(nodeID)
   715  			} else {
   716  				node = s.network.GetNodeByName(id)
   717  			}
   718  			if node == nil {
   719  				http.NotFound(w, req)
   720  				return
   721  			}
   722  			ctx = context.WithValue(ctx, "node", node)
   723  		}
   724  
   725  		if id := params.ByName("peerid"); id != "" {
   726  			var peer *Node
   727  			if peerID, err := discover.HexID(id); err == nil {
   728  				peer = s.network.GetNode(peerID)
   729  			} else {
   730  				peer = s.network.GetNodeByName(id)
   731  			}
   732  			if peer == nil {
   733  				http.NotFound(w, req)
   734  				return
   735  			}
   736  			ctx = context.WithValue(ctx, "peer", peer)
   737  		}
   738  
   739  		handler(w, req.WithContext(ctx))
   740  	}
   741  }