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