// findSrcPaths uses the "go/build" package to find the source root for Revel // and the app. func findSrcPaths(importPath string) (revelSourcePath, appSourcePath string) { var ( gopaths = filepath.SplitList(build.Default.GOPATH) goroot = build.Default.GOROOT ) if len(gopaths) == 0 { glog.Fatal("GOPATH environment variable is not set. ", "Please refer to http://golang.org/doc/code.html to configure your Go environment.") } if ContainsString(gopaths, goroot) { glog.Fatalf("GOPATH (%s) must not include your GOROOT (%s). "+ "Please refer to http://golang.org/doc/code.html to configure your Go environment.", gopaths, goroot) } appPkg, err := build.Import(importPath, "", build.FindOnly) if err != nil { glog.Fatalln("Failed to import", importPath, "with error:", err) } revelPkg, err := build.Import(REVEL_IMPORT_PATH, "", build.FindOnly) if err != nil { glog.Fatalln("Failed to find Revel with error:", err) } return revelPkg.SrcRoot, appPkg.SrcRoot }
// genSource renders the given template to produce source code, which it writes // to the given directory and file. func genSource(dir, filename, templateSource string, args map[string]interface{}) { sourceCode := revel.ExecuteTemplate( template.Must(template.New("").Parse(templateSource)), args) // Create a fresh dir. tmpPath := filepath.Join(revel.AppPath, dir) err := os.RemoveAll(tmpPath) if err != nil { glog.Errorln("Failed to remove dir:", err) } err = os.Mkdir(tmpPath, 0777) if err != nil { glog.Fatalln("Failed to make tmp directory:", err) } // Create the file file, err := os.Create(filepath.Join(tmpPath, filename)) defer file.Close() if err != nil { glog.Fatalln("Failed to create file:", err) } _, err = file.WriteString(sourceCode) if err != nil { glog.Fatalln("Failed to write to file:", err) } }
// Run the harness, which listens for requests and proxies them to the app // server, which it runs and rebuilds as necessary. func (h *Harness) Run() { revel.ConfigureLogging() watcher = revel.NewWatcher() watcher.Listen(h, revel.CodePaths...) go func() { addr := fmt.Sprintf("%s:%d", revel.HttpAddr, revel.HttpPort) glog.Infof("Listening on %s", addr) var err error if revel.HttpSsl { err = http.ListenAndServeTLS(addr, revel.HttpSslCert, revel.HttpSslKey, h) } else { err = http.ListenAndServe(addr, h) } if err != nil { glog.Fatalln("Failed to start reverse proxy:", err) } }() // Kill the app on signal. ch := make(chan os.Signal) signal.Notify(ch, os.Interrupt, os.Kill) <-ch if h.app != nil { h.app.Kill() } os.Exit(1) }
// ConfigureLogging applies the configuration in revel.Config to the glog flags. // Logger flags specified explicitly on the command line are not changed. func ConfigureLogging() { // Get the flags specified on the command line. specifiedFlags := make(map[string]struct{}) flag.Visit(func(f *flag.Flag) { specifiedFlags[f.Name] = struct{}{} }) // For each logger option in app.conf.. var err error for _, option := range Config.Options("log.") { val, _ := Config.String(option) switch flagname := option[len("log."):]; flagname { case "v", "vmodule", "logtostderr", "alsologtostderr", "stderrthreshold", "log_dir": // If it was specified on the command line, don't set it from app.conf if _, ok := specifiedFlags[flagname]; ok { continue } // Look up the flag and set it. // If it's log_dir, make it into an absolute path and creat it if necessary. if flagname == "log_dir" { if val, err = filepath.Abs(val); err != nil { glog.Fatalln("Failed to get absolute path to log_dir:", err) } os.MkdirAll(val, 0777) // Create the log dir if it doesn't already exist. } if err = flag.Set(flagname, val); err != nil { glog.Fatalf("Failed to set glog option for %s=%s: %s", flagname, val, err) } case "maxsize": if glog.MaxSize, err = humanize.ParseBytes(val); err != nil { glog.Fatalf("Failed to parse log.MaxSize=%s: %s", val, err) } } } }
// Load mime-types.conf on init. func LoadMimeConfig() { var err error mimeConfig, err = LoadConfig("mime-types.conf") if err != nil { glog.Fatalln("Failed to load mime type config:", err) } }
// Terminate the app server if it's running. func (cmd AppCmd) Kill() { if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) { glog.V(1).Infoln("Killing revel server pid", cmd.Process.Pid) err := cmd.Process.Kill() if err != nil { glog.Fatalln("Failed to kill revel server:", err) } } }
// Run the server. // This is called from the generated main file. // If port is non-zero, use that. Else, read the port from app.conf. func Run(port int) { address := HttpAddr if port == 0 { port = HttpPort } MainTemplateLoader = NewTemplateLoader(TemplatePaths) // The "watch" config variable can turn on and off all watching. // (As a convenient way to control it all together.) if Config.BoolDefault("watch", true) { MainWatcher = NewWatcher() Filters = append([]Filter{WatchFilter}, Filters...) } // If desired (or by default), create a watcher for templates and routes. // The watcher calls Refresh() on things on the first request. if MainWatcher != nil && Config.BoolDefault("watch.templates", true) { MainWatcher.Listen(MainTemplateLoader, TemplatePaths...) } else { MainTemplateLoader.Refresh() } Server = &http.Server{ Addr: fmt.Sprintf("%s:%d", address, port), Handler: http.HandlerFunc(handle), } runStartupHooks() go func() { time.Sleep(100 * time.Millisecond) fmt.Printf("Listening on port %d...\n", port) }() if HttpSsl { glog.Fatalln("Failed to listen:", Server.ListenAndServeTLS(HttpSslCert, HttpSslKey)) } else { glog.Fatalln("Failed to listen:", Server.ListenAndServe()) } }
// LoadModules looks through Config for all modules that need to be loaded, // adding their controllers and templates to the revel application. func LoadModules() { for _, key := range Config.Options("module.") { moduleImportPath := Config.StringDefault(key, "") if moduleImportPath == "" { continue } modulePath, err := ResolveImportPath(moduleImportPath) if err != nil { glog.Fatalln("Failed to load module. Import of", moduleImportPath, "failed:", err) } addModule(key[len("module."):], moduleImportPath, modulePath) } }
// Start the app server, and wait until it is ready to serve requests. func (cmd AppCmd) Start() error { listeningWriter := startupListeningWriter{os.Stdout, make(chan bool)} cmd.Stdout = listeningWriter glog.V(1).Infoln("Exec app:", cmd.Path, cmd.Args) if err := cmd.Cmd.Start(); err != nil { glog.Fatalln("Error running:", err) } select { case <-cmd.waitChan(): return errors.New("revel/harness: app died") case <-time.After(30 * time.Second): cmd.Kill() return errors.New("revel/harness: app timed out") case <-listeningWriter.notifyReady: return nil } panic("Impossible") }
// Refresh scans the views directory and parses all templates using the // configured TemplateEngines. If a template fails to parse, the error is set // on the loader (and returned). func (loader *TemplateLoader) Refresh() *Error { glog.V(1).Infof("Refreshing templates from %s", loader.paths) loader.compileError = nil loader.templatePaths = map[string]string{} // Set the template delimiters for the project if present, then split into left // and right delimiters around a space character var splitDelims []string if delims := Config.StringDefault("template.delimiters", ""); delims != "" { splitDelims = strings.Split(delims, " ") if len(splitDelims) != 2 { glog.Fatalln("app.conf: Incorrect format for template.delimiters") } } loader.defaultEngine = NewTextTemplateEngine() loader.engines = map[string]TemplateEngine{ ".html": NewHtmlTemplateEngine(), ".xml": NewHtmlTemplateEngine(), ".json": NewTextTemplateEngine(), ".txt": NewTextTemplateEngine(), } // Walk through the template loader's paths and pass each template to the // appropriate engine. for _, basePath := range loader.paths { // Walk only returns an error if the template loader is completely unusable // (namely, if one of the TemplateFuncs does not have an acceptable signature). funcErr := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) (walkErr error) { defer func() { if err := recover(); err != nil { walkErr = &Error{ Title: "Panic (Template Loader)", Description: fmt.Sprintln(err), } } }() if err != nil { glog.Errorln("error walking templates:", err) return nil } // Walk into directories. if info.IsDir() { if !loader.WatchDir(info.Name()) { return filepath.SkipDir } return nil } if !loader.WatchFile(info.Name()) { return nil } // Convert template names to use forward slashes, even on Windows. templateName := path[len(basePath)+1:] if os.PathSeparator == '\\' { templateName = strings.Replace(templateName, "\\", "/", -1) } // If we already loaded a template of this name, skip it. if _, ok := loader.templatePaths[templateName]; ok { return nil } loader.templatePaths[templateName] = path fileBytes, err := ioutil.ReadFile(path) if err != nil { glog.Errorln("Failed reading file:", path) return nil } ext := filepath.Ext(templateName) engine, ok := loader.engines[ext] if !ok { engine = loader.defaultEngine } // If alternate delimiters set for the project, change them for this template. if splitDelims != nil { if strings.HasPrefix(path, ViewsPath) { engine.Delims(splitDelims[0], splitDelims[1]) } else { engine.Delims("", "") } } err = engine.Parse(templateName, string(fileBytes)) // Store / report the first error encountered. if err != nil && loader.compileError == nil { _, line, description := parseTemplateError(err) loader.compileError = &Error{ Title: "Template Compilation Error", Path: templateName, Description: description, Line: line, SourceLines: strings.Split(string(fileBytes), "\n"), } glog.Errorf("Template compilation error (In %s around line %d):\n%s", templateName, line, description) } return nil }) // If there was an error with the Funcs, set it and return immediately. if funcErr != nil { loader.compileError = funcErr.(*Error) return loader.compileError } } return loader.compileError }
// Run the app server inline. Never returns. func (cmd AppCmd) Run() { glog.V(1).Infoln("Exec app:", cmd.Path, cmd.Args) if err := cmd.Cmd.Run(); err != nil { glog.Fatalln("Error running:", err) } }
// Init initializes Revel -- it provides paths for getting around the app. // // Params: // mode - the run mode, which determines which app.conf settings are used. // importPath - the Go import path of the application. // srcPath - the path to the source directory, containing Revel and the app. // If not specified (""), then a functioning Go installation is required. func Init(mode, importPath, srcPath string) { // Ignore trailing slashes. ImportPath = strings.TrimRight(importPath, "/") SourcePath = srcPath RunMode = mode // If the SourcePath is not specified, find it using build.Import. var revelSourcePath string // may be different from the app source path if SourcePath == "" { revelSourcePath, SourcePath = findSrcPaths(importPath) } else { // If the SourcePath was specified, assume both Revel and the app are within it. SourcePath = filepath.Clean(SourcePath) revelSourcePath = SourcePath packaged = true } RevelPath = filepath.Join(revelSourcePath, filepath.FromSlash(REVEL_IMPORT_PATH)) BasePath = filepath.Join(SourcePath, filepath.FromSlash(importPath)) AppPath = filepath.Join(BasePath, "app") ViewsPath = filepath.Join(AppPath, "views") CodePaths = []string{AppPath} ConfPaths = []string{ filepath.Join(BasePath, "conf"), filepath.Join(RevelPath, "conf"), } TemplatePaths = []string{ ViewsPath, filepath.Join(RevelPath, "templates"), } // Load app.conf var err error Config, err = LoadConfig("app.conf") if err != nil || Config == nil { glog.Fatalln("Failed to load app.conf:", err) } // Ensure that the selected runmode appears in app.conf. // If empty string is passed as the mode, treat it as "DEFAULT" if mode == "" { mode = config.DEFAULT_SECTION } if !Config.HasSection(mode) { glog.Fatalln("app.conf: No mode found:", mode) } Config.SetSection(mode) // Configure properties from app.conf DevMode = Config.BoolDefault("mode.dev", false) HttpPort = Config.IntDefault("http.port", 9000) HttpAddr = Config.StringDefault("http.addr", "") HttpSsl = Config.BoolDefault("http.ssl", false) HttpSslCert = Config.StringDefault("http.sslcert", "") HttpSslKey = Config.StringDefault("http.sslkey", "") if HttpSsl { if HttpSslCert == "" { log.Fatalln("No http.sslcert provided.") } if HttpSslKey == "" { log.Fatalln("No http.sslkey provided.") } } AppName = Config.StringDefault("app.name", "(not set)") CookiePrefix = Config.StringDefault("cookie.prefix", "REVEL") CookieHttpOnly = Config.BoolDefault("cookie.httponly", false) CookieSecure = Config.BoolDefault("cookie.secure", false) if secretStr := Config.StringDefault("app.secret", ""); secretStr != "" { secretKey = []byte(secretStr) } Initialized = true }
// Build the app: // 1. Generate the the main.go file. // 2. Run the appropriate "go build" command. // Requires that revel.Init has been called previously. // Returns the path to the built binary, and an error if there was a problem building it. func Build() (app *App, compileError *revel.Error) { start := time.Now() // First, clear the generated files (to avoid them messing with ProcessSource). cleanSource("tmp", "routes") sourceInfo, compileError := ProcessSource(revel.CodePaths) if compileError != nil { return nil, compileError } // Add the db.import to the import paths. if dbImportPath, found := revel.Config.String("db.import"); found { sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, dbImportPath) } // Generate two source files. templateArgs := map[string]interface{}{ "Controllers": sourceInfo.ControllerSpecs(), "ValidationKeys": sourceInfo.ValidationKeys, "ImportPaths": calcImportAliases(sourceInfo), "TestSuites": sourceInfo.TestSuites(), } genSource("tmp", "main.go", MAIN, templateArgs) genSource("routes", "routes.go", ROUTES, templateArgs) // Read build config. buildTags := revel.Config.StringDefault("build.tags", "") // Build the user program (all code under app). // It relies on the user having "go" installed. goPath, err := exec.LookPath("go") if err != nil { glog.Fatalf("Go executable not found in PATH.") } pkg, err := build.Default.Import(revel.ImportPath, "", build.FindOnly) if err != nil { glog.Fatalln("Failure importing", revel.ImportPath) } binName := filepath.Join(pkg.BinDir, filepath.Base(revel.BasePath)) if runtime.GOOS == "windows" { binName += ".exe" } gotten := make(map[string]struct{}) for { buildCmd := exec.Command(goPath, "build", "-i", "-tags", buildTags, "-o", binName, path.Join(revel.ImportPath, "app", "tmp")) glog.V(1).Infoln("Exec:", buildCmd.Args) output, err := buildCmd.CombinedOutput() // If the build succeeded, we're done. if err == nil { glog.Infof("Build took %s", time.Since(start)) return NewApp(binName), nil } glog.Error(string(output)) // See if it was an import error that we can go get. matches := importErrorPattern.FindStringSubmatch(string(output)) if matches == nil { return nil, newCompileError(output) } // Ensure we haven't already tried to go get it. pkgName := matches[1] if _, alreadyTried := gotten[pkgName]; alreadyTried { return nil, newCompileError(output) } gotten[pkgName] = struct{}{} // Execute "go get <pkg>" getCmd := exec.Command(goPath, "get", pkgName) glog.V(1).Infoln("Exec:", getCmd.Args) getOutput, err := getCmd.CombinedOutput() if err != nil { glog.Error(string(getOutput)) return nil, newCompileError(output) } // Success getting the import, attempt to build again. } glog.Fatal("Not reachable") return nil, nil }