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