goku
goku is a Web Mvc Framework for golang, mostly like ASP.NET MVC.
doc & api
Installation
To install goku, simply run go get github.com/QLeelulu/goku
. To use it in a program, use import "github.com/QLeelulu/goku"
To run example "todo" app, just:
$ cd $GOROOT/src/pkg/github.com/QLeelulu/goku/examples/todo/
$ go run app.go
maybe you need run todo.sql first.
Usage
package main
import (
"github.com/QLeelulu/goku"
"log"
"path"
"runtime"
"time"
)
var routes []*goku.Route = []*goku.Route{
&goku.Route{
Name: "static",
IsStatic: true,
Pattern: "/static/(.*)",
},
&goku.Route{
Name: "default",
Pattern: "/{controller}/{action}/{id}",
Default: map[string]string{"controller": "home", "action": "index", "id": "0"},
Constraint: map[string]string{"id": "\\d+"},
},
}
var config *goku.ServerConfig = &goku.ServerConfig{
Addr: ":8888",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
StaticPath: "static",
ViewPath: "views",
Debug: true,
}
func init() {
_, filename, _, _ := runtime.Caller(1)
config.RootDir = path.Dir(filename)
goku.Controller("home").
Get("index", func(ctx *goku.HttpContext) goku.ActionResulter {
return ctx.Html("Hello World")
})
}
func main() {
rt := &goku.RouteTable{Routes: routes}
s := goku.CreateServer(rt, nil, config)
goku.Logger().Logln("Server start on", s.Addr)
log.Fatal(s.ListenAndServe())
}
Route
var Routes []*goku.Route = []*goku.Route{
&goku.Route{
Name: "static",
IsStatic: true,
Pattern: "/public/(.*)",
},
&goku.Route{
Name: "edit",
Pattern: "/{controller}/{id}/{action}",
Default: map[string]string{"action": "edit"},
Constraint: map[string]string{"id": "\\d+"},
},
&goku.Route{
Name: "default",
Pattern: "/{controller}/{action}",
Default: map[string]string{"controller": "todo", "action": "index"},
},
}
rt := &goku.RouteTable{Routes: todo.Routes}
s := goku.CreateServer(rt, nil, nil)
log.Fatal(s.ListenAndServe())
or
rt := new(goku.RouteTable)
rt.Static("staticFile", "/static/(.*)")
rt.Map(
"blog-page",
"/blog/p/{page}",
map[string]string{"controller": "blog", "action": "page", "page": "0"},
map[string]string{"page": "\\d+"}
)
rt.Map(
"default",
"/{controller}/{action}",
map[string]string{"controller": "home", "action": "index"},
)
Controller And Action
goku.Controller("home").
Filters(new(TestControllerFilter)).
Get("index", func(ctx *goku.HttpContext) goku.ActionResulter {
return ctx.Html("Hello World")
}).Filters(new(TestActionFilter)).
Post("about", func(ctx *goku.HttpContext) goku.ActionResulter {
return ctx.Raw("About")
})
- get
/home/index
will return Hello World
- post
/home/index
will return 404 - post
/home/about
will return About
ActionResult
ActionResulter
is type interface. all the action must return ActionResulter.
you can return an ActionResulter in the action like this:
ctx.Raw("hi")
ctx.NotFound("oh no! ):")
ctx.Redirect("/")
ctx.View(viewModel)
ctx.Render("viewName", viewModel)
or you can return a ActionResulter by this
return &ActionResult{
StatusCode: http.StatusNotFound,
Headers: map[string]string{"Content-Type": "text/html"},
Body: "Page Not Found",
}
for more info, check the code.
View and ViewData
Views are the components that display the application's user interface (UI)
To render a view, you can just return a ViewResut
which implement the ActionResulter
interface.
just like this:
goku.Controller("blog").
Get("page", func(ctx *goku.HttpContext) goku.ActionResulter {
blog := GetBlogByid(10)
ctx.ViewData["SiteName"] = "My Blog"
return ctx.View(blog)
})
ctx.View()
will find the view in these rules:
- /{ViewPath}/{Controller}/{action}
- /{ViewPath}/shared/{action}
for example, ServerConfig.ViewPath is set to "views",
and return ctx.View() in home
controller's about
action,
it will find the view file in this rule:
- {ProjectDir}/views/home/about.html
- {ProjectDir}/views/shared/about.html
if you want to return a view that specified view name,
you can use ctx.Render:
ctx.Render("viewName", ViewModel)
ViewEngine & Template
ctx.ViewData["SiteName"] = "My Blog"
blogs := GetBlogs()
return ctx.View(blogs)
default template engine is golang's template.
<div class="box todos">
<h2 class="box">{{ .Data.SiteName }}</h2>
<ul>
{{range .Model}}
<li id="blog-{{.Id}}">
{{.Title}}
</li>
{{end}}
</ul>
</div>
Layout
layout.html
<!DOCTYPE html>
<html>
<head>
<title>Goku</title>
{{template "head"}}
</head>
<body>
{{template "body" .}}
</body>
</html>
body.html
{{define "head"}}
{{end}}
{{define "body"}}
I'm main content.
{{end}}
note the dot in {{template "body" .}}
, it will pass the ViewData to the sub template.
HtmlHelper?
More Template Engine Support
if you want to use mustache template,
check mustache.goku
HttpContext
type HttpContext struct {
Request *http.Request
responseWriter http.ResponseWriter
Method string
RouteData *RouteData
ViewData map[string]interface{}
Data map[string]interface{}
Result ActionResulter
Err error
User string
Canceled bool
}
Form Validation
you can create a form, to valid the user's input, and get the clean value.
import "github.com/QLeelulu/goku/form"
func CreateCommentForm() *goku.Form {
name := NewCharField("name", "Name", true).Range(3, 10).Field()
nickName := NewCharField("nick_name", "Nick Name", false).Min(3).Max(20).Field()
age := NewIntegerField("age", "Age", true).Range(18, 50).Field()
content := NewTextField("content", "Content", true).Min(10).Field()
form := NewForm(name, nickName, age, content)
return form
}
and then you can use this form like this:
f := CreateCommentForm()
f.FillByRequest(ctx.Request)
if f.Valid() {
m := f.CleanValues()
} else {
errs := f.Errors()
}
checkout form_test.go
DataBase
simple database api.
db, err := OpenMysql("mymysql", "tcp:localhost:3306*test_db/lulu/123456")
_, err = db.Query("select 1")
r, err := db.Select("test_blog", SqlQueryInfo{
Fields: "id, title, content",
Where: "id>?",
Params: []interface{}{0},
Limit: 10,
Offset: 0,
Group: "",
Order: "id desc",
})
vals := map[string]interface{}{
"title": "golang",
"content": "Go is an open source programming environment that " +
"makes it easy to build simple, reliable, and efficient software.",
"create_at": time.Now(),
}
r, err := db.Insert("test_blog", vals)
blog := TestBlog{
Title: "goku",
Content: "a mvc framework",
CreateAt: time.Now(),
}
r, err = db.InsertStruct(&blog)
blog := &TestBlog{}
err = db.GetStruct(blog, "id=?", 3)
qi := SqlQueryInfo{}
var blogs []Blog
err := db.GetStructs(&blogs, qi)
vals := map[string]interface{}{
"title": "js",
}
r, err2 := db.Update("test_blog", vals, "id=?", blog.Id)
r, err := db.Delete("test_blog", "id=?", 8)
checkout db_test.go
DataBase SQL Debug
if you want to debug what the sql query is, set db.Debug to true
db, err := OpenMysql("mymysql", "tcp:localhost:3306*test_db/username/pwd")
db.Debug = true
after you set db.Debug to true, while you run a db command, it will print the sql query to the log,
juse like this:
2012/07/30 20:58:03 SQL: UPDATE `user` SET friends=friends+? WHERE id=?;
PARAMS: [[1 2]]
Action Filter
type TestActionFilter struct {
}
func (tf *TestActionFilter) OnActionExecuting(ctx *goku.HttpContext) (ar goku.ActionResulter, err error) {
ctx.WriteString("OnActionExecuting - TestActionFilter \n")
return
}
func (tf *TestActionFilter) OnActionExecuted(ctx *goku.HttpContext) (ar goku.ActionResulter, err error) {
ctx.WriteString("OnActionExecuted - TestActionFilter \n")
return
}
func (tf *TestActionFilter) OnResultExecuting(ctx *goku.HttpContext) (ar goku.ActionResulter, err error) {
ctx.WriteString(" OnResultExecuting - TestActionFilter \n")
return
}
func (tf *TestActionFilter) OnResultExecuted(ctx *goku.HttpContext) (ar goku.ActionResulter, err error) {
ctx.WriteString(" OnResultExecuted - TestActionFilter \n")
return
}
Order of the filters execution is:
- OnActionExecuting
- -> Execute Action -> return ActionResulter
- OnActionExecuted
- OnResultExecuting
- -> ActionResulter.ExecuteResult
- OnResultExecuted
Middleware
type TestMiddleware struct {
}
func (tmd *TestMiddleware) OnBeginRequest(ctx *goku.HttpContext) (goku.ActionResulter, error) {
ctx.WriteString("OnBeginRequest - TestMiddleware \n")
return nil, nil
}
func (tmd *TestMiddleware) OnBeginMvcHandle(ctx *goku.HttpContext) (goku.ActionResulter, error) {
ctx.WriteString(" OnBeginMvcHandle - TestMiddleware \n")
return nil, nil
}
func (tmd *TestMiddleware) OnEndMvcHandle(ctx *goku.HttpContext) (goku.ActionResulter, error) {
ctx.WriteString(" OnEndMvcHandle - TestMiddleware \n")
return nil, nil
}
func (tmd *TestMiddleware) OnEndRequest(ctx *goku.HttpContext) (goku.ActionResulter, error) {
ctx.WriteString("OnEndRequest - TestMiddleware \n")
return nil, nil
}
Order of the middleware event execution is:
OnBeginRequest
OnBeginMvcHandle
(if not the static file request)- => run controller action (if not the static file request)
OnEndMvcHandle
(if not the static file request)OnEndRequest
Log
To use logger in goku, just:
goku.Logger().Logln("i", "am", "log")
goku.Logger().Errorln("oh", "no!", "Server Down!")
this will log like this:
2012/07/14 20:07:46 i am log
2012/07/14 20:07:46 [ERROR] oh no! Server Down!
Authors
License
View the LICENSE file.