1
0

gosl.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. // gosl is a basic example of how to develop external web services for Second Life/OpenSimulator using the Go programming language.
  2. package main
  3. import (
  4. "bufio"
  5. "compress/bzip2"
  6. "encoding/csv"
  7. "encoding/json"
  8. "flag"
  9. "fmt"
  10. "github.com/tidwall/buntdb"
  11. "github.com/dgraph-io/badger"
  12. "github.com/dgraph-io/badger/options"
  13. "github.com/op/go-logging"
  14. "gopkg.in/natefinch/lumberjack.v2"
  15. "io"
  16. "net/http"
  17. "net/http/fcgi"
  18. _ "net/http/pprof"
  19. "os"
  20. "path/filepath"
  21. "regexp"
  22. "runtime"
  23. "strings"
  24. "time"
  25. )
  26. const NullUUID = "00000000-0000-0000-0000-000000000000" // always useful when we deal with SL/OpenSimulator...
  27. const databaseName = "gosl-database.db" // for BuntDB
  28. // Logging setup.
  29. var log = logging.MustGetLogger("gosl") // configuration for the go-logging logger, must be available everywhere
  30. var logFormat logging.Formatter
  31. // Opt is used for Badger database setup.
  32. var Opt badger.Options
  33. // AvatarUUID is the type that we store in the database; we keep a record from which grid it came from.
  34. type avatarUUID struct {
  35. UUID string // needs to be capitalised for JSON marshalling (it has to do with the way it works)
  36. Grid string
  37. }
  38. var BATCH_BLOCK = 100000 // how many entries to write to the Badger database as a block; the bigger, the faster, but the more memory it consumes
  39. // the authors say that 100000 is way too much
  40. // NOTE(gwyneth): let's see what happens with BuntDB
  41. /*
  42. .__
  43. _____ _____ |__| ____
  44. / \\__ \ | |/ \
  45. | Y Y \/ __ \| | | \
  46. |__|_| (____ /__|___| /
  47. \/ \/ \/
  48. */
  49. // some of these flags need to be global
  50. var noMemory, useBadger *bool
  51. var myDir *string
  52. var dbNamePath string // for BuntDB
  53. // main() starts here.
  54. func main() {
  55. // Flag setup
  56. var myPort = flag.String("port", "3000", "Server port")
  57. myDir = flag.String("dir", "slkvdb", "Directory where database files are stored")
  58. var isServer = flag.Bool("server", false, "Run as server on port " + *myPort)
  59. var isShell = flag.Bool("shell", false, "Run as an interactive shell")
  60. var importFilename = flag.String("import", "", "Import database from W-Hat (use the csv.bz2 version)")
  61. useBadger = flag.Bool("usebadger", false, "Use Badger instead of BuntDB")
  62. noMemory = flag.Bool("nomemory", false, "Attempt to use only disk to save memory on Badger (important for shared webservers)")
  63. // default is FastCGI
  64. flag.Parse()
  65. // We cannot write to stdout if we're running as FastCGI, only to logs!
  66. if *isServer || *isShell {
  67. fmt.Println("gosl is starting...")
  68. } else { // FastCGI: we cannot write to stdio, we need to setup the logger so that we can write to disk
  69. *noMemory = true
  70. }
  71. // Setup the lumberjack rotating logger. This is because we need it for the go-logging logger when writing to files. (20170813)
  72. rotatingLogger := &lumberjack.Logger{
  73. Filename: "gosl.log",
  74. MaxSize: 10, // megabytes
  75. MaxBackups: 3,
  76. MaxAge: 28, //days
  77. }
  78. // Set formatting for stderr and file (basically the same).
  79. logFormat := logging.MustStringFormatter(`%{color}%{time:2006/01/02 15:04:05.0} %{shortfile} - %{shortfunc} ▶ %{level:.4s}%{color:reset} %{message}`) // must be initialised or all hell breaks loose
  80. // Setup the go-logging Logger. Do **not** log to stderr if running as FastCGI!
  81. backendFile := logging.NewLogBackend(rotatingLogger, "", 0)
  82. backendFileFormatter := logging.NewBackendFormatter(backendFile, logFormat)
  83. backendFileLeveled := logging.AddModuleLevel(backendFileFormatter)
  84. backendFileLeveled.SetLevel(logging.INFO, "gosl") // we just send debug data to logs if we run as shell
  85. if *isServer || *isShell {
  86. backendStderr := logging.NewLogBackend(os.Stderr, "", 0)
  87. backendStderrFormatter := logging.NewBackendFormatter(backendStderr, logFormat)
  88. backendStderrLeveled := logging.AddModuleLevel(backendStderrFormatter)
  89. if *isShell {
  90. backendStderrLeveled.SetLevel(logging.DEBUG, "gosl") // shell is meant to be for debugging mostly
  91. } else {
  92. backendStderrLeveled.SetLevel(logging.INFO, "gosl")
  93. }
  94. logging.SetBackend(backendStderrLeveled, backendFileLeveled)
  95. } else {
  96. logging.SetBackend(backendFileLeveled) // FastCGI only logs to file
  97. }
  98. // Check if this directory actually exists; if not, create it. Panic if something wrong happens (we cannot proceed without a valid directory for the database to be written
  99. if stat, err := os.Stat(*myDir); err == nil && stat.IsDir() {
  100. // path is a valid directory
  101. log.Infof("Valid directory: %s\n", *myDir)
  102. } else {
  103. // try to create directory
  104. err = os.Mkdir(*myDir, 0700)
  105. checkErrPanic(err) // cannot make directory, panic and exit logging what went wrong
  106. log.Debugf("Created new directory: %s\n", *myDir)
  107. }
  108. if *useBadger {
  109. Opt = badger.DefaultOptions
  110. Opt.Dir = *myDir
  111. Opt.ValueDir = Opt.Dir
  112. Opt.TableLoadingMode = options.MemoryMap
  113. //Opt.TableLoadingMode = options.FileIO
  114. }
  115. if *noMemory && *useBadger {
  116. // Opt.TableLoadingMode = options.FileIO // use standard file I/O operations for tables instead of LoadRAM
  117. // Opt.TableLoadingMode = options.MemoryMap // MemoryMap indicates that that the file must be memory-mapped - https://github.com/dgraph-io/badger/issues/224#issuecomment-329643771
  118. Opt.TableLoadingMode = options.FileIO
  119. // Opt.ValueLogFileSize = 1048576
  120. Opt.MaxTableSize = 1048576 * 12
  121. Opt.NumMemtables = 0
  122. Opt.MaxLevels = 10
  123. Opt.SyncWrites = false
  124. Opt.NumCompactors = 10
  125. Opt.NumLevelZeroTables = 10
  126. // Opt.maxBatchSize =
  127. // Opt.maxBatchCount =
  128. BATCH_BLOCK = 10000 // try to import less at each time, it will take longer but hopefully work
  129. log.Info("Trying to avoid too much memory consumption")
  130. }
  131. // Do some testing to see if the database is available
  132. const testAvatarName = "Nobody Here"
  133. var err error
  134. log.Info("gosl started and logging is set up. Proceeding to test KV database.")
  135. var testValue = avatarUUID{ NullUUID, "all grids" }
  136. jsonTestValue, err := json.Marshal(testValue)
  137. checkErrPanic(err) // something went VERY wrong
  138. if *useBadger {
  139. kv, err := badger.Open(Opt)
  140. checkErrPanic(err) // should probably panic, cannot prep new database
  141. txn := kv.NewTransaction(true)
  142. err = txn.Set([]byte(testAvatarName), jsonTestValue, 0x00)
  143. checkErrPanic(err)
  144. err = txn.Commit(nil)
  145. checkErrPanic(err)
  146. log.Debugf("SET %+v (json: %v)\n", testValue, string(jsonTestValue))
  147. kv.Close()
  148. } else {
  149. /* NOTE(gwyneth): this fails because pointers to strings do not implement len(). Duh!
  150. if *myDir[len(*myDir)-1] != os.PathSeparator {
  151. *myDir = append(*myDir + os.PathSeparator
  152. } */
  153. dbNamePath = *myDir + string(os.PathSeparator) + databaseName
  154. db, err := buntdb.Open(dbNamePath)
  155. checkErrPanic(err)
  156. err = db.Update(func(tx *buntdb.Tx) error {
  157. _, _, err := tx.Set(testAvatarName, string(jsonTestValue), nil)
  158. return err
  159. })
  160. checkErr(err)
  161. log.Debugf("SET %+v (json: %v)\n", testValue, string(jsonTestValue))
  162. db.Close()
  163. }
  164. // common to both databases:
  165. key, grid := searchKVname(testAvatarName)
  166. log.Debugf("GET '%s' returned '%s' [grid '%s']\n", testAvatarName, key, grid)
  167. log.Info("KV database seems fine.")
  168. if *importFilename != "" {
  169. log.Info("Attempting to import", *importFilename, "...")
  170. importDatabase(*importFilename)
  171. log.Info("Database finished import.")
  172. }
  173. if *isShell {
  174. log.Info("Starting to run as interactive shell")
  175. reader := bufio.NewReader(os.Stdin)
  176. fmt.Println("Ctrl-C to quit.")
  177. var err error // to avoid assigning text in a different scope (this is a bit awkward, but that's the problem with bi-assignment)
  178. var checkInput, avatarName, avatarKey, gridName string
  179. for {
  180. // Prompt and read
  181. fmt.Print("Enter avatar name or UUID: ")
  182. checkInput, err = reader.ReadString('\n')
  183. checkErr(err)
  184. checkInput = strings.TrimRight(checkInput, "\r\n")
  185. // fmt.Printf("Ok, got %s length is %d and UUID is %v\n", checkInput, len(checkInput), isValidUUID(checkInput))
  186. if (len(checkInput) == 36) && isValidUUID(checkInput) {
  187. avatarName, gridName = searchKVUUID(checkInput)
  188. avatarKey = checkInput
  189. } else {
  190. avatarKey, gridName = searchKVname(checkInput)
  191. avatarName = checkInput
  192. }
  193. if avatarName != NullUUID && avatarKey != NullUUID {
  194. fmt.Println(avatarName, "which has UUID:", avatarKey, "comes from grid:", gridName)
  195. } else {
  196. fmt.Println("Sorry, unknown input", checkInput)
  197. }
  198. }
  199. // never leaves until Ctrl-C
  200. }
  201. // set up routing.
  202. // NOTE(gwyneth): one function only because FastCGI seems to have problems with multiple handlers.
  203. http.HandleFunc("/", handler)
  204. log.Info("Directory for database:", *myDir)
  205. if (*isServer) {
  206. log.Info("Starting to run as web server on port " + *myPort)
  207. err := http.ListenAndServe(":" + *myPort, nil) // set listen port
  208. checkErrPanic(err) // if it can't listen to all the above, then it has to abort anyway
  209. } else {
  210. // default is to run as FastCGI!
  211. // works like a charm thanks to http://www.dav-muz.net/blog/2013/09/how-to-use-go-and-fastcgi/
  212. log.Debug("http.DefaultServeMux is", http.DefaultServeMux)
  213. if err := fcgi.Serve(nil, nil); err != nil {
  214. checkErrPanic(err)
  215. }
  216. }
  217. // we should never have reached this point!
  218. log.Error("Unknown usage! This application may run as a standalone server, as FastCGI application, or as an interactive shell")
  219. if *isServer || *isShell {
  220. flag.PrintDefaults()
  221. }
  222. }
  223. // handler deals with incoming queries and/or associates avatar names with keys depending on parameters.
  224. // Basically we check if both an avatar name and a UUID key has been received: if yes, this means a new entry;
  225. // if just the avatar name was received, it means looking up its key;
  226. // if just the key was received, it means looking up the name (not necessary since llKey2Name does that, but it's just to illustrate);
  227. // if nothing is received, then return an error
  228. func handler(w http.ResponseWriter, r *http.Request) {
  229. if err := r.ParseForm(); err != nil {
  230. logErrHTTP(w, http.StatusNotFound, "No avatar and/or UUID received")
  231. return
  232. }
  233. // test first if this comes from Second Life or OpenSimulator
  234. /*
  235. if r.Header.Get("X-Secondlife-Region") == "" {
  236. logErrHTTP(w, http.StatusForbidden, "Sorry, this application only works inside Second Life.")
  237. return
  238. }
  239. */
  240. name := r.Form.Get("name") // can be empty
  241. key := r.Form.Get("key") // can be empty
  242. compat := r.Form.Get("compat") // compatibility mode with W-Hat
  243. var valueToInsert avatarUUID
  244. messageToSL := "" // this is what we send back to SL - defined here due to scope issues.
  245. if name != "" {
  246. if key != "" {
  247. // we received both: add a new entry
  248. valueToInsert.UUID = key
  249. valueToInsert.Grid = r.Header.Get("X-Secondlife-Shard")
  250. jsonValueToInsert, err := json.Marshal(valueToInsert)
  251. checkErr(err)
  252. if *useBadger {
  253. kv, err := badger.Open(Opt)
  254. checkErrPanic(err) // should probably panic
  255. txn := kv.NewTransaction(true)
  256. defer txn.Discard()
  257. err = txn.Set([]byte(name), jsonValueToInsert, 0x00)
  258. checkErrPanic(err)
  259. err = txn.Commit(nil)
  260. checkErrPanic(err)
  261. kv.Close()
  262. } else {
  263. db, err := buntdb.Open(dbNamePath)
  264. checkErrPanic(err)
  265. defer db.Close()
  266. err = db.Update(func(tx *buntdb.Tx) error {
  267. _, _, err := tx.Set(name, string(jsonValueToInsert), nil)
  268. return err
  269. })
  270. checkErr(err)
  271. }
  272. messageToSL += "Added new entry for '" + name + "' which is: " + valueToInsert.UUID + " from grid: '" + valueToInsert.Grid + "'"
  273. } else {
  274. // we received a name: look up its UUID key and grid.
  275. key, grid := searchKVname(name)
  276. if compat == "false" {
  277. messageToSL += "UUID for '" + name + "' is: " + key + " from grid: '" + grid + "'"
  278. } else { // empty also means true!
  279. messageToSL += key
  280. }
  281. }
  282. } else if key != "" {
  283. // in this scenario, we have the UUID key but no avatar name: do the equivalent of a llKey2Name (slow)
  284. name, grid := searchKVUUID(key)
  285. if compat == "false" {
  286. messageToSL += "Avatar name for " + key + "' is '" + name + "' on grid: '" + grid + "'"
  287. } else { // empty also means true!
  288. messageToSL += name
  289. }
  290. } else {
  291. // neither UUID key nor avatar received, this is an error
  292. logErrHTTP(w, http.StatusNotFound, "Empty avatar name and UUID key received, cannot proceed")
  293. return
  294. }
  295. w.WriteHeader(http.StatusOK)
  296. w.Header().Set("Content-type", "text/plain; charset=utf-8")
  297. fmt.Fprintf(w, messageToSL)
  298. }
  299. // searchKVname searches the KV database for an avatar name.
  300. func searchKVname(avatarName string) (UUID string, grid string) {
  301. var val = avatarUUID{ NullUUID, "" }
  302. time_start := time.Now()
  303. var err error // to deal with scope issues
  304. if *useBadger {
  305. kv, err := badger.Open(Opt)
  306. checkErrPanic(err)
  307. defer kv.Close()
  308. err = kv.View(func(txn *badger.Txn) error {
  309. item, err := txn.Get([]byte(avatarName))
  310. if err != nil {
  311. return err
  312. }
  313. data, err := item.Value()
  314. if err != nil {
  315. log.Errorf("Error '%s' while getting data from %v\n", err, item)
  316. return err
  317. }
  318. err = json.Unmarshal(data, &val)
  319. if err != nil {
  320. log.Errorf("Error while unparsing UUID for name: '%s' (%v)\n", avatarName, err)
  321. return err
  322. }
  323. return nil
  324. })
  325. } else {
  326. db, err := buntdb.Open(dbNamePath)
  327. checkErrPanic(err)
  328. defer db.Close()
  329. var data string
  330. err = db.View(func(tx *buntdb.Tx) error {
  331. data, err = tx.Get(avatarName)
  332. return err
  333. })
  334. err = json.Unmarshal([]byte(data), &val)
  335. if err != nil {
  336. log.Errorf("Error while unparsing UUID for name: '%s' (%v)\n", avatarName, err)
  337. }
  338. }
  339. time_end := time.Now()
  340. diffTime := time_end.Sub(time_start)
  341. log.Debugf("Time to lookup '%s': %v\n", avatarName, diffTime)
  342. if err != nil {
  343. return NullUUID, ""
  344. } // else:
  345. return val.UUID, val.Grid
  346. }
  347. // searchKVUUID searches the KV database for an avatar key.
  348. func searchKVUUID(avatarKey string) (name string, grid string) {
  349. time_start := time.Now()
  350. checks := 0
  351. var val = avatarUUID{ NullUUID, "" }
  352. var found string
  353. if *useBadger {
  354. kv, err := badger.Open(Opt)
  355. checkErr(err) // should probably panic
  356. itOpt := badger.DefaultIteratorOptions
  357. if !*noMemory {
  358. itOpt.PrefetchValues = true
  359. itOpt.PrefetchSize = 1000 // attempt to get this a little bit more efficient; we have many small entries, so this is not too much
  360. } else {
  361. itOpt.PrefetchValues = false
  362. }
  363. txn := kv.NewTransaction(true)
  364. defer txn.Discard()
  365. err = kv.View(func(txn *badger.Txn) error {
  366. itr := txn.NewIterator(itOpt)
  367. defer itr.Close()
  368. for itr.Rewind(); itr.Valid(); itr.Next() {
  369. item := itr.Item()
  370. data, err := item.Value()
  371. if err != nil {
  372. log.Errorf("Error '%s' while getting data from %v\n", err, item)
  373. return err
  374. }
  375. err = json.Unmarshal(data, &val)
  376. if err != nil {
  377. log.Errorf("Error '%s' while unparsing UUID for data: %v\n", err, data)
  378. return err
  379. }
  380. checks++ //Just to see how many checks we made, for statistical purposes
  381. if avatarKey == val.UUID {
  382. found = string(item.Key())
  383. break
  384. }
  385. }
  386. return nil
  387. })
  388. kv.Close()
  389. } else {
  390. db, err := buntdb.Open(dbNamePath)
  391. checkErrPanic(err)
  392. err = db.View(func(tx *buntdb.Tx) error {
  393. err := tx.Ascend("", func(key, value string) bool {
  394. err = json.Unmarshal([]byte(value), &val)
  395. if err != nil {
  396. log.Errorf("Error '%s' while unparsing UUID for value: %v\n", err, value)
  397. }
  398. checks++ //Just to see how many checks we made, for statistical purposes
  399. if avatarKey == val.UUID {
  400. found = key
  401. return false
  402. }
  403. return true
  404. })
  405. return err
  406. })
  407. db.Close()
  408. }
  409. time_end := time.Now()
  410. diffTime := time_end.Sub(time_start)
  411. log.Debugf("Made %d checks for '%s' in %v\n", checks, avatarKey, diffTime)
  412. return found, val.Grid
  413. }
  414. // importDatabase is essentially reading a bzip2'ed CSV file with UUID,AvatarName downloaded from http://w-hat.com/#name2key .
  415. // One could theoretically set a cron job to get this file, save it on disk periodically, and keep the database up-to-date
  416. // see https://stackoverflow.com/questions/24673335/how-do-i-read-a-gzipped-csv-file for the actual usage of these complicated things!
  417. func importDatabase(filename string) {
  418. filehandler, err := os.Open(filename)
  419. if err != nil {
  420. log.Fatal(err)
  421. }
  422. defer filehandler.Close()
  423. gr := bzip2.NewReader(filehandler) // open bzip2 reader
  424. cr := csv.NewReader(gr) // open csv reader and feed the bzip2 reader into it
  425. limit := 0 // outside of for loop so that we can count how many entries we had in total
  426. time_start := time.Now() // we want to get an idea on how long this takes
  427. if *useBadger {
  428. // prepare connection to KV database
  429. kv, err := badger.Open(Opt)
  430. checkErrPanic(err) // should probably panic
  431. defer kv.Close()
  432. txn := kv.NewTransaction(true) // start new transaction; we will commit only every BATCH_BLOCK entries
  433. defer txn.Discard()
  434. for ;;limit++ {
  435. record, err := cr.Read()
  436. if err == io.EOF {
  437. break
  438. } else if err != nil {
  439. log.Fatal(err)
  440. }
  441. jsonNewEntry, err := json.Marshal(avatarUUID{ record[0], "Production" }) // W-Hat keys come all from the main LL grid, known as 'Production'
  442. if err != nil {
  443. log.Warning(err)
  444. } else {
  445. err = txn.Set([]byte(record[1]), jsonNewEntry, 0x00)
  446. if err != nil {
  447. log.Fatal(err)
  448. }
  449. }
  450. if limit % BATCH_BLOCK == 0 && limit != 0 { // we do not run on the first time, and then only every BATCH_BLOCK times
  451. log.Info("Processing:", limit)
  452. err = txn.Commit(nil)
  453. if err != nil {
  454. log.Fatal(err)
  455. }
  456. txn = kv.NewTransaction(true) // start a new transaction
  457. defer txn.Discard()
  458. }
  459. }
  460. // commit last batch
  461. err = txn.Commit(nil)
  462. if err != nil {
  463. log.Fatal(err)
  464. }
  465. kv.PurgeOlderVersions()
  466. } else {
  467. db, err := buntdb.Open(dbNamePath)
  468. checkErrPanic(err)
  469. defer db.Close()
  470. txn, err := db.Begin(true)
  471. checkErrPanic(err)
  472. //defer txn.Commit()
  473. // very similar to Badger code...
  474. for ;;limit++ {
  475. record, err := cr.Read()
  476. if err == io.EOF {
  477. break
  478. } else if err != nil {
  479. log.Fatal(err)
  480. }
  481. jsonNewEntry, err := json.Marshal(avatarUUID{ record[0], "Production" }) // W-Hat keys come all from the main LL grid, known as 'Production'
  482. if err != nil {
  483. log.Warning(err)
  484. } else {
  485. _, _, err = txn.Set(record[1], string(jsonNewEntry), nil)
  486. if err != nil {
  487. log.Fatal(err)
  488. }
  489. }
  490. if limit % BATCH_BLOCK == 0 && limit != 0 { // we do not run on the first time, and then only every BATCH_BLOCK times
  491. log.Info("Processing:", limit)
  492. err = txn.Commit()
  493. if err != nil {
  494. log.Fatal(err)
  495. }
  496. txn, err = db.Begin(true) // start a new transaction
  497. checkErrPanic(err)
  498. //defer txn.Commit()
  499. }
  500. }
  501. // commit last batch
  502. err = txn.Commit()
  503. if err != nil {
  504. log.Fatal(err)
  505. }
  506. db.Shrink()
  507. }
  508. time_end := time.Now()
  509. diffTime := time_end.Sub(time_start)
  510. log.Info("Total read", limit, "records (or thereabouts) in", diffTime)
  511. }
  512. // NOTE(gwyneth):Auxiliary functions which I'm always using...
  513. // checkErrPanic logs a fatal error and panics.
  514. func checkErrPanic(err error) {
  515. if err != nil {
  516. pc, file, line, ok := runtime.Caller(1)
  517. log.Panic(filepath.Base(file), ":", line, ":", pc, ok, " - panic:", err)
  518. }
  519. }
  520. // checkErr checks if there is an error, and if yes, it logs it out and continues.
  521. // this is for 'normal' situations when we want to get a log if something goes wrong but do not need to panic
  522. func checkErr(err error) {
  523. if err != nil {
  524. pc, file, line, ok := runtime.Caller(1)
  525. log.Error(filepath.Base(file), ":", line, ":", pc, ok, " - error:", err)
  526. }
  527. }
  528. // Auxiliary functions for HTTP handling
  529. // checkErrHTTP returns an error via HTTP and also logs the error.
  530. func checkErrHTTP(w http.ResponseWriter, httpStatus int, errorMessage string, err error) {
  531. if err != nil {
  532. http.Error(w, fmt.Sprintf(errorMessage, err), httpStatus)
  533. pc, file, line, ok := runtime.Caller(1)
  534. log.Error("(", http.StatusText(httpStatus), ") ", filepath.Base(file), ":", line, ":", pc, ok, " - error:", errorMessage, err)
  535. }
  536. }
  537. // checkErrPanicHTTP returns an error via HTTP and logs the error with a panic.
  538. func checkErrPanicHTTP(w http.ResponseWriter, httpStatus int, errorMessage string, err error) {
  539. if err != nil {
  540. http.Error(w, fmt.Sprintf(errorMessage, err), httpStatus)
  541. pc, file, line, ok := runtime.Caller(1)
  542. log.Panic("(", http.StatusText(httpStatus), ") ", filepath.Base(file), ":", line, ":", pc, ok, " - panic:", errorMessage, err)
  543. }
  544. }
  545. // logErrHTTP assumes that the error message was already composed and writes it to HTTP and logs it.
  546. // this is mostly to avoid code duplication and make sure that all entries are written similarly
  547. func logErrHTTP(w http.ResponseWriter, httpStatus int, errorMessage string) {
  548. http.Error(w, errorMessage, httpStatus)
  549. log.Error("(" + http.StatusText(httpStatus) + ") " + errorMessage)
  550. }
  551. // funcName is @Sonia's solution to get the name of the function that Go is currently running.
  552. // This will be extensively used to deal with figuring out where in the code the errors are!
  553. // Source: https://stackoverflow.com/a/10743805/1035977 (20170708)
  554. func funcName() string {
  555. pc, _, _, _ := runtime.Caller(1)
  556. return runtime.FuncForPC(pc).Name()
  557. }
  558. // isValidUUID checks if the UUID is valid.
  559. // Thanks to Patrick D'Appollonio https://stackoverflow.com/questions/25051675/how-to-validate-uuid-v4-in-go
  560. func isValidUUID(uuid string) bool {
  561. r := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")
  562. return r.MatchString(uuid)
  563. }