2016-03-05 02:18:33 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2016-09-12 07:09:02 +03:00
|
|
|
"net"
|
2016-03-20 04:31:59 +03:00
|
|
|
"net/http"
|
2019-03-14 19:24:18 +03:00
|
|
|
_ "net/http/pprof"
|
2016-03-05 02:18:33 +03:00
|
|
|
"os"
|
2017-02-24 17:05:28 +03:00
|
|
|
"os/signal"
|
2016-03-05 02:18:33 +03:00
|
|
|
"runtime"
|
2019-03-14 19:24:18 +03:00
|
|
|
"runtime/pprof"
|
2016-03-05 02:18:33 +03:00
|
|
|
"strconv"
|
2016-03-08 03:37:39 +03:00
|
|
|
"strings"
|
2017-02-24 17:05:28 +03:00
|
|
|
"sync"
|
|
|
|
"syscall"
|
2016-03-05 02:18:33 +03:00
|
|
|
|
2018-10-11 00:25:40 +03:00
|
|
|
"github.com/tidwall/tile38/core"
|
|
|
|
"github.com/tidwall/tile38/internal/hservice"
|
|
|
|
"github.com/tidwall/tile38/internal/log"
|
2018-10-29 01:49:45 +03:00
|
|
|
"github.com/tidwall/tile38/internal/server"
|
2016-09-12 07:09:02 +03:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
"google.golang.org/grpc"
|
2016-03-05 02:18:33 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2016-03-08 16:11:03 +03:00
|
|
|
dir string
|
|
|
|
port int
|
|
|
|
host string
|
|
|
|
verbose bool
|
|
|
|
veryVerbose bool
|
|
|
|
devMode bool
|
|
|
|
quiet bool
|
2017-02-24 17:05:28 +03:00
|
|
|
pidfile string
|
2019-03-14 19:24:18 +03:00
|
|
|
cpuprofile string
|
|
|
|
memprofile string
|
|
|
|
pprofport int
|
2019-03-19 23:49:35 +03:00
|
|
|
nohup bool
|
2016-03-05 02:18:33 +03:00
|
|
|
)
|
|
|
|
|
2017-01-13 18:45:28 +03:00
|
|
|
// TODO: Set to false in 2.*
|
2018-11-06 01:24:45 +03:00
|
|
|
var httpTransport = true
|
2017-01-13 18:45:28 +03:00
|
|
|
|
2019-03-14 19:24:18 +03:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
2016-12-29 18:50:54 +03:00
|
|
|
// Fire up a webhook test server by using the --webhook-http-consumer-port
|
2016-05-23 23:10:43 +03:00
|
|
|
// for example
|
2016-09-12 07:09:02 +03:00
|
|
|
// $ ./tile38-server --webhook-http-consumer-port 9999
|
2016-05-23 23:10:43 +03:00
|
|
|
//
|
|
|
|
// The create hooks like such...
|
|
|
|
// SETHOOK myhook http://localhost:9999/myhook NEARBY mykey FENCE POINT 33.5 -115.5 1000
|
2019-03-14 19:24:18 +03:00
|
|
|
//
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// Memory profiling - start the server with the -pprofport flag
|
|
|
|
//
|
|
|
|
// $ ./tile38-server -pprofport 6060
|
|
|
|
//
|
|
|
|
// Then, at any point, from a different terminal execute:
|
|
|
|
// $ go tool pprof -svg http://localhost:6060/debug/pprof/heap > out.svg
|
|
|
|
//
|
|
|
|
// Load the SVG into a web browser to visualize the memory usage
|
|
|
|
//
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2016-05-23 23:10:43 +03:00
|
|
|
|
2016-09-12 07:09:02 +03:00
|
|
|
type hserver struct{}
|
|
|
|
|
|
|
|
func (s *hserver) Send(ctx context.Context, in *hservice.MessageRequest) (*hservice.MessageReply, error) {
|
2018-11-06 01:24:45 +03:00
|
|
|
return &hservice.MessageReply{Ok: true}, nil
|
2016-09-12 07:09:02 +03:00
|
|
|
}
|
|
|
|
|
2016-03-05 02:18:33 +03:00
|
|
|
func main() {
|
2018-04-11 20:53:36 +03:00
|
|
|
gitsha := " (" + core.GitSHA + ")"
|
|
|
|
if gitsha == " (0000000)" {
|
|
|
|
gitsha = ""
|
|
|
|
}
|
|
|
|
versionLine := `tile38-server version: ` + core.Version + gitsha
|
|
|
|
|
|
|
|
output := os.Stderr
|
|
|
|
flag.Usage = func() {
|
|
|
|
fmt.Fprintf(output,
|
|
|
|
versionLine+`
|
|
|
|
|
|
|
|
Usage: tile38-server [-p port]
|
|
|
|
|
|
|
|
Basic Options:
|
|
|
|
-h hostname : listening host
|
|
|
|
-p port : listening port (default: 9851)
|
|
|
|
-d path : data directory (default: data)
|
|
|
|
-q : no logging. totally silent output
|
|
|
|
-v : enable verbose logging
|
|
|
|
-vv : enable very verbose logging
|
|
|
|
|
|
|
|
Advanced Options:
|
|
|
|
--pidfile path : file that contains the pid
|
|
|
|
--appendonly yes/no : AOF persistence (default: yes)
|
2018-09-04 22:26:10 +03:00
|
|
|
--appendfilename path : AOF path (default: data/appendonly.aof)
|
|
|
|
--queuefilename path : Event queue path (default:data/queue.db)
|
2018-04-11 20:53:36 +03:00
|
|
|
--http-transport yes/no : HTTP transport (default: yes)
|
|
|
|
--protected-mode yes/no : protected mode (default: yes)
|
2018-10-29 01:49:45 +03:00
|
|
|
--threads num : number of network threads (default: num cores)
|
2020-08-17 11:18:17 +03:00
|
|
|
--nohup : do not exit on SIGHUP
|
2018-04-11 20:53:36 +03:00
|
|
|
|
|
|
|
Developer Options:
|
|
|
|
--dev : enable developer mode
|
|
|
|
--webhook-http-consumer-port port : Start a test HTTP webhook server
|
|
|
|
--webhook-grpc-consumer-port port : Start a test GRPC webhook server
|
2018-04-13 03:18:59 +03:00
|
|
|
|
2018-04-11 20:53:36 +03:00
|
|
|
`,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2016-09-12 07:09:02 +03:00
|
|
|
if len(os.Args) == 3 && os.Args[1] == "--webhook-http-consumer-port" {
|
2017-10-03 18:49:33 +03:00
|
|
|
log.SetOutput(os.Stderr)
|
2016-03-20 04:31:59 +03:00
|
|
|
port, err := strconv.ParseUint(os.Args[2], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
data, err := ioutil.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
2016-09-12 07:09:02 +03:00
|
|
|
log.Fatal(err)
|
2016-03-20 04:31:59 +03:00
|
|
|
}
|
|
|
|
log.HTTPf("http: %s : %s", r.URL.Path, string(data))
|
|
|
|
})
|
|
|
|
log.Infof("webhook server http://localhost:%d/", port)
|
|
|
|
if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-09-12 07:09:02 +03:00
|
|
|
if len(os.Args) == 3 && os.Args[1] == "--webhook-grpc-consumer-port" {
|
2017-10-03 18:49:33 +03:00
|
|
|
log.SetOutput(os.Stderr)
|
2016-09-12 07:09:02 +03:00
|
|
|
port, err := strconv.ParseUint(os.Args[2], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
s := grpc.NewServer()
|
|
|
|
hservice.RegisterHookServiceServer(s, &hserver{})
|
|
|
|
log.Infof("webhook server grpc://localhost:%d/", port)
|
|
|
|
if err := s.Serve(lis); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-04-26 21:50:49 +03:00
|
|
|
var showEvioDisabled bool
|
|
|
|
|
2016-03-08 03:37:39 +03:00
|
|
|
// parse non standard args.
|
|
|
|
nargs := []string{os.Args[0]}
|
|
|
|
for i := 1; i < len(os.Args); i++ {
|
|
|
|
switch os.Args[i] {
|
2018-04-11 20:53:36 +03:00
|
|
|
case "--help":
|
|
|
|
output = os.Stdout
|
|
|
|
flag.Usage()
|
|
|
|
return
|
|
|
|
case "--version":
|
|
|
|
fmt.Fprintf(os.Stdout, "%s\n", versionLine)
|
|
|
|
return
|
2016-03-08 03:37:39 +03:00
|
|
|
case "--protected-mode", "-protected-mode":
|
|
|
|
i++
|
|
|
|
if i < len(os.Args) {
|
|
|
|
switch strings.ToLower(os.Args[i]) {
|
|
|
|
case "no":
|
2018-11-15 23:45:36 +03:00
|
|
|
core.ProtectedMode = "no"
|
2018-10-29 01:49:45 +03:00
|
|
|
continue
|
2016-03-08 03:37:39 +03:00
|
|
|
case "yes":
|
2018-11-15 23:45:36 +03:00
|
|
|
core.ProtectedMode = "yes"
|
2018-10-29 01:49:45 +03:00
|
|
|
continue
|
2016-03-08 03:37:39 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "protected-mode must be 'yes' or 'no'\n")
|
|
|
|
os.Exit(1)
|
|
|
|
case "--dev", "-dev":
|
|
|
|
devMode = true
|
|
|
|
continue
|
2019-03-19 23:49:35 +03:00
|
|
|
case "--nohup", "-nohup":
|
|
|
|
nohup = true
|
|
|
|
continue
|
2018-04-11 20:53:36 +03:00
|
|
|
case "--appendonly", "-appendonly":
|
|
|
|
i++
|
|
|
|
if i < len(os.Args) {
|
|
|
|
switch strings.ToLower(os.Args[i]) {
|
|
|
|
case "no":
|
2018-11-06 01:58:43 +03:00
|
|
|
core.AppendOnly = false
|
2018-10-29 01:49:45 +03:00
|
|
|
continue
|
2018-04-11 20:53:36 +03:00
|
|
|
case "yes":
|
2018-11-06 01:58:43 +03:00
|
|
|
core.AppendOnly = true
|
2018-10-29 01:49:45 +03:00
|
|
|
continue
|
2018-04-11 20:53:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "appendonly must be 'yes' or 'no'\n")
|
|
|
|
os.Exit(1)
|
|
|
|
case "--appendfilename", "-appendfilename":
|
|
|
|
i++
|
|
|
|
if i == len(os.Args) || os.Args[i] == "" {
|
|
|
|
fmt.Fprintf(os.Stderr, "appendfilename must have a value\n")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
core.AppendFileName = os.Args[i]
|
2018-09-04 22:26:10 +03:00
|
|
|
case "--queuefilename", "-queuefilename":
|
|
|
|
i++
|
|
|
|
if i == len(os.Args) || os.Args[i] == "" {
|
|
|
|
fmt.Fprintf(os.Stderr, "queuefilename must have a value\n")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
core.QueueFileName = os.Args[i]
|
2017-01-13 18:53:01 +03:00
|
|
|
case "--http-transport", "-http-transport":
|
2017-01-13 18:45:28 +03:00
|
|
|
i++
|
|
|
|
if i < len(os.Args) {
|
|
|
|
switch strings.ToLower(os.Args[i]) {
|
|
|
|
case "1", "true", "yes":
|
|
|
|
httpTransport = true
|
2018-10-29 01:49:45 +03:00
|
|
|
continue
|
2017-01-13 18:45:28 +03:00
|
|
|
case "0", "false", "no":
|
|
|
|
httpTransport = false
|
2018-10-29 01:49:45 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "http-transport must be 'yes' or 'no'\n")
|
|
|
|
os.Exit(1)
|
|
|
|
case "--threads", "-threads":
|
|
|
|
i++
|
|
|
|
if i < len(os.Args) {
|
|
|
|
n, err := strconv.ParseUint(os.Args[i], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "threads must be a valid number\n")
|
|
|
|
os.Exit(1)
|
2017-01-13 18:45:28 +03:00
|
|
|
}
|
2018-10-29 01:49:45 +03:00
|
|
|
core.NumThreads = int(n)
|
2017-01-13 18:45:28 +03:00
|
|
|
continue
|
|
|
|
}
|
2017-01-13 18:53:01 +03:00
|
|
|
fmt.Fprintf(os.Stderr, "http-transport must be 'yes' or 'no'\n")
|
|
|
|
os.Exit(1)
|
2018-11-06 01:58:43 +03:00
|
|
|
case "--evio", "-evio":
|
|
|
|
i++
|
|
|
|
if i < len(os.Args) {
|
|
|
|
switch strings.ToLower(os.Args[i]) {
|
2019-04-26 21:50:49 +03:00
|
|
|
case "no", "yes":
|
|
|
|
showEvioDisabled = true
|
2018-11-06 01:58:43 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "evio must be 'yes' or 'no'\n")
|
|
|
|
os.Exit(1)
|
2016-03-08 03:37:39 +03:00
|
|
|
}
|
|
|
|
nargs = append(nargs, os.Args[i])
|
|
|
|
}
|
|
|
|
os.Args = nargs
|
|
|
|
|
2016-03-05 23:39:36 +03:00
|
|
|
flag.IntVar(&port, "p", 9851, "The listening port.")
|
2017-02-24 17:05:28 +03:00
|
|
|
flag.StringVar(&pidfile, "pidfile", "", "A file that contains the pid")
|
2016-03-08 03:37:39 +03:00
|
|
|
flag.StringVar(&host, "h", "", "The listening host.")
|
2016-03-05 02:18:33 +03:00
|
|
|
flag.StringVar(&dir, "d", "data", "The data directory.")
|
|
|
|
flag.BoolVar(&verbose, "v", false, "Enable verbose logging.")
|
|
|
|
flag.BoolVar(&quiet, "q", false, "Quiet logging. Totally silent.")
|
|
|
|
flag.BoolVar(&veryVerbose, "vv", false, "Enable very verbose logging.")
|
2019-03-14 19:24:18 +03:00
|
|
|
flag.IntVar(&pprofport, "pprofport", 0, "pprofport http at port")
|
|
|
|
flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to `file`")
|
|
|
|
flag.StringVar(&memprofile, "memprofile", "", "write memory profile to `file`")
|
2016-03-05 02:18:33 +03:00
|
|
|
flag.Parse()
|
2017-01-13 18:45:28 +03:00
|
|
|
|
2016-03-05 02:18:33 +03:00
|
|
|
var logw io.Writer = os.Stderr
|
2017-10-03 18:53:09 +03:00
|
|
|
if quiet {
|
|
|
|
logw = ioutil.Discard
|
|
|
|
}
|
2017-10-03 18:49:33 +03:00
|
|
|
log.SetOutput(logw)
|
2016-03-05 02:18:33 +03:00
|
|
|
if quiet {
|
2017-10-03 18:49:33 +03:00
|
|
|
log.Level = 0
|
|
|
|
} else if veryVerbose {
|
|
|
|
log.Level = 3
|
|
|
|
} else if verbose {
|
|
|
|
log.Level = 2
|
|
|
|
} else {
|
|
|
|
log.Level = 1
|
2016-03-05 02:18:33 +03:00
|
|
|
}
|
2016-03-06 17:55:00 +03:00
|
|
|
core.DevMode = devMode
|
|
|
|
core.ShowDebugMessages = veryVerbose
|
2016-03-08 03:37:39 +03:00
|
|
|
|
|
|
|
hostd := ""
|
|
|
|
if host != "" {
|
|
|
|
hostd = "Addr: " + host + ", "
|
|
|
|
}
|
2017-02-24 17:05:28 +03:00
|
|
|
|
2019-03-14 19:24:18 +03:00
|
|
|
// pprof
|
|
|
|
if cpuprofile != "" {
|
|
|
|
log.Debugf("cpuprofile active")
|
|
|
|
f, err := os.Create(cpuprofile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("could not create CPU profile: ", err)
|
|
|
|
}
|
|
|
|
if err := pprof.StartCPUProfile(f); err != nil {
|
|
|
|
log.Fatal("could not start CPU profile: ", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if memprofile != "" {
|
|
|
|
log.Debug("memprofile active")
|
|
|
|
}
|
|
|
|
|
|
|
|
var pprofcleanedup bool
|
|
|
|
var pprofcleanupMu sync.Mutex
|
|
|
|
pprofcleanup := func() {
|
|
|
|
pprofcleanupMu.Lock()
|
|
|
|
defer pprofcleanupMu.Unlock()
|
|
|
|
if pprofcleanedup {
|
2017-02-24 17:05:28 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
// cleanup code
|
2019-03-14 19:24:18 +03:00
|
|
|
if cpuprofile != "" {
|
|
|
|
pprof.StopCPUProfile()
|
2017-02-24 17:05:28 +03:00
|
|
|
}
|
2019-03-14 19:24:18 +03:00
|
|
|
if memprofile != "" {
|
|
|
|
f, err := os.Create(memprofile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("could not create memory profile: ", err)
|
|
|
|
}
|
|
|
|
runtime.GC() // get up-to-date statistics
|
|
|
|
if err := pprof.WriteHeapProfile(f); err != nil {
|
|
|
|
log.Fatal("could not write memory profile: ", err)
|
|
|
|
}
|
|
|
|
f.Close()
|
|
|
|
}
|
|
|
|
pprofcleanedup = true
|
2017-02-24 17:05:28 +03:00
|
|
|
}
|
2019-03-14 19:24:18 +03:00
|
|
|
defer pprofcleanup()
|
2017-02-24 17:05:28 +03:00
|
|
|
|
2019-03-14 19:24:18 +03:00
|
|
|
if pprofport != 0 {
|
|
|
|
log.Debugf("pprof http at port %d", pprofport)
|
|
|
|
go func() {
|
|
|
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", pprofport), nil))
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
// pid file
|
|
|
|
var pidferr error
|
|
|
|
var pidcleanedup bool
|
|
|
|
var pidcleanupMu sync.Mutex
|
|
|
|
pidcleanup := func() {
|
|
|
|
if pidfile != "" {
|
|
|
|
pidcleanupMu.Lock()
|
|
|
|
defer pidcleanupMu.Unlock()
|
|
|
|
if pidcleanedup {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// cleanup code
|
|
|
|
if pidfile != "" {
|
|
|
|
os.Remove(pidfile)
|
|
|
|
}
|
|
|
|
pidcleanedup = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
defer pidcleanup()
|
2017-02-24 17:05:28 +03:00
|
|
|
if pidfile != "" {
|
|
|
|
pidferr := ioutil.WriteFile(pidfile, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0666)
|
|
|
|
if pidferr == nil {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c := make(chan os.Signal, 1)
|
|
|
|
|
|
|
|
signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
|
|
|
go func() {
|
2019-03-12 18:40:27 +03:00
|
|
|
for s := range c {
|
2019-03-19 23:49:35 +03:00
|
|
|
if s == syscall.SIGHUP && nohup {
|
2019-03-12 18:40:27 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
log.Warnf("signal: %v", s)
|
2019-03-14 19:24:18 +03:00
|
|
|
pidcleanup()
|
|
|
|
pprofcleanup()
|
2019-03-12 18:40:27 +03:00
|
|
|
switch {
|
|
|
|
default:
|
|
|
|
os.Exit(-1)
|
2019-03-19 23:49:35 +03:00
|
|
|
case s == syscall.SIGHUP:
|
|
|
|
os.Exit(1)
|
2019-03-12 18:40:27 +03:00
|
|
|
case s == syscall.SIGINT:
|
|
|
|
os.Exit(2)
|
|
|
|
case s == syscall.SIGQUIT:
|
|
|
|
os.Exit(3)
|
|
|
|
case s == syscall.SIGTERM:
|
|
|
|
os.Exit(0xf)
|
|
|
|
}
|
2017-02-24 17:05:28 +03:00
|
|
|
}
|
|
|
|
}()
|
2016-03-05 02:18:33 +03:00
|
|
|
|
|
|
|
// _____ _ _ ___ ___
|
|
|
|
// |_ _|_| |___|_ | . |
|
|
|
|
// | | | | | -_|_ | . |
|
|
|
|
// |_| |_|_|___|___|___|
|
|
|
|
fmt.Fprintf(logw, `
|
|
|
|
_______ _______
|
|
|
|
| | |
|
2016-03-14 02:39:04 +03:00
|
|
|
|____ | _ | Tile38 %s%s %d bit (%s/%s)
|
2016-03-08 03:37:39 +03:00
|
|
|
| | | %sPort: %d, PID: %d
|
2017-01-13 18:53:01 +03:00
|
|
|
|____ | _ |
|
2018-06-16 21:40:22 +03:00
|
|
|
| | | tile38.com
|
2017-02-12 17:12:15 +03:00
|
|
|
|_______|_______|
|
2017-01-13 18:53:01 +03:00
|
|
|
`+"\n", core.Version, gitsha, strconv.IntSize, runtime.GOARCH, runtime.GOOS, hostd, port, os.Getpid())
|
2017-02-24 17:05:28 +03:00
|
|
|
if pidferr != nil {
|
|
|
|
log.Warnf("pidfile: %v", pidferr)
|
|
|
|
}
|
2019-04-26 21:50:49 +03:00
|
|
|
if showEvioDisabled {
|
|
|
|
// we don't currently support evio in Tile38
|
|
|
|
log.Warnf("evio is not currently supported")
|
|
|
|
}
|
2018-10-29 01:49:45 +03:00
|
|
|
if err := server.Serve(host, port, dir, httpTransport); err != nil {
|
2016-03-05 02:18:33 +03:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|