1
0
mirror of https://github.com/astaxie/beego.git synced 2024-12-22 16:30:48 +00:00

init framwork

This commit is contained in:
xiemengjun 2012-12-15 23:53:19 +08:00
parent afebcd891e
commit f593c1f3fd
10 changed files with 890 additions and 0 deletions

View File

112
beego.go Normal file
View 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
View 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
View 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
View 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
View 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)
}
}

1
model.go Normal file
View File

@ -0,0 +1 @@
package beego

283
router.go Normal file
View 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
View 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")
}

14
utils.go Normal file
View File

@ -0,0 +1,14 @@
package beego
import (
"strings"
"time"
)
func webTime(t time.Time) string {
ftime := t.Format(time.RFC1123)
if strings.HasSuffix(ftime, "UTC") {
ftime = ftime[0:len(ftime)-3] + "GMT"
}
return ftime
}