revel
- The Revel Web Framework for Go
- Go言語製のウェブフレームワークのひとつ
- 前回は
revel new hoge/fuga
を読んだ
読む場所
% cd $GOPATH/src/github.com/revel/cmd
revel run hoge/fuga prod
めんどいからprodモード起動の場合を読む
cmdのrevel/run.go
package main import ( "github.com/revel/revel" "github.com/revel/revel/harness" "strconv" "fmt" ) var cmdRun = &Command{ UsageLine: "run [import path] [run mode] [port]", Short: "run a Revel application", Long: `略`, } func init() { cmdRun.Run = runApp } func runApp(args []string) { if len(args) == 0 { errorf("No import path given.\nRun 'revel help run' for usage.\n") } // Determine the run mode. mode := "dev" if len(args) >= 2 { mode = args[1] } // ここでmodeはprodになる // Find and parse app.conf revel.Init(mode, args[0], "") revel.LoadMimeConfig() // revel.Init, revel.LoadMimeConfigの中を見ないと分からないけど、 // "prod"と"hoge/fuga"を受けて、revelのグローバルなvarを // 上書きしているっぽい // Determine the override port, if any. // 三番目の引数はポート指定 // 指定があればですけど port := revel.HttpPort if len(args) == 3 { var err error if port, err = strconv.Atoi(args[2]); err != nil { errorf("Failed to parse port as integer: %s", args[2]) } } // このへんただのstdoutなので無視る revel.INFO.Printf("Running %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, mode) revel.TRACE.Println("Base path:", revel.BasePath) // If the app is run in "watched" mode, use the harness to run it. // revel.Configはgithub.com/robfig/configでいうところの // sectionにrevel.RunModeを渡しているので、app.confの[prod]を読む // で、[prod]の場合これはデフォでfalse if revel.Config.BoolDefault("watch", true) && revel.Config.BoolDefault("watch.code", true) { revel.HttpPort = port harness.NewHarness().Run() // Never returns. } // Else, just build and run the app. // 最も読むべきなのはここのharness.Buildだと思われ app, err := harness.Build() if err != nil { errorf("Failed to build app: %s", err) } app.Port = port app.Cmd().Run() }
ちなみにrevel.Init()
で決定されるrevel直下のvarは、
$GOPATH/src/github.com/revel/revel/revel.go
var ( // App details AppName string // e.g. "sample" BasePath string // e.g. "/Users/robfig/gocode/src/corp/sample" AppPath string // e.g. "/Users/robfig/gocode/src/corp/sample/app" ViewsPath string // e.g. "/Users/robfig/gocode/src/corp/sample/app/views" ImportPath string // e.g. "corp/sample" SourcePath string // e.g. "/Users/robfig/gocode/src" Config *MergedConfig RunMode string // Application-defined (by default, "dev" or "prod") DevMode bool // if true, RunMode is a development mode. // Revel installation details RevelPath string // e.g. "/Users/robfig/gocode/src/revel" // Where to look for templates and configuration. // Ordered by priority. (Earlier paths take precedence over later paths.) CodePaths []string ConfPaths []string TemplatePaths []string Modules []Module // Server config. // // Alert: This is how the app is configured, which may be different from // the current process reality. For example, if the app is configured for // port 9000, HttpPort will always be 9000, even though in dev mode it is // run on a random port and proxied. HttpPort int // e.g. 9000 HttpAddr string // e.g. "", "127.0.0.1" HttpSsl bool // e.g. true if using ssl HttpSslCert string // e.g. "/path/to/cert.pem" HttpSslKey string // e.g. "/path/to/key.pem" // All cookies dropped by the framework begin with this prefix. CookiePrefix string // Cookie flags CookieHttpOnly bool CookieSecure bool // Delimiters to use when rendering templates TemplateDelims string Initialized bool // Private secretKey []byte // Key used to sign cookies. An empty key disables signing. packaged bool // If true, this is running from a pre-built package. )
$GOPATH/src/github.com/revel/revel/harness/build.go
package harness import ( "fmt" "github.com/revel/revel" "go/build" "os" "os/exec" "path" "path/filepath" "regexp" "runtime" "strconv" "strings" "text/template" ) var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"") // 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) { // 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 { revel.ERROR.Fatalf("Go executable not found in PATH.") } // このアプリケーションのbinへのパスを決定する pkg, err := build.Default.Import(revel.ImportPath, "", build.FindOnly) if err != nil { revel.ERROR.Fatalln("Failure importing", revel.ImportPath) } binName := path.Join(pkg.BinDir, path.Base(revel.BasePath)) if runtime.GOOS == "windows" { binName += ".exe" } fmt.Println("binNameとは?", binName) gotten := make(map[string]struct{}) for { appVersion := getAppVersion() versionLinkerFlags := fmt.Sprintf("-X %s/app.APP_VERSION \"%s\"", revel.ImportPath, appVersion) // ここでアプリケーションのbin ($GOPATH/bin/fuga) をつくってる buildCmd := exec.Command(goPath, "build", "-ldflags", versionLinkerFlags, "-tags", buildTags, "-o", binName, path.Join(revel.ImportPath, "app", "tmp")) revel.TRACE.Println("Exec:", buildCmd.Args) output, err := buildCmd.CombinedOutput() // If the build succeeded, we're done. if err == nil { return NewApp(binName), nil } revel.ERROR.Println(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) revel.TRACE.Println("Exec:", getCmd.Args) getOutput, err := getCmd.CombinedOutput() if err != nil { revel.ERROR.Println(string(getOutput)) return nil, newCompileError(output) } // Success getting the import, attempt to build again. } revel.ERROR.Fatalf("Not reachable") return nil, nil }
genSourceで生成されるGoコード
tmp
// GENERATED CODE - DO NOT EDIT package main import ( "flag" "reflect" "github.com/revel/revel" controllers0 "github.com/revel/revel/modules/static/app/controllers" _ "hoge/fuga/app" controllers "hoge/fuga/app/controllers" ) var ( runMode *string = flag.String("runMode", "", "Run mode.") port *int = flag.Int("port", 0, "By default, read from app.conf") importPath *string = flag.String("importPath", "", "Go Import Path for the app.") srcPath *string = flag.String("srcPath", "", "Path to the source root.") // So compiler won't complain if the generated code doesn't reference reflect package... _ = reflect.Invalid ) func main() { flag.Parse() revel.Init(*runMode, *importPath, *srcPath) revel.INFO.Println("Running revel server") revel.RegisterController((*controllers.App)(nil), []*revel.MethodType{ &revel.MethodType{ Name: "Index", Args: []*revel.MethodArg{ }, RenderArgNames: map[int][]string{ 10: []string{ }, }, }, }) revel.RegisterController((*controllers0.Static)(nil), []*revel.MethodType{ &revel.MethodType{ Name: "Serve", Args: []*revel.MethodArg{ &revel.MethodArg{Name: "prefix", Type: reflect.TypeOf((*string)(nil)) }, &revel.MethodArg{Name: "filepath", Type: reflect.TypeOf((*string)(nil)) }, }, RenderArgNames: map[int][]string{ }, }, &revel.MethodType{ Name: "ServeModule", Args: []*revel.MethodArg{ &revel.MethodArg{Name: "moduleName", Type: reflect.TypeOf((*string)(nil)) }, &revel.MethodArg{Name: "prefix", Type: reflect.TypeOf((*string)(nil)) }, &revel.MethodArg{Name: "filepath", Type: reflect.TypeOf((*string)(nil)) }, }, RenderArgNames: map[int][]string{ }, }, }) revel.DefaultValidationKeys = map[string]map[int]string{ } revel.TestSuites = []interface{}{ } revel.Run(*port) }
routes
// GENERATED CODE - DO NOT EDIT package routes import "github.com/revel/revel" type tApp struct {} var App tApp func (_ tApp) Index( ) string { args := make(map[string]string) return revel.MainRouter.Reverse("App.Index", args).Url } type tStatic struct {} var Static tStatic func (_ tStatic) Serve( prefix string, filepath string, ) string { args := make(map[string]string) revel.Unbind(args, "prefix", prefix) revel.Unbind(args, "filepath", filepath) return revel.MainRouter.Reverse("Static.Serve", args).Url } func (_ tStatic) ServeModule( moduleName string, prefix string, filepath string, ) string { args := make(map[string]string) revel.Unbind(args, "moduleName", moduleName) revel.Unbind(args, "prefix", prefix) revel.Unbind(args, "filepath", filepath) return revel.MainRouter.Reverse("Static.ServeModule", args).Url }
まとめ
今までわかったこと
- revel new hoge/fuga
- revelが持ってるskeltonをごっそりそのままhoge/fugaというpathにコピーする
- あとちょっとしたconfとかいじる
- revel run hoge/fuga
- アプリケーションのソースからアプリケーションのtmpディレクトリにbinとして動くmainパッケージのGoソースを自動生成(templateパッケージを利用)
- アプリケーションbinをgo installする(go/buildパッケージを利用)
- アプリケーションbinコマンドを実行
- その中ではrevel.Run()を実行
ということで、revelの役割は
- アプリケーションのskelをごっそりつくってあげる
- アプリケーション独自のルーチンを書いたら、それを見つけて自動でRegisterControllerとかまでしてくれるGoソースを自動生成してくれる
- あとはrevelがそれを動かす
雑感
とりあえず今はこんくらいでいいや