加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
faygo.go 16.18 KB
一键复制 编辑 原始数据 按行查看 历史
andeyalee 提交于 2019-06-29 13:13 . chore: Improve the flag parsing
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
// Copyright 2016 HenryLee. 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 faygo
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"reflect"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/henrylee2cn/faygo/acceptencoder"
"github.com/henrylee2cn/faygo/apiware"
"github.com/henrylee2cn/faygo/logging"
)
const (
// VERSION is faygo web framework's version
VERSION = "1.2.0"
banner = `
___
/ _/
_| |_ __ __ ___
/_ // _ |/\ /\/ _ \/ _ \
| | \/ /\ \/ / |/ / |/ /
/ / \_/\\ \ / \_ / \__/
/ / / / _\ \
\_/ \_/ \_\_/ VERSION ` + VERSION + " URL https://github.com/henrylee2cn/faygo\n"
)
// New uses the faygo web framework to create a new application.
func New(name string, version ...string) *Framework {
return newFramework(nil, name, version)
}
// NewWithConfig uses the faygo web framework to create a new application.
func NewWithConfig(config *Config, name string, version ...string) *Framework {
return newFramework(config, name, version)
}
// AllFrames returns the list of applications that have been created.
func AllFrames() []*Framework {
global.framesLock.RLock()
defer global.framesLock.RUnlock()
return global.frames
}
// GetFrame returns the specified frame instance by name and version.
func GetFrame(name string, version ...string) (*Framework, bool) {
if len(version) > 0 && len(version[0]) > 0 {
name = name + "_" + version[0]
}
global.framesLock.RLock()
defer global.framesLock.RUnlock()
for _, frame := range global.frames {
if frame.NameWithVersion() == name {
return frame, true
}
}
return nil, false
}
// Run starts all web services.
func Run() {
global.beforeRun()
global.framesLock.Lock()
for _, frame := range global.frames {
if !frame.Running() {
go frame.run()
time.Sleep(time.Second)
}
}
global.framesLock.Unlock()
select {}
}
// Running returns whether the frame service is running.
func Running(name string, version ...string) bool {
frame, ok := GetFrame(name, version...)
if !ok {
return false
}
return frame.Running()
}
// MinShutdownTimeout the default time-out period for the services shutdown.
const MinShutdownTimeout = 5 * time.Second
// SetShutdown sets the function which is called after the services shutdown,
// and the time-out period for the services shutdown.
// If 0<=timeout<5s, automatically use 'MinShutdownTimeout'(5s).
// If timeout<0, indefinite period.
// 'preCloseFunc' is executed before closing services, but not guaranteed to be completed.
// 'postCloseFunc' is executed after services are closed, but not guaranteed to be completed.
func SetShutdown(timeout time.Duration, preCloseFunc, postCloseFunc func() error) {
if timeout < 0 {
global.shutdownTimeout = 1<<63 - 1
} else if timeout < MinShutdownTimeout {
global.shutdownTimeout = MinShutdownTimeout
} else {
global.shutdownTimeout = timeout
}
global.preCloseFunc = preCloseFunc
global.postCloseFunc = postCloseFunc
}
// Shutdown closes all the frame services gracefully.
// Parameter timeout is used to reset time-out period for the services shutdown.
func Shutdown(timeout ...time.Duration) {
global.framesLock.Lock()
defer global.framesLock.Unlock()
defer CloseLog()
Print("\x1b[46m[SYS]\x1b[0m shutting down services...")
contextExec(timeout, "shutdown", func(ctxTimeout context.Context) <-chan struct{} {
endCh := make(chan struct{})
go func() {
defer close(endCh)
var graceful = true
if global.preCloseFunc != nil {
if err := global.preCloseFunc(); err != nil {
Errorf("[shutdown-preClose] %s", err.Error())
graceful = false
}
}
graceful = shutdown(ctxTimeout, "shutdown") && graceful
if graceful {
Print("\x1b[46m[SYS]\x1b[0m services are shutted down gracefully!")
} else {
Print("\x1b[46m[SYS]\x1b[0m services are shutted down, but not gracefully!")
}
}()
return endCh
})
}
func contextExec(timeout []time.Duration, action string, deferCallback func(ctxTimeout context.Context) <-chan struct{}) {
if len(timeout) > 0 {
SetShutdown(timeout[0], global.preCloseFunc, global.postCloseFunc)
}
ctxTimeout, _ := context.WithTimeout(context.Background(), global.shutdownTimeout)
select {
case <-ctxTimeout.Done():
if err := ctxTimeout.Err(); err != nil {
Errorf("[%s-timeout] %s", action, err.Error())
}
case <-deferCallback(ctxTimeout):
}
}
func shutdown(ctxTimeout context.Context, action string) bool {
var flag int32 = 1
count := new(sync.WaitGroup)
for _, frame := range global.frames {
count.Add(1)
go func(fm *Framework) {
graceful := fm.shutdown(ctxTimeout)
if !graceful {
atomic.StoreInt32(&flag, 0)
}
count.Done()
}(frame)
}
count.Wait()
if global.postCloseFunc != nil {
if err := global.postCloseFunc(); err != nil {
atomic.StoreInt32(&flag, 0)
Errorf("[%s-postClose] %s", action, err.Error())
}
}
return flag == 1
}
// HandleError calls the default error handler.
func HandleError(ctx *Context, errStr string, status int) {
global.errorFunc(ctx, errStr, status)
}
// SetErrorFunc sets the global default `ErrorFunc` function.
func SetErrorFunc(errorFunc ErrorFunc) {
if errorFunc == nil {
global.errorFunc = defaultErrorFunc
} else {
global.errorFunc = errorFunc
}
}
// DecodeBody decodes params from request body.
func DecodeBody(dest reflect.Value, body []byte) error {
return global.bodydecoder(dest, body)
}
// SetBodydecoder sets the global default `Bodydecoder` function.
func SetBodydecoder(bodydecoder apiware.Bodydecoder) {
if bodydecoder == nil {
global.bodydecoder = defaultBodydecoder
} else {
global.bodydecoder = bodydecoder
}
}
// HandleBinderror calls the default parameter binding failure handler.
func HandleBinderror(ctx *Context, err error) {
global.binderrorFunc(ctx, err)
}
// SetBinderrorFunc sets the global default `BinderrorFunc` function.
func SetBinderrorFunc(binderrorFunc BinderrorFunc) {
if binderrorFunc == nil {
global.binderrorFunc = defaultBinderrorFunc
} else {
global.binderrorFunc = binderrorFunc
}
}
// MapParamName maps the APIHander's parameter name from the structure field.
// When the APIHander's parameter name (struct tag) is unsetted,
// it is mapped from the structure field name by default.
// If `paramNameMapper` is nil, use snake style.
func MapParamName(fieldName string) (paramName string) {
return global.paramNameMapper(fieldName)
}
// SetParamNameMapper sets the global default `ParamNameMapper` function.
func SetParamNameMapper(paramNameMapper apiware.ParamNameMapper) {
if paramNameMapper == nil {
global.paramNameMapper = defaultParamNameMapper
} else {
global.paramNameMapper = paramNameMapper
}
}
// GetRender returns a custom faygo template renderer using pongo2.
func GetRender() *Render {
return global.render
}
// RenderVar sets the global template variable, function or pongo2.FilterFunction for pongo2 render.
func RenderVar(name string, v interface{}) {
global.render.TemplateVar(name, v)
}
// LogDir returns logs folder path with a slash at the end
func LogDir() string {
return global.logDir
}
// SetUpload sets upload folder path such as `./upload/`
// with a slash `/` at the end.
// note: it should be called before Run()
func SetUpload(dir string, nocompress bool, nocache bool, handlers ...Handler) {
if !strings.HasSuffix(dir, "/") {
dir += "/"
}
global.upload = PresetStatic{
root: dir,
nocompress: nocompress,
nocache: nocache,
handlers: handlers,
}
}
// UploadDir returns upload folder path with a slash at the end
func UploadDir() string {
return global.upload.root
}
// SetStatic sets static folder path, such as `./staic/`
// with a slash `/` at the end.
// note: it should be called before Run()
func SetStatic(dir string, nocompress bool, nocache bool, handlers ...Handler) {
if !strings.HasSuffix(dir, "/") {
dir += "/"
}
global.static = PresetStatic{
root: dir,
nocompress: nocompress,
nocache: nocache,
handlers: handlers,
}
}
// StaticDir returns static folder path with a slash at the end
func StaticDir() string {
return global.static.root
}
// CloseLog closes global loggers.
func CloseLog() {
global.bizlog.Close()
global.syslog.Close()
}
// Fatal is equivalent to l.Critical(fmt.Sprint()) followed by a call to os.Exit(1).
func Fatal(args ...interface{}) {
global.bizlog.Fatal(args...)
}
// Fatalf is equivalent to l.Critical followed by a call to os.Exit(1).
func Fatalf(format string, args ...interface{}) {
global.bizlog.Fatalf(format, args...)
}
// Panic is equivalent to l.Critical(fmt.Sprint()) followed by a call to panic().
func Panic(args ...interface{}) {
global.bizlog.Panic(args...)
}
// Panicf is equivalent to l.Critical followed by a call to panic().
func Panicf(format string, args ...interface{}) {
global.bizlog.Panicf(format, args...)
}
// Critical logs a message using CRITICAL as log level.
func Critical(args ...interface{}) {
global.bizlog.Critical(args...)
}
// Criticalf logs a message using CRITICAL as log level.
func Criticalf(format string, args ...interface{}) {
global.bizlog.Criticalf(format, args...)
}
// Error logs a message using ERROR as log level.
func Error(args ...interface{}) {
global.bizlog.Error(args...)
}
// Errorf logs a message using ERROR as log level.
func Errorf(format string, args ...interface{}) {
global.bizlog.Errorf(format, args...)
}
// Warning logs a message using WARNING as log level.
func Warning(args ...interface{}) {
global.bizlog.Warning(args...)
}
// Warningf logs a message using WARNING as log level.
func Warningf(format string, args ...interface{}) {
global.bizlog.Warningf(format, args...)
}
// Notice logs a message using NOTICE as log level.
func Notice(args ...interface{}) {
global.bizlog.Notice(args...)
}
// Noticef logs a message using NOTICE as log level.
func Noticef(format string, args ...interface{}) {
global.bizlog.Noticef(format, args...)
}
// Info logs a message using INFO as log level.
func Info(args ...interface{}) {
global.bizlog.Info(args...)
}
// Infof logs a message using INFO as log level.
func Infof(format string, args ...interface{}) {
global.bizlog.Infof(format, args...)
}
// Debug logs a message using DEBUG as log level.
func Debug(args ...interface{}) {
global.bizlog.Debug(args...)
}
// Debugf logs a message using DEBUG as log level.
func Debugf(format string, args ...interface{}) {
global.bizlog.Debugf(format, args...)
}
// Print logs a message using CRITICAL as log level, only with time prefix.
func Print(args ...interface{}) {
global.syslog.Critical(args...)
}
// Printf logs a message using CRITICAL as log level, only with time prefix.
func Printf(format string, args ...interface{}) {
global.syslog.Criticalf(format, args...)
}
type (
// GlobalVariables defines the global frames, configuration, function and so on.
GlobalVariables struct {
// the list of applications that have been created.
frames []*Framework
framesLock sync.RWMutex
// global config
config GlobalConfig
// Error replies to the request with the specified error message and HTTP code.
// It does not otherwise end the request; the caller should ensure no further
// writes are done to response.
// The error message should be plain text.
errorFunc ErrorFunc
// The following is only for the APIHandler
binderrorFunc BinderrorFunc
// Decode params from request body.
bodydecoder apiware.Bodydecoder
// When the APIHander's parameter name (struct tag) is unsetted,
// it is mapped from the structure field name by default.
// If `paramNameMapper` is nil, use snake style.
// If the APIHander's parameter binding fails, the default handler is invoked
paramNameMapper apiware.ParamNameMapper
// global file cache system manager
fsManager *FileServerManager
// Render is a custom faygo template renderer using pongo2.
render *Render
// The path for the upload files.
// When does not have a custom route, the route is automatically created.
upload PresetStatic
// The path for the static files.
// When does not have a custom route, the route is automatically created.
static PresetStatic
// The path for the log files
logDir string
syslog *logging.Logger
bizlog *logging.Logger
// the time-out period for the services shutdown.
shutdownTimeout time.Duration
// executed before closing services, but not guaranteed to be completed.
preCloseFunc func() error
// executed after services are closed, but not guaranteed to be completed.
postCloseFunc func() error
beforeRunOnce sync.Once
}
// PresetStatic is the system default static file routing information
PresetStatic struct {
root string
nocompress bool
nocache bool
handlers []Handler
}
)
var (
// global is the global configuration, functions and so on.
global = func() *GlobalVariables {
global := &GlobalVariables{
frames: []*Framework{},
config: globalConfig,
errorFunc: defaultErrorFunc,
bodydecoder: defaultBodydecoder,
binderrorFunc: defaultBinderrorFunc,
paramNameMapper: defaultParamNameMapper,
fsManager: newFileServerManager(
globalConfig.Cache.SizeMB*1024*1024,
globalConfig.Cache.ExpireSecond,
globalConfig.Cache.Enable,
globalConfig.Gzip.Enable,
),
upload: defaultUpload,
static: defaultStatic,
logDir: defaultLogDir,
shutdownTimeout: MinShutdownTimeout,
}
if globalConfig.Cache.Enable {
global.render = newRender(func(name string) (http.File, error) {
return global.fsManager.Open(name, "", false)
})
} else {
global.render = newRender(nil)
}
global.initLogger()
return global
}()
defaultErrorFunc = func(ctx *Context, errStr string, status int) {
if ctx.W.Committed() {
if status >= 500 {
ctx.Log().Debug(errStr)
}
return
}
if status >= 500 {
ctx.Log().Error(errStr)
}
statusText := http.StatusText(status)
if len(errStr) > 0 {
errStr = `<br><p><b style="color:red;">[ERROR]</b> <pre>` + errStr + `</pre></p>`
}
ctx.W.Header().Set(HeaderXContentTypeOptions, nosniff)
ctx.HTML(status, fmt.Sprintf("<html>\n"+
"<head><title>%d %s</title></head>\n"+
"<body bgcolor=\"white\">\n"+
"<center><h1>%d %s</h1></center>\n"+
"<hr>\n<center>faygo/%s</center>\n%s\n</body>\n</html>\n",
status, statusText, status, statusText, VERSION, errStr),
)
}
// The default body decoder is json format decoding
defaultBodydecoder = func(dest reflect.Value, body []byte) error {
var err error
if dest.Kind() == reflect.Ptr {
err = json.Unmarshal(body, dest.Interface())
} else {
err = json.Unmarshal(body, dest.Addr().Interface())
}
return err
}
defaultBinderrorFunc = func(ctx *Context, err error) {
ctx.String(http.StatusBadRequest, "%v", err)
}
defaultParamNameMapper = SnakeString
// The default path for the upload files
defaultUpload = PresetStatic{
root: "./upload/",
}
// The default path for the static files
defaultStatic = PresetStatic{
root: "./static/",
}
// The default path for the log files
defaultLogDir = "./log/"
)
func init() {
fmt.Println(banner[1:])
global.syslog.Criticalf("The PID of the current process is %d", os.Getpid())
if global.config.warnMsg != "" {
Warning(global.config.warnMsg)
global.config.warnMsg = ""
}
// init file cache
acceptencoder.InitGzip(global.config.Gzip.MinLength, global.config.Gzip.CompressLevel, global.config.Gzip.Methods)
}
func addFrame(frame *Framework) {
global.framesLock.Lock()
defer global.framesLock.Unlock()
name := frame.NameWithVersion()
for _, v := range global.frames {
if v.NameWithVersion() == name {
frame.Log().Panicf("frame %s is registered repeatedly", name)
}
}
global.frames = append(global.frames, frame)
}
func (g *GlobalVariables) beforeRun() {
g.beforeRunOnce.Do(func() {
resetFlag()
WritePid(LogDir() + "app.pid")
go graceSignal()
})
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化