github.com/metacurrency/holochain@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/cmd/bs/bs.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	holo "github.com/holochain/holochain-proto"
     8  	"github.com/op/go-logging"
     9  	"github.com/tidwall/buntdb"
    10  	"github.com/urfave/cli"
    11  	"net/http"
    12  	"os"
    13  	"os/user"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  const (
    19  	DefaultPort = 3142
    20  )
    21  
    22  var log = logging.MustGetLogger("main")
    23  
    24  var store *buntdb.DB
    25  
    26  func setupDB(dbpath string) (err error) {
    27  	if dbpath == "" {
    28  		dbpath = os.Getenv("HOLOBSPATH")
    29  		if dbpath == "" {
    30  			u, err := user.Current()
    31  			if err != nil {
    32  				return err
    33  			}
    34  			userPath := u.HomeDir
    35  			dbpath = userPath + "/.hcbootstrapdb"
    36  		}
    37  	}
    38  	store, err = buntdb.Open(dbpath)
    39  	if err != nil {
    40  		panic(err)
    41  	}
    42  	store.CreateIndex("chain", "*", buntdb.IndexJSON("HID"))
    43  	return
    44  }
    45  
    46  func setupApp() (app *cli.App) {
    47  	app = cli.NewApp()
    48  	app.Name = "bs"
    49  	app.Usage = "holochain bootstrap server"
    50  	app.Version = "0.0.2"
    51  
    52  	var port int
    53  	var dbpath string
    54  
    55  	app.Flags = []cli.Flag{
    56  		cli.IntFlag{
    57  			Name:        "port",
    58  			Usage:       "bootstrap server port",
    59  			Value:       DefaultPort,
    60  			Destination: &port,
    61  		},
    62  	}
    63  
    64  	app.Before = func(c *cli.Context) error {
    65  
    66  		log.Infof("app version: %s; Holochain bootstrap server", app.Version)
    67  
    68  		err := setupDB(dbpath)
    69  		return err
    70  	}
    71  
    72  	app.Action = func(c *cli.Context) error {
    73  		return serve(port)
    74  	}
    75  	return
    76  }
    77  
    78  func main() {
    79  	app := setupApp()
    80  	app.Run(os.Args)
    81  }
    82  
    83  type Node struct {
    84  	Req      holo.BSReq
    85  	Remote   string
    86  	HID      string
    87  	LastSeen time.Time
    88  }
    89  
    90  func get(chain string) (result string, err error) {
    91  	err = store.View(func(tx *buntdb.Tx) error {
    92  		nodes := make([]holo.BSResp, 0)
    93  		//hid := fmt.Sprintf(`{"HID":"%s"}`, chain)
    94  
    95  		now := time.Now()
    96  		tx.Ascend("chain", func(key, value string) bool {
    97  			var nd Node
    98  			json.Unmarshal([]byte(value), &nd)
    99  			if nd.HID == chain {
   100  				if nd.LastSeen.Add(holo.BootstrapTTL).After(now) {
   101  					log.Infof("Found: %s=>%s", key, value)
   102  					resp := holo.BSResp{Req: nd.Req, Remote: nd.Remote, LastSeen: nd.LastSeen}
   103  					nodes = append(nodes, resp)
   104  				}
   105  			}
   106  			return true
   107  		})
   108  		var b []byte
   109  		b, err = json.Marshal(nodes)
   110  		if err == nil {
   111  			result = string(b)
   112  		}
   113  		return err
   114  	})
   115  	return
   116  }
   117  
   118  func post(chain string, req *holo.BSReq, remote string, seen time.Time) (err error) {
   119  	err = store.Update(func(tx *buntdb.Tx) error {
   120  		var b []byte
   121  		n := Node{Remote: remote, Req: *req, HID: chain, LastSeen: seen}
   122  		b, err = json.Marshal(n)
   123  		if err == nil {
   124  			key := chain + ":" + req.NodeID
   125  			_, _, err = tx.Set(key, string(b), nil)
   126  			if err == nil {
   127  				log.Infof("Set: %s", string(b))
   128  			}
   129  		}
   130  		return err
   131  	})
   132  	return
   133  }
   134  
   135  func h(w http.ResponseWriter, r *http.Request) {
   136  	var err error
   137  	log.Infof("%s: processing req:%s\n", r.Method, r.URL.Path)
   138  	path := strings.Split(r.URL.Path, "/")
   139  	if r.Method == "GET" {
   140  		if len(path) != 2 {
   141  			http.Error(w, "expecting path /<holochainid>", 400)
   142  			return
   143  		}
   144  		chain := string(path[1])
   145  		var result string
   146  		result, err = get(chain)
   147  		if err == nil {
   148  			fmt.Fprintf(w, result)
   149  		}
   150  	} else if r.Method == "POST" {
   151  		if len(path) != 3 {
   152  			http.Error(w, "expecting path /<holochainid>/<peerid>", 400)
   153  			return
   154  		}
   155  		chain := string(path[1])
   156  		node := string(path[2])
   157  
   158  		var req holo.BSReq
   159  		if r.Body == nil {
   160  			err = errors.New("Please send a request body")
   161  		}
   162  		if err == nil {
   163  			err = json.NewDecoder(r.Body).Decode(&req)
   164  			if err == nil {
   165  				if req.NodeID != node {
   166  					err = errors.New("id in post URL doesn't match Req")
   167  				} else {
   168  					err = post(chain, &req, r.RemoteAddr, time.Now())
   169  					if err == nil {
   170  						fmt.Fprintf(w, "ok")
   171  					}
   172  				}
   173  			}
   174  		}
   175  	}
   176  	if err != nil {
   177  		log.Infof("Error:%s", err.Error())
   178  		http.Error(w, err.Error(), 400)
   179  	} else {
   180  		log.Infof("Success")
   181  	}
   182  }
   183  
   184  func getCompleteConnectionList(response http.ResponseWriter, request *http.Request) {
   185  	var err error
   186  	err = store.View(func(tx *buntdb.Tx) error {
   187  		nodes := make([]holo.BSResp, 0)
   188  		//hid := fmt.Sprintf(`{"HID":"%s"}`, chain)
   189  
   190  		tx.Ascend("chain", func(key, value string) bool {
   191  			var node Node
   192  			json.Unmarshal([]byte(value), &node)
   193  
   194  			log.Infof("Found: %s=>%s", key, value)
   195  			resp := holo.BSResp{Req: node.Req, Remote: node.Remote}
   196  
   197  			nodes = append(nodes, resp)
   198  
   199  			return true
   200  		})
   201  		var b []byte
   202  		b, err = json.Marshal(nodes)
   203  		if err == nil {
   204  			fmt.Fprintf(response, string(b))
   205  			fmt.Fprintf(response, string("oK"))
   206  		}
   207  
   208  		return err
   209  	})
   210  }
   211  
   212  func serve(port int) (err error) {
   213  
   214  	mux := http.NewServeMux()
   215  
   216  	mux.HandleFunc("/", h)
   217  	mux.HandleFunc("/getCompleteConnectionList", getCompleteConnectionList)
   218  
   219  	log.Infof("starting up on port %d", port)
   220  
   221  	s := &http.Server{
   222  		Addr:           fmt.Sprintf(":%d", port), // set listen port
   223  		Handler:        mux,
   224  		ReadTimeout:    10 * time.Second,
   225  		WriteTimeout:   10 * time.Second,
   226  		MaxHeaderBytes: 1 << 20,
   227  	}
   228  	err = s.ListenAndServe()
   229  	return
   230  }