func main()

in grove/grove.go [54:297]


func main() {
	runtime.GOMAXPROCS(32) // DEBUG
	configFileName := flag.String("cfg", "", "The config file path")
	pprof := flag.Bool("pprof", false, "Whether to profile")
	showVersion := flag.Bool("version", false, "Print the application version")
	flag.Parse()

	if *showVersion {
		fmt.Println(Version)
		os.Exit(0)
	}

	if *configFileName == "" {
		fmt.Println(time.Now().Format(time.RFC3339Nano) + " Error starting service: The -cfg argument is required")
		os.Exit(1)
	}

	cfg, err := config.LoadConfig(*configFileName)
	if err != nil {
		fmt.Println(time.Now().Format(time.RFC3339Nano) + " Error starting service: loading config: " + err.Error())
		os.Exit(1)
	}

	eventW, errW, warnW, infoW, debugW, err := log.GetLogWriters(cfg)
	if err != nil {
		fmt.Println(time.Now().Format(time.RFC3339Nano) + " Error starting service: failed to create log writers: " + err.Error())
		os.Exit(1)
	}
	log.Init(eventW, errW, warnW, infoW, debugW)

	caches, err := createCaches(cfg.CacheFiles, uint64(cfg.FileMemBytes), uint64(cfg.CacheSizeBytes))
	if err != nil {
		log.Errorln("starting service: creating caches: " + err.Error())
		os.Exit(1)
	}

	reqTimeout := time.Duration(cfg.ReqTimeoutMS) * time.Millisecond
	reqKeepAlive := time.Duration(cfg.ReqKeepAliveMS) * time.Millisecond
	reqMaxIdleConns := cfg.ReqMaxIdleConns
	reqIdleConnTimeout := time.Duration(cfg.ReqIdleConnTimeoutMS) * time.Millisecond
	baseTransport := remap.NewRemappingTransport(reqTimeout, reqKeepAlive, reqMaxIdleConns, reqIdleConnTimeout)

	plugins := plugin.Get(cfg.Plugins)
	remapper, err := remap.LoadRemapper(cfg.RemapRulesFile, plugins.LoadFuncs(), caches, baseTransport)
	if err != nil {
		log.Errorf("starting service: loading remap rules: %v\n", err)
		os.Exit(1)
	}

	certs, err := loadCerts(remapper.Rules())
	if err != nil {
		log.Errorf("starting service: loading certificates: %v\n", err)
		os.Exit(1)
	}
	defaultCert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile)
	if err != nil {
		log.Errorf("starting service: loading default certificate: %v\n", err)
		os.Exit(1)
	}
	certs = append(certs, defaultCert)

	httpListener, httpConns, httpConnStateCallback, err := web.InterceptListen("tcp", fmt.Sprintf(":%d", cfg.Port))
	if err != nil {
		log.Errorf("creating HTTP listener %v: %v\n", cfg.Port, err)
		os.Exit(1)
	}

	httpsConns := (*web.ConnMap)(nil)
	httpsServer := (*http.Server)(nil)
	httpsListener := net.Listener(nil)
	httpsConnStateCallback := (func(net.Conn, http.ConnState))(nil)
	tlsConfig := (*tls.Config)(nil)
	if cfg.CertFile != "" && cfg.KeyFile != "" {
		if httpsListener, httpsConns, httpsConnStateCallback, tlsConfig, err = web.InterceptListenTLS("tcp", fmt.Sprintf(":%d", cfg.HTTPSPort), certs, cfg.DisableHTTP2); err != nil {
			log.Errorf("creating HTTPS listener %v: %v\n", cfg.HTTPSPort, err)
			return
		}
	}

	// TODO pass total size for all file groups?
	stats := stat.New(remapper.Rules(), caches, uint64(cfg.CacheSizeBytes), httpConns, httpsConns, Version)

	buildHandler := func(scheme string, port string, conns *web.ConnMap, stats stat.Stats, pluginContext map[string]*interface{}) *cache.HandlerPointer {
		return cache.NewHandlerPointer(cache.NewHandler(
			remapper,
			uint64(cfg.ConcurrentRuleRequests),
			stats,
			scheme,
			port,
			conns,
			cfg.RFCCompliant,
			cfg.ConnectionClose,
			plugins,
			pluginContext,
			httpConns,
			httpsConns,
			cfg.InterfaceName,
		))
	}

	pluginContext := map[string]*interface{}{}

	httpHandler := buildHandler("http", strconv.Itoa(cfg.Port), httpConns, stats, pluginContext)
	httpsHandler := buildHandler("https", strconv.Itoa(cfg.HTTPSPort), httpsConns, stats, pluginContext)

	idleTimeout := time.Duration(cfg.ServerIdleTimeoutMS) * time.Millisecond
	readTimeout := time.Duration(cfg.ServerReadTimeoutMS) * time.Millisecond
	writeTimeout := time.Duration(cfg.ServerWriteTimeoutMS) * time.Millisecond

	plugins.OnStartup(remapper.PluginCfg(), pluginContext, plugin.StartupData{Config: cfg, Shared: remapper.PluginSharedCfg()})

	// TODO add config to not serve HTTP (only HTTPS). If port is not set?
	httpServer := startServer(httpHandler, httpListener, httpConnStateCallback, nil, cfg.Port, idleTimeout, readTimeout, writeTimeout, cfg.DisableHTTP2, "http")

	if cfg.CertFile != "" && cfg.KeyFile != "" {
		httpsServer = startServer(httpsHandler, httpsListener, httpsConnStateCallback, tlsConfig, cfg.HTTPSPort, idleTimeout, readTimeout, writeTimeout, cfg.DisableHTTP2, "https")
	}

	reloadConfig := func() {
		log.Infoln("reloading config")
		err := error(nil)
		oldCfg := cfg
		cfg, err = config.LoadConfig(*configFileName)
		if err != nil {
			log.Errorln("reloading config: failed to load config file, keeping existing config: " + err.Error())
			cfg = oldCfg
			return
		}
		eventW, errW, warnW, infoW, debugW, err := log.GetLogWriters(cfg)
		if err != nil {
			log.Errorln("reloading config: failed to get log writers from '" + *configFileName + "', keeping existing log locations: " + err.Error())
		} else {
			log.Init(eventW, errW, warnW, infoW, debugW)
		}

		// TODO add cache file reloading
		// The problem is, the disk db needs file locks, so there's no way to close and create new files without making all requests cache miss in the meantime.
		// Thus, the file paths must be kept, diffed, only removed paths' dbs closed, only new paths opened, and dbs for existing paths passed into the new caches object.
		if cachesChanged(oldCfg, cfg) {
			log.Warnln("reloading config: caches changed in new config! Dynamic cache reloading is not supported! Old cache files and sizes will be used, and new cache config will NOT be loaded! Restart service to apply cache changes!")
		}

		plugins = plugin.Get(cfg.Plugins)
		oldRemapper := remapper
		remapper, err = remap.LoadRemapper(cfg.RemapRulesFile, plugins.LoadFuncs(), caches, baseTransport)
		if err != nil {
			log.Errorln("reloading config: failed to load remap rules, keeping existing rules: " + err.Error())
			remapper = oldRemapper
			return
		}

		if cfg.Port != oldCfg.Port {
			if httpListener, httpConns, httpConnStateCallback, err = web.InterceptListen("tcp", fmt.Sprintf(":%d", cfg.Port)); err != nil {
				log.Errorf("reloading config: creating HTTP listener %v: %v\n", cfg.Port, err)
				return
			}
		}

		if (cfg.CertFile != oldCfg.CertFile || cfg.KeyFile != oldCfg.KeyFile) && cfg.HTTPSPort != oldCfg.HTTPSPort {
			log.Warnln("config certificate changed, but port did not. Cannot recreate listener on same port without stopping the service. Restart the service to load the new certificate.")
		}

		if cfg.HTTPSPort != oldCfg.HTTPSPort {
			if httpsListener, httpsConns, httpsConnStateCallback, tlsConfig, err = web.InterceptListenTLS("tcp", fmt.Sprintf(":%d", cfg.HTTPSPort), certs, cfg.DisableHTTP2); err != nil {
				log.Errorf("creating HTTPS listener %v: %v\n", cfg.HTTPSPort, err)
			}
		}

		stats = stat.New(remapper.Rules(), caches, uint64(cfg.CacheSizeBytes), httpConns, httpsConns, Version) // TODO copy stats from old stats object?

		httpCacheHandler := cache.NewHandler(
			remapper,
			uint64(cfg.ConcurrentRuleRequests),
			stats,
			"http",
			strconv.Itoa(cfg.Port),
			httpConns,
			cfg.RFCCompliant,
			cfg.ConnectionClose,
			plugins,
			pluginContext,
			httpConns,
			httpsConns,
			cfg.InterfaceName,
		)
		httpHandler.Set(httpCacheHandler)

		httpsCacheHandler := cache.NewHandler(
			remapper,
			uint64(cfg.ConcurrentRuleRequests),
			stats,
			"https",
			strconv.Itoa(cfg.HTTPSPort),
			httpsConns,
			cfg.RFCCompliant,
			cfg.ConnectionClose,
			plugins,
			pluginContext,
			httpConns,
			httpsConns,
			cfg.InterfaceName,
		)
		httpsHandler.Set(httpsCacheHandler)

		plugins.OnStartup(remapper.PluginCfg(), pluginContext, plugin.StartupData{Config: cfg, Shared: remapper.PluginSharedCfg()})

		if cfg.Port != oldCfg.Port {
			ctx, cancel := context.WithTimeout(context.Background(), ShutdownTimeout)
			defer cancel()
			if err := httpServer.Shutdown(ctx); err != nil {
				if err == context.DeadlineExceeded {
					log.Errorf("closing http server: connections didn't close gracefully in %v, forcefully closing.\n", ShutdownTimeout)
					httpServer.Close()
				} else {
					log.Errorf("closing http server: %v\n", err)
				}

			}
			httpServer = startServer(httpHandler, httpListener, httpConnStateCallback, nil, cfg.Port, idleTimeout, readTimeout, writeTimeout, cfg.DisableHTTP2, "http")
		}

		if (httpsServer == nil || cfg.HTTPSPort != oldCfg.HTTPSPort) && cfg.CertFile != "" && cfg.KeyFile != "" {
			if httpsServer != nil {
				ctx, cancel := context.WithTimeout(context.Background(), ShutdownTimeout)
				defer cancel()
				if err := httpsServer.Shutdown(ctx); err != nil {
					if err == context.DeadlineExceeded {
						log.Errorf("closing https server: connections didn't close gracefully in %v, forcefully closing.\n", ShutdownTimeout)
						httpServer.Close()
					} else {
						log.Errorf("closing https server: %v\n", err)
					}
				}
			}

			httpsServer = startServer(httpsHandler, httpsListener, httpsConnStateCallback, tlsConfig, cfg.HTTPSPort, idleTimeout, readTimeout, writeTimeout, cfg.DisableHTTP2, "https")
		}
	}

	if *pprof {
		profile()
	}
	signalReloader(unix.SIGHUP, reloadConfig)
}