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 }