// Read Linux system VM parameters and print performance configuration advice when necessary. func linuxPerfAdvice() { readFileIntContent := func(filePath string) (contentInt int, err error) { content, err := ioutil.ReadFile(filePath) if err != nil { return } contentInt, err = strconv.Atoi(strings.TrimSpace(string(content))) return } swappiness, err := readFileIntContent("/proc/sys/vm/swappiness") if err != nil { tdlog.Notice("Non-fatal - unable to offer performance advice based on vm.swappiness.") } else if swappiness > 30 { tdlog.Noticef("System vm.swappiness is very high (%d), for optimium performance please lower it below 30.", swappiness) } dirtyRatio, err := readFileIntContent("/proc/sys/vm/dirty_ratio") if err != nil { tdlog.Notice("Non-fatal - unable to offer performance advice based on vm.dirty_ratio.") } else if dirtyRatio < 50 { tdlog.Noticef("System vm.dirty_ratio is very low (%d), for optimium performance please raise it above 50.", dirtyRatio) } dirtyBGRatio, err := readFileIntContent("/proc/sys/vm/dirty_background_ratio") if err != nil { tdlog.Notice("Non-fatal - unable to offer performance advice based on vm.dirty_background_ratio.") } else if dirtyBGRatio > 20 { tdlog.Noticef("System vm.dirty_background_ratio is very high (%d), for optimium performance please lower it below 20.", dirtyBGRatio) } }
func Start(dir string, port int, tlsCrt, tlsKey, jwtPubKey, jwtPrivateKey string) { var err error HttpDB, err = db.OpenDB(dir) if err != nil { panic(err) } // These endpoints are always available and do not require JWT auth http.HandleFunc("/", Welcome) http.HandleFunc("/version", Version) http.HandleFunc("/memstats", MemStats) if jwtPrivateKey != "" { // JWT support ServeJWTEnabledEndpoints(jwtPubKey, jwtPrivateKey) } else { // No JWT ServeEndpoints() } if tlsCrt != "" { tdlog.Noticef("Will listen on all interfaces (HTTPS), port %d.", port) if err := http.ListenAndServeTLS(fmt.Sprintf(":%d", port), tlsCrt, tlsKey, nil); err != nil { tdlog.Panicf("Failed to start HTTPS service - %s", err) } } else { tdlog.Noticef("Will listen on all interfaces (HTTP), port %d.", port) http.ListenAndServe(fmt.Sprintf(":%d", port), nil) } }
func RegisterWebCp() { if WebCp == "" || WebCp == "none" || WebCp == "no" || WebCp == "false" { tdlog.Noticef("Web control panel is disabled on your request") return } http.HandleFunc("/"+WebCp, handleWebCp) http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { w.Write(rice.MustFindBox("static/img").MustBytes("favicon.ico")) }) http.Handle("/"+WebCp+"/css/", http.StripPrefix("/"+WebCp+"/css/", http.FileServer(rice.MustFindBox("static/css").HTTPBox()))) http.Handle("/"+WebCp+"/js/", http.StripPrefix("/"+WebCp+"/js/", http.FileServer(rice.MustFindBox("static/js").HTTPBox()))) tdlog.Noticef("Web control panel is accessible at /%s", WebCp) }
// Copy this database into destination directory (for backup). func (db *DB) Dump(dest string) error { db.schemaLock.Lock() defer db.schemaLock.Unlock() for _, col := range db.cols { if err := col.sync(); err != nil { return err } } cpFun := func(currPath string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { relPath, err := filepath.Rel(db.path, currPath) if err != nil { return err } destDir := path.Join(dest, relPath) if err := os.MkdirAll(destDir, 0770); err != nil { return err } tdlog.Noticef("Dump: created directory %s", destDir) } else { src, err := os.Open(currPath) if err != nil { return err } relPath, err := filepath.Rel(db.path, currPath) if err != nil { return err } destPath := path.Join(dest, relPath) if _, fileExists := os.Open(destPath); fileExists == nil { return fmt.Errorf("Destination file %s already exists", destPath) } destFile, err := os.Create(destPath) if err != nil { return err } written, err := io.Copy(destFile, src) if err != nil { return err } tdlog.Noticef("Dump: copied file %s, size is %d", destPath, written) } return nil } return filepath.Walk(db.path, cpFun) }
func ServeAuthTokenEndpoints(token string) { authToken = token // collection management (stop-the-world) http.HandleFunc("/create", authWrap(Create)) http.HandleFunc("/rename", authWrap(Rename)) http.HandleFunc("/drop", authWrap(Drop)) http.HandleFunc("/all", authWrap(All)) http.HandleFunc("/scrub", authWrap(Scrub)) http.HandleFunc("/sync", authWrap(Sync)) // query http.HandleFunc("/query", authWrap(Query)) http.HandleFunc("/count", authWrap(Count)) // document management http.HandleFunc("/insert", authWrap(Insert)) http.HandleFunc("/get", authWrap(Get)) http.HandleFunc("/getpage", authWrap(GetPage)) http.HandleFunc("/update", authWrap(Update)) http.HandleFunc("/delete", authWrap(Delete)) http.HandleFunc("/approxdoccount", authWrap(ApproxDocCount)) // index management (stop-the-world) http.HandleFunc("/index", authWrap(Index)) http.HandleFunc("/indexes", authWrap(Indexes)) http.HandleFunc("/unindex", authWrap(Unindex)) // misc http.HandleFunc("/shutdown", authWrap(Shutdown)) http.HandleFunc("/dump", authWrap(Dump)) tdlog.Noticef("API endpoints now require the 'Authorization: token' header.") }
// Delete a document. func (col *Col) Delete(id int) error { col.db.schemaLock.RLock() part := col.parts[id%col.db.numParts] // Place lock, read back original document and delete document part.DataLock.Lock() originalB, err := part.Read(id) if err != nil { part.DataLock.Unlock() col.db.schemaLock.RUnlock() return err } err = part.Delete(id) part.DataLock.Unlock() if err != nil { col.db.schemaLock.RUnlock() return err } // Done with the collection data, next is to remove indexed values var original map[string]interface{} err = json.Unmarshal(originalB, &original) if err == nil { part.LockUpdate(id) col.unindexDoc(id, original) part.UnlockUpdate(id) } else { tdlog.Noticef("Will not attempt to unindex document %d during delete", id) } col.db.schemaLock.RUnlock() return nil }
func Start(db *db.DB, port int) { HttpDB = db // collection management (stop-the-world) http.HandleFunc("/create", Create) http.HandleFunc("/rename", Rename) http.HandleFunc("/drop", Drop) http.HandleFunc("/all", All) http.HandleFunc("/scrub", Scrub) http.HandleFunc("/sync", Sync) // query http.HandleFunc("/query", Query) http.HandleFunc("/count", Count) // document management http.HandleFunc("/insert", Insert) http.HandleFunc("/get", Get) http.HandleFunc("/update", Update) http.HandleFunc("/delete", Delete) // index management (stop-the-world) http.HandleFunc("/index", Index) http.HandleFunc("/indexes", Indexes) http.HandleFunc("/unindex", Unindex) // misc (stop-the-world) http.HandleFunc("/shutdown", Shutdown) http.HandleFunc("/dump", Dump) // misc http.HandleFunc("/version", Version) http.HandleFunc("/memstats", MemStats) tdlog.Noticef("Will listen on all interfaces, port %d", port) http.ListenAndServe(fmt.Sprintf(":%d", port), nil) }
// Delete a document. func (col *Col) Delete(id int) error { part := col.parts[id%col.db.numParts] part.Lock.Lock() // Place lock, read back original document and delete document if err := part.LockUpdate(id); err != nil { part.Lock.Unlock() return fmt.Errorf("Document %d is locked for update, please try again later", id) } originalB, err := part.Read(id) if err != nil { part.UnlockUpdate(id) part.Lock.Unlock() return fmt.Errorf("Cannot delete %d: %v", id, err) } var original map[string]interface{} if err = json.Unmarshal(originalB, &original); err != nil { tdlog.Noticef("Will not attempt to unindex document %d during delete", id) } if err = part.Delete(id); err != nil { part.UnlockUpdate(id) part.Lock.Unlock() return err } // Done with the collection data, next is to remove indexed values part.Lock.Unlock() if original != nil { col.unindexDoc(id, original) } part.Lock.Lock() part.UnlockUpdate(id) part.Lock.Unlock() return nil }
// Load all collection schema. func (db *DB) load() error { // Create DB directory and PART_NUM_FILE if necessary var numPartsAssumed = false numPartsFilePath := path.Join(db.path, PART_NUM_FILE) if err := os.MkdirAll(db.path, 0777); err != nil { return err } if partNumFile, err := os.Stat(numPartsFilePath); err != nil { // The new database has as many partitions as number of CPUs recognized by OS if err := ioutil.WriteFile(numPartsFilePath, []byte(strconv.Itoa(runtime.NumCPU())), 0600); err != nil { return err } numPartsAssumed = true } else if partNumFile.IsDir() { return fmt.Errorf("Database config file %s is actually a directory, is database path correct?", PART_NUM_FILE) } // Get number of partitions from the text file if numParts, err := ioutil.ReadFile(numPartsFilePath); err != nil { return err } else if db.numParts, err = strconv.Atoi(strings.Trim(string(numParts), "\r\n ")); err != nil { return err } // Look for collection directories and open the collections db.cols = make(map[string]*Col) dirContent, err := ioutil.ReadDir(db.path) if err != nil { return err } for _, maybeColDir := range dirContent { if !maybeColDir.IsDir() { continue } if numPartsAssumed { return fmt.Errorf("Please manually repair database partition number config file %s", numPartsFilePath) } if db.cols[maybeColDir.Name()], err = OpenCol(db, maybeColDir.Name()); err != nil { return err } } // Synchronize data files at regular interval if db.autoSync == nil { db.autoSync = time.NewTicker(AUTO_SYNC_INTERVAL * time.Millisecond) go func() { for { select { case <-db.autoSync.C: if err := db.Sync(); err != nil { tdlog.Noticef("Background Auto-Sync on %s: Failed with error: %v", db.path, err) } case <-db.autoSyncStop: db.autoSync.Stop() return } } }() } return err }
// Scrub a collection - fix corrupted documents and de-fragment free space. func (db *DB) Scrub(name string) error { db.schemaLock.Lock() defer db.schemaLock.Unlock() if _, exists := db.cols[name]; !exists { return fmt.Errorf("Collection %s does not exist", name) } else if err := db.cols[name].sync(); err != nil { return err } // Prepare a temporary collection in file system tmpColName := fmt.Sprintf("scrub-%s-%d", name, time.Now().UnixNano()) tmpColDir := path.Join(db.path, tmpColName) if err := os.MkdirAll(tmpColDir, 0777); err != nil { return err } // Mirror indexes from original collection for _, idxPath := range db.cols[name].indexPaths { if err := os.MkdirAll(path.Join(tmpColDir, strings.Join(idxPath, INDEX_PATH_SEP)), 0777); err != nil { return err } } // Iterate through all documents and put them into the temporary collection tmpCol, err := OpenCol(db, tmpColName) if err != nil { return err } db.cols[name].ForEachDoc(func(id int, doc []byte) bool { var docObj map[string]interface{} if err := json.Unmarshal([]byte(doc), &docObj); err != nil { // Skip corrupted document return true } if err := tmpCol.InsertRecovery(id, docObj); err != nil { tdlog.Noticef("Scrub %s: failed to insert back document %v", name, docObj) } return true }) if err := tmpCol.close(); err != nil { return err } // Replace the original collection with the "temporary" one db.cols[name].close() if err := os.RemoveAll(path.Join(db.path, name)); err != nil { return err } if err := os.Rename(path.Join(db.path, tmpColName), path.Join(db.path, name)); err != nil { return err } if db.cols[name], err = OpenCol(db, name); err != nil { return err } return nil }
// Update a document. func (col *Col) Update(id int, doc map[string]interface{}) error { if doc == nil { return fmt.Errorf("Updating %d: input doc may not be nil", id) } docJS, err := json.Marshal(doc) if err != nil { return err } col.db.schemaLock.RLock() part := col.parts[id%col.db.numParts] part.Lock.Lock() // Place lock, read back original document and update if err := part.LockUpdate(id); err != nil { part.Lock.Unlock() col.db.schemaLock.RUnlock() return err } originalB, err := part.Read(id) if err != nil { part.UnlockUpdate(id) part.Lock.Unlock() col.db.schemaLock.RUnlock() return err } var original map[string]interface{} if err = json.Unmarshal(originalB, &original); err != nil { tdlog.Noticef("Will not attempt to unindex document %d during update", id) } if err = part.Update(id, []byte(docJS)); err != nil { part.UnlockUpdate(id) part.Lock.Unlock() col.db.schemaLock.RUnlock() return err } // Done with the collection data, next is to maintain indexed values part.Lock.Unlock() if original != nil { col.unindexDoc(id, original) } col.indexDoc(id, doc) // Done with the document part.Lock.Lock() part.UnlockUpdate(id) part.Lock.Unlock() col.db.schemaLock.RUnlock() return nil }
// UpdateBytesFunc will update a document bytes. // update func will get current document bytes and should return bytes of updated document; // updated document should be valid JSON; // provided buffer could be modified (reused for returned value); // non-nil error will be propagated back and returned from UpdateBytesFunc. func (col *Col) UpdateBytesFunc(id int, update func(origDoc []byte) (newDoc []byte, err error)) error { col.db.schemaLock.RLock() part := col.parts[id%col.db.numParts] // Place lock, read back original document and update part.DataLock.Lock() originalB, err := part.Read(id) if err != nil { part.DataLock.Unlock() col.db.schemaLock.RUnlock() return err } var original map[string]interface{} json.Unmarshal(originalB, &original) // Unmarshal originalB before passing it to update docB, err := update(originalB) if err != nil { part.DataLock.Unlock() col.db.schemaLock.RUnlock() return err } var doc map[string]interface{} // check if docB are valid JSON before Update if err = json.Unmarshal(docB, &doc); err != nil { part.DataLock.Unlock() col.db.schemaLock.RUnlock() return err } err = part.Update(id, docB) part.DataLock.Unlock() if err != nil { col.db.schemaLock.RUnlock() return err } // Done with the collection data, next is to maintain indexed values part.LockUpdate(id) if original != nil { col.unindexDoc(id, original) } else { tdlog.Noticef("Will not attempt to unindex document %d during update", id) } col.indexDoc(id, doc) // Done with the index part.UnlockUpdate(id) col.db.schemaLock.RUnlock() return nil }
func ServeJWTEnabledEndpoints(jwtPubKey, jwtPrivateKey string) { var e error if publicKey, e = ioutil.ReadFile(jwtPubKey); e != nil { tdlog.Panicf("JWT: Failed to read public key file - %s", e) } else if privateKey, e = ioutil.ReadFile(jwtPrivateKey); e != nil { tdlog.Panicf("JWT: Failed to read private key file - %s", e) } jwtInitSetup() // collection management (stop-the-world) http.HandleFunc("/create", jwtWrap(Create)) http.HandleFunc("/rename", jwtWrap(Rename)) http.HandleFunc("/drop", jwtWrap(Drop)) http.HandleFunc("/all", jwtWrap(All)) http.HandleFunc("/scrub", jwtWrap(Scrub)) http.HandleFunc("/sync", jwtWrap(Sync)) // query http.HandleFunc("/query", jwtWrap(Query)) http.HandleFunc("/count", jwtWrap(Count)) // document management http.HandleFunc("/insert", jwtWrap(Insert)) http.HandleFunc("/get", jwtWrap(Get)) http.HandleFunc("/getpage", jwtWrap(GetPage)) http.HandleFunc("/update", jwtWrap(Update)) http.HandleFunc("/delete", jwtWrap(Delete)) http.HandleFunc("/approxdoccount", jwtWrap(ApproxDocCount)) // index management (stop-the-world) http.HandleFunc("/index", jwtWrap(Index)) http.HandleFunc("/indexes", jwtWrap(Indexes)) http.HandleFunc("/unindex", jwtWrap(Unindex)) // misc http.HandleFunc("/shutdown", jwtWrap(Shutdown)) http.HandleFunc("/dump", jwtWrap(Dump)) // does not require JWT auth http.HandleFunc("/getjwt", getJWT) http.HandleFunc("/checkjwt", checkJWT) tdlog.Noticef("JWT is enabled. API endpoints require JWT authorization.") }
func main() { var err error var defaultMaxprocs int if defaultMaxprocs, err = strconv.Atoi(os.Getenv("GOMAXPROCS")); err != nil { defaultMaxprocs = runtime.NumCPU() } // Parse CLI parameters var mode, dir string var port, maxprocs int var profile bool flag.StringVar(&mode, "mode", "", "[httpd|bench|bench2|example]") flag.StringVar(&dir, "dir", "", "(HTTP API) database directory") flag.IntVar(&port, "port", 8080, "(HTTP API) port number") flag.StringVar(&webcp.WebCp, "webcp", "admin", "(HTTP API) web control panel route (without leading slash)") flag.IntVar(&maxprocs, "gomaxprocs", defaultMaxprocs, "GOMAXPROCS") flag.IntVar(&benchSize, "benchsize", 400000, "Benchmark sample size") flag.BoolVar(&profile, "profile", false, "Write profiler results to prof.out") flag.BoolVar(&tdlog.VerboseLog, "verbose", false, "Turn verbose logging on/off") flag.BoolVar(&benchCleanup, "benchcleanup", true, "Whether to clean up (delete benchmark DB) after benchmark") flag.Parse() // User must specify a mode to run if mode == "" { flag.PrintDefaults() return } // Set appropriate GOMAXPROCS runtime.GOMAXPROCS(maxprocs) tdlog.Noticef("GOMAXPROCS is set to %d", maxprocs) if maxprocs < runtime.NumCPU() { tdlog.Noticef("GOMAXPROCS (%d) is less than number of CPUs (%d), this may reduce performance. You can change it via environment variable GOMAXPROCS or by passing CLI parameter -gomaxprocs", maxprocs, runtime.NumCPU()) } // Start profiler if enabled if profile { resultFile, err := os.Create("perf.out") if err != nil { log.Panicf("Cannot create profiler result file %s", resultFile) } pprof.StartCPUProfile(resultFile) defer pprof.StopCPUProfile() } switch mode { case "httpd": // Run HTTP API server if dir == "" { tdlog.Panicf("Please specify database directory, for example -dir=/tmp/db") } if port == 0 { tdlog.Panicf("Please specify port number, for example -port=8080") } db, err := db.OpenDB(dir) if err != nil { panic(err) } httpapi.Start(db, port) case "example": // Run embedded usage examples embeddedExample() case "bench": // Benchmark scenarios benchmark() case "bench2": benchmark2() default: flag.PrintDefaults() return } }
func main() { var err error var defaultMaxprocs int if defaultMaxprocs, err = strconv.Atoi(os.Getenv("GOMAXPROCS")); err != nil { defaultMaxprocs = runtime.NumCPU() } // Parse CLI parameters // General params var mode string var maxprocs int flag.StringVar(&mode, "mode", "", "Mandatory - specify the execution mode [httpd|bench|bench2|example]") flag.IntVar(&maxprocs, "gomaxprocs", defaultMaxprocs, "GOMAXPROCS") // Debug params var profile, debug bool flag.BoolVar(&tdlog.VerboseLog, "verbose", false, "Turn verbose logging on/off") flag.BoolVar(&profile, "profile", false, "Write profiler results to prof.out") flag.BoolVar(&debug, "debug", false, "Dump goroutine stack traces upon receiving interrupt signal") // HTTP mode params var dir string var bind string var port int var authToken string var tlsCrt, tlsKey string flag.StringVar(&dir, "dir", "", "(HTTP server) database directory") flag.StringVar(&bind, "bind", "", "(HTTP server) bind to IP address (all network interfaces by default)") flag.IntVar(&port, "port", 8080, "(HTTP server) port number") flag.StringVar(&tlsCrt, "tlscrt", "", "(HTTP server) TLS certificate (empty to disable TLS).") flag.StringVar(&tlsKey, "tlskey", "", "(HTTP server) TLS certificate key (empty to disable TLS).") flag.StringVar(&authToken, "authtoken", "", "(HTTP server) Only authorize requests carrying this token in 'Authorization: token TOKEN' header. (empty to disable)") // HTTP + JWT params var jwtPubKey, jwtPrivateKey string flag.StringVar(&jwtPubKey, "jwtpubkey", "", "(HTTP JWT server) Public key for signing tokens (empty to disable JWT)") flag.StringVar(&jwtPrivateKey, "jwtprivatekey", "", "(HTTP JWT server) Private key for decoding tokens (empty to disable JWT)") // Benchmark mode params flag.IntVar(&benchSize, "benchsize", 400000, "Benchmark sample size") flag.BoolVar(&benchCleanup, "benchcleanup", true, "Whether to clean up (delete benchmark DB) after benchmark") flag.Parse() // User must specify a mode to run if mode == "" { flag.PrintDefaults() os.Exit(1) } // Set appropriate GOMAXPROCS runtime.GOMAXPROCS(maxprocs) tdlog.Noticef("GOMAXPROCS is set to %d", maxprocs) // Performance advices if maxprocs < runtime.NumCPU() { tdlog.Noticef("GOMAXPROCS (%d) is less than number of CPUs (%d), this may reduce performance. You can change it via environment variable GOMAXPROCS or by passing CLI parameter -gomaxprocs", maxprocs, runtime.NumCPU()) } linuxPerfAdvice() // Start profiler if enabled if profile { resultFile, err := os.Create("perf.out") if err != nil { tdlog.Noticef("Cannot create profiler result file %s", resultFile) os.Exit(1) } pprof.StartCPUProfile(resultFile) defer pprof.StopCPUProfile() } // Dump goroutine stacktraces upon receiving interrupt signal if debug { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) go func() { for range c { pprof.Lookup("goroutine").WriteTo(os.Stderr, 1) } }() } switch mode { case "httpd": // Run HTTP API server if dir == "" { tdlog.Notice("Please specify database directory, for example -dir=/tmp/db") os.Exit(1) } if port == 0 { tdlog.Notice("Please specify port number, for example -port=8080") os.Exit(1) } if tlsCrt != "" && tlsKey == "" || tlsKey != "" && tlsCrt == "" { tdlog.Notice("To enable HTTPS, please specify both RSA certificate and key file.") os.Exit(1) } if jwtPrivateKey != "" && jwtPubKey == "" || jwtPubKey != "" && jwtPrivateKey == "" { tdlog.Notice("To enable JWT, please specify RSA private and public key.") os.Exit(1) } httpapi.Start(dir, port, tlsCrt, tlsKey, jwtPubKey, jwtPrivateKey, bind, authToken) case "example": // Run embedded usage examples embeddedExample() case "bench": // Benchmark scenarios benchmark() case "bench2": benchmark2() default: flag.PrintDefaults() return } }
// Start HTTP server and block until the server shuts down. Panic on error. func Start(dir string, port int, tlsCrt, tlsKey, jwtPubKey, jwtPrivateKey, bind, authToken string) { var err error HttpDB, err = db.OpenDB(dir) if err != nil { panic(err) } // These endpoints are always available and do not require authentication http.HandleFunc("/", Welcome) http.HandleFunc("/version", Version) http.HandleFunc("/memstats", MemStats) // Install API endpoint handlers that may require authorization var authWrap func(http.HandlerFunc) http.HandlerFunc if authToken != "" { tdlog.Noticef("API endpoints now require the pre-shared token in Authorization header.") authWrap = func(originalHandler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if "token "+authToken != r.Header.Get("Authorization") { http.Error(w, "", http.StatusUnauthorized) return } originalHandler(w, r) } } } else if jwtPubKey != "" && jwtPrivateKey != "" { tdlog.Noticef("API endpoints now require JWT in Authorization header.") var publicKeyContent, privateKeyContent []byte if publicKeyContent, err = ioutil.ReadFile(jwtPubKey); err != nil { panic(err) } else if publicKey, err = jwt.ParseRSAPublicKeyFromPEM(publicKeyContent); err != nil { panic(err) } else if privateKeyContent, err = ioutil.ReadFile(jwtPrivateKey); err != nil { panic(err) } else if privateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKeyContent); err != nil { panic(err) } jwtInitSetup() authWrap = jwtWrap // does not require JWT auth http.HandleFunc("/getjwt", getJWT) http.HandleFunc("/checkjwt", checkJWT) } else { tdlog.Noticef("API endpoints do not require Authorization header.") authWrap = func(originalHandler http.HandlerFunc) http.HandlerFunc { return originalHandler } } // collection management (stop-the-world) http.HandleFunc("/create", authWrap(Create)) http.HandleFunc("/rename", authWrap(Rename)) http.HandleFunc("/drop", authWrap(Drop)) http.HandleFunc("/all", authWrap(All)) http.HandleFunc("/scrub", authWrap(Scrub)) http.HandleFunc("/sync", authWrap(Sync)) // query http.HandleFunc("/query", authWrap(Query)) http.HandleFunc("/count", authWrap(Count)) // document management http.HandleFunc("/insert", authWrap(Insert)) http.HandleFunc("/get", authWrap(Get)) http.HandleFunc("/getpage", authWrap(GetPage)) http.HandleFunc("/update", authWrap(Update)) http.HandleFunc("/delete", authWrap(Delete)) http.HandleFunc("/approxdoccount", authWrap(ApproxDocCount)) // index management (stop-the-world) http.HandleFunc("/index", authWrap(Index)) http.HandleFunc("/indexes", authWrap(Indexes)) http.HandleFunc("/unindex", authWrap(Unindex)) // misc (stop-the-world) http.HandleFunc("/shutdown", authWrap(Shutdown)) http.HandleFunc("/dump", authWrap(Dump)) iface := "all interfaces" if bind != "" { iface = bind } if tlsCrt != "" { tdlog.Noticef("Will listen on %s (HTTPS), port %d.", iface, port) if err := http.ListenAndServeTLS(fmt.Sprintf("%s:%d", bind, port), tlsCrt, tlsKey, nil); err != nil { tdlog.Panicf("Failed to start HTTPS service - %s", err) } } else { tdlog.Noticef("Will listen on %s (HTTP), port %d.", iface, port) http.ListenAndServe(fmt.Sprintf("%s:%d", bind, port), nil) } }