Esempio n. 1
0
func main() {

	// Define the options for the command line and config file options parser.
	opts := optparse.Parser(
		"Usage: live-server <config.yaml> [options]\n",
		"live-server 0.0.1")

	debug := opts.Bool([]string{"-d", "--debug"}, false,
		"enable debug mode")

	genConfig := opts.Bool([]string{"-g", "--gen-config"}, false,
		"show the default yaml config")

	frontendHost := opts.StringConfig("frontend-host", "",
		"the host to bind the HTTPS Frontends to")

	frontendPort := opts.IntConfig("frontend-port", 9040,
		"the base port for the HTTPS Frontends [9040]")

	officialHost := opts.StringConfig("offficial-host", "",
		"the official public host for the HTTPS Frontends")

	primaryHosts := opts.StringConfig("primary-hosts", "",
		"limit the primary HTTPS Frontend to the specified host pattern")

	primaryCert := opts.StringConfig("primary-cert", "cert/primary.cert",
		"the path to the primary host's TLS certificate [cert/primary.cert]")

	primaryKey := opts.StringConfig("primary-key", "cert/primary.key",
		"the path to the primary host's TLS key [cert/primary.key]")

	noSecondary := opts.BoolConfig("no-secondary", false,
		"disable the secondary HTTPS Frontend [false]")

	secondaryHosts := opts.StringConfig("secondary-hosts", "",
		"limit the secondary HTTPS Frontend to the specified host pattern")

	secondaryCert := opts.StringConfig("secondary-cert", "cert/secondary.cert",
		"the path to the secondary host's TLS certificate [cert/secondary.cert]")

	secondaryKey := opts.StringConfig("secondary-key", "cert/secondary.key",
		"the path to the secondary host's TLS key [cert/secondary.key]")

	errorDirectory := opts.StringConfig("error-dir", "error",
		"the path to the HTTP error files directory [error]")

	logDirectory := opts.StringConfig("log-dir", "log",
		"the path to the log directory [log]")

	runDirectory := opts.StringConfig("run-dir", "run",
		"the path to the run directory to store locks, pid files, etc. [run]")

	staticDirectory := opts.StringConfig("static-dir", "www",
		"the path to the static files directory [www]")

	staticMaxAge := opts.IntConfig("static-max-age", 86400,
		"max-age cache header value when serving the static files [86400]")

	noLivequery := opts.BoolConfig("no-livequery", false,
		"disable the LiveQuery node and WebSocket/Comet support [false]")

	websocketPrefix := opts.StringConfig("websocket-prefix", "/.live/ws",
		"URL path prefix for WebSocket requests [/.live/ws]")

	cometPrefix := opts.StringConfig("comet-prefix", "/.live/poll",
		"URL path prefix for Comet requests [/.live/poll]")

	livequeryHost := opts.StringConfig("livequery-host", "",
		"the host to bind the LiveQuery node to")

	livequeryPort := opts.IntConfig("livequery-port", 9050,
		"the port (both UDP and TCP) to bind the LiveQuery node to [9050]")

	livequeryExpiry := opts.IntConfig("livequery-expiry", 40,
		"maximum number of seconds a LiveQuery subscription is valid [40]")

	cookieKeyPath := opts.StringConfig("cookie-key", "cert/cookie.key",
		"the path to the file containing the key used to sign cookies [cert/cookie.key]")

	cookieName := opts.StringConfig("cookie-name", "user",
		"the property name of the cookie containing the user id [user]")

	acceptors := opts.StringConfig("acceptor-nodes", "localhost:9060",
		"comma-separated addresses of Acceptor nodes [localhost:9060]")

	acceptorKeyPath := opts.StringConfig("acceptor-key", "cert/acceptor.key",
		"the path to the file containing the Acceptor secret key [cert/acceptor.key]")

	runAcceptor := opts.BoolConfig("run-as-acceptor", false,
		"run as an Acceptor node [false]")

	acceptorIndex := opts.IntConfig("acceptor-index", 0,
		"this node's index in the Acceptor nodes address list [0]")

	leaseExpiry := opts.IntConfig("lease-expiry", 7,
		"maximum number of seconds a lease from an Acceptor node is valid [7]")

	noRedirect := opts.BoolConfig("no-redirect", false,
		"disable the HTTP Redirector [false]")

	httpHost := opts.StringConfig("http-host", "",
		"the host to bind the HTTP Redirector to")

	httpPort := opts.IntConfig("http-port", 9080,
		"the port to bind the HTTP Redirector to [9080]")

	redirectURL := opts.StringConfig("redirect-url", "",
		"the URL that the HTTP Redirector redirects to")

	pingPath := opts.StringConfig("ping-path", "/.ping",
		`URL path for a "ping" request [/.ping]`)

	enableHSTS := opts.BoolConfig("enable-hsts", false,
		"enable HTTP Strict Transport Security (HSTS) on redirects [false]")

	hstsMaxAge := opts.IntConfig("hsts-max-age", 50000000,
		"max-age value of HSTS in number of seconds [50000000]")

	upstreamHost := opts.StringConfig("upstream-host", "localhost",
		"the upstream host to connect to [localhost]")

	upstreamPort := opts.IntConfig("upstream-port", 8080,
		"the upstream port to connect to [8080]")

	upstreamTLS := opts.BoolConfig("upstream-tls", false,
		"use TLS when connecting to upstream [false]")

	logRotate := opts.StringConfig("log-rotate", "never",
		"specify one of 'hourly', 'daily' or 'never' [never]")

	noConsoleLog := opts.BoolConfig("no-console-log", false,
		"disable server requests being logged to the console [false]")

	maintenanceMode := opts.BoolConfig("maintenance", false,
		"start up in maintenance mode [false]")

	extraConfig := opts.StringConfig("extra-config", "",
		"path to a YAML config file with additional options")

	// Parse the command line options.
	os.Args[0] = "live-server"
	args := opts.Parse(os.Args)

	// Print the default YAML config file if the ``-g`` flag was specified.
	if *genConfig {
		opts.PrintDefaultConfigFile("live-server")
		runtime.Exit(0)
	}

	// Setup the console logger early.
	if !*noConsoleLog {
		log.AddConsoleLogger()
		log.ConsoleFilters["ls"] = func(items []interface{}) (bool, []interface{}) {
			return true, items[2 : len(items)-2]
		}
	}

	// Set the debug mode flag if the ``-d`` flag was specified.
	debugMode = *debug

	var instanceDirectory string
	var configPath string
	var err error

	// Assume the parent directory of the config as the instance directory.
	if len(args) >= 1 {
		if args[0] == "help" {
			opts.PrintUsage()
			runtime.Exit(0)
		}
		configPath, err = filepath.Abs(filepath.Clean(args[0]))
		if err != nil {
			runtime.StandardError(err)
		}
		err = opts.ParseConfig(configPath, os.Args)
		if err != nil {
			runtime.StandardError(err)
		}
		instanceDirectory, _ = filepath.Split(configPath)
	} else {
		opts.PrintUsage()
		runtime.Exit(0)
	}

	// Load the extra config file with additional options if one has been
	// specified.
	if *extraConfig != "" {
		extraConfigPath, err := filepath.Abs(filepath.Clean(*extraConfig))
		if err != nil {
			runtime.StandardError(err)
		}
		extraConfigPath = runtime.JoinPath(instanceDirectory, extraConfigPath)
		err = opts.ParseConfig(extraConfigPath, os.Args)
		if err != nil {
			runtime.StandardError(err)
		}
	}

	// Create the log directory if it doesn't exist.
	logPath := runtime.JoinPath(instanceDirectory, *logDirectory)
	err = os.MkdirAll(logPath, 0755)
	if err != nil {
		runtime.StandardError(err)
	}

	// Create the run directory if it doesn't exist.
	runPath := runtime.JoinPath(instanceDirectory, *runDirectory)
	err = os.MkdirAll(runPath, 0755)
	if err != nil {
		runtime.StandardError(err)
	}

	// Initialise the runtime -- which will run ``live-server`` on multiple
	// processors if possible.
	runtime.Init()

	// Handle running as an Acceptor node if ``--run-as-acceptor`` was
	// specified.
	if *runAcceptor {

		// Exit if the `--acceptor-index`` is negative.
		if *acceptorIndex < 0 {
			runtime.Error("The --acceptor-index cannot be negative.")
		}

		var index int
		var selfAddress string
		var acceptorNodes []string

		// Generate a list of all the acceptor node addresses and exit if we
		// couldn't find the address four ourselves at the given index.
		for _, acceptor := range strings.Split(*acceptors, ",") {
			acceptor = strings.TrimSpace(acceptor)
			if acceptor != "" {
				if index == *acceptorIndex {
					selfAddress = acceptor
				} else {
					acceptorNodes = append(acceptorNodes, acceptor)
				}
			}
			index += 1
		}

		if selfAddress == "" {
			runtime.Error("Couldn't determine the address for the acceptor.")
		}

		// Initialise the process-related resources.
		runtime.InitProcess(fmt.Sprintf("acceptor-%d", *acceptorIndex), runPath)

		return

	}

	// Initialise the process-related resources.
	runtime.InitProcess("live-server", runPath)

	// Ensure that the directory containing static files exists.
	staticPath := runtime.JoinPath(instanceDirectory, *staticDirectory)
	dirInfo, err := os.Stat(staticPath)
	if err == nil {
		if !dirInfo.IsDir() {
			runtime.Error("Static path %q is not a directory", staticPath)
		}
	} else {
		runtime.StandardError(err)
	}

	// Load up all static files into a mapping.
	staticFiles := make(map[string]*StaticFile)
	getFiles(staticPath, staticFiles, "")

	// Pre-format the Cache-Control header for static files.
	staticCache := fmt.Sprintf("public, max-age=%d", *staticMaxAge)
	staticMaxAge64 := time.Duration(*staticMaxAge)

	// Exit if the directory containing the 50x.html files isn't present.
	errorPath := runtime.JoinPath(instanceDirectory, *errorDirectory)
	dirInfo, err = os.Stat(errorPath)
	if err == nil {
		if !dirInfo.IsDir() {
			runtime.Error("Error path %q is not a directory", errorPath)
		}
	} else {
		runtime.StandardError(err)
	}

	// Load the content for the HTTP ``400``, ``500``, ``502`` and ``503``
	// errors.
	error400, error400Length = getErrorInfo(errorPath, "400.html")
	error500, error500Length = getErrorInfo(errorPath, "500.html")
	error502, error502Length = getErrorInfo(errorPath, "502.html")
	error503, error503Length = getErrorInfo(errorPath, "503.html")

	// Initialise the TLS config.
	tlsconf.Init()

	// Setup the file loggers.
	var rotate int

	switch *logRotate {
	case "daily":
		rotate = log.RotateDaily
	case "hourly":
		rotate = log.RotateHourly
	case "never":
		rotate = log.RotateNever
	default:
		runtime.Error("Unknown log rotation format %q", *logRotate)
	}

	_, err = log.AddFileLogger("live-server", logPath, rotate, log.InfoLog)
	if err != nil {
		runtime.Error("Couldn't initialise logfile: %s", err)
	}

	_, err = log.AddFileLogger("error", logPath, rotate, log.ErrorLog)
	if err != nil {
		runtime.Error("Couldn't initialise logfile: %s", err)
	}

	var liveMode bool

	// Setup the live support as long as it hasn't been disabled.
	if !*noLivequery {
		go handleLiveMessages()
		acceptorKey, err = ioutil.ReadFile(runtime.JoinPath(instanceDirectory, *acceptorKeyPath))
		if err != nil {
			runtime.StandardError(err)
		}
		cookieKey, err = ioutil.ReadFile(runtime.JoinPath(instanceDirectory, *cookieKeyPath))
		if err != nil {
			runtime.StandardError(err)
		}
		liveMode = true
		_ = *livequeryHost
		_ = *livequeryPort
		_ = *cookieName
		_ = *leaseExpiry
		livequeryTimeout = (time.Duration(*livequeryExpiry) / 2) * 1000000000
	}

	// Create a container for the Frontend instances.
	frontends := make([]*Frontend, 0)

	// Create a channel which is used to toggle the state of the live-server's
	// maintenance mode based on process signals.
	maintenanceChannel := make(chan bool, 1)

	// Fork a goroutine which toggles the maintenance mode in a single place and
	// thus ensures "thread safety".
	go func() {
		for {
			enabledState := <-maintenanceChannel
			for _, frontend := range frontends {
				if enabledState {
					frontend.maintenanceMode = true
				} else {
					frontend.maintenanceMode = false
				}
			}
		}
	}()

	// Register the signal handlers for SIGUSR1 and SIGUSR2.
	runtime.SignalHandlers[os.SIGUSR1] = func() {
		maintenanceChannel <- true
	}

	runtime.SignalHandlers[os.SIGUSR2] = func() {
		maintenanceChannel <- false
	}

	// Let the user know how many CPUs we're currently running on.
	fmt.Printf("Running live-server with %d CPUs:\n", runtime.CPUCount)

	// If ``--public-address`` hasn't been specified, generate it from the given
	// frontend host and base port values -- assuming ``localhost`` for a blank
	// host.
	publicHost := *officialHost
	if publicHost == "" {
		if *frontendHost == "" {
			publicHost = fmt.Sprintf("localhost:%d", *frontendPort)
		} else {
			publicHost = fmt.Sprintf("%s:%d", *frontendHost, *frontendPort)
		}
	}

	// Setup and run the primary HTTPS Frontend.
	frontends = append(frontends, initFrontend("primary", *frontendHost,
		*frontendPort, publicHost, *primaryHosts, *primaryCert, *primaryKey,
		*cometPrefix, *websocketPrefix, instanceDirectory, *upstreamHost,
		*upstreamPort, *upstreamTLS, *maintenanceMode, liveMode, staticCache,
		staticFiles, staticMaxAge64))

	// Setup and run the secondary HTTPS Frontend.
	if !*noSecondary {
		frontends = append(frontends, initFrontend("secondary", *frontendHost,
			*frontendPort+1, publicHost, *secondaryHosts, *secondaryCert,
			*secondaryKey, *cometPrefix, *websocketPrefix, instanceDirectory,
			*upstreamHost, *upstreamPort, *upstreamTLS, *maintenanceMode,
			liveMode, staticCache, staticFiles, staticMaxAge64))
	}

	// Enter a wait loop if the HTTP Redirector has been disabled.
	if *noRedirect {
		loopForever := make(chan bool, 1)
		<-loopForever
	}

	// Otherwise, setup the HTTP Redirector.
	if *httpHost == "" {
		*httpHost = "localhost"
	}

	if *redirectURL == "" {
		*redirectURL = "https://" + publicHost
	}

	httpAddr := fmt.Sprintf("%s:%d", *httpHost, *httpPort)
	httpListener, err := net.Listen("tcp", httpAddr)
	if err != nil {
		runtime.Error("Cannot listen on %s: %v", httpAddr, err)
	}

	hsts := ""
	if *enableHSTS {
		hsts = fmt.Sprintf("max-age=%d", *hstsMaxAge)
	}

	redirector := &Redirector{
		hsts:     hsts,
		pingPath: *pingPath,
		url:      *redirectURL,
	}

	// Start a goroutine which runs the HTTP redirector.
	go func() {
		err = http.Serve(httpListener, redirector)
		if err != nil {
			runtime.Error("Couldn't serve HTTP Redirector: %s", err)
		}
	}()

	fmt.Printf("* HTTP Redirector running on http://%s:%d -> %s\n",
		*httpHost, *httpPort, *redirectURL)

	// Enter the wait loop for the process to be killed.
	loopForever := make(chan bool, 1)
	<-loopForever

}
Esempio n. 2
0
func DefaultOpts(name string, opts *optparse.OptionParser, argv []string) (bool, string, string) {

	var (
		configPath        string
		instanceDirectory string
		err               error
	)

	debug := opts.Bool([]string{"-d", "--debug"}, false,
		"enable debug mode")

	genConfig := opts.Bool([]string{"-g", "--gen-config"}, false,
		"show the default yaml config")

	runDirectory := opts.StringConfig("run-dir", "run",
		"the path to the run directory to store locks, pid files, etc. [run]")

	logDirectory := opts.StringConfig("log-dir", "log",
		"the path to the log directory [log]")

	logRotate := opts.StringConfig("log-rotate", "never",
		"specify one of 'hourly', 'daily' or 'never' [never]")

	noConsoleLog := opts.BoolConfig("no-console-log", false,
		"disable server requests being logged to the console [false]")

	extraConfig := opts.StringConfig("extra-config", "",
		"path to a YAML config file with additional options")

	// Parse the command line options.
	args := opts.Parse(argv)

	// Print the default YAML config file if the ``-g`` flag was specified.
	if *genConfig {
		opts.PrintDefaultConfigFile(name)
		Exit(0)
	}

	// Enable the console logger early.
	if !*noConsoleLog {
		log.AddConsoleLogger()
	}

	// Assume the parent directory of the config as the instance directory.
	if len(args) >= 1 {
		configPath, err = filepath.Abs(filepath.Clean(args[0]))
		if err != nil {
			StandardError(err)
		}
		err = opts.ParseConfig(configPath, os.Args)
		if err != nil {
			StandardError(err)
		}
		instanceDirectory, _ = filepath.Split(configPath)
		Profile = strings.Split(filepath.Base(configPath), ".")[0]
	} else {
		opts.PrintUsage()
		Exit(0)
	}

	// Load the extra config file with additional options if one has been
	// specified.
	if *extraConfig != "" {
		extraConfigPath, err := filepath.Abs(filepath.Clean(*extraConfig))
		if err != nil {
			StandardError(err)
		}
		extraConfigPath = JoinPath(instanceDirectory, extraConfigPath)
		err = opts.ParseConfig(extraConfigPath, os.Args)
		if err != nil {
			StandardError(err)
		}
	}

	// Create the log directory if it doesn't exist.
	logPath := JoinPath(instanceDirectory, *logDirectory)
	err = os.MkdirAll(logPath, 0755)
	if err != nil {
		StandardError(err)
	}

	// Create the run directory if it doesn't exist.
	runPath := JoinPath(instanceDirectory, *runDirectory)
	err = os.MkdirAll(runPath, 0755)
	if err != nil {
		StandardError(err)
	}

	// Setup the file and console logging.
	var rotate int

	switch *logRotate {
	case "daily":
		rotate = log.RotateDaily
	case "hourly":
		rotate = log.RotateHourly
	case "never":
		rotate = log.RotateNever
	default:
		Error("Unknown log rotation format %q", *logRotate)
	}

	_, err = log.AddFileLogger(name, logPath, rotate, log.InfoLog)
	if err != nil {
		Error("Couldn't initialise logfile: %s", err)
	}

	_, err = log.AddFileLogger("error", logPath, rotate, log.ErrorLog)
	if err != nil {
		Error("Couldn't initialise logfile: %s", err)
	}

	// Initialise the runtime -- which will run the process on multiple
	// processors if possible.
	Init()

	// Initialise the process-related resources.
	InitProcess(name, runPath)

	return *debug, instanceDirectory, runPath

}