Commit 934dd2e8 authored by Chen Liang's avatar Chen Liang

Merge branch 'master' of https://github.com/astaxie/beego

parents 0188fb37 07c628c7
{
"file_line": 500,
"func_line": 80,
"params_num":4,
"results_num":3,
"formated": true,
"pkg_name": true,
"camel_name":true,
"ignore":[
"a/*",
"b/*/c/*.go"
],
"fatal":[
"formated"
]
}
...@@ -113,8 +113,6 @@ func listConf(rw http.ResponseWriter, r *http.Request) { ...@@ -113,8 +113,6 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
m["SessionName"] = SessionName m["SessionName"] = SessionName
m["SessionGCMaxLifetime"] = SessionGCMaxLifetime m["SessionGCMaxLifetime"] = SessionGCMaxLifetime
m["SessionSavePath"] = SessionSavePath m["SessionSavePath"] = SessionSavePath
m["SessionHashFunc"] = SessionHashFunc
m["SessionHashKey"] = SessionHashKey
m["SessionCookieLifeTime"] = SessionCookieLifeTime m["SessionCookieLifeTime"] = SessionCookieLifeTime
m["UseFcgi"] = UseFcgi m["UseFcgi"] = UseFcgi
m["MaxMemory"] = MaxMemory m["MaxMemory"] = MaxMemory
...@@ -458,6 +456,7 @@ func (admin *adminApp) Run() { ...@@ -458,6 +456,7 @@ func (admin *adminApp) Run() {
for p, f := range admin.routers { for p, f := range admin.routers {
http.Handle(p, f) http.Handle(p, f)
} }
BeeLogger.Info("Admin server Running on %s", addr)
err := http.ListenAndServe(addr, nil) err := http.ListenAndServe(addr, nil)
if err != nil { if err != nil {
BeeLogger.Critical("Admin ListenAndServe: ", err) BeeLogger.Critical("Admin ListenAndServe: ", err)
......
...@@ -20,13 +20,8 @@ import ( ...@@ -20,13 +20,8 @@ import (
"net/http" "net/http"
"net/http/fcgi" "net/http/fcgi"
"time" "time"
"github.com/astaxie/beego/context"
) )
// FilterFunc defines filter function type.
type FilterFunc func(*context.Context)
// App defines beego application with a new PatternServeMux. // App defines beego application with a new PatternServeMux.
type App struct { type App struct {
Handlers *ControllerRegistor Handlers *ControllerRegistor
...@@ -48,8 +43,6 @@ func (app *App) Run() { ...@@ -48,8 +43,6 @@ func (app *App) Run() {
addr = fmt.Sprintf("%s:%d", HttpAddr, HttpPort) addr = fmt.Sprintf("%s:%d", HttpAddr, HttpPort)
} }
BeeLogger.Info("Running on %s", addr)
var ( var (
err error err error
l net.Listener l net.Listener
...@@ -57,15 +50,24 @@ func (app *App) Run() { ...@@ -57,15 +50,24 @@ func (app *App) Run() {
endRunning := make(chan bool, 1) endRunning := make(chan bool, 1)
if UseFcgi { if UseFcgi {
if HttpPort == 0 { if UseStdIo {
l, err = net.Listen("unix", addr) err = fcgi.Serve(nil, app.Handlers) // standard I/O
if err == nil {
BeeLogger.Info("Use FCGI via standard I/O")
} else {
BeeLogger.Info("Cannot use FCGI via standard I/O", err)
}
} else { } else {
l, err = net.Listen("tcp", addr) if HttpPort == 0 {
} l, err = net.Listen("unix", addr)
if err != nil { } else {
BeeLogger.Critical("Listen: ", err) l, err = net.Listen("tcp", addr)
}
if err != nil {
BeeLogger.Critical("Listen: ", err)
}
err = fcgi.Serve(l, app.Handlers)
} }
err = fcgi.Serve(l, app.Handlers)
} else { } else {
app.Server.Addr = addr app.Server.Addr = addr
app.Server.Handler = app.Handlers app.Server.Handler = app.Handlers
...@@ -78,6 +80,7 @@ func (app *App) Run() { ...@@ -78,6 +80,7 @@ func (app *App) Run() {
if HttpsPort != 0 { if HttpsPort != 0 {
app.Server.Addr = fmt.Sprintf("%s:%d", HttpAddr, HttpsPort) app.Server.Addr = fmt.Sprintf("%s:%d", HttpAddr, HttpsPort)
} }
BeeLogger.Info("https server Running on %s", app.Server.Addr)
err := app.Server.ListenAndServeTLS(HttpCertFile, HttpKeyFile) err := app.Server.ListenAndServeTLS(HttpCertFile, HttpKeyFile)
if err != nil { if err != nil {
BeeLogger.Critical("ListenAndServeTLS: ", err) BeeLogger.Critical("ListenAndServeTLS: ", err)
...@@ -90,11 +93,29 @@ func (app *App) Run() { ...@@ -90,11 +93,29 @@ func (app *App) Run() {
if EnableHttpListen { if EnableHttpListen {
go func() { go func() {
app.Server.Addr = addr app.Server.Addr = addr
err := app.Server.ListenAndServe() BeeLogger.Info("http server Running on %s", app.Server.Addr)
if err != nil { if ListenTCP4 && HttpAddr == "" {
BeeLogger.Critical("ListenAndServe: ", err) ln, err := net.Listen("tcp4", app.Server.Addr)
time.Sleep(100 * time.Microsecond) if err != nil {
endRunning <- true BeeLogger.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
return
}
err = app.Server.Serve(ln)
if err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
return
}
} else {
err := app.Server.ListenAndServe()
if err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
} }
}() }()
} }
......
...@@ -38,7 +38,7 @@ import ( ...@@ -38,7 +38,7 @@ import (
) )
// beego web framework version. // beego web framework version.
const VERSION = "1.4.1" const VERSION = "1.4.2"
type hookfunc func() error //hook function to run type hookfunc func() error //hook function to run
var hooks []hookfunc //hook function slice to store the hookfunc var hooks []hookfunc //hook function slice to store the hookfunc
...@@ -308,15 +308,20 @@ func SetStaticPath(url string, path string) *App { ...@@ -308,15 +308,20 @@ func SetStaticPath(url string, path string) *App {
// DelStaticPath removes the static folder setting in this url pattern in beego application. // DelStaticPath removes the static folder setting in this url pattern in beego application.
func DelStaticPath(url string) *App { func DelStaticPath(url string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
url = strings.TrimRight(url, "/")
delete(StaticDir, url) delete(StaticDir, url)
return BeeApp return BeeApp
} }
// InsertFilter adds a FilterFunc with pattern condition and action constant. // InsertFilter adds a FilterFunc with pattern condition and action constant.
// The pos means action constant including // The pos means action constant including
// beego.BeforeRouter, beego.AfterStatic, beego.BeforeExec, beego.AfterExec and beego.FinishRouter. // beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter.
func InsertFilter(pattern string, pos int, filter FilterFunc) *App { // The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
BeeApp.Handlers.InsertFilter(pattern, pos, filter) func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App {
BeeApp.Handlers.InsertFilter(pattern, pos, filter, params...)
return BeeApp return BeeApp
} }
...@@ -359,6 +364,9 @@ func initBeforeHttpRun() { ...@@ -359,6 +364,9 @@ func initBeforeHttpRun() {
} }
} }
//init mime
AddAPPStartHook(initMime)
// do hooks function // do hooks function
for _, hk := range hooks { for _, hk := range hooks {
err := hk() err := hk()
...@@ -373,10 +381,8 @@ func initBeforeHttpRun() { ...@@ -373,10 +381,8 @@ func initBeforeHttpRun() {
if sessionConfig == "" { if sessionConfig == "" {
sessionConfig = `{"cookieName":"` + SessionName + `",` + sessionConfig = `{"cookieName":"` + SessionName + `",` +
`"gclifetime":` + strconv.FormatInt(SessionGCMaxLifetime, 10) + `,` + `"gclifetime":` + strconv.FormatInt(SessionGCMaxLifetime, 10) + `,` +
`"providerConfig":"` + SessionSavePath + `",` + `"providerConfig":"` + filepath.ToSlash(SessionSavePath) + `",` +
`"secure":` + strconv.FormatBool(EnableHttpTLS) + `,` + `"secure":` + strconv.FormatBool(EnableHttpTLS) + `,` +
`"sessionIDHashFunc":"` + SessionHashFunc + `",` +
`"sessionIDHashKey":"` + SessionHashKey + `",` +
`"enableSetCookie":` + strconv.FormatBool(SessionAutoSetCookie) + `,` + `"enableSetCookie":` + strconv.FormatBool(SessionAutoSetCookie) + `,` +
`"domain":"` + SessionDomain + `",` + `"domain":"` + SessionDomain + `",` +
`"cookieLifeTime":` + strconv.Itoa(SessionCookieLifeTime) + `}` `"cookieLifeTime":` + strconv.Itoa(SessionCookieLifeTime) + `}`
...@@ -404,9 +410,6 @@ func initBeforeHttpRun() { ...@@ -404,9 +410,6 @@ func initBeforeHttpRun() {
Get("/docs", serverDocs) Get("/docs", serverDocs)
Get("/docs/*", serverDocs) Get("/docs/*", serverDocs)
} }
//init mime
AddAPPStartHook(initMime)
} }
// this function is for test package init // this function is for test package init
......
...@@ -81,13 +81,13 @@ func Register(name string, adapter Cache) { ...@@ -81,13 +81,13 @@ func Register(name string, adapter Cache) {
// Create a new cache driver by adapter name and config string. // Create a new cache driver by adapter name and config string.
// config need to be correct JSON as string: {"interval":360}. // config need to be correct JSON as string: {"interval":360}.
// it will start gc automatically. // it will start gc automatically.
func NewCache(adapterName, config string) (adapter Cache, e error) { func NewCache(adapterName, config string) (adapter Cache, err error) {
adapter, ok := adapters[adapterName] adapter, ok := adapters[adapterName]
if !ok { if !ok {
e = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName) err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
return return
} }
err := adapter.StartAndGC(config) err = adapter.StartAndGC(config)
if err != nil { if err != nil {
adapter = nil adapter = nil
} }
......
...@@ -75,14 +75,13 @@ func (rc *RedisCache) Get(key string) interface{} { ...@@ -75,14 +75,13 @@ func (rc *RedisCache) Get(key string) interface{} {
// put cache to redis. // put cache to redis.
func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error { func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error {
var err error var err error
if _, err = rc.do("SET", key, val); err != nil { if _, err = rc.do("SETEX", key, timeout, val); err != nil {
return err return err
} }
if _, err = rc.do("HSET", rc.key, key, true); err != nil { if _, err = rc.do("HSET", rc.key, key, true); err != nil {
return err return err
} }
_, err = rc.do("EXPIRE", key, timeout)
return err return err
} }
......
This diff is collapsed.
...@@ -48,6 +48,10 @@ type IniConfig struct { ...@@ -48,6 +48,10 @@ type IniConfig struct {
// ParseFile creates a new Config and parses the file configuration from the named file. // ParseFile creates a new Config and parses the file configuration from the named file.
func (ini *IniConfig) Parse(name string) (ConfigContainer, error) { func (ini *IniConfig) Parse(name string) (ConfigContainer, error) {
return ini.parseFile(name)
}
func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
file, err := os.Open(name) file, err := os.Open(name)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -66,6 +70,13 @@ func (ini *IniConfig) Parse(name string) (ConfigContainer, error) { ...@@ -66,6 +70,13 @@ func (ini *IniConfig) Parse(name string) (ConfigContainer, error) {
var comment bytes.Buffer var comment bytes.Buffer
buf := bufio.NewReader(file) buf := bufio.NewReader(file)
// check the BOM
head, err := buf.Peek(3)
if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 {
for i := 1; i <= 3; i++ {
buf.ReadByte()
}
}
section := DEFAULT_SECTION section := DEFAULT_SECTION
for { for {
line, _, err := buf.ReadLine() line, _, err := buf.ReadLine()
...@@ -108,13 +119,48 @@ func (ini *IniConfig) Parse(name string) (ConfigContainer, error) { ...@@ -108,13 +119,48 @@ func (ini *IniConfig) Parse(name string) (ConfigContainer, error) {
cfg.data[section] = make(map[string]string) cfg.data[section] = make(map[string]string)
} }
keyValue := bytes.SplitN(line, bEqual, 2) keyValue := bytes.SplitN(line, bEqual, 2)
key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
key = strings.ToLower(key)
// handle include "other.conf"
if len(keyValue) == 1 && strings.HasPrefix(key, "include") {
includefiles := strings.Fields(key)
if includefiles[0] == "include" && len(includefiles) == 2 {
otherfile := strings.Trim(includefiles[1], "\"")
if !path.IsAbs(otherfile) {
otherfile = path.Join(path.Dir(name), otherfile)
}
i, err := ini.parseFile(otherfile)
if err != nil {
return nil, err
}
for sec, dt := range i.data {
if _, ok := cfg.data[sec]; !ok {
cfg.data[sec] = make(map[string]string)
}
for k, v := range dt {
cfg.data[sec][k] = v
}
}
for sec, comm := range i.sectionComment {
cfg.sectionComment[sec] = comm
}
for k, comm := range i.keyComment {
cfg.keyComment[k] = comm
}
continue
}
}
if len(keyValue) != 2 {
return nil, errors.New("read the content error: \"" + string(line) + "\", should key = val")
}
val := bytes.TrimSpace(keyValue[1]) val := bytes.TrimSpace(keyValue[1])
if bytes.HasPrefix(val, bDQuote) { if bytes.HasPrefix(val, bDQuote) {
val = bytes.Trim(val, `"`) val = bytes.Trim(val, `"`)
} }
key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
key = strings.ToLower(key)
cfg.data[section][key] = string(val) cfg.data[section][key] = string(val)
if comment.Len() > 0 { if comment.Len() > 0 {
cfg.keyComment[section+"."+key] = comment.String() cfg.keyComment[section+"."+key] = comment.String()
......
...@@ -69,7 +69,8 @@ func (ctx *Context) Abort(status int, body string) { ...@@ -69,7 +69,8 @@ func (ctx *Context) Abort(status int, body string) {
panic(e) panic(e)
} }
// last panic user string // last panic user string
panic(body) ctx.ResponseWriter.Write([]byte(body))
panic("User stop run")
} }
// Write string to response body. // Write string to response body.
......
...@@ -382,8 +382,37 @@ func (c *Controller) GetStrings(key string) []string { ...@@ -382,8 +382,37 @@ func (c *Controller) GetStrings(key string) []string {
return []string{} return []string{}
} }
// GetInt returns input value as int64. // GetInt returns input as an int
func (c *Controller) GetInt(key string) (int64, error) { func (c *Controller) GetInt(key string) (int, error) {
return strconv.Atoi(c.Ctx.Input.Query(key))
}
// GetInt8 return input as an int8
func (c *Controller) GetInt8(key string) (int8, error) {
i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 8)
i8 := int8(i64)
return i8, err
}
// GetInt16 returns input as an int16
func (c *Controller) GetInt16(key string) (int16, error) {
i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 16)
i16 := int16(i64)
return i16, err
}
// GetInt32 returns input as an int32
func (c *Controller) GetInt32(key string) (int32, error) {
i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 32)
i32 := int32(i64)
return i32, err
}
// GetInt64 returns input value as int64.
func (c *Controller) GetInt64(key string) (int64, error) {
return strconv.ParseInt(c.Ctx.Input.Query(key), 10, 64) return strconv.ParseInt(c.Ctx.Input.Query(key), 10, 64)
} }
......
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"fmt"
"github.com/astaxie/beego/context"
)
func ExampleGetInt() {
i := &context.BeegoInput{Params: map[string]string{"age": "40"}}
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt("age")
fmt.Printf("%T", val)
//Output: int
}
func ExampleGetInt8() {
i := &context.BeegoInput{Params: map[string]string{"age": "40"}}
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt8("age")
fmt.Printf("%T", val)
//Output: int8
}
func ExampleGetInt16() {
i := &context.BeegoInput{Params: map[string]string{"age": "40"}}
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt16("age")
fmt.Printf("%T", val)
//Output: int16
}
func ExampleGetInt32() {
i := &context.BeegoInput{Params: map[string]string{"age": "40"}}
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt32("age")
fmt.Printf("%T", val)
//Output: int32
}
func ExampleGetInt64() {
i := &context.BeegoInput{Params: map[string]string{"age": "40"}}
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt64("age")
fmt.Printf("%T", val)
//Output: int64
}
...@@ -17,47 +17,47 @@ type ObjectController struct { ...@@ -17,47 +17,47 @@ type ObjectController struct {
beego.Controller beego.Controller
} }
func (this *ObjectController) Post() { func (o *ObjectController) Post() {
var ob models.Object var ob models.Object
json.Unmarshal(this.Ctx.Input.RequestBody, &ob) json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
objectid := models.AddOne(ob) objectid := models.AddOne(ob)
this.Data["json"] = map[string]string{"ObjectId": objectid} o.Data["json"] = map[string]string{"ObjectId": objectid}
this.ServeJson() o.ServeJson()
} }
func (this *ObjectController) Get() { func (o *ObjectController) Get() {
objectId := this.Ctx.Input.Params[":objectId"] objectId := o.Ctx.Input.Params[":objectId"]
if objectId != "" { if objectId != "" {
ob, err := models.GetOne(objectId) ob, err := models.GetOne(objectId)
if err != nil { if err != nil {
this.Data["json"] = err o.Data["json"] = err
} else { } else {
this.Data["json"] = ob o.Data["json"] = ob
} }
} else { } else {
obs := models.GetAll() obs := models.GetAll()
this.Data["json"] = obs o.Data["json"] = obs
} }
this.ServeJson() o.ServeJson()
} }
func (this *ObjectController) Put() { func (o *ObjectController) Put() {
objectId := this.Ctx.Input.Params[":objectId"] objectId := o.Ctx.Input.Params[":objectId"]
var ob models.Object var ob models.Object
json.Unmarshal(this.Ctx.Input.RequestBody, &ob) json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
err := models.Update(objectId, ob.Score) err := models.Update(objectId, ob.Score)
if err != nil { if err != nil {
this.Data["json"] = err o.Data["json"] = err
} else { } else {
this.Data["json"] = "update success!" o.Data["json"] = "update success!"
} }
this.ServeJson() o.ServeJson()
} }
func (this *ObjectController) Delete() { func (o *ObjectController) Delete() {
objectId := this.Ctx.Input.Params[":objectId"] objectId := o.Ctx.Input.Params[":objectId"]
models.Delete(objectId) models.Delete(objectId)
this.Data["json"] = "delete success!" o.Data["json"] = "delete success!"
this.ServeJson() o.ServeJson()
} }
...@@ -14,7 +14,7 @@ type MainController struct { ...@@ -14,7 +14,7 @@ type MainController struct {
beego.Controller beego.Controller
} }
func (this *MainController) Get() { func (m *MainController) Get() {
this.Data["host"] = this.Ctx.Request.Host m.Data["host"] = m.Ctx.Request.Host
this.TplNames = "index.tpl" m.TplNames = "index.tpl"
} }
...@@ -150,14 +150,14 @@ type WSController struct { ...@@ -150,14 +150,14 @@ type WSController struct {
} }
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
ReadBufferSize: 1024, ReadBufferSize: 1024,
WriteBufferSize: 1024, WriteBufferSize: 1024,
} }
func (this *WSController) Get() { func (w *WSController) Get() {
ws, err := upgrader.Upgrade(this.Ctx.ResponseWriter, this.Ctx.Request,nil) ws, err := upgrader.Upgrade(w.Ctx.ResponseWriter, w.Ctx.Request, nil)
if _, ok := err.(websocket.HandshakeError); ok { if _, ok := err.(websocket.HandshakeError); ok {
http.Error(this.Ctx.ResponseWriter, "Not a websocket handshake", 400) http.Error(w.Ctx.ResponseWriter, "Not a websocket handshake", 400)
return return
} else if err != nil { } else if err != nil {
return return
......
...@@ -14,12 +14,18 @@ ...@@ -14,12 +14,18 @@
package beego package beego
import "github.com/astaxie/beego/context"
// FilterFunc defines filter function type.
type FilterFunc func(*context.Context)
// FilterRouter defines filter operation before controller handler execution. // FilterRouter defines filter operation before controller handler execution.
// it can match patterned url and do filter function when action arrives. // it can match patterned url and do filter function when action arrives.
type FilterRouter struct { type FilterRouter struct {
filterFunc FilterFunc filterFunc FilterFunc
tree *Tree tree *Tree
pattern string pattern string
returnOnOutput bool
} }
// ValidRouter check current request is valid for this filter. // ValidRouter check current request is valid for this filter.
......
...@@ -32,6 +32,24 @@ func NewFlash() *FlashData { ...@@ -32,6 +32,24 @@ func NewFlash() *FlashData {
} }
} }
// Set message to flash
func (fd *FlashData) Set(key string, msg string, args ...interface{}) {
if len(args) == 0 {
fd.Data[key] = msg
} else {
fd.Data[key] = fmt.Sprintf(msg, args...)
}
}
// Success writes success message to flash.
func (fd *FlashData) Success(msg string, args ...interface{}) {
if len(args) == 0 {
fd.Data["success"] = msg
} else {
fd.Data["success"] = fmt.Sprintf(msg, args...)
}
}
// Notice writes notice message to flash. // Notice writes notice message to flash.
func (fd *FlashData) Notice(msg string, args ...interface{}) { func (fd *FlashData) Notice(msg string, args ...interface{}) {
if len(args) == 0 { if len(args) == 0 {
......
...@@ -25,12 +25,12 @@ type TestFlashController struct { ...@@ -25,12 +25,12 @@ type TestFlashController struct {
Controller Controller
} }
func (this *TestFlashController) TestWriteFlash() { func (t *TestFlashController) TestWriteFlash() {
flash := NewFlash() flash := NewFlash()
flash.Notice("TestFlashString") flash.Notice("TestFlashString")
flash.Store(&this.Controller) flash.Store(&t.Controller)
// we choose to serve json because we don't want to load a template html file // we choose to serve json because we don't want to load a template html file
this.ServeJson(true) t.ServeJson(true)
} }
func TestFlashHeader(t *testing.T) { func TestFlashHeader(t *testing.T) {
......
...@@ -37,6 +37,7 @@ import ( ...@@ -37,6 +37,7 @@ import (
"encoding/xml" "encoding/xml"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"mime/multipart" "mime/multipart"
"net" "net"
"net/http" "net/http"
...@@ -275,35 +276,36 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) { ...@@ -275,35 +276,36 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
} else { } else {
b.url = b.url + "?" + paramBody b.url = b.url + "?" + paramBody
} }
} else if b.req.Method == "POST" && b.req.Body == nil && len(paramBody) > 0 { } else if b.req.Method == "POST" && b.req.Body == nil {
if len(b.files) > 0 { if len(b.files) > 0 {
bodyBuf := &bytes.Buffer{} pr, pw := io.Pipe()
bodyWriter := multipart.NewWriter(bodyBuf) bodyWriter := multipart.NewWriter(pw)
for formname, filename := range b.files { go func() {
fileWriter, err := bodyWriter.CreateFormFile(formname, filename) for formname, filename := range b.files {
if err != nil { fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
return nil, err if err != nil {
log.Fatal(err)
}
fh, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
//iocopy
_, err = io.Copy(fileWriter, fh)
fh.Close()
if err != nil {
log.Fatal(err)
}
} }
fh, err := os.Open(filename) for k, v := range b.params {
if err != nil { bodyWriter.WriteField(k, v)
return nil, err
} }
//iocopy bodyWriter.Close()
_, err = io.Copy(fileWriter, fh) pw.Close()
fh.Close() }()
if err != nil { b.Header("Content-Type", bodyWriter.FormDataContentType())
return nil, err b.req.Body = ioutil.NopCloser(pr)
} } else if len(paramBody) > 0 {
}
for k, v := range b.params {
bodyWriter.WriteField(k, v)
}
contentType := bodyWriter.FormDataContentType()
bodyWriter.Close()
b.Header("Content-Type", contentType)
b.req.Body = ioutil.NopCloser(bodyBuf)
b.req.ContentLength = int64(bodyBuf.Len())
} else {
b.Header("Content-Type", "application/x-www-form-urlencoded") b.Header("Content-Type", "application/x-www-form-urlencoded")
b.Body(paramBody) b.Body(paramBody)
} }
...@@ -355,7 +357,7 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) { ...@@ -355,7 +357,7 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
Jar: jar, Jar: jar,
} }
if b.setting.UserAgent != "" { if b.setting.UserAgent != "" && b.req.Header.Get("User-Agent") == "" {
b.req.Header.Set("User-Agent", b.setting.UserAgent) b.req.Header.Set("User-Agent", b.setting.UserAgent)
} }
......
...@@ -66,23 +66,24 @@ func TestSimplePost(t *testing.T) { ...@@ -66,23 +66,24 @@ func TestSimplePost(t *testing.T) {
} }
} }
func TestPostFile(t *testing.T) { //func TestPostFile(t *testing.T) {
v := "smallfish" // v := "smallfish"
req := Post("http://httpbin.org/post") // req := Post("http://httpbin.org/post")
req.Param("username", v) // req.Debug(true)
req.PostFile("uploadfile", "httplib_test.go") // req.Param("username", v)
// req.PostFile("uploadfile", "httplib_test.go")
str, err := req.String()
if err != nil { // str, err := req.String()
t.Fatal(err) // if err != nil {
} // t.Fatal(err)
t.Log(str) // }
// t.Log(str)
n := strings.Index(str, v)
if n == -1 { // n := strings.Index(str, v)
t.Fatal(v + " not found in post") // if n == -1 {
} // t.Fatal(v + " not found in post")
} // }
//}
func TestSimplePut(t *testing.T) { func TestSimplePut(t *testing.T) {
str, err := Put("http://httpbin.org/put").String() str, err := Put("http://httpbin.org/put").String()
...@@ -203,3 +204,13 @@ func TestToFile(t *testing.T) { ...@@ -203,3 +204,13 @@ func TestToFile(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestHeader(t *testing.T) {
req := Get("http://httpbin.org/headers")
req.Header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36")
str, err := req.String()
if err != nil {
t.Fatal(err)
}
t.Log(str)
}
...@@ -155,6 +155,9 @@ func (bl *BeeLogger) writerMsg(loglevel int, msg string) error { ...@@ -155,6 +155,9 @@ func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
lm.level = loglevel lm.level = loglevel
if bl.enableFuncCallDepth { if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
if _, filename := path.Split(file); filename == "log.go" && (line == 97 || line == 83) {
_, file, line, ok = runtime.Caller(bl.loggerFuncCallDepth + 1)
}
if ok { if ok {
_, filename := path.Split(file) _, filename := path.Split(file)
lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg) lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg)
......
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package migration
type Table struct {
TableName string
Columns []*Column
}
func (t *Table) Create() string {
return ""
}
func (t *Table) Drop() string {
return ""
}
type Column struct {
Name string
Type string
Default interface{}
}
func Create(tbname string, columns ...Column) string {
return ""
}
func Drop(tbname string, columns ...Column) string {
return ""
}
func TableDDL(tbname string, columns ...Column) string {
return ""
}
...@@ -217,7 +217,7 @@ func (n *Namespace) Namespace(ns ...*Namespace) *Namespace { ...@@ -217,7 +217,7 @@ func (n *Namespace) Namespace(ns ...*Namespace) *Namespace {
n.handlers.routers[k] = t n.handlers.routers[k] = t
} }
} }
if n.handlers.enableFilter { if ni.handlers.enableFilter {
for pos, filterList := range ni.handlers.filters { for pos, filterList := range ni.handlers.filters {
for _, mr := range filterList { for _, mr := range filterList {
t := NewTree() t := NewTree()
......
...@@ -6,8 +6,6 @@ A powerful orm framework for go. ...@@ -6,8 +6,6 @@ A powerful orm framework for go.
It is heavily influenced by Django ORM, SQLAlchemy. It is heavily influenced by Django ORM, SQLAlchemy.
now, beta, unstable, may be changing some api make your app build failed.
**Support Database:** **Support Database:**
* MySQL: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) * MySQL: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
......
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package orm
import "errors"
type QueryBuilder interface {
Select(fields ...string) QueryBuilder
From(tables ...string) QueryBuilder
InnerJoin(table string) QueryBuilder
LeftJoin(table string) QueryBuilder
RightJoin(table string) QueryBuilder
On(cond string) QueryBuilder
Where(cond string) QueryBuilder
And(cond string) QueryBuilder
Or(cond string) QueryBuilder
In(vals ...string) QueryBuilder
OrderBy(fields ...string) QueryBuilder
Asc() QueryBuilder
Desc() QueryBuilder
Limit(limit int) QueryBuilder
Offset(offset int) QueryBuilder
GroupBy(fields ...string) QueryBuilder
Having(cond string) QueryBuilder
Update(tables ...string) QueryBuilder
Set(kv ...string) QueryBuilder
Delete(tables ...string) QueryBuilder
InsertInto(table string, fields ...string) QueryBuilder
Values(vals ...string) QueryBuilder
Subquery(sub string, alias string) string
String() string
}
func NewQueryBuilder(driver string) (qb QueryBuilder, err error) {
if driver == "mysql" {
qb = new(MySQLQueryBuilder)
} else if driver == "postgres" {
err = errors.New("postgres query builder is not supported yet!")
} else if driver == "sqlite" {
err = errors.New("sqlite query builder is not supported yet!")
} else {
err = errors.New("unknown driver for query builder!")
}
return
}
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package orm
import (
"fmt"
"strconv"
"strings"
)
const COMMA_SPACE = ", "
type MySQLQueryBuilder struct {
Tokens []string
}
func (qb *MySQLQueryBuilder) Select(fields ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "SELECT", strings.Join(fields, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) InnerJoin(table string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "INNER JOIN", table)
return qb
}
func (qb *MySQLQueryBuilder) LeftJoin(table string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "LEFT JOIN", table)
return qb
}
func (qb *MySQLQueryBuilder) RightJoin(table string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "RIGHT JOIN", table)
return qb
}
func (qb *MySQLQueryBuilder) On(cond string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "ON", cond)
return qb
}
func (qb *MySQLQueryBuilder) Where(cond string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "WHERE", cond)
return qb
}
func (qb *MySQLQueryBuilder) And(cond string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "AND", cond)
return qb
}
func (qb *MySQLQueryBuilder) Or(cond string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "OR", cond)
return qb
}
func (qb *MySQLQueryBuilder) In(vals ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "IN", "(", strings.Join(vals, COMMA_SPACE), ")")
return qb
}
func (qb *MySQLQueryBuilder) OrderBy(fields ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "ORDER BY", strings.Join(fields, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) Asc() QueryBuilder {
qb.Tokens = append(qb.Tokens, "ASC")
return qb
}
func (qb *MySQLQueryBuilder) Desc() QueryBuilder {
qb.Tokens = append(qb.Tokens, "DESC")
return qb
}
func (qb *MySQLQueryBuilder) Limit(limit int) QueryBuilder {
qb.Tokens = append(qb.Tokens, "LIMIT", strconv.Itoa(limit))
return qb
}
func (qb *MySQLQueryBuilder) Offset(offset int) QueryBuilder {
qb.Tokens = append(qb.Tokens, "OFFSET", strconv.Itoa(offset))
return qb
}
func (qb *MySQLQueryBuilder) GroupBy(fields ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "GROUP BY", strings.Join(fields, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) Having(cond string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "HAVING", cond)
return qb
}
func (qb *MySQLQueryBuilder) Update(tables ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "UPDATE", strings.Join(tables, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) Set(kv ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "SET", strings.Join(kv, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) Delete(tables ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "DELETE")
if len(tables) != 0 {
qb.Tokens = append(qb.Tokens, strings.Join(tables, COMMA_SPACE))
}
return qb
}
func (qb *MySQLQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "INSERT INTO", table)
if len(fields) != 0 {
fieldsStr := strings.Join(fields, COMMA_SPACE)
qb.Tokens = append(qb.Tokens, "(", fieldsStr, ")")
}
return qb
}
func (qb *MySQLQueryBuilder) Values(vals ...string) QueryBuilder {
valsStr := strings.Join(vals, COMMA_SPACE)
qb.Tokens = append(qb.Tokens, "VALUES", "(", valsStr, ")")
return qb
}
func (qb *MySQLQueryBuilder) Subquery(sub string, alias string) string {
return fmt.Sprintf("(%s) AS %s", sub, alias)
}
func (qb *MySQLQueryBuilder) String() string {
return strings.Join(qb.Tokens, " ")
}
...@@ -42,20 +42,25 @@ func init() { ...@@ -42,20 +42,25 @@ func init() {
var ( var (
lastupdateFilename string = "lastupdate.tmp" lastupdateFilename string = "lastupdate.tmp"
commentFilename string
pkgLastupdate map[string]int64 pkgLastupdate map[string]int64
genInfoList map[string][]ControllerComments genInfoList map[string][]ControllerComments
) )
const COMMENTFL = "commentsRouter_"
func init() { func init() {
pkgLastupdate = make(map[string]int64) pkgLastupdate = make(map[string]int64)
genInfoList = make(map[string][]ControllerComments)
} }
func parserPkg(pkgRealpath, pkgpath string) error { func parserPkg(pkgRealpath, pkgpath string) error {
rep := strings.NewReplacer("/", "_", ".", "_")
commentFilename = COMMENTFL + rep.Replace(pkgpath) + ".go"
if !compareFile(pkgRealpath) { if !compareFile(pkgRealpath) {
Info(pkgRealpath + " don't has updated") Info(pkgRealpath + " don't has updated")
return nil return nil
} }
genInfoList = make(map[string][]ControllerComments)
fileSet := token.NewFileSet() fileSet := token.NewFileSet()
astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool { astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool {
name := info.Name() name := info.Name()
...@@ -155,7 +160,7 @@ func genRouterCode() { ...@@ -155,7 +160,7 @@ func genRouterCode() {
} }
} }
if globalinfo != "" { if globalinfo != "" {
f, err := os.Create(path.Join(workPath, "routers", "commentsRouter.go")) f, err := os.Create(path.Join(workPath, "routers", commentFilename))
if err != nil { if err != nil {
panic(err) panic(err)
} }
...@@ -165,7 +170,7 @@ func genRouterCode() { ...@@ -165,7 +170,7 @@ func genRouterCode() {
} }
func compareFile(pkgRealpath string) bool { func compareFile(pkgRealpath string) bool {
if !utils.FileExists(path.Join(workPath, "routers", "commentsRouter.go")) { if !utils.FileExists(path.Join(workPath, "routers", commentFilename)) {
return true return true
} }
if utils.FileExists(path.Join(workPath, lastupdateFilename)) { if utils.FileExists(path.Join(workPath, lastupdateFilename)) {
......
...@@ -72,9 +72,31 @@ var ( ...@@ -72,9 +72,31 @@ var (
"SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml", "SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml",
"GetControllerAndAction"} "GetControllerAndAction"}
url_placeholder = "{{placeholder}}" url_placeholder = "{{placeholder}}"
DefaultLogFilter FilterHandler = &logFilter{}
) )
type FilterHandler interface {
Filter(*beecontext.Context) bool
}
// default log filter static file will not show
type logFilter struct {
}
func (l *logFilter) Filter(ctx *beecontext.Context) bool {
requestPath := path.Clean(ctx.Input.Request.URL.Path)
if requestPath == "/favicon.ico" || requestPath == "/robots.txt" {
return true
}
for prefix, _ := range StaticDir {
if strings.HasPrefix(requestPath, prefix) {
return true
}
}
return false
}
// To append a slice's value into "exceptMethod", for controller's methods shouldn't reflect to AutoRouter // To append a slice's value into "exceptMethod", for controller's methods shouldn't reflect to AutoRouter
func ExceptMethodAppend(action string) { func ExceptMethodAppend(action string) {
exceptMethod = append(exceptMethod, action) exceptMethod = append(exceptMethod, action)
...@@ -163,6 +185,9 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingM ...@@ -163,6 +185,9 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingM
} }
func (p *ControllerRegistor) addToRouter(method, pattern string, r *controllerInfo) { func (p *ControllerRegistor) addToRouter(method, pattern string, r *controllerInfo) {
if !RouterCaseSensitive {
pattern = strings.ToLower(pattern)
}
if t, ok := p.routers[method]; ok { if t, ok := p.routers[method]; ok {
t.AddRouter(pattern, r) t.AddRouter(pattern, r)
} else { } else {
...@@ -376,11 +401,21 @@ func (p *ControllerRegistor) AddAutoPrefix(prefix string, c ControllerInterface) ...@@ -376,11 +401,21 @@ func (p *ControllerRegistor) AddAutoPrefix(prefix string, c ControllerInterface)
} }
// Add a FilterFunc with pattern rule and action constant. // Add a FilterFunc with pattern rule and action constant.
func (p *ControllerRegistor) InsertFilter(pattern string, pos int, filter FilterFunc) error { // The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
func (p *ControllerRegistor) InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) error {
mr := new(FilterRouter) mr := new(FilterRouter)
mr.tree = NewTree() mr.tree = NewTree()
mr.pattern = pattern mr.pattern = pattern
mr.filterFunc = filter mr.filterFunc = filter
if !RouterCaseSensitive {
pattern = strings.ToLower(pattern)
}
if len(params) == 0 {
mr.returnOnOutput = true
} else {
mr.returnOnOutput = params[0]
}
mr.tree.AddRouter(pattern, true) mr.tree.AddRouter(pattern, true)
return p.insertFilterRouter(pos, mr) return p.insertFilterRouter(pos, mr)
} }
...@@ -415,10 +450,10 @@ func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string { ...@@ -415,10 +450,10 @@ func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string {
} }
} }
} }
controllName := strings.Join(paths[:len(paths)-1], ".") controllName := strings.Join(paths[:len(paths)-1], "/")
methodName := paths[len(paths)-1] methodName := paths[len(paths)-1]
for _, t := range p.routers { for m, t := range p.routers {
ok, url := p.geturl(t, "/", controllName, methodName, params) ok, url := p.geturl(t, "/", controllName, methodName, params, m)
if ok { if ok {
return url return url
} }
...@@ -426,24 +461,25 @@ func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string { ...@@ -426,24 +461,25 @@ func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string {
return "" return ""
} }
func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName string, params map[string]string) (bool, string) { func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName string, params map[string]string, httpMethod string) (bool, string) {
for k, subtree := range t.fixrouters { for k, subtree := range t.fixrouters {
u := path.Join(url, k) u := path.Join(url, k)
ok, u := p.geturl(subtree, u, controllName, methodName, params) ok, u := p.geturl(subtree, u, controllName, methodName, params, httpMethod)
if ok { if ok {
return ok, u return ok, u
} }
} }
if t.wildcard != nil { if t.wildcard != nil {
url = path.Join(url, url_placeholder) u := path.Join(url, url_placeholder)
ok, u := p.geturl(t.wildcard, url, controllName, methodName, params) ok, u := p.geturl(t.wildcard, u, controllName, methodName, params, httpMethod)
if ok { if ok {
return ok, u return ok, u
} }
} }
for _, l := range t.leaves { for _, l := range t.leaves {
if c, ok := l.runObject.(*controllerInfo); ok { if c, ok := l.runObject.(*controllerInfo); ok {
if c.routerType == routerTypeBeego && c.controllerType.Name() == controllName { if c.routerType == routerTypeBeego &&
strings.HasSuffix(path.Join(c.controllerType.PkgPath(), c.controllerType.Name()), controllName) {
find := false find := false
if _, ok := HTTPMETHOD[strings.ToUpper(methodName)]; ok { if _, ok := HTTPMETHOD[strings.ToUpper(methodName)]; ok {
if len(c.methods) == 0 { if len(c.methods) == 0 {
...@@ -455,8 +491,8 @@ func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName strin ...@@ -455,8 +491,8 @@ func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName strin
} }
} }
if !find { if !find {
for _, md := range c.methods { for m, md := range c.methods {
if md == methodName { if (m == "*" || m == httpMethod) && md == methodName {
find = true find = true
} }
} }
...@@ -564,15 +600,21 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) ...@@ -564,15 +600,21 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
context.Output.Context = context context.Output.Context = context
context.Output.EnableGzip = EnableGzip context.Output.EnableGzip = EnableGzip
var urlPath string
if !RouterCaseSensitive {
urlPath = strings.ToLower(r.URL.Path)
} else {
urlPath = r.URL.Path
}
// defined filter function // defined filter function
do_filter := func(pos int) (started bool) { do_filter := func(pos int) (started bool) {
if p.enableFilter { if p.enableFilter {
if l, ok := p.filters[pos]; ok { if l, ok := p.filters[pos]; ok {
for _, filterR := range l { for _, filterR := range l {
if ok, p := filterR.ValidRouter(r.URL.Path); ok { if ok, p := filterR.ValidRouter(urlPath); ok {
context.Input.Params = p context.Input.Params = p
filterR.filterFunc(context) filterR.filterFunc(context)
if w.started { if filterR.returnOnOutput && w.started {
return true return true
} }
} }
...@@ -602,7 +644,13 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) ...@@ -602,7 +644,13 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
// session init // session init
if SessionOn { if SessionOn {
context.Input.CruSession = GlobalSessions.SessionStart(w, r) var err error
context.Input.CruSession, err = GlobalSessions.SessionStart(w, r)
if err != nil {
Error(err)
middleware.Exception("503", rw, r, "")
return
}
defer func() { defer func() {
context.Input.CruSession.SessionRelease(w) context.Input.CruSession.SessionRelease(w)
}() }()
...@@ -626,8 +674,18 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) ...@@ -626,8 +674,18 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
} }
if !findrouter { if !findrouter {
if t, ok := p.routers[r.Method]; ok { http_method := r.Method
runObject, p := t.Match(r.URL.Path)
if http_method == "POST" && context.Input.Query("_method") == "PUT" {
http_method = "PUT"
}
if http_method == "POST" && context.Input.Query("_method") == "DELETE" {
http_method = "DELETE"
}
if t, ok := p.routers[http_method]; ok {
runObject, p := t.Match(urlPath)
if r, ok := runObject.(*controllerInfo); ok { if r, ok := runObject.(*controllerInfo); ok {
routerInfo = r routerInfo = r
findrouter = true findrouter = true
...@@ -783,7 +841,9 @@ Admin: ...@@ -783,7 +841,9 @@ Admin:
} else { } else {
devinfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s |", r.Method, r.URL.Path, timeend.String(), "notmatch") devinfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s |", r.Method, r.URL.Path, timeend.String(), "notmatch")
} }
Debug(devinfo) if DefaultLogFilter == nil || !DefaultLogFilter.Filter(context) {
Debug(devinfo)
}
} }
// Call WriteHeader if status code has been set changed // Call WriteHeader if status code has been set changed
...@@ -797,7 +857,9 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques ...@@ -797,7 +857,9 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques
if err == USERSTOPRUN { if err == USERSTOPRUN {
return return
} }
if _, ok := err.(middleware.HTTPException); ok { if he, ok := err.(middleware.HTTPException); ok {
rw.WriteHeader(he.StatusCode)
rw.Write([]byte(he.Description))
// catch intented errors, only for HTTP 4XX and 5XX // catch intented errors, only for HTTP 4XX and 5XX
} else { } else {
if RunMode == "dev" { if RunMode == "dev" {
...@@ -829,9 +891,15 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques ...@@ -829,9 +891,15 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques
} else { } else {
// in production model show all infomation // in production model show all infomation
if ErrorsShow { if ErrorsShow {
handler := p.getErrorHandler(fmt.Sprint(err)) if handler, ok := middleware.ErrorMaps[fmt.Sprint(err)]; ok {
handler(rw, r) handler(rw, r)
return return
} else if handler, ok := middleware.ErrorMaps["503"]; ok {
handler(rw, r)
return
} else {
rw.Write([]byte(fmt.Sprint(err)))
}
} else { } else {
Critical("the request url is ", r.URL.Path) Critical("the request url is ", r.URL.Path)
Critical("Handler crashed with error", err) Critical("Handler crashed with error", err)
...@@ -850,24 +918,6 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques ...@@ -850,24 +918,6 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques
} }
} }
// there always should be error handler that sets error code accordingly for all unhandled errors.
// in order to have custom UI for error page it's necessary to override "500" error.
func (p *ControllerRegistor) getErrorHandler(errorCode string) func(rw http.ResponseWriter, r *http.Request) {
handler := middleware.SimpleServerError
ok := true
if errorCode != "" {
handler, ok = middleware.ErrorMaps[errorCode]
if !ok {
handler, ok = middleware.ErrorMaps["500"]
}
if !ok || handler == nil {
handler = middleware.SimpleServerError
}
}
return handler
}
//responseWriter is a wrapper for the http.ResponseWriter //responseWriter is a wrapper for the http.ResponseWriter
//started set to true if response was written to then don't execute other handler //started set to true if response was written to then don't execute other handler
type responseWriter struct { type responseWriter struct {
......
...@@ -17,6 +17,7 @@ package beego ...@@ -17,6 +17,7 @@ package beego
import ( import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings"
"testing" "testing"
"github.com/astaxie/beego/context" "github.com/astaxie/beego/context"
...@@ -26,33 +27,33 @@ type TestController struct { ...@@ -26,33 +27,33 @@ type TestController struct {
Controller Controller
} }
func (this *TestController) Get() { func (tc *TestController) Get() {
this.Data["Username"] = "astaxie" tc.Data["Username"] = "astaxie"
this.Ctx.Output.Body([]byte("ok")) tc.Ctx.Output.Body([]byte("ok"))
} }
func (this *TestController) Post() { func (tc *TestController) Post() {
this.Ctx.Output.Body([]byte(this.Ctx.Input.Query(":name"))) tc.Ctx.Output.Body([]byte(tc.Ctx.Input.Query(":name")))
} }
func (this *TestController) Param() { func (tc *TestController) Param() {
this.Ctx.Output.Body([]byte(this.Ctx.Input.Query(":name"))) tc.Ctx.Output.Body([]byte(tc.Ctx.Input.Query(":name")))
} }
func (this *TestController) List() { func (tc *TestController) List() {
this.Ctx.Output.Body([]byte("i am list")) tc.Ctx.Output.Body([]byte("i am list"))
} }
func (this *TestController) Params() { func (tc *TestController) Params() {
this.Ctx.Output.Body([]byte(this.Ctx.Input.Params["0"] + this.Ctx.Input.Params["1"] + this.Ctx.Input.Params["2"])) tc.Ctx.Output.Body([]byte(tc.Ctx.Input.Params["0"] + tc.Ctx.Input.Params["1"] + tc.Ctx.Input.Params["2"]))
} }
func (this *TestController) Myext() { func (tc *TestController) Myext() {
this.Ctx.Output.Body([]byte(this.Ctx.Input.Param(":ext"))) tc.Ctx.Output.Body([]byte(tc.Ctx.Input.Param(":ext")))
} }
func (this *TestController) GetUrl() { func (tc *TestController) GetUrl() {
this.Ctx.Output.Body([]byte(this.UrlFor(".Myext"))) tc.Ctx.Output.Body([]byte(tc.UrlFor(".Myext")))
} }
func (t *TestController) GetParams() { func (t *TestController) GetParams() {
...@@ -385,3 +386,196 @@ func testRequest(method, path string) (*httptest.ResponseRecorder, *http.Request ...@@ -385,3 +386,196 @@ func testRequest(method, path string) (*httptest.ResponseRecorder, *http.Request
return recorder, request return recorder, request
} }
// Execution point: BeforeRouter
// expectation: only BeforeRouter function is executed, notmatch output as router doesn't handle
func TestFilterBeforeRouter(t *testing.T) {
testName := "TestFilterBeforeRouter"
url := "/beforeRouter"
mux := NewControllerRegister()
mux.InsertFilter(url, BeforeRouter, beegoBeforeRouter1)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "BeforeRouter1") == false {
t.Errorf(testName + " BeforeRouter did not run")
}
if strings.Contains(rw.Body.String(), "hello") == true {
t.Errorf(testName + " BeforeRouter did not return properly")
}
}
// Execution point: BeforeExec
// expectation: only BeforeExec function is executed, match as router determines route only
func TestFilterBeforeExec(t *testing.T) {
testName := "TestFilterBeforeExec"
url := "/beforeExec"
mux := NewControllerRegister()
mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput)
mux.InsertFilter(url, BeforeExec, beegoBeforeExec1)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "BeforeExec1") == false {
t.Errorf(testName + " BeforeExec did not run")
}
if strings.Contains(rw.Body.String(), "hello") == true {
t.Errorf(testName + " BeforeExec did not return properly")
}
if strings.Contains(rw.Body.String(), "BeforeRouter") == true {
t.Errorf(testName + " BeforeRouter ran in error")
}
}
// Execution point: AfterExec
// expectation: only AfterExec function is executed, match as router handles
func TestFilterAfterExec(t *testing.T) {
testName := "TestFilterAfterExec"
url := "/afterExec"
mux := NewControllerRegister()
mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput)
mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput)
mux.InsertFilter(url, AfterExec, beegoAfterExec1)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "AfterExec1") == false {
t.Errorf(testName + " AfterExec did not run")
}
if strings.Contains(rw.Body.String(), "hello") == false {
t.Errorf(testName + " handler did not run properly")
}
if strings.Contains(rw.Body.String(), "BeforeRouter") == true {
t.Errorf(testName + " BeforeRouter ran in error")
}
if strings.Contains(rw.Body.String(), "BeforeExec") == true {
t.Errorf(testName + " BeforeExec ran in error")
}
}
// Execution point: FinishRouter
// expectation: only FinishRouter function is executed, match as router handles
func TestFilterFinishRouter(t *testing.T) {
testName := "TestFilterFinishRouter"
url := "/finishRouter"
mux := NewControllerRegister()
mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput)
mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput)
mux.InsertFilter(url, AfterExec, beegoFilterNoOutput)
mux.InsertFilter(url, FinishRouter, beegoFinishRouter1)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "FinishRouter1") == true {
t.Errorf(testName + " FinishRouter did not run")
}
if strings.Contains(rw.Body.String(), "hello") == false {
t.Errorf(testName + " handler did not run properly")
}
if strings.Contains(rw.Body.String(), "AfterExec1") == true {
t.Errorf(testName + " AfterExec ran in error")
}
if strings.Contains(rw.Body.String(), "BeforeRouter") == true {
t.Errorf(testName + " BeforeRouter ran in error")
}
if strings.Contains(rw.Body.String(), "BeforeExec") == true {
t.Errorf(testName + " BeforeExec ran in error")
}
}
// Execution point: FinishRouter
// expectation: only first FinishRouter function is executed, match as router handles
func TestFilterFinishRouterMultiFirstOnly(t *testing.T) {
testName := "TestFilterFinishRouterMultiFirstOnly"
url := "/finishRouterMultiFirstOnly"
mux := NewControllerRegister()
mux.InsertFilter(url, FinishRouter, beegoFinishRouter1)
mux.InsertFilter(url, FinishRouter, beegoFinishRouter2)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "FinishRouter1") == false {
t.Errorf(testName + " FinishRouter1 did not run")
}
if strings.Contains(rw.Body.String(), "hello") == false {
t.Errorf(testName + " handler did not run properly")
}
// not expected in body
if strings.Contains(rw.Body.String(), "FinishRouter2") == true {
t.Errorf(testName + " FinishRouter2 did run")
}
}
// Execution point: FinishRouter
// expectation: both FinishRouter functions execute, match as router handles
func TestFilterFinishRouterMulti(t *testing.T) {
testName := "TestFilterFinishRouterMulti"
url := "/finishRouterMulti"
mux := NewControllerRegister()
mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, false)
mux.InsertFilter(url, FinishRouter, beegoFinishRouter2)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "FinishRouter1") == false {
t.Errorf(testName + " FinishRouter1 did not run")
}
if strings.Contains(rw.Body.String(), "hello") == false {
t.Errorf(testName + " handler did not run properly")
}
if strings.Contains(rw.Body.String(), "FinishRouter2") == false {
t.Errorf(testName + " FinishRouter2 did not run properly")
}
}
func beegoFilterNoOutput(ctx *context.Context) {
return
}
func beegoBeforeRouter1(ctx *context.Context) {
ctx.WriteString("|BeforeRouter1")
}
func beegoBeforeRouter2(ctx *context.Context) {
ctx.WriteString("|BeforeRouter2")
}
func beegoBeforeExec1(ctx *context.Context) {
ctx.WriteString("|BeforeExec1")
}
func beegoBeforeExec2(ctx *context.Context) {
ctx.WriteString("|BeforeExec2")
}
func beegoAfterExec1(ctx *context.Context) {
ctx.WriteString("|AfterExec1")
}
func beegoAfterExec2(ctx *context.Context) {
ctx.WriteString("|AfterExec2")
}
func beegoFinishRouter1(ctx *context.Context) {
ctx.WriteString("|FinishRouter1")
}
func beegoFinishRouter2(ctx *context.Context) {
ctx.WriteString("|FinishRouter2")
}
...@@ -109,7 +109,7 @@ func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) { ...@@ -109,7 +109,7 @@ func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) {
return return
} }
c.Do("SET", rs.sid, string(b), "EX", rs.maxlifetime) c.Do("SETEX", rs.sid, rs.maxlifetime, string(b))
} }
// redis session provider // redis session provider
......
...@@ -29,7 +29,10 @@ func TestCookie(t *testing.T) { ...@@ -29,7 +29,10 @@ func TestCookie(t *testing.T) {
} }
r, _ := http.NewRequest("GET", "/", nil) r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
sess := globalSessions.SessionStart(w, r) sess, err := globalSessions.SessionStart(w, r)
if err != nil {
t.Fatal("set error,", err)
}
err = sess.Set("username", "astaxie") err = sess.Set("username", "astaxie")
if err != nil { if err != nil {
t.Fatal("set error,", err) t.Fatal("set error,", err)
......
...@@ -26,9 +26,12 @@ func TestMem(t *testing.T) { ...@@ -26,9 +26,12 @@ func TestMem(t *testing.T) {
go globalSessions.GC() go globalSessions.GC()
r, _ := http.NewRequest("GET", "/", nil) r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
sess := globalSessions.SessionStart(w, r) sess, err := globalSessions.SessionStart(w, r)
if err != nil {
t.Fatal("set error,", err)
}
defer sess.SessionRelease(w) defer sess.SessionRelease(w)
err := sess.Set("username", "astaxie") err = sess.Set("username", "astaxie")
if err != nil { if err != nil {
t.Fatal("set error,", err) t.Fatal("set error,", err)
} }
......
...@@ -28,19 +28,13 @@ ...@@ -28,19 +28,13 @@
package session package session
import ( import (
"crypto/hmac"
"crypto/md5"
"crypto/rand" "crypto/rand"
"crypto/sha1"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"net/url" "net/url"
"time" "time"
"github.com/astaxie/beego/utils"
) )
// SessionStore contains all data for one session process with specific id. // SessionStore contains all data for one session process with specific id.
...@@ -81,16 +75,15 @@ func Register(name string, provide Provider) { ...@@ -81,16 +75,15 @@ func Register(name string, provide Provider) {
} }
type managerConfig struct { type managerConfig struct {
CookieName string `json:"cookieName"` CookieName string `json:"cookieName"`
EnableSetCookie bool `json:"enableSetCookie,omitempty"` EnableSetCookie bool `json:"enableSetCookie,omitempty"`
Gclifetime int64 `json:"gclifetime"` Gclifetime int64 `json:"gclifetime"`
Maxlifetime int64 `json:"maxLifetime"` Maxlifetime int64 `json:"maxLifetime"`
Secure bool `json:"secure"` Secure bool `json:"secure"`
SessionIDHashFunc string `json:"sessionIDHashFunc"` CookieLifeTime int `json:"cookieLifeTime"`
SessionIDHashKey string `json:"sessionIDHashKey"` ProviderConfig string `json:"providerConfig"`
CookieLifeTime int `json:"cookieLifeTime"` Domain string `json:"domain"`
ProviderConfig string `json:"providerConfig"` SessionIdLength int64 `json:"sessionIdLength"`
Domain string `json:"domain"`
} }
// Manager contains Provider and its configuration. // Manager contains Provider and its configuration.
...@@ -129,11 +122,9 @@ func NewManager(provideName, config string) (*Manager, error) { ...@@ -129,11 +122,9 @@ func NewManager(provideName, config string) (*Manager, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if cf.SessionIDHashFunc == "" {
cf.SessionIDHashFunc = "sha1" if cf.SessionIdLength == 0 {
} cf.SessionIdLength = 16
if cf.SessionIDHashKey == "" {
cf.SessionIDHashKey = string(generateRandomKey(16))
} }
return &Manager{ return &Manager{
...@@ -144,11 +135,14 @@ func NewManager(provideName, config string) (*Manager, error) { ...@@ -144,11 +135,14 @@ func NewManager(provideName, config string) (*Manager, error) {
// Start session. generate or read the session id from http request. // Start session. generate or read the session id from http request.
// if session id exists, return SessionStore with this id. // if session id exists, return SessionStore with this id.
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session SessionStore) { func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session SessionStore, err error) {
cookie, err := r.Cookie(manager.config.CookieName) cookie, errs := r.Cookie(manager.config.CookieName)
if err != nil || cookie.Value == "" { if errs != nil || cookie.Value == "" {
sid := manager.sessionId(r) sid, errs := manager.sessionId(r)
session, _ = manager.provider.SessionRead(sid) if errs != nil {
return nil, errs
}
session, err = manager.provider.SessionRead(sid)
cookie = &http.Cookie{Name: manager.config.CookieName, cookie = &http.Cookie{Name: manager.config.CookieName,
Value: url.QueryEscape(sid), Value: url.QueryEscape(sid),
Path: "/", Path: "/",
...@@ -163,12 +157,18 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se ...@@ -163,12 +157,18 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se
} }
r.AddCookie(cookie) r.AddCookie(cookie)
} else { } else {
sid, _ := url.QueryUnescape(cookie.Value) sid, errs := url.QueryUnescape(cookie.Value)
if errs != nil {
return nil, errs
}
if manager.provider.SessionExist(sid) { if manager.provider.SessionExist(sid) {
session, _ = manager.provider.SessionRead(sid) session, err = manager.provider.SessionRead(sid)
} else { } else {
sid = manager.sessionId(r) sid, err = manager.sessionId(r)
session, _ = manager.provider.SessionRead(sid) if err != nil {
return nil, err
}
session, err = manager.provider.SessionRead(sid)
cookie = &http.Cookie{Name: manager.config.CookieName, cookie = &http.Cookie{Name: manager.config.CookieName,
Value: url.QueryEscape(sid), Value: url.QueryEscape(sid),
Path: "/", Path: "/",
...@@ -219,7 +219,10 @@ func (manager *Manager) GC() { ...@@ -219,7 +219,10 @@ func (manager *Manager) GC() {
// Regenerate a session id for this SessionStore who's id is saving in http request. // Regenerate a session id for this SessionStore who's id is saving in http request.
func (manager *Manager) SessionRegenerateId(w http.ResponseWriter, r *http.Request) (session SessionStore) { func (manager *Manager) SessionRegenerateId(w http.ResponseWriter, r *http.Request) (session SessionStore) {
sid := manager.sessionId(r) sid, err := manager.sessionId(r)
if err != nil {
return
}
cookie, err := r.Cookie(manager.config.CookieName) cookie, err := r.Cookie(manager.config.CookieName)
if err != nil && cookie.Value == "" { if err != nil && cookie.Value == "" {
//delete old cookie //delete old cookie
...@@ -251,36 +254,16 @@ func (manager *Manager) GetActiveSession() int { ...@@ -251,36 +254,16 @@ func (manager *Manager) GetActiveSession() int {
return manager.provider.SessionAll() return manager.provider.SessionAll()
} }
// Set hash function for generating session id.
func (manager *Manager) SetHashFunc(hasfunc, hashkey string) {
manager.config.SessionIDHashFunc = hasfunc
manager.config.SessionIDHashKey = hashkey
}
// Set cookie with https. // Set cookie with https.
func (manager *Manager) SetSecure(secure bool) { func (manager *Manager) SetSecure(secure bool) {
manager.config.Secure = secure manager.config.Secure = secure
} }
// generate session id with rand string, unix nano time, remote addr by hash function. func (manager *Manager) sessionId(r *http.Request) (string, error) {
func (manager *Manager) sessionId(r *http.Request) (sid string) { b := make([]byte, manager.config.SessionIdLength)
bs := make([]byte, 32) n, err := rand.Read(b)
if n, err := io.ReadFull(rand.Reader, bs); n != 32 || err != nil { if n != len(b) || err != nil {
bs = utils.RandomCreateBytes(32) return "", fmt.Errorf("Could not successfully read from the system CSPRNG.")
}
sig := fmt.Sprintf("%s%d%s", r.RemoteAddr, time.Now().UnixNano(), bs)
if manager.config.SessionIDHashFunc == "md5" {
h := md5.New()
h.Write([]byte(sig))
sid = hex.EncodeToString(h.Sum(nil))
} else if manager.config.SessionIDHashFunc == "sha1" {
h := hmac.New(sha1.New, []byte(manager.config.SessionIDHashKey))
fmt.Fprintf(h, "%s", sig)
sid = hex.EncodeToString(h.Sum(nil))
} else {
h := hmac.New(sha1.New, []byte(manager.config.SessionIDHashKey))
fmt.Fprintf(h, "%s", sig)
sid = hex.EncodeToString(h.Sum(nil))
} }
return return hex.EncodeToString(b), nil
} }
...@@ -31,6 +31,7 @@ func serverStaticRouter(ctx *context.Context) { ...@@ -31,6 +31,7 @@ func serverStaticRouter(ctx *context.Context) {
return return
} }
requestPath := path.Clean(ctx.Input.Request.URL.Path) requestPath := path.Clean(ctx.Input.Request.URL.Path)
i := 0
for prefix, staticDir := range StaticDir { for prefix, staticDir := range StaticDir {
if len(prefix) == 0 { if len(prefix) == 0 {
continue continue
...@@ -41,8 +42,13 @@ func serverStaticRouter(ctx *context.Context) { ...@@ -41,8 +42,13 @@ func serverStaticRouter(ctx *context.Context) {
http.ServeFile(ctx.ResponseWriter, ctx.Request, file) http.ServeFile(ctx.ResponseWriter, ctx.Request, file)
return return
} else { } else {
http.NotFound(ctx.ResponseWriter, ctx.Request) i++
return if i == len(StaticDir) {
http.NotFound(ctx.ResponseWriter, ctx.Request)
return
} else {
continue
}
} }
} }
if strings.HasPrefix(requestPath, prefix) { if strings.HasPrefix(requestPath, prefix) {
...@@ -59,9 +65,20 @@ func serverStaticRouter(ctx *context.Context) { ...@@ -59,9 +65,20 @@ func serverStaticRouter(ctx *context.Context) {
return return
} }
//if the request is dir and DirectoryIndex is false then //if the request is dir and DirectoryIndex is false then
if finfo.IsDir() && !DirectoryIndex { if finfo.IsDir() {
middleware.Exception("403", ctx.ResponseWriter, ctx.Request, "403 Forbidden") if !DirectoryIndex {
return middleware.Exception("403", ctx.ResponseWriter, ctx.Request, "403 Forbidden")
return
} else if ctx.Input.Request.URL.Path[len(ctx.Input.Request.URL.Path)-1] != '/' {
http.Redirect(ctx.ResponseWriter, ctx.Request, ctx.Input.Request.URL.Path+"/", 302)
return
}
} else if strings.HasSuffix(requestPath, "/index.html") {
file := path.Join(staticDir, requestPath)
if utils.FileExists(file) {
http.ServeFile(ctx.ResponseWriter, ctx.Request, file)
return
}
} }
//This block obtained from (https://github.com/smithfox/beego) - it should probably get merged into astaxie/beego after a pull request //This block obtained from (https://github.com/smithfox/beego) - it should probably get merged into astaxie/beego after a pull request
......
...@@ -302,6 +302,14 @@ func ParseForm(form url.Values, obj interface{}) error { ...@@ -302,6 +302,14 @@ func ParseForm(form url.Values, obj interface{}) error {
switch fieldT.Type.Kind() { switch fieldT.Type.Kind() {
case reflect.Bool: case reflect.Bool:
if strings.ToLower(value) == "on" || strings.ToLower(value) == "1" || strings.ToLower(value) == "yes" {
fieldV.SetBool(true)
continue
}
if strings.ToLower(value) == "off" || strings.ToLower(value) == "0" || strings.ToLower(value) == "no" {
fieldV.SetBool(false)
continue
}
b, err := strconv.ParseBool(value) b, err := strconv.ParseBool(value)
if err != nil { if err != nil {
return err return err
...@@ -329,6 +337,19 @@ func ParseForm(form url.Values, obj interface{}) error { ...@@ -329,6 +337,19 @@ func ParseForm(form url.Values, obj interface{}) error {
fieldV.Set(reflect.ValueOf(value)) fieldV.Set(reflect.ValueOf(value))
case reflect.String: case reflect.String:
fieldV.SetString(value) fieldV.SetString(value)
case reflect.Struct:
switch fieldT.Type.String() {
case "time.Time":
format := time.RFC3339
if len(tags) > 1 {
format = tags[1]
}
t, err := time.Parse(format, value)
if err != nil {
return err
}
fieldV.Set(reflect.ValueOf(t))
}
} }
} }
return nil return nil
...@@ -368,23 +389,31 @@ func RenderForm(obj interface{}) template.HTML { ...@@ -368,23 +389,31 @@ func RenderForm(obj interface{}) template.HTML {
fieldT := objT.Field(i) fieldT := objT.Field(i)
label, name, fType, ignored := parseFormTag(fieldT) label, name, fType, id, class, ignored := parseFormTag(fieldT)
if ignored { if ignored {
continue continue
} }
raw = append(raw, renderFormField(label, name, fType, fieldV.Interface())) raw = append(raw, renderFormField(label, name, fType, fieldV.Interface(), id, class))
} }
return template.HTML(strings.Join(raw, "</br>")) return template.HTML(strings.Join(raw, "</br>"))
} }
// renderFormField returns a string containing HTML of a single form field. // renderFormField returns a string containing HTML of a single form field.
func renderFormField(label, name, fType string, value interface{}) string { func renderFormField(label, name, fType string, value interface{}, id string, class string) string {
if id != "" {
id = " id=\"" + id + "\""
}
if class != "" {
class = " class=\"" + class + "\""
}
if isValidForInput(fType) { if isValidForInput(fType) {
return fmt.Sprintf(`%v<input name="%v" type="%v" value="%v">`, label, name, fType, value) return fmt.Sprintf(`%v<input%v%v name="%v" type="%v" value="%v">`, label, id, class, name, fType, value)
} }
return fmt.Sprintf(`%v<%v name="%v">%v</%v>`, label, fType, name, value, fType) return fmt.Sprintf(`%v<%v%v%v name="%v">%v</%v>`, label, fType, id, class, name, value, fType)
} }
// isValidForInput checks if fType is a valid value for the `type` property of an HTML input element. // isValidForInput checks if fType is a valid value for the `type` property of an HTML input element.
...@@ -400,12 +429,14 @@ func isValidForInput(fType string) bool { ...@@ -400,12 +429,14 @@ func isValidForInput(fType string) bool {
// parseFormTag takes the stuct-tag of a StructField and parses the `form` value. // parseFormTag takes the stuct-tag of a StructField and parses the `form` value.
// returned are the form label, name-property, type and wether the field should be ignored. // returned are the form label, name-property, type and wether the field should be ignored.
func parseFormTag(fieldT reflect.StructField) (label, name, fType string, ignored bool) { func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id string, class string, ignored bool) {
tags := strings.Split(fieldT.Tag.Get("form"), ",") tags := strings.Split(fieldT.Tag.Get("form"), ",")
label = fieldT.Name + ": " label = fieldT.Name + ": "
name = fieldT.Name name = fieldT.Name
fType = "text" fType = "text"
ignored = false ignored = false
id = fieldT.Tag.Get("id")
class = fieldT.Tag.Get("class")
switch len(tags) { switch len(tags) {
case 1: case 1:
......
...@@ -102,12 +102,14 @@ func TestHtmlunquote(t *testing.T) { ...@@ -102,12 +102,14 @@ func TestHtmlunquote(t *testing.T) {
func TestParseForm(t *testing.T) { func TestParseForm(t *testing.T) {
type user struct { type user struct {
Id int `form:"-"` Id int `form:"-"`
tag string `form:"tag"` tag string `form:"tag"`
Name interface{} `form:"username"` Name interface{} `form:"username"`
Age int `form:"age,text"` Age int `form:"age,text"`
Email string Email string
Intro string `form:",textarea"` Intro string `form:",textarea"`
StrBool bool `form:"strbool"`
Date time.Time `form:"date,2006-01-02"`
} }
u := user{} u := user{}
...@@ -119,6 +121,8 @@ func TestParseForm(t *testing.T) { ...@@ -119,6 +121,8 @@ func TestParseForm(t *testing.T) {
"age": []string{"40"}, "age": []string{"40"},
"Email": []string{"test@gmail.com"}, "Email": []string{"test@gmail.com"},
"Intro": []string{"I am an engineer!"}, "Intro": []string{"I am an engineer!"},
"strbool": []string{"yes"},
"date": []string{"2014-11-12"},
} }
if err := ParseForm(form, u); err == nil { if err := ParseForm(form, u); err == nil {
t.Fatal("nothing will be changed") t.Fatal("nothing will be changed")
...@@ -144,6 +148,13 @@ func TestParseForm(t *testing.T) { ...@@ -144,6 +148,13 @@ func TestParseForm(t *testing.T) {
if u.Intro != "I am an engineer!" { if u.Intro != "I am an engineer!" {
t.Errorf("Intro should equal `I am an engineer!` but got `%v`", u.Intro) t.Errorf("Intro should equal `I am an engineer!` but got `%v`", u.Intro)
} }
if u.StrBool != true {
t.Errorf("strboll should equal `true`, but got `%v`", u.StrBool)
}
y, m, d := u.Date.Date()
if y != 2014 || m.String() != "November" || d != 12 {
t.Errorf("Date should equal `2014-11-12`, but got `%v`", u.Date.String())
}
} }
func TestRenderForm(t *testing.T) { func TestRenderForm(t *testing.T) {
...@@ -175,12 +186,12 @@ func TestRenderForm(t *testing.T) { ...@@ -175,12 +186,12 @@ func TestRenderForm(t *testing.T) {
} }
func TestRenderFormField(t *testing.T) { func TestRenderFormField(t *testing.T) {
html := renderFormField("Label: ", "Name", "text", "Value") html := renderFormField("Label: ", "Name", "text", "Value", "", "")
if html != `Label: <input name="Name" type="text" value="Value">` { if html != `Label: <input name="Name" type="text" value="Value">` {
t.Errorf("Wrong html output for input[type=text]: %v ", html) t.Errorf("Wrong html output for input[type=text]: %v ", html)
} }
html = renderFormField("Label: ", "Name", "textarea", "Value") html = renderFormField("Label: ", "Name", "textarea", "Value", "", "")
if html != `Label: <textarea name="Name">Value</textarea>` { if html != `Label: <textarea name="Name">Value</textarea>` {
t.Errorf("Wrong html output for textarea: %v ", html) t.Errorf("Wrong html output for textarea: %v ", html)
} }
...@@ -192,33 +203,34 @@ func TestParseFormTag(t *testing.T) { ...@@ -192,33 +203,34 @@ func TestParseFormTag(t *testing.T) {
All int `form:"name,text,年龄:"` All int `form:"name,text,年龄:"`
NoName int `form:",hidden,年龄:"` NoName int `form:",hidden,年龄:"`
OnlyLabel int `form:",,年龄:"` OnlyLabel int `form:",,年龄:"`
OnlyName int `form:"name"` OnlyName int `form:"name" id:"name" class:"form-name"`
Ignored int `form:"-"` Ignored int `form:"-"`
} }
objT := reflect.TypeOf(&user{}).Elem() objT := reflect.TypeOf(&user{}).Elem()
label, name, fType, ignored := parseFormTag(objT.Field(0)) label, name, fType, id, class, ignored := parseFormTag(objT.Field(0))
if !(name == "name" && label == "年龄:" && fType == "text" && ignored == false) { if !(name == "name" && label == "年龄:" && fType == "text" && ignored == false) {
t.Errorf("Form Tag with name, label and type was not correctly parsed.") t.Errorf("Form Tag with name, label and type was not correctly parsed.")
} }
label, name, fType, ignored = parseFormTag(objT.Field(1)) label, name, fType, id, class, ignored = parseFormTag(objT.Field(1))
if !(name == "NoName" && label == "年龄:" && fType == "hidden" && ignored == false) { if !(name == "NoName" && label == "年龄:" && fType == "hidden" && ignored == false) {
t.Errorf("Form Tag with label and type but without name was not correctly parsed.") t.Errorf("Form Tag with label and type but without name was not correctly parsed.")
} }
label, name, fType, ignored = parseFormTag(objT.Field(2)) label, name, fType, id, class, ignored = parseFormTag(objT.Field(2))
if !(name == "OnlyLabel" && label == "年龄:" && fType == "text" && ignored == false) { if !(name == "OnlyLabel" && label == "年龄:" && fType == "text" && ignored == false) {
t.Errorf("Form Tag containing only label was not correctly parsed.") t.Errorf("Form Tag containing only label was not correctly parsed.")
} }
label, name, fType, ignored = parseFormTag(objT.Field(3)) label, name, fType, id, class, ignored = parseFormTag(objT.Field(3))
if !(name == "name" && label == "OnlyName: " && fType == "text" && ignored == false) { if !(name == "name" && label == "OnlyName: " && fType == "text" && ignored == false &&
id == "name" && class == "form-name") {
t.Errorf("Form Tag containing only name was not correctly parsed.") t.Errorf("Form Tag containing only name was not correctly parsed.")
} }
label, name, fType, ignored = parseFormTag(objT.Field(4)) label, name, fType, id, class, ignored = parseFormTag(objT.Field(4))
if ignored == false { if ignored == false {
t.Errorf("Form Tag that should be ignored was not correctly parsed.") t.Errorf("Form Tag that should be ignored was not correctly parsed.")
} }
......
...@@ -111,6 +111,27 @@ func (m *UrlMap) GetMap() map[string]interface{} { ...@@ -111,6 +111,27 @@ func (m *UrlMap) GetMap() map[string]interface{} {
return content return content
} }
func (m *UrlMap) GetMapData() []map[string]interface{} {
resultLists := make([]map[string]interface{}, 0)
for k, v := range m.urlmap {
for kk, vv := range v {
result := map[string]interface{}{
"request_url": k,
"method": kk,
"times": vv.RequestNum,
"total_time": toS(vv.TotalTime),
"max_time": toS(vv.MaxTime),
"min_time": toS(vv.MinTime),
"avg_time": toS(time.Duration(int64(vv.TotalTime) / vv.RequestNum)),
}
resultLists = append(resultLists, result)
}
}
return resultLists
}
// global statistics data map // global statistics data map
var StatisticsMap *UrlMap var StatisticsMap *UrlMap
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
package toolbox package toolbox
import ( import (
"encoding/json"
"testing" "testing"
"time" "time"
) )
...@@ -28,4 +29,12 @@ func TestStatics(t *testing.T) { ...@@ -28,4 +29,12 @@ func TestStatics(t *testing.T) {
StatisticsMap.AddStatistics("POST", "/api/user/xiemengjun", "&admin.user", time.Duration(13000)) StatisticsMap.AddStatistics("POST", "/api/user/xiemengjun", "&admin.user", time.Duration(13000))
StatisticsMap.AddStatistics("DELETE", "/api/user", "&admin.user", time.Duration(1400)) StatisticsMap.AddStatistics("DELETE", "/api/user", "&admin.user", time.Duration(1400))
t.Log(StatisticsMap.GetMap()) t.Log(StatisticsMap.GetMap())
data := StatisticsMap.GetMapData()
b, err := json.Marshal(data)
if err != nil {
t.Errorf(err.Error())
}
t.Log(string(b))
} }
...@@ -394,6 +394,9 @@ func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params map[string ...@@ -394,6 +394,9 @@ func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params map[string
} }
return true, params return true, params
} }
if len(wildcardValues) <= j {
return false, nil
}
params[v] = wildcardValues[j] params[v] = wildcardValues[j]
j += 1 j += 1
} }
......
...@@ -157,19 +157,37 @@ func (e *Email) Bytes() ([]byte, error) { ...@@ -157,19 +157,37 @@ func (e *Email) Bytes() ([]byte, error) {
} }
// Add attach file to the send mail // Add attach file to the send mail
func (e *Email) AttachFile(filename string) (a *Attachment, err error) { func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
if len(args) < 1 && len(args) > 2 {
err = errors.New("Must specify a file name and number of parameters can not exceed at least two")
return
}
filename := args[0]
id := ""
if len(args) > 1 {
id = args[1]
}
f, err := os.Open(filename) f, err := os.Open(filename)
if err != nil { if err != nil {
return return
} }
ct := mime.TypeByExtension(filepath.Ext(filename)) ct := mime.TypeByExtension(filepath.Ext(filename))
basename := path.Base(filename) basename := path.Base(filename)
return e.Attach(f, basename, ct) return e.Attach(f, basename, ct, id)
} }
// Attach is used to attach content from an io.Reader to the email. // Attach is used to attach content from an io.Reader to the email.
// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type. // Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type.
func (e *Email) Attach(r io.Reader, filename string, c string) (a *Attachment, err error) { func (e *Email) Attach(r io.Reader, filename string, args ...string) (a *Attachment, err error) {
if len(args) < 1 && len(args) > 2 {
err = errors.New("Must specify the file type and number of parameters can not exceed at least two")
return
}
c := args[0] //Content-Type
id := ""
if len(args) > 1 {
id = args[1] //Content-ID
}
var buffer bytes.Buffer var buffer bytes.Buffer
if _, err = io.Copy(&buffer, r); err != nil { if _, err = io.Copy(&buffer, r); err != nil {
return return
...@@ -186,7 +204,12 @@ func (e *Email) Attach(r io.Reader, filename string, c string) (a *Attachment, e ...@@ -186,7 +204,12 @@ func (e *Email) Attach(r io.Reader, filename string, c string) (a *Attachment, e
// If the Content-Type is blank, set the Content-Type to "application/octet-stream" // If the Content-Type is blank, set the Content-Type to "application/octet-stream"
at.Header.Set("Content-Type", "application/octet-stream") at.Header.Set("Content-Type", "application/octet-stream")
} }
at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename)) if id != "" {
at.Header.Set("Content-Disposition", fmt.Sprintf("inline;\r\n filename=\"%s\"", filename))
at.Header.Set("Content-ID", fmt.Sprintf("<%s>", id))
} else {
at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
}
at.Header.Set("Content-Transfer-Encoding", "base64") at.Header.Set("Content-Transfer-Encoding", "base64")
e.Attachments = append(e.Attachments, at) e.Attachments = append(e.Attachments, at)
return at, nil return at, nil
...@@ -269,7 +292,7 @@ func qpEscape(dest []byte, c byte) { ...@@ -269,7 +292,7 @@ func qpEscape(dest []byte, c byte) {
const nums = "0123456789ABCDEF" const nums = "0123456789ABCDEF"
dest[0] = '=' dest[0] = '='
dest[1] = nums[(c&0xf0)>>4] dest[1] = nums[(c&0xf0)>>4]
dest[2] = nums[(c & 0xf)] dest[2] = nums[(c&0xf)]
} }
// headerToBytes enumerates the key and values in the header, and writes the results to the IO Writer // headerToBytes enumerates the key and values in the header, and writes the results to the IO Writer
......
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pagination
import (
"github.com/astaxie/beego/context"
)
// Instantiates a Paginator and assigns it to context.Input.Data["paginator"].
func SetPaginator(context *context.Context, per int, nums int64) (paginator *Paginator) {
paginator = NewPaginator(context.Request, per, nums)
context.Input.Data["paginator"] = paginator
return
}
/*
The pagination package provides utilities to setup a paginator within the
context of a http request.
Usage
In your beego.Controller:
package controllers
import "github.com/astaxie/beego/utils/pagination"
type PostsController struct {
beego.Controller
}
func (this *PostsController) ListAllPosts() {
// sets this.Data["paginator"] with the current offset (from the url query param)
postsPerPage := 20
paginator := pagination.SetPaginator(this.Ctx, postsPerPage, CountPosts())
// fetch the next 20 posts
this.Data["posts"] = ListPostsByOffsetAndLimit(paginator.Offset(), postsPerPage)
}
In your view templates:
{{if .paginator.HasPages}}
<ul class="pagination pagination">
{{if .paginator.HasPrev}}
<li><a href="{{.paginator.PageLinkFirst}}">{{ i18n .Lang "paginator.first_page"}}</a></li>
<li><a href="{{.paginator.PageLinkPrev}}">&laquo;</a></li>
{{else}}
<li class="disabled"><a>{{ i18n .Lang "paginator.first_page"}}</a></li>
<li class="disabled"><a>&laquo;</a></li>
{{end}}
{{range $index, $page := .paginator.Pages}}
<li{{if $.paginator.IsActive .}} class="active"{{end}}>
<a href="{{$.paginator.PageLink $page}}">{{$page}}</a>
</li>
{{end}}
{{if .paginator.HasNext}}
<li><a href="{{.paginator.PageLinkNext}}">&raquo;</a></li>
<li><a href="{{.paginator.PageLinkLast}}">{{ i18n .Lang "paginator.last_page"}}</a></li>
{{else}}
<li class="disabled"><a>&raquo;</a></li>
<li class="disabled"><a>{{ i18n .Lang "paginator.last_page"}}</a></li>
{{end}}
</ul>
{{end}}
See also
http://beego.me/docs/mvc/view/page.md
*/
package pagination
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pagination
import (
"math"
"net/http"
"net/url"
"strconv"
)
// Paginator within the state of a http request.
type Paginator struct {
Request *http.Request
PerPageNums int
MaxPages int
nums int64
pageRange []int
pageNums int
page int
}
// Returns the total number of pages.
func (p *Paginator) PageNums() int {
if p.pageNums != 0 {
return p.pageNums
}
pageNums := math.Ceil(float64(p.nums) / float64(p.PerPageNums))
if p.MaxPages > 0 {
pageNums = math.Min(pageNums, float64(p.MaxPages))
}
p.pageNums = int(pageNums)
return p.pageNums
}
// Returns the total number of items (e.g. from doing SQL count).
func (p *Paginator) Nums() int64 {
return p.nums
}
// Sets the total number of items.
func (p *Paginator) SetNums(nums interface{}) {
p.nums, _ = ToInt64(nums)
}
// Returns the current page.
func (p *Paginator) Page() int {
if p.page != 0 {
return p.page
}
if p.Request.Form == nil {
p.Request.ParseForm()
}
p.page, _ = strconv.Atoi(p.Request.Form.Get("p"))
if p.page > p.PageNums() {
p.page = p.PageNums()
}
if p.page <= 0 {
p.page = 1
}
return p.page
}
// Returns a list of all pages.
//
// Usage (in a view template):
//
// {{range $index, $page := .paginator.Pages}}
// <li{{if $.paginator.IsActive .}} class="active"{{end}}>
// <a href="{{$.paginator.PageLink $page}}">{{$page}}</a>
// </li>
// {{end}}
func (p *Paginator) Pages() []int {
if p.pageRange == nil && p.nums > 0 {
var pages []int
pageNums := p.PageNums()
page := p.Page()
switch {
case page >= pageNums-4 && pageNums > 9:
start := pageNums - 9 + 1
pages = make([]int, 9)
for i, _ := range pages {
pages[i] = start + i
}
case page >= 5 && pageNums > 9:
start := page - 5 + 1
pages = make([]int, int(math.Min(9, float64(page+4+1))))
for i, _ := range pages {
pages[i] = start + i
}
default:
pages = make([]int, int(math.Min(9, float64(pageNums))))
for i, _ := range pages {
pages[i] = i + 1
}
}
p.pageRange = pages
}
return p.pageRange
}
// Returns URL for a given page index.
func (p *Paginator) PageLink(page int) string {
link, _ := url.ParseRequestURI(p.Request.RequestURI)
values := link.Query()
if page == 1 {
values.Del("p")
} else {
values.Set("p", strconv.Itoa(page))
}
link.RawQuery = values.Encode()
return link.String()
}
// Returns URL to the previous page.
func (p *Paginator) PageLinkPrev() (link string) {
if p.HasPrev() {
link = p.PageLink(p.Page() - 1)
}
return
}
// Returns URL to the next page.
func (p *Paginator) PageLinkNext() (link string) {
if p.HasNext() {
link = p.PageLink(p.Page() + 1)
}
return
}
// Returns URL to the first page.
func (p *Paginator) PageLinkFirst() (link string) {
return p.PageLink(1)
}
// Returns URL to the last page.
func (p *Paginator) PageLinkLast() (link string) {
return p.PageLink(p.PageNums())
}
// Returns true if the current page has a predecessor.
func (p *Paginator) HasPrev() bool {
return p.Page() > 1
}
// Returns true if the current page has a successor.
func (p *Paginator) HasNext() bool {
return p.Page() < p.PageNums()
}
// Returns true if the given page index points to the current page.
func (p *Paginator) IsActive(page int) bool {
return p.Page() == page
}
// Returns the current offset.
func (p *Paginator) Offset() int {
return (p.Page() - 1) * p.PerPageNums
}
// Returns true if there is more than one page.
func (p *Paginator) HasPages() bool {
return p.PageNums() > 1
}
// Instantiates a paginator struct for the current http request.
func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator {
p := Paginator{}
p.Request = req
if per <= 0 {
per = 10
}
p.PerPageNums = per
p.SetNums(nums)
return &p
}
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pagination
import (
"fmt"
"reflect"
)
// convert any numeric value to int64
func ToInt64(value interface{}) (d int64, err error) {
val := reflect.ValueOf(value)
switch value.(type) {
case int, int8, int16, int32, int64:
d = val.Int()
case uint, uint8, uint16, uint32, uint64:
d = int64(val.Uint())
default:
err = fmt.Errorf("ToInt64 need numeric not `%T`", value)
}
return
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment