mirror of
https://github.com/astaxie/beego.git
synced 2024-11-25 19:10:54 +00:00
init framwork
This commit is contained in:
parent
afebcd891e
commit
f593c1f3fd
112
beego.go
Normal file
112
beego.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
BeeApp *App
|
||||||
|
AppName string
|
||||||
|
AppPath string
|
||||||
|
StaticDir map[string]string
|
||||||
|
HttpAddr string
|
||||||
|
HttpPort int
|
||||||
|
RecoverPanic bool
|
||||||
|
AutoRender bool
|
||||||
|
ViewsPath string
|
||||||
|
RunMode string //"dev" or "prod"
|
||||||
|
AppConfig *Config
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
BeeApp = NewApp()
|
||||||
|
AppPath, _ = os.Getwd()
|
||||||
|
StaticDir = make(map[string]string)
|
||||||
|
var err error
|
||||||
|
AppConfig, err = LoadConfig(path.Join(AppPath, "conf", "app.conf"))
|
||||||
|
if err != nil {
|
||||||
|
Trace("open Config err:", err)
|
||||||
|
HttpAddr = ""
|
||||||
|
HttpPort = 8080
|
||||||
|
AppName = "beego"
|
||||||
|
RunMode = "prod"
|
||||||
|
AutoRender = true
|
||||||
|
RecoverPanic = true
|
||||||
|
ViewsPath = "views"
|
||||||
|
} else {
|
||||||
|
HttpAddr = AppConfig.String("httpaddr")
|
||||||
|
if v, err := AppConfig.Int("httpport"); err != nil {
|
||||||
|
HttpPort = 8080
|
||||||
|
} else {
|
||||||
|
HttpPort = v
|
||||||
|
}
|
||||||
|
AppName = AppConfig.String("appname")
|
||||||
|
if runmode := AppConfig.String("runmode"); runmode != "" {
|
||||||
|
RunMode = runmode
|
||||||
|
} else {
|
||||||
|
RunMode = "prod"
|
||||||
|
}
|
||||||
|
if ar, err := AppConfig.Bool("autorender"); err != nil {
|
||||||
|
AutoRender = true
|
||||||
|
} else {
|
||||||
|
AutoRender = ar
|
||||||
|
}
|
||||||
|
if ar, err := AppConfig.Bool("autorecover"); err != nil {
|
||||||
|
RecoverPanic = true
|
||||||
|
} else {
|
||||||
|
RecoverPanic = ar
|
||||||
|
}
|
||||||
|
if views := AppConfig.String("viewspath"); views == "" {
|
||||||
|
ViewsPath = "views"
|
||||||
|
} else {
|
||||||
|
ViewsPath = views
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StaticDir["/static"] = "static"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
Handlers *ControllerRegistor
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new PatternServeMux.
|
||||||
|
func NewApp() *App {
|
||||||
|
cr := NewControllerRegistor()
|
||||||
|
app := &App{Handlers: cr}
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Run() {
|
||||||
|
addr := fmt.Sprintf("%s:%d", HttpAddr, HttpPort)
|
||||||
|
err := http.ListenAndServe(addr, app.Handlers)
|
||||||
|
if err != nil {
|
||||||
|
BeeLogger.Fatal("ListenAndServe: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) RegisterController(path string, c ControllerInterface) *App {
|
||||||
|
app.Handlers.Add(path, c)
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) SetViewsPath(path string) *App {
|
||||||
|
ViewsPath = path
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) SetStaticPath(url string, path string) *App {
|
||||||
|
StaticDir[url] = path
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) ErrorLog(ctx *Context) {
|
||||||
|
BeeLogger.Printf("[ERR] host: '%s', request: '%s %s', proto: '%s', ua: '%s', remote: '%s'\n", ctx.Request.Host, ctx.Request.Method, ctx.Request.URL.Path, ctx.Request.Proto, ctx.Request.UserAgent(), ctx.Request.RemoteAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) AccessLog(ctx *Context) {
|
||||||
|
BeeLogger.Printf("[ACC] host: '%s', request: '%s %s', proto: '%s', ua: %s'', remote: '%s'\n", ctx.Request.Host, ctx.Request.Method, ctx.Request.URL.Path, ctx.Request.Proto, ctx.Request.UserAgent(), ctx.Request.RemoteAddr)
|
||||||
|
}
|
121
config.go
Normal file
121
config.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bComment = []byte{'#'}
|
||||||
|
bEmpty = []byte{}
|
||||||
|
bEqual = []byte{'='}
|
||||||
|
bDQuote = []byte{'"'}
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Config represents the configuration.
|
||||||
|
type Config struct {
|
||||||
|
filename string
|
||||||
|
comment map[int][]string // id: []{comment, key...}; id 1 is for main comment.
|
||||||
|
data map[string]string // key: value
|
||||||
|
offset map[string]int64 // key: offset; for editing.
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFile creates a new Config and parses the file configuration from the
|
||||||
|
// named file.
|
||||||
|
func LoadConfig(name string) (*Config, error) {
|
||||||
|
file, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
file.Name(),
|
||||||
|
make(map[int][]string),
|
||||||
|
make(map[string]string),
|
||||||
|
make(map[string]int64),
|
||||||
|
sync.RWMutex{},
|
||||||
|
}
|
||||||
|
cfg.Lock()
|
||||||
|
defer cfg.Unlock()
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var comment bytes.Buffer
|
||||||
|
buf := bufio.NewReader(file)
|
||||||
|
|
||||||
|
for nComment, off := 0, int64(1); ; {
|
||||||
|
line, _, err := buf.ReadLine()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if bytes.Equal(line, bEmpty) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
off += int64(len(line))
|
||||||
|
|
||||||
|
if bytes.HasPrefix(line, bComment) {
|
||||||
|
line = bytes.TrimLeft(line, "#")
|
||||||
|
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
||||||
|
comment.Write(line)
|
||||||
|
comment.WriteByte('\n')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if comment.Len() != 0 {
|
||||||
|
cfg.comment[nComment] = []string{comment.String()}
|
||||||
|
comment.Reset()
|
||||||
|
nComment++
|
||||||
|
}
|
||||||
|
|
||||||
|
val := bytes.SplitN(line, bEqual, 2)
|
||||||
|
if bytes.HasPrefix(val[1], bDQuote) {
|
||||||
|
val[1] = bytes.Trim(val[1], `"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.TrimSpace(string(val[0]))
|
||||||
|
cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key)
|
||||||
|
cfg.data[key] = strings.TrimSpace(string(val[1]))
|
||||||
|
cfg.offset[key] = off
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the boolean value for a given key.
|
||||||
|
func (c *Config) Bool(key string) (bool, error) {
|
||||||
|
return strconv.ParseBool(c.data[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns the integer value for a given key.
|
||||||
|
func (c *Config) Int(key string) (int, error) {
|
||||||
|
return strconv.Atoi(c.data[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float returns the float value for a given key.
|
||||||
|
func (c *Config) Float(key string) (float64, error) {
|
||||||
|
return strconv.ParseFloat(c.data[key], 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string value for a given key.
|
||||||
|
func (c *Config) String(key string) string {
|
||||||
|
return c.data[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteValue writes a new value for key.
|
||||||
|
func (c *Config) SetValue(key, value string) error {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
|
||||||
|
if _, found := c.data[key]; !found {
|
||||||
|
return errors.New("key not found: " + key)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.data[key] = value
|
||||||
|
return nil
|
||||||
|
}
|
72
context.go
Normal file
72
context.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
ResponseWriter http.ResponseWriter
|
||||||
|
Request *http.Request
|
||||||
|
Params map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) WriteString(content string) {
|
||||||
|
ctx.ResponseWriter.Write([]byte(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) Abort(status int, body string) {
|
||||||
|
ctx.ResponseWriter.WriteHeader(status)
|
||||||
|
ctx.ResponseWriter.Write([]byte(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) Redirect(status int, url_ string) {
|
||||||
|
ctx.ResponseWriter.Header().Set("Location", url_)
|
||||||
|
ctx.ResponseWriter.WriteHeader(status)
|
||||||
|
ctx.ResponseWriter.Write([]byte("Redirecting to: " + url_))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) NotModified() {
|
||||||
|
ctx.ResponseWriter.WriteHeader(304)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) NotFound(message string) {
|
||||||
|
ctx.ResponseWriter.WriteHeader(404)
|
||||||
|
ctx.ResponseWriter.Write([]byte(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sets the content type by extension, as defined in the mime package.
|
||||||
|
//For example, ctx.ContentType("json") sets the content-type to "application/json"
|
||||||
|
func (ctx *Context) ContentType(ext string) {
|
||||||
|
if !strings.HasPrefix(ext, ".") {
|
||||||
|
ext = "." + ext
|
||||||
|
}
|
||||||
|
ctype := mime.TypeByExtension(ext)
|
||||||
|
if ctype != "" {
|
||||||
|
ctx.ResponseWriter.Header().Set("Content-Type", ctype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) SetHeader(hdr string, val string, unique bool) {
|
||||||
|
if unique {
|
||||||
|
ctx.ResponseWriter.Header().Set(hdr, val)
|
||||||
|
} else {
|
||||||
|
ctx.ResponseWriter.Header().Add(hdr, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sets a cookie -- duration is the amount of time in seconds. 0 = forever
|
||||||
|
func (ctx *Context) SetCookie(name string, value string, age int64) {
|
||||||
|
var utctime time.Time
|
||||||
|
if age == 0 {
|
||||||
|
// 2^31 - 1 seconds (roughly 2038)
|
||||||
|
utctime = time.Unix(2147483647, 0)
|
||||||
|
} else {
|
||||||
|
utctime = time.Unix(time.Now().Unix()+age, 0)
|
||||||
|
}
|
||||||
|
cookie := fmt.Sprintf("%s=%s; expires=%s", name, value, webTime(utctime))
|
||||||
|
ctx.SetHeader("Set-Cookie", cookie, false)
|
||||||
|
}
|
147
controller.go
Normal file
147
controller.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
Ct *Context
|
||||||
|
Tpl *template.Template
|
||||||
|
Data map[interface{}]interface{}
|
||||||
|
ChildName string
|
||||||
|
TplNames string
|
||||||
|
Layout string
|
||||||
|
TplExt string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ControllerInterface interface {
|
||||||
|
Init(ct *Context, cn string)
|
||||||
|
Prepare()
|
||||||
|
Get()
|
||||||
|
Post()
|
||||||
|
Delete()
|
||||||
|
Put()
|
||||||
|
Head()
|
||||||
|
Patch()
|
||||||
|
Options()
|
||||||
|
Finish()
|
||||||
|
Render() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Init(ct *Context, cn string) {
|
||||||
|
c.Data = make(map[interface{}]interface{})
|
||||||
|
c.Tpl = template.New(cn + ct.Request.Method)
|
||||||
|
c.Tpl = c.Tpl.Funcs(beegoTplFuncMap)
|
||||||
|
c.Layout = ""
|
||||||
|
c.TplNames = ""
|
||||||
|
c.ChildName = cn
|
||||||
|
c.Ct = ct
|
||||||
|
c.TplExt = "tpl"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Prepare() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Finish() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Get() {
|
||||||
|
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Post() {
|
||||||
|
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Delete() {
|
||||||
|
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Put() {
|
||||||
|
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Head() {
|
||||||
|
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Patch() {
|
||||||
|
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Options() {
|
||||||
|
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Render() error {
|
||||||
|
//if the controller has set layout, then first get the tplname's content set the content to the layout
|
||||||
|
if c.Layout != "" {
|
||||||
|
if c.TplNames == "" {
|
||||||
|
c.TplNames = c.ChildName + "/" + c.Ct.Request.Method + "." + c.TplExt
|
||||||
|
}
|
||||||
|
t, err := c.Tpl.ParseFiles(path.Join(ViewsPath, c.TplNames), path.Join(ViewsPath, c.Layout))
|
||||||
|
if err != nil {
|
||||||
|
Trace("template ParseFiles err:", err)
|
||||||
|
}
|
||||||
|
_, file := path.Split(c.TplNames)
|
||||||
|
newbytes := bytes.NewBufferString("")
|
||||||
|
t.ExecuteTemplate(newbytes, file, c.Data)
|
||||||
|
tplcontent, _ := ioutil.ReadAll(newbytes)
|
||||||
|
c.Data["LayoutContent"] = template.HTML(string(tplcontent))
|
||||||
|
_, file = path.Split(c.Layout)
|
||||||
|
err = t.ExecuteTemplate(c.Ct.ResponseWriter, file, c.Data)
|
||||||
|
if err != nil {
|
||||||
|
Trace("template Execute err:", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if c.TplNames == "" {
|
||||||
|
c.TplNames = c.ChildName + "/" + c.Ct.Request.Method + "." + c.TplExt
|
||||||
|
}
|
||||||
|
t, err := c.Tpl.ParseFiles(path.Join(ViewsPath, c.TplNames))
|
||||||
|
if err != nil {
|
||||||
|
Trace("template ParseFiles err:", err)
|
||||||
|
}
|
||||||
|
_, file := path.Split(c.TplNames)
|
||||||
|
err = t.ExecuteTemplate(c.Ct.ResponseWriter, file, c.Data)
|
||||||
|
if err != nil {
|
||||||
|
Trace("template Execute err:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Redirect(url string, code int) {
|
||||||
|
c.Ct.Redirect(code, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeJson() {
|
||||||
|
content, err := json.MarshalIndent(c.Data, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c.Ct.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Ct.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
|
||||||
|
c.Ct.ContentType("json")
|
||||||
|
c.Ct.ResponseWriter.Write(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeXml() {
|
||||||
|
content, err := xml.Marshal(c.Data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c.Ct.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Ct.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
|
||||||
|
c.Ct.ContentType("xml")
|
||||||
|
c.Ct.ResponseWriter.Write(content)
|
||||||
|
}
|
85
log.go
Normal file
85
log.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
//--------------------
|
||||||
|
// LOG LEVEL
|
||||||
|
//--------------------
|
||||||
|
|
||||||
|
// Log levels to control the logging output.
|
||||||
|
const (
|
||||||
|
LevelTrace = iota
|
||||||
|
LevelDebug
|
||||||
|
LevelInfo
|
||||||
|
LevelWarning
|
||||||
|
LevelError
|
||||||
|
LevelCritical
|
||||||
|
)
|
||||||
|
|
||||||
|
// logLevel controls the global log level used by the logger.
|
||||||
|
var level = LevelTrace
|
||||||
|
|
||||||
|
// LogLevel returns the global log level and can be used in
|
||||||
|
// own implementations of the logger interface.
|
||||||
|
func Level() int {
|
||||||
|
return level
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogLevel sets the global log level used by the simple
|
||||||
|
// logger.
|
||||||
|
func SetLevel(l int) {
|
||||||
|
level = l
|
||||||
|
}
|
||||||
|
|
||||||
|
// logger references the used application logger.
|
||||||
|
var BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
|
||||||
|
|
||||||
|
// SetLogger sets a new logger.
|
||||||
|
func SetLogger(l *log.Logger) {
|
||||||
|
BeeLogger = l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace logs a message at trace level.
|
||||||
|
func Trace(v ...interface{}) {
|
||||||
|
if level <= LevelTrace {
|
||||||
|
BeeLogger.Printf("[T] %v\n", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at debug level.
|
||||||
|
func Debug(v ...interface{}) {
|
||||||
|
if level <= LevelDebug {
|
||||||
|
BeeLogger.Printf("[D] %v\n", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs a message at info level.
|
||||||
|
func Info(v ...interface{}) {
|
||||||
|
if level <= LevelInfo {
|
||||||
|
BeeLogger.Printf("[I] %v\n", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs a message at warning level.
|
||||||
|
func Warn(v ...interface{}) {
|
||||||
|
if level <= LevelWarning {
|
||||||
|
BeeLogger.Printf("[W] %v\n", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at error level.
|
||||||
|
func Error(v ...interface{}) {
|
||||||
|
if level <= LevelError {
|
||||||
|
BeeLogger.Printf("[E] %v\n", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical logs a message at critical level.
|
||||||
|
func Critical(v ...interface{}) {
|
||||||
|
if level <= LevelCritical {
|
||||||
|
BeeLogger.Printf("[C] %v\n", v)
|
||||||
|
}
|
||||||
|
}
|
283
router.go
Normal file
283
router.go
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type controllerInfo struct {
|
||||||
|
pattern string
|
||||||
|
regex *regexp.Regexp
|
||||||
|
params map[int]string
|
||||||
|
controllerType reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
type ControllerRegistor struct {
|
||||||
|
routers []*controllerInfo
|
||||||
|
fixrouters []*controllerInfo
|
||||||
|
filters []http.HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewControllerRegistor() *ControllerRegistor {
|
||||||
|
return &ControllerRegistor{routers: make([]*controllerInfo, 0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
|
||||||
|
parts := strings.Split(pattern, "/")
|
||||||
|
|
||||||
|
j := 0
|
||||||
|
params := make(map[int]string)
|
||||||
|
for i, part := range parts {
|
||||||
|
if strings.HasPrefix(part, ":") {
|
||||||
|
expr := "([^/]+)"
|
||||||
|
//a user may choose to override the defult expression
|
||||||
|
// similar to expressjs: ‘/user/:id([0-9]+)’
|
||||||
|
if index := strings.Index(part, "("); index != -1 {
|
||||||
|
expr = part[index:]
|
||||||
|
part = part[:index]
|
||||||
|
}
|
||||||
|
params[j] = part
|
||||||
|
parts[i] = expr
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if j == 0 {
|
||||||
|
//now create the Route
|
||||||
|
t := reflect.Indirect(reflect.ValueOf(c)).Type()
|
||||||
|
route := &controllerInfo{}
|
||||||
|
route.pattern = pattern
|
||||||
|
route.controllerType = t
|
||||||
|
|
||||||
|
p.fixrouters = append(p.fixrouters, route)
|
||||||
|
} else { // add regexp routers
|
||||||
|
//recreate the url pattern, with parameters replaced
|
||||||
|
//by regular expressions. then compile the regex
|
||||||
|
pattern = strings.Join(parts, "/")
|
||||||
|
regex, regexErr := regexp.Compile(pattern)
|
||||||
|
if regexErr != nil {
|
||||||
|
//TODO add error handling here to avoid panic
|
||||||
|
panic(regexErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//now create the Route
|
||||||
|
t := reflect.Indirect(reflect.ValueOf(c)).Type()
|
||||||
|
route := &controllerInfo{}
|
||||||
|
route.regex = regex
|
||||||
|
route.params = params
|
||||||
|
route.controllerType = t
|
||||||
|
|
||||||
|
p.routers = append(p.routers, route)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter adds the middleware filter.
|
||||||
|
func (p *ControllerRegistor) Filter(filter http.HandlerFunc) {
|
||||||
|
p.filters = append(p.filters, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterParam adds the middleware filter if the REST URL parameter exists.
|
||||||
|
func (p *ControllerRegistor) FilterParam(param string, filter http.HandlerFunc) {
|
||||||
|
if !strings.HasPrefix(param, ":") {
|
||||||
|
param = ":" + param
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Filter(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
p := r.URL.Query().Get(param)
|
||||||
|
if len(p) > 0 {
|
||||||
|
filter(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterPrefixPath adds the middleware filter if the prefix path exists.
|
||||||
|
func (p *ControllerRegistor) FilterPrefixPath(path string, filter http.HandlerFunc) {
|
||||||
|
p.Filter(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if strings.HasPrefix(r.URL.Path, path) {
|
||||||
|
filter(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoRoute
|
||||||
|
func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
if !RecoverPanic {
|
||||||
|
// go back to panic
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
Critical("Handler crashed with error", err)
|
||||||
|
for i := 1; ; i += 1 {
|
||||||
|
_, file, line, ok := runtime.Caller(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
Critical(file, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
w := &responseWriter{writer: rw}
|
||||||
|
|
||||||
|
var runrouter *controllerInfo
|
||||||
|
var findrouter bool
|
||||||
|
|
||||||
|
params := make(map[string]string)
|
||||||
|
|
||||||
|
//static file server
|
||||||
|
for prefix, staticDir := range StaticDir {
|
||||||
|
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||||
|
file := staticDir + r.URL.Path[len(prefix):]
|
||||||
|
http.ServeFile(w, r, file)
|
||||||
|
w.started = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestPath := r.URL.Path
|
||||||
|
|
||||||
|
//first find path from the fixrouters to Improve Performance
|
||||||
|
for _, route := range p.fixrouters {
|
||||||
|
n := len(requestPath)
|
||||||
|
if (requestPath[n-1] != '/' && route.pattern == requestPath) ||
|
||||||
|
(len(route.pattern) >= n && requestPath[0:n] == route.pattern) {
|
||||||
|
runrouter = route
|
||||||
|
findrouter = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !findrouter {
|
||||||
|
//find a matching Route
|
||||||
|
for _, route := range p.routers {
|
||||||
|
|
||||||
|
//check if Route pattern matches url
|
||||||
|
if !route.regex.MatchString(requestPath) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//get submatches (params)
|
||||||
|
matches := route.regex.FindStringSubmatch(requestPath)
|
||||||
|
|
||||||
|
//double check that the Route matches the URL pattern.
|
||||||
|
if len(matches[0]) != len(requestPath) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(route.params) > 0 {
|
||||||
|
//add url parameters to the query param map
|
||||||
|
values := r.URL.Query()
|
||||||
|
for i, match := range matches[1:] {
|
||||||
|
values.Add(route.params[i], match)
|
||||||
|
params[route.params[i]] = match
|
||||||
|
}
|
||||||
|
//reassemble query params and add to RawQuery
|
||||||
|
r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery
|
||||||
|
//r.URL.RawQuery = url.Values(values).Encode()
|
||||||
|
}
|
||||||
|
runrouter = route
|
||||||
|
findrouter = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if runrouter != nil {
|
||||||
|
//execute middleware filters
|
||||||
|
for _, filter := range p.filters {
|
||||||
|
filter(w, r)
|
||||||
|
if w.started {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Invoke the request handler
|
||||||
|
vc := reflect.New(runrouter.controllerType)
|
||||||
|
|
||||||
|
//call the controller init function
|
||||||
|
init := vc.MethodByName("Init")
|
||||||
|
in := make([]reflect.Value, 2)
|
||||||
|
ct := &Context{ResponseWriter: w, Request: r, Params: params}
|
||||||
|
in[0] = reflect.ValueOf(ct)
|
||||||
|
in[1] = reflect.ValueOf(runrouter.controllerType.Name())
|
||||||
|
init.Call(in)
|
||||||
|
//call prepare function
|
||||||
|
in = make([]reflect.Value, 0)
|
||||||
|
method := vc.MethodByName("Prepare")
|
||||||
|
method.Call(in)
|
||||||
|
|
||||||
|
//if response has written,yes don't run next
|
||||||
|
if !w.started {
|
||||||
|
if r.Method == "GET" {
|
||||||
|
method = vc.MethodByName("Get")
|
||||||
|
method.Call(in)
|
||||||
|
} else if r.Method == "POST" {
|
||||||
|
method = vc.MethodByName("Post")
|
||||||
|
method.Call(in)
|
||||||
|
} else if r.Method == "HEAD" {
|
||||||
|
method = vc.MethodByName("Head")
|
||||||
|
method.Call(in)
|
||||||
|
} else if r.Method == "DELETE" {
|
||||||
|
method = vc.MethodByName("Delete")
|
||||||
|
method.Call(in)
|
||||||
|
} else if r.Method == "PUT" {
|
||||||
|
method = vc.MethodByName("Put")
|
||||||
|
method.Call(in)
|
||||||
|
} else if r.Method == "PATCH" {
|
||||||
|
method = vc.MethodByName("Patch")
|
||||||
|
method.Call(in)
|
||||||
|
} else if r.Method == "OPTIONS" {
|
||||||
|
method = vc.MethodByName("Options")
|
||||||
|
method.Call(in)
|
||||||
|
}
|
||||||
|
if !w.started {
|
||||||
|
if AutoRender {
|
||||||
|
method = vc.MethodByName("Render")
|
||||||
|
method.Call(in)
|
||||||
|
}
|
||||||
|
if !w.started {
|
||||||
|
method = vc.MethodByName("Finish")
|
||||||
|
method.Call(in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//if no matches to url, throw a not found exception
|
||||||
|
if w.started == false {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//responseWriter is a wrapper for the http.ResponseWriter
|
||||||
|
//started set to true if response was written to then don't execute other handler
|
||||||
|
type responseWriter struct {
|
||||||
|
writer http.ResponseWriter
|
||||||
|
started bool
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header returns the header map that will be sent by WriteHeader.
|
||||||
|
func (w *responseWriter) Header() http.Header {
|
||||||
|
return w.writer.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the data to the connection as part of an HTTP reply,
|
||||||
|
// and sets `started` to true
|
||||||
|
func (w *responseWriter) Write(p []byte) (int, error) {
|
||||||
|
w.started = true
|
||||||
|
return w.writer.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader sends an HTTP response header with status code,
|
||||||
|
// and sets `started` to true
|
||||||
|
func (w *responseWriter) WriteHeader(code int) {
|
||||||
|
w.status = code
|
||||||
|
w.started = true
|
||||||
|
w.writer.WriteHeader(code)
|
||||||
|
}
|
55
template.go
Normal file
55
template.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package beego
|
||||||
|
|
||||||
|
//@todo add template funcs
|
||||||
|
|
||||||
|
import (
|
||||||
|
//"fmt"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/russross/blackfriday"
|
||||||
|
"html/template"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var beegoTplFuncMap template.FuncMap
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
beegoTplFuncMap = make(template.FuncMap)
|
||||||
|
beegoTplFuncMap["markdown"] = MarkDown
|
||||||
|
beegoTplFuncMap["dateformat"] = DateFormat
|
||||||
|
beegoTplFuncMap["compare"] = Compare
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkDown parses a string in MarkDown format and returns HTML. Used by the template parser as "markdown"
|
||||||
|
func MarkDown(raw string) (output template.HTML) {
|
||||||
|
input := []byte(raw)
|
||||||
|
bOutput := blackfriday.MarkdownBasic(input)
|
||||||
|
output = template.HTML(string(bOutput))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateFormat takes a time and a layout string and returns a string with the formatted date. Used by the template parser as "dateformat"
|
||||||
|
func DateFormat(t time.Time, layout string) (datestring string) {
|
||||||
|
datestring = t.Format(layout)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare is a quick and dirty comparison function. It will convert whatever you give it to strings and see if the two values are equal.
|
||||||
|
// Whitespace is trimmed. Used by the template parser as "eq"
|
||||||
|
func Compare(a, b interface{}) (equal bool) {
|
||||||
|
equal = false
|
||||||
|
if strings.TrimSpace(fmt.Sprintf("%v", a)) == strings.TrimSpace(fmt.Sprintf("%v", b)) {
|
||||||
|
equal = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFuncMap let user to register a func in the template
|
||||||
|
func AddFuncMap(key string, funname interface{}) error {
|
||||||
|
if _, ok := beegoTplFuncMap["key"]; ok {
|
||||||
|
beegoTplFuncMap[key] = funname
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("funcmap already has the key")
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user