mirror of
https://github.com/astaxie/beego.git
synced 2024-12-18 07:00:50 +00:00
Move package
This commit is contained in:
parent
16b66509f6
commit
9c51952db4
@ -178,9 +178,9 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
|
|||||||
column += " " + "NOT NULL"
|
column += " " + "NOT NULL"
|
||||||
}
|
}
|
||||||
|
|
||||||
// if fi.initial.String() != "" {
|
//if fi.initial.String() != "" {
|
||||||
// column += " DEFAULT " + fi.initial.String()
|
// column += " DEFAULT " + fi.initial.String()
|
||||||
// }
|
//}
|
||||||
|
|
||||||
// Append attribute DEFAULT
|
// Append attribute DEFAULT
|
||||||
column += getColumnDefault(fi)
|
column += getColumnDefault(fi)
|
||||||
@ -197,9 +197,9 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
|
|||||||
if strings.Contains(column, "%COL%") {
|
if strings.Contains(column, "%COL%") {
|
||||||
column = strings.Replace(column, "%COL%", fi.column, -1)
|
column = strings.Replace(column, "%COL%", fi.column, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.description != "" && al.Driver != DRSqlite {
|
if fi.description != "" && al.Driver!=DRSqlite {
|
||||||
column += " " + fmt.Sprintf("COMMENT '%s'", fi.description)
|
column += " " + fmt.Sprintf("COMMENT '%s'",fi.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
columns = append(columns, column)
|
columns = append(columns, column)
|
||||||
|
@ -12,21 +12,16 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Deprecated: we will remove this package, please using pkg/orm
|
|
||||||
package orm
|
package orm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
lru "github.com/hashicorp/golang-lru"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego/pkg/common"
|
|
||||||
orm2 "github.com/astaxie/beego/pkg/orm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DriverType database driver constant int.
|
// DriverType database driver constant int.
|
||||||
@ -68,7 +63,7 @@ var (
|
|||||||
"tidb": DRTiDB,
|
"tidb": DRTiDB,
|
||||||
"oracle": DROracle,
|
"oracle": DROracle,
|
||||||
"oci8": DROracle, // github.com/mattn/go-oci8
|
"oci8": DROracle, // github.com/mattn/go-oci8
|
||||||
"ora": DROracle, // https://github.com/rana/ora
|
"ora": DROracle, //https://github.com/rana/ora
|
||||||
}
|
}
|
||||||
dbBasers = map[DriverType]dbBaser{
|
dbBasers = map[DriverType]dbBaser{
|
||||||
DRMySQL: newdbBaseMysql(),
|
DRMySQL: newdbBaseMysql(),
|
||||||
@ -124,7 +119,7 @@ func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
|
|||||||
return d.DB.BeginTx(ctx, opts)
|
return d.DB.BeginTx(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// su must call release to release *sql.Stmt after using
|
//su must call release to release *sql.Stmt after using
|
||||||
func (d *DB) getStmtDecorator(query string) (*stmtDecorator, error) {
|
func (d *DB) getStmtDecorator(query string) (*stmtDecorator, error) {
|
||||||
d.RLock()
|
d.RLock()
|
||||||
c, ok := d.stmtDecorators.Get(query)
|
c, ok := d.stmtDecorators.Get(query)
|
||||||
@ -294,26 +289,82 @@ func detectTZ(al *alias) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addAliasWthDB(aliasName, driverName string, db *sql.DB) (*alias, error) {
|
||||||
|
al := new(alias)
|
||||||
|
al.Name = aliasName
|
||||||
|
al.DriverName = driverName
|
||||||
|
al.DB = &DB{
|
||||||
|
RWMutex: new(sync.RWMutex),
|
||||||
|
DB: db,
|
||||||
|
stmtDecorators: newStmtDecoratorLruWithEvict(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if dr, ok := drivers[driverName]; ok {
|
||||||
|
al.DbBaser = dbBasers[dr]
|
||||||
|
al.Driver = dr
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("driver name `%s` have not registered", driverName)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dataBaseCache.add(aliasName, al) {
|
||||||
|
return nil, fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return al, nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddAliasWthDB add a aliasName for the drivename
|
// AddAliasWthDB add a aliasName for the drivename
|
||||||
// Deprecated: please using pkg/orm
|
|
||||||
func AddAliasWthDB(aliasName, driverName string, db *sql.DB) error {
|
func AddAliasWthDB(aliasName, driverName string, db *sql.DB) error {
|
||||||
return orm2.AddAliasWthDB(aliasName, driverName, db)
|
_, err := addAliasWthDB(aliasName, driverName, db)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterDataBase Setting the database connect params. Use the database driver self dataSource args.
|
// RegisterDataBase Setting the database connect params. Use the database driver self dataSource args.
|
||||||
func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) error {
|
func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) error {
|
||||||
kvs := make([]common.KV, 0, 2)
|
var (
|
||||||
|
err error
|
||||||
|
db *sql.DB
|
||||||
|
al *alias
|
||||||
|
)
|
||||||
|
|
||||||
|
db, err = sql.Open(driverName, dataSource)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error())
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
|
||||||
|
al, err = addAliasWthDB(aliasName, driverName, db)
|
||||||
|
if err != nil {
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
|
||||||
|
al.DataSource = dataSource
|
||||||
|
|
||||||
|
detectTZ(al)
|
||||||
|
|
||||||
for i, v := range params {
|
for i, v := range params {
|
||||||
switch i {
|
switch i {
|
||||||
case 0:
|
case 0:
|
||||||
kvs = append(kvs, common.KV{Key: orm2.MaxIdleConnsKey, Value: v})
|
SetMaxIdleConns(al.Name, v)
|
||||||
case 1:
|
case 1:
|
||||||
kvs = append(kvs, common.KV{Key: orm2.MaxOpenConnsKey, Value: v})
|
SetMaxOpenConns(al.Name, v)
|
||||||
case 2:
|
|
||||||
kvs = append(kvs, common.KV{Key: orm2.ConnMaxLifetimeKey, Value: time.Duration(v) * time.Millisecond})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return orm2.RegisterDataBase(aliasName, driverName, dataSource, kvs...)
|
|
||||||
|
end:
|
||||||
|
if err != nil {
|
||||||
|
if db != nil {
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
DebugLog.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterDriver Register a database driver use specify driver name, this can be definition the driver is which database type.
|
// RegisterDriver Register a database driver use specify driver name, this can be definition the driver is which database type.
|
||||||
@ -373,7 +424,7 @@ func GetDB(aliasNames ...string) (*sql.DB, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type stmtDecorator struct {
|
type stmtDecorator struct {
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
stmt *sql.Stmt
|
stmt *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,7 +444,7 @@ func (s *stmtDecorator) release() {
|
|||||||
s.wg.Done()
|
s.wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// garbage recycle for stmt
|
//garbage recycle for stmt
|
||||||
func (s *stmtDecorator) destroy() {
|
func (s *stmtDecorator) destroy() {
|
||||||
go func() {
|
go func() {
|
||||||
s.wg.Wait()
|
s.wg.Wait()
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
// Copyright 2020 beego-dev
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
var DBARGS = struct {
|
|
||||||
Driver string
|
|
||||||
Source string
|
|
||||||
Debug string
|
|
||||||
}{
|
|
||||||
os.Getenv("ORM_DRIVER"),
|
|
||||||
os.Getenv("ORM_SOURCE"),
|
|
||||||
os.Getenv("ORM_DEBUG"),
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegisterDataBase(t *testing.T) {
|
|
||||||
err := RegisterDataBase("test-adapt1", DBARGS.Driver, DBARGS.Source)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
err = RegisterDataBase("test-adapt2", DBARGS.Driver, DBARGS.Source, 20)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
err = RegisterDataBase("test-adapt3", DBARGS.Driver, DBARGS.Source, 20, 300)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
err = RegisterDataBase("test-adapt4", DBARGS.Driver, DBARGS.Source, 20, 300, 60*1000)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
}
|
|
458
pkg/admin.go
Normal file
458
pkg/admin.go
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/grace"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/astaxie/beego/toolbox"
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BeeAdminApp is the default adminApp used by admin module.
|
||||||
|
var beeAdminApp *adminApp
|
||||||
|
|
||||||
|
// FilterMonitorFunc is default monitor filter when admin module is enable.
|
||||||
|
// if this func returns, admin module records qps for this request by condition of this function logic.
|
||||||
|
// usage:
|
||||||
|
// func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool {
|
||||||
|
// if method == "POST" {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// if t.Nanoseconds() < 100 {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// if strings.HasPrefix(requestPath, "/astaxie") {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// beego.FilterMonitorFunc = MyFilterMonitor.
|
||||||
|
var FilterMonitorFunc func(string, string, time.Duration, string, int) bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
beeAdminApp = &adminApp{
|
||||||
|
routers: make(map[string]http.HandlerFunc),
|
||||||
|
}
|
||||||
|
// keep in mind that all data should be html escaped to avoid XSS attack
|
||||||
|
beeAdminApp.Route("/", adminIndex)
|
||||||
|
beeAdminApp.Route("/qps", qpsIndex)
|
||||||
|
beeAdminApp.Route("/prof", profIndex)
|
||||||
|
beeAdminApp.Route("/healthcheck", healthcheck)
|
||||||
|
beeAdminApp.Route("/task", taskStatus)
|
||||||
|
beeAdminApp.Route("/listconf", listConf)
|
||||||
|
beeAdminApp.Route("/metrics", promhttp.Handler().ServeHTTP)
|
||||||
|
FilterMonitorFunc = func(string, string, time.Duration, string, int) bool { return true }
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminIndex is the default http.Handler for admin module.
|
||||||
|
// it matches url pattern "/".
|
||||||
|
func adminIndex(rw http.ResponseWriter, _ *http.Request) {
|
||||||
|
writeTemplate(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter.
|
||||||
|
// it's registered with url pattern "/qps" in admin module.
|
||||||
|
func qpsIndex(rw http.ResponseWriter, _ *http.Request) {
|
||||||
|
data := make(map[interface{}]interface{})
|
||||||
|
data["Content"] = toolbox.StatisticsMap.GetMap()
|
||||||
|
|
||||||
|
// do html escape before display path, avoid xss
|
||||||
|
if content, ok := (data["Content"]).(M); ok {
|
||||||
|
if resultLists, ok := (content["Data"]).([][]string); ok {
|
||||||
|
for i := range resultLists {
|
||||||
|
if len(resultLists[i]) > 0 {
|
||||||
|
resultLists[i][0] = template.HTMLEscapeString(resultLists[i][0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeTemplate(rw, data, qpsTpl, defaultScriptsTpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConf is the http.Handler of displaying all beego configuration values as key/value pair.
|
||||||
|
// it's registered with url pattern "/listconf" in admin module.
|
||||||
|
func listConf(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
r.ParseForm()
|
||||||
|
command := r.Form.Get("command")
|
||||||
|
if command == "" {
|
||||||
|
rw.Write([]byte("command not support"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[interface{}]interface{})
|
||||||
|
switch command {
|
||||||
|
case "conf":
|
||||||
|
m := make(M)
|
||||||
|
list("BConfig", BConfig, m)
|
||||||
|
m["AppConfigPath"] = template.HTMLEscapeString(appConfigPath)
|
||||||
|
m["AppConfigProvider"] = template.HTMLEscapeString(appConfigProvider)
|
||||||
|
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
|
||||||
|
tmpl = template.Must(tmpl.Parse(configTpl))
|
||||||
|
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
|
||||||
|
|
||||||
|
data["Content"] = m
|
||||||
|
|
||||||
|
tmpl.Execute(rw, data)
|
||||||
|
|
||||||
|
case "router":
|
||||||
|
content := PrintTree()
|
||||||
|
content["Fields"] = []string{
|
||||||
|
"Router Pattern",
|
||||||
|
"Methods",
|
||||||
|
"Controller",
|
||||||
|
}
|
||||||
|
data["Content"] = content
|
||||||
|
data["Title"] = "Routers"
|
||||||
|
writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl)
|
||||||
|
case "filter":
|
||||||
|
var (
|
||||||
|
content = M{
|
||||||
|
"Fields": []string{
|
||||||
|
"Router Pattern",
|
||||||
|
"Filter Function",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
filterTypes = []string{}
|
||||||
|
filterTypeData = make(M)
|
||||||
|
)
|
||||||
|
|
||||||
|
if BeeApp.Handlers.enableFilter {
|
||||||
|
var filterType string
|
||||||
|
for k, fr := range map[int]string{
|
||||||
|
BeforeStatic: "Before Static",
|
||||||
|
BeforeRouter: "Before Router",
|
||||||
|
BeforeExec: "Before Exec",
|
||||||
|
AfterExec: "After Exec",
|
||||||
|
FinishRouter: "Finish Router"} {
|
||||||
|
if bf := BeeApp.Handlers.filters[k]; len(bf) > 0 {
|
||||||
|
filterType = fr
|
||||||
|
filterTypes = append(filterTypes, filterType)
|
||||||
|
resultList := new([][]string)
|
||||||
|
for _, f := range bf {
|
||||||
|
var result = []string{
|
||||||
|
// void xss
|
||||||
|
template.HTMLEscapeString(f.pattern),
|
||||||
|
template.HTMLEscapeString(utils.GetFuncName(f.filterFunc)),
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
}
|
||||||
|
filterTypeData[filterType] = resultList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content["Data"] = filterTypeData
|
||||||
|
content["Methods"] = filterTypes
|
||||||
|
|
||||||
|
data["Content"] = content
|
||||||
|
data["Title"] = "Filters"
|
||||||
|
writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl)
|
||||||
|
default:
|
||||||
|
rw.Write([]byte("command not support"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func list(root string, p interface{}, m M) {
|
||||||
|
pt := reflect.TypeOf(p)
|
||||||
|
pv := reflect.ValueOf(p)
|
||||||
|
if pt.Kind() == reflect.Ptr {
|
||||||
|
pt = pt.Elem()
|
||||||
|
pv = pv.Elem()
|
||||||
|
}
|
||||||
|
for i := 0; i < pv.NumField(); i++ {
|
||||||
|
var key string
|
||||||
|
if root == "" {
|
||||||
|
key = pt.Field(i).Name
|
||||||
|
} else {
|
||||||
|
key = root + "." + pt.Field(i).Name
|
||||||
|
}
|
||||||
|
if pv.Field(i).Kind() == reflect.Struct {
|
||||||
|
list(key, pv.Field(i).Interface(), m)
|
||||||
|
} else {
|
||||||
|
m[key] = pv.Field(i).Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintTree prints all registered routers.
|
||||||
|
func PrintTree() M {
|
||||||
|
var (
|
||||||
|
content = M{}
|
||||||
|
methods = []string{}
|
||||||
|
methodsData = make(M)
|
||||||
|
)
|
||||||
|
for method, t := range BeeApp.Handlers.routers {
|
||||||
|
|
||||||
|
resultList := new([][]string)
|
||||||
|
|
||||||
|
printTree(resultList, t)
|
||||||
|
|
||||||
|
methods = append(methods, template.HTMLEscapeString(method))
|
||||||
|
methodsData[template.HTMLEscapeString(method)] = resultList
|
||||||
|
}
|
||||||
|
|
||||||
|
content["Data"] = methodsData
|
||||||
|
content["Methods"] = methods
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTree(resultList *[][]string, t *Tree) {
|
||||||
|
for _, tr := range t.fixrouters {
|
||||||
|
printTree(resultList, tr)
|
||||||
|
}
|
||||||
|
if t.wildcard != nil {
|
||||||
|
printTree(resultList, t.wildcard)
|
||||||
|
}
|
||||||
|
for _, l := range t.leaves {
|
||||||
|
if v, ok := l.runObject.(*ControllerInfo); ok {
|
||||||
|
if v.routerType == routerTypeBeego {
|
||||||
|
var result = []string{
|
||||||
|
template.HTMLEscapeString(v.pattern),
|
||||||
|
template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)),
|
||||||
|
template.HTMLEscapeString(v.controllerType.String()),
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
} else if v.routerType == routerTypeRESTFul {
|
||||||
|
var result = []string{
|
||||||
|
template.HTMLEscapeString(v.pattern),
|
||||||
|
template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)),
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
} else if v.routerType == routerTypeHandler {
|
||||||
|
var result = []string{
|
||||||
|
template.HTMLEscapeString(v.pattern),
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfIndex is a http.Handler for showing profile command.
|
||||||
|
// it's in url pattern "/prof" in admin module.
|
||||||
|
func profIndex(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
r.ParseForm()
|
||||||
|
command := r.Form.Get("command")
|
||||||
|
if command == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
format = r.Form.Get("format")
|
||||||
|
data = make(map[interface{}]interface{})
|
||||||
|
result bytes.Buffer
|
||||||
|
)
|
||||||
|
toolbox.ProcessInput(command, &result)
|
||||||
|
data["Content"] = template.HTMLEscapeString(result.String())
|
||||||
|
|
||||||
|
if format == "json" && command == "gc summary" {
|
||||||
|
dataJSON, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(rw, dataJSON)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data["Title"] = template.HTMLEscapeString(command)
|
||||||
|
defaultTpl := defaultScriptsTpl
|
||||||
|
if command == "gc summary" {
|
||||||
|
defaultTpl = gcAjaxTpl
|
||||||
|
}
|
||||||
|
writeTemplate(rw, data, profillingTpl, defaultTpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Healthcheck is a http.Handler calling health checking and showing the result.
|
||||||
|
// it's in "/healthcheck" pattern in admin module.
|
||||||
|
func healthcheck(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
result []string
|
||||||
|
data = make(map[interface{}]interface{})
|
||||||
|
resultList = new([][]string)
|
||||||
|
content = M{
|
||||||
|
"Fields": []string{"Name", "Message", "Status"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for name, h := range toolbox.AdminCheckList {
|
||||||
|
if err := h.Check(); err != nil {
|
||||||
|
result = []string{
|
||||||
|
"error",
|
||||||
|
template.HTMLEscapeString(name),
|
||||||
|
template.HTMLEscapeString(err.Error()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = []string{
|
||||||
|
"success",
|
||||||
|
template.HTMLEscapeString(name),
|
||||||
|
"OK",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams := r.URL.Query()
|
||||||
|
jsonFlag := queryParams.Get("json")
|
||||||
|
shouldReturnJSON, _ := strconv.ParseBool(jsonFlag)
|
||||||
|
|
||||||
|
if shouldReturnJSON {
|
||||||
|
response := buildHealthCheckResponseList(resultList)
|
||||||
|
jsonResponse, err := json.Marshal(response)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
} else {
|
||||||
|
writeJSON(rw, jsonResponse)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content["Data"] = resultList
|
||||||
|
data["Content"] = content
|
||||||
|
data["Title"] = "Health Check"
|
||||||
|
|
||||||
|
writeTemplate(rw, data, healthCheckTpl, defaultScriptsTpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildHealthCheckResponseList(healthCheckResults *[][]string) []map[string]interface{} {
|
||||||
|
response := make([]map[string]interface{}, len(*healthCheckResults))
|
||||||
|
|
||||||
|
for i, healthCheckResult := range *healthCheckResults {
|
||||||
|
currentResultMap := make(map[string]interface{})
|
||||||
|
|
||||||
|
currentResultMap["name"] = healthCheckResult[0]
|
||||||
|
currentResultMap["message"] = healthCheckResult[1]
|
||||||
|
currentResultMap["status"] = healthCheckResult[2]
|
||||||
|
|
||||||
|
response[i] = currentResultMap
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeJSON(rw http.ResponseWriter, jsonData []byte) {
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
rw.Write(jsonData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskStatus is a http.Handler with running task status (task name, status and the last execution).
|
||||||
|
// it's in "/task" pattern in admin module.
|
||||||
|
func taskStatus(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
data := make(map[interface{}]interface{})
|
||||||
|
|
||||||
|
// Run Task
|
||||||
|
req.ParseForm()
|
||||||
|
taskname := req.Form.Get("taskname")
|
||||||
|
if taskname != "" {
|
||||||
|
if t, ok := toolbox.AdminTaskList[taskname]; ok {
|
||||||
|
if err := t.Run(); err != nil {
|
||||||
|
data["Message"] = []string{"error", template.HTMLEscapeString(fmt.Sprintf("%s", err))}
|
||||||
|
}
|
||||||
|
data["Message"] = []string{"success", template.HTMLEscapeString(fmt.Sprintf("%s run success,Now the Status is <br>%s", taskname, t.GetStatus()))}
|
||||||
|
} else {
|
||||||
|
data["Message"] = []string{"warning", template.HTMLEscapeString(fmt.Sprintf("there's no task which named: %s", taskname))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List Tasks
|
||||||
|
content := make(M)
|
||||||
|
resultList := new([][]string)
|
||||||
|
var fields = []string{
|
||||||
|
"Task Name",
|
||||||
|
"Task Spec",
|
||||||
|
"Task Status",
|
||||||
|
"Last Time",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
for tname, tk := range toolbox.AdminTaskList {
|
||||||
|
result := []string{
|
||||||
|
template.HTMLEscapeString(tname),
|
||||||
|
template.HTMLEscapeString(tk.GetSpec()),
|
||||||
|
template.HTMLEscapeString(tk.GetStatus()),
|
||||||
|
template.HTMLEscapeString(tk.GetPrev().String()),
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
content["Fields"] = fields
|
||||||
|
content["Data"] = resultList
|
||||||
|
data["Content"] = content
|
||||||
|
data["Title"] = "Tasks"
|
||||||
|
writeTemplate(rw, data, tasksTpl, defaultScriptsTpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTemplate(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) {
|
||||||
|
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
|
||||||
|
for _, tpl := range tpls {
|
||||||
|
tmpl = template.Must(tmpl.Parse(tpl))
|
||||||
|
}
|
||||||
|
tmpl.Execute(rw, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// adminApp is an http.HandlerFunc map used as beeAdminApp.
|
||||||
|
type adminApp struct {
|
||||||
|
routers map[string]http.HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route adds http.HandlerFunc to adminApp with url pattern.
|
||||||
|
func (admin *adminApp) Route(pattern string, f http.HandlerFunc) {
|
||||||
|
admin.routers[pattern] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run adminApp http server.
|
||||||
|
// Its addr is defined in configuration file as adminhttpaddr and adminhttpport.
|
||||||
|
func (admin *adminApp) Run() {
|
||||||
|
if len(toolbox.AdminTaskList) > 0 {
|
||||||
|
toolbox.StartTask()
|
||||||
|
}
|
||||||
|
addr := BConfig.Listen.AdminAddr
|
||||||
|
|
||||||
|
if BConfig.Listen.AdminPort != 0 {
|
||||||
|
addr = fmt.Sprintf("%s:%d", BConfig.Listen.AdminAddr, BConfig.Listen.AdminPort)
|
||||||
|
}
|
||||||
|
for p, f := range admin.routers {
|
||||||
|
http.Handle(p, f)
|
||||||
|
}
|
||||||
|
logs.Info("Admin server Running on %s", addr)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if BConfig.Listen.Graceful {
|
||||||
|
err = grace.ListenAndServe(addr, nil)
|
||||||
|
} else {
|
||||||
|
err = http.ListenAndServe(addr, nil)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logs.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
|
}
|
||||||
|
}
|
239
pkg/admin_test.go
Normal file
239
pkg/admin_test.go
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/toolbox"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SampleDatabaseCheck struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type SampleCacheCheck struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *SampleDatabaseCheck) Check() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *SampleCacheCheck) Check() error {
|
||||||
|
return errors.New("no cache detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestList_01(t *testing.T) {
|
||||||
|
m := make(M)
|
||||||
|
list("BConfig", BConfig, m)
|
||||||
|
t.Log(m)
|
||||||
|
om := oldMap()
|
||||||
|
for k, v := range om {
|
||||||
|
if fmt.Sprint(m[k]) != fmt.Sprint(v) {
|
||||||
|
t.Log(k, "old-key", v, "new-key", m[k])
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oldMap() M {
|
||||||
|
m := make(M)
|
||||||
|
m["BConfig.AppName"] = BConfig.AppName
|
||||||
|
m["BConfig.RunMode"] = BConfig.RunMode
|
||||||
|
m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive
|
||||||
|
m["BConfig.ServerName"] = BConfig.ServerName
|
||||||
|
m["BConfig.RecoverPanic"] = BConfig.RecoverPanic
|
||||||
|
m["BConfig.CopyRequestBody"] = BConfig.CopyRequestBody
|
||||||
|
m["BConfig.EnableGzip"] = BConfig.EnableGzip
|
||||||
|
m["BConfig.MaxMemory"] = BConfig.MaxMemory
|
||||||
|
m["BConfig.EnableErrorsShow"] = BConfig.EnableErrorsShow
|
||||||
|
m["BConfig.Listen.Graceful"] = BConfig.Listen.Graceful
|
||||||
|
m["BConfig.Listen.ServerTimeOut"] = BConfig.Listen.ServerTimeOut
|
||||||
|
m["BConfig.Listen.ListenTCP4"] = BConfig.Listen.ListenTCP4
|
||||||
|
m["BConfig.Listen.EnableHTTP"] = BConfig.Listen.EnableHTTP
|
||||||
|
m["BConfig.Listen.HTTPAddr"] = BConfig.Listen.HTTPAddr
|
||||||
|
m["BConfig.Listen.HTTPPort"] = BConfig.Listen.HTTPPort
|
||||||
|
m["BConfig.Listen.EnableHTTPS"] = BConfig.Listen.EnableHTTPS
|
||||||
|
m["BConfig.Listen.HTTPSAddr"] = BConfig.Listen.HTTPSAddr
|
||||||
|
m["BConfig.Listen.HTTPSPort"] = BConfig.Listen.HTTPSPort
|
||||||
|
m["BConfig.Listen.HTTPSCertFile"] = BConfig.Listen.HTTPSCertFile
|
||||||
|
m["BConfig.Listen.HTTPSKeyFile"] = BConfig.Listen.HTTPSKeyFile
|
||||||
|
m["BConfig.Listen.EnableAdmin"] = BConfig.Listen.EnableAdmin
|
||||||
|
m["BConfig.Listen.AdminAddr"] = BConfig.Listen.AdminAddr
|
||||||
|
m["BConfig.Listen.AdminPort"] = BConfig.Listen.AdminPort
|
||||||
|
m["BConfig.Listen.EnableFcgi"] = BConfig.Listen.EnableFcgi
|
||||||
|
m["BConfig.Listen.EnableStdIo"] = BConfig.Listen.EnableStdIo
|
||||||
|
m["BConfig.WebConfig.AutoRender"] = BConfig.WebConfig.AutoRender
|
||||||
|
m["BConfig.WebConfig.EnableDocs"] = BConfig.WebConfig.EnableDocs
|
||||||
|
m["BConfig.WebConfig.FlashName"] = BConfig.WebConfig.FlashName
|
||||||
|
m["BConfig.WebConfig.FlashSeparator"] = BConfig.WebConfig.FlashSeparator
|
||||||
|
m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex
|
||||||
|
m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir
|
||||||
|
m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip
|
||||||
|
m["BConfig.WebConfig.StaticCacheFileSize"] = BConfig.WebConfig.StaticCacheFileSize
|
||||||
|
m["BConfig.WebConfig.StaticCacheFileNum"] = BConfig.WebConfig.StaticCacheFileNum
|
||||||
|
m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft
|
||||||
|
m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight
|
||||||
|
m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath
|
||||||
|
m["BConfig.WebConfig.EnableXSRF"] = BConfig.WebConfig.EnableXSRF
|
||||||
|
m["BConfig.WebConfig.XSRFExpire"] = BConfig.WebConfig.XSRFExpire
|
||||||
|
m["BConfig.WebConfig.Session.SessionOn"] = BConfig.WebConfig.Session.SessionOn
|
||||||
|
m["BConfig.WebConfig.Session.SessionProvider"] = BConfig.WebConfig.Session.SessionProvider
|
||||||
|
m["BConfig.WebConfig.Session.SessionName"] = BConfig.WebConfig.Session.SessionName
|
||||||
|
m["BConfig.WebConfig.Session.SessionGCMaxLifetime"] = BConfig.WebConfig.Session.SessionGCMaxLifetime
|
||||||
|
m["BConfig.WebConfig.Session.SessionProviderConfig"] = BConfig.WebConfig.Session.SessionProviderConfig
|
||||||
|
m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime
|
||||||
|
m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie
|
||||||
|
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
|
||||||
|
m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly
|
||||||
|
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
|
||||||
|
m["BConfig.Log.EnableStaticLogs"] = BConfig.Log.EnableStaticLogs
|
||||||
|
m["BConfig.Log.AccessLogsFormat"] = BConfig.Log.AccessLogsFormat
|
||||||
|
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
|
||||||
|
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteJSON(t *testing.T) {
|
||||||
|
t.Log("Testing the adding of JSON to the response")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
originalBody := []int{1, 2, 3}
|
||||||
|
|
||||||
|
res, _ := json.Marshal(originalBody)
|
||||||
|
|
||||||
|
writeJSON(w, res)
|
||||||
|
|
||||||
|
decodedBody := []int{}
|
||||||
|
err := json.NewDecoder(w.Body).Decode(&decodedBody)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Could not decode response body into slice.")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range decodedBody {
|
||||||
|
if decodedBody[i] != originalBody[i] {
|
||||||
|
t.Fatalf("Expected %d but got %d in decoded body slice", originalBody[i], decodedBody[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHealthCheckHandlerDefault(t *testing.T) {
|
||||||
|
endpointPath := "/healthcheck"
|
||||||
|
|
||||||
|
toolbox.AddHealthCheck("database", &SampleDatabaseCheck{})
|
||||||
|
toolbox.AddHealthCheck("cache", &SampleCacheCheck{})
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", endpointPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler := http.HandlerFunc(healthcheck)
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if status := w.Code; status != http.StatusOK {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||||
|
status, http.StatusOK)
|
||||||
|
}
|
||||||
|
if !strings.Contains(w.Body.String(), "database") {
|
||||||
|
t.Errorf("Expected 'database' in generated template.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildHealthCheckResponseList(t *testing.T) {
|
||||||
|
healthCheckResults := [][]string{
|
||||||
|
[]string{
|
||||||
|
"error",
|
||||||
|
"Database",
|
||||||
|
"Error occured whie starting the db",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"success",
|
||||||
|
"Cache",
|
||||||
|
"Cache started successfully",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
responseList := buildHealthCheckResponseList(&healthCheckResults)
|
||||||
|
|
||||||
|
if len(responseList) != len(healthCheckResults) {
|
||||||
|
t.Errorf("invalid response map length: got %d want %d",
|
||||||
|
len(responseList), len(healthCheckResults))
|
||||||
|
}
|
||||||
|
|
||||||
|
responseFields := []string{"name", "message", "status"}
|
||||||
|
|
||||||
|
for _, response := range responseList {
|
||||||
|
for _, field := range responseFields {
|
||||||
|
_, ok := response[field]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected %s to be in the response %v", field, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHealthCheckHandlerReturnsJSON(t *testing.T) {
|
||||||
|
|
||||||
|
toolbox.AddHealthCheck("database", &SampleDatabaseCheck{})
|
||||||
|
toolbox.AddHealthCheck("cache", &SampleCacheCheck{})
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/healthcheck?json=true", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler := http.HandlerFunc(healthcheck)
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
if status := w.Code; status != http.StatusOK {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||||
|
status, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedResponseBody := []map[string]interface{}{}
|
||||||
|
expectedResponseBody := []map[string]interface{}{}
|
||||||
|
|
||||||
|
expectedJSONString := []byte(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"message":"database",
|
||||||
|
"name":"success",
|
||||||
|
"status":"OK"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message":"cache",
|
||||||
|
"name":"error",
|
||||||
|
"status":"no cache detected"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
json.Unmarshal(expectedJSONString, &expectedResponseBody)
|
||||||
|
|
||||||
|
json.Unmarshal(w.Body.Bytes(), &decodedResponseBody)
|
||||||
|
|
||||||
|
if len(expectedResponseBody) != len(decodedResponseBody) {
|
||||||
|
t.Errorf("invalid response map length: got %d want %d",
|
||||||
|
len(decodedResponseBody), len(expectedResponseBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(decodedResponseBody, expectedResponseBody) {
|
||||||
|
t.Errorf("handler returned unexpected body: got %v want %v",
|
||||||
|
decodedResponseBody, expectedResponseBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
356
pkg/adminui.go
Normal file
356
pkg/adminui.go
Normal file
File diff suppressed because one or more lines are too long
496
pkg/app.go
Normal file
496
pkg/app.go
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
// 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 (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/fcgi"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/grace"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// BeeApp is an application instance
|
||||||
|
BeeApp *App
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// create beego application
|
||||||
|
BeeApp = NewApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
// App defines beego application with a new PatternServeMux.
|
||||||
|
type App struct {
|
||||||
|
Handlers *ControllerRegister
|
||||||
|
Server *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApp returns a new beego application.
|
||||||
|
func NewApp() *App {
|
||||||
|
cr := NewControllerRegister()
|
||||||
|
app := &App{Handlers: cr, Server: &http.Server{}}
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
// MiddleWare function for http.Handler
|
||||||
|
type MiddleWare func(http.Handler) http.Handler
|
||||||
|
|
||||||
|
// Run beego application.
|
||||||
|
func (app *App) Run(mws ...MiddleWare) {
|
||||||
|
addr := BConfig.Listen.HTTPAddr
|
||||||
|
|
||||||
|
if BConfig.Listen.HTTPPort != 0 {
|
||||||
|
addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPAddr, BConfig.Listen.HTTPPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
l net.Listener
|
||||||
|
endRunning = make(chan bool, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
// run cgi server
|
||||||
|
if BConfig.Listen.EnableFcgi {
|
||||||
|
if BConfig.Listen.EnableStdIo {
|
||||||
|
if err = fcgi.Serve(nil, app.Handlers); err == nil { // standard I/O
|
||||||
|
logs.Info("Use FCGI via standard I/O")
|
||||||
|
} else {
|
||||||
|
logs.Critical("Cannot use FCGI via standard I/O", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if BConfig.Listen.HTTPPort == 0 {
|
||||||
|
// remove the Socket file before start
|
||||||
|
if utils.FileExists(addr) {
|
||||||
|
os.Remove(addr)
|
||||||
|
}
|
||||||
|
l, err = net.Listen("unix", addr)
|
||||||
|
} else {
|
||||||
|
l, err = net.Listen("tcp", addr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logs.Critical("Listen: ", err)
|
||||||
|
}
|
||||||
|
if err = fcgi.Serve(l, app.Handlers); err != nil {
|
||||||
|
logs.Critical("fcgi.Serve: ", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Server.Handler = app.Handlers
|
||||||
|
for i := len(mws) - 1; i >= 0; i-- {
|
||||||
|
if mws[i] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
app.Server.Handler = mws[i](app.Server.Handler)
|
||||||
|
}
|
||||||
|
app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
||||||
|
app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
||||||
|
app.Server.ErrorLog = logs.GetLogger("HTTP")
|
||||||
|
|
||||||
|
// run graceful mode
|
||||||
|
if BConfig.Listen.Graceful {
|
||||||
|
httpsAddr := BConfig.Listen.HTTPSAddr
|
||||||
|
app.Server.Addr = httpsAddr
|
||||||
|
if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS {
|
||||||
|
go func() {
|
||||||
|
time.Sleep(1000 * time.Microsecond)
|
||||||
|
if BConfig.Listen.HTTPSPort != 0 {
|
||||||
|
httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
||||||
|
app.Server.Addr = httpsAddr
|
||||||
|
}
|
||||||
|
server := grace.NewServer(httpsAddr, app.Server.Handler)
|
||||||
|
server.Server.ReadTimeout = app.Server.ReadTimeout
|
||||||
|
server.Server.WriteTimeout = app.Server.WriteTimeout
|
||||||
|
if BConfig.Listen.EnableMutualHTTPS {
|
||||||
|
if err := server.ListenAndServeMutualTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile, BConfig.Listen.TrustCaFile); err != nil {
|
||||||
|
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if BConfig.Listen.AutoTLS {
|
||||||
|
m := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...),
|
||||||
|
Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir),
|
||||||
|
}
|
||||||
|
app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate}
|
||||||
|
BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", ""
|
||||||
|
}
|
||||||
|
if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
||||||
|
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endRunning <- true
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if BConfig.Listen.EnableHTTP {
|
||||||
|
go func() {
|
||||||
|
server := grace.NewServer(addr, app.Server.Handler)
|
||||||
|
server.Server.ReadTimeout = app.Server.ReadTimeout
|
||||||
|
server.Server.WriteTimeout = app.Server.WriteTimeout
|
||||||
|
if BConfig.Listen.ListenTCP4 {
|
||||||
|
server.Network = "tcp4"
|
||||||
|
}
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
}
|
||||||
|
endRunning <- true
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
<-endRunning
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// run normal mode
|
||||||
|
if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS {
|
||||||
|
go func() {
|
||||||
|
time.Sleep(1000 * time.Microsecond)
|
||||||
|
if BConfig.Listen.HTTPSPort != 0 {
|
||||||
|
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
||||||
|
} else if BConfig.Listen.EnableHTTP {
|
||||||
|
logs.Info("Start https server error, conflict with http. Please reset https port")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logs.Info("https server Running on https://%s", app.Server.Addr)
|
||||||
|
if BConfig.Listen.AutoTLS {
|
||||||
|
m := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...),
|
||||||
|
Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir),
|
||||||
|
}
|
||||||
|
app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate}
|
||||||
|
BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", ""
|
||||||
|
} else if BConfig.Listen.EnableMutualHTTPS {
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile)
|
||||||
|
if err != nil {
|
||||||
|
logs.Info("MutualHTTPS should provide TrustCaFile")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pool.AppendCertsFromPEM(data)
|
||||||
|
app.Server.TLSConfig = &tls.Config{
|
||||||
|
ClientCAs: pool,
|
||||||
|
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
||||||
|
logs.Critical("ListenAndServeTLS: ", err)
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
if BConfig.Listen.EnableHTTP {
|
||||||
|
go func() {
|
||||||
|
app.Server.Addr = addr
|
||||||
|
logs.Info("http server Running on http://%s", app.Server.Addr)
|
||||||
|
if BConfig.Listen.ListenTCP4 {
|
||||||
|
ln, err := net.Listen("tcp4", app.Server.Addr)
|
||||||
|
if err != nil {
|
||||||
|
logs.Critical("ListenAndServe: ", err)
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = app.Server.Serve(ln); err != nil {
|
||||||
|
logs.Critical("ListenAndServe: ", err)
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := app.Server.ListenAndServe(); err != nil {
|
||||||
|
logs.Critical("ListenAndServe: ", err)
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
<-endRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router adds a patterned controller handler to BeeApp.
|
||||||
|
// it's an alias method of App.Router.
|
||||||
|
// usage:
|
||||||
|
// simple router
|
||||||
|
// beego.Router("/admin", &admin.UserController{})
|
||||||
|
// beego.Router("/admin/index", &admin.ArticleController{})
|
||||||
|
//
|
||||||
|
// regex router
|
||||||
|
//
|
||||||
|
// beego.Router("/api/:id([0-9]+)", &controllers.RController{})
|
||||||
|
//
|
||||||
|
// custom rules
|
||||||
|
// beego.Router("/api/list",&RestController{},"*:ListFood")
|
||||||
|
// beego.Router("/api/create",&RestController{},"post:CreateFood")
|
||||||
|
// beego.Router("/api/update",&RestController{},"put:UpdateFood")
|
||||||
|
// beego.Router("/api/delete",&RestController{},"delete:DeleteFood")
|
||||||
|
func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App {
|
||||||
|
BeeApp.Handlers.Add(rootpath, c, mappingMethods...)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful
|
||||||
|
// in web applications that inherit most routes from a base webapp via the underscore
|
||||||
|
// import, and aim to overwrite only certain paths.
|
||||||
|
// The method parameter can be empty or "*" for all HTTP methods, or a particular
|
||||||
|
// method type (e.g. "GET" or "POST") for selective removal.
|
||||||
|
//
|
||||||
|
// Usage (replace "GET" with "*" for all methods):
|
||||||
|
// beego.UnregisterFixedRoute("/yourpreviouspath", "GET")
|
||||||
|
// beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage")
|
||||||
|
func UnregisterFixedRoute(fixedRoute string, method string) *App {
|
||||||
|
subPaths := splitPath(fixedRoute)
|
||||||
|
if method == "" || method == "*" {
|
||||||
|
for m := range HTTPMETHOD {
|
||||||
|
if _, ok := BeeApp.Handlers.routers[m]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if BeeApp.Handlers.routers[m].prefix == strings.Trim(fixedRoute, "/ ") {
|
||||||
|
findAndRemoveSingleTree(BeeApp.Handlers.routers[m])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
findAndRemoveTree(subPaths, BeeApp.Handlers.routers[m], m)
|
||||||
|
}
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
// Single HTTP method
|
||||||
|
um := strings.ToUpper(method)
|
||||||
|
if _, ok := BeeApp.Handlers.routers[um]; ok {
|
||||||
|
if BeeApp.Handlers.routers[um].prefix == strings.Trim(fixedRoute, "/ ") {
|
||||||
|
findAndRemoveSingleTree(BeeApp.Handlers.routers[um])
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
findAndRemoveTree(subPaths, BeeApp.Handlers.routers[um], um)
|
||||||
|
}
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAndRemoveTree(paths []string, entryPointTree *Tree, method string) {
|
||||||
|
for i := range entryPointTree.fixrouters {
|
||||||
|
if entryPointTree.fixrouters[i].prefix == paths[0] {
|
||||||
|
if len(paths) == 1 {
|
||||||
|
if len(entryPointTree.fixrouters[i].fixrouters) > 0 {
|
||||||
|
// If the route had children subtrees, remove just the functional leaf,
|
||||||
|
// to allow children to function as before
|
||||||
|
if len(entryPointTree.fixrouters[i].leaves) > 0 {
|
||||||
|
entryPointTree.fixrouters[i].leaves[0] = nil
|
||||||
|
entryPointTree.fixrouters[i].leaves = entryPointTree.fixrouters[i].leaves[1:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Remove the *Tree from the fixrouters slice
|
||||||
|
entryPointTree.fixrouters[i] = nil
|
||||||
|
|
||||||
|
if i == len(entryPointTree.fixrouters)-1 {
|
||||||
|
entryPointTree.fixrouters = entryPointTree.fixrouters[:i]
|
||||||
|
} else {
|
||||||
|
entryPointTree.fixrouters = append(entryPointTree.fixrouters[:i], entryPointTree.fixrouters[i+1:len(entryPointTree.fixrouters)]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
findAndRemoveTree(paths[1:], entryPointTree.fixrouters[i], method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAndRemoveSingleTree(entryPointTree *Tree) {
|
||||||
|
if entryPointTree == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(entryPointTree.fixrouters) > 0 {
|
||||||
|
// If the route had children subtrees, remove just the functional leaf,
|
||||||
|
// to allow children to function as before
|
||||||
|
if len(entryPointTree.leaves) > 0 {
|
||||||
|
entryPointTree.leaves[0] = nil
|
||||||
|
entryPointTree.leaves = entryPointTree.leaves[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include will generate router file in the router/xxx.go from the controller's comments
|
||||||
|
// usage:
|
||||||
|
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})
|
||||||
|
// type BankAccount struct{
|
||||||
|
// beego.Controller
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// register the function
|
||||||
|
// func (b *BankAccount)Mapping(){
|
||||||
|
// b.Mapping("ShowAccount" , b.ShowAccount)
|
||||||
|
// b.Mapping("ModifyAccount", b.ModifyAccount)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
// //@router /account/:id [get]
|
||||||
|
// func (b *BankAccount) ShowAccount(){
|
||||||
|
// //logic
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// //@router /account/:id [post]
|
||||||
|
// func (b *BankAccount) ModifyAccount(){
|
||||||
|
// //logic
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// the comments @router url methodlist
|
||||||
|
// url support all the function Router's pattern
|
||||||
|
// methodlist [get post head put delete options *]
|
||||||
|
func Include(cList ...ControllerInterface) *App {
|
||||||
|
BeeApp.Handlers.Include(cList...)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// RESTRouter adds a restful controller handler to BeeApp.
|
||||||
|
// its' controller implements beego.ControllerInterface and
|
||||||
|
// defines a param "pattern/:objectId" to visit each resource.
|
||||||
|
func RESTRouter(rootpath string, c ControllerInterface) *App {
|
||||||
|
Router(rootpath, c)
|
||||||
|
Router(path.Join(rootpath, ":objectId"), c)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoRouter adds defined controller handler to BeeApp.
|
||||||
|
// it's same to App.AutoRouter.
|
||||||
|
// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page,
|
||||||
|
// visit the url /main/list to exec List function or /main/page to exec Page function.
|
||||||
|
func AutoRouter(c ControllerInterface) *App {
|
||||||
|
BeeApp.Handlers.AddAuto(c)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoPrefix adds controller handler to BeeApp with prefix.
|
||||||
|
// it's same to App.AutoRouterWithPrefix.
|
||||||
|
// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page,
|
||||||
|
// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function.
|
||||||
|
func AutoPrefix(prefix string, c ControllerInterface) *App {
|
||||||
|
BeeApp.Handlers.AddAutoPrefix(prefix, c)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get used to register router for Get method
|
||||||
|
// usage:
|
||||||
|
// beego.Get("/", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Get(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Get(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post used to register router for Post method
|
||||||
|
// usage:
|
||||||
|
// beego.Post("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Post(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Post(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete used to register router for Delete method
|
||||||
|
// usage:
|
||||||
|
// beego.Delete("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Delete(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Delete(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put used to register router for Put method
|
||||||
|
// usage:
|
||||||
|
// beego.Put("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Put(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Put(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head used to register router for Head method
|
||||||
|
// usage:
|
||||||
|
// beego.Head("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Head(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Head(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options used to register router for Options method
|
||||||
|
// usage:
|
||||||
|
// beego.Options("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Options(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Options(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch used to register router for Patch method
|
||||||
|
// usage:
|
||||||
|
// beego.Patch("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Patch(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Patch(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any used to register router for all methods
|
||||||
|
// usage:
|
||||||
|
// beego.Any("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Any(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Any(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler used to register a Handler router
|
||||||
|
// usage:
|
||||||
|
// beego.Handler("/api", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
|
||||||
|
// fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
|
||||||
|
// }))
|
||||||
|
func Handler(rootpath string, h http.Handler, options ...interface{}) *App {
|
||||||
|
BeeApp.Handlers.Handler(rootpath, h, options...)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertFilter adds a FilterFunc with pattern condition and action constant.
|
||||||
|
// The pos means action constant including
|
||||||
|
// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter.
|
||||||
|
// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
|
||||||
|
func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App {
|
||||||
|
BeeApp.Handlers.InsertFilter(pattern, pos, filter, params...)
|
||||||
|
return BeeApp
|
||||||
|
}
|
123
pkg/beego.go
Normal file
123
pkg/beego.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// 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 (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// VERSION represent beego web framework version.
|
||||||
|
VERSION = "1.12.2"
|
||||||
|
|
||||||
|
// DEV is for develop
|
||||||
|
DEV = "dev"
|
||||||
|
// PROD is for production
|
||||||
|
PROD = "prod"
|
||||||
|
)
|
||||||
|
|
||||||
|
// M is Map shortcut
|
||||||
|
type M map[string]interface{}
|
||||||
|
|
||||||
|
// Hook function to run
|
||||||
|
type hookfunc func() error
|
||||||
|
|
||||||
|
var (
|
||||||
|
hooks = make([]hookfunc, 0) //hook function slice to store the hookfunc
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddAPPStartHook is used to register the hookfunc
|
||||||
|
// The hookfuncs will run in beego.Run()
|
||||||
|
// such as initiating session , starting middleware , building template, starting admin control and so on.
|
||||||
|
func AddAPPStartHook(hf ...hookfunc) {
|
||||||
|
hooks = append(hooks, hf...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run beego application.
|
||||||
|
// beego.Run() default run on HttpPort
|
||||||
|
// beego.Run("localhost")
|
||||||
|
// beego.Run(":8089")
|
||||||
|
// beego.Run("127.0.0.1:8089")
|
||||||
|
func Run(params ...string) {
|
||||||
|
|
||||||
|
initBeforeHTTPRun()
|
||||||
|
|
||||||
|
if len(params) > 0 && params[0] != "" {
|
||||||
|
strs := strings.Split(params[0], ":")
|
||||||
|
if len(strs) > 0 && strs[0] != "" {
|
||||||
|
BConfig.Listen.HTTPAddr = strs[0]
|
||||||
|
}
|
||||||
|
if len(strs) > 1 && strs[1] != "" {
|
||||||
|
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
BConfig.Listen.Domains = params
|
||||||
|
}
|
||||||
|
|
||||||
|
BeeApp.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithMiddleWares Run beego application with middlewares.
|
||||||
|
func RunWithMiddleWares(addr string, mws ...MiddleWare) {
|
||||||
|
initBeforeHTTPRun()
|
||||||
|
|
||||||
|
strs := strings.Split(addr, ":")
|
||||||
|
if len(strs) > 0 && strs[0] != "" {
|
||||||
|
BConfig.Listen.HTTPAddr = strs[0]
|
||||||
|
BConfig.Listen.Domains = []string{strs[0]}
|
||||||
|
}
|
||||||
|
if len(strs) > 1 && strs[1] != "" {
|
||||||
|
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
BeeApp.Run(mws...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initBeforeHTTPRun() {
|
||||||
|
//init hooks
|
||||||
|
AddAPPStartHook(
|
||||||
|
registerMime,
|
||||||
|
registerDefaultErrorHandler,
|
||||||
|
registerSession,
|
||||||
|
registerTemplate,
|
||||||
|
registerAdmin,
|
||||||
|
registerGzip,
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, hk := range hooks {
|
||||||
|
if err := hk(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBeegoInit is for test package init
|
||||||
|
func TestBeegoInit(ap string) {
|
||||||
|
path := filepath.Join(ap, "conf", "app.conf")
|
||||||
|
os.Chdir(ap)
|
||||||
|
InitBeegoBeforeTest(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitBeegoBeforeTest is for test package init
|
||||||
|
func InitBeegoBeforeTest(appConfigPath string) {
|
||||||
|
if err := LoadAppConfig(appConfigProvider, appConfigPath); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
BConfig.RunMode = "test"
|
||||||
|
initBeforeHTTPRun()
|
||||||
|
}
|
27
pkg/build_info.go
Normal file
27
pkg/build_info.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2020 astaxie
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
var (
|
||||||
|
BuildVersion string
|
||||||
|
BuildGitRevision string
|
||||||
|
BuildStatus string
|
||||||
|
BuildTag string
|
||||||
|
BuildTime string
|
||||||
|
|
||||||
|
GoVersion string
|
||||||
|
|
||||||
|
GitBranch string
|
||||||
|
)
|
59
pkg/cache/README.md
vendored
Normal file
59
pkg/cache/README.md
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
## cache
|
||||||
|
cache is a Go cache manager. It can use many cache adapters. The repo is inspired by `database/sql` .
|
||||||
|
|
||||||
|
|
||||||
|
## How to install?
|
||||||
|
|
||||||
|
go get github.com/astaxie/beego/cache
|
||||||
|
|
||||||
|
|
||||||
|
## What adapters are supported?
|
||||||
|
|
||||||
|
As of now this cache support memory, Memcache and Redis.
|
||||||
|
|
||||||
|
|
||||||
|
## How to use it?
|
||||||
|
|
||||||
|
First you must import it
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
Then init a Cache (example with memory adapter)
|
||||||
|
|
||||||
|
bm, err := cache.NewCache("memory", `{"interval":60}`)
|
||||||
|
|
||||||
|
Use it like this:
|
||||||
|
|
||||||
|
bm.Put("astaxie", 1, 10 * time.Second)
|
||||||
|
bm.Get("astaxie")
|
||||||
|
bm.IsExist("astaxie")
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
|
||||||
|
|
||||||
|
## Memory adapter
|
||||||
|
|
||||||
|
Configure memory adapter like this:
|
||||||
|
|
||||||
|
{"interval":60}
|
||||||
|
|
||||||
|
interval means the gc time. The cache will check at each time interval, whether item has expired.
|
||||||
|
|
||||||
|
|
||||||
|
## Memcache adapter
|
||||||
|
|
||||||
|
Memcache adapter use the [gomemcache](http://github.com/bradfitz/gomemcache) client.
|
||||||
|
|
||||||
|
Configure like this:
|
||||||
|
|
||||||
|
{"conn":"127.0.0.1:11211"}
|
||||||
|
|
||||||
|
|
||||||
|
## Redis adapter
|
||||||
|
|
||||||
|
Redis adapter use the [redigo](http://github.com/gomodule/redigo) client.
|
||||||
|
|
||||||
|
Configure like this:
|
||||||
|
|
||||||
|
{"conn":":6039"}
|
103
pkg/cache/cache.go
vendored
Normal file
103
pkg/cache/cache.go
vendored
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// 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 cache provide a Cache interface and some implement engine
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// import(
|
||||||
|
// "github.com/astaxie/beego/cache"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// bm, err := cache.NewCache("memory", `{"interval":60}`)
|
||||||
|
//
|
||||||
|
// Use it like this:
|
||||||
|
//
|
||||||
|
// bm.Put("astaxie", 1, 10 * time.Second)
|
||||||
|
// bm.Get("astaxie")
|
||||||
|
// bm.IsExist("astaxie")
|
||||||
|
// bm.Delete("astaxie")
|
||||||
|
//
|
||||||
|
// more docs http://beego.me/docs/module/cache.md
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache interface contains all behaviors for cache adapter.
|
||||||
|
// usage:
|
||||||
|
// cache.Register("file",cache.NewFileCache) // this operation is run in init method of file.go.
|
||||||
|
// c,err := cache.NewCache("file","{....}")
|
||||||
|
// c.Put("key",value, 3600 * time.Second)
|
||||||
|
// v := c.Get("key")
|
||||||
|
//
|
||||||
|
// c.Incr("counter") // now is 1
|
||||||
|
// c.Incr("counter") // now is 2
|
||||||
|
// count := c.Get("counter").(int)
|
||||||
|
type Cache interface {
|
||||||
|
// get cached value by key.
|
||||||
|
Get(key string) interface{}
|
||||||
|
// GetMulti is a batch version of Get.
|
||||||
|
GetMulti(keys []string) []interface{}
|
||||||
|
// set cached value with key and expire time.
|
||||||
|
Put(key string, val interface{}, timeout time.Duration) error
|
||||||
|
// delete cached value by key.
|
||||||
|
Delete(key string) error
|
||||||
|
// increase cached int value by key, as a counter.
|
||||||
|
Incr(key string) error
|
||||||
|
// decrease cached int value by key, as a counter.
|
||||||
|
Decr(key string) error
|
||||||
|
// check if cached value exists or not.
|
||||||
|
IsExist(key string) bool
|
||||||
|
// clear all cache.
|
||||||
|
ClearAll() error
|
||||||
|
// start gc routine based on config string settings.
|
||||||
|
StartAndGC(config string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance is a function create a new Cache Instance
|
||||||
|
type Instance func() Cache
|
||||||
|
|
||||||
|
var adapters = make(map[string]Instance)
|
||||||
|
|
||||||
|
// Register makes a cache adapter available by the adapter name.
|
||||||
|
// If Register is called twice with the same name or if driver is nil,
|
||||||
|
// it panics.
|
||||||
|
func Register(name string, adapter Instance) {
|
||||||
|
if adapter == nil {
|
||||||
|
panic("cache: Register adapter is nil")
|
||||||
|
}
|
||||||
|
if _, ok := adapters[name]; ok {
|
||||||
|
panic("cache: Register called twice for adapter " + name)
|
||||||
|
}
|
||||||
|
adapters[name] = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCache Create a new cache driver by adapter name and config string.
|
||||||
|
// config need to be correct JSON as string: {"interval":360}.
|
||||||
|
// it will start gc automatically.
|
||||||
|
func NewCache(adapterName, config string) (adapter Cache, err error) {
|
||||||
|
instanceFunc, ok := adapters[adapterName]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adapter = instanceFunc()
|
||||||
|
err = adapter.StartAndGC(config)
|
||||||
|
if err != nil {
|
||||||
|
adapter = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
191
pkg/cache/cache_test.go
vendored
Normal file
191
pkg/cache/cache_test.go
vendored
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// 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 cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCacheIncr(t *testing.T) {
|
||||||
|
bm, err := NewCache("memory", `{"interval":20}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
//timeoutDuration := 10 * time.Second
|
||||||
|
|
||||||
|
bm.Put("edwardhey", 0, time.Second*20)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(10)
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
bm.Incr("edwardhey")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
if bm.Get("edwardhey").(int) != 10 {
|
||||||
|
t.Error("Incr err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCache(t *testing.T) {
|
||||||
|
bm, err := NewCache("memory", `{"interval":20}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Incr("astaxie"); err != nil {
|
||||||
|
t.Error("Incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 2 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Decr("astaxie"); err != nil {
|
||||||
|
t.Error("Decr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if v := bm.Get("astaxie"); v.(string) != "author" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[0].(string) != "author" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[1].(string) != "author1" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileCache(t *testing.T) {
|
||||||
|
bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Incr("astaxie"); err != nil {
|
||||||
|
t.Error("Incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 2 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Decr("astaxie"); err != nil {
|
||||||
|
t.Error("Decr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test string
|
||||||
|
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if v := bm.Get("astaxie"); v.(string) != "author" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[0].(string) != "author" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[1].(string) != "author1" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
os.RemoveAll("cache")
|
||||||
|
}
|
100
pkg/cache/conv.go
vendored
Normal file
100
pkg/cache/conv.go
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// 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 cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetString convert interface to string.
|
||||||
|
func GetString(v interface{}) string {
|
||||||
|
switch result := v.(type) {
|
||||||
|
case string:
|
||||||
|
return result
|
||||||
|
case []byte:
|
||||||
|
return string(result)
|
||||||
|
default:
|
||||||
|
if v != nil {
|
||||||
|
return fmt.Sprint(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt convert interface to int.
|
||||||
|
func GetInt(v interface{}) int {
|
||||||
|
switch result := v.(type) {
|
||||||
|
case int:
|
||||||
|
return result
|
||||||
|
case int32:
|
||||||
|
return int(result)
|
||||||
|
case int64:
|
||||||
|
return int(result)
|
||||||
|
default:
|
||||||
|
if d := GetString(v); d != "" {
|
||||||
|
value, _ := strconv.Atoi(d)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt64 convert interface to int64.
|
||||||
|
func GetInt64(v interface{}) int64 {
|
||||||
|
switch result := v.(type) {
|
||||||
|
case int:
|
||||||
|
return int64(result)
|
||||||
|
case int32:
|
||||||
|
return int64(result)
|
||||||
|
case int64:
|
||||||
|
return result
|
||||||
|
default:
|
||||||
|
|
||||||
|
if d := GetString(v); d != "" {
|
||||||
|
value, _ := strconv.ParseInt(d, 10, 64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat64 convert interface to float64.
|
||||||
|
func GetFloat64(v interface{}) float64 {
|
||||||
|
switch result := v.(type) {
|
||||||
|
case float64:
|
||||||
|
return result
|
||||||
|
default:
|
||||||
|
if d := GetString(v); d != "" {
|
||||||
|
value, _ := strconv.ParseFloat(d, 64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool convert interface to bool.
|
||||||
|
func GetBool(v interface{}) bool {
|
||||||
|
switch result := v.(type) {
|
||||||
|
case bool:
|
||||||
|
return result
|
||||||
|
default:
|
||||||
|
if d := GetString(v); d != "" {
|
||||||
|
value, _ := strconv.ParseBool(d)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
143
pkg/cache/conv_test.go
vendored
Normal file
143
pkg/cache/conv_test.go
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
// 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 cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetString(t *testing.T) {
|
||||||
|
var t1 = "test1"
|
||||||
|
if "test1" != GetString(t1) {
|
||||||
|
t.Error("get string from string error")
|
||||||
|
}
|
||||||
|
var t2 = []byte("test2")
|
||||||
|
if "test2" != GetString(t2) {
|
||||||
|
t.Error("get string from byte array error")
|
||||||
|
}
|
||||||
|
var t3 = 1
|
||||||
|
if "1" != GetString(t3) {
|
||||||
|
t.Error("get string from int error")
|
||||||
|
}
|
||||||
|
var t4 int64 = 1
|
||||||
|
if "1" != GetString(t4) {
|
||||||
|
t.Error("get string from int64 error")
|
||||||
|
}
|
||||||
|
var t5 = 1.1
|
||||||
|
if "1.1" != GetString(t5) {
|
||||||
|
t.Error("get string from float64 error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if "" != GetString(nil) {
|
||||||
|
t.Error("get string from nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInt(t *testing.T) {
|
||||||
|
var t1 = 1
|
||||||
|
if 1 != GetInt(t1) {
|
||||||
|
t.Error("get int from int error")
|
||||||
|
}
|
||||||
|
var t2 int32 = 32
|
||||||
|
if 32 != GetInt(t2) {
|
||||||
|
t.Error("get int from int32 error")
|
||||||
|
}
|
||||||
|
var t3 int64 = 64
|
||||||
|
if 64 != GetInt(t3) {
|
||||||
|
t.Error("get int from int64 error")
|
||||||
|
}
|
||||||
|
var t4 = "128"
|
||||||
|
if 128 != GetInt(t4) {
|
||||||
|
t.Error("get int from num string error")
|
||||||
|
}
|
||||||
|
if 0 != GetInt(nil) {
|
||||||
|
t.Error("get int from nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInt64(t *testing.T) {
|
||||||
|
var i int64 = 1
|
||||||
|
var t1 = 1
|
||||||
|
if i != GetInt64(t1) {
|
||||||
|
t.Error("get int64 from int error")
|
||||||
|
}
|
||||||
|
var t2 int32 = 1
|
||||||
|
if i != GetInt64(t2) {
|
||||||
|
t.Error("get int64 from int32 error")
|
||||||
|
}
|
||||||
|
var t3 int64 = 1
|
||||||
|
if i != GetInt64(t3) {
|
||||||
|
t.Error("get int64 from int64 error")
|
||||||
|
}
|
||||||
|
var t4 = "1"
|
||||||
|
if i != GetInt64(t4) {
|
||||||
|
t.Error("get int64 from num string error")
|
||||||
|
}
|
||||||
|
if 0 != GetInt64(nil) {
|
||||||
|
t.Error("get int64 from nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFloat64(t *testing.T) {
|
||||||
|
var f = 1.11
|
||||||
|
var t1 float32 = 1.11
|
||||||
|
if f != GetFloat64(t1) {
|
||||||
|
t.Error("get float64 from float32 error")
|
||||||
|
}
|
||||||
|
var t2 = 1.11
|
||||||
|
if f != GetFloat64(t2) {
|
||||||
|
t.Error("get float64 from float64 error")
|
||||||
|
}
|
||||||
|
var t3 = "1.11"
|
||||||
|
if f != GetFloat64(t3) {
|
||||||
|
t.Error("get float64 from string error")
|
||||||
|
}
|
||||||
|
|
||||||
|
var f2 float64 = 1
|
||||||
|
var t4 = 1
|
||||||
|
if f2 != GetFloat64(t4) {
|
||||||
|
t.Error("get float64 from int error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 != GetFloat64(nil) {
|
||||||
|
t.Error("get float64 from nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBool(t *testing.T) {
|
||||||
|
var t1 = true
|
||||||
|
if !GetBool(t1) {
|
||||||
|
t.Error("get bool from bool error")
|
||||||
|
}
|
||||||
|
var t2 = "true"
|
||||||
|
if !GetBool(t2) {
|
||||||
|
t.Error("get bool from string error")
|
||||||
|
}
|
||||||
|
if GetBool(nil) {
|
||||||
|
t.Error("get bool from nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func byteArrayEquals(a []byte, b []byte) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range a {
|
||||||
|
if v != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
258
pkg/cache/file.go
vendored
Normal file
258
pkg/cache/file.go
vendored
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
// 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 cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileCacheItem is basic unit of file cache adapter.
|
||||||
|
// it contains data and expire time.
|
||||||
|
type FileCacheItem struct {
|
||||||
|
Data interface{}
|
||||||
|
Lastaccess time.Time
|
||||||
|
Expired time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileCache Config
|
||||||
|
var (
|
||||||
|
FileCachePath = "cache" // cache directory
|
||||||
|
FileCacheFileSuffix = ".bin" // cache file suffix
|
||||||
|
FileCacheDirectoryLevel = 2 // cache file deep level if auto generated cache files.
|
||||||
|
FileCacheEmbedExpiry time.Duration // cache expire time, default is no expire forever.
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileCache is cache adapter for file storage.
|
||||||
|
type FileCache struct {
|
||||||
|
CachePath string
|
||||||
|
FileSuffix string
|
||||||
|
DirectoryLevel int
|
||||||
|
EmbedExpiry int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileCache Create new file cache with no config.
|
||||||
|
// the level and expiry need set in method StartAndGC as config string.
|
||||||
|
func NewFileCache() Cache {
|
||||||
|
// return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix}
|
||||||
|
return &FileCache{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC will start and begin gc for file cache.
|
||||||
|
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}
|
||||||
|
func (fc *FileCache) StartAndGC(config string) error {
|
||||||
|
|
||||||
|
cfg := make(map[string]string)
|
||||||
|
err := json.Unmarshal([]byte(config), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, ok := cfg["CachePath"]; !ok {
|
||||||
|
cfg["CachePath"] = FileCachePath
|
||||||
|
}
|
||||||
|
if _, ok := cfg["FileSuffix"]; !ok {
|
||||||
|
cfg["FileSuffix"] = FileCacheFileSuffix
|
||||||
|
}
|
||||||
|
if _, ok := cfg["DirectoryLevel"]; !ok {
|
||||||
|
cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
|
||||||
|
}
|
||||||
|
if _, ok := cfg["EmbedExpiry"]; !ok {
|
||||||
|
cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
|
||||||
|
}
|
||||||
|
fc.CachePath = cfg["CachePath"]
|
||||||
|
fc.FileSuffix = cfg["FileSuffix"]
|
||||||
|
fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
|
||||||
|
fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])
|
||||||
|
|
||||||
|
fc.Init()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init will make new dir for file cache if not exist.
|
||||||
|
func (fc *FileCache) Init() {
|
||||||
|
if ok, _ := exists(fc.CachePath); !ok { // todo : error handle
|
||||||
|
_ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get cached file name. it's md5 encoded.
|
||||||
|
func (fc *FileCache) getCacheFileName(key string) string {
|
||||||
|
m := md5.New()
|
||||||
|
io.WriteString(m, key)
|
||||||
|
keyMd5 := hex.EncodeToString(m.Sum(nil))
|
||||||
|
cachePath := fc.CachePath
|
||||||
|
switch fc.DirectoryLevel {
|
||||||
|
case 2:
|
||||||
|
cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4])
|
||||||
|
case 1:
|
||||||
|
cachePath = filepath.Join(cachePath, keyMd5[0:2])
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, _ := exists(cachePath); !ok { // todo : error handle
|
||||||
|
_ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get value from file cache.
|
||||||
|
// if non-exist or expired, return empty string.
|
||||||
|
func (fc *FileCache) Get(key string) interface{} {
|
||||||
|
fileData, err := FileGetContents(fc.getCacheFileName(key))
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var to FileCacheItem
|
||||||
|
GobDecode(fileData, &to)
|
||||||
|
if to.Expired.Before(time.Now()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return to.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti gets values from file cache.
|
||||||
|
// if non-exist or expired, return empty string.
|
||||||
|
func (fc *FileCache) GetMulti(keys []string) []interface{} {
|
||||||
|
var rc []interface{}
|
||||||
|
for _, key := range keys {
|
||||||
|
rc = append(rc, fc.Get(key))
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put value into file cache.
|
||||||
|
// timeout means how long to keep this file, unit of ms.
|
||||||
|
// if timeout equals fc.EmbedExpiry(default is 0), cache this item forever.
|
||||||
|
func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||||
|
gob.Register(val)
|
||||||
|
|
||||||
|
item := FileCacheItem{Data: val}
|
||||||
|
if timeout == time.Duration(fc.EmbedExpiry) {
|
||||||
|
item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
|
||||||
|
} else {
|
||||||
|
item.Expired = time.Now().Add(timeout)
|
||||||
|
}
|
||||||
|
item.Lastaccess = time.Now()
|
||||||
|
data, err := GobEncode(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return FilePutContents(fc.getCacheFileName(key), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete file cache value.
|
||||||
|
func (fc *FileCache) Delete(key string) error {
|
||||||
|
filename := fc.getCacheFileName(key)
|
||||||
|
if ok, _ := exists(filename); ok {
|
||||||
|
return os.Remove(filename)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr will increase cached int value.
|
||||||
|
// fc value is saving forever unless Delete.
|
||||||
|
func (fc *FileCache) Incr(key string) error {
|
||||||
|
data := fc.Get(key)
|
||||||
|
var incr int
|
||||||
|
if reflect.TypeOf(data).Name() != "int" {
|
||||||
|
incr = 0
|
||||||
|
} else {
|
||||||
|
incr = data.(int) + 1
|
||||||
|
}
|
||||||
|
fc.Put(key, incr, time.Duration(fc.EmbedExpiry))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr will decrease cached int value.
|
||||||
|
func (fc *FileCache) Decr(key string) error {
|
||||||
|
data := fc.Get(key)
|
||||||
|
var decr int
|
||||||
|
if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 {
|
||||||
|
decr = 0
|
||||||
|
} else {
|
||||||
|
decr = data.(int) - 1
|
||||||
|
}
|
||||||
|
fc.Put(key, decr, time.Duration(fc.EmbedExpiry))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist check value is exist.
|
||||||
|
func (fc *FileCache) IsExist(key string) bool {
|
||||||
|
ret, _ := exists(fc.getCacheFileName(key))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAll will clean cached files.
|
||||||
|
// not implemented.
|
||||||
|
func (fc *FileCache) ClearAll() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check file exist.
|
||||||
|
func exists(path string) (bool, error) {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileGetContents Get bytes to file.
|
||||||
|
// if non-exist, create this file.
|
||||||
|
func FileGetContents(filename string) (data []byte, e error) {
|
||||||
|
return ioutil.ReadFile(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilePutContents Put bytes to file.
|
||||||
|
// if non-exist, create this file.
|
||||||
|
func FilePutContents(filename string, content []byte) error {
|
||||||
|
return ioutil.WriteFile(filename, content, os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobEncode Gob encodes file cache item.
|
||||||
|
func GobEncode(data interface{}) ([]byte, error) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
enc := gob.NewEncoder(buf)
|
||||||
|
err := enc.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobDecode Gob decodes file cache item.
|
||||||
|
func GobDecode(data []byte, to *FileCacheItem) error {
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
dec := gob.NewDecoder(buf)
|
||||||
|
return dec.Decode(&to)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register("file", NewFileCache)
|
||||||
|
}
|
188
pkg/cache/memcache/memcache.go
vendored
Normal file
188
pkg/cache/memcache/memcache.go
vendored
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// 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 memcache for cache provider
|
||||||
|
//
|
||||||
|
// depend on github.com/bradfitz/gomemcache/memcache
|
||||||
|
//
|
||||||
|
// go install github.com/bradfitz/gomemcache/memcache
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// import(
|
||||||
|
// _ "github.com/astaxie/beego/cache/memcache"
|
||||||
|
// "github.com/astaxie/beego/cache"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// bm, err := cache.NewCache("memcache", `{"conn":"127.0.0.1:11211"}`)
|
||||||
|
//
|
||||||
|
// more docs http://beego.me/docs/module/cache.md
|
||||||
|
package memcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
"github.com/bradfitz/gomemcache/memcache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache Memcache adapter.
|
||||||
|
type Cache struct {
|
||||||
|
conn *memcache.Client
|
||||||
|
conninfo []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMemCache create new memcache adapter.
|
||||||
|
func NewMemCache() cache.Cache {
|
||||||
|
return &Cache{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get get value from memcache.
|
||||||
|
func (rc *Cache) Get(key string) interface{} {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if item, err := rc.conn.Get(key); err == nil {
|
||||||
|
return item.Value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti get value from memcache.
|
||||||
|
func (rc *Cache) GetMulti(keys []string) []interface{} {
|
||||||
|
size := len(keys)
|
||||||
|
var rv []interface{}
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
rv = append(rv, err)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mv, err := rc.conn.GetMulti(keys)
|
||||||
|
if err == nil {
|
||||||
|
for _, v := range mv {
|
||||||
|
rv = append(rv, v.Value)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
rv = append(rv, err)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put put value to memcache.
|
||||||
|
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)}
|
||||||
|
if v, ok := val.([]byte); ok {
|
||||||
|
item.Value = v
|
||||||
|
} else if str, ok := val.(string); ok {
|
||||||
|
item.Value = []byte(str)
|
||||||
|
} else {
|
||||||
|
return errors.New("val only support string and []byte")
|
||||||
|
}
|
||||||
|
return rc.conn.Set(&item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete delete value in memcache.
|
||||||
|
func (rc *Cache) Delete(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc.conn.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr increase counter.
|
||||||
|
func (rc *Cache) Incr(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Increment(key, 1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr decrease counter.
|
||||||
|
func (rc *Cache) Decr(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Decrement(key, 1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist check value exists in memcache.
|
||||||
|
func (rc *Cache) IsExist(key string) bool {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Get(key)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAll clear all cached in memcache.
|
||||||
|
func (rc *Cache) ClearAll() error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc.conn.FlushAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC start memcache adapter.
|
||||||
|
// config string is like {"conn":"connection info"}.
|
||||||
|
// if connecting error, return.
|
||||||
|
func (rc *Cache) StartAndGC(config string) error {
|
||||||
|
var cf map[string]string
|
||||||
|
json.Unmarshal([]byte(config), &cf)
|
||||||
|
if _, ok := cf["conn"]; !ok {
|
||||||
|
return errors.New("config has no conn key")
|
||||||
|
}
|
||||||
|
rc.conninfo = strings.Split(cf["conn"], ";")
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect to memcache and keep the connection.
|
||||||
|
func (rc *Cache) connectInit() error {
|
||||||
|
rc.conn = memcache.New(rc.conninfo...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cache.Register("memcache", NewMemCache)
|
||||||
|
}
|
108
pkg/cache/memcache/memcache_test.go
vendored
Normal file
108
pkg/cache/memcache/memcache_test.go
vendored
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// 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 memcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/bradfitz/gomemcache/memcache"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMemcacheCache(t *testing.T) {
|
||||||
|
bm, err := cache.NewCache("memcache", `{"conn": "127.0.0.1:11211"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
if err = bm.Put("astaxie", "1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(11 * time.Second)
|
||||||
|
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if err = bm.Put("astaxie", "1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Incr("astaxie"); err != nil {
|
||||||
|
t.Error("Incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 2 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Decr("astaxie"); err != nil {
|
||||||
|
t.Error("Decr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test string
|
||||||
|
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie").([]byte); string(v) != "author" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if string(vv[0].([]byte)) != "author" && string(vv[0].([]byte)) != "author1" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if string(vv[1].([]byte)) != "author1" && string(vv[1].([]byte)) != "author" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test clear all
|
||||||
|
if err = bm.ClearAll(); err != nil {
|
||||||
|
t.Error("clear all err")
|
||||||
|
}
|
||||||
|
}
|
256
pkg/cache/memory.go
vendored
Normal file
256
pkg/cache/memory.go
vendored
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
// 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 cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultEvery means the clock time of recycling the expired cache items in memory.
|
||||||
|
DefaultEvery = 60 // 1 minute
|
||||||
|
)
|
||||||
|
|
||||||
|
// MemoryItem store memory cache item.
|
||||||
|
type MemoryItem struct {
|
||||||
|
val interface{}
|
||||||
|
createdTime time.Time
|
||||||
|
lifespan time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mi *MemoryItem) isExpire() bool {
|
||||||
|
// 0 means forever
|
||||||
|
if mi.lifespan == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return time.Now().Sub(mi.createdTime) > mi.lifespan
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryCache is Memory cache adapter.
|
||||||
|
// it contains a RW locker for safe map storage.
|
||||||
|
type MemoryCache struct {
|
||||||
|
sync.RWMutex
|
||||||
|
dur time.Duration
|
||||||
|
items map[string]*MemoryItem
|
||||||
|
Every int // run an expiration check Every clock time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMemoryCache returns a new MemoryCache.
|
||||||
|
func NewMemoryCache() Cache {
|
||||||
|
cache := MemoryCache{items: make(map[string]*MemoryItem)}
|
||||||
|
return &cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get cache from memory.
|
||||||
|
// if non-existed or expired, return nil.
|
||||||
|
func (bc *MemoryCache) Get(name string) interface{} {
|
||||||
|
bc.RLock()
|
||||||
|
defer bc.RUnlock()
|
||||||
|
if itm, ok := bc.items[name]; ok {
|
||||||
|
if itm.isExpire() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return itm.val
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti gets caches from memory.
|
||||||
|
// if non-existed or expired, return nil.
|
||||||
|
func (bc *MemoryCache) GetMulti(names []string) []interface{} {
|
||||||
|
var rc []interface{}
|
||||||
|
for _, name := range names {
|
||||||
|
rc = append(rc, bc.Get(name))
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put cache to memory.
|
||||||
|
// if lifespan is 0, it will be forever till restart.
|
||||||
|
func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error {
|
||||||
|
bc.Lock()
|
||||||
|
defer bc.Unlock()
|
||||||
|
bc.items[name] = &MemoryItem{
|
||||||
|
val: value,
|
||||||
|
createdTime: time.Now(),
|
||||||
|
lifespan: lifespan,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete cache in memory.
|
||||||
|
func (bc *MemoryCache) Delete(name string) error {
|
||||||
|
bc.Lock()
|
||||||
|
defer bc.Unlock()
|
||||||
|
if _, ok := bc.items[name]; !ok {
|
||||||
|
return errors.New("key not exist")
|
||||||
|
}
|
||||||
|
delete(bc.items, name)
|
||||||
|
if _, ok := bc.items[name]; ok {
|
||||||
|
return errors.New("delete key error")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr increase cache counter in memory.
|
||||||
|
// it supports int,int32,int64,uint,uint32,uint64.
|
||||||
|
func (bc *MemoryCache) Incr(key string) error {
|
||||||
|
bc.Lock()
|
||||||
|
defer bc.Unlock()
|
||||||
|
itm, ok := bc.items[key]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("key not exist")
|
||||||
|
}
|
||||||
|
switch val := itm.val.(type) {
|
||||||
|
case int:
|
||||||
|
itm.val = val + 1
|
||||||
|
case int32:
|
||||||
|
itm.val = val + 1
|
||||||
|
case int64:
|
||||||
|
itm.val = val + 1
|
||||||
|
case uint:
|
||||||
|
itm.val = val + 1
|
||||||
|
case uint32:
|
||||||
|
itm.val = val + 1
|
||||||
|
case uint64:
|
||||||
|
itm.val = val + 1
|
||||||
|
default:
|
||||||
|
return errors.New("item val is not (u)int (u)int32 (u)int64")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr decrease counter in memory.
|
||||||
|
func (bc *MemoryCache) Decr(key string) error {
|
||||||
|
bc.Lock()
|
||||||
|
defer bc.Unlock()
|
||||||
|
itm, ok := bc.items[key]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("key not exist")
|
||||||
|
}
|
||||||
|
switch val := itm.val.(type) {
|
||||||
|
case int:
|
||||||
|
itm.val = val - 1
|
||||||
|
case int64:
|
||||||
|
itm.val = val - 1
|
||||||
|
case int32:
|
||||||
|
itm.val = val - 1
|
||||||
|
case uint:
|
||||||
|
if val > 0 {
|
||||||
|
itm.val = val - 1
|
||||||
|
} else {
|
||||||
|
return errors.New("item val is less than 0")
|
||||||
|
}
|
||||||
|
case uint32:
|
||||||
|
if val > 0 {
|
||||||
|
itm.val = val - 1
|
||||||
|
} else {
|
||||||
|
return errors.New("item val is less than 0")
|
||||||
|
}
|
||||||
|
case uint64:
|
||||||
|
if val > 0 {
|
||||||
|
itm.val = val - 1
|
||||||
|
} else {
|
||||||
|
return errors.New("item val is less than 0")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("item val is not int int64 int32")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist check cache exist in memory.
|
||||||
|
func (bc *MemoryCache) IsExist(name string) bool {
|
||||||
|
bc.RLock()
|
||||||
|
defer bc.RUnlock()
|
||||||
|
if v, ok := bc.items[name]; ok {
|
||||||
|
return !v.isExpire()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAll will delete all cache in memory.
|
||||||
|
func (bc *MemoryCache) ClearAll() error {
|
||||||
|
bc.Lock()
|
||||||
|
defer bc.Unlock()
|
||||||
|
bc.items = make(map[string]*MemoryItem)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC start memory cache. it will check expiration in every clock time.
|
||||||
|
func (bc *MemoryCache) StartAndGC(config string) error {
|
||||||
|
var cf map[string]int
|
||||||
|
json.Unmarshal([]byte(config), &cf)
|
||||||
|
if _, ok := cf["interval"]; !ok {
|
||||||
|
cf = make(map[string]int)
|
||||||
|
cf["interval"] = DefaultEvery
|
||||||
|
}
|
||||||
|
dur := time.Duration(cf["interval"]) * time.Second
|
||||||
|
bc.Every = cf["interval"]
|
||||||
|
bc.dur = dur
|
||||||
|
go bc.vacuum()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check expiration.
|
||||||
|
func (bc *MemoryCache) vacuum() {
|
||||||
|
bc.RLock()
|
||||||
|
every := bc.Every
|
||||||
|
bc.RUnlock()
|
||||||
|
|
||||||
|
if every < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
<-time.After(bc.dur)
|
||||||
|
bc.RLock()
|
||||||
|
if bc.items == nil {
|
||||||
|
bc.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bc.RUnlock()
|
||||||
|
if keys := bc.expiredKeys(); len(keys) != 0 {
|
||||||
|
bc.clearItems(keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expiredKeys returns key list which are expired.
|
||||||
|
func (bc *MemoryCache) expiredKeys() (keys []string) {
|
||||||
|
bc.RLock()
|
||||||
|
defer bc.RUnlock()
|
||||||
|
for key, itm := range bc.items {
|
||||||
|
if itm.isExpire() {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearItems removes all the items which key in keys.
|
||||||
|
func (bc *MemoryCache) clearItems(keys []string) {
|
||||||
|
bc.Lock()
|
||||||
|
defer bc.Unlock()
|
||||||
|
for _, key := range keys {
|
||||||
|
delete(bc.items, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register("memory", NewMemoryCache)
|
||||||
|
}
|
272
pkg/cache/redis/redis.go
vendored
Normal file
272
pkg/cache/redis/redis.go
vendored
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
// 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 redis for cache provider
|
||||||
|
//
|
||||||
|
// depend on github.com/gomodule/redigo/redis
|
||||||
|
//
|
||||||
|
// go install github.com/gomodule/redigo/redis
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// import(
|
||||||
|
// _ "github.com/astaxie/beego/cache/redis"
|
||||||
|
// "github.com/astaxie/beego/cache"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// bm, err := cache.NewCache("redis", `{"conn":"127.0.0.1:11211"}`)
|
||||||
|
//
|
||||||
|
// more docs http://beego.me/docs/module/cache.md
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gomodule/redigo/redis"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultKey the collection name of redis for cache adapter.
|
||||||
|
DefaultKey = "beecacheRedis"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache is Redis cache adapter.
|
||||||
|
type Cache struct {
|
||||||
|
p *redis.Pool // redis connection pool
|
||||||
|
conninfo string
|
||||||
|
dbNum int
|
||||||
|
key string
|
||||||
|
password string
|
||||||
|
maxIdle int
|
||||||
|
|
||||||
|
//the timeout to a value less than the redis server's timeout.
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRedisCache create new redis cache with default collection name.
|
||||||
|
func NewRedisCache() cache.Cache {
|
||||||
|
return &Cache{key: DefaultKey}
|
||||||
|
}
|
||||||
|
|
||||||
|
// actually do the redis cmds, args[0] must be the key name.
|
||||||
|
func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return nil, errors.New("missing required arguments")
|
||||||
|
}
|
||||||
|
args[0] = rc.associate(args[0])
|
||||||
|
c := rc.p.Get()
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
return c.Do(commandName, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// associate with config key.
|
||||||
|
func (rc *Cache) associate(originKey interface{}) string {
|
||||||
|
return fmt.Sprintf("%s:%s", rc.key, originKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get cache from redis.
|
||||||
|
func (rc *Cache) Get(key string) interface{} {
|
||||||
|
if v, err := rc.do("GET", key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti get cache from redis.
|
||||||
|
func (rc *Cache) GetMulti(keys []string) []interface{} {
|
||||||
|
c := rc.p.Get()
|
||||||
|
defer c.Close()
|
||||||
|
var args []interface{}
|
||||||
|
for _, key := range keys {
|
||||||
|
args = append(args, rc.associate(key))
|
||||||
|
}
|
||||||
|
values, err := redis.Values(c.Do("MGET", args...))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put put cache to redis.
|
||||||
|
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||||
|
_, err := rc.do("SETEX", key, int64(timeout/time.Second), val)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete delete cache in redis.
|
||||||
|
func (rc *Cache) Delete(key string) error {
|
||||||
|
_, err := rc.do("DEL", key)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist check cache's existence in redis.
|
||||||
|
func (rc *Cache) IsExist(key string) bool {
|
||||||
|
v, err := redis.Bool(rc.do("EXISTS", key))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr increase counter in redis.
|
||||||
|
func (rc *Cache) Incr(key string) error {
|
||||||
|
_, err := redis.Bool(rc.do("INCRBY", key, 1))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr decrease counter in redis.
|
||||||
|
func (rc *Cache) Decr(key string) error {
|
||||||
|
_, err := redis.Bool(rc.do("INCRBY", key, -1))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAll clean all cache in redis. delete this redis collection.
|
||||||
|
func (rc *Cache) ClearAll() error {
|
||||||
|
cachedKeys, err := rc.Scan(rc.key + ":*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c := rc.p.Get()
|
||||||
|
defer c.Close()
|
||||||
|
for _, str := range cachedKeys {
|
||||||
|
if _, err = c.Do("DEL", str); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan scan all keys matching the pattern. a better choice than `keys`
|
||||||
|
func (rc *Cache) Scan(pattern string) (keys []string, err error) {
|
||||||
|
c := rc.p.Get()
|
||||||
|
defer c.Close()
|
||||||
|
var (
|
||||||
|
cursor uint64 = 0 // start
|
||||||
|
result []interface{}
|
||||||
|
list []string
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
result, err = redis.Values(c.Do("SCAN", cursor, "MATCH", pattern, "COUNT", 1024))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, err = redis.Strings(result[1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keys = append(keys, list...)
|
||||||
|
cursor, err = redis.Uint64(result[0], nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cursor == 0 { // over
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC start redis cache adapter.
|
||||||
|
// config is like {"key":"collection key","conn":"connection info","dbNum":"0"}
|
||||||
|
// the cache item in redis are stored forever,
|
||||||
|
// so no gc operation.
|
||||||
|
func (rc *Cache) StartAndGC(config string) error {
|
||||||
|
var cf map[string]string
|
||||||
|
json.Unmarshal([]byte(config), &cf)
|
||||||
|
|
||||||
|
if _, ok := cf["key"]; !ok {
|
||||||
|
cf["key"] = DefaultKey
|
||||||
|
}
|
||||||
|
if _, ok := cf["conn"]; !ok {
|
||||||
|
return errors.New("config has no conn key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format redis://<password>@<host>:<port>
|
||||||
|
cf["conn"] = strings.Replace(cf["conn"], "redis://", "", 1)
|
||||||
|
if i := strings.Index(cf["conn"], "@"); i > -1 {
|
||||||
|
cf["password"] = cf["conn"][0:i]
|
||||||
|
cf["conn"] = cf["conn"][i+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := cf["dbNum"]; !ok {
|
||||||
|
cf["dbNum"] = "0"
|
||||||
|
}
|
||||||
|
if _, ok := cf["password"]; !ok {
|
||||||
|
cf["password"] = ""
|
||||||
|
}
|
||||||
|
if _, ok := cf["maxIdle"]; !ok {
|
||||||
|
cf["maxIdle"] = "3"
|
||||||
|
}
|
||||||
|
if _, ok := cf["timeout"]; !ok {
|
||||||
|
cf["timeout"] = "180s"
|
||||||
|
}
|
||||||
|
rc.key = cf["key"]
|
||||||
|
rc.conninfo = cf["conn"]
|
||||||
|
rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
|
||||||
|
rc.password = cf["password"]
|
||||||
|
rc.maxIdle, _ = strconv.Atoi(cf["maxIdle"])
|
||||||
|
|
||||||
|
if v, err := time.ParseDuration(cf["timeout"]); err == nil {
|
||||||
|
rc.timeout = v
|
||||||
|
} else {
|
||||||
|
rc.timeout = 180 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.connectInit()
|
||||||
|
|
||||||
|
c := rc.p.Get()
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
return c.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect to redis.
|
||||||
|
func (rc *Cache) connectInit() {
|
||||||
|
dialFunc := func() (c redis.Conn, err error) {
|
||||||
|
c, err = redis.Dial("tcp", rc.conninfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rc.password != "" {
|
||||||
|
if _, err := c.Do("AUTH", rc.password); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, selecterr := c.Do("SELECT", rc.dbNum)
|
||||||
|
if selecterr != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, selecterr
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// initialize a new pool
|
||||||
|
rc.p = &redis.Pool{
|
||||||
|
MaxIdle: rc.maxIdle,
|
||||||
|
IdleTimeout: rc.timeout,
|
||||||
|
Dial: dialFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cache.Register("redis", NewRedisCache)
|
||||||
|
}
|
144
pkg/cache/redis/redis_test.go
vendored
Normal file
144
pkg/cache/redis/redis_test.go
vendored
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// 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 redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
"github.com/gomodule/redigo/redis"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRedisCache(t *testing.T) {
|
||||||
|
bm, err := cache.NewCache("redis", `{"conn": "127.0.0.1:6379"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(11 * time.Second)
|
||||||
|
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Incr("astaxie"); err != nil {
|
||||||
|
t.Error("Incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 2 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Decr("astaxie"); err != nil {
|
||||||
|
t.Error("Decr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test string
|
||||||
|
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := redis.String(bm.Get("astaxie"), err); v != "author" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if v, _ := redis.String(vv[0], nil); v != "author" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if v, _ := redis.String(vv[1], nil); v != "author1" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test clear all
|
||||||
|
if err = bm.ClearAll(); err != nil {
|
||||||
|
t.Error("clear all err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCache_Scan(t *testing.T) {
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
// init
|
||||||
|
bm, err := cache.NewCache("redis", `{"conn": "127.0.0.1:6379"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
// insert all
|
||||||
|
for i := 0; i < 10000; i++ {
|
||||||
|
if err = bm.Put(fmt.Sprintf("astaxie%d", i), fmt.Sprintf("author%d", i), timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// scan all for the first time
|
||||||
|
keys, err := bm.(*Cache).Scan(DefaultKey + ":*")
|
||||||
|
if err != nil {
|
||||||
|
t.Error("scan Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 10000, len(keys), "scan all error")
|
||||||
|
|
||||||
|
// clear all
|
||||||
|
if err = bm.ClearAll(); err != nil {
|
||||||
|
t.Error("clear all err")
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan all for the second time
|
||||||
|
keys, err = bm.(*Cache).Scan(DefaultKey + ":*")
|
||||||
|
if err != nil {
|
||||||
|
t.Error("scan Error", err)
|
||||||
|
}
|
||||||
|
if len(keys) != 0 {
|
||||||
|
t.Error("scan all err")
|
||||||
|
}
|
||||||
|
}
|
231
pkg/cache/ssdb/ssdb.go
vendored
Normal file
231
pkg/cache/ssdb/ssdb.go
vendored
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
package ssdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ssdb/gossdb/ssdb"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache SSDB adapter
|
||||||
|
type Cache struct {
|
||||||
|
conn *ssdb.Client
|
||||||
|
conninfo []string
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewSsdbCache create new ssdb adapter.
|
||||||
|
func NewSsdbCache() cache.Cache {
|
||||||
|
return &Cache{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get get value from memcache.
|
||||||
|
func (rc *Cache) Get(key string) interface{} {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value, err := rc.conn.Get(key)
|
||||||
|
if err == nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti get value from memcache.
|
||||||
|
func (rc *Cache) GetMulti(keys []string) []interface{} {
|
||||||
|
size := len(keys)
|
||||||
|
var values []interface{}
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
values = append(values, err)
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res, err := rc.conn.Do("multi_get", keys)
|
||||||
|
resSize := len(res)
|
||||||
|
if err == nil {
|
||||||
|
for i := 1; i < resSize; i += 2 {
|
||||||
|
values = append(values, res[i+1])
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
values = append(values, err)
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelMulti get value from memcache.
|
||||||
|
func (rc *Cache) DelMulti(keys []string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Do("multi_del", keys)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put put value to memcache. only support string.
|
||||||
|
func (rc *Cache) Put(key string, value interface{}, timeout time.Duration) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value must string")
|
||||||
|
}
|
||||||
|
var resp []string
|
||||||
|
var err error
|
||||||
|
ttl := int(timeout / time.Second)
|
||||||
|
if ttl < 0 {
|
||||||
|
resp, err = rc.conn.Do("set", key, v)
|
||||||
|
} else {
|
||||||
|
resp, err = rc.conn.Do("setx", key, v, ttl)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(resp) == 2 && resp[0] == "ok" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("bad response")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete delete value in memcache.
|
||||||
|
func (rc *Cache) Delete(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Del(key)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr increase counter.
|
||||||
|
func (rc *Cache) Incr(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Do("incr", key, 1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr decrease counter.
|
||||||
|
func (rc *Cache) Decr(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Do("incr", key, -1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist check value exists in memcache.
|
||||||
|
func (rc *Cache) IsExist(key string) bool {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp, err := rc.conn.Do("exists", key)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(resp) == 2 && resp[1] == "1" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAll clear all cached in memcache.
|
||||||
|
func (rc *Cache) ClearAll() error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keyStart, keyEnd, limit := "", "", 50
|
||||||
|
resp, err := rc.Scan(keyStart, keyEnd, limit)
|
||||||
|
for err == nil {
|
||||||
|
size := len(resp)
|
||||||
|
if size == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
keys := []string{}
|
||||||
|
for i := 1; i < size; i += 2 {
|
||||||
|
keys = append(keys, resp[i])
|
||||||
|
}
|
||||||
|
_, e := rc.conn.Do("multi_del", keys)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
keyStart = resp[size-2]
|
||||||
|
resp, err = rc.Scan(keyStart, keyEnd, limit)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan key all cached in ssdb.
|
||||||
|
func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, error) {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp, err := rc.conn.Do("scan", keyStart, keyEnd, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC start memcache adapter.
|
||||||
|
// config string is like {"conn":"connection info"}.
|
||||||
|
// if connecting error, return.
|
||||||
|
func (rc *Cache) StartAndGC(config string) error {
|
||||||
|
var cf map[string]string
|
||||||
|
json.Unmarshal([]byte(config), &cf)
|
||||||
|
if _, ok := cf["conn"]; !ok {
|
||||||
|
return errors.New("config has no conn key")
|
||||||
|
}
|
||||||
|
rc.conninfo = strings.Split(cf["conn"], ";")
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect to memcache and keep the connection.
|
||||||
|
func (rc *Cache) connectInit() error {
|
||||||
|
conninfoArray := strings.Split(rc.conninfo[0], ":")
|
||||||
|
host := conninfoArray[0]
|
||||||
|
port, e := strconv.Atoi(conninfoArray[1])
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
rc.conn, err = ssdb.Connect(host, port)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cache.Register("ssdb", NewSsdbCache)
|
||||||
|
}
|
104
pkg/cache/ssdb/ssdb_test.go
vendored
Normal file
104
pkg/cache/ssdb/ssdb_test.go
vendored
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package ssdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSsdbcacheCache(t *testing.T) {
|
||||||
|
ssdb, err := cache.NewCache("ssdb", `{"conn": "127.0.0.1:8888"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test put and exist
|
||||||
|
if ssdb.IsExist("ssdb") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
//timeoutDuration := -10*time.Second if timeoutDuration is negtive,it means permanent
|
||||||
|
if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !ssdb.IsExist("ssdb") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get test done
|
||||||
|
if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := ssdb.Get("ssdb"); v != "ssdb" {
|
||||||
|
t.Error("get Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
//inc/dec test done
|
||||||
|
if err = ssdb.Put("ssdb", "2", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if err = ssdb.Incr("ssdb"); err != nil {
|
||||||
|
t.Error("incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ssdb.Decr("ssdb"); err != nil {
|
||||||
|
t.Error("decr error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test del
|
||||||
|
if err = ssdb.Put("ssdb", "3", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
if err := ssdb.Delete("ssdb"); err == nil {
|
||||||
|
if ssdb.IsExist("ssdb") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//test string
|
||||||
|
if err = ssdb.Put("ssdb", "ssdb", -10*time.Second); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !ssdb.IsExist("ssdb") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if v := ssdb.Get("ssdb").(string); v != "ssdb" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti done
|
||||||
|
if err = ssdb.Put("ssdb1", "ssdb1", -10*time.Second); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !ssdb.IsExist("ssdb1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
vv := ssdb.GetMulti([]string{"ssdb", "ssdb1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("getmulti error")
|
||||||
|
}
|
||||||
|
if vv[0].(string) != "ssdb" {
|
||||||
|
t.Error("getmulti error")
|
||||||
|
}
|
||||||
|
if vv[1].(string) != "ssdb1" {
|
||||||
|
t.Error("getmulti error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test clear all done
|
||||||
|
if err = ssdb.ClearAll(); err != nil {
|
||||||
|
t.Error("clear all err")
|
||||||
|
}
|
||||||
|
if ssdb.IsExist("ssdb") || ssdb.IsExist("ssdb1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
}
|
524
pkg/config.go
Normal file
524
pkg/config.go
Normal file
@ -0,0 +1,524 @@
|
|||||||
|
// 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"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/config"
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/astaxie/beego/session"
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is the main struct for BConfig
|
||||||
|
type Config struct {
|
||||||
|
AppName string //Application name
|
||||||
|
RunMode string //Running Mode: dev | prod
|
||||||
|
RouterCaseSensitive bool
|
||||||
|
ServerName string
|
||||||
|
RecoverPanic bool
|
||||||
|
RecoverFunc func(*context.Context)
|
||||||
|
CopyRequestBody bool
|
||||||
|
EnableGzip bool
|
||||||
|
MaxMemory int64
|
||||||
|
EnableErrorsShow bool
|
||||||
|
EnableErrorsRender bool
|
||||||
|
Listen Listen
|
||||||
|
WebConfig WebConfig
|
||||||
|
Log LogConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen holds for http and https related config
|
||||||
|
type Listen struct {
|
||||||
|
Graceful bool // Graceful means use graceful module to start the server
|
||||||
|
ServerTimeOut int64
|
||||||
|
ListenTCP4 bool
|
||||||
|
EnableHTTP bool
|
||||||
|
HTTPAddr string
|
||||||
|
HTTPPort int
|
||||||
|
AutoTLS bool
|
||||||
|
Domains []string
|
||||||
|
TLSCacheDir string
|
||||||
|
EnableHTTPS bool
|
||||||
|
EnableMutualHTTPS bool
|
||||||
|
HTTPSAddr string
|
||||||
|
HTTPSPort int
|
||||||
|
HTTPSCertFile string
|
||||||
|
HTTPSKeyFile string
|
||||||
|
TrustCaFile string
|
||||||
|
EnableAdmin bool
|
||||||
|
AdminAddr string
|
||||||
|
AdminPort int
|
||||||
|
EnableFcgi bool
|
||||||
|
EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebConfig holds web related config
|
||||||
|
type WebConfig struct {
|
||||||
|
AutoRender bool
|
||||||
|
EnableDocs bool
|
||||||
|
FlashName string
|
||||||
|
FlashSeparator string
|
||||||
|
DirectoryIndex bool
|
||||||
|
StaticDir map[string]string
|
||||||
|
StaticExtensionsToGzip []string
|
||||||
|
StaticCacheFileSize int
|
||||||
|
StaticCacheFileNum int
|
||||||
|
TemplateLeft string
|
||||||
|
TemplateRight string
|
||||||
|
ViewsPath string
|
||||||
|
EnableXSRF bool
|
||||||
|
XSRFKey string
|
||||||
|
XSRFExpire int
|
||||||
|
Session SessionConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionConfig holds session related config
|
||||||
|
type SessionConfig struct {
|
||||||
|
SessionOn bool
|
||||||
|
SessionProvider string
|
||||||
|
SessionName string
|
||||||
|
SessionGCMaxLifetime int64
|
||||||
|
SessionProviderConfig string
|
||||||
|
SessionCookieLifeTime int
|
||||||
|
SessionAutoSetCookie bool
|
||||||
|
SessionDomain string
|
||||||
|
SessionDisableHTTPOnly bool // used to allow for cross domain cookies/javascript cookies.
|
||||||
|
SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers
|
||||||
|
SessionNameInHTTPHeader string
|
||||||
|
SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogConfig holds Log related config
|
||||||
|
type LogConfig struct {
|
||||||
|
AccessLogs bool
|
||||||
|
EnableStaticLogs bool //log static files requests default: false
|
||||||
|
AccessLogsFormat string //access log format: JSON_FORMAT, APACHE_FORMAT or empty string
|
||||||
|
FileLineNum bool
|
||||||
|
Outputs map[string]string // Store Adaptor : config
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// BConfig is the default config for Application
|
||||||
|
BConfig *Config
|
||||||
|
// AppConfig is the instance of Config, store the config information from file
|
||||||
|
AppConfig *beegoAppConfig
|
||||||
|
// AppPath is the absolute path to the app
|
||||||
|
AppPath string
|
||||||
|
// GlobalSessions is the instance for the session manager
|
||||||
|
GlobalSessions *session.Manager
|
||||||
|
|
||||||
|
// appConfigPath is the path to the config files
|
||||||
|
appConfigPath string
|
||||||
|
// appConfigProvider is the provider for the config, default is ini
|
||||||
|
appConfigProvider = "ini"
|
||||||
|
// WorkPath is the absolute path to project root directory
|
||||||
|
WorkPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
BConfig = newBConfig()
|
||||||
|
var err error
|
||||||
|
if AppPath, err = filepath.Abs(filepath.Dir(os.Args[0])); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
WorkPath, err = os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var filename = "app.conf"
|
||||||
|
if os.Getenv("BEEGO_RUNMODE") != "" {
|
||||||
|
filename = os.Getenv("BEEGO_RUNMODE") + ".app.conf"
|
||||||
|
}
|
||||||
|
appConfigPath = filepath.Join(WorkPath, "conf", filename)
|
||||||
|
if !utils.FileExists(appConfigPath) {
|
||||||
|
appConfigPath = filepath.Join(AppPath, "conf", filename)
|
||||||
|
if !utils.FileExists(appConfigPath) {
|
||||||
|
AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = parseConfig(appConfigPath); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func recoverPanic(ctx *context.Context) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
if err == ErrAbort {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !BConfig.RecoverPanic {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if BConfig.EnableErrorsShow {
|
||||||
|
if _, ok := ErrorMaps[fmt.Sprint(err)]; ok {
|
||||||
|
exception(fmt.Sprint(err), ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var stack string
|
||||||
|
logs.Critical("the request url is ", ctx.Input.URL())
|
||||||
|
logs.Critical("Handler crashed with error", err)
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
_, file, line, ok := runtime.Caller(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logs.Critical(fmt.Sprintf("%s:%d", file, line))
|
||||||
|
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
|
||||||
|
}
|
||||||
|
if BConfig.RunMode == DEV && BConfig.EnableErrorsRender {
|
||||||
|
showErr(err, ctx, stack)
|
||||||
|
}
|
||||||
|
if ctx.Output.Status != 0 {
|
||||||
|
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
|
||||||
|
} else {
|
||||||
|
ctx.ResponseWriter.WriteHeader(500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBConfig() *Config {
|
||||||
|
return &Config{
|
||||||
|
AppName: "beego",
|
||||||
|
RunMode: PROD,
|
||||||
|
RouterCaseSensitive: true,
|
||||||
|
ServerName: "beegoServer:" + VERSION,
|
||||||
|
RecoverPanic: true,
|
||||||
|
RecoverFunc: recoverPanic,
|
||||||
|
CopyRequestBody: false,
|
||||||
|
EnableGzip: false,
|
||||||
|
MaxMemory: 1 << 26, //64MB
|
||||||
|
EnableErrorsShow: true,
|
||||||
|
EnableErrorsRender: true,
|
||||||
|
Listen: Listen{
|
||||||
|
Graceful: false,
|
||||||
|
ServerTimeOut: 0,
|
||||||
|
ListenTCP4: false,
|
||||||
|
EnableHTTP: true,
|
||||||
|
AutoTLS: false,
|
||||||
|
Domains: []string{},
|
||||||
|
TLSCacheDir: ".",
|
||||||
|
HTTPAddr: "",
|
||||||
|
HTTPPort: 8080,
|
||||||
|
EnableHTTPS: false,
|
||||||
|
HTTPSAddr: "",
|
||||||
|
HTTPSPort: 10443,
|
||||||
|
HTTPSCertFile: "",
|
||||||
|
HTTPSKeyFile: "",
|
||||||
|
EnableAdmin: false,
|
||||||
|
AdminAddr: "",
|
||||||
|
AdminPort: 8088,
|
||||||
|
EnableFcgi: false,
|
||||||
|
EnableStdIo: false,
|
||||||
|
},
|
||||||
|
WebConfig: WebConfig{
|
||||||
|
AutoRender: true,
|
||||||
|
EnableDocs: false,
|
||||||
|
FlashName: "BEEGO_FLASH",
|
||||||
|
FlashSeparator: "BEEGOFLASH",
|
||||||
|
DirectoryIndex: false,
|
||||||
|
StaticDir: map[string]string{"/static": "static"},
|
||||||
|
StaticExtensionsToGzip: []string{".css", ".js"},
|
||||||
|
StaticCacheFileSize: 1024 * 100,
|
||||||
|
StaticCacheFileNum: 1000,
|
||||||
|
TemplateLeft: "{{",
|
||||||
|
TemplateRight: "}}",
|
||||||
|
ViewsPath: "views",
|
||||||
|
EnableXSRF: false,
|
||||||
|
XSRFKey: "beegoxsrf",
|
||||||
|
XSRFExpire: 0,
|
||||||
|
Session: SessionConfig{
|
||||||
|
SessionOn: false,
|
||||||
|
SessionProvider: "memory",
|
||||||
|
SessionName: "beegosessionID",
|
||||||
|
SessionGCMaxLifetime: 3600,
|
||||||
|
SessionProviderConfig: "",
|
||||||
|
SessionDisableHTTPOnly: false,
|
||||||
|
SessionCookieLifeTime: 0, //set cookie default is the browser life
|
||||||
|
SessionAutoSetCookie: true,
|
||||||
|
SessionDomain: "",
|
||||||
|
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
|
||||||
|
SessionNameInHTTPHeader: "Beegosessionid",
|
||||||
|
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Log: LogConfig{
|
||||||
|
AccessLogs: false,
|
||||||
|
EnableStaticLogs: false,
|
||||||
|
AccessLogsFormat: "APACHE_FORMAT",
|
||||||
|
FileLineNum: true,
|
||||||
|
Outputs: map[string]string{"console": ""},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now only support ini, next will support json.
|
||||||
|
func parseConfig(appConfigPath string) (err error) {
|
||||||
|
AppConfig, err = newAppConfig(appConfigProvider, appConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return assignConfig(AppConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignConfig(ac config.Configer) error {
|
||||||
|
for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} {
|
||||||
|
assignSingleConfig(i, ac)
|
||||||
|
}
|
||||||
|
// set the run mode first
|
||||||
|
if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" {
|
||||||
|
BConfig.RunMode = envRunMode
|
||||||
|
} else if runMode := ac.String("RunMode"); runMode != "" {
|
||||||
|
BConfig.RunMode = runMode
|
||||||
|
}
|
||||||
|
|
||||||
|
if sd := ac.String("StaticDir"); sd != "" {
|
||||||
|
BConfig.WebConfig.StaticDir = map[string]string{}
|
||||||
|
sds := strings.Fields(sd)
|
||||||
|
for _, v := range sds {
|
||||||
|
if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 {
|
||||||
|
BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[1]
|
||||||
|
} else {
|
||||||
|
BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sgz := ac.String("StaticExtensionsToGzip"); sgz != "" {
|
||||||
|
extensions := strings.Split(sgz, ",")
|
||||||
|
fileExts := []string{}
|
||||||
|
for _, ext := range extensions {
|
||||||
|
ext = strings.TrimSpace(ext)
|
||||||
|
if ext == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(ext, ".") {
|
||||||
|
ext = "." + ext
|
||||||
|
}
|
||||||
|
fileExts = append(fileExts, ext)
|
||||||
|
}
|
||||||
|
if len(fileExts) > 0 {
|
||||||
|
BConfig.WebConfig.StaticExtensionsToGzip = fileExts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sfs, err := ac.Int("StaticCacheFileSize"); err == nil {
|
||||||
|
BConfig.WebConfig.StaticCacheFileSize = sfs
|
||||||
|
}
|
||||||
|
|
||||||
|
if sfn, err := ac.Int("StaticCacheFileNum"); err == nil {
|
||||||
|
BConfig.WebConfig.StaticCacheFileNum = sfn
|
||||||
|
}
|
||||||
|
|
||||||
|
if lo := ac.String("LogOutputs"); lo != "" {
|
||||||
|
// if lo is not nil or empty
|
||||||
|
// means user has set his own LogOutputs
|
||||||
|
// clear the default setting to BConfig.Log.Outputs
|
||||||
|
BConfig.Log.Outputs = make(map[string]string)
|
||||||
|
los := strings.Split(lo, ";")
|
||||||
|
for _, v := range los {
|
||||||
|
if logType2Config := strings.SplitN(v, ",", 2); len(logType2Config) == 2 {
|
||||||
|
BConfig.Log.Outputs[logType2Config[0]] = logType2Config[1]
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//init log
|
||||||
|
logs.Reset()
|
||||||
|
for adaptor, config := range BConfig.Log.Outputs {
|
||||||
|
err := logs.SetLogger(adaptor, config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, fmt.Sprintf("%s with the config %q got err:%s", adaptor, config, err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logs.SetLogFuncCall(BConfig.Log.FileLineNum)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignSingleConfig(p interface{}, ac config.Configer) {
|
||||||
|
pt := reflect.TypeOf(p)
|
||||||
|
if pt.Kind() != reflect.Ptr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pt = pt.Elem()
|
||||||
|
if pt.Kind() != reflect.Struct {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pv := reflect.ValueOf(p).Elem()
|
||||||
|
|
||||||
|
for i := 0; i < pt.NumField(); i++ {
|
||||||
|
pf := pv.Field(i)
|
||||||
|
if !pf.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := pt.Field(i).Name
|
||||||
|
switch pf.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
pf.SetString(ac.DefaultString(name, pf.String()))
|
||||||
|
case reflect.Int, reflect.Int64:
|
||||||
|
pf.SetInt(ac.DefaultInt64(name, pf.Int()))
|
||||||
|
case reflect.Bool:
|
||||||
|
pf.SetBool(ac.DefaultBool(name, pf.Bool()))
|
||||||
|
case reflect.Struct:
|
||||||
|
default:
|
||||||
|
//do nothing here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAppConfig allow developer to apply a config file
|
||||||
|
func LoadAppConfig(adapterName, configPath string) error {
|
||||||
|
absConfigPath, err := filepath.Abs(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.FileExists(absConfigPath) {
|
||||||
|
return fmt.Errorf("the target config file: %s don't exist", configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
appConfigPath = absConfigPath
|
||||||
|
appConfigProvider = adapterName
|
||||||
|
|
||||||
|
return parseConfig(appConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
type beegoAppConfig struct {
|
||||||
|
innerConfig config.Configer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAppConfig(appConfigProvider, appConfigPath string) (*beegoAppConfig, error) {
|
||||||
|
ac, err := config.NewConfig(appConfigProvider, appConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &beegoAppConfig{ac}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Set(key, val string) error {
|
||||||
|
if err := b.innerConfig.Set(BConfig.RunMode+"::"+key, val); err != nil {
|
||||||
|
return b.innerConfig.Set(key, val)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) String(key string) string {
|
||||||
|
if v := b.innerConfig.String(BConfig.RunMode + "::" + key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return b.innerConfig.String(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Strings(key string) []string {
|
||||||
|
if v := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return b.innerConfig.Strings(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Int(key string) (int, error) {
|
||||||
|
if v, err := b.innerConfig.Int(BConfig.RunMode + "::" + key); err == nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return b.innerConfig.Int(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Int64(key string) (int64, error) {
|
||||||
|
if v, err := b.innerConfig.Int64(BConfig.RunMode + "::" + key); err == nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return b.innerConfig.Int64(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Bool(key string) (bool, error) {
|
||||||
|
if v, err := b.innerConfig.Bool(BConfig.RunMode + "::" + key); err == nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return b.innerConfig.Bool(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Float(key string) (float64, error) {
|
||||||
|
if v, err := b.innerConfig.Float(BConfig.RunMode + "::" + key); err == nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return b.innerConfig.Float(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string {
|
||||||
|
if v := b.String(key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string {
|
||||||
|
if v := b.Strings(key); len(v) != 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultInt(key string, defaultVal int) int {
|
||||||
|
if v, err := b.Int(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultInt64(key string, defaultVal int64) int64 {
|
||||||
|
if v, err := b.Int64(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultBool(key string, defaultVal bool) bool {
|
||||||
|
if v, err := b.Bool(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultFloat(key string, defaultVal float64) float64 {
|
||||||
|
if v, err := b.Float(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DIY(key string) (interface{}, error) {
|
||||||
|
return b.innerConfig.DIY(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) GetSection(section string) (map[string]string, error) {
|
||||||
|
return b.innerConfig.GetSection(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) SaveConfigFile(filename string) error {
|
||||||
|
return b.innerConfig.SaveConfigFile(filename)
|
||||||
|
}
|
242
pkg/config/config.go
Normal file
242
pkg/config/config.go
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
// 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 config is used to parse config.
|
||||||
|
// Usage:
|
||||||
|
// import "github.com/astaxie/beego/config"
|
||||||
|
//Examples.
|
||||||
|
//
|
||||||
|
// cnf, err := config.NewConfig("ini", "config.conf")
|
||||||
|
//
|
||||||
|
// cnf APIS:
|
||||||
|
//
|
||||||
|
// cnf.Set(key, val string) error
|
||||||
|
// cnf.String(key string) string
|
||||||
|
// cnf.Strings(key string) []string
|
||||||
|
// cnf.Int(key string) (int, error)
|
||||||
|
// cnf.Int64(key string) (int64, error)
|
||||||
|
// cnf.Bool(key string) (bool, error)
|
||||||
|
// cnf.Float(key string) (float64, error)
|
||||||
|
// cnf.DefaultString(key string, defaultVal string) string
|
||||||
|
// cnf.DefaultStrings(key string, defaultVal []string) []string
|
||||||
|
// cnf.DefaultInt(key string, defaultVal int) int
|
||||||
|
// cnf.DefaultInt64(key string, defaultVal int64) int64
|
||||||
|
// cnf.DefaultBool(key string, defaultVal bool) bool
|
||||||
|
// cnf.DefaultFloat(key string, defaultVal float64) float64
|
||||||
|
// cnf.DIY(key string) (interface{}, error)
|
||||||
|
// cnf.GetSection(section string) (map[string]string, error)
|
||||||
|
// cnf.SaveConfigFile(filename string) error
|
||||||
|
//More docs http://beego.me/docs/module/config.md
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configer defines how to get and set value from configuration raw data.
|
||||||
|
type Configer interface {
|
||||||
|
Set(key, val string) error //support section::key type in given key when using ini type.
|
||||||
|
String(key string) string //support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
|
||||||
|
Strings(key string) []string //get string slice
|
||||||
|
Int(key string) (int, error)
|
||||||
|
Int64(key string) (int64, error)
|
||||||
|
Bool(key string) (bool, error)
|
||||||
|
Float(key string) (float64, error)
|
||||||
|
DefaultString(key string, defaultVal string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
|
||||||
|
DefaultStrings(key string, defaultVal []string) []string //get string slice
|
||||||
|
DefaultInt(key string, defaultVal int) int
|
||||||
|
DefaultInt64(key string, defaultVal int64) int64
|
||||||
|
DefaultBool(key string, defaultVal bool) bool
|
||||||
|
DefaultFloat(key string, defaultVal float64) float64
|
||||||
|
DIY(key string) (interface{}, error)
|
||||||
|
GetSection(section string) (map[string]string, error)
|
||||||
|
SaveConfigFile(filename string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the adapter interface for parsing config file to get raw data to Configer.
|
||||||
|
type Config interface {
|
||||||
|
Parse(key string) (Configer, error)
|
||||||
|
ParseData(data []byte) (Configer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var adapters = make(map[string]Config)
|
||||||
|
|
||||||
|
// Register makes a config adapter available by the adapter name.
|
||||||
|
// If Register is called twice with the same name or if driver is nil,
|
||||||
|
// it panics.
|
||||||
|
func Register(name string, adapter Config) {
|
||||||
|
if adapter == nil {
|
||||||
|
panic("config: Register adapter is nil")
|
||||||
|
}
|
||||||
|
if _, ok := adapters[name]; ok {
|
||||||
|
panic("config: Register called twice for adapter " + name)
|
||||||
|
}
|
||||||
|
adapters[name] = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfig adapterName is ini/json/xml/yaml.
|
||||||
|
// filename is the config file path.
|
||||||
|
func NewConfig(adapterName, filename string) (Configer, error) {
|
||||||
|
adapter, ok := adapters[adapterName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
|
||||||
|
}
|
||||||
|
return adapter.Parse(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigData adapterName is ini/json/xml/yaml.
|
||||||
|
// data is the config data.
|
||||||
|
func NewConfigData(adapterName string, data []byte) (Configer, error) {
|
||||||
|
adapter, ok := adapters[adapterName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
|
||||||
|
}
|
||||||
|
return adapter.ParseData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandValueEnvForMap convert all string value with environment variable.
|
||||||
|
func ExpandValueEnvForMap(m map[string]interface{}) map[string]interface{} {
|
||||||
|
for k, v := range m {
|
||||||
|
switch value := v.(type) {
|
||||||
|
case string:
|
||||||
|
m[k] = ExpandValueEnv(value)
|
||||||
|
case map[string]interface{}:
|
||||||
|
m[k] = ExpandValueEnvForMap(value)
|
||||||
|
case map[string]string:
|
||||||
|
for k2, v2 := range value {
|
||||||
|
value[k2] = ExpandValueEnv(v2)
|
||||||
|
}
|
||||||
|
m[k] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandValueEnv returns value of convert with environment variable.
|
||||||
|
//
|
||||||
|
// Return environment variable if value start with "${" and end with "}".
|
||||||
|
// Return default value if environment variable is empty or not exist.
|
||||||
|
//
|
||||||
|
// It accept value formats "${env}" , "${env||}}" , "${env||defaultValue}" , "defaultvalue".
|
||||||
|
// Examples:
|
||||||
|
// v1 := config.ExpandValueEnv("${GOPATH}") // return the GOPATH environment variable.
|
||||||
|
// v2 := config.ExpandValueEnv("${GOAsta||/usr/local/go}") // return the default value "/usr/local/go/".
|
||||||
|
// v3 := config.ExpandValueEnv("Astaxie") // return the value "Astaxie".
|
||||||
|
func ExpandValueEnv(value string) (realValue string) {
|
||||||
|
realValue = value
|
||||||
|
|
||||||
|
vLen := len(value)
|
||||||
|
// 3 = ${}
|
||||||
|
if vLen < 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Need start with "${" and end with "}", then return.
|
||||||
|
if value[0] != '$' || value[1] != '{' || value[vLen-1] != '}' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ""
|
||||||
|
defaultV := ""
|
||||||
|
// value start with "${"
|
||||||
|
for i := 2; i < vLen; i++ {
|
||||||
|
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
|
||||||
|
key = value[2:i]
|
||||||
|
defaultV = value[i+2 : vLen-1] // other string is default value.
|
||||||
|
break
|
||||||
|
} else if value[i] == '}' {
|
||||||
|
key = value[2:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
realValue = os.Getenv(key)
|
||||||
|
if realValue == "" {
|
||||||
|
realValue = defaultV
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBool returns the boolean value represented by the string.
|
||||||
|
//
|
||||||
|
// It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On,
|
||||||
|
// 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off.
|
||||||
|
// Any other value returns an error.
|
||||||
|
func ParseBool(val interface{}) (value bool, err error) {
|
||||||
|
if val != nil {
|
||||||
|
switch v := val.(type) {
|
||||||
|
case bool:
|
||||||
|
return v, nil
|
||||||
|
case string:
|
||||||
|
switch v {
|
||||||
|
case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "Y", "y", "ON", "on", "On":
|
||||||
|
return true, nil
|
||||||
|
case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "N", "n", "OFF", "off", "Off":
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
case int8, int32, int64:
|
||||||
|
strV := fmt.Sprintf("%d", v)
|
||||||
|
if strV == "1" {
|
||||||
|
return true, nil
|
||||||
|
} else if strV == "0" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
if v == 1.0 {
|
||||||
|
return true, nil
|
||||||
|
} else if v == 0.0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("parsing %q: invalid syntax", val)
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("parsing <nil>: invalid syntax")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString converts values of any type to string.
|
||||||
|
func ToString(x interface{}) string {
|
||||||
|
switch y := x.(type) {
|
||||||
|
|
||||||
|
// Handle dates with special logic
|
||||||
|
// This needs to come above the fmt.Stringer
|
||||||
|
// test since time.Time's have a .String()
|
||||||
|
// method
|
||||||
|
case time.Time:
|
||||||
|
return y.Format("A Monday")
|
||||||
|
|
||||||
|
// Handle type string
|
||||||
|
case string:
|
||||||
|
return y
|
||||||
|
|
||||||
|
// Handle type with .String() method
|
||||||
|
case fmt.Stringer:
|
||||||
|
return y.String()
|
||||||
|
|
||||||
|
// Handle type with .Error() method
|
||||||
|
case error:
|
||||||
|
return y.Error()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle named string type
|
||||||
|
if v := reflect.ValueOf(x); v.Kind() == reflect.String {
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to fmt package for anything else like numeric types
|
||||||
|
return fmt.Sprint(x)
|
||||||
|
}
|
55
pkg/config/config_test.go
Normal file
55
pkg/config/config_test.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2016 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExpandValueEnv(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
item string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"", ""},
|
||||||
|
{"$", "$"},
|
||||||
|
{"{", "{"},
|
||||||
|
{"{}", "{}"},
|
||||||
|
{"${}", ""},
|
||||||
|
{"${|}", ""},
|
||||||
|
{"${}", ""},
|
||||||
|
{"${{}}", ""},
|
||||||
|
{"${{||}}", "}"},
|
||||||
|
{"${pwd||}", ""},
|
||||||
|
{"${pwd||}", ""},
|
||||||
|
{"${pwd||}", ""},
|
||||||
|
{"${pwd||}}", "}"},
|
||||||
|
{"${pwd||{{||}}}", "{{||}}"},
|
||||||
|
{"${GOPATH}", os.Getenv("GOPATH")},
|
||||||
|
{"${GOPATH||}", os.Getenv("GOPATH")},
|
||||||
|
{"${GOPATH||root}", os.Getenv("GOPATH")},
|
||||||
|
{"${GOPATH_NOT||root}", "root"},
|
||||||
|
{"${GOPATH_NOT||||root}", "||root"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range testCases {
|
||||||
|
if got := ExpandValueEnv(c.item); got != c.want {
|
||||||
|
t.Errorf("expand value error, item %q want %q, got %q", c.item, c.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
87
pkg/config/env/env.go
vendored
Normal file
87
pkg/config/env/env.go
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
// Copyright 2017 Faissal Elamraoui. 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 env is used to parse environment.
|
||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var env *utils.BeeMap
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
env = utils.NewBeeMap()
|
||||||
|
for _, e := range os.Environ() {
|
||||||
|
splits := strings.Split(e, "=")
|
||||||
|
env.Set(splits[0], os.Getenv(splits[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a value by key.
|
||||||
|
// If the key does not exist, the default value will be returned.
|
||||||
|
func Get(key string, defVal string) string {
|
||||||
|
if val := env.Get(key); val != nil {
|
||||||
|
return val.(string)
|
||||||
|
}
|
||||||
|
return defVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGet returns a value by key.
|
||||||
|
// If the key does not exist, it will return an error.
|
||||||
|
func MustGet(key string) (string, error) {
|
||||||
|
if val := env.Get(key); val != nil {
|
||||||
|
return val.(string), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no env variable with %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets a value in the ENV copy.
|
||||||
|
// This does not affect the child process environment.
|
||||||
|
func Set(key string, value string) {
|
||||||
|
env.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustSet sets a value in the ENV copy and the child process environment.
|
||||||
|
// It returns an error in case the set operation failed.
|
||||||
|
func MustSet(key string, value string) error {
|
||||||
|
err := os.Setenv(key, value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env.Set(key, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns all keys/values in the current child process environment.
|
||||||
|
func GetAll() map[string]string {
|
||||||
|
items := env.Items()
|
||||||
|
envs := make(map[string]string, env.Count())
|
||||||
|
|
||||||
|
for key, val := range items {
|
||||||
|
switch key := key.(type) {
|
||||||
|
case string:
|
||||||
|
switch val := val.(type) {
|
||||||
|
case string:
|
||||||
|
envs[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return envs
|
||||||
|
}
|
75
pkg/config/env/env_test.go
vendored
Normal file
75
pkg/config/env/env_test.go
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
// Copyright 2017 Faissal Elamraoui. 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 env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnvGet(t *testing.T) {
|
||||||
|
gopath := Get("GOPATH", "")
|
||||||
|
if gopath != os.Getenv("GOPATH") {
|
||||||
|
t.Error("expected GOPATH not empty.")
|
||||||
|
}
|
||||||
|
|
||||||
|
noExistVar := Get("NOEXISTVAR", "foo")
|
||||||
|
if noExistVar != "foo" {
|
||||||
|
t.Errorf("expected NOEXISTVAR to equal foo, got %s.", noExistVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvMustGet(t *testing.T) {
|
||||||
|
gopath, err := MustGet("GOPATH")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gopath != os.Getenv("GOPATH") {
|
||||||
|
t.Errorf("expected GOPATH to be the same, got %s.", gopath)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = MustGet("NOEXISTVAR")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error to be non-nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvSet(t *testing.T) {
|
||||||
|
Set("MYVAR", "foo")
|
||||||
|
myVar := Get("MYVAR", "bar")
|
||||||
|
if myVar != "foo" {
|
||||||
|
t.Errorf("expected MYVAR to equal foo, got %s.", myVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvMustSet(t *testing.T) {
|
||||||
|
err := MustSet("FOO", "bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fooVar := os.Getenv("FOO")
|
||||||
|
if fooVar != "bar" {
|
||||||
|
t.Errorf("expected FOO variable to equal bar, got %s.", fooVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvGetAll(t *testing.T) {
|
||||||
|
envMap := GetAll()
|
||||||
|
if len(envMap) == 0 {
|
||||||
|
t.Error("expected environment not empty.")
|
||||||
|
}
|
||||||
|
}
|
134
pkg/config/fake.go
Normal file
134
pkg/config/fake.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeConfigContainer struct {
|
||||||
|
data map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) getData(key string) string {
|
||||||
|
return c.data[strings.ToLower(key)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Set(key, val string) error {
|
||||||
|
c.data[strings.ToLower(key)] = val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) String(key string) string {
|
||||||
|
return c.getData(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Strings(key string) []string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return strings.Split(v, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
|
v := c.Strings(key)
|
||||||
|
if v == nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Int(key string) (int, error) {
|
||||||
|
return strconv.Atoi(c.getData(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
|
v, err := c.Int(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Int64(key string) (int64, error) {
|
||||||
|
return strconv.ParseInt(c.getData(key), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
|
v, err := c.Int64(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Bool(key string) (bool, error) {
|
||||||
|
return ParseBool(c.getData(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||||
|
v, err := c.Bool(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Float(key string) (float64, error) {
|
||||||
|
return strconv.ParseFloat(c.getData(key), 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
|
v, err := c.Float(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DIY(key string) (interface{}, error) {
|
||||||
|
if v, ok := c.data[strings.ToLower(key)]; ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("key not find")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) GetSection(section string) (map[string]string, error) {
|
||||||
|
return nil, errors.New("not implement in the fakeConfigContainer")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) SaveConfigFile(filename string) error {
|
||||||
|
return errors.New("not implement in the fakeConfigContainer")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Configer = new(fakeConfigContainer)
|
||||||
|
|
||||||
|
// NewFakeConfig return a fake Configer
|
||||||
|
func NewFakeConfig() Configer {
|
||||||
|
return &fakeConfigContainer{
|
||||||
|
data: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
504
pkg/config/ini.go
Normal file
504
pkg/config/ini.go
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
// 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultSection = "default" // default section means if some ini items not in a section, make them in default section,
|
||||||
|
bNumComment = []byte{'#'} // number signal
|
||||||
|
bSemComment = []byte{';'} // semicolon signal
|
||||||
|
bEmpty = []byte{}
|
||||||
|
bEqual = []byte{'='} // equal signal
|
||||||
|
bDQuote = []byte{'"'} // quote signal
|
||||||
|
sectionStart = []byte{'['} // section start signal
|
||||||
|
sectionEnd = []byte{']'} // section end signal
|
||||||
|
lineBreak = "\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IniConfig implements Config to parse ini file.
|
||||||
|
type IniConfig struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse creates a new Config and parses the file configuration from the named file.
|
||||||
|
func (ini *IniConfig) Parse(name string) (Configer, error) {
|
||||||
|
return ini.parseFile(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
|
||||||
|
data, err := ioutil.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ini.parseData(filepath.Dir(name), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) {
|
||||||
|
cfg := &IniConfigContainer{
|
||||||
|
data: make(map[string]map[string]string),
|
||||||
|
sectionComment: make(map[string]string),
|
||||||
|
keyComment: make(map[string]string),
|
||||||
|
RWMutex: sync.RWMutex{},
|
||||||
|
}
|
||||||
|
cfg.Lock()
|
||||||
|
defer cfg.Unlock()
|
||||||
|
|
||||||
|
var comment bytes.Buffer
|
||||||
|
buf := bufio.NewReader(bytes.NewBuffer(data))
|
||||||
|
// 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 := defaultSection
|
||||||
|
tmpBuf := bytes.NewBuffer(nil)
|
||||||
|
for {
|
||||||
|
tmpBuf.Reset()
|
||||||
|
|
||||||
|
shouldBreak := false
|
||||||
|
for {
|
||||||
|
tmp, isPrefix, err := buf.ReadLine()
|
||||||
|
if err == io.EOF {
|
||||||
|
shouldBreak = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
//It might be a good idea to throw a error on all unknonw errors?
|
||||||
|
if _, ok := err.(*os.PathError); ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpBuf.Write(tmp)
|
||||||
|
if isPrefix {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isPrefix {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if shouldBreak {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
line := tmpBuf.Bytes()
|
||||||
|
line = bytes.TrimSpace(line)
|
||||||
|
if bytes.Equal(line, bEmpty) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var bComment []byte
|
||||||
|
switch {
|
||||||
|
case bytes.HasPrefix(line, bNumComment):
|
||||||
|
bComment = bNumComment
|
||||||
|
case bytes.HasPrefix(line, bSemComment):
|
||||||
|
bComment = bSemComment
|
||||||
|
}
|
||||||
|
if bComment != nil {
|
||||||
|
line = bytes.TrimLeft(line, string(bComment))
|
||||||
|
// Need append to a new line if multi-line comments.
|
||||||
|
if comment.Len() > 0 {
|
||||||
|
comment.WriteByte('\n')
|
||||||
|
}
|
||||||
|
comment.Write(line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) {
|
||||||
|
section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive
|
||||||
|
if comment.Len() > 0 {
|
||||||
|
cfg.sectionComment[section] = comment.String()
|
||||||
|
comment.Reset()
|
||||||
|
}
|
||||||
|
if _, ok := cfg.data[section]; !ok {
|
||||||
|
cfg.data[section] = make(map[string]string)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := cfg.data[section]; !ok {
|
||||||
|
cfg.data[section] = make(map[string]string)
|
||||||
|
}
|
||||||
|
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 !filepath.IsAbs(otherfile) {
|
||||||
|
otherfile = filepath.Join(dir, 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])
|
||||||
|
if bytes.HasPrefix(val, bDQuote) {
|
||||||
|
val = bytes.Trim(val, `"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.data[section][key] = ExpandValueEnv(string(val))
|
||||||
|
if comment.Len() > 0 {
|
||||||
|
cfg.keyComment[section+"."+key] = comment.String()
|
||||||
|
comment.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseData parse ini the data
|
||||||
|
// When include other.conf,other.conf is either absolute directory
|
||||||
|
// or under beego in default temporary directory(/tmp/beego[-username]).
|
||||||
|
func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
|
||||||
|
dir := "beego"
|
||||||
|
currentUser, err := user.Current()
|
||||||
|
if err == nil {
|
||||||
|
dir = "beego-" + currentUser.Username
|
||||||
|
}
|
||||||
|
dir = filepath.Join(os.TempDir(), dir)
|
||||||
|
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ini.parseData(dir, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IniConfigContainer A Config represents the ini configuration.
|
||||||
|
// When set and get value, support key as section:name type.
|
||||||
|
type IniConfigContainer struct {
|
||||||
|
data map[string]map[string]string // section=> key:val
|
||||||
|
sectionComment map[string]string // section : comment
|
||||||
|
keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment.
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the boolean value for a given key.
|
||||||
|
func (c *IniConfigContainer) Bool(key string) (bool, error) {
|
||||||
|
return ParseBool(c.getdata(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultBool returns the boolean value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||||
|
v, err := c.Bool(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns the integer value for a given key.
|
||||||
|
func (c *IniConfigContainer) Int(key string) (int, error) {
|
||||||
|
return strconv.Atoi(c.getdata(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt returns the integer value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
|
v, err := c.Int(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns the int64 value for a given key.
|
||||||
|
func (c *IniConfigContainer) Int64(key string) (int64, error) {
|
||||||
|
return strconv.ParseInt(c.getdata(key), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
|
v, err := c.Int64(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float returns the float value for a given key.
|
||||||
|
func (c *IniConfigContainer) Float(key string) (float64, error) {
|
||||||
|
return strconv.ParseFloat(c.getdata(key), 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
|
v, err := c.Float(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string value for a given key.
|
||||||
|
func (c *IniConfigContainer) String(key string) string {
|
||||||
|
return c.getdata(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultString returns the string value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings returns the []string value for a given key.
|
||||||
|
// Return nil if config value does not exist or is empty.
|
||||||
|
func (c *IniConfigContainer) Strings(key string) []string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return strings.Split(v, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultStrings returns the []string value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
|
v := c.Strings(key)
|
||||||
|
if v == nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSection returns map for the given section
|
||||||
|
func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) {
|
||||||
|
if v, ok := c.data[section]; ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("not exist section")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfigFile save the config into file.
|
||||||
|
//
|
||||||
|
// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Function.
|
||||||
|
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||||
|
// Write configuration file by filename.
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Get section or key comments. Fixed #1607
|
||||||
|
getCommentStr := func(section, key string) string {
|
||||||
|
var (
|
||||||
|
comment string
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
if len(key) == 0 {
|
||||||
|
comment, ok = c.sectionComment[section]
|
||||||
|
} else {
|
||||||
|
comment, ok = c.keyComment[section+"."+key]
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
// Empty comment
|
||||||
|
if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 {
|
||||||
|
return string(bNumComment)
|
||||||
|
}
|
||||||
|
prefix := string(bNumComment)
|
||||||
|
// Add the line head character "#"
|
||||||
|
return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
// Save default section at first place
|
||||||
|
if dt, ok := c.data[defaultSection]; ok {
|
||||||
|
for key, val := range dt {
|
||||||
|
if key != " " {
|
||||||
|
// Write key comments.
|
||||||
|
if v := getCommentStr(defaultSection, key); len(v) > 0 {
|
||||||
|
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write key and value.
|
||||||
|
if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put a line between sections.
|
||||||
|
if _, err = buf.WriteString(lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save named sections
|
||||||
|
for section, dt := range c.data {
|
||||||
|
if section != defaultSection {
|
||||||
|
// Write section comments.
|
||||||
|
if v := getCommentStr(section, ""); len(v) > 0 {
|
||||||
|
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write section name.
|
||||||
|
if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range dt {
|
||||||
|
if key != " " {
|
||||||
|
// Write key comments.
|
||||||
|
if v := getCommentStr(section, key); len(v) > 0 {
|
||||||
|
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write key and value.
|
||||||
|
if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put a line between sections.
|
||||||
|
if _, err = buf.WriteString(lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = buf.WriteTo(f)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set writes a new value for key.
|
||||||
|
// if write to one section, the key need be "section::key".
|
||||||
|
// if the section is not existed, it panics.
|
||||||
|
func (c *IniConfigContainer) Set(key, value string) error {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
if len(key) == 0 {
|
||||||
|
return errors.New("key is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
section, k string
|
||||||
|
sectionKey = strings.Split(strings.ToLower(key), "::")
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(sectionKey) >= 2 {
|
||||||
|
section = sectionKey[0]
|
||||||
|
k = sectionKey[1]
|
||||||
|
} else {
|
||||||
|
section = defaultSection
|
||||||
|
k = sectionKey[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := c.data[section]; !ok {
|
||||||
|
c.data[section] = make(map[string]string)
|
||||||
|
}
|
||||||
|
c.data[section][k] = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DIY returns the raw value by a given key.
|
||||||
|
func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
|
||||||
|
if v, ok := c.data[strings.ToLower(key)]; ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return v, errors.New("key not find")
|
||||||
|
}
|
||||||
|
|
||||||
|
// section.key or key
|
||||||
|
func (c *IniConfigContainer) getdata(key string) string {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
var (
|
||||||
|
section, k string
|
||||||
|
sectionKey = strings.Split(strings.ToLower(key), "::")
|
||||||
|
)
|
||||||
|
if len(sectionKey) >= 2 {
|
||||||
|
section = sectionKey[0]
|
||||||
|
k = sectionKey[1]
|
||||||
|
} else {
|
||||||
|
section = defaultSection
|
||||||
|
k = sectionKey[0]
|
||||||
|
}
|
||||||
|
if v, ok := c.data[section]; ok {
|
||||||
|
if vv, ok := v[k]; ok {
|
||||||
|
return vv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register("ini", &IniConfig{})
|
||||||
|
}
|
190
pkg/config/ini_test.go
Normal file
190
pkg/config/ini_test.go
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
// 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIni(t *testing.T) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
inicontext = `
|
||||||
|
;comment one
|
||||||
|
#comment two
|
||||||
|
appname = beeapi
|
||||||
|
httpport = 8080
|
||||||
|
mysqlport = 3600
|
||||||
|
PI = 3.1415976
|
||||||
|
runmode = "dev"
|
||||||
|
autorender = false
|
||||||
|
copyrequestbody = true
|
||||||
|
session= on
|
||||||
|
cookieon= off
|
||||||
|
newreg = OFF
|
||||||
|
needlogin = ON
|
||||||
|
enableSession = Y
|
||||||
|
enableCookie = N
|
||||||
|
flag = 1
|
||||||
|
path1 = ${GOPATH}
|
||||||
|
path2 = ${GOPATH||/home/go}
|
||||||
|
[demo]
|
||||||
|
key1="asta"
|
||||||
|
key2 = "xie"
|
||||||
|
CaseInsensitive = true
|
||||||
|
peers = one;two;three
|
||||||
|
password = ${GOPATH}
|
||||||
|
`
|
||||||
|
|
||||||
|
keyValue = map[string]interface{}{
|
||||||
|
"appname": "beeapi",
|
||||||
|
"httpport": 8080,
|
||||||
|
"mysqlport": int64(3600),
|
||||||
|
"pi": 3.1415976,
|
||||||
|
"runmode": "dev",
|
||||||
|
"autorender": false,
|
||||||
|
"copyrequestbody": true,
|
||||||
|
"session": true,
|
||||||
|
"cookieon": false,
|
||||||
|
"newreg": false,
|
||||||
|
"needlogin": true,
|
||||||
|
"enableSession": true,
|
||||||
|
"enableCookie": false,
|
||||||
|
"flag": true,
|
||||||
|
"path1": os.Getenv("GOPATH"),
|
||||||
|
"path2": os.Getenv("GOPATH"),
|
||||||
|
"demo::key1": "asta",
|
||||||
|
"demo::key2": "xie",
|
||||||
|
"demo::CaseInsensitive": true,
|
||||||
|
"demo::peers": []string{"one", "two", "three"},
|
||||||
|
"demo::password": os.Getenv("GOPATH"),
|
||||||
|
"null": "",
|
||||||
|
"demo2::key1": "",
|
||||||
|
"error": "",
|
||||||
|
"emptystrings": []string{},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
f, err := os.Create("testini.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = f.WriteString(inicontext)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove("testini.conf")
|
||||||
|
iniconf, err := NewConfig("ini", "testini.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for k, v := range keyValue {
|
||||||
|
var err error
|
||||||
|
var value interface{}
|
||||||
|
switch v.(type) {
|
||||||
|
case int:
|
||||||
|
value, err = iniconf.Int(k)
|
||||||
|
case int64:
|
||||||
|
value, err = iniconf.Int64(k)
|
||||||
|
case float64:
|
||||||
|
value, err = iniconf.Float(k)
|
||||||
|
case bool:
|
||||||
|
value, err = iniconf.Bool(k)
|
||||||
|
case []string:
|
||||||
|
value = iniconf.Strings(k)
|
||||||
|
case string:
|
||||||
|
value = iniconf.String(k)
|
||||||
|
default:
|
||||||
|
value, err = iniconf.DIY(k)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("get key %q value fail,err %s", k, err)
|
||||||
|
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
|
||||||
|
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if err = iniconf.Set("name", "astaxie"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if iniconf.String("name") != "astaxie" {
|
||||||
|
t.Fatal("get name error")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIniSave(t *testing.T) {
|
||||||
|
|
||||||
|
const (
|
||||||
|
inicontext = `
|
||||||
|
app = app
|
||||||
|
;comment one
|
||||||
|
#comment two
|
||||||
|
# comment three
|
||||||
|
appname = beeapi
|
||||||
|
httpport = 8080
|
||||||
|
# DB Info
|
||||||
|
# enable db
|
||||||
|
[dbinfo]
|
||||||
|
# db type name
|
||||||
|
# suport mysql,sqlserver
|
||||||
|
name = mysql
|
||||||
|
`
|
||||||
|
|
||||||
|
saveResult = `
|
||||||
|
app=app
|
||||||
|
#comment one
|
||||||
|
#comment two
|
||||||
|
# comment three
|
||||||
|
appname=beeapi
|
||||||
|
httpport=8080
|
||||||
|
|
||||||
|
# DB Info
|
||||||
|
# enable db
|
||||||
|
[dbinfo]
|
||||||
|
# db type name
|
||||||
|
# suport mysql,sqlserver
|
||||||
|
name=mysql
|
||||||
|
`
|
||||||
|
)
|
||||||
|
cfg, err := NewConfigData("ini", []byte(inicontext))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
name := "newIniConfig.ini"
|
||||||
|
if err := cfg.SaveConfigFile(name); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(name)
|
||||||
|
|
||||||
|
if data, err := ioutil.ReadFile(name); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
cfgData := string(data)
|
||||||
|
datas := strings.Split(saveResult, "\n")
|
||||||
|
for _, line := range datas {
|
||||||
|
if !strings.Contains(cfgData, line+"\n") {
|
||||||
|
t.Fatalf("different after save ini config file. need contains %q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
269
pkg/config/json.go
Normal file
269
pkg/config/json.go
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
// 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSONConfig is a json config parser and implements Config interface.
|
||||||
|
type JSONConfig struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns a ConfigContainer with parsed json config map.
|
||||||
|
func (js *JSONConfig) Parse(filename string) (Configer, error) {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
content, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return js.ParseData(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseData returns a ConfigContainer with json string
|
||||||
|
func (js *JSONConfig) ParseData(data []byte) (Configer, error) {
|
||||||
|
x := &JSONConfigContainer{
|
||||||
|
data: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(data, &x.data)
|
||||||
|
if err != nil {
|
||||||
|
var wrappingArray []interface{}
|
||||||
|
err2 := json.Unmarshal(data, &wrappingArray)
|
||||||
|
if err2 != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x.data["rootArray"] = wrappingArray
|
||||||
|
}
|
||||||
|
|
||||||
|
x.data = ExpandValueEnvForMap(x.data)
|
||||||
|
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONConfigContainer A Config represents the json configuration.
|
||||||
|
// Only when get value, support key as section:name type.
|
||||||
|
type JSONConfigContainer struct {
|
||||||
|
data map[string]interface{}
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the boolean value for a given key.
|
||||||
|
func (c *JSONConfigContainer) Bool(key string) (bool, error) {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
return ParseBool(val)
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("not exist key: %q", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultBool return the bool value if has no error
|
||||||
|
// otherwise return the defaultval
|
||||||
|
func (c *JSONConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||||
|
if v, err := c.Bool(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns the integer value for a given key.
|
||||||
|
func (c *JSONConfigContainer) Int(key string) (int, error) {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
if v, ok := val.(float64); ok {
|
||||||
|
return int(v), nil
|
||||||
|
} else if v, ok := val.(string); ok {
|
||||||
|
return strconv.Atoi(v)
|
||||||
|
}
|
||||||
|
return 0, errors.New("not valid value")
|
||||||
|
}
|
||||||
|
return 0, errors.New("not exist key:" + key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt returns the integer value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
|
if v, err := c.Int(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns the int64 value for a given key.
|
||||||
|
func (c *JSONConfigContainer) Int64(key string) (int64, error) {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
if v, ok := val.(float64); ok {
|
||||||
|
return int64(v), nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("not int64 value")
|
||||||
|
}
|
||||||
|
return 0, errors.New("not exist key:" + key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
|
if v, err := c.Int64(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float returns the float value for a given key.
|
||||||
|
func (c *JSONConfigContainer) Float(key string) (float64, error) {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
if v, ok := val.(float64); ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return 0.0, errors.New("not float64 value")
|
||||||
|
}
|
||||||
|
return 0.0, errors.New("not exist key:" + key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
|
if v, err := c.Float(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string value for a given key.
|
||||||
|
func (c *JSONConfigContainer) String(key string) string {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
if v, ok := val.(string); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultString returns the string value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
|
// TODO FIXME should not use "" to replace non existence
|
||||||
|
if v := c.String(key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings returns the []string value for a given key.
|
||||||
|
func (c *JSONConfigContainer) Strings(key string) []string {
|
||||||
|
stringVal := c.String(key)
|
||||||
|
if stringVal == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return strings.Split(c.String(key), ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultStrings returns the []string value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
|
if v := c.Strings(key); v != nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSection returns map for the given section
|
||||||
|
func (c *JSONConfigContainer) GetSection(section string) (map[string]string, error) {
|
||||||
|
if v, ok := c.data[section]; ok {
|
||||||
|
return v.(map[string]string), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("nonexist section " + section)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfigFile save the config into file
|
||||||
|
func (c *JSONConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||||
|
// Write configuration file by filename.
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
b, err := json.MarshalIndent(c.data, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.Write(b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set writes a new value for key.
|
||||||
|
func (c *JSONConfigContainer) Set(key, val string) error {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
c.data[key] = val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DIY returns the raw value by a given key.
|
||||||
|
func (c *JSONConfigContainer) DIY(key string) (v interface{}, err error) {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("not exist key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// section.key or key
|
||||||
|
func (c *JSONConfigContainer) getData(key string) interface{} {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
sectionKeys := strings.Split(key, "::")
|
||||||
|
if len(sectionKeys) >= 2 {
|
||||||
|
curValue, ok := c.data[sectionKeys[0]]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, key := range sectionKeys[1:] {
|
||||||
|
if v, ok := curValue.(map[string]interface{}); ok {
|
||||||
|
if curValue, ok = v[key]; !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return curValue
|
||||||
|
}
|
||||||
|
if v, ok := c.data[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register("json", &JSONConfig{})
|
||||||
|
}
|
222
pkg/config/json_test.go
Normal file
222
pkg/config/json_test.go
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
// 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJsonStartsWithArray(t *testing.T) {
|
||||||
|
|
||||||
|
const jsoncontextwitharray = `[
|
||||||
|
{
|
||||||
|
"url": "user",
|
||||||
|
"serviceAPI": "http://www.test.com/user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "employee",
|
||||||
|
"serviceAPI": "http://www.test.com/employee"
|
||||||
|
}
|
||||||
|
]`
|
||||||
|
f, err := os.Create("testjsonWithArray.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = f.WriteString(jsoncontextwitharray)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove("testjsonWithArray.conf")
|
||||||
|
jsonconf, err := NewConfig("json", "testjsonWithArray.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rootArray, err := jsonconf.DIY("rootArray")
|
||||||
|
if err != nil {
|
||||||
|
t.Error("array does not exist as element")
|
||||||
|
}
|
||||||
|
rootArrayCasted := rootArray.([]interface{})
|
||||||
|
if rootArrayCasted == nil {
|
||||||
|
t.Error("array from root is nil")
|
||||||
|
} else {
|
||||||
|
elem := rootArrayCasted[0].(map[string]interface{})
|
||||||
|
if elem["url"] != "user" || elem["serviceAPI"] != "http://www.test.com/user" {
|
||||||
|
t.Error("array[0] values are not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
elem2 := rootArrayCasted[1].(map[string]interface{})
|
||||||
|
if elem2["url"] != "employee" || elem2["serviceAPI"] != "http://www.test.com/employee" {
|
||||||
|
t.Error("array[1] values are not valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJson(t *testing.T) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
jsoncontext = `{
|
||||||
|
"appname": "beeapi",
|
||||||
|
"testnames": "foo;bar",
|
||||||
|
"httpport": 8080,
|
||||||
|
"mysqlport": 3600,
|
||||||
|
"PI": 3.1415976,
|
||||||
|
"runmode": "dev",
|
||||||
|
"autorender": false,
|
||||||
|
"copyrequestbody": true,
|
||||||
|
"session": "on",
|
||||||
|
"cookieon": "off",
|
||||||
|
"newreg": "OFF",
|
||||||
|
"needlogin": "ON",
|
||||||
|
"enableSession": "Y",
|
||||||
|
"enableCookie": "N",
|
||||||
|
"flag": 1,
|
||||||
|
"path1": "${GOPATH}",
|
||||||
|
"path2": "${GOPATH||/home/go}",
|
||||||
|
"database": {
|
||||||
|
"host": "host",
|
||||||
|
"port": "port",
|
||||||
|
"database": "database",
|
||||||
|
"username": "username",
|
||||||
|
"password": "${GOPATH}",
|
||||||
|
"conns":{
|
||||||
|
"maxconnection":12,
|
||||||
|
"autoconnect":true,
|
||||||
|
"connectioninfo":"info",
|
||||||
|
"root": "${GOPATH}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
keyValue = map[string]interface{}{
|
||||||
|
"appname": "beeapi",
|
||||||
|
"testnames": []string{"foo", "bar"},
|
||||||
|
"httpport": 8080,
|
||||||
|
"mysqlport": int64(3600),
|
||||||
|
"PI": 3.1415976,
|
||||||
|
"runmode": "dev",
|
||||||
|
"autorender": false,
|
||||||
|
"copyrequestbody": true,
|
||||||
|
"session": true,
|
||||||
|
"cookieon": false,
|
||||||
|
"newreg": false,
|
||||||
|
"needlogin": true,
|
||||||
|
"enableSession": true,
|
||||||
|
"enableCookie": false,
|
||||||
|
"flag": true,
|
||||||
|
"path1": os.Getenv("GOPATH"),
|
||||||
|
"path2": os.Getenv("GOPATH"),
|
||||||
|
"database::host": "host",
|
||||||
|
"database::port": "port",
|
||||||
|
"database::database": "database",
|
||||||
|
"database::password": os.Getenv("GOPATH"),
|
||||||
|
"database::conns::maxconnection": 12,
|
||||||
|
"database::conns::autoconnect": true,
|
||||||
|
"database::conns::connectioninfo": "info",
|
||||||
|
"database::conns::root": os.Getenv("GOPATH"),
|
||||||
|
"unknown": "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
f, err := os.Create("testjson.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = f.WriteString(jsoncontext)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove("testjson.conf")
|
||||||
|
jsonconf, err := NewConfig("json", "testjson.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range keyValue {
|
||||||
|
var err error
|
||||||
|
var value interface{}
|
||||||
|
switch v.(type) {
|
||||||
|
case int:
|
||||||
|
value, err = jsonconf.Int(k)
|
||||||
|
case int64:
|
||||||
|
value, err = jsonconf.Int64(k)
|
||||||
|
case float64:
|
||||||
|
value, err = jsonconf.Float(k)
|
||||||
|
case bool:
|
||||||
|
value, err = jsonconf.Bool(k)
|
||||||
|
case []string:
|
||||||
|
value = jsonconf.Strings(k)
|
||||||
|
case string:
|
||||||
|
value = jsonconf.String(k)
|
||||||
|
default:
|
||||||
|
value, err = jsonconf.DIY(k)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("get key %q value fatal,%v err %s", k, v, err)
|
||||||
|
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
|
||||||
|
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if err = jsonconf.Set("name", "astaxie"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if jsonconf.String("name") != "astaxie" {
|
||||||
|
t.Fatal("get name error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if db, err := jsonconf.DIY("database"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if m, ok := db.(map[string]interface{}); !ok {
|
||||||
|
t.Log(db)
|
||||||
|
t.Fatal("db not map[string]interface{}")
|
||||||
|
} else {
|
||||||
|
if m["host"].(string) != "host" {
|
||||||
|
t.Fatal("get host err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := jsonconf.Int("unknown"); err == nil {
|
||||||
|
t.Error("unknown keys should return an error when expecting an Int")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := jsonconf.Int64("unknown"); err == nil {
|
||||||
|
t.Error("unknown keys should return an error when expecting an Int64")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := jsonconf.Float("unknown"); err == nil {
|
||||||
|
t.Error("unknown keys should return an error when expecting a Float")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := jsonconf.DIY("unknown"); err == nil {
|
||||||
|
t.Error("unknown keys should return an error when expecting an interface{}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := jsonconf.String("unknown"); val != "" {
|
||||||
|
t.Error("unknown keys should return an empty string when expecting a String")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := jsonconf.Bool("unknown"); err == nil {
|
||||||
|
t.Error("unknown keys should return an error when expecting a Bool")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !jsonconf.DefaultBool("unknown", true) {
|
||||||
|
t.Error("unknown keys with default value wrong")
|
||||||
|
}
|
||||||
|
}
|
228
pkg/config/xml/xml.go
Normal file
228
pkg/config/xml/xml.go
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
// 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 xml for config provider.
|
||||||
|
//
|
||||||
|
// depend on github.com/beego/x2j.
|
||||||
|
//
|
||||||
|
// go install github.com/beego/x2j.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// import(
|
||||||
|
// _ "github.com/astaxie/beego/config/xml"
|
||||||
|
// "github.com/astaxie/beego/config"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// cnf, err := config.NewConfig("xml", "config.xml")
|
||||||
|
//
|
||||||
|
//More docs http://beego.me/docs/module/config.md
|
||||||
|
package xml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/config"
|
||||||
|
"github.com/beego/x2j"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is a xml config parser and implements Config interface.
|
||||||
|
// xml configurations should be included in <config></config> tag.
|
||||||
|
// only support key/value pair as <key>value</key> as each item.
|
||||||
|
type Config struct{}
|
||||||
|
|
||||||
|
// Parse returns a ConfigContainer with parsed xml config map.
|
||||||
|
func (xc *Config) Parse(filename string) (config.Configer, error) {
|
||||||
|
context, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return xc.ParseData(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseData xml data
|
||||||
|
func (xc *Config) ParseData(data []byte) (config.Configer, error) {
|
||||||
|
x := &ConfigContainer{data: make(map[string]interface{})}
|
||||||
|
|
||||||
|
d, err := x2j.DocToMap(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
x.data = config.ExpandValueEnvForMap(d["config"].(map[string]interface{}))
|
||||||
|
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigContainer A Config represents the xml configuration.
|
||||||
|
type ConfigContainer struct {
|
||||||
|
data map[string]interface{}
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the boolean value for a given key.
|
||||||
|
func (c *ConfigContainer) Bool(key string) (bool, error) {
|
||||||
|
if v := c.data[key]; v != nil {
|
||||||
|
return config.ParseBool(v)
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("not exist key: %q", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultBool return the bool value if has no error
|
||||||
|
// otherwise return the defaultval
|
||||||
|
func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||||
|
v, err := c.Bool(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns the integer value for a given key.
|
||||||
|
func (c *ConfigContainer) Int(key string) (int, error) {
|
||||||
|
return strconv.Atoi(c.data[key].(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt returns the integer value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
|
v, err := c.Int(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns the int64 value for a given key.
|
||||||
|
func (c *ConfigContainer) Int64(key string) (int64, error) {
|
||||||
|
return strconv.ParseInt(c.data[key].(string), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
|
v, err := c.Int64(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float returns the float value for a given key.
|
||||||
|
func (c *ConfigContainer) Float(key string) (float64, error) {
|
||||||
|
return strconv.ParseFloat(c.data[key].(string), 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
|
v, err := c.Float(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string value for a given key.
|
||||||
|
func (c *ConfigContainer) String(key string) string {
|
||||||
|
if v, ok := c.data[key].(string); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultString returns the string value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings returns the []string value for a given key.
|
||||||
|
func (c *ConfigContainer) Strings(key string) []string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return strings.Split(v, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultStrings returns the []string value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
|
v := c.Strings(key)
|
||||||
|
if v == nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSection returns map for the given section
|
||||||
|
func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
|
||||||
|
if v, ok := c.data[section].(map[string]interface{}); ok {
|
||||||
|
mapstr := make(map[string]string)
|
||||||
|
for k, val := range v {
|
||||||
|
mapstr[k] = config.ToString(val)
|
||||||
|
}
|
||||||
|
return mapstr, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("section '%s' not found", section)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfigFile save the config into file
|
||||||
|
func (c *ConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||||
|
// Write configuration file by filename.
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
b, err := xml.MarshalIndent(c.data, " ", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.Write(b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set writes a new value for key.
|
||||||
|
func (c *ConfigContainer) Set(key, val string) error {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
c.data[key] = val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DIY returns the raw value by a given key.
|
||||||
|
func (c *ConfigContainer) DIY(key string) (v interface{}, err error) {
|
||||||
|
if v, ok := c.data[key]; ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("not exist key")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
config.Register("xml", &Config{})
|
||||||
|
}
|
125
pkg/config/xml/xml_test.go
Normal file
125
pkg/config/xml/xml_test.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// 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 xml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestXML(t *testing.T) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
//xml parse should incluce in <config></config> tags
|
||||||
|
xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<config>
|
||||||
|
<appname>beeapi</appname>
|
||||||
|
<httpport>8080</httpport>
|
||||||
|
<mysqlport>3600</mysqlport>
|
||||||
|
<PI>3.1415976</PI>
|
||||||
|
<runmode>dev</runmode>
|
||||||
|
<autorender>false</autorender>
|
||||||
|
<copyrequestbody>true</copyrequestbody>
|
||||||
|
<path1>${GOPATH}</path1>
|
||||||
|
<path2>${GOPATH||/home/go}</path2>
|
||||||
|
<mysection>
|
||||||
|
<id>1</id>
|
||||||
|
<name>MySection</name>
|
||||||
|
</mysection>
|
||||||
|
</config>
|
||||||
|
`
|
||||||
|
keyValue = map[string]interface{}{
|
||||||
|
"appname": "beeapi",
|
||||||
|
"httpport": 8080,
|
||||||
|
"mysqlport": int64(3600),
|
||||||
|
"PI": 3.1415976,
|
||||||
|
"runmode": "dev",
|
||||||
|
"autorender": false,
|
||||||
|
"copyrequestbody": true,
|
||||||
|
"path1": os.Getenv("GOPATH"),
|
||||||
|
"path2": os.Getenv("GOPATH"),
|
||||||
|
"error": "",
|
||||||
|
"emptystrings": []string{},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
f, err := os.Create("testxml.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = f.WriteString(xmlcontext)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove("testxml.conf")
|
||||||
|
|
||||||
|
xmlconf, err := config.NewConfig("xml", "testxml.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xmlsection map[string]string
|
||||||
|
xmlsection, err = xmlconf.GetSection("mysection")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(xmlsection) == 0 {
|
||||||
|
t.Error("section should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range keyValue {
|
||||||
|
|
||||||
|
var (
|
||||||
|
value interface{}
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
switch v.(type) {
|
||||||
|
case int:
|
||||||
|
value, err = xmlconf.Int(k)
|
||||||
|
case int64:
|
||||||
|
value, err = xmlconf.Int64(k)
|
||||||
|
case float64:
|
||||||
|
value, err = xmlconf.Float(k)
|
||||||
|
case bool:
|
||||||
|
value, err = xmlconf.Bool(k)
|
||||||
|
case []string:
|
||||||
|
value = xmlconf.Strings(k)
|
||||||
|
case string:
|
||||||
|
value = xmlconf.String(k)
|
||||||
|
default:
|
||||||
|
value, err = xmlconf.DIY(k)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("get key %q value fatal,%v err %s", k, v, err)
|
||||||
|
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
|
||||||
|
t.Errorf("get key %q value, want %v got %v .", k, v, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = xmlconf.Set("name", "astaxie"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if xmlconf.String("name") != "astaxie" {
|
||||||
|
t.Fatal("get name error")
|
||||||
|
}
|
||||||
|
}
|
316
pkg/config/yaml/yaml.go
Normal file
316
pkg/config/yaml/yaml.go
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
// 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 yaml for config provider
|
||||||
|
//
|
||||||
|
// depend on github.com/beego/goyaml2
|
||||||
|
//
|
||||||
|
// go install github.com/beego/goyaml2
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// import(
|
||||||
|
// _ "github.com/astaxie/beego/config/yaml"
|
||||||
|
// "github.com/astaxie/beego/config"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// cnf, err := config.NewConfig("yaml", "config.yaml")
|
||||||
|
//
|
||||||
|
//More docs http://beego.me/docs/module/config.md
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/config"
|
||||||
|
"github.com/beego/goyaml2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is a yaml config parser and implements Config interface.
|
||||||
|
type Config struct{}
|
||||||
|
|
||||||
|
// Parse returns a ConfigContainer with parsed yaml config map.
|
||||||
|
func (yaml *Config) Parse(filename string) (y config.Configer, err error) {
|
||||||
|
cnf, err := ReadYmlReader(filename)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
y = &ConfigContainer{
|
||||||
|
data: cnf,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseData parse yaml data
|
||||||
|
func (yaml *Config) ParseData(data []byte) (config.Configer, error) {
|
||||||
|
cnf, err := parseYML(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ConfigContainer{
|
||||||
|
data: cnf,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadYmlReader Read yaml file to map.
|
||||||
|
// if json like, use json package, unless goyaml2 package.
|
||||||
|
func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
|
||||||
|
buf, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseYML(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseYML parse yaml formatted []byte to map.
|
||||||
|
func parseYML(buf []byte) (cnf map[string]interface{}, err error) {
|
||||||
|
if len(buf) < 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(buf[0:1]) == "{" {
|
||||||
|
log.Println("Look like a Json, try json umarshal")
|
||||||
|
err = json.Unmarshal(buf, &cnf)
|
||||||
|
if err == nil {
|
||||||
|
log.Println("It is Json Map")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := goyaml2.Read(bytes.NewReader(buf))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Goyaml2 ERR>", string(buf), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if data == nil {
|
||||||
|
log.Println("Goyaml2 output nil? Pls report bug\n" + string(buf))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cnf, ok := data.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
log.Println("Not a Map? >> ", string(buf), data)
|
||||||
|
cnf = nil
|
||||||
|
}
|
||||||
|
cnf = config.ExpandValueEnvForMap(cnf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigContainer A Config represents the yaml configuration.
|
||||||
|
type ConfigContainer struct {
|
||||||
|
data map[string]interface{}
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the boolean value for a given key.
|
||||||
|
func (c *ConfigContainer) Bool(key string) (bool, error) {
|
||||||
|
v, err := c.getData(key)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return config.ParseBool(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultBool return the bool value if has no error
|
||||||
|
// otherwise return the defaultval
|
||||||
|
func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||||
|
v, err := c.Bool(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns the integer value for a given key.
|
||||||
|
func (c *ConfigContainer) Int(key string) (int, error) {
|
||||||
|
if v, err := c.getData(key); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if vv, ok := v.(int); ok {
|
||||||
|
return vv, nil
|
||||||
|
} else if vv, ok := v.(int64); ok {
|
||||||
|
return int(vv), nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("not int value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt returns the integer value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
|
v, err := c.Int(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns the int64 value for a given key.
|
||||||
|
func (c *ConfigContainer) Int64(key string) (int64, error) {
|
||||||
|
if v, err := c.getData(key); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if vv, ok := v.(int64); ok {
|
||||||
|
return vv, nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("not bool value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
|
v, err := c.Int64(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float returns the float value for a given key.
|
||||||
|
func (c *ConfigContainer) Float(key string) (float64, error) {
|
||||||
|
if v, err := c.getData(key); err != nil {
|
||||||
|
return 0.0, err
|
||||||
|
} else if vv, ok := v.(float64); ok {
|
||||||
|
return vv, nil
|
||||||
|
} else if vv, ok := v.(int); ok {
|
||||||
|
return float64(vv), nil
|
||||||
|
} else if vv, ok := v.(int64); ok {
|
||||||
|
return float64(vv), nil
|
||||||
|
}
|
||||||
|
return 0.0, errors.New("not float64 value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
|
v, err := c.Float(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string value for a given key.
|
||||||
|
func (c *ConfigContainer) String(key string) string {
|
||||||
|
if v, err := c.getData(key); err == nil {
|
||||||
|
if vv, ok := v.(string); ok {
|
||||||
|
return vv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultString returns the string value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings returns the []string value for a given key.
|
||||||
|
func (c *ConfigContainer) Strings(key string) []string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return strings.Split(v, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultStrings returns the []string value for a given key.
|
||||||
|
// if err != nil return defaultval
|
||||||
|
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
|
v := c.Strings(key)
|
||||||
|
if v == nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSection returns map for the given section
|
||||||
|
func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
|
||||||
|
|
||||||
|
if v, ok := c.data[section]; ok {
|
||||||
|
return v.(map[string]string), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("not exist section")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfigFile save the config into file
|
||||||
|
func (c *ConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||||
|
// Write configuration file by filename.
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
err = goyaml2.Write(f, c.data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set writes a new value for key.
|
||||||
|
func (c *ConfigContainer) Set(key, val string) error {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
c.data[key] = val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DIY returns the raw value by a given key.
|
||||||
|
func (c *ConfigContainer) DIY(key string) (v interface{}, err error) {
|
||||||
|
return c.getData(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConfigContainer) getData(key string) (interface{}, error) {
|
||||||
|
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil, errors.New("key is empty")
|
||||||
|
}
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
keys := strings.Split(key, ".")
|
||||||
|
tmpData := c.data
|
||||||
|
for idx, k := range keys {
|
||||||
|
if v, ok := tmpData[k]; ok {
|
||||||
|
switch v.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
{
|
||||||
|
tmpData = v.(map[string]interface{})
|
||||||
|
if idx == len(keys) - 1 {
|
||||||
|
return tmpData, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not exist key %q", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
config.Register("yaml", &Config{})
|
||||||
|
}
|
115
pkg/config/yaml/yaml_test.go
Normal file
115
pkg/config/yaml/yaml_test.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// 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 yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestYaml(t *testing.T) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
yamlcontext = `
|
||||||
|
"appname": beeapi
|
||||||
|
"httpport": 8080
|
||||||
|
"mysqlport": 3600
|
||||||
|
"PI": 3.1415976
|
||||||
|
"runmode": dev
|
||||||
|
"autorender": false
|
||||||
|
"copyrequestbody": true
|
||||||
|
"PATH": GOPATH
|
||||||
|
"path1": ${GOPATH}
|
||||||
|
"path2": ${GOPATH||/home/go}
|
||||||
|
"empty": ""
|
||||||
|
`
|
||||||
|
|
||||||
|
keyValue = map[string]interface{}{
|
||||||
|
"appname": "beeapi",
|
||||||
|
"httpport": 8080,
|
||||||
|
"mysqlport": int64(3600),
|
||||||
|
"PI": 3.1415976,
|
||||||
|
"runmode": "dev",
|
||||||
|
"autorender": false,
|
||||||
|
"copyrequestbody": true,
|
||||||
|
"PATH": "GOPATH",
|
||||||
|
"path1": os.Getenv("GOPATH"),
|
||||||
|
"path2": os.Getenv("GOPATH"),
|
||||||
|
"error": "",
|
||||||
|
"emptystrings": []string{},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
f, err := os.Create("testyaml.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = f.WriteString(yamlcontext)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove("testyaml.conf")
|
||||||
|
yamlconf, err := config.NewConfig("yaml", "testyaml.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if yamlconf.String("appname") != "beeapi" {
|
||||||
|
t.Fatal("appname not equal to beeapi")
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range keyValue {
|
||||||
|
|
||||||
|
var (
|
||||||
|
value interface{}
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
switch v.(type) {
|
||||||
|
case int:
|
||||||
|
value, err = yamlconf.Int(k)
|
||||||
|
case int64:
|
||||||
|
value, err = yamlconf.Int64(k)
|
||||||
|
case float64:
|
||||||
|
value, err = yamlconf.Float(k)
|
||||||
|
case bool:
|
||||||
|
value, err = yamlconf.Bool(k)
|
||||||
|
case []string:
|
||||||
|
value = yamlconf.Strings(k)
|
||||||
|
case string:
|
||||||
|
value = yamlconf.String(k)
|
||||||
|
default:
|
||||||
|
value, err = yamlconf.DIY(k)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("get key %q value fatal,%v err %s", k, v, err)
|
||||||
|
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
|
||||||
|
t.Errorf("get key %q value, want %v got %v .", k, v, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = yamlconf.Set("name", "astaxie"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if yamlconf.String("name") != "astaxie" {
|
||||||
|
t.Fatal("get name error")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
146
pkg/config_test.go
Normal file
146
pkg/config_test.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDefaults(t *testing.T) {
|
||||||
|
if BConfig.WebConfig.FlashName != "BEEGO_FLASH" {
|
||||||
|
t.Errorf("FlashName was not set to default.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if BConfig.WebConfig.FlashSeparator != "BEEGOFLASH" {
|
||||||
|
t.Errorf("FlashName was not set to default.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAssignConfig_01(t *testing.T) {
|
||||||
|
_BConfig := &Config{}
|
||||||
|
_BConfig.AppName = "beego_test"
|
||||||
|
jcf := &config.JSONConfig{}
|
||||||
|
ac, _ := jcf.ParseData([]byte(`{"AppName":"beego_json"}`))
|
||||||
|
assignSingleConfig(_BConfig, ac)
|
||||||
|
if _BConfig.AppName != "beego_json" {
|
||||||
|
t.Log(_BConfig)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAssignConfig_02(t *testing.T) {
|
||||||
|
_BConfig := &Config{}
|
||||||
|
bs, _ := json.Marshal(newBConfig())
|
||||||
|
|
||||||
|
jsonMap := M{}
|
||||||
|
json.Unmarshal(bs, &jsonMap)
|
||||||
|
|
||||||
|
configMap := M{}
|
||||||
|
for k, v := range jsonMap {
|
||||||
|
if reflect.TypeOf(v).Kind() == reflect.Map {
|
||||||
|
for k1, v1 := range v.(M) {
|
||||||
|
if reflect.TypeOf(v1).Kind() == reflect.Map {
|
||||||
|
for k2, v2 := range v1.(M) {
|
||||||
|
configMap[k2] = v2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
configMap[k1] = v1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
configMap[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
configMap["MaxMemory"] = 1024
|
||||||
|
configMap["Graceful"] = true
|
||||||
|
configMap["XSRFExpire"] = 32
|
||||||
|
configMap["SessionProviderConfig"] = "file"
|
||||||
|
configMap["FileLineNum"] = true
|
||||||
|
|
||||||
|
jcf := &config.JSONConfig{}
|
||||||
|
bs, _ = json.Marshal(configMap)
|
||||||
|
ac, _ := jcf.ParseData(bs)
|
||||||
|
|
||||||
|
for _, i := range []interface{}{_BConfig, &_BConfig.Listen, &_BConfig.WebConfig, &_BConfig.Log, &_BConfig.WebConfig.Session} {
|
||||||
|
assignSingleConfig(i, ac)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _BConfig.MaxMemory != 1024 {
|
||||||
|
t.Log(_BConfig.MaxMemory)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !_BConfig.Listen.Graceful {
|
||||||
|
t.Log(_BConfig.Listen.Graceful)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _BConfig.WebConfig.XSRFExpire != 32 {
|
||||||
|
t.Log(_BConfig.WebConfig.XSRFExpire)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _BConfig.WebConfig.Session.SessionProviderConfig != "file" {
|
||||||
|
t.Log(_BConfig.WebConfig.Session.SessionProviderConfig)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !_BConfig.Log.FileLineNum {
|
||||||
|
t.Log(_BConfig.Log.FileLineNum)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAssignConfig_03(t *testing.T) {
|
||||||
|
jcf := &config.JSONConfig{}
|
||||||
|
ac, _ := jcf.ParseData([]byte(`{"AppName":"beego"}`))
|
||||||
|
ac.Set("AppName", "test_app")
|
||||||
|
ac.Set("RunMode", "online")
|
||||||
|
ac.Set("StaticDir", "download:down download2:down2")
|
||||||
|
ac.Set("StaticExtensionsToGzip", ".css,.js,.html,.jpg,.png")
|
||||||
|
ac.Set("StaticCacheFileSize", "87456")
|
||||||
|
ac.Set("StaticCacheFileNum", "1254")
|
||||||
|
assignConfig(ac)
|
||||||
|
|
||||||
|
t.Logf("%#v", BConfig)
|
||||||
|
|
||||||
|
if BConfig.AppName != "test_app" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if BConfig.RunMode != "online" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if BConfig.WebConfig.StaticDir["/download"] != "down" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if BConfig.WebConfig.StaticDir["/download2"] != "down2" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if BConfig.WebConfig.StaticCacheFileSize != 87456 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if BConfig.WebConfig.StaticCacheFileNum != 1254 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if len(BConfig.WebConfig.StaticExtensionsToGzip) != 5 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
232
pkg/context/acceptencoder.go
Normal file
232
pkg/context/acceptencoder.go
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
// Copyright 2015 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 context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/flate"
|
||||||
|
"compress/gzip"
|
||||||
|
"compress/zlib"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//Default size==20B same as nginx
|
||||||
|
defaultGzipMinLength = 20
|
||||||
|
//Content will only be compressed if content length is either unknown or greater than gzipMinLength.
|
||||||
|
gzipMinLength = defaultGzipMinLength
|
||||||
|
//The compression level used for deflate compression. (0-9).
|
||||||
|
gzipCompressLevel int
|
||||||
|
//List of HTTP methods to compress. If not set, only GET requests are compressed.
|
||||||
|
includedMethods map[string]bool
|
||||||
|
getMethodOnly bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitGzip init the gzipcompress
|
||||||
|
func InitGzip(minLength, compressLevel int, methods []string) {
|
||||||
|
if minLength >= 0 {
|
||||||
|
gzipMinLength = minLength
|
||||||
|
}
|
||||||
|
gzipCompressLevel = compressLevel
|
||||||
|
if gzipCompressLevel < flate.NoCompression || gzipCompressLevel > flate.BestCompression {
|
||||||
|
gzipCompressLevel = flate.BestSpeed
|
||||||
|
}
|
||||||
|
getMethodOnly = (len(methods) == 0) || (len(methods) == 1 && strings.ToUpper(methods[0]) == "GET")
|
||||||
|
includedMethods = make(map[string]bool, len(methods))
|
||||||
|
for _, v := range methods {
|
||||||
|
includedMethods[strings.ToUpper(v)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type resetWriter interface {
|
||||||
|
io.Writer
|
||||||
|
Reset(w io.Writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
type nopResetWriter struct {
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n nopResetWriter) Reset(w io.Writer) {
|
||||||
|
//do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
type acceptEncoder struct {
|
||||||
|
name string
|
||||||
|
levelEncode func(int) resetWriter
|
||||||
|
customCompressLevelPool *sync.Pool
|
||||||
|
bestCompressionPool *sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac acceptEncoder) encode(wr io.Writer, level int) resetWriter {
|
||||||
|
if ac.customCompressLevelPool == nil || ac.bestCompressionPool == nil {
|
||||||
|
return nopResetWriter{wr}
|
||||||
|
}
|
||||||
|
var rwr resetWriter
|
||||||
|
switch level {
|
||||||
|
case flate.BestSpeed:
|
||||||
|
rwr = ac.customCompressLevelPool.Get().(resetWriter)
|
||||||
|
case flate.BestCompression:
|
||||||
|
rwr = ac.bestCompressionPool.Get().(resetWriter)
|
||||||
|
default:
|
||||||
|
rwr = ac.levelEncode(level)
|
||||||
|
}
|
||||||
|
rwr.Reset(wr)
|
||||||
|
return rwr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac acceptEncoder) put(wr resetWriter, level int) {
|
||||||
|
if ac.customCompressLevelPool == nil || ac.bestCompressionPool == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wr.Reset(nil)
|
||||||
|
|
||||||
|
//notice
|
||||||
|
//compressionLevel==BestCompression DOES NOT MATTER
|
||||||
|
//sync.Pool will not memory leak
|
||||||
|
|
||||||
|
switch level {
|
||||||
|
case gzipCompressLevel:
|
||||||
|
ac.customCompressLevelPool.Put(wr)
|
||||||
|
case flate.BestCompression:
|
||||||
|
ac.bestCompressionPool.Put(wr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
noneCompressEncoder = acceptEncoder{"", nil, nil, nil}
|
||||||
|
gzipCompressEncoder = acceptEncoder{
|
||||||
|
name: "gzip",
|
||||||
|
levelEncode: func(level int) resetWriter { wr, _ := gzip.NewWriterLevel(nil, level); return wr },
|
||||||
|
customCompressLevelPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, gzipCompressLevel); return wr }},
|
||||||
|
bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, flate.BestCompression); return wr }},
|
||||||
|
}
|
||||||
|
|
||||||
|
//according to the sec :http://tools.ietf.org/html/rfc2616#section-3.5 ,the deflate compress in http is zlib indeed
|
||||||
|
//deflate
|
||||||
|
//The "zlib" format defined in RFC 1950 [31] in combination with
|
||||||
|
//the "deflate" compression mechanism described in RFC 1951 [29].
|
||||||
|
deflateCompressEncoder = acceptEncoder{
|
||||||
|
name: "deflate",
|
||||||
|
levelEncode: func(level int) resetWriter { wr, _ := zlib.NewWriterLevel(nil, level); return wr },
|
||||||
|
customCompressLevelPool: &sync.Pool{New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, gzipCompressLevel); return wr }},
|
||||||
|
bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, flate.BestCompression); return wr }},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
encoderMap = map[string]acceptEncoder{ // all the other compress methods will ignore
|
||||||
|
"gzip": gzipCompressEncoder,
|
||||||
|
"deflate": deflateCompressEncoder,
|
||||||
|
"*": gzipCompressEncoder, // * means any compress will accept,we prefer gzip
|
||||||
|
"identity": noneCompressEncoder, // identity means none-compress
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteFile reads from file and writes to writer by the specific encoding(gzip/deflate)
|
||||||
|
func WriteFile(encoding string, writer io.Writer, file *os.File) (bool, string, error) {
|
||||||
|
return writeLevel(encoding, writer, file, flate.BestCompression)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBody reads writes content to writer by the specific encoding(gzip/deflate)
|
||||||
|
func WriteBody(encoding string, writer io.Writer, content []byte) (bool, string, error) {
|
||||||
|
if encoding == "" || len(content) < gzipMinLength {
|
||||||
|
_, err := writer.Write(content)
|
||||||
|
return false, "", err
|
||||||
|
}
|
||||||
|
return writeLevel(encoding, writer, bytes.NewReader(content), gzipCompressLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeLevel reads from reader,writes to writer by specific encoding and compress level
|
||||||
|
// the compress level is defined by deflate package
|
||||||
|
func writeLevel(encoding string, writer io.Writer, reader io.Reader, level int) (bool, string, error) {
|
||||||
|
var outputWriter resetWriter
|
||||||
|
var err error
|
||||||
|
var ce = noneCompressEncoder
|
||||||
|
|
||||||
|
if cf, ok := encoderMap[encoding]; ok {
|
||||||
|
ce = cf
|
||||||
|
}
|
||||||
|
encoding = ce.name
|
||||||
|
outputWriter = ce.encode(writer, level)
|
||||||
|
defer ce.put(outputWriter, level)
|
||||||
|
|
||||||
|
_, err = io.Copy(outputWriter, reader)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch outputWriter.(type) {
|
||||||
|
case io.WriteCloser:
|
||||||
|
outputWriter.(io.WriteCloser).Close()
|
||||||
|
}
|
||||||
|
return encoding != "", encoding, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEncoding will extract the right encoding for response
|
||||||
|
// the Accept-Encoding's sec is here:
|
||||||
|
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
|
||||||
|
func ParseEncoding(r *http.Request) string {
|
||||||
|
if r == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if (getMethodOnly && r.Method == "GET") || includedMethods[r.Method] {
|
||||||
|
return parseEncoding(r)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type q struct {
|
||||||
|
name string
|
||||||
|
value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEncoding(r *http.Request) string {
|
||||||
|
acceptEncoding := r.Header.Get("Accept-Encoding")
|
||||||
|
if acceptEncoding == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var lastQ q
|
||||||
|
for _, v := range strings.Split(acceptEncoding, ",") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vs := strings.Split(v, ";")
|
||||||
|
var cf acceptEncoder
|
||||||
|
var ok bool
|
||||||
|
if cf, ok = encoderMap[vs[0]]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(vs) == 1 {
|
||||||
|
return cf.name
|
||||||
|
}
|
||||||
|
if len(vs) == 2 {
|
||||||
|
f, _ := strconv.ParseFloat(strings.Replace(vs[1], "q=", "", -1), 64)
|
||||||
|
if f == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if f > lastQ.value {
|
||||||
|
lastQ = q{cf.name, f}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastQ.name
|
||||||
|
}
|
59
pkg/context/acceptencoder_test.go
Normal file
59
pkg/context/acceptencoder_test.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2015 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 context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ExtractEncoding(t *testing.T) {
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip,deflate"}}}) != "gzip" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"deflate,gzip"}}}) != "deflate" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=.5,deflate"}}}) != "deflate" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=.5,deflate;q=0.3"}}}) != "gzip" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=0,deflate"}}}) != "deflate" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"deflate;q=0.5,gzip;q=0.5,identity"}}}) != "" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"*"}}}) != "gzip" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"x,gzip,deflate"}}}) != "gzip" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip,x,deflate"}}}) != "gzip" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=0.5,x,deflate"}}}) != "deflate" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"x"}}}) != "" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=0.5,x;q=0.8"}}}) != "gzip" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
263
pkg/context/context.go
Normal file
263
pkg/context/context.go
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
// 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 context provide the context utils
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// import "github.com/astaxie/beego/context"
|
||||||
|
//
|
||||||
|
// ctx := context.Context{Request:req,ResponseWriter:rw}
|
||||||
|
//
|
||||||
|
// more docs http://beego.me/docs/module/context.md
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
//commonly used mime-types
|
||||||
|
const (
|
||||||
|
ApplicationJSON = "application/json"
|
||||||
|
ApplicationXML = "application/xml"
|
||||||
|
ApplicationYAML = "application/x-yaml"
|
||||||
|
TextXML = "text/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewContext return the Context with Input and Output
|
||||||
|
func NewContext() *Context {
|
||||||
|
return &Context{
|
||||||
|
Input: NewInput(),
|
||||||
|
Output: NewOutput(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context Http request context struct including BeegoInput, BeegoOutput, http.Request and http.ResponseWriter.
|
||||||
|
// BeegoInput and BeegoOutput provides some api to operate request and response more easily.
|
||||||
|
type Context struct {
|
||||||
|
Input *BeegoInput
|
||||||
|
Output *BeegoOutput
|
||||||
|
Request *http.Request
|
||||||
|
ResponseWriter *Response
|
||||||
|
_xsrfToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset init Context, BeegoInput and BeegoOutput
|
||||||
|
func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx.Request = r
|
||||||
|
if ctx.ResponseWriter == nil {
|
||||||
|
ctx.ResponseWriter = &Response{}
|
||||||
|
}
|
||||||
|
ctx.ResponseWriter.reset(rw)
|
||||||
|
ctx.Input.Reset(ctx)
|
||||||
|
ctx.Output.Reset(ctx)
|
||||||
|
ctx._xsrfToken = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect does redirection to localurl with http header status code.
|
||||||
|
func (ctx *Context) Redirect(status int, localurl string) {
|
||||||
|
http.Redirect(ctx.ResponseWriter, ctx.Request, localurl, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort stops this request.
|
||||||
|
// if beego.ErrorMaps exists, panic body.
|
||||||
|
func (ctx *Context) Abort(status int, body string) {
|
||||||
|
ctx.Output.SetStatus(status)
|
||||||
|
panic(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString Write string to response body.
|
||||||
|
// it sends response body.
|
||||||
|
func (ctx *Context) WriteString(content string) {
|
||||||
|
ctx.ResponseWriter.Write([]byte(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCookie Get cookie from request by a given key.
|
||||||
|
// It's alias of BeegoInput.Cookie.
|
||||||
|
func (ctx *Context) GetCookie(key string) string {
|
||||||
|
return ctx.Input.Cookie(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCookie Set cookie for response.
|
||||||
|
// It's alias of BeegoOutput.Cookie.
|
||||||
|
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
|
||||||
|
ctx.Output.Cookie(name, value, others...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSecureCookie Get secure cookie from request by a given key.
|
||||||
|
func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
|
||||||
|
val := ctx.Input.Cookie(key)
|
||||||
|
if val == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(val, "|", 3)
|
||||||
|
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
vs := parts[0]
|
||||||
|
timestamp := parts[1]
|
||||||
|
sig := parts[2]
|
||||||
|
|
||||||
|
h := hmac.New(sha256.New, []byte(Secret))
|
||||||
|
fmt.Fprintf(h, "%s%s", vs, timestamp)
|
||||||
|
|
||||||
|
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
res, _ := base64.URLEncoding.DecodeString(vs)
|
||||||
|
return string(res), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSecureCookie Set Secure cookie for response.
|
||||||
|
func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {
|
||||||
|
vs := base64.URLEncoding.EncodeToString([]byte(value))
|
||||||
|
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
|
h := hmac.New(sha256.New, []byte(Secret))
|
||||||
|
fmt.Fprintf(h, "%s%s", vs, timestamp)
|
||||||
|
sig := fmt.Sprintf("%02x", h.Sum(nil))
|
||||||
|
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
|
||||||
|
ctx.Output.Cookie(name, cookie, others...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XSRFToken creates a xsrf token string and returns.
|
||||||
|
func (ctx *Context) XSRFToken(key string, expire int64) string {
|
||||||
|
if ctx._xsrfToken == "" {
|
||||||
|
token, ok := ctx.GetSecureCookie(key, "_xsrf")
|
||||||
|
if !ok {
|
||||||
|
token = string(utils.RandomCreateBytes(32))
|
||||||
|
ctx.SetSecureCookie(key, "_xsrf", token, expire)
|
||||||
|
}
|
||||||
|
ctx._xsrfToken = token
|
||||||
|
}
|
||||||
|
return ctx._xsrfToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckXSRFCookie checks xsrf token in this request is valid or not.
|
||||||
|
// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken"
|
||||||
|
// or in form field value named as "_xsrf".
|
||||||
|
func (ctx *Context) CheckXSRFCookie() bool {
|
||||||
|
token := ctx.Input.Query("_xsrf")
|
||||||
|
if token == "" {
|
||||||
|
token = ctx.Request.Header.Get("X-Xsrftoken")
|
||||||
|
}
|
||||||
|
if token == "" {
|
||||||
|
token = ctx.Request.Header.Get("X-Csrftoken")
|
||||||
|
}
|
||||||
|
if token == "" {
|
||||||
|
ctx.Abort(422, "422")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ctx._xsrfToken != token {
|
||||||
|
ctx.Abort(417, "417")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderMethodResult renders the return value of a controller method to the output
|
||||||
|
func (ctx *Context) RenderMethodResult(result interface{}) {
|
||||||
|
if result != nil {
|
||||||
|
renderer, ok := result.(Renderer)
|
||||||
|
if !ok {
|
||||||
|
err, ok := result.(error)
|
||||||
|
if ok {
|
||||||
|
renderer = errorRenderer(err)
|
||||||
|
} else {
|
||||||
|
renderer = jsonRenderer(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderer.Render(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Response is a wrapper for the http.ResponseWriter
|
||||||
|
//started set to true if response was written to then don't execute other handler
|
||||||
|
type Response struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
Started bool
|
||||||
|
Status int
|
||||||
|
Elapsed time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Response) reset(rw http.ResponseWriter) {
|
||||||
|
r.ResponseWriter = rw
|
||||||
|
r.Status = 0
|
||||||
|
r.Started = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the data to the connection as part of an HTTP reply,
|
||||||
|
// and sets `started` to true.
|
||||||
|
// started means the response has sent out.
|
||||||
|
func (r *Response) Write(p []byte) (int, error) {
|
||||||
|
r.Started = true
|
||||||
|
return r.ResponseWriter.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader sends an HTTP response header with status code,
|
||||||
|
// and sets `started` to true.
|
||||||
|
func (r *Response) WriteHeader(code int) {
|
||||||
|
if r.Status > 0 {
|
||||||
|
//prevent multiple response.WriteHeader calls
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.Status = code
|
||||||
|
r.Started = true
|
||||||
|
r.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hijack hijacker for http
|
||||||
|
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
hj, ok := r.ResponseWriter.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, errors.New("webserver doesn't support hijacking")
|
||||||
|
}
|
||||||
|
return hj.Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush http.Flusher
|
||||||
|
func (r *Response) Flush() {
|
||||||
|
if f, ok := r.ResponseWriter.(http.Flusher); ok {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseNotify http.CloseNotifier
|
||||||
|
func (r *Response) CloseNotify() <-chan bool {
|
||||||
|
if cn, ok := r.ResponseWriter.(http.CloseNotifier); ok {
|
||||||
|
return cn.CloseNotify()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pusher http.Pusher
|
||||||
|
func (r *Response) Pusher() (pusher http.Pusher) {
|
||||||
|
if pusher, ok := r.ResponseWriter.(http.Pusher); ok {
|
||||||
|
return pusher
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
47
pkg/context/context_test.go
Normal file
47
pkg/context/context_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2016 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 context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestXsrfReset_01(t *testing.T) {
|
||||||
|
r := &http.Request{}
|
||||||
|
c := NewContext()
|
||||||
|
c.Request = r
|
||||||
|
c.ResponseWriter = &Response{}
|
||||||
|
c.ResponseWriter.reset(httptest.NewRecorder())
|
||||||
|
c.Output.Reset(c)
|
||||||
|
c.Input.Reset(c)
|
||||||
|
c.XSRFToken("key", 16)
|
||||||
|
if c._xsrfToken == "" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
token := c._xsrfToken
|
||||||
|
c.Reset(&Response{ResponseWriter: httptest.NewRecorder()}, r)
|
||||||
|
if c._xsrfToken != "" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
c.XSRFToken("key", 16)
|
||||||
|
if c._xsrfToken == "" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if token == c._xsrfToken {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
689
pkg/context/input.go
Normal file
689
pkg/context/input.go
Normal file
@ -0,0 +1,689 @@
|
|||||||
|
// 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 context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Regexes for checking the accept headers
|
||||||
|
// TODO make sure these are correct
|
||||||
|
var (
|
||||||
|
acceptsHTMLRegex = regexp.MustCompile(`(text/html|application/xhtml\+xml)(?:,|$)`)
|
||||||
|
acceptsXMLRegex = regexp.MustCompile(`(application/xml|text/xml)(?:,|$)`)
|
||||||
|
acceptsJSONRegex = regexp.MustCompile(`(application/json)(?:,|$)`)
|
||||||
|
acceptsYAMLRegex = regexp.MustCompile(`(application/x-yaml)(?:,|$)`)
|
||||||
|
maxParam = 50
|
||||||
|
)
|
||||||
|
|
||||||
|
// BeegoInput operates the http request header, data, cookie and body.
|
||||||
|
// it also contains router params and current session.
|
||||||
|
type BeegoInput struct {
|
||||||
|
Context *Context
|
||||||
|
CruSession session.Store
|
||||||
|
pnames []string
|
||||||
|
pvalues []string
|
||||||
|
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
|
||||||
|
dataLock sync.RWMutex
|
||||||
|
RequestBody []byte
|
||||||
|
RunMethod string
|
||||||
|
RunController reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInput return BeegoInput generated by Context.
|
||||||
|
func NewInput() *BeegoInput {
|
||||||
|
return &BeegoInput{
|
||||||
|
pnames: make([]string, 0, maxParam),
|
||||||
|
pvalues: make([]string, 0, maxParam),
|
||||||
|
data: make(map[interface{}]interface{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset init the BeegoInput
|
||||||
|
func (input *BeegoInput) Reset(ctx *Context) {
|
||||||
|
input.Context = ctx
|
||||||
|
input.CruSession = nil
|
||||||
|
input.pnames = input.pnames[:0]
|
||||||
|
input.pvalues = input.pvalues[:0]
|
||||||
|
input.dataLock.Lock()
|
||||||
|
input.data = nil
|
||||||
|
input.dataLock.Unlock()
|
||||||
|
input.RequestBody = []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol returns request protocol name, such as HTTP/1.1 .
|
||||||
|
func (input *BeegoInput) Protocol() string {
|
||||||
|
return input.Context.Request.Proto
|
||||||
|
}
|
||||||
|
|
||||||
|
// URI returns full request url with query string, fragment.
|
||||||
|
func (input *BeegoInput) URI() string {
|
||||||
|
return input.Context.Request.RequestURI
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL returns request url path (without query string, fragment).
|
||||||
|
func (input *BeegoInput) URL() string {
|
||||||
|
return input.Context.Request.URL.EscapedPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Site returns base site url as scheme://domain type.
|
||||||
|
func (input *BeegoInput) Site() string {
|
||||||
|
return input.Scheme() + "://" + input.Domain()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scheme returns request scheme as "http" or "https".
|
||||||
|
func (input *BeegoInput) Scheme() string {
|
||||||
|
if scheme := input.Header("X-Forwarded-Proto"); scheme != "" {
|
||||||
|
return scheme
|
||||||
|
}
|
||||||
|
if input.Context.Request.URL.Scheme != "" {
|
||||||
|
return input.Context.Request.URL.Scheme
|
||||||
|
}
|
||||||
|
if input.Context.Request.TLS == nil {
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
return "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain returns host name.
|
||||||
|
// Alias of Host method.
|
||||||
|
func (input *BeegoInput) Domain() string {
|
||||||
|
return input.Host()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host returns host name.
|
||||||
|
// if no host info in request, return localhost.
|
||||||
|
func (input *BeegoInput) Host() string {
|
||||||
|
if input.Context.Request.Host != "" {
|
||||||
|
if hostPart, _, err := net.SplitHostPort(input.Context.Request.Host); err == nil {
|
||||||
|
return hostPart
|
||||||
|
}
|
||||||
|
return input.Context.Request.Host
|
||||||
|
}
|
||||||
|
return "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method returns http request method.
|
||||||
|
func (input *BeegoInput) Method() string {
|
||||||
|
return input.Context.Request.Method
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is returns boolean of this request is on given method, such as Is("POST").
|
||||||
|
func (input *BeegoInput) Is(method string) bool {
|
||||||
|
return input.Method() == method
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGet Is this a GET method request?
|
||||||
|
func (input *BeegoInput) IsGet() bool {
|
||||||
|
return input.Is("GET")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPost Is this a POST method request?
|
||||||
|
func (input *BeegoInput) IsPost() bool {
|
||||||
|
return input.Is("POST")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHead Is this a Head method request?
|
||||||
|
func (input *BeegoInput) IsHead() bool {
|
||||||
|
return input.Is("HEAD")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOptions Is this a OPTIONS method request?
|
||||||
|
func (input *BeegoInput) IsOptions() bool {
|
||||||
|
return input.Is("OPTIONS")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPut Is this a PUT method request?
|
||||||
|
func (input *BeegoInput) IsPut() bool {
|
||||||
|
return input.Is("PUT")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDelete Is this a DELETE method request?
|
||||||
|
func (input *BeegoInput) IsDelete() bool {
|
||||||
|
return input.Is("DELETE")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPatch Is this a PATCH method request?
|
||||||
|
func (input *BeegoInput) IsPatch() bool {
|
||||||
|
return input.Is("PATCH")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAjax returns boolean of this request is generated by ajax.
|
||||||
|
func (input *BeegoInput) IsAjax() bool {
|
||||||
|
return input.Header("X-Requested-With") == "XMLHttpRequest"
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSecure returns boolean of this request is in https.
|
||||||
|
func (input *BeegoInput) IsSecure() bool {
|
||||||
|
return input.Scheme() == "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWebsocket returns boolean of this request is in webSocket.
|
||||||
|
func (input *BeegoInput) IsWebsocket() bool {
|
||||||
|
return input.Header("Upgrade") == "websocket"
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUpload returns boolean of whether file uploads in this request or not..
|
||||||
|
func (input *BeegoInput) IsUpload() bool {
|
||||||
|
return strings.Contains(input.Header("Content-Type"), "multipart/form-data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptsHTML Checks if request accepts html response
|
||||||
|
func (input *BeegoInput) AcceptsHTML() bool {
|
||||||
|
return acceptsHTMLRegex.MatchString(input.Header("Accept"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptsXML Checks if request accepts xml response
|
||||||
|
func (input *BeegoInput) AcceptsXML() bool {
|
||||||
|
return acceptsXMLRegex.MatchString(input.Header("Accept"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptsJSON Checks if request accepts json response
|
||||||
|
func (input *BeegoInput) AcceptsJSON() bool {
|
||||||
|
return acceptsJSONRegex.MatchString(input.Header("Accept"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptsYAML Checks if request accepts json response
|
||||||
|
func (input *BeegoInput) AcceptsYAML() bool {
|
||||||
|
return acceptsYAMLRegex.MatchString(input.Header("Accept"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP returns request client ip.
|
||||||
|
// if in proxy, return first proxy id.
|
||||||
|
// if error, return RemoteAddr.
|
||||||
|
func (input *BeegoInput) IP() string {
|
||||||
|
ips := input.Proxy()
|
||||||
|
if len(ips) > 0 && ips[0] != "" {
|
||||||
|
rip, _, err := net.SplitHostPort(ips[0])
|
||||||
|
if err != nil {
|
||||||
|
rip = ips[0]
|
||||||
|
}
|
||||||
|
return rip
|
||||||
|
}
|
||||||
|
if ip, _, err := net.SplitHostPort(input.Context.Request.RemoteAddr); err == nil {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
return input.Context.Request.RemoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxy returns proxy client ips slice.
|
||||||
|
func (input *BeegoInput) Proxy() []string {
|
||||||
|
if ips := input.Header("X-Forwarded-For"); ips != "" {
|
||||||
|
return strings.Split(ips, ",")
|
||||||
|
}
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Referer returns http referer header.
|
||||||
|
func (input *BeegoInput) Referer() string {
|
||||||
|
return input.Header("Referer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refer returns http referer header.
|
||||||
|
func (input *BeegoInput) Refer() string {
|
||||||
|
return input.Referer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubDomains returns sub domain string.
|
||||||
|
// if aa.bb.domain.com, returns aa.bb .
|
||||||
|
func (input *BeegoInput) SubDomains() string {
|
||||||
|
parts := strings.Split(input.Host(), ".")
|
||||||
|
if len(parts) >= 3 {
|
||||||
|
return strings.Join(parts[:len(parts)-2], ".")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Port returns request client port.
|
||||||
|
// when error or empty, return 80.
|
||||||
|
func (input *BeegoInput) Port() int {
|
||||||
|
if _, portPart, err := net.SplitHostPort(input.Context.Request.Host); err == nil {
|
||||||
|
port, _ := strconv.Atoi(portPart)
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
return 80
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAgent returns request client user agent string.
|
||||||
|
func (input *BeegoInput) UserAgent() string {
|
||||||
|
return input.Header("User-Agent")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamsLen return the length of the params
|
||||||
|
func (input *BeegoInput) ParamsLen() int {
|
||||||
|
return len(input.pnames)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Param returns router param by a given key.
|
||||||
|
func (input *BeegoInput) Param(key string) string {
|
||||||
|
for i, v := range input.pnames {
|
||||||
|
if v == key && i <= len(input.pvalues) {
|
||||||
|
// we cannot use url.PathEscape(input.pvalues[i])
|
||||||
|
// for example, if the value is /a/b
|
||||||
|
// after url.PathEscape(input.pvalues[i]), the value is %2Fa%2Fb
|
||||||
|
// However, the value is used in ControllerRegister.ServeHTTP
|
||||||
|
// and split by "/", so function crash...
|
||||||
|
return input.pvalues[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params returns the map[key]value.
|
||||||
|
func (input *BeegoInput) Params() map[string]string {
|
||||||
|
m := make(map[string]string)
|
||||||
|
for i, v := range input.pnames {
|
||||||
|
if i <= len(input.pvalues) {
|
||||||
|
m[v] = input.pvalues[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetParam will set the param with key and value
|
||||||
|
func (input *BeegoInput) SetParam(key, val string) {
|
||||||
|
// check if already exists
|
||||||
|
for i, v := range input.pnames {
|
||||||
|
if v == key && i <= len(input.pvalues) {
|
||||||
|
input.pvalues[i] = val
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input.pvalues = append(input.pvalues, val)
|
||||||
|
input.pnames = append(input.pnames, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetParams clears any of the input's Params
|
||||||
|
// This function is used to clear parameters so they may be reset between filter
|
||||||
|
// passes.
|
||||||
|
func (input *BeegoInput) ResetParams() {
|
||||||
|
input.pnames = input.pnames[:0]
|
||||||
|
input.pvalues = input.pvalues[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query returns input data item string by a given string.
|
||||||
|
func (input *BeegoInput) Query(key string) string {
|
||||||
|
if val := input.Param(key); val != "" {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
if input.Context.Request.Form == nil {
|
||||||
|
input.dataLock.Lock()
|
||||||
|
if input.Context.Request.Form == nil {
|
||||||
|
input.Context.Request.ParseForm()
|
||||||
|
}
|
||||||
|
input.dataLock.Unlock()
|
||||||
|
}
|
||||||
|
input.dataLock.RLock()
|
||||||
|
defer input.dataLock.RUnlock()
|
||||||
|
return input.Context.Request.Form.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header returns request header item string by a given string.
|
||||||
|
// if non-existed, return empty string.
|
||||||
|
func (input *BeegoInput) Header(key string) string {
|
||||||
|
return input.Context.Request.Header.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cookie returns request cookie item string by a given key.
|
||||||
|
// if non-existed, return empty string.
|
||||||
|
func (input *BeegoInput) Cookie(key string) string {
|
||||||
|
ck, err := input.Context.Request.Cookie(key)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return ck.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session returns current session item value by a given key.
|
||||||
|
// if non-existed, return nil.
|
||||||
|
func (input *BeegoInput) Session(key interface{}) interface{} {
|
||||||
|
return input.CruSession.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyBody returns the raw request body data as bytes.
|
||||||
|
func (input *BeegoInput) CopyBody(MaxMemory int64) []byte {
|
||||||
|
if input.Context.Request.Body == nil {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestbody []byte
|
||||||
|
safe := &io.LimitedReader{R: input.Context.Request.Body, N: MaxMemory}
|
||||||
|
if input.Header("Content-Encoding") == "gzip" {
|
||||||
|
reader, err := gzip.NewReader(safe)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
requestbody, _ = ioutil.ReadAll(reader)
|
||||||
|
} else {
|
||||||
|
requestbody, _ = ioutil.ReadAll(safe)
|
||||||
|
}
|
||||||
|
|
||||||
|
input.Context.Request.Body.Close()
|
||||||
|
bf := bytes.NewBuffer(requestbody)
|
||||||
|
input.Context.Request.Body = http.MaxBytesReader(input.Context.ResponseWriter, ioutil.NopCloser(bf), MaxMemory)
|
||||||
|
input.RequestBody = requestbody
|
||||||
|
return requestbody
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data return the implicit data in the input
|
||||||
|
func (input *BeegoInput) Data() map[interface{}]interface{} {
|
||||||
|
input.dataLock.Lock()
|
||||||
|
defer input.dataLock.Unlock()
|
||||||
|
if input.data == nil {
|
||||||
|
input.data = make(map[interface{}]interface{})
|
||||||
|
}
|
||||||
|
return input.data
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetData returns the stored data in this context.
|
||||||
|
func (input *BeegoInput) GetData(key interface{}) interface{} {
|
||||||
|
input.dataLock.Lock()
|
||||||
|
defer input.dataLock.Unlock()
|
||||||
|
if v, ok := input.data[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetData stores data with given key in this context.
|
||||||
|
// This data are only available in this context.
|
||||||
|
func (input *BeegoInput) SetData(key, val interface{}) {
|
||||||
|
input.dataLock.Lock()
|
||||||
|
defer input.dataLock.Unlock()
|
||||||
|
if input.data == nil {
|
||||||
|
input.data = make(map[interface{}]interface{})
|
||||||
|
}
|
||||||
|
input.data[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFormOrMulitForm parseForm or parseMultiForm based on Content-type
|
||||||
|
func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error {
|
||||||
|
// Parse the body depending on the content type.
|
||||||
|
if strings.Contains(input.Header("Content-Type"), "multipart/form-data") {
|
||||||
|
if err := input.Context.Request.ParseMultipartForm(maxMemory); err != nil {
|
||||||
|
return errors.New("Error parsing request body:" + err.Error())
|
||||||
|
}
|
||||||
|
} else if err := input.Context.Request.ParseForm(); err != nil {
|
||||||
|
return errors.New("Error parsing request body:" + err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind data from request.Form[key] to dest
|
||||||
|
// like /?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie
|
||||||
|
// var id int beegoInput.Bind(&id, "id") id ==123
|
||||||
|
// var isok bool beegoInput.Bind(&isok, "isok") isok ==true
|
||||||
|
// var ft float64 beegoInput.Bind(&ft, "ft") ft ==1.2
|
||||||
|
// ol := make([]int, 0, 2) beegoInput.Bind(&ol, "ol") ol ==[1 2]
|
||||||
|
// ul := make([]string, 0, 2) beegoInput.Bind(&ul, "ul") ul ==[str array]
|
||||||
|
// user struct{Name} beegoInput.Bind(&user, "user") user == {Name:"astaxie"}
|
||||||
|
func (input *BeegoInput) Bind(dest interface{}, key string) error {
|
||||||
|
value := reflect.ValueOf(dest)
|
||||||
|
if value.Kind() != reflect.Ptr {
|
||||||
|
return errors.New("beego: non-pointer passed to Bind: " + key)
|
||||||
|
}
|
||||||
|
value = value.Elem()
|
||||||
|
if !value.CanSet() {
|
||||||
|
return errors.New("beego: non-settable variable passed to Bind: " + key)
|
||||||
|
}
|
||||||
|
typ := value.Type()
|
||||||
|
// Get real type if dest define with interface{}.
|
||||||
|
// e.g var dest interface{} dest=1.0
|
||||||
|
if value.Kind() == reflect.Interface {
|
||||||
|
typ = value.Elem().Type()
|
||||||
|
}
|
||||||
|
rv := input.bind(key, typ)
|
||||||
|
if !rv.IsValid() {
|
||||||
|
return errors.New("beego: reflect value is empty")
|
||||||
|
}
|
||||||
|
value.Set(rv)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input *BeegoInput) bind(key string, typ reflect.Type) reflect.Value {
|
||||||
|
if input.Context.Request.Form == nil {
|
||||||
|
input.Context.Request.ParseForm()
|
||||||
|
}
|
||||||
|
rv := reflect.Zero(typ)
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
val := input.Query(key)
|
||||||
|
if len(val) == 0 {
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
rv = input.bindInt(val, typ)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
val := input.Query(key)
|
||||||
|
if len(val) == 0 {
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
rv = input.bindUint(val, typ)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
val := input.Query(key)
|
||||||
|
if len(val) == 0 {
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
rv = input.bindFloat(val, typ)
|
||||||
|
case reflect.String:
|
||||||
|
val := input.Query(key)
|
||||||
|
if len(val) == 0 {
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
rv = input.bindString(val, typ)
|
||||||
|
case reflect.Bool:
|
||||||
|
val := input.Query(key)
|
||||||
|
if len(val) == 0 {
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
rv = input.bindBool(val, typ)
|
||||||
|
case reflect.Slice:
|
||||||
|
rv = input.bindSlice(&input.Context.Request.Form, key, typ)
|
||||||
|
case reflect.Struct:
|
||||||
|
rv = input.bindStruct(&input.Context.Request.Form, key, typ)
|
||||||
|
case reflect.Ptr:
|
||||||
|
rv = input.bindPoint(key, typ)
|
||||||
|
case reflect.Map:
|
||||||
|
rv = input.bindMap(&input.Context.Request.Form, key, typ)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input *BeegoInput) bindValue(val string, typ reflect.Type) reflect.Value {
|
||||||
|
rv := reflect.Zero(typ)
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
rv = input.bindInt(val, typ)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
rv = input.bindUint(val, typ)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
rv = input.bindFloat(val, typ)
|
||||||
|
case reflect.String:
|
||||||
|
rv = input.bindString(val, typ)
|
||||||
|
case reflect.Bool:
|
||||||
|
rv = input.bindBool(val, typ)
|
||||||
|
case reflect.Slice:
|
||||||
|
rv = input.bindSlice(&url.Values{"": {val}}, "", typ)
|
||||||
|
case reflect.Struct:
|
||||||
|
rv = input.bindStruct(&url.Values{"": {val}}, "", typ)
|
||||||
|
case reflect.Ptr:
|
||||||
|
rv = input.bindPoint(val, typ)
|
||||||
|
case reflect.Map:
|
||||||
|
rv = input.bindMap(&url.Values{"": {val}}, "", typ)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input *BeegoInput) bindInt(val string, typ reflect.Type) reflect.Value {
|
||||||
|
intValue, err := strconv.ParseInt(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return reflect.Zero(typ)
|
||||||
|
}
|
||||||
|
pValue := reflect.New(typ)
|
||||||
|
pValue.Elem().SetInt(intValue)
|
||||||
|
return pValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input *BeegoInput) bindUint(val string, typ reflect.Type) reflect.Value {
|
||||||
|
uintValue, err := strconv.ParseUint(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return reflect.Zero(typ)
|
||||||
|
}
|
||||||
|
pValue := reflect.New(typ)
|
||||||
|
pValue.Elem().SetUint(uintValue)
|
||||||
|
return pValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input *BeegoInput) bindFloat(val string, typ reflect.Type) reflect.Value {
|
||||||
|
floatValue, err := strconv.ParseFloat(val, 64)
|
||||||
|
if err != nil {
|
||||||
|
return reflect.Zero(typ)
|
||||||
|
}
|
||||||
|
pValue := reflect.New(typ)
|
||||||
|
pValue.Elem().SetFloat(floatValue)
|
||||||
|
return pValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input *BeegoInput) bindString(val string, typ reflect.Type) reflect.Value {
|
||||||
|
return reflect.ValueOf(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input *BeegoInput) bindBool(val string, typ reflect.Type) reflect.Value {
|
||||||
|
val = strings.TrimSpace(strings.ToLower(val))
|
||||||
|
switch val {
|
||||||
|
case "true", "on", "1":
|
||||||
|
return reflect.ValueOf(true)
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sliceValue struct {
|
||||||
|
index int // Index extracted from brackets. If -1, no index was provided.
|
||||||
|
value reflect.Value // the bound value for this slice element.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input *BeegoInput) bindSlice(params *url.Values, key string, typ reflect.Type) reflect.Value {
|
||||||
|
maxIndex := -1
|
||||||
|
numNoIndex := 0
|
||||||
|
sliceValues := []sliceValue{}
|
||||||
|
for reqKey, vals := range *params {
|
||||||
|
if !strings.HasPrefix(reqKey, key+"[") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Extract the index, and the index where a sub-key starts. (e.g. field[0].subkey)
|
||||||
|
index := -1
|
||||||
|
leftBracket, rightBracket := len(key), strings.Index(reqKey[len(key):], "]")+len(key)
|
||||||
|
if rightBracket > leftBracket+1 {
|
||||||
|
index, _ = strconv.Atoi(reqKey[leftBracket+1 : rightBracket])
|
||||||
|
}
|
||||||
|
subKeyIndex := rightBracket + 1
|
||||||
|
|
||||||
|
// Handle the indexed case.
|
||||||
|
if index > -1 {
|
||||||
|
if index > maxIndex {
|
||||||
|
maxIndex = index
|
||||||
|
}
|
||||||
|
sliceValues = append(sliceValues, sliceValue{
|
||||||
|
index: index,
|
||||||
|
value: input.bind(reqKey[:subKeyIndex], typ.Elem()),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's an un-indexed element. (e.g. element[])
|
||||||
|
numNoIndex += len(vals)
|
||||||
|
for _, val := range vals {
|
||||||
|
// Unindexed values can only be direct-bound.
|
||||||
|
sliceValues = append(sliceValues, sliceValue{
|
||||||
|
index: -1,
|
||||||
|
value: input.bindValue(val, typ.Elem()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultArray := reflect.MakeSlice(typ, maxIndex+1, maxIndex+1+numNoIndex)
|
||||||
|
for _, sv := range sliceValues {
|
||||||
|
if sv.index != -1 {
|
||||||
|
resultArray.Index(sv.index).Set(sv.value)
|
||||||
|
} else {
|
||||||
|
resultArray = reflect.Append(resultArray, sv.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultArray
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input *BeegoInput) bindStruct(params *url.Values, key string, typ reflect.Type) reflect.Value {
|
||||||
|
result := reflect.New(typ).Elem()
|
||||||
|
fieldValues := make(map[string]reflect.Value)
|
||||||
|
for reqKey, val := range *params {
|
||||||
|
var fieldName string
|
||||||
|
if strings.HasPrefix(reqKey, key+".") {
|
||||||
|
fieldName = reqKey[len(key)+1:]
|
||||||
|
} else if strings.HasPrefix(reqKey, key+"[") && reqKey[len(reqKey)-1] == ']' {
|
||||||
|
fieldName = reqKey[len(key)+1 : len(reqKey)-1]
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := fieldValues[fieldName]; !ok {
|
||||||
|
// Time to bind this field. Get it and make sure we can set it.
|
||||||
|
fieldValue := result.FieldByName(fieldName)
|
||||||
|
if !fieldValue.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !fieldValue.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
boundVal := input.bindValue(val[0], fieldValue.Type())
|
||||||
|
fieldValue.Set(boundVal)
|
||||||
|
fieldValues[fieldName] = boundVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input *BeegoInput) bindPoint(key string, typ reflect.Type) reflect.Value {
|
||||||
|
return input.bind(key, typ.Elem()).Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input *BeegoInput) bindMap(params *url.Values, key string, typ reflect.Type) reflect.Value {
|
||||||
|
var (
|
||||||
|
result = reflect.MakeMap(typ)
|
||||||
|
keyType = typ.Key()
|
||||||
|
valueType = typ.Elem()
|
||||||
|
)
|
||||||
|
for paramName, values := range *params {
|
||||||
|
if !strings.HasPrefix(paramName, key+"[") || paramName[len(paramName)-1] != ']' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := paramName[len(key)+1 : len(paramName)-1]
|
||||||
|
result.SetMapIndex(input.bindValue(key, keyType), input.bindValue(values[0], valueType))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
217
pkg/context/input_test.go
Normal file
217
pkg/context/input_test.go
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
// 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 context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBind(t *testing.T) {
|
||||||
|
type testItem struct {
|
||||||
|
field string
|
||||||
|
empty interface{}
|
||||||
|
want interface{}
|
||||||
|
}
|
||||||
|
type Human struct {
|
||||||
|
ID int
|
||||||
|
Nick string
|
||||||
|
Pwd string
|
||||||
|
Ms bool
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
request string
|
||||||
|
valueGp []testItem
|
||||||
|
}{
|
||||||
|
{"/?p=str", []testItem{{"p", interface{}(""), interface{}("str")}}},
|
||||||
|
|
||||||
|
{"/?p=", []testItem{{"p", "", ""}}},
|
||||||
|
{"/?p=str", []testItem{{"p", "", "str"}}},
|
||||||
|
|
||||||
|
{"/?p=123", []testItem{{"p", 0, 123}}},
|
||||||
|
{"/?p=123", []testItem{{"p", uint(0), uint(123)}}},
|
||||||
|
|
||||||
|
{"/?p=1.0", []testItem{{"p", 0.0, 1.0}}},
|
||||||
|
{"/?p=1", []testItem{{"p", false, true}}},
|
||||||
|
|
||||||
|
{"/?p=true", []testItem{{"p", false, true}}},
|
||||||
|
{"/?p=ON", []testItem{{"p", false, true}}},
|
||||||
|
{"/?p=on", []testItem{{"p", false, true}}},
|
||||||
|
{"/?p=1", []testItem{{"p", false, true}}},
|
||||||
|
{"/?p=2", []testItem{{"p", false, false}}},
|
||||||
|
{"/?p=false", []testItem{{"p", false, false}}},
|
||||||
|
|
||||||
|
{"/?p[a]=1&p[b]=2&p[c]=3", []testItem{{"p", map[string]int{}, map[string]int{"a": 1, "b": 2, "c": 3}}}},
|
||||||
|
{"/?p[a]=v1&p[b]=v2&p[c]=v3", []testItem{{"p", map[string]string{}, map[string]string{"a": "v1", "b": "v2", "c": "v3"}}}},
|
||||||
|
|
||||||
|
{"/?p[]=8&p[]=9&p[]=10", []testItem{{"p", []int{}, []int{8, 9, 10}}}},
|
||||||
|
{"/?p[0]=8&p[1]=9&p[2]=10", []testItem{{"p", []int{}, []int{8, 9, 10}}}},
|
||||||
|
{"/?p[0]=8&p[1]=9&p[2]=10&p[5]=14", []testItem{{"p", []int{}, []int{8, 9, 10, 0, 0, 14}}}},
|
||||||
|
{"/?p[0]=8.0&p[1]=9.0&p[2]=10.0", []testItem{{"p", []float64{}, []float64{8.0, 9.0, 10.0}}}},
|
||||||
|
|
||||||
|
{"/?p[]=10&p[]=9&p[]=8", []testItem{{"p", []string{}, []string{"10", "9", "8"}}}},
|
||||||
|
{"/?p[0]=8&p[1]=9&p[2]=10", []testItem{{"p", []string{}, []string{"8", "9", "10"}}}},
|
||||||
|
|
||||||
|
{"/?p[0]=true&p[1]=false&p[2]=true&p[5]=1&p[6]=ON&p[7]=other", []testItem{{"p", []bool{}, []bool{true, false, true, false, false, true, true, false}}}},
|
||||||
|
|
||||||
|
{"/?human.Nick=astaxie", []testItem{{"human", Human{}, Human{Nick: "astaxie"}}}},
|
||||||
|
{"/?human.ID=888&human.Nick=astaxie&human.Ms=true&human[Pwd]=pass", []testItem{{"human", Human{}, Human{ID: 888, Nick: "astaxie", Ms: true, Pwd: "pass"}}}},
|
||||||
|
{"/?human[0].ID=888&human[0].Nick=astaxie&human[0].Ms=true&human[0][Pwd]=pass01&human[1].ID=999&human[1].Nick=ysqi&human[1].Ms=On&human[1].Pwd=pass02",
|
||||||
|
[]testItem{{"human", []Human{}, []Human{
|
||||||
|
{ID: 888, Nick: "astaxie", Ms: true, Pwd: "pass01"},
|
||||||
|
{ID: 999, Nick: "ysqi", Ms: true, Pwd: "pass02"},
|
||||||
|
}}}},
|
||||||
|
|
||||||
|
{
|
||||||
|
"/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&human.Nick=astaxie",
|
||||||
|
[]testItem{
|
||||||
|
{"id", 0, 123},
|
||||||
|
{"isok", false, true},
|
||||||
|
{"ft", 0.0, 1.2},
|
||||||
|
{"ol", []int{}, []int{1, 2}},
|
||||||
|
{"ul", []string{}, []string{"str", "array"}},
|
||||||
|
{"human", Human{}, Human{Nick: "astaxie"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
r, _ := http.NewRequest("GET", c.request, nil)
|
||||||
|
beegoInput := NewInput()
|
||||||
|
beegoInput.Context = NewContext()
|
||||||
|
beegoInput.Context.Reset(httptest.NewRecorder(), r)
|
||||||
|
|
||||||
|
for _, item := range c.valueGp {
|
||||||
|
got := item.empty
|
||||||
|
err := beegoInput.Bind(&got, item.field)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, item.want) {
|
||||||
|
t.Fatalf("Bind %q error,should be:\n%#v \ngot:\n%#v", item.field, item.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubDomain(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", "http://www.example.com/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie", nil)
|
||||||
|
beegoInput := NewInput()
|
||||||
|
beegoInput.Context = NewContext()
|
||||||
|
beegoInput.Context.Reset(httptest.NewRecorder(), r)
|
||||||
|
|
||||||
|
subdomain := beegoInput.SubDomains()
|
||||||
|
if subdomain != "www" {
|
||||||
|
t.Fatal("Subdomain parse error, got" + subdomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _ = http.NewRequest("GET", "http://localhost/", nil)
|
||||||
|
beegoInput.Context.Request = r
|
||||||
|
if beegoInput.SubDomains() != "" {
|
||||||
|
t.Fatal("Subdomain parse error, should be empty, got " + beegoInput.SubDomains())
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _ = http.NewRequest("GET", "http://aa.bb.example.com/", nil)
|
||||||
|
beegoInput.Context.Request = r
|
||||||
|
if beegoInput.SubDomains() != "aa.bb" {
|
||||||
|
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO Fix this
|
||||||
|
r, _ = http.NewRequest("GET", "http://127.0.0.1/", nil)
|
||||||
|
beegoInput.Context.Request = r
|
||||||
|
if beegoInput.SubDomains() != "" {
|
||||||
|
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
r, _ = http.NewRequest("GET", "http://example.com/", nil)
|
||||||
|
beegoInput.Context.Request = r
|
||||||
|
if beegoInput.SubDomains() != "" {
|
||||||
|
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _ = http.NewRequest("GET", "http://aa.bb.cc.dd.example.com/", nil)
|
||||||
|
beegoInput.Context.Request = r
|
||||||
|
if beegoInput.SubDomains() != "aa.bb.cc.dd" {
|
||||||
|
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParams(t *testing.T) {
|
||||||
|
inp := NewInput()
|
||||||
|
|
||||||
|
inp.SetParam("p1", "val1_ver1")
|
||||||
|
inp.SetParam("p2", "val2_ver1")
|
||||||
|
inp.SetParam("p3", "val3_ver1")
|
||||||
|
if l := inp.ParamsLen(); l != 3 {
|
||||||
|
t.Fatalf("Input.ParamsLen wrong value: %d, expected %d", l, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := inp.Param("p1"); val != "val1_ver1" {
|
||||||
|
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver1")
|
||||||
|
}
|
||||||
|
if val := inp.Param("p3"); val != "val3_ver1" {
|
||||||
|
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val3_ver1")
|
||||||
|
}
|
||||||
|
vals := inp.Params()
|
||||||
|
expected := map[string]string{
|
||||||
|
"p1": "val1_ver1",
|
||||||
|
"p2": "val2_ver1",
|
||||||
|
"p3": "val3_ver1",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(vals, expected) {
|
||||||
|
t.Fatalf("Input.Params wrong value: %s, expected %s", vals, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overwriting existing params
|
||||||
|
inp.SetParam("p1", "val1_ver2")
|
||||||
|
inp.SetParam("p2", "val2_ver2")
|
||||||
|
expected = map[string]string{
|
||||||
|
"p1": "val1_ver2",
|
||||||
|
"p2": "val2_ver2",
|
||||||
|
"p3": "val3_ver1",
|
||||||
|
}
|
||||||
|
vals = inp.Params()
|
||||||
|
if !reflect.DeepEqual(vals, expected) {
|
||||||
|
t.Fatalf("Input.Params wrong value: %s, expected %s", vals, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := inp.ParamsLen(); l != 3 {
|
||||||
|
t.Fatalf("Input.ParamsLen wrong value: %d, expected %d", l, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := inp.Param("p1"); val != "val1_ver2" {
|
||||||
|
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver2")
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := inp.Param("p2"); val != "val2_ver2" {
|
||||||
|
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver2")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func BenchmarkQuery(b *testing.B) {
|
||||||
|
beegoInput := NewInput()
|
||||||
|
beegoInput.Context = NewContext()
|
||||||
|
beegoInput.Context.Request, _ = http.NewRequest("POST", "http://www.example.com/?q=foo", nil)
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
beegoInput.Query("q")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
408
pkg/context/output.go
Normal file
408
pkg/context/output.go
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
// 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 context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BeegoOutput does work for sending response header.
|
||||||
|
type BeegoOutput struct {
|
||||||
|
Context *Context
|
||||||
|
Status int
|
||||||
|
EnableGzip bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOutput returns new BeegoOutput.
|
||||||
|
// it contains nothing now.
|
||||||
|
func NewOutput() *BeegoOutput {
|
||||||
|
return &BeegoOutput{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset init BeegoOutput
|
||||||
|
func (output *BeegoOutput) Reset(ctx *Context) {
|
||||||
|
output.Context = ctx
|
||||||
|
output.Status = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header sets response header item string via given key.
|
||||||
|
func (output *BeegoOutput) Header(key, val string) {
|
||||||
|
output.Context.ResponseWriter.Header().Set(key, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body sets response body content.
|
||||||
|
// if EnableGzip, compress content string.
|
||||||
|
// it sends out response body directly.
|
||||||
|
func (output *BeegoOutput) Body(content []byte) error {
|
||||||
|
var encoding string
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
if output.EnableGzip {
|
||||||
|
encoding = ParseEncoding(output.Context.Request)
|
||||||
|
}
|
||||||
|
if b, n, _ := WriteBody(encoding, buf, content); b {
|
||||||
|
output.Header("Content-Encoding", n)
|
||||||
|
output.Header("Content-Length", strconv.Itoa(buf.Len()))
|
||||||
|
} else {
|
||||||
|
output.Header("Content-Length", strconv.Itoa(len(content)))
|
||||||
|
}
|
||||||
|
// Write status code if it has been set manually
|
||||||
|
// Set it to 0 afterwards to prevent "multiple response.WriteHeader calls"
|
||||||
|
if output.Status != 0 {
|
||||||
|
output.Context.ResponseWriter.WriteHeader(output.Status)
|
||||||
|
output.Status = 0
|
||||||
|
} else {
|
||||||
|
output.Context.ResponseWriter.Started = true
|
||||||
|
}
|
||||||
|
io.Copy(output.Context.ResponseWriter, buf)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cookie sets cookie value via given key.
|
||||||
|
// others are ordered as cookie's max age time, path,domain, secure and httponly.
|
||||||
|
func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value))
|
||||||
|
|
||||||
|
//fix cookie not work in IE
|
||||||
|
if len(others) > 0 {
|
||||||
|
var maxAge int64
|
||||||
|
|
||||||
|
switch v := others[0].(type) {
|
||||||
|
case int:
|
||||||
|
maxAge = int64(v)
|
||||||
|
case int32:
|
||||||
|
maxAge = int64(v)
|
||||||
|
case int64:
|
||||||
|
maxAge = v
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case maxAge > 0:
|
||||||
|
fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(maxAge)*time.Second).UTC().Format(time.RFC1123), maxAge)
|
||||||
|
case maxAge < 0:
|
||||||
|
fmt.Fprintf(&b, "; Max-Age=0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the settings below
|
||||||
|
// Path, Domain, Secure, HttpOnly
|
||||||
|
// can use nil skip set
|
||||||
|
|
||||||
|
// default "/"
|
||||||
|
if len(others) > 1 {
|
||||||
|
if v, ok := others[1].(string); ok && len(v) > 0 {
|
||||||
|
fmt.Fprintf(&b, "; Path=%s", sanitizeValue(v))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(&b, "; Path=%s", "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// default empty
|
||||||
|
if len(others) > 2 {
|
||||||
|
if v, ok := others[2].(string); ok && len(v) > 0 {
|
||||||
|
fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default empty
|
||||||
|
if len(others) > 3 {
|
||||||
|
var secure bool
|
||||||
|
switch v := others[3].(type) {
|
||||||
|
case bool:
|
||||||
|
secure = v
|
||||||
|
default:
|
||||||
|
if others[3] != nil {
|
||||||
|
secure = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if secure {
|
||||||
|
fmt.Fprintf(&b, "; Secure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default false. for session cookie default true
|
||||||
|
if len(others) > 4 {
|
||||||
|
if v, ok := others[4].(bool); ok && v {
|
||||||
|
fmt.Fprintf(&b, "; HttpOnly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.Context.ResponseWriter.Header().Add("Set-Cookie", b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
|
||||||
|
|
||||||
|
func sanitizeName(n string) string {
|
||||||
|
return cookieNameSanitizer.Replace(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ")
|
||||||
|
|
||||||
|
func sanitizeValue(v string) string {
|
||||||
|
return cookieValueSanitizer.Replace(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonRenderer(value interface{}) Renderer {
|
||||||
|
return rendererFunc(func(ctx *Context) {
|
||||||
|
ctx.Output.JSON(value, false, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorRenderer(err error) Renderer {
|
||||||
|
return rendererFunc(func(ctx *Context) {
|
||||||
|
ctx.Output.SetStatus(500)
|
||||||
|
ctx.Output.Body([]byte(err.Error()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON writes json to response body.
|
||||||
|
// if encoding is true, it converts utf-8 to \u0000 type.
|
||||||
|
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error {
|
||||||
|
output.Header("Content-Type", "application/json; charset=utf-8")
|
||||||
|
var content []byte
|
||||||
|
var err error
|
||||||
|
if hasIndent {
|
||||||
|
content, err = json.MarshalIndent(data, "", " ")
|
||||||
|
} else {
|
||||||
|
content, err = json.Marshal(data)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if encoding {
|
||||||
|
content = []byte(stringsToJSON(string(content)))
|
||||||
|
}
|
||||||
|
return output.Body(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAML writes yaml to response body.
|
||||||
|
func (output *BeegoOutput) YAML(data interface{}) error {
|
||||||
|
output.Header("Content-Type", "application/x-yaml; charset=utf-8")
|
||||||
|
var content []byte
|
||||||
|
var err error
|
||||||
|
content, err = yaml.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return output.Body(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONP writes jsonp to response body.
|
||||||
|
func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
|
||||||
|
output.Header("Content-Type", "application/javascript; charset=utf-8")
|
||||||
|
var content []byte
|
||||||
|
var err error
|
||||||
|
if hasIndent {
|
||||||
|
content, err = json.MarshalIndent(data, "", " ")
|
||||||
|
} else {
|
||||||
|
content, err = json.Marshal(data)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
callback := output.Context.Input.Query("callback")
|
||||||
|
if callback == "" {
|
||||||
|
return errors.New(`"callback" parameter required`)
|
||||||
|
}
|
||||||
|
callback = template.JSEscapeString(callback)
|
||||||
|
callbackContent := bytes.NewBufferString(" if(window." + callback + ")" + callback)
|
||||||
|
callbackContent.WriteString("(")
|
||||||
|
callbackContent.Write(content)
|
||||||
|
callbackContent.WriteString(");\r\n")
|
||||||
|
return output.Body(callbackContent.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// XML writes xml string to response body.
|
||||||
|
func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error {
|
||||||
|
output.Header("Content-Type", "application/xml; charset=utf-8")
|
||||||
|
var content []byte
|
||||||
|
var err error
|
||||||
|
if hasIndent {
|
||||||
|
content, err = xml.MarshalIndent(data, "", " ")
|
||||||
|
} else {
|
||||||
|
content, err = xml.Marshal(data)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return output.Body(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header
|
||||||
|
func (output *BeegoOutput) ServeFormatted(data interface{}, hasIndent bool, hasEncode ...bool) {
|
||||||
|
accept := output.Context.Input.Header("Accept")
|
||||||
|
switch accept {
|
||||||
|
case ApplicationYAML:
|
||||||
|
output.YAML(data)
|
||||||
|
case ApplicationXML, TextXML:
|
||||||
|
output.XML(data, hasIndent)
|
||||||
|
default:
|
||||||
|
output.JSON(data, hasIndent, len(hasEncode) > 0 && hasEncode[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download forces response for download file.
|
||||||
|
// it prepares the download response header automatically.
|
||||||
|
func (output *BeegoOutput) Download(file string, filename ...string) {
|
||||||
|
// check get file error, file not found or other error.
|
||||||
|
if _, err := os.Stat(file); err != nil {
|
||||||
|
http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var fName string
|
||||||
|
if len(filename) > 0 && filename[0] != "" {
|
||||||
|
fName = filename[0]
|
||||||
|
} else {
|
||||||
|
fName = filepath.Base(file)
|
||||||
|
}
|
||||||
|
//https://tools.ietf.org/html/rfc6266#section-4.3
|
||||||
|
fn := url.PathEscape(fName)
|
||||||
|
if fName == fn {
|
||||||
|
fn = "filename=" + fn
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
The parameters "filename" and "filename*" differ only in that
|
||||||
|
"filename*" uses the encoding defined in [RFC5987], allowing the use
|
||||||
|
of characters not present in the ISO-8859-1 character set
|
||||||
|
([ISO-8859-1]).
|
||||||
|
*/
|
||||||
|
fn = "filename=" + fName + "; filename*=utf-8''" + fn
|
||||||
|
}
|
||||||
|
output.Header("Content-Disposition", "attachment; "+fn)
|
||||||
|
output.Header("Content-Description", "File Transfer")
|
||||||
|
output.Header("Content-Type", "application/octet-stream")
|
||||||
|
output.Header("Content-Transfer-Encoding", "binary")
|
||||||
|
output.Header("Expires", "0")
|
||||||
|
output.Header("Cache-Control", "must-revalidate")
|
||||||
|
output.Header("Pragma", "public")
|
||||||
|
http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentType sets the content type from ext string.
|
||||||
|
// MIME type is given in mime package.
|
||||||
|
func (output *BeegoOutput) ContentType(ext string) {
|
||||||
|
if !strings.HasPrefix(ext, ".") {
|
||||||
|
ext = "." + ext
|
||||||
|
}
|
||||||
|
ctype := mime.TypeByExtension(ext)
|
||||||
|
if ctype != "" {
|
||||||
|
output.Header("Content-Type", ctype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStatus sets response status code.
|
||||||
|
// It writes response header directly.
|
||||||
|
func (output *BeegoOutput) SetStatus(status int) {
|
||||||
|
output.Status = status
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCachable returns boolean of this request is cached.
|
||||||
|
// HTTP 304 means cached.
|
||||||
|
func (output *BeegoOutput) IsCachable() bool {
|
||||||
|
return output.Status >= 200 && output.Status < 300 || output.Status == 304
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns boolean of this request is empty.
|
||||||
|
// HTTP 201,204 and 304 means empty.
|
||||||
|
func (output *BeegoOutput) IsEmpty() bool {
|
||||||
|
return output.Status == 201 || output.Status == 204 || output.Status == 304
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOk returns boolean of this request runs well.
|
||||||
|
// HTTP 200 means ok.
|
||||||
|
func (output *BeegoOutput) IsOk() bool {
|
||||||
|
return output.Status == 200
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccessful returns boolean of this request runs successfully.
|
||||||
|
// HTTP 2xx means ok.
|
||||||
|
func (output *BeegoOutput) IsSuccessful() bool {
|
||||||
|
return output.Status >= 200 && output.Status < 300
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRedirect returns boolean of this request is redirection header.
|
||||||
|
// HTTP 301,302,307 means redirection.
|
||||||
|
func (output *BeegoOutput) IsRedirect() bool {
|
||||||
|
return output.Status == 301 || output.Status == 302 || output.Status == 303 || output.Status == 307
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsForbidden returns boolean of this request is forbidden.
|
||||||
|
// HTTP 403 means forbidden.
|
||||||
|
func (output *BeegoOutput) IsForbidden() bool {
|
||||||
|
return output.Status == 403
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotFound returns boolean of this request is not found.
|
||||||
|
// HTTP 404 means not found.
|
||||||
|
func (output *BeegoOutput) IsNotFound() bool {
|
||||||
|
return output.Status == 404
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClientError returns boolean of this request client sends error data.
|
||||||
|
// HTTP 4xx means client error.
|
||||||
|
func (output *BeegoOutput) IsClientError() bool {
|
||||||
|
return output.Status >= 400 && output.Status < 500
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsServerError returns boolean of this server handler errors.
|
||||||
|
// HTTP 5xx means server internal error.
|
||||||
|
func (output *BeegoOutput) IsServerError() bool {
|
||||||
|
return output.Status >= 500 && output.Status < 600
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringsToJSON(str string) string {
|
||||||
|
var jsons bytes.Buffer
|
||||||
|
for _, r := range str {
|
||||||
|
rint := int(r)
|
||||||
|
if rint < 128 {
|
||||||
|
jsons.WriteRune(r)
|
||||||
|
} else {
|
||||||
|
jsons.WriteString("\\u")
|
||||||
|
if rint < 0x100 {
|
||||||
|
jsons.WriteString("00")
|
||||||
|
} else if rint < 0x1000 {
|
||||||
|
jsons.WriteString("0")
|
||||||
|
}
|
||||||
|
jsons.WriteString(strconv.FormatInt(int64(rint), 16))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsons.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session sets session item value with given key.
|
||||||
|
func (output *BeegoOutput) Session(name interface{}, value interface{}) {
|
||||||
|
output.Context.Input.CruSession.Set(name, value)
|
||||||
|
}
|
78
pkg/context/param/conv.go
Normal file
78
pkg/context/param/conv.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
beecontext "github.com/astaxie/beego/context"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConvertParams converts http method params to values that will be passed to the method controller as arguments
|
||||||
|
func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) {
|
||||||
|
result = make([]reflect.Value, 0, len(methodParams))
|
||||||
|
for i := 0; i < len(methodParams); i++ {
|
||||||
|
reflectValue := convertParam(methodParams[i], methodType.In(i), ctx)
|
||||||
|
result = append(result, reflectValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Context) (result reflect.Value) {
|
||||||
|
paramValue := getParamValue(param, ctx)
|
||||||
|
if paramValue == "" {
|
||||||
|
if param.required {
|
||||||
|
ctx.Abort(400, fmt.Sprintf("Missing parameter %s", param.name))
|
||||||
|
} else {
|
||||||
|
paramValue = param.defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reflectValue, err := parseValue(param, paramValue, paramType)
|
||||||
|
if err != nil {
|
||||||
|
logs.Debug(fmt.Sprintf("Error converting param %s to type %s. Value: %v, Error: %s", param.name, paramType, paramValue, err))
|
||||||
|
ctx.Abort(400, fmt.Sprintf("Invalid parameter %s. Can not convert %v to type %s", param.name, paramValue, paramType))
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflectValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getParamValue(param *MethodParam, ctx *beecontext.Context) string {
|
||||||
|
switch param.in {
|
||||||
|
case body:
|
||||||
|
return string(ctx.Input.RequestBody)
|
||||||
|
case header:
|
||||||
|
return ctx.Input.Header(param.name)
|
||||||
|
case path:
|
||||||
|
return ctx.Input.Query(":" + param.name)
|
||||||
|
default:
|
||||||
|
return ctx.Input.Query(param.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseValue(param *MethodParam, paramValue string, paramType reflect.Type) (result reflect.Value, err error) {
|
||||||
|
if paramValue == "" {
|
||||||
|
return reflect.Zero(paramType), nil
|
||||||
|
}
|
||||||
|
parser := getParser(param, paramType)
|
||||||
|
value, err := parser.parse(paramValue, paramType)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return safeConvert(reflect.ValueOf(value), paramType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeConvert(value reflect.Value, t reflect.Type) (result reflect.Value, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
var ok bool
|
||||||
|
err, ok = r.(error)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
result = value.Convert(t)
|
||||||
|
return
|
||||||
|
}
|
69
pkg/context/param/methodparams.go
Normal file
69
pkg/context/param/methodparams.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//MethodParam keeps param information to be auto passed to controller methods
|
||||||
|
type MethodParam struct {
|
||||||
|
name string
|
||||||
|
in paramType
|
||||||
|
required bool
|
||||||
|
defaultValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
type paramType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
param paramType = iota
|
||||||
|
path
|
||||||
|
body
|
||||||
|
header
|
||||||
|
)
|
||||||
|
|
||||||
|
//New creates a new MethodParam with name and specific options
|
||||||
|
func New(name string, opts ...MethodParamOption) *MethodParam {
|
||||||
|
return newParam(name, nil, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParam(name string, parser paramParser, opts []MethodParamOption) (param *MethodParam) {
|
||||||
|
param = &MethodParam{name: name}
|
||||||
|
for _, option := range opts {
|
||||||
|
option(param)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Make creates an array of MethodParmas or an empty array
|
||||||
|
func Make(list ...*MethodParam) []*MethodParam {
|
||||||
|
if len(list) > 0 {
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp *MethodParam) String() string {
|
||||||
|
options := []string{}
|
||||||
|
result := "param.New(\"" + mp.name + "\""
|
||||||
|
if mp.required {
|
||||||
|
options = append(options, "param.IsRequired")
|
||||||
|
}
|
||||||
|
switch mp.in {
|
||||||
|
case path:
|
||||||
|
options = append(options, "param.InPath")
|
||||||
|
case body:
|
||||||
|
options = append(options, "param.InBody")
|
||||||
|
case header:
|
||||||
|
options = append(options, "param.InHeader")
|
||||||
|
}
|
||||||
|
if mp.defaultValue != "" {
|
||||||
|
options = append(options, fmt.Sprintf(`param.Default("%s")`, mp.defaultValue))
|
||||||
|
}
|
||||||
|
if len(options) > 0 {
|
||||||
|
result += ", "
|
||||||
|
}
|
||||||
|
result += strings.Join(options, ", ")
|
||||||
|
result += ")"
|
||||||
|
return result
|
||||||
|
}
|
37
pkg/context/param/options.go
Normal file
37
pkg/context/param/options.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MethodParamOption defines a func which apply options on a MethodParam
|
||||||
|
type MethodParamOption func(*MethodParam)
|
||||||
|
|
||||||
|
// IsRequired indicates that this param is required and can not be omitted from the http request
|
||||||
|
var IsRequired MethodParamOption = func(p *MethodParam) {
|
||||||
|
p.required = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// InHeader indicates that this param is passed via an http header
|
||||||
|
var InHeader MethodParamOption = func(p *MethodParam) {
|
||||||
|
p.in = header
|
||||||
|
}
|
||||||
|
|
||||||
|
// InPath indicates that this param is part of the URL path
|
||||||
|
var InPath MethodParamOption = func(p *MethodParam) {
|
||||||
|
p.in = path
|
||||||
|
}
|
||||||
|
|
||||||
|
// InBody indicates that this param is passed as an http request body
|
||||||
|
var InBody MethodParamOption = func(p *MethodParam) {
|
||||||
|
p.in = body
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default provides a default value for the http param
|
||||||
|
func Default(defaultValue interface{}) MethodParamOption {
|
||||||
|
return func(p *MethodParam) {
|
||||||
|
if defaultValue != nil {
|
||||||
|
p.defaultValue = fmt.Sprint(defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
149
pkg/context/param/parsers.go
Normal file
149
pkg/context/param/parsers.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type paramParser interface {
|
||||||
|
parse(value string, toType reflect.Type) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getParser(param *MethodParam, t reflect.Type) paramParser {
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return intParser{}
|
||||||
|
case reflect.Slice:
|
||||||
|
if t.Elem().Kind() == reflect.Uint8 { //treat []byte as string
|
||||||
|
return stringParser{}
|
||||||
|
}
|
||||||
|
if param.in == body {
|
||||||
|
return jsonParser{}
|
||||||
|
}
|
||||||
|
elemParser := getParser(param, t.Elem())
|
||||||
|
if elemParser == (jsonParser{}) {
|
||||||
|
return elemParser
|
||||||
|
}
|
||||||
|
return sliceParser(elemParser)
|
||||||
|
case reflect.Bool:
|
||||||
|
return boolParser{}
|
||||||
|
case reflect.String:
|
||||||
|
return stringParser{}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return floatParser{}
|
||||||
|
case reflect.Ptr:
|
||||||
|
elemParser := getParser(param, t.Elem())
|
||||||
|
if elemParser == (jsonParser{}) {
|
||||||
|
return elemParser
|
||||||
|
}
|
||||||
|
return ptrParser(elemParser)
|
||||||
|
default:
|
||||||
|
if t.PkgPath() == "time" && t.Name() == "Time" {
|
||||||
|
return timeParser{}
|
||||||
|
}
|
||||||
|
return jsonParser{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type parserFunc func(value string, toType reflect.Type) (interface{}, error)
|
||||||
|
|
||||||
|
func (f parserFunc) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
return f(value, toType)
|
||||||
|
}
|
||||||
|
|
||||||
|
type boolParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p boolParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
return strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p stringParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type intParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p intParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
return strconv.Atoi(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type floatParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p floatParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
if toType.Kind() == reflect.Float32 {
|
||||||
|
res, err := strconv.ParseFloat(value, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return float32(res), nil
|
||||||
|
}
|
||||||
|
return strconv.ParseFloat(value, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
type timeParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p timeParser) parse(value string, toType reflect.Type) (result interface{}, err error) {
|
||||||
|
result, err = time.Parse(time.RFC3339, value)
|
||||||
|
if err != nil {
|
||||||
|
result, err = time.Parse("2006-01-02", value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p jsonParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
pResult := reflect.New(toType)
|
||||||
|
v := pResult.Interface()
|
||||||
|
err := json.Unmarshal([]byte(value), v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pResult.Elem().Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sliceParser(elemParser paramParser) paramParser {
|
||||||
|
return parserFunc(func(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
values := strings.Split(value, ",")
|
||||||
|
result := reflect.MakeSlice(toType, 0, len(values))
|
||||||
|
elemType := toType.Elem()
|
||||||
|
for _, v := range values {
|
||||||
|
parsedValue, err := elemParser.parse(v, elemType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = reflect.Append(result, reflect.ValueOf(parsedValue))
|
||||||
|
}
|
||||||
|
return result.Interface(), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptrParser(elemParser paramParser) paramParser {
|
||||||
|
return parserFunc(func(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
parsedValue, err := elemParser.parse(value, toType.Elem())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newValPtr := reflect.New(toType.Elem())
|
||||||
|
newVal := reflect.Indirect(newValPtr)
|
||||||
|
convertedVal, err := safeConvert(reflect.ValueOf(parsedValue), toType.Elem())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newVal.Set(convertedVal)
|
||||||
|
return newValPtr.Interface(), nil
|
||||||
|
})
|
||||||
|
}
|
84
pkg/context/param/parsers_test.go
Normal file
84
pkg/context/param/parsers_test.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
import "reflect"
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type testDefinition struct {
|
||||||
|
strValue string
|
||||||
|
expectedValue interface{}
|
||||||
|
expectedParser paramParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Parsers(t *testing.T) {
|
||||||
|
|
||||||
|
//ints
|
||||||
|
checkParser(testDefinition{"1", 1, intParser{}}, t)
|
||||||
|
checkParser(testDefinition{"-1", int64(-1), intParser{}}, t)
|
||||||
|
checkParser(testDefinition{"1", uint64(1), intParser{}}, t)
|
||||||
|
|
||||||
|
//floats
|
||||||
|
checkParser(testDefinition{"1.0", float32(1.0), floatParser{}}, t)
|
||||||
|
checkParser(testDefinition{"-1.0", float64(-1.0), floatParser{}}, t)
|
||||||
|
|
||||||
|
//strings
|
||||||
|
checkParser(testDefinition{"AB", "AB", stringParser{}}, t)
|
||||||
|
checkParser(testDefinition{"AB", []byte{65, 66}, stringParser{}}, t)
|
||||||
|
|
||||||
|
//bools
|
||||||
|
checkParser(testDefinition{"true", true, boolParser{}}, t)
|
||||||
|
checkParser(testDefinition{"0", false, boolParser{}}, t)
|
||||||
|
|
||||||
|
//timeParser
|
||||||
|
checkParser(testDefinition{"2017-05-30T13:54:53Z", time.Date(2017, 5, 30, 13, 54, 53, 0, time.UTC), timeParser{}}, t)
|
||||||
|
checkParser(testDefinition{"2017-05-30", time.Date(2017, 5, 30, 0, 0, 0, 0, time.UTC), timeParser{}}, t)
|
||||||
|
|
||||||
|
//json
|
||||||
|
checkParser(testDefinition{`{"X": 5, "Y":"Z"}`, struct {
|
||||||
|
X int
|
||||||
|
Y string
|
||||||
|
}{5, "Z"}, jsonParser{}}, t)
|
||||||
|
|
||||||
|
//slice in query is parsed as comma delimited
|
||||||
|
checkParser(testDefinition{`1,2`, []int{1, 2}, sliceParser(intParser{})}, t)
|
||||||
|
|
||||||
|
//slice in body is parsed as json
|
||||||
|
checkParser(testDefinition{`["a","b"]`, []string{"a", "b"}, jsonParser{}}, t, MethodParam{in: body})
|
||||||
|
|
||||||
|
//pointers
|
||||||
|
var someInt = 1
|
||||||
|
checkParser(testDefinition{`1`, &someInt, ptrParser(intParser{})}, t)
|
||||||
|
|
||||||
|
var someStruct = struct{ X int }{5}
|
||||||
|
checkParser(testDefinition{`{"X": 5}`, &someStruct, jsonParser{}}, t)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkParser(def testDefinition, t *testing.T, methodParam ...MethodParam) {
|
||||||
|
toType := reflect.TypeOf(def.expectedValue)
|
||||||
|
var mp MethodParam
|
||||||
|
if len(methodParam) == 0 {
|
||||||
|
mp = MethodParam{}
|
||||||
|
} else {
|
||||||
|
mp = methodParam[0]
|
||||||
|
}
|
||||||
|
parser := getParser(&mp, toType)
|
||||||
|
|
||||||
|
if reflect.TypeOf(parser) != reflect.TypeOf(def.expectedParser) {
|
||||||
|
t.Errorf("Invalid parser for value %v. Expected: %v, actual: %v", def.strValue, reflect.TypeOf(def.expectedParser).Name(), reflect.TypeOf(parser).Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, err := parser.parse(def.strValue, toType)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Parsing error for value %v. Expected result: %v, error: %v", def.strValue, def.expectedValue, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
convResult, err := safeConvert(reflect.ValueOf(result), toType)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Conversion error for %v. from value: %v, toType: %v, error: %v", def.strValue, result, toType, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(convResult.Interface(), def.expectedValue) {
|
||||||
|
t.Errorf("Parsing error for value %v. Expected result: %v, actual: %v", def.strValue, def.expectedValue, result)
|
||||||
|
}
|
||||||
|
}
|
12
pkg/context/renderer.go
Normal file
12
pkg/context/renderer.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
// Renderer defines an http response renderer
|
||||||
|
type Renderer interface {
|
||||||
|
Render(ctx *Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
type rendererFunc func(ctx *Context)
|
||||||
|
|
||||||
|
func (f rendererFunc) Render(ctx *Context) {
|
||||||
|
f(ctx)
|
||||||
|
}
|
27
pkg/context/response.go
Normal file
27
pkg/context/response.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
//BadRequest indicates http error 400
|
||||||
|
BadRequest StatusCode = http.StatusBadRequest
|
||||||
|
|
||||||
|
//NotFound indicates http error 404
|
||||||
|
NotFound StatusCode = http.StatusNotFound
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatusCode sets the http response status code
|
||||||
|
type StatusCode int
|
||||||
|
|
||||||
|
func (s StatusCode) Error() string {
|
||||||
|
return strconv.Itoa(int(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render sets the http status code
|
||||||
|
func (s StatusCode) Render(ctx *Context) {
|
||||||
|
ctx.Output.SetStatus(int(s))
|
||||||
|
}
|
706
pkg/controller.go
Normal file
706
pkg/controller.go
Normal file
@ -0,0 +1,706 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
|
"github.com/astaxie/beego/context/param"
|
||||||
|
"github.com/astaxie/beego/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrAbort custom error when user stop request handler manually.
|
||||||
|
ErrAbort = errors.New("user stop run")
|
||||||
|
// GlobalControllerRouter store comments with controller. pkgpath+controller:comments
|
||||||
|
GlobalControllerRouter = make(map[string][]ControllerComments)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ControllerFilter store the filter for controller
|
||||||
|
type ControllerFilter struct {
|
||||||
|
Pattern string
|
||||||
|
Pos int
|
||||||
|
Filter FilterFunc
|
||||||
|
ReturnOnOutput bool
|
||||||
|
ResetParams bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControllerFilterComments store the comment for controller level filter
|
||||||
|
type ControllerFilterComments struct {
|
||||||
|
Pattern string
|
||||||
|
Pos int
|
||||||
|
Filter string // NOQA
|
||||||
|
ReturnOnOutput bool
|
||||||
|
ResetParams bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControllerImportComments store the import comment for controller needed
|
||||||
|
type ControllerImportComments struct {
|
||||||
|
ImportPath string
|
||||||
|
ImportAlias string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControllerComments store the comment for the controller method
|
||||||
|
type ControllerComments struct {
|
||||||
|
Method string
|
||||||
|
Router string
|
||||||
|
Filters []*ControllerFilter
|
||||||
|
ImportComments []*ControllerImportComments
|
||||||
|
FilterComments []*ControllerFilterComments
|
||||||
|
AllowHTTPMethods []string
|
||||||
|
Params []map[string]string
|
||||||
|
MethodParams []*param.MethodParam
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControllerCommentsSlice implements the sort interface
|
||||||
|
type ControllerCommentsSlice []ControllerComments
|
||||||
|
|
||||||
|
func (p ControllerCommentsSlice) Len() int { return len(p) }
|
||||||
|
func (p ControllerCommentsSlice) Less(i, j int) bool { return p[i].Router < p[j].Router }
|
||||||
|
func (p ControllerCommentsSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
|
||||||
|
// Controller defines some basic http request handler operations, such as
|
||||||
|
// http context, template and view, session and xsrf.
|
||||||
|
type Controller struct {
|
||||||
|
// context data
|
||||||
|
Ctx *context.Context
|
||||||
|
Data map[interface{}]interface{}
|
||||||
|
|
||||||
|
// route controller info
|
||||||
|
controllerName string
|
||||||
|
actionName string
|
||||||
|
methodMapping map[string]func() //method:routertree
|
||||||
|
AppController interface{}
|
||||||
|
|
||||||
|
// template data
|
||||||
|
TplName string
|
||||||
|
ViewPath string
|
||||||
|
Layout string
|
||||||
|
LayoutSections map[string]string // the key is the section name and the value is the template name
|
||||||
|
TplPrefix string
|
||||||
|
TplExt string
|
||||||
|
EnableRender bool
|
||||||
|
|
||||||
|
// xsrf data
|
||||||
|
_xsrfToken string
|
||||||
|
XSRFExpire int
|
||||||
|
EnableXSRF bool
|
||||||
|
|
||||||
|
// session
|
||||||
|
CruSession session.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControllerInterface is an interface to uniform all controller handler.
|
||||||
|
type ControllerInterface interface {
|
||||||
|
Init(ct *context.Context, controllerName, actionName string, app interface{})
|
||||||
|
Prepare()
|
||||||
|
Get()
|
||||||
|
Post()
|
||||||
|
Delete()
|
||||||
|
Put()
|
||||||
|
Head()
|
||||||
|
Patch()
|
||||||
|
Options()
|
||||||
|
Trace()
|
||||||
|
Finish()
|
||||||
|
Render() error
|
||||||
|
XSRFToken() string
|
||||||
|
CheckXSRFCookie() bool
|
||||||
|
HandlerFunc(fn string) bool
|
||||||
|
URLMapping()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init generates default values of controller operations.
|
||||||
|
func (c *Controller) Init(ctx *context.Context, controllerName, actionName string, app interface{}) {
|
||||||
|
c.Layout = ""
|
||||||
|
c.TplName = ""
|
||||||
|
c.controllerName = controllerName
|
||||||
|
c.actionName = actionName
|
||||||
|
c.Ctx = ctx
|
||||||
|
c.TplExt = "tpl"
|
||||||
|
c.AppController = app
|
||||||
|
c.EnableRender = true
|
||||||
|
c.EnableXSRF = true
|
||||||
|
c.Data = ctx.Input.Data()
|
||||||
|
c.methodMapping = make(map[string]func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare runs after Init before request function execution.
|
||||||
|
func (c *Controller) Prepare() {}
|
||||||
|
|
||||||
|
// Finish runs after request function execution.
|
||||||
|
func (c *Controller) Finish() {}
|
||||||
|
|
||||||
|
// Get adds a request function to handle GET request.
|
||||||
|
func (c *Controller) Get() {
|
||||||
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post adds a request function to handle POST request.
|
||||||
|
func (c *Controller) Post() {
|
||||||
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete adds a request function to handle DELETE request.
|
||||||
|
func (c *Controller) Delete() {
|
||||||
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put adds a request function to handle PUT request.
|
||||||
|
func (c *Controller) Put() {
|
||||||
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head adds a request function to handle HEAD request.
|
||||||
|
func (c *Controller) Head() {
|
||||||
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch adds a request function to handle PATCH request.
|
||||||
|
func (c *Controller) Patch() {
|
||||||
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options adds a request function to handle OPTIONS request.
|
||||||
|
func (c *Controller) Options() {
|
||||||
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace adds a request function to handle Trace request.
|
||||||
|
// this method SHOULD NOT be overridden.
|
||||||
|
// https://tools.ietf.org/html/rfc7231#section-4.3.8
|
||||||
|
// The TRACE method requests a remote, application-level loop-back of
|
||||||
|
// the request message. The final recipient of the request SHOULD
|
||||||
|
// reflect the message received, excluding some fields described below,
|
||||||
|
// back to the client as the message body of a 200 (OK) response with a
|
||||||
|
// Content-Type of "message/http" (Section 8.3.1 of [RFC7230]).
|
||||||
|
func (c *Controller) Trace() {
|
||||||
|
ts := func(h http.Header) (hs string) {
|
||||||
|
for k, v := range h {
|
||||||
|
hs += fmt.Sprintf("\r\n%s: %s", k, v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hs := fmt.Sprintf("\r\nTRACE %s %s%s\r\n", c.Ctx.Request.RequestURI, c.Ctx.Request.Proto, ts(c.Ctx.Request.Header))
|
||||||
|
c.Ctx.Output.Header("Content-Type", "message/http")
|
||||||
|
c.Ctx.Output.Header("Content-Length", fmt.Sprint(len(hs)))
|
||||||
|
c.Ctx.Output.Header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
c.Ctx.WriteString(hs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFunc call function with the name
|
||||||
|
func (c *Controller) HandlerFunc(fnname string) bool {
|
||||||
|
if v, ok := c.methodMapping[fnname]; ok {
|
||||||
|
v()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLMapping register the internal Controller router.
|
||||||
|
func (c *Controller) URLMapping() {}
|
||||||
|
|
||||||
|
// Mapping the method to function
|
||||||
|
func (c *Controller) Mapping(method string, fn func()) {
|
||||||
|
c.methodMapping[method] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render sends the response with rendered template bytes as text/html type.
|
||||||
|
func (c *Controller) Render() error {
|
||||||
|
if !c.EnableRender {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rb, err := c.RenderBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Ctx.ResponseWriter.Header().Get("Content-Type") == "" {
|
||||||
|
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Ctx.Output.Body(rb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderString returns the rendered template string. Do not send out response.
|
||||||
|
func (c *Controller) RenderString() (string, error) {
|
||||||
|
b, e := c.RenderBytes()
|
||||||
|
return string(b), e
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderBytes returns the bytes of rendered template string. Do not send out response.
|
||||||
|
func (c *Controller) RenderBytes() ([]byte, error) {
|
||||||
|
buf, err := c.renderTemplate()
|
||||||
|
//if the controller has set layout, then first get the tplName's content set the content to the layout
|
||||||
|
if err == nil && c.Layout != "" {
|
||||||
|
c.Data["LayoutContent"] = template.HTML(buf.String())
|
||||||
|
|
||||||
|
if c.LayoutSections != nil {
|
||||||
|
for sectionName, sectionTpl := range c.LayoutSections {
|
||||||
|
if sectionTpl == "" {
|
||||||
|
c.Data[sectionName] = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
err = ExecuteViewPathTemplate(&buf, sectionTpl, c.viewPath(), c.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Data[sectionName] = template.HTML(buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
ExecuteViewPathTemplate(&buf, c.Layout, c.viewPath(), c.Data)
|
||||||
|
}
|
||||||
|
return buf.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) renderTemplate() (bytes.Buffer, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if c.TplName == "" {
|
||||||
|
c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
|
||||||
|
}
|
||||||
|
if c.TplPrefix != "" {
|
||||||
|
c.TplName = c.TplPrefix + c.TplName
|
||||||
|
}
|
||||||
|
if BConfig.RunMode == DEV {
|
||||||
|
buildFiles := []string{c.TplName}
|
||||||
|
if c.Layout != "" {
|
||||||
|
buildFiles = append(buildFiles, c.Layout)
|
||||||
|
if c.LayoutSections != nil {
|
||||||
|
for _, sectionTpl := range c.LayoutSections {
|
||||||
|
if sectionTpl == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buildFiles = append(buildFiles, sectionTpl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BuildTemplate(c.viewPath(), buildFiles...)
|
||||||
|
}
|
||||||
|
return buf, ExecuteViewPathTemplate(&buf, c.TplName, c.viewPath(), c.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) viewPath() string {
|
||||||
|
if c.ViewPath == "" {
|
||||||
|
return BConfig.WebConfig.ViewsPath
|
||||||
|
}
|
||||||
|
return c.ViewPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect sends the redirection response to url with status code.
|
||||||
|
func (c *Controller) Redirect(url string, code int) {
|
||||||
|
LogAccess(c.Ctx, nil, code)
|
||||||
|
c.Ctx.Redirect(code, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetData set the data depending on the accepted
|
||||||
|
func (c *Controller) SetData(data interface{}) {
|
||||||
|
accept := c.Ctx.Input.Header("Accept")
|
||||||
|
switch accept {
|
||||||
|
case context.ApplicationYAML:
|
||||||
|
c.Data["yaml"] = data
|
||||||
|
case context.ApplicationXML, context.TextXML:
|
||||||
|
c.Data["xml"] = data
|
||||||
|
default:
|
||||||
|
c.Data["json"] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort stops controller handler and show the error data if code is defined in ErrorMap or code string.
|
||||||
|
func (c *Controller) Abort(code string) {
|
||||||
|
status, err := strconv.Atoi(code)
|
||||||
|
if err != nil {
|
||||||
|
status = 200
|
||||||
|
}
|
||||||
|
c.CustomAbort(status, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body.
|
||||||
|
func (c *Controller) CustomAbort(status int, body string) {
|
||||||
|
// first panic from ErrorMaps, it is user defined error functions.
|
||||||
|
if _, ok := ErrorMaps[body]; ok {
|
||||||
|
c.Ctx.Output.Status = status
|
||||||
|
panic(body)
|
||||||
|
}
|
||||||
|
// last panic user string
|
||||||
|
c.Ctx.ResponseWriter.WriteHeader(status)
|
||||||
|
c.Ctx.ResponseWriter.Write([]byte(body))
|
||||||
|
panic(ErrAbort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopRun makes panic of USERSTOPRUN error and go to recover function if defined.
|
||||||
|
func (c *Controller) StopRun() {
|
||||||
|
panic(ErrAbort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLFor does another controller handler in this request function.
|
||||||
|
// it goes to this controller method if endpoint is not clear.
|
||||||
|
func (c *Controller) URLFor(endpoint string, values ...interface{}) string {
|
||||||
|
if len(endpoint) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if endpoint[0] == '.' {
|
||||||
|
return URLFor(reflect.Indirect(reflect.ValueOf(c.AppController)).Type().Name()+endpoint, values...)
|
||||||
|
}
|
||||||
|
return URLFor(endpoint, values...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeJSON sends a json response with encoding charset.
|
||||||
|
func (c *Controller) ServeJSON(encoding ...bool) {
|
||||||
|
var (
|
||||||
|
hasIndent = BConfig.RunMode != PROD
|
||||||
|
hasEncoding = len(encoding) > 0 && encoding[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
c.Ctx.Output.JSON(c.Data["json"], hasIndent, hasEncoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeJSONP sends a jsonp response.
|
||||||
|
func (c *Controller) ServeJSONP() {
|
||||||
|
hasIndent := BConfig.RunMode != PROD
|
||||||
|
c.Ctx.Output.JSONP(c.Data["jsonp"], hasIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeXML sends xml response.
|
||||||
|
func (c *Controller) ServeXML() {
|
||||||
|
hasIndent := BConfig.RunMode != PROD
|
||||||
|
c.Ctx.Output.XML(c.Data["xml"], hasIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeYAML sends yaml response.
|
||||||
|
func (c *Controller) ServeYAML() {
|
||||||
|
c.Ctx.Output.YAML(c.Data["yaml"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header
|
||||||
|
func (c *Controller) ServeFormatted(encoding ...bool) {
|
||||||
|
hasIndent := BConfig.RunMode != PROD
|
||||||
|
hasEncoding := len(encoding) > 0 && encoding[0]
|
||||||
|
c.Ctx.Output.ServeFormatted(c.Data, hasIndent, hasEncoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input returns the input data map from POST or PUT request body and query string.
|
||||||
|
func (c *Controller) Input() url.Values {
|
||||||
|
if c.Ctx.Request.Form == nil {
|
||||||
|
c.Ctx.Request.ParseForm()
|
||||||
|
}
|
||||||
|
return c.Ctx.Request.Form
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseForm maps input data map to obj struct.
|
||||||
|
func (c *Controller) ParseForm(obj interface{}) error {
|
||||||
|
return ParseForm(c.Input(), obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetString returns the input value by key string or the default value while it's present and input is blank
|
||||||
|
func (c *Controller) GetString(key string, def ...string) string {
|
||||||
|
if v := c.Ctx.Input.Query(key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if len(def) > 0 {
|
||||||
|
return def[0]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStrings returns the input string slice by key string or the default value while it's present and input is blank
|
||||||
|
// it's designed for multi-value input field such as checkbox(input[type=checkbox]), multi-selection.
|
||||||
|
func (c *Controller) GetStrings(key string, def ...[]string) []string {
|
||||||
|
var defv []string
|
||||||
|
if len(def) > 0 {
|
||||||
|
defv = def[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if f := c.Input(); f == nil {
|
||||||
|
return defv
|
||||||
|
} else if vs := f[key]; len(vs) > 0 {
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
return defv
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt returns input as an int or the default value while it's present and input is blank
|
||||||
|
func (c *Controller) GetInt(key string, def ...int) (int, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
return strconv.Atoi(strv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt8 return input as an int8 or the default value while it's present and input is blank
|
||||||
|
func (c *Controller) GetInt8(key string, def ...int8) (int8, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
i64, err := strconv.ParseInt(strv, 10, 8)
|
||||||
|
return int8(i64), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint8 return input as an uint8 or the default value while it's present and input is blank
|
||||||
|
func (c *Controller) GetUint8(key string, def ...uint8) (uint8, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
u64, err := strconv.ParseUint(strv, 10, 8)
|
||||||
|
return uint8(u64), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt16 returns input as an int16 or the default value while it's present and input is blank
|
||||||
|
func (c *Controller) GetInt16(key string, def ...int16) (int16, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
i64, err := strconv.ParseInt(strv, 10, 16)
|
||||||
|
return int16(i64), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint16 returns input as an uint16 or the default value while it's present and input is blank
|
||||||
|
func (c *Controller) GetUint16(key string, def ...uint16) (uint16, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
u64, err := strconv.ParseUint(strv, 10, 16)
|
||||||
|
return uint16(u64), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt32 returns input as an int32 or the default value while it's present and input is blank
|
||||||
|
func (c *Controller) GetInt32(key string, def ...int32) (int32, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
i64, err := strconv.ParseInt(strv, 10, 32)
|
||||||
|
return int32(i64), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint32 returns input as an uint32 or the default value while it's present and input is blank
|
||||||
|
func (c *Controller) GetUint32(key string, def ...uint32) (uint32, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
u64, err := strconv.ParseUint(strv, 10, 32)
|
||||||
|
return uint32(u64), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt64 returns input value as int64 or the default value while it's present and input is blank.
|
||||||
|
func (c *Controller) GetInt64(key string, def ...int64) (int64, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
return strconv.ParseInt(strv, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint64 returns input value as uint64 or the default value while it's present and input is blank.
|
||||||
|
func (c *Controller) GetUint64(key string, def ...uint64) (uint64, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
return strconv.ParseUint(strv, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool returns input value as bool or the default value while it's present and input is blank.
|
||||||
|
func (c *Controller) GetBool(key string, def ...bool) (bool, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
return strconv.ParseBool(strv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat returns input value as float64 or the default value while it's present and input is blank.
|
||||||
|
func (c *Controller) GetFloat(key string, def ...float64) (float64, error) {
|
||||||
|
strv := c.Ctx.Input.Query(key)
|
||||||
|
if len(strv) == 0 && len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
|
}
|
||||||
|
return strconv.ParseFloat(strv, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFile returns the file data in file upload field named as key.
|
||||||
|
// it returns the first one of multi-uploaded files.
|
||||||
|
func (c *Controller) GetFile(key string) (multipart.File, *multipart.FileHeader, error) {
|
||||||
|
return c.Ctx.Request.FormFile(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFiles return multi-upload files
|
||||||
|
// files, err:=c.GetFiles("myfiles")
|
||||||
|
// if err != nil {
|
||||||
|
// http.Error(w, err.Error(), http.StatusNoContent)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// for i, _ := range files {
|
||||||
|
// //for each fileheader, get a handle to the actual file
|
||||||
|
// file, err := files[i].Open()
|
||||||
|
// defer file.Close()
|
||||||
|
// if err != nil {
|
||||||
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// //create destination file making sure the path is writeable.
|
||||||
|
// dst, err := os.Create("upload/" + files[i].Filename)
|
||||||
|
// defer dst.Close()
|
||||||
|
// if err != nil {
|
||||||
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// //copy the uploaded file to the destination file
|
||||||
|
// if _, err := io.Copy(dst, file); err != nil {
|
||||||
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
func (c *Controller) GetFiles(key string) ([]*multipart.FileHeader, error) {
|
||||||
|
if files, ok := c.Ctx.Request.MultipartForm.File[key]; ok {
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
return nil, http.ErrMissingFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveToFile saves uploaded file to new path.
|
||||||
|
// it only operates the first one of mutil-upload form file field.
|
||||||
|
func (c *Controller) SaveToFile(fromfile, tofile string) error {
|
||||||
|
file, _, err := c.Ctx.Request.FormFile(fromfile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
f, err := os.OpenFile(tofile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
io.Copy(f, file)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartSession starts session and load old session data info this controller.
|
||||||
|
func (c *Controller) StartSession() session.Store {
|
||||||
|
if c.CruSession == nil {
|
||||||
|
c.CruSession = c.Ctx.Input.CruSession
|
||||||
|
}
|
||||||
|
return c.CruSession
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSession puts value into session.
|
||||||
|
func (c *Controller) SetSession(name interface{}, value interface{}) {
|
||||||
|
if c.CruSession == nil {
|
||||||
|
c.StartSession()
|
||||||
|
}
|
||||||
|
c.CruSession.Set(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSession gets value from session.
|
||||||
|
func (c *Controller) GetSession(name interface{}) interface{} {
|
||||||
|
if c.CruSession == nil {
|
||||||
|
c.StartSession()
|
||||||
|
}
|
||||||
|
return c.CruSession.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelSession removes value from session.
|
||||||
|
func (c *Controller) DelSession(name interface{}) {
|
||||||
|
if c.CruSession == nil {
|
||||||
|
c.StartSession()
|
||||||
|
}
|
||||||
|
c.CruSession.Delete(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionRegenerateID regenerates session id for this session.
|
||||||
|
// the session data have no changes.
|
||||||
|
func (c *Controller) SessionRegenerateID() {
|
||||||
|
if c.CruSession != nil {
|
||||||
|
c.CruSession.SessionRelease(c.Ctx.ResponseWriter)
|
||||||
|
}
|
||||||
|
c.CruSession = GlobalSessions.SessionRegenerateID(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||||
|
c.Ctx.Input.CruSession = c.CruSession
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroySession cleans session data and session cookie.
|
||||||
|
func (c *Controller) DestroySession() {
|
||||||
|
c.Ctx.Input.CruSession.Flush()
|
||||||
|
c.Ctx.Input.CruSession = nil
|
||||||
|
GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAjax returns this request is ajax or not.
|
||||||
|
func (c *Controller) IsAjax() bool {
|
||||||
|
return c.Ctx.Input.IsAjax()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSecureCookie returns decoded cookie value from encoded browser cookie values.
|
||||||
|
func (c *Controller) GetSecureCookie(Secret, key string) (string, bool) {
|
||||||
|
return c.Ctx.GetSecureCookie(Secret, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSecureCookie puts value into cookie after encoded the value.
|
||||||
|
func (c *Controller) SetSecureCookie(Secret, name, value string, others ...interface{}) {
|
||||||
|
c.Ctx.SetSecureCookie(Secret, name, value, others...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XSRFToken creates a CSRF token string and returns.
|
||||||
|
func (c *Controller) XSRFToken() string {
|
||||||
|
if c._xsrfToken == "" {
|
||||||
|
expire := int64(BConfig.WebConfig.XSRFExpire)
|
||||||
|
if c.XSRFExpire > 0 {
|
||||||
|
expire = int64(c.XSRFExpire)
|
||||||
|
}
|
||||||
|
c._xsrfToken = c.Ctx.XSRFToken(BConfig.WebConfig.XSRFKey, expire)
|
||||||
|
}
|
||||||
|
return c._xsrfToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckXSRFCookie checks xsrf token in this request is valid or not.
|
||||||
|
// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken"
|
||||||
|
// or in form field value named as "_xsrf".
|
||||||
|
func (c *Controller) CheckXSRFCookie() bool {
|
||||||
|
if !c.EnableXSRF {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return c.Ctx.CheckXSRFCookie()
|
||||||
|
}
|
||||||
|
|
||||||
|
// XSRFFormHTML writes an input field contains xsrf token value.
|
||||||
|
func (c *Controller) XSRFFormHTML() string {
|
||||||
|
return `<input type="hidden" name="_xsrf" value="` +
|
||||||
|
c.XSRFToken() + `" />`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetControllerAndAction gets the executing controller name and action name.
|
||||||
|
func (c *Controller) GetControllerAndAction() (string, string) {
|
||||||
|
return c.controllerName, c.actionName
|
||||||
|
}
|
181
pkg/controller_test.go
Normal file
181
pkg/controller_test.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
// 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 (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetInt(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", "40")
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetInt("age")
|
||||||
|
if val != 40 {
|
||||||
|
t.Errorf("TestGetInt expect 40,get %T,%v", val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInt8(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", "40")
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetInt8("age")
|
||||||
|
if val != 40 {
|
||||||
|
t.Errorf("TestGetInt8 expect 40,get %T,%v", val, val)
|
||||||
|
}
|
||||||
|
//Output: int8
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInt16(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", "40")
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetInt16("age")
|
||||||
|
if val != 40 {
|
||||||
|
t.Errorf("TestGetInt16 expect 40,get %T,%v", val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInt32(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", "40")
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetInt32("age")
|
||||||
|
if val != 40 {
|
||||||
|
t.Errorf("TestGetInt32 expect 40,get %T,%v", val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInt64(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", "40")
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetInt64("age")
|
||||||
|
if val != 40 {
|
||||||
|
t.Errorf("TestGeetInt64 expect 40,get %T,%v", val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUint8(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", strconv.FormatUint(math.MaxUint8, 10))
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetUint8("age")
|
||||||
|
if val != math.MaxUint8 {
|
||||||
|
t.Errorf("TestGetUint8 expect %v,get %T,%v", math.MaxUint8, val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUint16(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", strconv.FormatUint(math.MaxUint16, 10))
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetUint16("age")
|
||||||
|
if val != math.MaxUint16 {
|
||||||
|
t.Errorf("TestGetUint16 expect %v,get %T,%v", math.MaxUint16, val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUint32(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", strconv.FormatUint(math.MaxUint32, 10))
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetUint32("age")
|
||||||
|
if val != math.MaxUint32 {
|
||||||
|
t.Errorf("TestGetUint32 expect %v,get %T,%v", math.MaxUint32, val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUint64(t *testing.T) {
|
||||||
|
i := context.NewInput()
|
||||||
|
i.SetParam("age", strconv.FormatUint(math.MaxUint64, 10))
|
||||||
|
ctx := &context.Context{Input: i}
|
||||||
|
ctrlr := Controller{Ctx: ctx}
|
||||||
|
val, _ := ctrlr.GetUint64("age")
|
||||||
|
if val != math.MaxUint64 {
|
||||||
|
t.Errorf("TestGetUint64 expect %v,get %T,%v", uint64(math.MaxUint64), val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdditionalViewPaths(t *testing.T) {
|
||||||
|
dir1 := "_beeTmp"
|
||||||
|
dir2 := "_beeTmp2"
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer os.RemoveAll(dir2)
|
||||||
|
|
||||||
|
dir1file := "file1.tpl"
|
||||||
|
dir2file := "file2.tpl"
|
||||||
|
|
||||||
|
genFile := func(dir string, name string, content string) {
|
||||||
|
os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777)
|
||||||
|
if f, err := os.Create(filepath.Join(dir, name)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
defer f.Close()
|
||||||
|
f.WriteString(content)
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
genFile(dir1, dir1file, `<div>{{.Content}}</div>`)
|
||||||
|
genFile(dir2, dir2file, `<html>{{.Content}}</html>`)
|
||||||
|
|
||||||
|
AddViewPath(dir1)
|
||||||
|
AddViewPath(dir2)
|
||||||
|
|
||||||
|
ctrl := Controller{
|
||||||
|
TplName: "file1.tpl",
|
||||||
|
ViewPath: dir1,
|
||||||
|
}
|
||||||
|
ctrl.Data = map[interface{}]interface{}{
|
||||||
|
"Content": "value2",
|
||||||
|
}
|
||||||
|
if result, err := ctrl.RenderString(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
if result != "<div>value2</div>" {
|
||||||
|
t.Fatalf("TestAdditionalViewPaths expect %s got %s", "<div>value2</div>", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
ctrl.TplName = "file2.tpl"
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil {
|
||||||
|
t.Fatal("TestAdditionalViewPaths expected error")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
ctrl.RenderString()
|
||||||
|
}()
|
||||||
|
|
||||||
|
ctrl.TplName = "file2.tpl"
|
||||||
|
ctrl.ViewPath = dir2
|
||||||
|
ctrl.RenderString()
|
||||||
|
}
|
17
pkg/doc.go
Normal file
17
pkg/doc.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
Package beego provide a MVC framework
|
||||||
|
beego: an open-source, high-performance, modular, full-stack web framework
|
||||||
|
|
||||||
|
It is used for rapid development of RESTful APIs, web apps and backend services in Go.
|
||||||
|
beego is inspired by Tornado, Sinatra and Flask with the added benefit of some Go-specific features such as interfaces and struct embedding.
|
||||||
|
|
||||||
|
package main
|
||||||
|
import "github.com/astaxie/beego"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
beego.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
more information: http://beego.me
|
||||||
|
*/
|
||||||
|
package beego
|
488
pkg/error.go
Normal file
488
pkg/error.go
Normal file
@ -0,0 +1,488 @@
|
|||||||
|
// 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"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
errorTypeHandler = iota
|
||||||
|
errorTypeController
|
||||||
|
)
|
||||||
|
|
||||||
|
var tpl = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<title>beego application error</title>
|
||||||
|
<style>
|
||||||
|
html, body, body * {padding: 0; margin: 0;}
|
||||||
|
#header {background:#ffd; border-bottom:solid 2px #A31515; padding: 20px 10px;}
|
||||||
|
#header h2{ }
|
||||||
|
#footer {border-top:solid 1px #aaa; padding: 5px 10px; font-size: 12px; color:green;}
|
||||||
|
#content {padding: 5px;}
|
||||||
|
#content .stack b{ font-size: 13px; color: red;}
|
||||||
|
#content .stack pre{padding-left: 10px;}
|
||||||
|
table {}
|
||||||
|
td.t {text-align: right; padding-right: 5px; color: #888;}
|
||||||
|
</style>
|
||||||
|
<script type="text/javascript">
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="header">
|
||||||
|
<h2>{{.AppError}}</h2>
|
||||||
|
</div>
|
||||||
|
<div id="content">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="t">Request Method: </td><td>{{.RequestMethod}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="t">Request URL: </td><td>{{.RequestURL}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="t">RemoteAddr: </td><td>{{.RemoteAddr }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="stack">
|
||||||
|
<b>Stack</b>
|
||||||
|
<pre>{{.Stack}}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="footer">
|
||||||
|
<p>beego {{ .BeegoVersion }} (beego framework)</p>
|
||||||
|
<p>golang version: {{.GoVersion}}</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
// render default application error page with error and stack string.
|
||||||
|
func showErr(err interface{}, ctx *context.Context, stack string) {
|
||||||
|
t, _ := template.New("beegoerrortemp").Parse(tpl)
|
||||||
|
data := map[string]string{
|
||||||
|
"AppError": fmt.Sprintf("%s:%v", BConfig.AppName, err),
|
||||||
|
"RequestMethod": ctx.Input.Method(),
|
||||||
|
"RequestURL": ctx.Input.URI(),
|
||||||
|
"RemoteAddr": ctx.Input.IP(),
|
||||||
|
"Stack": stack,
|
||||||
|
"BeegoVersion": VERSION,
|
||||||
|
"GoVersion": runtime.Version(),
|
||||||
|
}
|
||||||
|
t.Execute(ctx.ResponseWriter, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errtpl = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<title>{{.Title}}</title>
|
||||||
|
<style type="text/css">
|
||||||
|
* {
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color:#EFEFEF;
|
||||||
|
font: .9em "Lucida Sans Unicode", "Lucida Grande", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper{
|
||||||
|
width:600px;
|
||||||
|
margin:40px auto 0;
|
||||||
|
text-align:center;
|
||||||
|
-moz-box-shadow: 5px 5px 10px rgba(0,0,0,0.3);
|
||||||
|
-webkit-box-shadow: 5px 5px 10px rgba(0,0,0,0.3);
|
||||||
|
box-shadow: 5px 5px 10px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper h1{
|
||||||
|
color:#FFF;
|
||||||
|
text-align:center;
|
||||||
|
margin-bottom:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper a{
|
||||||
|
display:block;
|
||||||
|
font-size:.9em;
|
||||||
|
padding-top:20px;
|
||||||
|
color:#FFF;
|
||||||
|
text-decoration:none;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
width:600px;
|
||||||
|
padding-bottom:15px;
|
||||||
|
background-color:#FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navtop{
|
||||||
|
height:40px;
|
||||||
|
background-color:#24B2EB;
|
||||||
|
padding:13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding:10px 10px 25px;
|
||||||
|
background: #FFFFFF;
|
||||||
|
margin:;
|
||||||
|
color:#333;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button{
|
||||||
|
color:white;
|
||||||
|
padding:15px 20px;
|
||||||
|
text-shadow:1px 1px 0 #00A5FF;
|
||||||
|
font-weight:bold;
|
||||||
|
text-align:center;
|
||||||
|
border:1px solid #24B2EB;
|
||||||
|
margin:0px 200px;
|
||||||
|
clear:both;
|
||||||
|
background-color: #24B2EB;
|
||||||
|
border-radius:100px;
|
||||||
|
-moz-border-radius:100px;
|
||||||
|
-webkit-border-radius:100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button:hover{
|
||||||
|
text-decoration:none;
|
||||||
|
background-color: #24B2EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="wrapper">
|
||||||
|
<div id="container">
|
||||||
|
<div class="navtop">
|
||||||
|
<h1>{{.Title}}</h1>
|
||||||
|
</div>
|
||||||
|
<div id="content">
|
||||||
|
{{.Content}}
|
||||||
|
<a href="/" title="Home" class="button">Go Home</a><br />
|
||||||
|
|
||||||
|
<br>Powered by beego {{.BeegoVersion}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
type errorInfo struct {
|
||||||
|
controllerType reflect.Type
|
||||||
|
handler http.HandlerFunc
|
||||||
|
method string
|
||||||
|
errorType int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMaps holds map of http handlers for each error string.
|
||||||
|
// there is 10 kinds default error(40x and 50x)
|
||||||
|
var ErrorMaps = make(map[string]*errorInfo, 10)
|
||||||
|
|
||||||
|
// show 401 unauthorized error.
|
||||||
|
func unauthorized(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
401,
|
||||||
|
"<br>The page you have requested can't be authorized."+
|
||||||
|
"<br>Perhaps you are here because:"+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>The credentials you supplied are incorrect"+
|
||||||
|
"<br>There are errors in the website address"+
|
||||||
|
"</ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 402 Payment Required
|
||||||
|
func paymentRequired(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
402,
|
||||||
|
"<br>The page you have requested Payment Required."+
|
||||||
|
"<br>Perhaps you are here because:"+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>The credentials you supplied are incorrect"+
|
||||||
|
"<br>There are errors in the website address"+
|
||||||
|
"</ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 403 forbidden error.
|
||||||
|
func forbidden(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
403,
|
||||||
|
"<br>The page you have requested is forbidden."+
|
||||||
|
"<br>Perhaps you are here because:"+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>Your address may be blocked"+
|
||||||
|
"<br>The site may be disabled"+
|
||||||
|
"<br>You need to log in"+
|
||||||
|
"</ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 422 missing xsrf token
|
||||||
|
func missingxsrf(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
422,
|
||||||
|
"<br>The page you have requested is forbidden."+
|
||||||
|
"<br>Perhaps you are here because:"+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>'_xsrf' argument missing from POST"+
|
||||||
|
"</ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 417 invalid xsrf token
|
||||||
|
func invalidxsrf(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
417,
|
||||||
|
"<br>The page you have requested is forbidden."+
|
||||||
|
"<br>Perhaps you are here because:"+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>expected XSRF not found"+
|
||||||
|
"</ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 404 not found error.
|
||||||
|
func notFound(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
404,
|
||||||
|
"<br>The page you have requested has flown the coop."+
|
||||||
|
"<br>Perhaps you are here because:"+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>The page has moved"+
|
||||||
|
"<br>The page no longer exists"+
|
||||||
|
"<br>You were looking for your puppy and got lost"+
|
||||||
|
"<br>You like 404 pages"+
|
||||||
|
"</ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 405 Method Not Allowed
|
||||||
|
func methodNotAllowed(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
405,
|
||||||
|
"<br>The method you have requested Not Allowed."+
|
||||||
|
"<br>Perhaps you are here because:"+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>The method specified in the Request-Line is not allowed for the resource identified by the Request-URI"+
|
||||||
|
"<br>The response MUST include an Allow header containing a list of valid methods for the requested resource."+
|
||||||
|
"</ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 500 internal server error.
|
||||||
|
func internalServerError(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
500,
|
||||||
|
"<br>The page you have requested is down right now."+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>Please try again later and report the error to the website administrator"+
|
||||||
|
"<br></ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 501 Not Implemented.
|
||||||
|
func notImplemented(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
501,
|
||||||
|
"<br>The page you have requested is Not Implemented."+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>Please try again later and report the error to the website administrator"+
|
||||||
|
"<br></ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 502 Bad Gateway.
|
||||||
|
func badGateway(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
502,
|
||||||
|
"<br>The page you have requested is down right now."+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br>The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request."+
|
||||||
|
"<br>Please try again later and report the error to the website administrator"+
|
||||||
|
"<br></ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 503 service unavailable error.
|
||||||
|
func serviceUnavailable(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
503,
|
||||||
|
"<br>The page you have requested is unavailable."+
|
||||||
|
"<br>Perhaps you are here because:"+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br><br>The page is overloaded"+
|
||||||
|
"<br>Please try again later."+
|
||||||
|
"</ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 504 Gateway Timeout.
|
||||||
|
func gatewayTimeout(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
504,
|
||||||
|
"<br>The page you have requested is unavailable"+
|
||||||
|
"<br>Perhaps you are here because:"+
|
||||||
|
"<br><br><ul>"+
|
||||||
|
"<br><br>The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI."+
|
||||||
|
"<br>Please try again later."+
|
||||||
|
"</ul>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show 413 Payload Too Large
|
||||||
|
func payloadTooLarge(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
responseError(rw, r,
|
||||||
|
413,
|
||||||
|
`<br>The page you have requested is unavailable.
|
||||||
|
<br>Perhaps you are here because:<br><br>
|
||||||
|
<ul>
|
||||||
|
<br>The request entity is larger than limits defined by server.
|
||||||
|
<br>Please change the request entity and try again.
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) {
|
||||||
|
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||||
|
data := M{
|
||||||
|
"Title": http.StatusText(errCode),
|
||||||
|
"BeegoVersion": VERSION,
|
||||||
|
"Content": template.HTML(errContent),
|
||||||
|
}
|
||||||
|
t.Execute(rw, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorHandler registers http.HandlerFunc to each http err code string.
|
||||||
|
// usage:
|
||||||
|
// beego.ErrorHandler("404",NotFound)
|
||||||
|
// beego.ErrorHandler("500",InternalServerError)
|
||||||
|
func ErrorHandler(code string, h http.HandlerFunc) *App {
|
||||||
|
ErrorMaps[code] = &errorInfo{
|
||||||
|
errorType: errorTypeHandler,
|
||||||
|
handler: h,
|
||||||
|
method: code,
|
||||||
|
}
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorController registers ControllerInterface to each http err code string.
|
||||||
|
// usage:
|
||||||
|
// beego.ErrorController(&controllers.ErrorController{})
|
||||||
|
func ErrorController(c ControllerInterface) *App {
|
||||||
|
reflectVal := reflect.ValueOf(c)
|
||||||
|
rt := reflectVal.Type()
|
||||||
|
ct := reflect.Indirect(reflectVal).Type()
|
||||||
|
for i := 0; i < rt.NumMethod(); i++ {
|
||||||
|
methodName := rt.Method(i).Name
|
||||||
|
if !utils.InSlice(methodName, exceptMethod) && strings.HasPrefix(methodName, "Error") {
|
||||||
|
errName := strings.TrimPrefix(methodName, "Error")
|
||||||
|
ErrorMaps[errName] = &errorInfo{
|
||||||
|
errorType: errorTypeController,
|
||||||
|
controllerType: ct,
|
||||||
|
method: methodName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exception Write HttpStatus with errCode and Exec error handler if exist.
|
||||||
|
func Exception(errCode uint64, ctx *context.Context) {
|
||||||
|
exception(strconv.FormatUint(errCode, 10), ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show error string as simple text message.
|
||||||
|
// if error string is empty, show 503 or 500 error as default.
|
||||||
|
func exception(errCode string, ctx *context.Context) {
|
||||||
|
atoi := func(code string) int {
|
||||||
|
v, err := strconv.Atoi(code)
|
||||||
|
if err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if ctx.Output.Status == 0 {
|
||||||
|
return 503
|
||||||
|
}
|
||||||
|
return ctx.Output.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ec := range []string{errCode, "503", "500"} {
|
||||||
|
if h, ok := ErrorMaps[ec]; ok {
|
||||||
|
executeError(h, ctx, atoi(ec))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//if 50x error has been removed from errorMap
|
||||||
|
ctx.ResponseWriter.WriteHeader(atoi(errCode))
|
||||||
|
ctx.WriteString(errCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeError(err *errorInfo, ctx *context.Context, code int) {
|
||||||
|
//make sure to log the error in the access log
|
||||||
|
LogAccess(ctx, nil, code)
|
||||||
|
|
||||||
|
if err.errorType == errorTypeHandler {
|
||||||
|
ctx.ResponseWriter.WriteHeader(code)
|
||||||
|
err.handler(ctx.ResponseWriter, ctx.Request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err.errorType == errorTypeController {
|
||||||
|
ctx.Output.SetStatus(code)
|
||||||
|
//Invoke the request handler
|
||||||
|
vc := reflect.New(err.controllerType)
|
||||||
|
execController, ok := vc.Interface().(ControllerInterface)
|
||||||
|
if !ok {
|
||||||
|
panic("controller is not ControllerInterface")
|
||||||
|
}
|
||||||
|
//call the controller init function
|
||||||
|
execController.Init(ctx, err.controllerType.Name(), err.method, vc.Interface())
|
||||||
|
|
||||||
|
//call prepare function
|
||||||
|
execController.Prepare()
|
||||||
|
|
||||||
|
execController.URLMapping()
|
||||||
|
|
||||||
|
method := vc.MethodByName(err.method)
|
||||||
|
method.Call([]reflect.Value{})
|
||||||
|
|
||||||
|
//render template
|
||||||
|
if BConfig.WebConfig.AutoRender {
|
||||||
|
if err := execController.Render(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finish all runrouter. release resource
|
||||||
|
execController.Finish()
|
||||||
|
}
|
||||||
|
}
|
88
pkg/error_test.go
Normal file
88
pkg/error_test.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// Copyright 2016 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 (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type errorTestController struct {
|
||||||
|
Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseCodeError = "parse code error"
|
||||||
|
|
||||||
|
func (ec *errorTestController) Get() {
|
||||||
|
errorCode, err := ec.GetInt("code")
|
||||||
|
if err != nil {
|
||||||
|
ec.Abort(parseCodeError)
|
||||||
|
}
|
||||||
|
if errorCode != 0 {
|
||||||
|
ec.CustomAbort(errorCode, ec.GetString("code"))
|
||||||
|
}
|
||||||
|
ec.Abort("404")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorCode_01(t *testing.T) {
|
||||||
|
registerDefaultErrorHandler()
|
||||||
|
for k := range ErrorMaps {
|
||||||
|
r, _ := http.NewRequest("GET", "/error?code="+k, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler := NewControllerRegister()
|
||||||
|
handler.Add("/error", &errorTestController{})
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
code, _ := strconv.Atoi(k)
|
||||||
|
if w.Code != code {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if !strings.Contains(w.Body.String(), http.StatusText(code)) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorCode_02(t *testing.T) {
|
||||||
|
registerDefaultErrorHandler()
|
||||||
|
r, _ := http.NewRequest("GET", "/error?code=0", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler := NewControllerRegister()
|
||||||
|
handler.Add("/error", &errorTestController{})
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
if w.Code != 404 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorCode_03(t *testing.T) {
|
||||||
|
registerDefaultErrorHandler()
|
||||||
|
r, _ := http.NewRequest("GET", "/error?code=panic", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler := NewControllerRegister()
|
||||||
|
handler.Add("/error", &errorTestController{})
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
if w.Code != 200 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if w.Body.String() != parseCodeError {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
44
pkg/filter.go
Normal file
44
pkg/filter.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// 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 "github.com/astaxie/beego/context"
|
||||||
|
|
||||||
|
// FilterFunc defines a filter function which is invoked before the controller handler is executed.
|
||||||
|
type FilterFunc func(*context.Context)
|
||||||
|
|
||||||
|
// FilterRouter defines a filter operation which is invoked before the controller handler is executed.
|
||||||
|
// It can match the URL against a pattern, and execute a filter function
|
||||||
|
// when a request with a matching URL arrives.
|
||||||
|
type FilterRouter struct {
|
||||||
|
filterFunc FilterFunc
|
||||||
|
tree *Tree
|
||||||
|
pattern string
|
||||||
|
returnOnOutput bool
|
||||||
|
resetParams bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidRouter checks if the current request is matched by this filter.
|
||||||
|
// If the request is matched, the values of the URL parameters defined
|
||||||
|
// by the filter pattern are also returned.
|
||||||
|
func (f *FilterRouter) ValidRouter(url string, ctx *context.Context) bool {
|
||||||
|
isOk := f.tree.Match(url, ctx)
|
||||||
|
if isOk != nil {
|
||||||
|
if b, ok := isOk.(bool); ok {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
68
pkg/filter_test.go
Normal file
68
pkg/filter_test.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// 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 (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
var FilterUser = func(ctx *context.Context) {
|
||||||
|
ctx.Output.Body([]byte("i am " + ctx.Input.Param(":last") + ctx.Input.Param(":first")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilter(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", "/person/asta/Xie", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler := NewControllerRegister()
|
||||||
|
handler.InsertFilter("/person/:last/:first", BeforeRouter, FilterUser)
|
||||||
|
handler.Add("/person/:last/:first", &TestController{})
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
if w.Body.String() != "i am astaXie" {
|
||||||
|
t.Errorf("user define func can't run")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var FilterAdminUser = func(ctx *context.Context) {
|
||||||
|
ctx.Output.Body([]byte("i am admin"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter pattern /admin/:all
|
||||||
|
// all url like /admin/ /admin/xie will all get filter
|
||||||
|
|
||||||
|
func TestPatternTwo(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", "/admin/", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler := NewControllerRegister()
|
||||||
|
handler.InsertFilter("/admin/?:all", BeforeRouter, FilterAdminUser)
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
if w.Body.String() != "i am admin" {
|
||||||
|
t.Errorf("filter /admin/ can't run")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPatternThree(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", "/admin/astaxie", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler := NewControllerRegister()
|
||||||
|
handler.InsertFilter("/admin/:all", BeforeRouter, FilterAdminUser)
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
if w.Body.String() != "i am admin" {
|
||||||
|
t.Errorf("filter /admin/astaxie can't run")
|
||||||
|
}
|
||||||
|
}
|
110
pkg/flash.go
Normal file
110
pkg/flash.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// 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"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FlashData is a tools to maintain data when using across request.
|
||||||
|
type FlashData struct {
|
||||||
|
Data map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFlash return a new empty FlashData struct.
|
||||||
|
func NewFlash() *FlashData {
|
||||||
|
return &FlashData{
|
||||||
|
Data: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (fd *FlashData) Notice(msg string, args ...interface{}) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
fd.Data["notice"] = msg
|
||||||
|
} else {
|
||||||
|
fd.Data["notice"] = fmt.Sprintf(msg, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning writes warning message to flash.
|
||||||
|
func (fd *FlashData) Warning(msg string, args ...interface{}) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
fd.Data["warning"] = msg
|
||||||
|
} else {
|
||||||
|
fd.Data["warning"] = fmt.Sprintf(msg, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error writes error message to flash.
|
||||||
|
func (fd *FlashData) Error(msg string, args ...interface{}) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
fd.Data["error"] = msg
|
||||||
|
} else {
|
||||||
|
fd.Data["error"] = fmt.Sprintf(msg, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store does the saving operation of flash data.
|
||||||
|
// the data are encoded and saved in cookie.
|
||||||
|
func (fd *FlashData) Store(c *Controller) {
|
||||||
|
c.Data["flash"] = fd.Data
|
||||||
|
var flashValue string
|
||||||
|
for key, value := range fd.Data {
|
||||||
|
flashValue += "\x00" + key + "\x23" + BConfig.WebConfig.FlashSeparator + "\x23" + value + "\x00"
|
||||||
|
}
|
||||||
|
c.Ctx.SetCookie(BConfig.WebConfig.FlashName, url.QueryEscape(flashValue), 0, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFromRequest parsed flash data from encoded values in cookie.
|
||||||
|
func ReadFromRequest(c *Controller) *FlashData {
|
||||||
|
flash := NewFlash()
|
||||||
|
if cookie, err := c.Ctx.Request.Cookie(BConfig.WebConfig.FlashName); err == nil {
|
||||||
|
v, _ := url.QueryUnescape(cookie.Value)
|
||||||
|
vals := strings.Split(v, "\x00")
|
||||||
|
for _, v := range vals {
|
||||||
|
if len(v) > 0 {
|
||||||
|
kv := strings.Split(v, "\x23"+BConfig.WebConfig.FlashSeparator+"\x23")
|
||||||
|
if len(kv) == 2 {
|
||||||
|
flash.Data[kv[0]] = kv[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//read one time then delete it
|
||||||
|
c.Ctx.SetCookie(BConfig.WebConfig.FlashName, "", -1, "/")
|
||||||
|
}
|
||||||
|
c.Data["flash"] = flash.Data
|
||||||
|
return flash
|
||||||
|
}
|
54
pkg/flash_test.go
Normal file
54
pkg/flash_test.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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 (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestFlashController struct {
|
||||||
|
Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestFlashController) TestWriteFlash() {
|
||||||
|
flash := NewFlash()
|
||||||
|
flash.Notice("TestFlashString")
|
||||||
|
flash.Store(&t.Controller)
|
||||||
|
// we choose to serve json because we don't want to load a template html file
|
||||||
|
t.ServeJSON(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlashHeader(t *testing.T) {
|
||||||
|
// create fake GET request
|
||||||
|
r, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// setup the handler
|
||||||
|
handler := NewControllerRegister()
|
||||||
|
handler.Add("/", &TestFlashController{}, "get:TestWriteFlash")
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
// get the Set-Cookie value
|
||||||
|
sc := w.Header().Get("Set-Cookie")
|
||||||
|
// match for the expected header
|
||||||
|
res := strings.Contains(sc, "BEEGO_FLASH=%00notice%23BEEGOFLASH%23TestFlashString%00")
|
||||||
|
// validate the assertion
|
||||||
|
if !res {
|
||||||
|
t.Errorf("TestFlashHeader() unable to validate flash message")
|
||||||
|
}
|
||||||
|
}
|
74
pkg/fs.go
Normal file
74
pkg/fs.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileSystem struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d FileSystem) Open(name string) (http.File, error) {
|
||||||
|
return os.Open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk walks the file tree rooted at root in filesystem, calling walkFn for each file or
|
||||||
|
// directory in the tree, including root. All errors that arise visiting files
|
||||||
|
// and directories are filtered by walkFn.
|
||||||
|
func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error {
|
||||||
|
|
||||||
|
f, err := fs.Open(root)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
err = walkFn(root, nil, err)
|
||||||
|
} else {
|
||||||
|
err = walk(fs, root, info, walkFn)
|
||||||
|
}
|
||||||
|
if err == filepath.SkipDir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk recursively descends path, calling walkFn.
|
||||||
|
func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
||||||
|
var err error
|
||||||
|
if !info.IsDir() {
|
||||||
|
return walkFn(path, info, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, err := fs.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
if err1 := walkFn(path, info, err); err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dir.Close()
|
||||||
|
dirs, err := dir.Readdir(-1)
|
||||||
|
err1 := walkFn(path, info, err)
|
||||||
|
// If err != nil, walk can't walk into this directory.
|
||||||
|
// err1 != nil means walkFn want walk to skip this directory or stop walking.
|
||||||
|
// Therefore, if one of err and err1 isn't nil, walk will return.
|
||||||
|
if err != nil || err1 != nil {
|
||||||
|
// The caller's behavior is controlled by the return value, which is decided
|
||||||
|
// by walkFn. walkFn may ignore err and return nil.
|
||||||
|
// If walkFn returns SkipDir, it will be handled by the caller.
|
||||||
|
// So walk should return whatever walkFn returns.
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fileInfo := range dirs {
|
||||||
|
filename := filepath.Join(path, fileInfo.Name())
|
||||||
|
if err = walk(fs, filename, fileInfo, walkFn); err != nil {
|
||||||
|
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
166
pkg/grace/grace.go
Normal file
166
pkg/grace/grace.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
// 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 grace use to hot reload
|
||||||
|
// Description: http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// import(
|
||||||
|
// "log"
|
||||||
|
// "net/http"
|
||||||
|
// "os"
|
||||||
|
//
|
||||||
|
// "github.com/astaxie/beego/grace"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// w.Write([]byte("WORLD!"))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// mux := http.NewServeMux()
|
||||||
|
// mux.HandleFunc("/hello", handler)
|
||||||
|
//
|
||||||
|
// err := grace.ListenAndServe("localhost:8080", mux)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Println(err)
|
||||||
|
// }
|
||||||
|
// log.Println("Server on 8080 stopped")
|
||||||
|
// os.Exit(0)
|
||||||
|
// }
|
||||||
|
package grace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PreSignal is the position to add filter before signal
|
||||||
|
PreSignal = iota
|
||||||
|
// PostSignal is the position to add filter after signal
|
||||||
|
PostSignal
|
||||||
|
// StateInit represent the application inited
|
||||||
|
StateInit
|
||||||
|
// StateRunning represent the application is running
|
||||||
|
StateRunning
|
||||||
|
// StateShuttingDown represent the application is shutting down
|
||||||
|
StateShuttingDown
|
||||||
|
// StateTerminate represent the application is killed
|
||||||
|
StateTerminate
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
regLock *sync.Mutex
|
||||||
|
runningServers map[string]*Server
|
||||||
|
runningServersOrder []string
|
||||||
|
socketPtrOffsetMap map[string]uint
|
||||||
|
runningServersForked bool
|
||||||
|
|
||||||
|
// DefaultReadTimeOut is the HTTP read timeout
|
||||||
|
DefaultReadTimeOut time.Duration
|
||||||
|
// DefaultWriteTimeOut is the HTTP Write timeout
|
||||||
|
DefaultWriteTimeOut time.Duration
|
||||||
|
// DefaultMaxHeaderBytes is the Max HTTP Header size, default is 0, no limit
|
||||||
|
DefaultMaxHeaderBytes int
|
||||||
|
// DefaultTimeout is the shutdown server's timeout. default is 60s
|
||||||
|
DefaultTimeout = 60 * time.Second
|
||||||
|
|
||||||
|
isChild bool
|
||||||
|
socketOrder string
|
||||||
|
|
||||||
|
hookableSignals []os.Signal
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&isChild, "graceful", false, "listen on open fd (after forking)")
|
||||||
|
flag.StringVar(&socketOrder, "socketorder", "", "previous initialization order - used when more than one listener was started")
|
||||||
|
|
||||||
|
regLock = &sync.Mutex{}
|
||||||
|
runningServers = make(map[string]*Server)
|
||||||
|
runningServersOrder = []string{}
|
||||||
|
socketPtrOffsetMap = make(map[string]uint)
|
||||||
|
|
||||||
|
hookableSignals = []os.Signal{
|
||||||
|
syscall.SIGHUP,
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer returns a new graceServer.
|
||||||
|
func NewServer(addr string, handler http.Handler) (srv *Server) {
|
||||||
|
regLock.Lock()
|
||||||
|
defer regLock.Unlock()
|
||||||
|
|
||||||
|
if !flag.Parsed() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
if len(socketOrder) > 0 {
|
||||||
|
for i, addr := range strings.Split(socketOrder, ",") {
|
||||||
|
socketPtrOffsetMap[addr] = uint(i)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
socketPtrOffsetMap[addr] = uint(len(runningServersOrder))
|
||||||
|
}
|
||||||
|
|
||||||
|
srv = &Server{
|
||||||
|
sigChan: make(chan os.Signal),
|
||||||
|
isChild: isChild,
|
||||||
|
SignalHooks: map[int]map[os.Signal][]func(){
|
||||||
|
PreSignal: {
|
||||||
|
syscall.SIGHUP: {},
|
||||||
|
syscall.SIGINT: {},
|
||||||
|
syscall.SIGTERM: {},
|
||||||
|
},
|
||||||
|
PostSignal: {
|
||||||
|
syscall.SIGHUP: {},
|
||||||
|
syscall.SIGINT: {},
|
||||||
|
syscall.SIGTERM: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state: StateInit,
|
||||||
|
Network: "tcp",
|
||||||
|
terminalChan: make(chan error), //no cache channel
|
||||||
|
}
|
||||||
|
srv.Server = &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
ReadTimeout: DefaultReadTimeOut,
|
||||||
|
WriteTimeout: DefaultWriteTimeOut,
|
||||||
|
MaxHeaderBytes: DefaultMaxHeaderBytes,
|
||||||
|
Handler: handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
runningServersOrder = append(runningServersOrder, addr)
|
||||||
|
runningServers[addr] = srv
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe refer http.ListenAndServe
|
||||||
|
func ListenAndServe(addr string, handler http.Handler) error {
|
||||||
|
server := NewServer(addr, handler)
|
||||||
|
return server.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServeTLS refer http.ListenAndServeTLS
|
||||||
|
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error {
|
||||||
|
server := NewServer(addr, handler)
|
||||||
|
return server.ListenAndServeTLS(certFile, keyFile)
|
||||||
|
}
|
356
pkg/grace/server.go
Normal file
356
pkg/grace/server.go
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
package grace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server embedded http.Server
|
||||||
|
type Server struct {
|
||||||
|
*http.Server
|
||||||
|
ln net.Listener
|
||||||
|
SignalHooks map[int]map[os.Signal][]func()
|
||||||
|
sigChan chan os.Signal
|
||||||
|
isChild bool
|
||||||
|
state uint8
|
||||||
|
Network string
|
||||||
|
terminalChan chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve accepts incoming connections on the Listener l,
|
||||||
|
// creating a new service goroutine for each.
|
||||||
|
// The service goroutines read requests and then call srv.Handler to reply to them.
|
||||||
|
func (srv *Server) Serve() (err error) {
|
||||||
|
srv.state = StateRunning
|
||||||
|
defer func() { srv.state = StateTerminate }()
|
||||||
|
|
||||||
|
// When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS
|
||||||
|
// immediately return ErrServerClosed. Make sure the program doesn't exit
|
||||||
|
// and waits instead for Shutdown to return.
|
||||||
|
if err = srv.Server.Serve(srv.ln); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Println(syscall.Getpid(), "Server.Serve() error:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(syscall.Getpid(), srv.ln.Addr(), "Listener closed.")
|
||||||
|
// wait for Shutdown to return
|
||||||
|
if shutdownErr := <-srv.terminalChan; shutdownErr != nil {
|
||||||
|
return shutdownErr
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe listens on the TCP network address srv.Addr and then calls Serve
|
||||||
|
// to handle requests on incoming connections. If srv.Addr is blank, ":http" is
|
||||||
|
// used.
|
||||||
|
func (srv *Server) ListenAndServe() (err error) {
|
||||||
|
addr := srv.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":http"
|
||||||
|
}
|
||||||
|
|
||||||
|
go srv.handleSignals()
|
||||||
|
|
||||||
|
srv.ln, err = srv.getListener(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if srv.isChild {
|
||||||
|
process, err := os.FindProcess(os.Getppid())
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = process.Signal(syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(os.Getpid(), srv.Addr)
|
||||||
|
return srv.Serve()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServeTLS listens on the TCP network address srv.Addr and then calls
|
||||||
|
// Serve to handle requests on incoming TLS connections.
|
||||||
|
//
|
||||||
|
// Filenames containing a certificate and matching private key for the server must
|
||||||
|
// be provided. If the certificate is signed by a certificate authority, the
|
||||||
|
// certFile should be the concatenation of the server's certificate followed by the
|
||||||
|
// CA's certificate.
|
||||||
|
//
|
||||||
|
// If srv.Addr is blank, ":https" is used.
|
||||||
|
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
|
||||||
|
addr := srv.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":https"
|
||||||
|
}
|
||||||
|
|
||||||
|
if srv.TLSConfig == nil {
|
||||||
|
srv.TLSConfig = &tls.Config{}
|
||||||
|
}
|
||||||
|
if srv.TLSConfig.NextProtos == nil {
|
||||||
|
srv.TLSConfig.NextProtos = []string{"http/1.1"}
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.TLSConfig.Certificates = make([]tls.Certificate, 1)
|
||||||
|
srv.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go srv.handleSignals()
|
||||||
|
|
||||||
|
ln, err := srv.getListener(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srv.ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig)
|
||||||
|
|
||||||
|
if srv.isChild {
|
||||||
|
process, err := os.FindProcess(os.Getppid())
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = process.Signal(syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(os.Getpid(), srv.Addr)
|
||||||
|
return srv.Serve()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServeMutualTLS listens on the TCP network address srv.Addr and then calls
|
||||||
|
// Serve to handle requests on incoming mutual TLS connections.
|
||||||
|
func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string) (err error) {
|
||||||
|
addr := srv.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":https"
|
||||||
|
}
|
||||||
|
|
||||||
|
if srv.TLSConfig == nil {
|
||||||
|
srv.TLSConfig = &tls.Config{}
|
||||||
|
}
|
||||||
|
if srv.TLSConfig.NextProtos == nil {
|
||||||
|
srv.TLSConfig.NextProtos = []string{"http/1.1"}
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.TLSConfig.Certificates = make([]tls.Certificate, 1)
|
||||||
|
srv.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srv.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
data, err := ioutil.ReadFile(trustFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pool.AppendCertsFromPEM(data)
|
||||||
|
srv.TLSConfig.ClientCAs = pool
|
||||||
|
log.Println("Mutual HTTPS")
|
||||||
|
go srv.handleSignals()
|
||||||
|
|
||||||
|
ln, err := srv.getListener(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srv.ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig)
|
||||||
|
|
||||||
|
if srv.isChild {
|
||||||
|
process, err := os.FindProcess(os.Getppid())
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = process.Signal(syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(os.Getpid(), srv.Addr)
|
||||||
|
return srv.Serve()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getListener either opens a new socket to listen on, or takes the acceptor socket
|
||||||
|
// it got passed when restarted.
|
||||||
|
func (srv *Server) getListener(laddr string) (l net.Listener, err error) {
|
||||||
|
if srv.isChild {
|
||||||
|
var ptrOffset uint
|
||||||
|
if len(socketPtrOffsetMap) > 0 {
|
||||||
|
ptrOffset = socketPtrOffsetMap[laddr]
|
||||||
|
log.Println("laddr", laddr, "ptr offset", socketPtrOffsetMap[laddr])
|
||||||
|
}
|
||||||
|
|
||||||
|
f := os.NewFile(uintptr(3+ptrOffset), "")
|
||||||
|
l, err = net.FileListener(f)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("net.FileListener error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
l, err = net.Listen(srv.Network, laddr)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("net.Listen error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type tcpKeepAliveListener struct {
|
||||||
|
*net.TCPListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||||
|
tc, err := ln.AcceptTCP()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tc.SetKeepAlive(true)
|
||||||
|
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||||
|
return tc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSignals listens for os Signals and calls any hooked in function that the
|
||||||
|
// user had registered with the signal.
|
||||||
|
func (srv *Server) handleSignals() {
|
||||||
|
var sig os.Signal
|
||||||
|
|
||||||
|
signal.Notify(
|
||||||
|
srv.sigChan,
|
||||||
|
hookableSignals...,
|
||||||
|
)
|
||||||
|
|
||||||
|
pid := syscall.Getpid()
|
||||||
|
for {
|
||||||
|
sig = <-srv.sigChan
|
||||||
|
srv.signalHooks(PreSignal, sig)
|
||||||
|
switch sig {
|
||||||
|
case syscall.SIGHUP:
|
||||||
|
log.Println(pid, "Received SIGHUP. forking.")
|
||||||
|
err := srv.fork()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Fork err:", err)
|
||||||
|
}
|
||||||
|
case syscall.SIGINT:
|
||||||
|
log.Println(pid, "Received SIGINT.")
|
||||||
|
srv.shutdown()
|
||||||
|
case syscall.SIGTERM:
|
||||||
|
log.Println(pid, "Received SIGTERM.")
|
||||||
|
srv.shutdown()
|
||||||
|
default:
|
||||||
|
log.Printf("Received %v: nothing i care about...\n", sig)
|
||||||
|
}
|
||||||
|
srv.signalHooks(PostSignal, sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) signalHooks(ppFlag int, sig os.Signal) {
|
||||||
|
if _, notSet := srv.SignalHooks[ppFlag][sig]; !notSet {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, f := range srv.SignalHooks[ppFlag][sig] {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutdown closes the listener so that no new connections are accepted. it also
|
||||||
|
// starts a goroutine that will serverTimeout (stop all running requests) the server
|
||||||
|
// after DefaultTimeout.
|
||||||
|
func (srv *Server) shutdown() {
|
||||||
|
if srv.state != StateRunning {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.state = StateShuttingDown
|
||||||
|
log.Println(syscall.Getpid(), "Waiting for connections to finish...")
|
||||||
|
ctx := context.Background()
|
||||||
|
if DefaultTimeout >= 0 {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithTimeout(context.Background(), DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
srv.terminalChan <- srv.Server.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) fork() (err error) {
|
||||||
|
regLock.Lock()
|
||||||
|
defer regLock.Unlock()
|
||||||
|
if runningServersForked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runningServersForked = true
|
||||||
|
|
||||||
|
var files = make([]*os.File, len(runningServers))
|
||||||
|
var orderArgs = make([]string, len(runningServers))
|
||||||
|
for _, srvPtr := range runningServers {
|
||||||
|
f, _ := srvPtr.ln.(*net.TCPListener).File()
|
||||||
|
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = f
|
||||||
|
orderArgs[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.Server.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(files)
|
||||||
|
path := os.Args[0]
|
||||||
|
var args []string
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
for _, arg := range os.Args[1:] {
|
||||||
|
if arg == "-graceful" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
args = append(args, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args = append(args, "-graceful")
|
||||||
|
if len(runningServers) > 1 {
|
||||||
|
args = append(args, fmt.Sprintf(`-socketorder=%s`, strings.Join(orderArgs, ",")))
|
||||||
|
log.Println(args)
|
||||||
|
}
|
||||||
|
cmd := exec.Command(path, args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.ExtraFiles = files
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Restart: Failed to launch, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterSignalHook registers a function to be run PreSignal or PostSignal for a given signal.
|
||||||
|
func (srv *Server) RegisterSignalHook(ppFlag int, sig os.Signal, f func()) (err error) {
|
||||||
|
if ppFlag != PreSignal && ppFlag != PostSignal {
|
||||||
|
err = fmt.Errorf("Invalid ppFlag argument. Must be either grace.PreSignal or grace.PostSignal")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, s := range hookableSignals {
|
||||||
|
if s == sig {
|
||||||
|
srv.SignalHooks[ppFlag][sig] = append(srv.SignalHooks[ppFlag][sig], f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("Signal '%v' is not supported", sig)
|
||||||
|
return
|
||||||
|
}
|
104
pkg/hooks.go
Normal file
104
pkg/hooks.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/astaxie/beego/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
// register MIME type with content type
|
||||||
|
func registerMime() error {
|
||||||
|
for k, v := range mimemaps {
|
||||||
|
mime.AddExtensionType(k, v)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// register default error http handlers, 404,401,403,500 and 503.
|
||||||
|
func registerDefaultErrorHandler() error {
|
||||||
|
m := map[string]func(http.ResponseWriter, *http.Request){
|
||||||
|
"401": unauthorized,
|
||||||
|
"402": paymentRequired,
|
||||||
|
"403": forbidden,
|
||||||
|
"404": notFound,
|
||||||
|
"405": methodNotAllowed,
|
||||||
|
"500": internalServerError,
|
||||||
|
"501": notImplemented,
|
||||||
|
"502": badGateway,
|
||||||
|
"503": serviceUnavailable,
|
||||||
|
"504": gatewayTimeout,
|
||||||
|
"417": invalidxsrf,
|
||||||
|
"422": missingxsrf,
|
||||||
|
"413": payloadTooLarge,
|
||||||
|
}
|
||||||
|
for e, h := range m {
|
||||||
|
if _, ok := ErrorMaps[e]; !ok {
|
||||||
|
ErrorHandler(e, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerSession() error {
|
||||||
|
if BConfig.WebConfig.Session.SessionOn {
|
||||||
|
var err error
|
||||||
|
sessionConfig := AppConfig.String("sessionConfig")
|
||||||
|
conf := new(session.ManagerConfig)
|
||||||
|
if sessionConfig == "" {
|
||||||
|
conf.CookieName = BConfig.WebConfig.Session.SessionName
|
||||||
|
conf.EnableSetCookie = BConfig.WebConfig.Session.SessionAutoSetCookie
|
||||||
|
conf.Gclifetime = BConfig.WebConfig.Session.SessionGCMaxLifetime
|
||||||
|
conf.Secure = BConfig.Listen.EnableHTTPS
|
||||||
|
conf.CookieLifeTime = BConfig.WebConfig.Session.SessionCookieLifeTime
|
||||||
|
conf.ProviderConfig = filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig)
|
||||||
|
conf.DisableHTTPOnly = BConfig.WebConfig.Session.SessionDisableHTTPOnly
|
||||||
|
conf.Domain = BConfig.WebConfig.Session.SessionDomain
|
||||||
|
conf.EnableSidInHTTPHeader = BConfig.WebConfig.Session.SessionEnableSidInHTTPHeader
|
||||||
|
conf.SessionNameInHTTPHeader = BConfig.WebConfig.Session.SessionNameInHTTPHeader
|
||||||
|
conf.EnableSidInURLQuery = BConfig.WebConfig.Session.SessionEnableSidInURLQuery
|
||||||
|
} else {
|
||||||
|
if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if GlobalSessions, err = session.NewManager(BConfig.WebConfig.Session.SessionProvider, conf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go GlobalSessions.GC()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerTemplate() error {
|
||||||
|
defer lockViewPaths()
|
||||||
|
if err := AddViewPath(BConfig.WebConfig.ViewsPath); err != nil {
|
||||||
|
if BConfig.RunMode == DEV {
|
||||||
|
logs.Warn(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerAdmin() error {
|
||||||
|
if BConfig.Listen.EnableAdmin {
|
||||||
|
go beeAdminApp.Run()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerGzip() error {
|
||||||
|
if BConfig.EnableGzip {
|
||||||
|
context.InitGzip(
|
||||||
|
AppConfig.DefaultInt("gzipMinLength", -1),
|
||||||
|
AppConfig.DefaultInt("gzipCompressLevel", -1),
|
||||||
|
AppConfig.DefaultStrings("includedMethods", []string{"GET"}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
97
pkg/httplib/README.md
Normal file
97
pkg/httplib/README.md
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# httplib
|
||||||
|
httplib is an libs help you to curl remote url.
|
||||||
|
|
||||||
|
# How to use?
|
||||||
|
|
||||||
|
## GET
|
||||||
|
you can use Get to crawl data.
|
||||||
|
|
||||||
|
import "github.com/astaxie/beego/httplib"
|
||||||
|
|
||||||
|
str, err := httplib.Get("http://beego.me/").String()
|
||||||
|
if err != nil {
|
||||||
|
// error
|
||||||
|
}
|
||||||
|
fmt.Println(str)
|
||||||
|
|
||||||
|
## POST
|
||||||
|
POST data to remote url
|
||||||
|
|
||||||
|
req := httplib.Post("http://beego.me/")
|
||||||
|
req.Param("username","astaxie")
|
||||||
|
req.Param("password","123456")
|
||||||
|
str, err := req.String()
|
||||||
|
if err != nil {
|
||||||
|
// error
|
||||||
|
}
|
||||||
|
fmt.Println(str)
|
||||||
|
|
||||||
|
## Set timeout
|
||||||
|
|
||||||
|
The default timeout is `60` seconds, function prototype:
|
||||||
|
|
||||||
|
SetTimeout(connectTimeout, readWriteTimeout time.Duration)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
// GET
|
||||||
|
httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
|
||||||
|
|
||||||
|
// POST
|
||||||
|
httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
|
||||||
|
|
||||||
|
|
||||||
|
## Debug
|
||||||
|
|
||||||
|
If you want to debug the request info, set the debug on
|
||||||
|
|
||||||
|
httplib.Get("http://beego.me/").Debug(true)
|
||||||
|
|
||||||
|
## Set HTTP Basic Auth
|
||||||
|
|
||||||
|
str, err := Get("http://beego.me/").SetBasicAuth("user", "passwd").String()
|
||||||
|
if err != nil {
|
||||||
|
// error
|
||||||
|
}
|
||||||
|
fmt.Println(str)
|
||||||
|
|
||||||
|
## Set HTTPS
|
||||||
|
|
||||||
|
If request url is https, You can set the client support TSL:
|
||||||
|
|
||||||
|
httplib.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
||||||
|
|
||||||
|
More info about the `tls.Config` please visit http://golang.org/pkg/crypto/tls/#Config
|
||||||
|
|
||||||
|
## Set HTTP Version
|
||||||
|
|
||||||
|
some servers need to specify the protocol version of HTTP
|
||||||
|
|
||||||
|
httplib.Get("http://beego.me/").SetProtocolVersion("HTTP/1.1")
|
||||||
|
|
||||||
|
## Set Cookie
|
||||||
|
|
||||||
|
some http request need setcookie. So set it like this:
|
||||||
|
|
||||||
|
cookie := &http.Cookie{}
|
||||||
|
cookie.Name = "username"
|
||||||
|
cookie.Value = "astaxie"
|
||||||
|
httplib.Get("http://beego.me/").SetCookie(cookie)
|
||||||
|
|
||||||
|
## Upload file
|
||||||
|
|
||||||
|
httplib support mutil file upload, use `req.PostFile()`
|
||||||
|
|
||||||
|
req := httplib.Post("http://beego.me/")
|
||||||
|
req.Param("username","astaxie")
|
||||||
|
req.PostFile("uploadfile1", "httplib.pdf")
|
||||||
|
str, err := req.String()
|
||||||
|
if err != nil {
|
||||||
|
// error
|
||||||
|
}
|
||||||
|
fmt.Println(str)
|
||||||
|
|
||||||
|
|
||||||
|
See godoc for further documentation and examples.
|
||||||
|
|
||||||
|
* [godoc.org/github.com/astaxie/beego/httplib](https://godoc.org/github.com/astaxie/beego/httplib)
|
654
pkg/httplib/httplib.go
Normal file
654
pkg/httplib/httplib.go
Normal file
@ -0,0 +1,654 @@
|
|||||||
|
// 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 httplib is used as http.Client
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// import "github.com/astaxie/beego/httplib"
|
||||||
|
//
|
||||||
|
// b := httplib.Post("http://beego.me/")
|
||||||
|
// b.Param("username","astaxie")
|
||||||
|
// b.Param("password","123456")
|
||||||
|
// b.PostFile("uploadfile1", "httplib.pdf")
|
||||||
|
// b.PostFile("uploadfile2", "httplib.txt")
|
||||||
|
// str, err := b.String()
|
||||||
|
// if err != nil {
|
||||||
|
// t.Fatal(err)
|
||||||
|
// }
|
||||||
|
// fmt.Println(str)
|
||||||
|
//
|
||||||
|
// more docs http://beego.me/docs/module/httplib.md
|
||||||
|
package httplib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"mime/multipart"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultSetting = BeegoHTTPSettings{
|
||||||
|
UserAgent: "beegoServer",
|
||||||
|
ConnectTimeout: 60 * time.Second,
|
||||||
|
ReadWriteTimeout: 60 * time.Second,
|
||||||
|
Gzip: true,
|
||||||
|
DumpBody: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultCookieJar http.CookieJar
|
||||||
|
var settingMutex sync.Mutex
|
||||||
|
|
||||||
|
// createDefaultCookie creates a global cookiejar to store cookies.
|
||||||
|
func createDefaultCookie() {
|
||||||
|
settingMutex.Lock()
|
||||||
|
defer settingMutex.Unlock()
|
||||||
|
defaultCookieJar, _ = cookiejar.New(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultSetting Overwrite default settings
|
||||||
|
func SetDefaultSetting(setting BeegoHTTPSettings) {
|
||||||
|
settingMutex.Lock()
|
||||||
|
defer settingMutex.Unlock()
|
||||||
|
defaultSetting = setting
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBeegoRequest return *BeegoHttpRequest with specific method
|
||||||
|
func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest {
|
||||||
|
var resp http.Response
|
||||||
|
u, err := url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Httplib:", err)
|
||||||
|
}
|
||||||
|
req := http.Request{
|
||||||
|
URL: u,
|
||||||
|
Method: method,
|
||||||
|
Header: make(http.Header),
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
}
|
||||||
|
return &BeegoHTTPRequest{
|
||||||
|
url: rawurl,
|
||||||
|
req: &req,
|
||||||
|
params: map[string][]string{},
|
||||||
|
files: map[string]string{},
|
||||||
|
setting: defaultSetting,
|
||||||
|
resp: &resp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns *BeegoHttpRequest with GET method.
|
||||||
|
func Get(url string) *BeegoHTTPRequest {
|
||||||
|
return NewBeegoRequest(url, "GET")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post returns *BeegoHttpRequest with POST method.
|
||||||
|
func Post(url string) *BeegoHTTPRequest {
|
||||||
|
return NewBeegoRequest(url, "POST")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put returns *BeegoHttpRequest with PUT method.
|
||||||
|
func Put(url string) *BeegoHTTPRequest {
|
||||||
|
return NewBeegoRequest(url, "PUT")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete returns *BeegoHttpRequest DELETE method.
|
||||||
|
func Delete(url string) *BeegoHTTPRequest {
|
||||||
|
return NewBeegoRequest(url, "DELETE")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head returns *BeegoHttpRequest with HEAD method.
|
||||||
|
func Head(url string) *BeegoHTTPRequest {
|
||||||
|
return NewBeegoRequest(url, "HEAD")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeegoHTTPSettings is the http.Client setting
|
||||||
|
type BeegoHTTPSettings struct {
|
||||||
|
ShowDebug bool
|
||||||
|
UserAgent string
|
||||||
|
ConnectTimeout time.Duration
|
||||||
|
ReadWriteTimeout time.Duration
|
||||||
|
TLSClientConfig *tls.Config
|
||||||
|
Proxy func(*http.Request) (*url.URL, error)
|
||||||
|
Transport http.RoundTripper
|
||||||
|
CheckRedirect func(req *http.Request, via []*http.Request) error
|
||||||
|
EnableCookie bool
|
||||||
|
Gzip bool
|
||||||
|
DumpBody bool
|
||||||
|
Retries int // if set to -1 means will retry forever
|
||||||
|
RetryDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeegoHTTPRequest provides more useful methods for requesting one url than http.Request.
|
||||||
|
type BeegoHTTPRequest struct {
|
||||||
|
url string
|
||||||
|
req *http.Request
|
||||||
|
params map[string][]string
|
||||||
|
files map[string]string
|
||||||
|
setting BeegoHTTPSettings
|
||||||
|
resp *http.Response
|
||||||
|
body []byte
|
||||||
|
dump []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequest return the request object
|
||||||
|
func (b *BeegoHTTPRequest) GetRequest() *http.Request {
|
||||||
|
return b.req
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting Change request settings
|
||||||
|
func (b *BeegoHTTPRequest) Setting(setting BeegoHTTPSettings) *BeegoHTTPRequest {
|
||||||
|
b.setting = setting
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password.
|
||||||
|
func (b *BeegoHTTPRequest) SetBasicAuth(username, password string) *BeegoHTTPRequest {
|
||||||
|
b.req.SetBasicAuth(username, password)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEnableCookie sets enable/disable cookiejar
|
||||||
|
func (b *BeegoHTTPRequest) SetEnableCookie(enable bool) *BeegoHTTPRequest {
|
||||||
|
b.setting.EnableCookie = enable
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserAgent sets User-Agent header field
|
||||||
|
func (b *BeegoHTTPRequest) SetUserAgent(useragent string) *BeegoHTTPRequest {
|
||||||
|
b.setting.UserAgent = useragent
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug sets show debug or not when executing request.
|
||||||
|
func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest {
|
||||||
|
b.setting.ShowDebug = isdebug
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retries sets Retries times.
|
||||||
|
// default is 0 means no retried.
|
||||||
|
// -1 means retried forever.
|
||||||
|
// others means retried times.
|
||||||
|
func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest {
|
||||||
|
b.setting.Retries = times
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest {
|
||||||
|
b.setting.RetryDelay = delay
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpBody setting whether need to Dump the Body.
|
||||||
|
func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest {
|
||||||
|
b.setting.DumpBody = isdump
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpRequest return the DumpRequest
|
||||||
|
func (b *BeegoHTTPRequest) DumpRequest() []byte {
|
||||||
|
return b.dump
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
|
||||||
|
func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHTTPRequest {
|
||||||
|
b.setting.ConnectTimeout = connectTimeout
|
||||||
|
b.setting.ReadWriteTimeout = readWriteTimeout
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTLSClientConfig sets tls connection configurations if visiting https url.
|
||||||
|
func (b *BeegoHTTPRequest) SetTLSClientConfig(config *tls.Config) *BeegoHTTPRequest {
|
||||||
|
b.setting.TLSClientConfig = config
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header add header item string in request.
|
||||||
|
func (b *BeegoHTTPRequest) Header(key, value string) *BeegoHTTPRequest {
|
||||||
|
b.req.Header.Set(key, value)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHost set the request host
|
||||||
|
func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest {
|
||||||
|
b.req.Host = host
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProtocolVersion Set the protocol version for incoming requests.
|
||||||
|
// Client requests always use HTTP/1.1.
|
||||||
|
func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest {
|
||||||
|
if len(vers) == 0 {
|
||||||
|
vers = "HTTP/1.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
major, minor, ok := http.ParseHTTPVersion(vers)
|
||||||
|
if ok {
|
||||||
|
b.req.Proto = vers
|
||||||
|
b.req.ProtoMajor = major
|
||||||
|
b.req.ProtoMinor = minor
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCookie add cookie into request.
|
||||||
|
func (b *BeegoHTTPRequest) SetCookie(cookie *http.Cookie) *BeegoHTTPRequest {
|
||||||
|
b.req.Header.Add("Cookie", cookie.String())
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTransport set the setting transport
|
||||||
|
func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPRequest {
|
||||||
|
b.setting.Transport = transport
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProxy set the http proxy
|
||||||
|
// example:
|
||||||
|
//
|
||||||
|
// func(req *http.Request) (*url.URL, error) {
|
||||||
|
// u, _ := url.ParseRequestURI("http://127.0.0.1:8118")
|
||||||
|
// return u, nil
|
||||||
|
// }
|
||||||
|
func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHTTPRequest {
|
||||||
|
b.setting.Proxy = proxy
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCheckRedirect specifies the policy for handling redirects.
|
||||||
|
//
|
||||||
|
// If CheckRedirect is nil, the Client uses its default policy,
|
||||||
|
// which is to stop after 10 consecutive requests.
|
||||||
|
func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) *BeegoHTTPRequest {
|
||||||
|
b.setting.CheckRedirect = redirect
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Param adds query param in to request.
|
||||||
|
// params build query string as ?key1=value1&key2=value2...
|
||||||
|
func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest {
|
||||||
|
if param, ok := b.params[key]; ok {
|
||||||
|
b.params[key] = append(param, value)
|
||||||
|
} else {
|
||||||
|
b.params[key] = []string{value}
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostFile add a post file to the request
|
||||||
|
func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest {
|
||||||
|
b.files[formname] = filename
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body adds request raw body.
|
||||||
|
// it supports string and []byte.
|
||||||
|
func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
|
||||||
|
switch t := data.(type) {
|
||||||
|
case string:
|
||||||
|
bf := bytes.NewBufferString(t)
|
||||||
|
b.req.Body = ioutil.NopCloser(bf)
|
||||||
|
b.req.ContentLength = int64(len(t))
|
||||||
|
case []byte:
|
||||||
|
bf := bytes.NewBuffer(t)
|
||||||
|
b.req.Body = ioutil.NopCloser(bf)
|
||||||
|
b.req.ContentLength = int64(len(t))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// XMLBody adds request raw body encoding by XML.
|
||||||
|
func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
||||||
|
if b.req.Body == nil && obj != nil {
|
||||||
|
byts, err := xml.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
|
||||||
|
b.req.ContentLength = int64(len(byts))
|
||||||
|
b.req.Header.Set("Content-Type", "application/xml")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLBody adds request raw body encoding by YAML.
|
||||||
|
func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
||||||
|
if b.req.Body == nil && obj != nil {
|
||||||
|
byts, err := yaml.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
|
||||||
|
b.req.ContentLength = int64(len(byts))
|
||||||
|
b.req.Header.Set("Content-Type", "application/x+yaml")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONBody adds request raw body encoding by JSON.
|
||||||
|
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
||||||
|
if b.req.Body == nil && obj != nil {
|
||||||
|
byts, err := json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
|
||||||
|
b.req.ContentLength = int64(len(byts))
|
||||||
|
b.req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BeegoHTTPRequest) buildURL(paramBody string) {
|
||||||
|
// build GET url with query string
|
||||||
|
if b.req.Method == "GET" && len(paramBody) > 0 {
|
||||||
|
if strings.Contains(b.url, "?") {
|
||||||
|
b.url += "&" + paramBody
|
||||||
|
} else {
|
||||||
|
b.url = b.url + "?" + paramBody
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// build POST/PUT/PATCH url and body
|
||||||
|
if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH" || b.req.Method == "DELETE") && b.req.Body == nil {
|
||||||
|
// with files
|
||||||
|
if len(b.files) > 0 {
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
bodyWriter := multipart.NewWriter(pw)
|
||||||
|
go func() {
|
||||||
|
for formname, filename := range b.files {
|
||||||
|
fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Httplib:", err)
|
||||||
|
}
|
||||||
|
fh, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Httplib:", err)
|
||||||
|
}
|
||||||
|
//iocopy
|
||||||
|
_, err = io.Copy(fileWriter, fh)
|
||||||
|
fh.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Httplib:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range b.params {
|
||||||
|
for _, vv := range v {
|
||||||
|
bodyWriter.WriteField(k, vv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bodyWriter.Close()
|
||||||
|
pw.Close()
|
||||||
|
}()
|
||||||
|
b.Header("Content-Type", bodyWriter.FormDataContentType())
|
||||||
|
b.req.Body = ioutil.NopCloser(pr)
|
||||||
|
b.Header("Transfer-Encoding", "chunked")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// with params
|
||||||
|
if len(paramBody) > 0 {
|
||||||
|
b.Header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
b.Body(paramBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) {
|
||||||
|
if b.resp.StatusCode != 0 {
|
||||||
|
return b.resp, nil
|
||||||
|
}
|
||||||
|
resp, err := b.DoRequest()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.resp = resp
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoRequest will do the client.Do
|
||||||
|
func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
|
||||||
|
var paramBody string
|
||||||
|
if len(b.params) > 0 {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for k, v := range b.params {
|
||||||
|
for _, vv := range v {
|
||||||
|
buf.WriteString(url.QueryEscape(k))
|
||||||
|
buf.WriteByte('=')
|
||||||
|
buf.WriteString(url.QueryEscape(vv))
|
||||||
|
buf.WriteByte('&')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paramBody = buf.String()
|
||||||
|
paramBody = paramBody[0 : len(paramBody)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
b.buildURL(paramBody)
|
||||||
|
urlParsed, err := url.Parse(b.url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.req.URL = urlParsed
|
||||||
|
|
||||||
|
trans := b.setting.Transport
|
||||||
|
|
||||||
|
if trans == nil {
|
||||||
|
// create default transport
|
||||||
|
trans = &http.Transport{
|
||||||
|
TLSClientConfig: b.setting.TLSClientConfig,
|
||||||
|
Proxy: b.setting.Proxy,
|
||||||
|
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
|
||||||
|
MaxIdleConnsPerHost: 100,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if b.transport is *http.Transport then set the settings.
|
||||||
|
if t, ok := trans.(*http.Transport); ok {
|
||||||
|
if t.TLSClientConfig == nil {
|
||||||
|
t.TLSClientConfig = b.setting.TLSClientConfig
|
||||||
|
}
|
||||||
|
if t.Proxy == nil {
|
||||||
|
t.Proxy = b.setting.Proxy
|
||||||
|
}
|
||||||
|
if t.Dial == nil {
|
||||||
|
t.Dial = TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var jar http.CookieJar
|
||||||
|
if b.setting.EnableCookie {
|
||||||
|
if defaultCookieJar == nil {
|
||||||
|
createDefaultCookie()
|
||||||
|
}
|
||||||
|
jar = defaultCookieJar
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: trans,
|
||||||
|
Jar: jar,
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.setting.UserAgent != "" && b.req.Header.Get("User-Agent") == "" {
|
||||||
|
b.req.Header.Set("User-Agent", b.setting.UserAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.setting.CheckRedirect != nil {
|
||||||
|
client.CheckRedirect = b.setting.CheckRedirect
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.setting.ShowDebug {
|
||||||
|
dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
b.dump = dump
|
||||||
|
}
|
||||||
|
// retries default value is 0, it will run once.
|
||||||
|
// retries equal to -1, it will run forever until success
|
||||||
|
// retries is setted, it will retries fixed times.
|
||||||
|
// Sleeps for a 400ms inbetween calls to reduce spam
|
||||||
|
for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ {
|
||||||
|
resp, err = client.Do(b.req)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(b.setting.RetryDelay)
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the body string in response.
|
||||||
|
// it calls Response inner.
|
||||||
|
func (b *BeegoHTTPRequest) String() (string, error) {
|
||||||
|
data, err := b.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the body []byte in response.
|
||||||
|
// it calls Response inner.
|
||||||
|
func (b *BeegoHTTPRequest) Bytes() ([]byte, error) {
|
||||||
|
if b.body != nil {
|
||||||
|
return b.body, nil
|
||||||
|
}
|
||||||
|
resp, err := b.getResponse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Body == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if b.setting.Gzip && resp.Header.Get("Content-Encoding") == "gzip" {
|
||||||
|
reader, err := gzip.NewReader(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.body, err = ioutil.ReadAll(reader)
|
||||||
|
return b.body, err
|
||||||
|
}
|
||||||
|
b.body, err = ioutil.ReadAll(resp.Body)
|
||||||
|
return b.body, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFile saves the body data in response to one file.
|
||||||
|
// it calls Response inner.
|
||||||
|
func (b *BeegoHTTPRequest) ToFile(filename string) error {
|
||||||
|
resp, err := b.getResponse()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.Body == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
err = pathExistAndMkdir(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = io.Copy(f, resp.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check that the file directory exists, there is no automatically created
|
||||||
|
func pathExistAndMkdir(filename string) (err error) {
|
||||||
|
filename = path.Dir(filename)
|
||||||
|
_, err = os.Stat(filename)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(filename, os.ModePerm)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToJSON returns the map that marshals from the body bytes as json in response .
|
||||||
|
// it calls Response inner.
|
||||||
|
func (b *BeegoHTTPRequest) ToJSON(v interface{}) error {
|
||||||
|
data, err := b.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToXML returns the map that marshals from the body bytes as xml in response .
|
||||||
|
// it calls Response inner.
|
||||||
|
func (b *BeegoHTTPRequest) ToXML(v interface{}) error {
|
||||||
|
data, err := b.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return xml.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToYAML returns the map that marshals from the body bytes as yaml in response .
|
||||||
|
// it calls Response inner.
|
||||||
|
func (b *BeegoHTTPRequest) ToYAML(v interface{}) error {
|
||||||
|
data, err := b.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return yaml.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response executes request client gets response mannually.
|
||||||
|
func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
|
||||||
|
return b.getResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
|
||||||
|
func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
|
||||||
|
return func(netw, addr string) (net.Conn, error) {
|
||||||
|
conn, err := net.DialTimeout(netw, addr, cTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = conn.SetDeadline(time.Now().Add(rwTimeout))
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
}
|
286
pkg/httplib/httplib_test.go
Normal file
286
pkg/httplib/httplib_test.go
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
// 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 httplib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResponse(t *testing.T) {
|
||||||
|
req := Get("http://httpbin.org/get")
|
||||||
|
resp, err := req.Response()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoRequest(t *testing.T) {
|
||||||
|
req := Get("https://goolnk.com/33BD2j")
|
||||||
|
retryAmount := 1
|
||||||
|
req.Retries(1)
|
||||||
|
req.RetryDelay(1400 * time.Millisecond)
|
||||||
|
retryDelay := 1400 * time.Millisecond
|
||||||
|
|
||||||
|
req.setting.CheckRedirect = func(redirectReq *http.Request, redirectVia []*http.Request) error {
|
||||||
|
return errors.New("Redirect triggered")
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
_, err := req.Response()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Response should have yielded an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
endTime := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
elapsedTime := endTime - startTime
|
||||||
|
delayedTime := int64(retryAmount) * retryDelay.Milliseconds()
|
||||||
|
|
||||||
|
if elapsedTime < delayedTime {
|
||||||
|
t.Errorf("Not enough retries. Took %dms. Delay was meant to take %dms", elapsedTime, delayedTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
req := Get("http://httpbin.org/get")
|
||||||
|
b, err := req.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(b)
|
||||||
|
|
||||||
|
s, err := req.String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(s)
|
||||||
|
|
||||||
|
if string(b) != s {
|
||||||
|
t.Fatal("request data not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimplePost(t *testing.T) {
|
||||||
|
v := "smallfish"
|
||||||
|
req := Post("http://httpbin.org/post")
|
||||||
|
req.Param("username", v)
|
||||||
|
|
||||||
|
str, err := req.String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(str)
|
||||||
|
|
||||||
|
n := strings.Index(str, v)
|
||||||
|
if n == -1 {
|
||||||
|
t.Fatal(v + " not found in post")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//func TestPostFile(t *testing.T) {
|
||||||
|
// v := "smallfish"
|
||||||
|
// req := Post("http://httpbin.org/post")
|
||||||
|
// req.Debug(true)
|
||||||
|
// req.Param("username", v)
|
||||||
|
// req.PostFile("uploadfile", "httplib_test.go")
|
||||||
|
|
||||||
|
// str, err := req.String()
|
||||||
|
// if err != nil {
|
||||||
|
// t.Fatal(err)
|
||||||
|
// }
|
||||||
|
// t.Log(str)
|
||||||
|
|
||||||
|
// n := strings.Index(str, v)
|
||||||
|
// if n == -1 {
|
||||||
|
// t.Fatal(v + " not found in post")
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
func TestSimplePut(t *testing.T) {
|
||||||
|
str, err := Put("http://httpbin.org/put").String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleDelete(t *testing.T) {
|
||||||
|
str, err := Delete("http://httpbin.org/delete").String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleDeleteParam(t *testing.T) {
|
||||||
|
str, err := Delete("http://httpbin.org/delete").Param("key", "val").String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithCookie(t *testing.T) {
|
||||||
|
v := "smallfish"
|
||||||
|
str, err := Get("http://httpbin.org/cookies/set?k1=" + v).SetEnableCookie(true).String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(str)
|
||||||
|
|
||||||
|
str, err = Get("http://httpbin.org/cookies").SetEnableCookie(true).String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(str)
|
||||||
|
|
||||||
|
n := strings.Index(str, v)
|
||||||
|
if n == -1 {
|
||||||
|
t.Fatal(v + " not found in cookie")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithBasicAuth(t *testing.T) {
|
||||||
|
str, err := Get("http://httpbin.org/basic-auth/user/passwd").SetBasicAuth("user", "passwd").String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(str)
|
||||||
|
n := strings.Index(str, "authenticated")
|
||||||
|
if n == -1 {
|
||||||
|
t.Fatal("authenticated not found in response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithUserAgent(t *testing.T) {
|
||||||
|
v := "beego"
|
||||||
|
str, err := Get("http://httpbin.org/headers").SetUserAgent(v).String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(str)
|
||||||
|
|
||||||
|
n := strings.Index(str, v)
|
||||||
|
if n == -1 {
|
||||||
|
t.Fatal(v + " not found in user-agent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithSetting(t *testing.T) {
|
||||||
|
v := "beego"
|
||||||
|
var setting BeegoHTTPSettings
|
||||||
|
setting.EnableCookie = true
|
||||||
|
setting.UserAgent = v
|
||||||
|
setting.Transport = &http.Transport{
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
DualStack: true,
|
||||||
|
}).DialContext,
|
||||||
|
MaxIdleConns: 50,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
}
|
||||||
|
setting.ReadWriteTimeout = 5 * time.Second
|
||||||
|
SetDefaultSetting(setting)
|
||||||
|
|
||||||
|
str, err := Get("http://httpbin.org/get").String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(str)
|
||||||
|
|
||||||
|
n := strings.Index(str, v)
|
||||||
|
if n == -1 {
|
||||||
|
t.Fatal(v + " not found in user-agent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToJson(t *testing.T) {
|
||||||
|
req := Get("http://httpbin.org/ip")
|
||||||
|
resp, err := req.Response()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(resp)
|
||||||
|
|
||||||
|
// httpbin will return http remote addr
|
||||||
|
type IP struct {
|
||||||
|
Origin string `json:"origin"`
|
||||||
|
}
|
||||||
|
var ip IP
|
||||||
|
err = req.ToJSON(&ip)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(ip.Origin)
|
||||||
|
ips := strings.Split(ip.Origin, ",")
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Fatal("response is not valid ip")
|
||||||
|
}
|
||||||
|
for i := range ips {
|
||||||
|
if net.ParseIP(strings.TrimSpace(ips[i])).To4() == nil {
|
||||||
|
t.Fatal("response is not valid ip")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToFile(t *testing.T) {
|
||||||
|
f := "beego_testfile"
|
||||||
|
req := Get("http://httpbin.org/ip")
|
||||||
|
err := req.ToFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f)
|
||||||
|
b, err := ioutil.ReadFile(f)
|
||||||
|
if n := strings.Index(string(b), "origin"); n == -1 {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToFileDir(t *testing.T) {
|
||||||
|
f := "./files/beego_testfile"
|
||||||
|
req := Get("http://httpbin.org/ip")
|
||||||
|
err := req.ToFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll("./files")
|
||||||
|
b, err := ioutil.ReadFile(f)
|
||||||
|
if n := strings.Index(string(b), "origin"); n == -1 {
|
||||||
|
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)
|
||||||
|
}
|
127
pkg/log.go
Normal file
127
pkg/log.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// 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 (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log levels to control the logging output.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
const (
|
||||||
|
LevelEmergency = iota
|
||||||
|
LevelAlert
|
||||||
|
LevelCritical
|
||||||
|
LevelError
|
||||||
|
LevelWarning
|
||||||
|
LevelNotice
|
||||||
|
LevelInformational
|
||||||
|
LevelDebug
|
||||||
|
)
|
||||||
|
|
||||||
|
// BeeLogger references the used application logger.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
var BeeLogger = logs.GetBeeLogger()
|
||||||
|
|
||||||
|
// SetLevel sets the global log level used by the simple logger.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func SetLevel(l int) {
|
||||||
|
logs.SetLevel(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogFuncCall set the CallDepth, default is 3
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func SetLogFuncCall(b bool) {
|
||||||
|
logs.SetLogFuncCall(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger sets a new logger.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func SetLogger(adaptername string, config string) error {
|
||||||
|
return logs.SetLogger(adaptername, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emergency logs a message at emergency level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func Emergency(v ...interface{}) {
|
||||||
|
logs.Emergency(generateFmtStr(len(v)), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert logs a message at alert level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func Alert(v ...interface{}) {
|
||||||
|
logs.Alert(generateFmtStr(len(v)), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical logs a message at critical level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func Critical(v ...interface{}) {
|
||||||
|
logs.Critical(generateFmtStr(len(v)), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at error level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func Error(v ...interface{}) {
|
||||||
|
logs.Error(generateFmtStr(len(v)), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs a message at warning level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func Warning(v ...interface{}) {
|
||||||
|
logs.Warning(generateFmtStr(len(v)), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn compatibility alias for Warning()
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func Warn(v ...interface{}) {
|
||||||
|
logs.Warn(generateFmtStr(len(v)), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notice logs a message at notice level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func Notice(v ...interface{}) {
|
||||||
|
logs.Notice(generateFmtStr(len(v)), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Informational logs a message at info level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func Informational(v ...interface{}) {
|
||||||
|
logs.Informational(generateFmtStr(len(v)), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info compatibility alias for Warning()
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func Info(v ...interface{}) {
|
||||||
|
logs.Info(generateFmtStr(len(v)), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at debug level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func Debug(v ...interface{}) {
|
||||||
|
logs.Debug(generateFmtStr(len(v)), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace logs a message at trace level.
|
||||||
|
// compatibility alias for Warning()
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
|
func Trace(v ...interface{}) {
|
||||||
|
logs.Trace(generateFmtStr(len(v)), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateFmtStr(n int) string {
|
||||||
|
return strings.Repeat("%v ", n)
|
||||||
|
}
|
72
pkg/logs/README.md
Normal file
72
pkg/logs/README.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
## logs
|
||||||
|
logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` .
|
||||||
|
|
||||||
|
|
||||||
|
## How to install?
|
||||||
|
|
||||||
|
go get github.com/astaxie/beego/logs
|
||||||
|
|
||||||
|
|
||||||
|
## What adapters are supported?
|
||||||
|
|
||||||
|
As of now this logs support console, file,smtp and conn.
|
||||||
|
|
||||||
|
|
||||||
|
## How to use it?
|
||||||
|
|
||||||
|
First you must import it
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then init a Log (example with console adapter)
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := logs.NewLogger(10000)
|
||||||
|
log.SetLogger("console", "")
|
||||||
|
```
|
||||||
|
|
||||||
|
> the first params stand for how many channel
|
||||||
|
|
||||||
|
Use it like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log.Trace("trace")
|
||||||
|
log.Info("info")
|
||||||
|
log.Warn("warning")
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Critical("critical")
|
||||||
|
```
|
||||||
|
|
||||||
|
## File adapter
|
||||||
|
|
||||||
|
Configure file adapter like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conn adapter
|
||||||
|
|
||||||
|
Configure like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := NewLogger(1000)
|
||||||
|
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||||
|
log.Info("info")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Smtp adapter
|
||||||
|
|
||||||
|
Configure like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||||
|
log.Critical("sendmail critical")
|
||||||
|
time.Sleep(time.Second * 30)
|
||||||
|
```
|
83
pkg/logs/accesslog.go
Normal file
83
pkg/logs/accesslog.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s"
|
||||||
|
apacheFormat = "APACHE_FORMAT"
|
||||||
|
jsonFormat = "JSON_FORMAT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessLogRecord struct for holding access log data.
|
||||||
|
type AccessLogRecord struct {
|
||||||
|
RemoteAddr string `json:"remote_addr"`
|
||||||
|
RequestTime time.Time `json:"request_time"`
|
||||||
|
RequestMethod string `json:"request_method"`
|
||||||
|
Request string `json:"request"`
|
||||||
|
ServerProtocol string `json:"server_protocol"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
BodyBytesSent int64 `json:"body_bytes_sent"`
|
||||||
|
ElapsedTime time.Duration `json:"elapsed_time"`
|
||||||
|
HTTPReferrer string `json:"http_referrer"`
|
||||||
|
HTTPUserAgent string `json:"http_user_agent"`
|
||||||
|
RemoteUser string `json:"remote_user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccessLogRecord) json() ([]byte, error) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
disableEscapeHTML(encoder)
|
||||||
|
|
||||||
|
err := encoder.Encode(r)
|
||||||
|
return buffer.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableEscapeHTML(i interface{}) {
|
||||||
|
if e, ok := i.(interface {
|
||||||
|
SetEscapeHTML(bool)
|
||||||
|
}); ok {
|
||||||
|
e.SetEscapeHTML(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessLog - Format and print access log.
|
||||||
|
func AccessLog(r *AccessLogRecord, format string) {
|
||||||
|
var msg string
|
||||||
|
switch format {
|
||||||
|
case apacheFormat:
|
||||||
|
timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05")
|
||||||
|
msg = fmt.Sprintf(apacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent,
|
||||||
|
r.ElapsedTime.Seconds(), r.HTTPReferrer, r.HTTPUserAgent)
|
||||||
|
case jsonFormat:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
jsonData, err := r.json()
|
||||||
|
if err != nil {
|
||||||
|
msg = fmt.Sprintf(`{"Error": "%s"}`, err)
|
||||||
|
} else {
|
||||||
|
msg = string(jsonData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
beeLogger.writeMsg(levelLoggerImpl, strings.TrimSpace(msg))
|
||||||
|
}
|
186
pkg/logs/alils/alils.go
Normal file
186
pkg/logs/alils/alils.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CacheSize set the flush size
|
||||||
|
CacheSize int = 64
|
||||||
|
// Delimiter define the topic delimiter
|
||||||
|
Delimiter string = "##"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is the Config for Ali Log
|
||||||
|
type Config struct {
|
||||||
|
Project string `json:"project"`
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
KeyID string `json:"key_id"`
|
||||||
|
KeySecret string `json:"key_secret"`
|
||||||
|
LogStore string `json:"log_store"`
|
||||||
|
Topics []string `json:"topics"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
FlushWhen int `json:"flush_when"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// aliLSWriter implements LoggerInterface.
|
||||||
|
// it writes messages in keep-live tcp connection.
|
||||||
|
type aliLSWriter struct {
|
||||||
|
store *LogStore
|
||||||
|
group []*LogGroup
|
||||||
|
withMap bool
|
||||||
|
groupMap map[string]*LogGroup
|
||||||
|
lock *sync.Mutex
|
||||||
|
Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAliLS create a new Logger
|
||||||
|
func NewAliLS() logs.Logger {
|
||||||
|
alils := new(aliLSWriter)
|
||||||
|
alils.Level = logs.LevelTrace
|
||||||
|
return alils
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init parse config and init struct
|
||||||
|
func (c *aliLSWriter) Init(jsonConfig string) (err error) {
|
||||||
|
|
||||||
|
json.Unmarshal([]byte(jsonConfig), c)
|
||||||
|
|
||||||
|
if c.FlushWhen > CacheSize {
|
||||||
|
c.FlushWhen = CacheSize
|
||||||
|
}
|
||||||
|
|
||||||
|
prj := &LogProject{
|
||||||
|
Name: c.Project,
|
||||||
|
Endpoint: c.Endpoint,
|
||||||
|
AccessKeyID: c.KeyID,
|
||||||
|
AccessKeySecret: c.KeySecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.store, err = prj.GetLogStore(c.LogStore)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create default Log Group
|
||||||
|
c.group = append(c.group, &LogGroup{
|
||||||
|
Topic: proto.String(""),
|
||||||
|
Source: proto.String(c.Source),
|
||||||
|
Logs: make([]*Log, 0, c.FlushWhen),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create other Log Group
|
||||||
|
c.groupMap = make(map[string]*LogGroup)
|
||||||
|
for _, topic := range c.Topics {
|
||||||
|
|
||||||
|
lg := &LogGroup{
|
||||||
|
Topic: proto.String(topic),
|
||||||
|
Source: proto.String(c.Source),
|
||||||
|
Logs: make([]*Log, 0, c.FlushWhen),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.group = append(c.group, lg)
|
||||||
|
c.groupMap[topic] = lg
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.group) == 1 {
|
||||||
|
c.withMap = false
|
||||||
|
} else {
|
||||||
|
c.withMap = true
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lock = &sync.Mutex{}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in connection.
|
||||||
|
// if connection is down, try to re-connect.
|
||||||
|
func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error) {
|
||||||
|
|
||||||
|
if level > c.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var topic string
|
||||||
|
var content string
|
||||||
|
var lg *LogGroup
|
||||||
|
if c.withMap {
|
||||||
|
|
||||||
|
// Topic,LogGroup
|
||||||
|
strs := strings.SplitN(msg, Delimiter, 2)
|
||||||
|
if len(strs) == 2 {
|
||||||
|
pos := strings.LastIndex(strs[0], " ")
|
||||||
|
topic = strs[0][pos+1 : len(strs[0])]
|
||||||
|
content = strs[0][0:pos] + strs[1]
|
||||||
|
lg = c.groupMap[topic]
|
||||||
|
}
|
||||||
|
|
||||||
|
// send to empty Topic
|
||||||
|
if lg == nil {
|
||||||
|
content = msg
|
||||||
|
lg = c.group[0]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content = msg
|
||||||
|
lg = c.group[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
c1 := &LogContent{
|
||||||
|
Key: proto.String("msg"),
|
||||||
|
Value: proto.String(content),
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &Log{
|
||||||
|
Time: proto.Uint32(uint32(when.Unix())),
|
||||||
|
Contents: []*LogContent{
|
||||||
|
c1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
lg.Logs = append(lg.Logs, l)
|
||||||
|
c.lock.Unlock()
|
||||||
|
|
||||||
|
if len(lg.Logs) >= c.FlushWhen {
|
||||||
|
c.flush(lg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (c *aliLSWriter) Flush() {
|
||||||
|
|
||||||
|
// flush all group
|
||||||
|
for _, lg := range c.group {
|
||||||
|
c.flush(lg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy destroy connection writer and close tcp listener.
|
||||||
|
func (c *aliLSWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *aliLSWriter) flush(lg *LogGroup) {
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
err := c.store.PutLogs(lg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lg.Logs = make([]*Log, 0, c.FlushWhen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logs.Register(logs.AdapterAliLS, NewAliLS)
|
||||||
|
}
|
13
pkg/logs/alils/config.go
Executable file
13
pkg/logs/alils/config.go
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
const (
|
||||||
|
version = "0.5.0" // SDK version
|
||||||
|
signatureMethod = "hmac-sha1" // Signature method
|
||||||
|
|
||||||
|
// OffsetNewest stands for the log head offset, i.e. the offset that will be
|
||||||
|
// assigned to the next message that will be produced to the shard.
|
||||||
|
OffsetNewest = "end"
|
||||||
|
// OffsetOldest stands for the oldest offset available on the logstore for a
|
||||||
|
// shard.
|
||||||
|
OffsetOldest = "begin"
|
||||||
|
)
|
1038
pkg/logs/alils/log.pb.go
Executable file
1038
pkg/logs/alils/log.pb.go
Executable file
File diff suppressed because it is too large
Load Diff
42
pkg/logs/alils/log_config.go
Executable file
42
pkg/logs/alils/log_config.go
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
// InputDetail define log detail
|
||||||
|
type InputDetail struct {
|
||||||
|
LogType string `json:"logType"`
|
||||||
|
LogPath string `json:"logPath"`
|
||||||
|
FilePattern string `json:"filePattern"`
|
||||||
|
LocalStorage bool `json:"localStorage"`
|
||||||
|
TimeFormat string `json:"timeFormat"`
|
||||||
|
LogBeginRegex string `json:"logBeginRegex"`
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
Keys []string `json:"key"`
|
||||||
|
FilterKeys []string `json:"filterKey"`
|
||||||
|
FilterRegex []string `json:"filterRegex"`
|
||||||
|
TopicFormat string `json:"topicFormat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputDetail define the output detail
|
||||||
|
type OutputDetail struct {
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
LogStoreName string `json:"logstoreName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogConfig define Log Config
|
||||||
|
type LogConfig struct {
|
||||||
|
Name string `json:"configName"`
|
||||||
|
InputType string `json:"inputType"`
|
||||||
|
InputDetail InputDetail `json:"inputDetail"`
|
||||||
|
OutputType string `json:"outputType"`
|
||||||
|
OutputDetail OutputDetail `json:"outputDetail"`
|
||||||
|
|
||||||
|
CreateTime uint32
|
||||||
|
LastModifyTime uint32
|
||||||
|
|
||||||
|
project *LogProject
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedMachineGroup returns applied machine group of this config.
|
||||||
|
func (c *LogConfig) GetAppliedMachineGroup(confName string) (groupNames []string, err error) {
|
||||||
|
groupNames, err = c.project.GetAppliedMachineGroups(c.Name)
|
||||||
|
return
|
||||||
|
}
|
819
pkg/logs/alils/log_project.go
Executable file
819
pkg/logs/alils/log_project.go
Executable file
@ -0,0 +1,819 @@
|
|||||||
|
/*
|
||||||
|
Package alils implements the SDK(v0.5.0) of Simple Log Service(abbr. SLS).
|
||||||
|
|
||||||
|
For more description about SLS, please read this article:
|
||||||
|
http://gitlab.alibaba-inc.com/sls/doc.
|
||||||
|
*/
|
||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error message in SLS HTTP response.
|
||||||
|
type errorMessage struct {
|
||||||
|
Code string `json:"errorCode"`
|
||||||
|
Message string `json:"errorMessage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogProject Define the Ali Project detail
|
||||||
|
type LogProject struct {
|
||||||
|
Name string // Project name
|
||||||
|
Endpoint string // IP or hostname of SLS endpoint
|
||||||
|
AccessKeyID string
|
||||||
|
AccessKeySecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogProject creates a new SLS project.
|
||||||
|
func NewLogProject(name, endpoint, AccessKeyID, accessKeySecret string) (p *LogProject, err error) {
|
||||||
|
p = &LogProject{
|
||||||
|
Name: name,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
AccessKeyID: AccessKeyID,
|
||||||
|
AccessKeySecret: accessKeySecret,
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLogStore returns all logstore names of project p.
|
||||||
|
func (p *LogProject) ListLogStore() (storeNames []string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores")
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to list logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Count int
|
||||||
|
LogStores []string
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storeNames = body.LogStores
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogStore returns logstore according by logstore name.
|
||||||
|
func (p *LogProject) GetLogStore(name string) (s *LogStore, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "GET", "/logstores/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s = &LogStore{}
|
||||||
|
err = json.Unmarshal(buf, s)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.project = p
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLogStore creates a new logstore in SLS,
|
||||||
|
// where name is logstore name,
|
||||||
|
// and ttl is time-to-live(in day) of logs,
|
||||||
|
// and shardCnt is the number of shards.
|
||||||
|
func (p *LogProject) CreateLogStore(name string, ttl, shardCnt int) (err error) {
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Name string `json:"logstoreName"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
ShardCount int `json:"shardCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
store := &Body{
|
||||||
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
ShardCount: shardCnt,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(store)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "POST", "/logstores", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to create logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLogStore deletes a logstore according by logstore name.
|
||||||
|
func (p *LogProject) DeleteLogStore(name string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "DELETE", "/logstores/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLogStore updates a logstore according by logstore name,
|
||||||
|
// obviously we can't modify the logstore name itself.
|
||||||
|
func (p *LogProject) UpdateLogStore(name string, ttl, shardCnt int) (err error) {
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Name string `json:"logstoreName"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
ShardCount int `json:"shardCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
store := &Body{
|
||||||
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
ShardCount: shardCnt,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(store)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "PUT", "/logstores", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMachineGroup returns machine group name list and the total number of machine groups.
|
||||||
|
// The offset starts from 0 and the size is the max number of machine groups could be returned.
|
||||||
|
func (p *LogProject) ListMachineGroup(offset, size int) (m []string, total int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
size = 500
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups?offset=%v&size=%v", offset, size)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to list machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
MachineGroups []string
|
||||||
|
Count int
|
||||||
|
Total int
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m = body.MachineGroups
|
||||||
|
total = body.Total
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMachineGroup retruns machine group according by machine group name.
|
||||||
|
func (p *LogProject) GetMachineGroup(name string) (m *MachineGroup, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "GET", "/machinegroups/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get machine group:%v", name)
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m = &MachineGroup{}
|
||||||
|
err = json.Unmarshal(buf, m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.project = p
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMachineGroup creates a new machine group in SLS.
|
||||||
|
func (p *LogProject) CreateMachineGroup(m *MachineGroup) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "POST", "/machinegroups", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to create machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMachineGroup updates a machine group.
|
||||||
|
func (p *LogProject) UpdateMachineGroup(m *MachineGroup) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "PUT", "/machinegroups/"+m.Name, h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMachineGroup deletes machine group according machine group name.
|
||||||
|
func (p *LogProject) DeleteMachineGroup(name string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "DELETE", "/machinegroups/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConfig returns config names list and the total number of configs.
|
||||||
|
// The offset starts from 0 and the size is the max number of configs could be returned.
|
||||||
|
func (p *LogProject) ListConfig(offset, size int) (cfgNames []string, total int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
size = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/configs?offset=%v&size=%v", offset, size)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Total int
|
||||||
|
Configs []string
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgNames = body.Configs
|
||||||
|
total = body.Total
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig returns config according by config name.
|
||||||
|
func (p *LogProject) GetConfig(name string) (c *LogConfig, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "GET", "/configs/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &LogConfig{}
|
||||||
|
err = json.Unmarshal(buf, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.project = p
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfig updates a config.
|
||||||
|
func (p *LogProject) UpdateConfig(c *LogConfig) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "PUT", "/configs/"+c.Name, h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateConfig creates a new config in SLS.
|
||||||
|
func (p *LogProject) CreateConfig(c *LogConfig) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "POST", "/configs", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteConfig deletes a config according by config name.
|
||||||
|
func (p *LogProject) DeleteConfig(name string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "DELETE", "/configs/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedMachineGroups returns applied machine group names list according config name.
|
||||||
|
func (p *LogProject) GetAppliedMachineGroups(confName string) (groupNames []string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/configs/%v/machinegroups", confName)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get applied machine groups")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Count int
|
||||||
|
Machinegroups []string
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &Body{}
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
groupNames = body.Machinegroups
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedConfigs returns applied config names list according machine group name groupName.
|
||||||
|
func (p *LogProject) GetAppliedConfigs(groupName string) (confNames []string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/configs", groupName)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to applied configs")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cfg struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Configs []string `json:"configs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &Cfg{}
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
confNames = body.Configs
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyConfigToMachineGroup applies config to machine group.
|
||||||
|
func (p *LogProject) ApplyConfigToMachineGroup(confName, groupName string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
|
||||||
|
r, err := request(p, "PUT", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to apply config to machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveConfigFromMachineGroup removes config from machine group.
|
||||||
|
func (p *LogProject) RemoveConfigFromMachineGroup(confName, groupName string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
|
||||||
|
r, err := request(p, "DELETE", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to remove config from machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
271
pkg/logs/alils/log_store.go
Executable file
271
pkg/logs/alils/log_store.go
Executable file
@ -0,0 +1,271 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
lz4 "github.com/cloudflare/golz4"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogStore Store the logs
|
||||||
|
type LogStore struct {
|
||||||
|
Name string `json:"logstoreName"`
|
||||||
|
TTL int
|
||||||
|
ShardCount int
|
||||||
|
|
||||||
|
CreateTime uint32
|
||||||
|
LastModifyTime uint32
|
||||||
|
|
||||||
|
project *LogProject
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shard define the Log Shard
|
||||||
|
type Shard struct {
|
||||||
|
ShardID int `json:"shardID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListShards returns shard id list of this logstore.
|
||||||
|
func (s *LogStore) ListShards() (shardIDs []int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v/shards", s.Name)
|
||||||
|
r, err := request(s.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to list logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var shards []*Shard
|
||||||
|
err = json.Unmarshal(buf, &shards)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range shards {
|
||||||
|
shardIDs = append(shardIDs, v.ShardID)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutLogs put logs into logstore.
|
||||||
|
// The callers should transform user logs into LogGroup.
|
||||||
|
func (s *LogStore) PutLogs(lg *LogGroup) (err error) {
|
||||||
|
body, err := proto.Marshal(lg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compresse body with lz4
|
||||||
|
out := make([]byte, lz4.CompressBound(body))
|
||||||
|
n, err := lz4.Compress(body, out)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-compresstype": "lz4",
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/x-protobuf",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v", s.Name)
|
||||||
|
r, err := request(s.project, "POST", uri, h, out[:n])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to put logs")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCursor gets log cursor of one shard specified by shardID.
|
||||||
|
// The from can be in three form: a) unix timestamp in seccond, b) "begin", c) "end".
|
||||||
|
// For more detail please read: http://gitlab.alibaba-inc.com/sls/doc/blob/master/api/shard.md#logstore
|
||||||
|
func (s *LogStore) GetCursor(shardID int, from string) (cursor string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=cursor&from=%v",
|
||||||
|
s.Name, shardID, from)
|
||||||
|
|
||||||
|
r, err := request(s.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get cursor")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Cursor string
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cursor = body.Cursor
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogsBytes gets logs binary data from shard specified by shardID according cursor.
|
||||||
|
// The logGroupMaxCount is the max number of logGroup could be returned.
|
||||||
|
// The nextCursor is the next curosr can be used to read logs at next time.
|
||||||
|
func (s *LogStore) GetLogsBytes(shardID int, cursor string,
|
||||||
|
logGroupMaxCount int) (out []byte, nextCursor string, err error) {
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
"Accept": "application/x-protobuf",
|
||||||
|
"Accept-Encoding": "lz4",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=logs&cursor=%v&count=%v",
|
||||||
|
s.Name, shardID, cursor, logGroupMaxCount)
|
||||||
|
|
||||||
|
r, err := request(s.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get cursor")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := r.Header["X-Sls-Compresstype"]
|
||||||
|
if !ok || len(v) == 0 {
|
||||||
|
err = fmt.Errorf("can't find 'x-sls-compresstype' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] != "lz4" {
|
||||||
|
err = fmt.Errorf("unexpected compress type:%v", v[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok = r.Header["X-Sls-Cursor"]
|
||||||
|
if !ok || len(v) == 0 {
|
||||||
|
err = fmt.Errorf("can't find 'x-sls-cursor' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nextCursor = v[0]
|
||||||
|
|
||||||
|
v, ok = r.Header["X-Sls-Bodyrawsize"]
|
||||||
|
if !ok || len(v) == 0 {
|
||||||
|
err = fmt.Errorf("can't find 'x-sls-bodyrawsize' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bodyRawSize, err := strconv.Atoi(v[0])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out = make([]byte, bodyRawSize)
|
||||||
|
err = lz4.Uncompress(buf, out)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogsBytesDecode decodes logs binary data retruned by GetLogsBytes API
|
||||||
|
func LogsBytesDecode(data []byte) (gl *LogGroupList, err error) {
|
||||||
|
|
||||||
|
gl = &LogGroupList{}
|
||||||
|
err = proto.Unmarshal(data, gl)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogs gets logs from shard specified by shardID according cursor.
|
||||||
|
// The logGroupMaxCount is the max number of logGroup could be returned.
|
||||||
|
// The nextCursor is the next curosr can be used to read logs at next time.
|
||||||
|
func (s *LogStore) GetLogs(shardID int, cursor string,
|
||||||
|
logGroupMaxCount int) (gl *LogGroupList, nextCursor string, err error) {
|
||||||
|
|
||||||
|
out, nextCursor, err := s.GetLogsBytes(shardID, cursor, logGroupMaxCount)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gl, err = LogsBytesDecode(out)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
91
pkg/logs/alils/machine_group.go
Executable file
91
pkg/logs/alils/machine_group.go
Executable file
@ -0,0 +1,91 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MachineGroupAttribute define the Attribute
|
||||||
|
type MachineGroupAttribute struct {
|
||||||
|
ExternalName string `json:"externalName"`
|
||||||
|
TopicName string `json:"groupTopic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MachineGroup define the machine Group
|
||||||
|
type MachineGroup struct {
|
||||||
|
Name string `json:"groupName"`
|
||||||
|
Type string `json:"groupType"`
|
||||||
|
MachineIDType string `json:"machineIdentifyType"`
|
||||||
|
MachineIDList []string `json:"machineList"`
|
||||||
|
|
||||||
|
Attribute MachineGroupAttribute `json:"groupAttribute"`
|
||||||
|
|
||||||
|
CreateTime uint32
|
||||||
|
LastModifyTime uint32
|
||||||
|
|
||||||
|
project *LogProject
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine define the Machine
|
||||||
|
type Machine struct {
|
||||||
|
IP string
|
||||||
|
UniqueID string `json:"machine-uniqueid"`
|
||||||
|
UserdefinedID string `json:"userdefined-id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MachineList define the Machine List
|
||||||
|
type MachineList struct {
|
||||||
|
Total int
|
||||||
|
Machines []*Machine
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMachines returns machine list of this machine group.
|
||||||
|
func (m *MachineGroup) ListMachines() (ms []*Machine, total int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/machines", m.Name)
|
||||||
|
r, err := request(m.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to remove config from machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &MachineList{}
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ms = body.Machines
|
||||||
|
total = body.Total
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedConfigs returns applied configs of this machine group.
|
||||||
|
func (m *MachineGroup) GetAppliedConfigs() (confNames []string, err error) {
|
||||||
|
confNames, err = m.project.GetAppliedConfigs(m.Name)
|
||||||
|
return
|
||||||
|
}
|
62
pkg/logs/alils/request.go
Executable file
62
pkg/logs/alils/request.go
Executable file
@ -0,0 +1,62 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// request sends a request to SLS.
|
||||||
|
func request(project *LogProject, method, uri string, headers map[string]string,
|
||||||
|
body []byte) (resp *http.Response, err error) {
|
||||||
|
|
||||||
|
// The caller should provide 'x-sls-bodyrawsize' header
|
||||||
|
if _, ok := headers["x-sls-bodyrawsize"]; !ok {
|
||||||
|
err = fmt.Errorf("Can't find 'x-sls-bodyrawsize' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SLS public request headers
|
||||||
|
headers["Host"] = project.Name + "." + project.Endpoint
|
||||||
|
headers["Date"] = nowRFC1123()
|
||||||
|
headers["x-sls-apiversion"] = version
|
||||||
|
headers["x-sls-signaturemethod"] = signatureMethod
|
||||||
|
if body != nil {
|
||||||
|
bodyMD5 := fmt.Sprintf("%X", md5.Sum(body))
|
||||||
|
headers["Content-MD5"] = bodyMD5
|
||||||
|
|
||||||
|
if _, ok := headers["Content-Type"]; !ok {
|
||||||
|
err = fmt.Errorf("Can't find 'Content-Type' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calc Authorization
|
||||||
|
// Authorization = "SLS <AccessKeyID>:<Signature>"
|
||||||
|
digest, err := signature(project, method, uri, headers)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
auth := fmt.Sprintf("SLS %v:%v", project.AccessKeyID, digest)
|
||||||
|
headers["Authorization"] = auth
|
||||||
|
|
||||||
|
// Initialize http request
|
||||||
|
reader := bytes.NewReader(body)
|
||||||
|
urlStr := fmt.Sprintf("http://%v.%v%v", project.Name, project.Endpoint, uri)
|
||||||
|
req, err := http.NewRequest(method, urlStr, reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v := range headers {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ready to do request
|
||||||
|
resp, err = http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
111
pkg/logs/alils/signature.go
Executable file
111
pkg/logs/alils/signature.go
Executable file
@ -0,0 +1,111 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GMT location
|
||||||
|
var gmtLoc = time.FixedZone("GMT", 0)
|
||||||
|
|
||||||
|
// NowRFC1123 returns now time in RFC1123 format with GMT timezone,
|
||||||
|
// eg. "Mon, 02 Jan 2006 15:04:05 GMT".
|
||||||
|
func nowRFC1123() string {
|
||||||
|
return time.Now().In(gmtLoc).Format(time.RFC1123)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature calculates a request's signature digest.
|
||||||
|
func signature(project *LogProject, method, uri string,
|
||||||
|
headers map[string]string) (digest string, err error) {
|
||||||
|
var contentMD5, contentType, date, canoHeaders, canoResource string
|
||||||
|
var slsHeaderKeys sort.StringSlice
|
||||||
|
|
||||||
|
// SignString = VERB + "\n"
|
||||||
|
// + CONTENT-MD5 + "\n"
|
||||||
|
// + CONTENT-TYPE + "\n"
|
||||||
|
// + DATE + "\n"
|
||||||
|
// + CanonicalizedSLSHeaders + "\n"
|
||||||
|
// + CanonicalizedResource
|
||||||
|
|
||||||
|
if val, ok := headers["Content-MD5"]; ok {
|
||||||
|
contentMD5 = val
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := headers["Content-Type"]; ok {
|
||||||
|
contentType = val
|
||||||
|
}
|
||||||
|
|
||||||
|
date, ok := headers["Date"]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("Can't find 'Date' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calc CanonicalizedSLSHeaders
|
||||||
|
slsHeaders := make(map[string]string, len(headers))
|
||||||
|
for k, v := range headers {
|
||||||
|
l := strings.TrimSpace(strings.ToLower(k))
|
||||||
|
if strings.HasPrefix(l, "x-sls-") {
|
||||||
|
slsHeaders[l] = strings.TrimSpace(v)
|
||||||
|
slsHeaderKeys = append(slsHeaderKeys, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(slsHeaderKeys)
|
||||||
|
for i, k := range slsHeaderKeys {
|
||||||
|
canoHeaders += k + ":" + slsHeaders[k]
|
||||||
|
if i+1 < len(slsHeaderKeys) {
|
||||||
|
canoHeaders += "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calc CanonicalizedResource
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
canoResource += url.QueryEscape(u.Path)
|
||||||
|
if u.RawQuery != "" {
|
||||||
|
var keys sort.StringSlice
|
||||||
|
|
||||||
|
vals := u.Query()
|
||||||
|
for k := range vals {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(keys)
|
||||||
|
canoResource += "?"
|
||||||
|
for i, k := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
canoResource += "&"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vals[k] {
|
||||||
|
canoResource += k + "=" + v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signStr := method + "\n" +
|
||||||
|
contentMD5 + "\n" +
|
||||||
|
contentType + "\n" +
|
||||||
|
date + "\n" +
|
||||||
|
canoHeaders + "\n" +
|
||||||
|
canoResource
|
||||||
|
|
||||||
|
// Signature = base64(hmac-sha1(UTF8-Encoding-Of(SignString),AccessKeySecret))
|
||||||
|
mac := hmac.New(sha1.New, []byte(project.AccessKeySecret))
|
||||||
|
_, err = mac.Write([]byte(signStr))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
digest = base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||||
|
return
|
||||||
|
}
|
119
pkg/logs/conn.go
Normal file
119
pkg/logs/conn.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// connWriter implements LoggerInterface.
|
||||||
|
// it writes messages in keep-live tcp connection.
|
||||||
|
type connWriter struct {
|
||||||
|
lg *logWriter
|
||||||
|
innerWriter io.WriteCloser
|
||||||
|
ReconnectOnMsg bool `json:"reconnectOnMsg"`
|
||||||
|
Reconnect bool `json:"reconnect"`
|
||||||
|
Net string `json:"net"`
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn create new ConnWrite returning as LoggerInterface.
|
||||||
|
func NewConn() Logger {
|
||||||
|
conn := new(connWriter)
|
||||||
|
conn.Level = LevelTrace
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init init connection writer with json config.
|
||||||
|
// json config only need key "level".
|
||||||
|
func (c *connWriter) Init(jsonConfig string) error {
|
||||||
|
return json.Unmarshal([]byte(jsonConfig), c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in connection.
|
||||||
|
// if connection is down, try to re-connect.
|
||||||
|
func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > c.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.needToConnectOnMsg() {
|
||||||
|
err := c.connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ReconnectOnMsg {
|
||||||
|
defer c.innerWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.lg.writeln(when, msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (c *connWriter) Flush() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy destroy connection writer and close tcp listener.
|
||||||
|
func (c *connWriter) Destroy() {
|
||||||
|
if c.innerWriter != nil {
|
||||||
|
c.innerWriter.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connWriter) connect() error {
|
||||||
|
if c.innerWriter != nil {
|
||||||
|
c.innerWriter.Close()
|
||||||
|
c.innerWriter = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.Dial(c.Net, c.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||||
|
tcpConn.SetKeepAlive(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.innerWriter = conn
|
||||||
|
c.lg = newLogWriter(conn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connWriter) needToConnectOnMsg() bool {
|
||||||
|
if c.Reconnect {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.innerWriter == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.ReconnectOnMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterConn, NewConn)
|
||||||
|
}
|
79
pkg/logs/conn_test.go
Normal file
79
pkg/logs/conn_test.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConnTCPListener takes a TCP listener and accepts n TCP connections
|
||||||
|
// Returns connections using connChan
|
||||||
|
func connTCPListener(t *testing.T, n int, ln net.Listener, connChan chan<- net.Conn) {
|
||||||
|
|
||||||
|
// Listen and accept n incoming connections
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Log("Error accepting connection: ", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send accepted connection to channel
|
||||||
|
connChan <- conn
|
||||||
|
}
|
||||||
|
ln.Close()
|
||||||
|
close(connChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConn(t *testing.T) {
|
||||||
|
log := NewLogger(1000)
|
||||||
|
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||||
|
log.Informational("informational")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReconnect(t *testing.T) {
|
||||||
|
// Setup connection listener
|
||||||
|
newConns := make(chan net.Conn)
|
||||||
|
connNum := 2
|
||||||
|
ln, err := net.Listen("tcp", ":6002")
|
||||||
|
if err != nil {
|
||||||
|
t.Log("Error listening:", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
go connTCPListener(t, connNum, ln, newConns)
|
||||||
|
|
||||||
|
// Setup logger
|
||||||
|
log := NewLogger(1000)
|
||||||
|
log.SetPrefix("test")
|
||||||
|
log.SetLogger(AdapterConn, `{"net":"tcp","reconnect":true,"level":6,"addr":":6002"}`)
|
||||||
|
log.Informational("informational 1")
|
||||||
|
|
||||||
|
// Refuse first connection
|
||||||
|
first := <-newConns
|
||||||
|
first.Close()
|
||||||
|
|
||||||
|
// Send another log after conn closed
|
||||||
|
log.Informational("informational 2")
|
||||||
|
|
||||||
|
// Check if there was a second connection attempt
|
||||||
|
select {
|
||||||
|
case second := <-newConns:
|
||||||
|
second.Close()
|
||||||
|
default:
|
||||||
|
t.Error("Did not reconnect")
|
||||||
|
}
|
||||||
|
}
|
99
pkg/logs/console.go
Normal file
99
pkg/logs/console.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/shiena/ansicolor"
|
||||||
|
)
|
||||||
|
|
||||||
|
// brush is a color join function
|
||||||
|
type brush func(string) string
|
||||||
|
|
||||||
|
// newBrush return a fix color Brush
|
||||||
|
func newBrush(color string) brush {
|
||||||
|
pre := "\033["
|
||||||
|
reset := "\033[0m"
|
||||||
|
return func(text string) string {
|
||||||
|
return pre + color + "m" + text + reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors = []brush{
|
||||||
|
newBrush("1;37"), // Emergency white
|
||||||
|
newBrush("1;36"), // Alert cyan
|
||||||
|
newBrush("1;35"), // Critical magenta
|
||||||
|
newBrush("1;31"), // Error red
|
||||||
|
newBrush("1;33"), // Warning yellow
|
||||||
|
newBrush("1;32"), // Notice green
|
||||||
|
newBrush("1;34"), // Informational blue
|
||||||
|
newBrush("1;44"), // Debug Background blue
|
||||||
|
}
|
||||||
|
|
||||||
|
// consoleWriter implements LoggerInterface and writes messages to terminal.
|
||||||
|
type consoleWriter struct {
|
||||||
|
lg *logWriter
|
||||||
|
Level int `json:"level"`
|
||||||
|
Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConsole create ConsoleWriter returning as LoggerInterface.
|
||||||
|
func NewConsole() Logger {
|
||||||
|
cw := &consoleWriter{
|
||||||
|
lg: newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)),
|
||||||
|
Level: LevelDebug,
|
||||||
|
Colorful: true,
|
||||||
|
}
|
||||||
|
return cw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init init console logger.
|
||||||
|
// jsonConfig like '{"level":LevelTrace}'.
|
||||||
|
func (c *consoleWriter) Init(jsonConfig string) error {
|
||||||
|
if len(jsonConfig) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.Unmarshal([]byte(jsonConfig), c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in console.
|
||||||
|
func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > c.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.Colorful {
|
||||||
|
msg = strings.Replace(msg, levelPrefix[level], colors[level](levelPrefix[level]), 1)
|
||||||
|
}
|
||||||
|
c.lg.writeln(when, msg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (c *consoleWriter) Destroy() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (c *consoleWriter) Flush() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterConsole, NewConsole)
|
||||||
|
}
|
64
pkg/logs/console_test.go
Normal file
64
pkg/logs/console_test.go
Normal file
@ -0,0 +1,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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Try each log level in decreasing order of priority.
|
||||||
|
func testConsoleCalls(bl *BeeLogger) {
|
||||||
|
bl.Emergency("emergency")
|
||||||
|
bl.Alert("alert")
|
||||||
|
bl.Critical("critical")
|
||||||
|
bl.Error("error")
|
||||||
|
bl.Warning("warning")
|
||||||
|
bl.Notice("notice")
|
||||||
|
bl.Informational("informational")
|
||||||
|
bl.Debug("debug")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test console logging by visually comparing the lines being output with and
|
||||||
|
// without a log level specification.
|
||||||
|
func TestConsole(t *testing.T) {
|
||||||
|
log1 := NewLogger(10000)
|
||||||
|
log1.EnableFuncCallDepth(true)
|
||||||
|
log1.SetLogger("console", "")
|
||||||
|
testConsoleCalls(log1)
|
||||||
|
|
||||||
|
log2 := NewLogger(100)
|
||||||
|
log2.SetLogger("console", `{"level":3}`)
|
||||||
|
testConsoleCalls(log2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test console without color
|
||||||
|
func TestConsoleNoColor(t *testing.T) {
|
||||||
|
log := NewLogger(100)
|
||||||
|
log.SetLogger("console", `{"color":false}`)
|
||||||
|
testConsoleCalls(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test console async
|
||||||
|
func TestConsoleAsync(t *testing.T) {
|
||||||
|
log := NewLogger(100)
|
||||||
|
log.SetLogger("console")
|
||||||
|
log.Async()
|
||||||
|
//log.Close()
|
||||||
|
testConsoleCalls(log)
|
||||||
|
for len(log.msgChan) != 0 {
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
102
pkg/logs/es/es.go
Normal file
102
pkg/logs/es/es.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package es
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/elastic/go-elasticsearch/v6"
|
||||||
|
"github.com/elastic/go-elasticsearch/v6/esapi"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewES return a LoggerInterface
|
||||||
|
func NewES() logs.Logger {
|
||||||
|
cw := &esLogger{
|
||||||
|
Level: logs.LevelDebug,
|
||||||
|
}
|
||||||
|
return cw
|
||||||
|
}
|
||||||
|
|
||||||
|
// esLogger will log msg into ES
|
||||||
|
// before you using this implementation,
|
||||||
|
// please import this package
|
||||||
|
// usually means that you can import this package in your main package
|
||||||
|
// for example, anonymous:
|
||||||
|
// import _ "github.com/astaxie/beego/logs/es"
|
||||||
|
type esLogger struct {
|
||||||
|
*elasticsearch.Client
|
||||||
|
DSN string `json:"dsn"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// {"dsn":"http://localhost:9200/","level":1}
|
||||||
|
func (el *esLogger) Init(jsonconfig string) error {
|
||||||
|
err := json.Unmarshal([]byte(jsonconfig), el)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if el.DSN == "" {
|
||||||
|
return errors.New("empty dsn")
|
||||||
|
} else if u, err := url.Parse(el.DSN); err != nil {
|
||||||
|
return err
|
||||||
|
} else if u.Path == "" {
|
||||||
|
return errors.New("missing prefix")
|
||||||
|
} else {
|
||||||
|
conn, err := elasticsearch.NewClient(elasticsearch.Config{
|
||||||
|
Addresses: []string{el.DSN},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
el.Client = conn
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg will write the msg and level into es
|
||||||
|
func (el *esLogger) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > el.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := LogDocument{
|
||||||
|
Timestamp: when.Format(time.RFC3339),
|
||||||
|
Msg: msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(idx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req := esapi.IndexRequest{
|
||||||
|
Index: fmt.Sprintf("%04d.%02d.%02d", when.Year(), when.Month(), when.Day()),
|
||||||
|
DocumentType: "logs",
|
||||||
|
Body: strings.NewReader(string(body)),
|
||||||
|
}
|
||||||
|
_, err = req.Do(context.Background(), el.Client)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy is a empty method
|
||||||
|
func (el *esLogger) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush is a empty method
|
||||||
|
func (el *esLogger) Flush() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogDocument struct {
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logs.Register(logs.AdapterEs, NewES)
|
||||||
|
}
|
409
pkg/logs/file.go
Normal file
409
pkg/logs/file.go
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
// 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileLogWriter implements LoggerInterface.
|
||||||
|
// It writes messages by lines limit, file size limit, or time frequency.
|
||||||
|
type fileLogWriter struct {
|
||||||
|
sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
|
||||||
|
// The opened file
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
fileWriter *os.File
|
||||||
|
|
||||||
|
// Rotate at line
|
||||||
|
MaxLines int `json:"maxlines"`
|
||||||
|
maxLinesCurLines int
|
||||||
|
|
||||||
|
MaxFiles int `json:"maxfiles"`
|
||||||
|
MaxFilesCurFiles int
|
||||||
|
|
||||||
|
// Rotate at size
|
||||||
|
MaxSize int `json:"maxsize"`
|
||||||
|
maxSizeCurSize int
|
||||||
|
|
||||||
|
// Rotate daily
|
||||||
|
Daily bool `json:"daily"`
|
||||||
|
MaxDays int64 `json:"maxdays"`
|
||||||
|
dailyOpenDate int
|
||||||
|
dailyOpenTime time.Time
|
||||||
|
|
||||||
|
// Rotate hourly
|
||||||
|
Hourly bool `json:"hourly"`
|
||||||
|
MaxHours int64 `json:"maxhours"`
|
||||||
|
hourlyOpenDate int
|
||||||
|
hourlyOpenTime time.Time
|
||||||
|
|
||||||
|
Rotate bool `json:"rotate"`
|
||||||
|
|
||||||
|
Level int `json:"level"`
|
||||||
|
|
||||||
|
Perm string `json:"perm"`
|
||||||
|
|
||||||
|
RotatePerm string `json:"rotateperm"`
|
||||||
|
|
||||||
|
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFileWriter create a FileLogWriter returning as LoggerInterface.
|
||||||
|
func newFileWriter() Logger {
|
||||||
|
w := &fileLogWriter{
|
||||||
|
Daily: true,
|
||||||
|
MaxDays: 7,
|
||||||
|
Hourly: false,
|
||||||
|
MaxHours: 168,
|
||||||
|
Rotate: true,
|
||||||
|
RotatePerm: "0440",
|
||||||
|
Level: LevelTrace,
|
||||||
|
Perm: "0660",
|
||||||
|
MaxLines: 10000000,
|
||||||
|
MaxFiles: 999,
|
||||||
|
MaxSize: 1 << 28,
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init file logger with json config.
|
||||||
|
// jsonConfig like:
|
||||||
|
// {
|
||||||
|
// "filename":"logs/beego.log",
|
||||||
|
// "maxLines":10000,
|
||||||
|
// "maxsize":1024,
|
||||||
|
// "daily":true,
|
||||||
|
// "maxDays":15,
|
||||||
|
// "rotate":true,
|
||||||
|
// "perm":"0600"
|
||||||
|
// }
|
||||||
|
func (w *fileLogWriter) Init(jsonConfig string) error {
|
||||||
|
err := json.Unmarshal([]byte(jsonConfig), w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(w.Filename) == 0 {
|
||||||
|
return errors.New("jsonconfig must have filename")
|
||||||
|
}
|
||||||
|
w.suffix = filepath.Ext(w.Filename)
|
||||||
|
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
|
||||||
|
if w.suffix == "" {
|
||||||
|
w.suffix = ".log"
|
||||||
|
}
|
||||||
|
err = w.startLogger()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start file logger. create log file and set to locker-inside file writer.
|
||||||
|
func (w *fileLogWriter) startLogger() error {
|
||||||
|
file, err := w.createLogFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w.fileWriter != nil {
|
||||||
|
w.fileWriter.Close()
|
||||||
|
}
|
||||||
|
w.fileWriter = file
|
||||||
|
return w.initFd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) needRotateDaily(size int, day int) bool {
|
||||||
|
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||||
|
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||||
|
(w.Daily && day != w.dailyOpenDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) needRotateHourly(size int, hour int) bool {
|
||||||
|
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||||
|
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||||
|
(w.Hourly && hour != w.hourlyOpenDate)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write logger message into file.
|
||||||
|
func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > w.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
hd, d, h := formatTimeHeader(when)
|
||||||
|
msg = string(hd) + msg + "\n"
|
||||||
|
if w.Rotate {
|
||||||
|
w.RLock()
|
||||||
|
if w.needRotateHourly(len(msg), h) {
|
||||||
|
w.RUnlock()
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateHourly(len(msg), h) {
|
||||||
|
if err := w.doRotate(when); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
} else if w.needRotateDaily(len(msg), d) {
|
||||||
|
w.RUnlock()
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateDaily(len(msg), d) {
|
||||||
|
if err := w.doRotate(when); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
} else {
|
||||||
|
w.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Lock()
|
||||||
|
_, err := w.fileWriter.Write([]byte(msg))
|
||||||
|
if err == nil {
|
||||||
|
w.maxLinesCurLines++
|
||||||
|
w.maxSizeCurSize += len(msg)
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) createLogFile() (*os.File, error) {
|
||||||
|
// Open the log file
|
||||||
|
perm, err := strconv.ParseInt(w.Perm, 8, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filepath := path.Dir(w.Filename)
|
||||||
|
os.MkdirAll(filepath, os.FileMode(perm))
|
||||||
|
|
||||||
|
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
|
||||||
|
if err == nil {
|
||||||
|
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
|
||||||
|
os.Chmod(w.Filename, os.FileMode(perm))
|
||||||
|
}
|
||||||
|
return fd, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) initFd() error {
|
||||||
|
fd := w.fileWriter
|
||||||
|
fInfo, err := fd.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get stat err: %s", err)
|
||||||
|
}
|
||||||
|
w.maxSizeCurSize = int(fInfo.Size())
|
||||||
|
w.dailyOpenTime = time.Now()
|
||||||
|
w.dailyOpenDate = w.dailyOpenTime.Day()
|
||||||
|
w.hourlyOpenTime = time.Now()
|
||||||
|
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
|
||||||
|
w.maxLinesCurLines = 0
|
||||||
|
if w.Hourly {
|
||||||
|
go w.hourlyRotate(w.hourlyOpenTime)
|
||||||
|
} else if w.Daily {
|
||||||
|
go w.dailyRotate(w.dailyOpenTime)
|
||||||
|
}
|
||||||
|
if fInfo.Size() > 0 && w.MaxLines > 0 {
|
||||||
|
count, err := w.lines()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.maxLinesCurLines = count
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) dailyRotate(openTime time.Time) {
|
||||||
|
y, m, d := openTime.Add(24 * time.Hour).Date()
|
||||||
|
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
|
||||||
|
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
||||||
|
<-tm.C
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateDaily(0, time.Now().Day()) {
|
||||||
|
if err := w.doRotate(time.Now()); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) hourlyRotate(openTime time.Time) {
|
||||||
|
y, m, d := openTime.Add(1 * time.Hour).Date()
|
||||||
|
h, _, _ := openTime.Add(1 * time.Hour).Clock()
|
||||||
|
nextHour := time.Date(y, m, d, h, 0, 0, 0, openTime.Location())
|
||||||
|
tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100))
|
||||||
|
<-tm.C
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateHourly(0, time.Now().Hour()) {
|
||||||
|
if err := w.doRotate(time.Now()); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) lines() (int, error) {
|
||||||
|
fd, err := os.Open(w.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, 32768) // 32k
|
||||||
|
count := 0
|
||||||
|
lineSep := []byte{'\n'}
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, err := fd.Read(buf)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count += bytes.Count(buf[:c], lineSep)
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoRotate means it need to write file in new file.
|
||||||
|
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
|
||||||
|
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||||
|
// file exists
|
||||||
|
// Find the next available number
|
||||||
|
num := w.MaxFilesCurFiles + 1
|
||||||
|
fName := ""
|
||||||
|
format := ""
|
||||||
|
var openTime time.Time
|
||||||
|
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Lstat(w.Filename)
|
||||||
|
if err != nil {
|
||||||
|
//even if the file is not exist or other ,we should RESTART the logger
|
||||||
|
goto RESTART_LOGGER
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Hourly {
|
||||||
|
format = "2006010215"
|
||||||
|
openTime = w.hourlyOpenTime
|
||||||
|
} else if w.Daily {
|
||||||
|
format = "2006-01-02"
|
||||||
|
openTime = w.dailyOpenTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// only when one of them be setted, then the file would be splited
|
||||||
|
if w.MaxLines > 0 || w.MaxSize > 0 {
|
||||||
|
for ; err == nil && num <= w.MaxFiles; num++ {
|
||||||
|
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format(format), num, w.suffix)
|
||||||
|
_, err = os.Lstat(fName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", openTime.Format(format), num, w.suffix)
|
||||||
|
_, err = os.Lstat(fName)
|
||||||
|
w.MaxFilesCurFiles = num
|
||||||
|
}
|
||||||
|
|
||||||
|
// return error if the last file checked still existed
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// close fileWriter before rename
|
||||||
|
w.fileWriter.Close()
|
||||||
|
|
||||||
|
// Rename the file to its new found name
|
||||||
|
// even if occurs error,we MUST guarantee to restart new logger
|
||||||
|
err = os.Rename(w.Filename, fName)
|
||||||
|
if err != nil {
|
||||||
|
goto RESTART_LOGGER
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chmod(fName, os.FileMode(rotatePerm))
|
||||||
|
|
||||||
|
RESTART_LOGGER:
|
||||||
|
|
||||||
|
startLoggerErr := w.startLogger()
|
||||||
|
go w.deleteOldLog()
|
||||||
|
|
||||||
|
if startLoggerErr != nil {
|
||||||
|
return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Rotate: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) deleteOldLog() {
|
||||||
|
dir := filepath.Dir(w.Filename)
|
||||||
|
absolutePath, err := filepath.EvalSymlinks(w.Filename)
|
||||||
|
if err == nil {
|
||||||
|
dir = filepath.Dir(absolutePath)
|
||||||
|
}
|
||||||
|
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if info == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w.Hourly {
|
||||||
|
if !info.IsDir() && info.ModTime().Add(1 * time.Hour * time.Duration(w.MaxHours)).Before(time.Now()) {
|
||||||
|
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||||
|
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||||
|
os.Remove(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if w.Daily {
|
||||||
|
if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) {
|
||||||
|
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||||
|
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||||
|
os.Remove(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy close the file description, close file writer.
|
||||||
|
func (w *fileLogWriter) Destroy() {
|
||||||
|
w.fileWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flush file logger.
|
||||||
|
// there are no buffering messages in file logger in memory.
|
||||||
|
// flush file means sync file from disk.
|
||||||
|
func (w *fileLogWriter) Flush() {
|
||||||
|
w.fileWriter.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterFile, newFileWriter)
|
||||||
|
}
|
420
pkg/logs/file_test.go
Normal file
420
pkg/logs/file_test.go
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
// 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilePerm(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
// use 0666 as test perm cause the default umask is 022
|
||||||
|
log.SetLogger("file", `{"filename":"test.log", "perm": "0666"}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Informational("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
file, err := os.Stat("test.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if file.Mode() != 0666 {
|
||||||
|
t.Fatal("unexpected log file permission")
|
||||||
|
}
|
||||||
|
os.Remove("test.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile1(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Informational("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
f, err := os.Open("test.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b := bufio.NewReader(f)
|
||||||
|
lineNum := 0
|
||||||
|
for {
|
||||||
|
line, _, err := b.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
lineNum++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var expected = LevelDebug + 1
|
||||||
|
if lineNum != expected {
|
||||||
|
t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines")
|
||||||
|
}
|
||||||
|
os.Remove("test.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile2(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", fmt.Sprintf(`{"filename":"test2.log","level":%d}`, LevelError))
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
f, err := os.Open("test2.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b := bufio.NewReader(f)
|
||||||
|
lineNum := 0
|
||||||
|
for {
|
||||||
|
line, _, err := b.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
lineNum++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var expected = LevelError + 1
|
||||||
|
if lineNum != expected {
|
||||||
|
t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines")
|
||||||
|
}
|
||||||
|
os.Remove("test2.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDailyRotate_01(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log"
|
||||||
|
b, err := exists(rotateName)
|
||||||
|
if !b || err != nil {
|
||||||
|
os.Remove("test3.log")
|
||||||
|
t.Fatal("rotate not generated")
|
||||||
|
}
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDailyRotate_02(t *testing.T) {
|
||||||
|
fn1 := "rotate_day.log"
|
||||||
|
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||||
|
testFileRotate(t, fn1, fn2, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDailyRotate_03(t *testing.T) {
|
||||||
|
fn1 := "rotate_day.log"
|
||||||
|
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||||
|
os.Create(fn)
|
||||||
|
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||||
|
testFileRotate(t, fn1, fn2, true, false)
|
||||||
|
os.Remove(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDailyRotate_04(t *testing.T) {
|
||||||
|
fn1 := "rotate_day.log"
|
||||||
|
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||||
|
testFileDailyRotate(t, fn1, fn2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDailyRotate_05(t *testing.T) {
|
||||||
|
fn1 := "rotate_day.log"
|
||||||
|
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||||
|
os.Create(fn)
|
||||||
|
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||||
|
testFileDailyRotate(t, fn1, fn2)
|
||||||
|
os.Remove(fn)
|
||||||
|
}
|
||||||
|
func TestFileDailyRotate_06(t *testing.T) { //test file mode
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log"
|
||||||
|
s, _ := os.Lstat(rotateName)
|
||||||
|
if s.Mode() != 0440 {
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
t.Fatal("rotate file mode error")
|
||||||
|
}
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_01(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
|
||||||
|
b, err := exists(rotateName)
|
||||||
|
if !b || err != nil {
|
||||||
|
os.Remove("test3.log")
|
||||||
|
t.Fatal("rotate not generated")
|
||||||
|
}
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_02(t *testing.T) {
|
||||||
|
fn1 := "rotate_hour.log"
|
||||||
|
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||||
|
testFileRotate(t, fn1, fn2, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_03(t *testing.T) {
|
||||||
|
fn1 := "rotate_hour.log"
|
||||||
|
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
|
||||||
|
os.Create(fn)
|
||||||
|
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||||
|
testFileRotate(t, fn1, fn2, false, true)
|
||||||
|
os.Remove(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_04(t *testing.T) {
|
||||||
|
fn1 := "rotate_hour.log"
|
||||||
|
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||||
|
testFileHourlyRotate(t, fn1, fn2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_05(t *testing.T) {
|
||||||
|
fn1 := "rotate_hour.log"
|
||||||
|
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
|
||||||
|
os.Create(fn)
|
||||||
|
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||||
|
testFileHourlyRotate(t, fn1, fn2)
|
||||||
|
os.Remove(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_06(t *testing.T) { //test file mode
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
|
||||||
|
s, _ := os.Lstat(rotateName)
|
||||||
|
if s.Mode() != 0440 {
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
t.Fatal("rotate file mode error")
|
||||||
|
}
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFileRotate(t *testing.T, fn1, fn2 string, daily, hourly bool) {
|
||||||
|
fw := &fileLogWriter{
|
||||||
|
Daily: daily,
|
||||||
|
MaxDays: 7,
|
||||||
|
Hourly: hourly,
|
||||||
|
MaxHours: 168,
|
||||||
|
Rotate: true,
|
||||||
|
Level: LevelTrace,
|
||||||
|
Perm: "0660",
|
||||||
|
RotatePerm: "0440",
|
||||||
|
}
|
||||||
|
|
||||||
|
if daily {
|
||||||
|
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||||
|
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||||
|
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||||
|
}
|
||||||
|
|
||||||
|
if hourly {
|
||||||
|
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
|
||||||
|
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
|
||||||
|
fw.hourlyOpenDate = fw.hourlyOpenTime.Day()
|
||||||
|
}
|
||||||
|
|
||||||
|
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug)
|
||||||
|
|
||||||
|
for _, file := range []string{fn1, fn2} {
|
||||||
|
_, err := os.Stat(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
fw.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
|
||||||
|
fw := &fileLogWriter{
|
||||||
|
Daily: true,
|
||||||
|
MaxDays: 7,
|
||||||
|
Rotate: true,
|
||||||
|
Level: LevelTrace,
|
||||||
|
Perm: "0660",
|
||||||
|
RotatePerm: "0440",
|
||||||
|
}
|
||||||
|
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||||
|
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||||
|
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||||
|
today, _ := time.ParseInLocation("2006-01-02", time.Now().Format("2006-01-02"), fw.dailyOpenTime.Location())
|
||||||
|
today = today.Add(-1 * time.Second)
|
||||||
|
fw.dailyRotate(today)
|
||||||
|
for _, file := range []string{fn1, fn2} {
|
||||||
|
_, err := os.Stat(file)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
content, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if len(content) > 0 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
fw.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFileHourlyRotate(t *testing.T, fn1, fn2 string) {
|
||||||
|
fw := &fileLogWriter{
|
||||||
|
Hourly: true,
|
||||||
|
MaxHours: 168,
|
||||||
|
Rotate: true,
|
||||||
|
Level: LevelTrace,
|
||||||
|
Perm: "0660",
|
||||||
|
RotatePerm: "0440",
|
||||||
|
}
|
||||||
|
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
|
||||||
|
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
|
||||||
|
fw.hourlyOpenDate = fw.hourlyOpenTime.Hour()
|
||||||
|
hour, _ := time.ParseInLocation("2006010215", time.Now().Format("2006010215"), fw.hourlyOpenTime.Location())
|
||||||
|
hour = hour.Add(-1 * time.Second)
|
||||||
|
fw.hourlyRotate(hour)
|
||||||
|
for _, file := range []string{fn1, fn2} {
|
||||||
|
_, err := os.Stat(file)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
content, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if len(content) > 0 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
fw.Destroy()
|
||||||
|
}
|
||||||
|
func exists(path string) (bool, error) {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFile(b *testing.B) {
|
||||||
|
log := NewLogger(100000)
|
||||||
|
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Debug("debug")
|
||||||
|
}
|
||||||
|
os.Remove("test4.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFileAsynchronous(b *testing.B) {
|
||||||
|
log := NewLogger(100000)
|
||||||
|
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||||
|
log.Async()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Debug("debug")
|
||||||
|
}
|
||||||
|
os.Remove("test4.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFileCallDepth(b *testing.B) {
|
||||||
|
log := NewLogger(100000)
|
||||||
|
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||||
|
log.EnableFuncCallDepth(true)
|
||||||
|
log.SetLogFuncCallDepth(2)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Debug("debug")
|
||||||
|
}
|
||||||
|
os.Remove("test4.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFileAsynchronousCallDepth(b *testing.B) {
|
||||||
|
log := NewLogger(100000)
|
||||||
|
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||||
|
log.EnableFuncCallDepth(true)
|
||||||
|
log.SetLogFuncCallDepth(2)
|
||||||
|
log.Async()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Debug("debug")
|
||||||
|
}
|
||||||
|
os.Remove("test4.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFileOnGoroutine(b *testing.B) {
|
||||||
|
log := NewLogger(100000)
|
||||||
|
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
go log.Debug("debug")
|
||||||
|
}
|
||||||
|
os.Remove("test4.log")
|
||||||
|
}
|
72
pkg/logs/jianliao.go
Normal file
72
pkg/logs/jianliao.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||||
|
type JLWriter struct {
|
||||||
|
AuthorName string `json:"authorname"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
WebhookURL string `json:"webhookurl"`
|
||||||
|
RedirectURL string `json:"redirecturl,omitempty"`
|
||||||
|
ImageURL string `json:"imageurl,omitempty"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newJLWriter create jiaoliao writer.
|
||||||
|
func newJLWriter() Logger {
|
||||||
|
return &JLWriter{Level: LevelTrace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init JLWriter with json config string
|
||||||
|
func (s *JLWriter) Init(jsonconfig string) error {
|
||||||
|
return json.Unmarshal([]byte(jsonconfig), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in smtp writer.
|
||||||
|
// it will send an email with subject and only this message.
|
||||||
|
func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > s.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg)
|
||||||
|
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("authorName", s.AuthorName)
|
||||||
|
form.Add("title", s.Title)
|
||||||
|
form.Add("text", text)
|
||||||
|
if s.RedirectURL != "" {
|
||||||
|
form.Add("redirectUrl", s.RedirectURL)
|
||||||
|
}
|
||||||
|
if s.ImageURL != "" {
|
||||||
|
form.Add("imageUrl", s.ImageURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.PostForm(s.WebhookURL, form)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (s *JLWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (s *JLWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterJianLiao, newJLWriter)
|
||||||
|
}
|
669
pkg/logs/log.go
Normal file
669
pkg/logs/log.go
Normal file
@ -0,0 +1,669 @@
|
|||||||
|
// 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 logs provide a general log interface
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// import "github.com/astaxie/beego/logs"
|
||||||
|
//
|
||||||
|
// log := NewLogger(10000)
|
||||||
|
// log.SetLogger("console", "")
|
||||||
|
//
|
||||||
|
// > the first params stand for how many channel
|
||||||
|
//
|
||||||
|
// Use it like this:
|
||||||
|
//
|
||||||
|
// log.Trace("trace")
|
||||||
|
// log.Info("info")
|
||||||
|
// log.Warn("warning")
|
||||||
|
// log.Debug("debug")
|
||||||
|
// log.Critical("critical")
|
||||||
|
//
|
||||||
|
// more docs http://beego.me/docs/module/logs.md
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RFC5424 log message levels.
|
||||||
|
const (
|
||||||
|
LevelEmergency = iota
|
||||||
|
LevelAlert
|
||||||
|
LevelCritical
|
||||||
|
LevelError
|
||||||
|
LevelWarning
|
||||||
|
LevelNotice
|
||||||
|
LevelInformational
|
||||||
|
LevelDebug
|
||||||
|
)
|
||||||
|
|
||||||
|
// levelLogLogger is defined to implement log.Logger
|
||||||
|
// the real log level will be LevelEmergency
|
||||||
|
const levelLoggerImpl = -1
|
||||||
|
|
||||||
|
// Name for adapter with beego official support
|
||||||
|
const (
|
||||||
|
AdapterConsole = "console"
|
||||||
|
AdapterFile = "file"
|
||||||
|
AdapterMultiFile = "multifile"
|
||||||
|
AdapterMail = "smtp"
|
||||||
|
AdapterConn = "conn"
|
||||||
|
AdapterEs = "es"
|
||||||
|
AdapterJianLiao = "jianliao"
|
||||||
|
AdapterSlack = "slack"
|
||||||
|
AdapterAliLS = "alils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Legacy log level constants to ensure backwards compatibility.
|
||||||
|
const (
|
||||||
|
LevelInfo = LevelInformational
|
||||||
|
LevelTrace = LevelDebug
|
||||||
|
LevelWarn = LevelWarning
|
||||||
|
)
|
||||||
|
|
||||||
|
type newLoggerFunc func() Logger
|
||||||
|
|
||||||
|
// Logger defines the behavior of a log provider.
|
||||||
|
type Logger interface {
|
||||||
|
Init(config string) error
|
||||||
|
WriteMsg(when time.Time, msg string, level int) error
|
||||||
|
Destroy()
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
var adapters = make(map[string]newLoggerFunc)
|
||||||
|
var levelPrefix = [LevelDebug + 1]string{"[M]", "[A]", "[C]", "[E]", "[W]", "[N]", "[I]", "[D]"}
|
||||||
|
|
||||||
|
// Register makes a log provide available by the provided name.
|
||||||
|
// If Register is called twice with the same name or if driver is nil,
|
||||||
|
// it panics.
|
||||||
|
func Register(name string, log newLoggerFunc) {
|
||||||
|
if log == nil {
|
||||||
|
panic("logs: Register provide is nil")
|
||||||
|
}
|
||||||
|
if _, dup := adapters[name]; dup {
|
||||||
|
panic("logs: Register called twice for provider " + name)
|
||||||
|
}
|
||||||
|
adapters[name] = log
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeeLogger is default logger in beego application.
|
||||||
|
// it can contain several providers and log message into all providers.
|
||||||
|
type BeeLogger struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
level int
|
||||||
|
init bool
|
||||||
|
enableFuncCallDepth bool
|
||||||
|
loggerFuncCallDepth int
|
||||||
|
asynchronous bool
|
||||||
|
prefix string
|
||||||
|
msgChanLen int64
|
||||||
|
msgChan chan *logMsg
|
||||||
|
signalChan chan string
|
||||||
|
wg sync.WaitGroup
|
||||||
|
outputs []*nameLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultAsyncMsgLen = 1e3
|
||||||
|
|
||||||
|
type nameLogger struct {
|
||||||
|
Logger
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type logMsg struct {
|
||||||
|
level int
|
||||||
|
msg string
|
||||||
|
when time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var logMsgPool *sync.Pool
|
||||||
|
|
||||||
|
// NewLogger returns a new BeeLogger.
|
||||||
|
// channelLen means the number of messages in chan(used where asynchronous is true).
|
||||||
|
// if the buffering chan is full, logger adapters write to file or other way.
|
||||||
|
func NewLogger(channelLens ...int64) *BeeLogger {
|
||||||
|
bl := new(BeeLogger)
|
||||||
|
bl.level = LevelDebug
|
||||||
|
bl.loggerFuncCallDepth = 2
|
||||||
|
bl.msgChanLen = append(channelLens, 0)[0]
|
||||||
|
if bl.msgChanLen <= 0 {
|
||||||
|
bl.msgChanLen = defaultAsyncMsgLen
|
||||||
|
}
|
||||||
|
bl.signalChan = make(chan string, 1)
|
||||||
|
bl.setLogger(AdapterConsole)
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async set the log to asynchronous and start the goroutine
|
||||||
|
func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger {
|
||||||
|
bl.lock.Lock()
|
||||||
|
defer bl.lock.Unlock()
|
||||||
|
if bl.asynchronous {
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
bl.asynchronous = true
|
||||||
|
if len(msgLen) > 0 && msgLen[0] > 0 {
|
||||||
|
bl.msgChanLen = msgLen[0]
|
||||||
|
}
|
||||||
|
bl.msgChan = make(chan *logMsg, bl.msgChanLen)
|
||||||
|
logMsgPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &logMsg{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
bl.wg.Add(1)
|
||||||
|
go bl.startLogger()
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||||
|
// config need to be correct JSON as string: {"interval":360}.
|
||||||
|
func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
|
||||||
|
config := append(configs, "{}")[0]
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
if l.name == adapterName {
|
||||||
|
return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logAdapter, ok := adapters[adapterName]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||||
|
}
|
||||||
|
|
||||||
|
lg := logAdapter()
|
||||||
|
err := lg.Init(config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||||
|
// config need to be correct JSON as string: {"interval":360}.
|
||||||
|
func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error {
|
||||||
|
bl.lock.Lock()
|
||||||
|
defer bl.lock.Unlock()
|
||||||
|
if !bl.init {
|
||||||
|
bl.outputs = []*nameLogger{}
|
||||||
|
bl.init = true
|
||||||
|
}
|
||||||
|
return bl.setLogger(adapterName, configs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelLogger remove a logger adapter in BeeLogger.
|
||||||
|
func (bl *BeeLogger) DelLogger(adapterName string) error {
|
||||||
|
bl.lock.Lock()
|
||||||
|
defer bl.lock.Unlock()
|
||||||
|
outputs := []*nameLogger{}
|
||||||
|
for _, lg := range bl.outputs {
|
||||||
|
if lg.name == adapterName {
|
||||||
|
lg.Destroy()
|
||||||
|
} else {
|
||||||
|
outputs = append(outputs, lg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(outputs) == len(bl.outputs) {
|
||||||
|
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||||
|
}
|
||||||
|
bl.outputs = outputs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
err := l.WriteMsg(when, msg, level)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) Write(p []byte) (n int, err error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
// writeMsg will always add a '\n' character
|
||||||
|
if p[len(p)-1] == '\n' {
|
||||||
|
p = p[0 : len(p)-1]
|
||||||
|
}
|
||||||
|
// set levelLoggerImpl to ensure all log message will be write out
|
||||||
|
err = bl.writeMsg(levelLoggerImpl, string(p))
|
||||||
|
if err == nil {
|
||||||
|
return len(p), err
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
|
||||||
|
if !bl.init {
|
||||||
|
bl.lock.Lock()
|
||||||
|
bl.setLogger(AdapterConsole)
|
||||||
|
bl.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v) > 0 {
|
||||||
|
msg = fmt.Sprintf(msg, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = bl.prefix + " " + msg
|
||||||
|
|
||||||
|
when := time.Now()
|
||||||
|
if bl.enableFuncCallDepth {
|
||||||
|
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
||||||
|
if !ok {
|
||||||
|
file = "???"
|
||||||
|
line = 0
|
||||||
|
}
|
||||||
|
_, filename := path.Split(file)
|
||||||
|
msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg
|
||||||
|
}
|
||||||
|
|
||||||
|
//set level info in front of filename info
|
||||||
|
if logLevel == levelLoggerImpl {
|
||||||
|
// set to emergency to ensure all log will be print out correctly
|
||||||
|
logLevel = LevelEmergency
|
||||||
|
} else {
|
||||||
|
msg = levelPrefix[logLevel] + " " + msg
|
||||||
|
}
|
||||||
|
|
||||||
|
if bl.asynchronous {
|
||||||
|
lm := logMsgPool.Get().(*logMsg)
|
||||||
|
lm.level = logLevel
|
||||||
|
lm.msg = msg
|
||||||
|
lm.when = when
|
||||||
|
if bl.outputs != nil {
|
||||||
|
bl.msgChan <- lm
|
||||||
|
} else {
|
||||||
|
logMsgPool.Put(lm)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bl.writeToLoggers(when, msg, logLevel)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel Set log message level.
|
||||||
|
// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning),
|
||||||
|
// log providers will not even be sent the message.
|
||||||
|
func (bl *BeeLogger) SetLevel(l int) {
|
||||||
|
bl.level = l
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel Get Current log message level.
|
||||||
|
func (bl *BeeLogger) GetLevel() int {
|
||||||
|
return bl.level
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogFuncCallDepth set log funcCallDepth
|
||||||
|
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
||||||
|
bl.loggerFuncCallDepth = d
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogFuncCallDepth return log funcCallDepth for wrapper
|
||||||
|
func (bl *BeeLogger) GetLogFuncCallDepth() int {
|
||||||
|
return bl.loggerFuncCallDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableFuncCallDepth enable log funcCallDepth
|
||||||
|
func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
||||||
|
bl.enableFuncCallDepth = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// set prefix
|
||||||
|
func (bl *BeeLogger) SetPrefix(s string) {
|
||||||
|
bl.prefix = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// start logger chan reading.
|
||||||
|
// when chan is not empty, write logs.
|
||||||
|
func (bl *BeeLogger) startLogger() {
|
||||||
|
gameOver := false
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case bm := <-bl.msgChan:
|
||||||
|
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||||
|
logMsgPool.Put(bm)
|
||||||
|
case sg := <-bl.signalChan:
|
||||||
|
// Now should only send "flush" or "close" to bl.signalChan
|
||||||
|
bl.flush()
|
||||||
|
if sg == "close" {
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
|
bl.outputs = nil
|
||||||
|
gameOver = true
|
||||||
|
}
|
||||||
|
bl.wg.Done()
|
||||||
|
}
|
||||||
|
if gameOver {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emergency Log EMERGENCY level message.
|
||||||
|
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
|
||||||
|
if LevelEmergency > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelEmergency, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert Log ALERT level message.
|
||||||
|
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
|
||||||
|
if LevelAlert > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelAlert, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical Log CRITICAL level message.
|
||||||
|
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
|
||||||
|
if LevelCritical > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelCritical, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error Log ERROR level message.
|
||||||
|
func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
||||||
|
if LevelError > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelError, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning Log WARNING level message.
|
||||||
|
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
||||||
|
if LevelWarn > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelWarn, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notice Log NOTICE level message.
|
||||||
|
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
|
||||||
|
if LevelNotice > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelNotice, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Informational Log INFORMATIONAL level message.
|
||||||
|
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
||||||
|
if LevelInfo > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelInfo, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug Log DEBUG level message.
|
||||||
|
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
|
||||||
|
if LevelDebug > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelDebug, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn Log WARN level message.
|
||||||
|
// compatibility alias for Warning()
|
||||||
|
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
|
||||||
|
if LevelWarn > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelWarn, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info Log INFO level message.
|
||||||
|
// compatibility alias for Informational()
|
||||||
|
func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
||||||
|
if LevelInfo > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelInfo, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace Log TRACE level message.
|
||||||
|
// compatibility alias for Debug()
|
||||||
|
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||||
|
if LevelDebug > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelDebug, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flush all chan data.
|
||||||
|
func (bl *BeeLogger) Flush() {
|
||||||
|
if bl.asynchronous {
|
||||||
|
bl.signalChan <- "flush"
|
||||||
|
bl.wg.Wait()
|
||||||
|
bl.wg.Add(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close close logger, flush all chan data and destroy all adapters in BeeLogger.
|
||||||
|
func (bl *BeeLogger) Close() {
|
||||||
|
if bl.asynchronous {
|
||||||
|
bl.signalChan <- "close"
|
||||||
|
bl.wg.Wait()
|
||||||
|
close(bl.msgChan)
|
||||||
|
} else {
|
||||||
|
bl.flush()
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
|
bl.outputs = nil
|
||||||
|
}
|
||||||
|
close(bl.signalChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset close all outputs, and set bl.outputs to nil
|
||||||
|
func (bl *BeeLogger) Reset() {
|
||||||
|
bl.Flush()
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
|
bl.outputs = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) flush() {
|
||||||
|
if bl.asynchronous {
|
||||||
|
for {
|
||||||
|
if len(bl.msgChan) > 0 {
|
||||||
|
bm := <-bl.msgChan
|
||||||
|
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||||
|
logMsgPool.Put(bm)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// beeLogger references the used application logger.
|
||||||
|
var beeLogger = NewLogger()
|
||||||
|
|
||||||
|
// GetBeeLogger returns the default BeeLogger
|
||||||
|
func GetBeeLogger() *BeeLogger {
|
||||||
|
return beeLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
var beeLoggerMap = struct {
|
||||||
|
sync.RWMutex
|
||||||
|
logs map[string]*log.Logger
|
||||||
|
}{
|
||||||
|
logs: map[string]*log.Logger{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogger returns the default BeeLogger
|
||||||
|
func GetLogger(prefixes ...string) *log.Logger {
|
||||||
|
prefix := append(prefixes, "")[0]
|
||||||
|
if prefix != "" {
|
||||||
|
prefix = fmt.Sprintf(`[%s] `, strings.ToUpper(prefix))
|
||||||
|
}
|
||||||
|
beeLoggerMap.RLock()
|
||||||
|
l, ok := beeLoggerMap.logs[prefix]
|
||||||
|
if ok {
|
||||||
|
beeLoggerMap.RUnlock()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
beeLoggerMap.RUnlock()
|
||||||
|
beeLoggerMap.Lock()
|
||||||
|
defer beeLoggerMap.Unlock()
|
||||||
|
l, ok = beeLoggerMap.logs[prefix]
|
||||||
|
if !ok {
|
||||||
|
l = log.New(beeLogger, prefix, 0)
|
||||||
|
beeLoggerMap.logs[prefix] = l
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset will remove all the adapter
|
||||||
|
func Reset() {
|
||||||
|
beeLogger.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async set the beelogger with Async mode and hold msglen messages
|
||||||
|
func Async(msgLen ...int64) *BeeLogger {
|
||||||
|
return beeLogger.Async(msgLen...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the global log level used by the simple logger.
|
||||||
|
func SetLevel(l int) {
|
||||||
|
beeLogger.SetLevel(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrefix sets the prefix
|
||||||
|
func SetPrefix(s string) {
|
||||||
|
beeLogger.SetPrefix(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableFuncCallDepth enable log funcCallDepth
|
||||||
|
func EnableFuncCallDepth(b bool) {
|
||||||
|
beeLogger.enableFuncCallDepth = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogFuncCall set the CallDepth, default is 4
|
||||||
|
func SetLogFuncCall(b bool) {
|
||||||
|
beeLogger.EnableFuncCallDepth(b)
|
||||||
|
beeLogger.SetLogFuncCallDepth(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogFuncCallDepth set log funcCallDepth
|
||||||
|
func SetLogFuncCallDepth(d int) {
|
||||||
|
beeLogger.loggerFuncCallDepth = d
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger sets a new logger.
|
||||||
|
func SetLogger(adapter string, config ...string) error {
|
||||||
|
return beeLogger.SetLogger(adapter, config...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emergency logs a message at emergency level.
|
||||||
|
func Emergency(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Emergency(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert logs a message at alert level.
|
||||||
|
func Alert(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Alert(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical logs a message at critical level.
|
||||||
|
func Critical(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Critical(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at error level.
|
||||||
|
func Error(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Error(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs a message at warning level.
|
||||||
|
func Warning(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Warn(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn compatibility alias for Warning()
|
||||||
|
func Warn(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Warn(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notice logs a message at notice level.
|
||||||
|
func Notice(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Notice(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Informational logs a message at info level.
|
||||||
|
func Informational(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Info(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info compatibility alias for Warning()
|
||||||
|
func Info(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Info(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at debug level.
|
||||||
|
func Debug(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Debug(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace logs a message at trace level.
|
||||||
|
// compatibility alias for Warning()
|
||||||
|
func Trace(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Trace(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatLog(f interface{}, v ...interface{}) string {
|
||||||
|
var msg string
|
||||||
|
switch f.(type) {
|
||||||
|
case string:
|
||||||
|
msg = f.(string)
|
||||||
|
if len(v) == 0 {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") {
|
||||||
|
//format string
|
||||||
|
} else {
|
||||||
|
//do not contain format char
|
||||||
|
msg += strings.Repeat(" %v", len(v))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
msg = fmt.Sprint(f)
|
||||||
|
if len(v) == 0 {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
msg += strings.Repeat(" %v", len(v))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(msg, v...)
|
||||||
|
}
|
176
pkg/logs/logger.go
Normal file
176
pkg/logs/logger.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
// 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logWriter struct {
|
||||||
|
sync.Mutex
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogWriter(wr io.Writer) *logWriter {
|
||||||
|
return &logWriter{writer: wr}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lg *logWriter) writeln(when time.Time, msg string) (int, error) {
|
||||||
|
lg.Lock()
|
||||||
|
h, _, _ := formatTimeHeader(when)
|
||||||
|
n, err := lg.writer.Write(append(append(h, msg...), '\n'))
|
||||||
|
lg.Unlock()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
y1 = `0123456789`
|
||||||
|
y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
y3 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999`
|
||||||
|
y4 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
mo1 = `000000000111`
|
||||||
|
mo2 = `123456789012`
|
||||||
|
d1 = `0000000001111111111222222222233`
|
||||||
|
d2 = `1234567890123456789012345678901`
|
||||||
|
h1 = `000000000011111111112222`
|
||||||
|
h2 = `012345678901234567890123`
|
||||||
|
mi1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||||
|
mi2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
s1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||||
|
s2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
ns1 = `0123456789`
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatTimeHeader(when time.Time) ([]byte, int, int) {
|
||||||
|
y, mo, d := when.Date()
|
||||||
|
h, mi, s := when.Clock()
|
||||||
|
ns := when.Nanosecond() / 1000000
|
||||||
|
//len("2006/01/02 15:04:05.123 ")==24
|
||||||
|
var buf [24]byte
|
||||||
|
|
||||||
|
buf[0] = y1[y/1000%10]
|
||||||
|
buf[1] = y2[y/100]
|
||||||
|
buf[2] = y3[y-y/100*100]
|
||||||
|
buf[3] = y4[y-y/100*100]
|
||||||
|
buf[4] = '/'
|
||||||
|
buf[5] = mo1[mo-1]
|
||||||
|
buf[6] = mo2[mo-1]
|
||||||
|
buf[7] = '/'
|
||||||
|
buf[8] = d1[d-1]
|
||||||
|
buf[9] = d2[d-1]
|
||||||
|
buf[10] = ' '
|
||||||
|
buf[11] = h1[h]
|
||||||
|
buf[12] = h2[h]
|
||||||
|
buf[13] = ':'
|
||||||
|
buf[14] = mi1[mi]
|
||||||
|
buf[15] = mi2[mi]
|
||||||
|
buf[16] = ':'
|
||||||
|
buf[17] = s1[s]
|
||||||
|
buf[18] = s2[s]
|
||||||
|
buf[19] = '.'
|
||||||
|
buf[20] = ns1[ns/100]
|
||||||
|
buf[21] = ns1[ns%100/10]
|
||||||
|
buf[22] = ns1[ns%10]
|
||||||
|
|
||||||
|
buf[23] = ' '
|
||||||
|
|
||||||
|
return buf[0:], d, h
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
||||||
|
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
|
||||||
|
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109})
|
||||||
|
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
|
||||||
|
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
||||||
|
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
||||||
|
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
|
||||||
|
|
||||||
|
w32Green = string([]byte{27, 91, 52, 50, 109})
|
||||||
|
w32White = string([]byte{27, 91, 52, 55, 109})
|
||||||
|
w32Yellow = string([]byte{27, 91, 52, 51, 109})
|
||||||
|
w32Red = string([]byte{27, 91, 52, 49, 109})
|
||||||
|
w32Blue = string([]byte{27, 91, 52, 52, 109})
|
||||||
|
w32Magenta = string([]byte{27, 91, 52, 53, 109})
|
||||||
|
w32Cyan = string([]byte{27, 91, 52, 54, 109})
|
||||||
|
|
||||||
|
reset = string([]byte{27, 91, 48, 109})
|
||||||
|
)
|
||||||
|
|
||||||
|
var once sync.Once
|
||||||
|
var colorMap map[string]string
|
||||||
|
|
||||||
|
func initColor() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
green = w32Green
|
||||||
|
white = w32White
|
||||||
|
yellow = w32Yellow
|
||||||
|
red = w32Red
|
||||||
|
blue = w32Blue
|
||||||
|
magenta = w32Magenta
|
||||||
|
cyan = w32Cyan
|
||||||
|
}
|
||||||
|
colorMap = map[string]string{
|
||||||
|
//by color
|
||||||
|
"green": green,
|
||||||
|
"white": white,
|
||||||
|
"yellow": yellow,
|
||||||
|
"red": red,
|
||||||
|
//by method
|
||||||
|
"GET": blue,
|
||||||
|
"POST": cyan,
|
||||||
|
"PUT": yellow,
|
||||||
|
"DELETE": red,
|
||||||
|
"PATCH": green,
|
||||||
|
"HEAD": magenta,
|
||||||
|
"OPTIONS": white,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorByStatus return color by http code
|
||||||
|
// 2xx return Green
|
||||||
|
// 3xx return White
|
||||||
|
// 4xx return Yellow
|
||||||
|
// 5xx return Red
|
||||||
|
func ColorByStatus(code int) string {
|
||||||
|
once.Do(initColor)
|
||||||
|
switch {
|
||||||
|
case code >= 200 && code < 300:
|
||||||
|
return colorMap["green"]
|
||||||
|
case code >= 300 && code < 400:
|
||||||
|
return colorMap["white"]
|
||||||
|
case code >= 400 && code < 500:
|
||||||
|
return colorMap["yellow"]
|
||||||
|
default:
|
||||||
|
return colorMap["red"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorByMethod return color by http code
|
||||||
|
func ColorByMethod(method string) string {
|
||||||
|
once.Do(initColor)
|
||||||
|
if c := colorMap[method]; c != "" {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return reset
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetColor return reset color
|
||||||
|
func ResetColor() string {
|
||||||
|
return reset
|
||||||
|
}
|
57
pkg/logs/logger_test.go
Normal file
57
pkg/logs/logger_test.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2016 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormatHeader_0(t *testing.T) {
|
||||||
|
tm := time.Now()
|
||||||
|
if tm.Year() >= 2100 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
dur := time.Second
|
||||||
|
for {
|
||||||
|
if tm.Year() >= 2100 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
h, _, _ := formatTimeHeader(tm)
|
||||||
|
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||||
|
t.Log(tm)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
tm = tm.Add(dur)
|
||||||
|
dur *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatHeader_1(t *testing.T) {
|
||||||
|
tm := time.Now()
|
||||||
|
year := tm.Year()
|
||||||
|
dur := time.Second
|
||||||
|
for {
|
||||||
|
if tm.Year() >= year+1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
h, _, _ := formatTimeHeader(tm)
|
||||||
|
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||||
|
t.Log(tm)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
tm = tm.Add(dur)
|
||||||
|
}
|
||||||
|
}
|
119
pkg/logs/multifile.go
Normal file
119
pkg/logs/multifile.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A filesLogWriter manages several fileLogWriter
|
||||||
|
// filesLogWriter will write logs to the file in json configuration and write the same level log to correspond file
|
||||||
|
// means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log
|
||||||
|
// and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log
|
||||||
|
// the rotate attribute also acts like fileLogWriter
|
||||||
|
type multiFileLogWriter struct {
|
||||||
|
writers [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter
|
||||||
|
fullLogWriter *fileLogWriter
|
||||||
|
Separate []string `json:"separate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"}
|
||||||
|
|
||||||
|
// Init file logger with json config.
|
||||||
|
// jsonConfig like:
|
||||||
|
// {
|
||||||
|
// "filename":"logs/beego.log",
|
||||||
|
// "maxLines":0,
|
||||||
|
// "maxsize":0,
|
||||||
|
// "daily":true,
|
||||||
|
// "maxDays":15,
|
||||||
|
// "rotate":true,
|
||||||
|
// "perm":0600,
|
||||||
|
// "separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"],
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) Init(config string) error {
|
||||||
|
writer := newFileWriter().(*fileLogWriter)
|
||||||
|
err := writer.Init(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.fullLogWriter = writer
|
||||||
|
f.writers[LevelDebug+1] = writer
|
||||||
|
|
||||||
|
//unmarshal "separate" field to f.Separate
|
||||||
|
json.Unmarshal([]byte(config), f)
|
||||||
|
|
||||||
|
jsonMap := map[string]interface{}{}
|
||||||
|
json.Unmarshal([]byte(config), &jsonMap)
|
||||||
|
|
||||||
|
for i := LevelEmergency; i < LevelDebug+1; i++ {
|
||||||
|
for _, v := range f.Separate {
|
||||||
|
if v == levelNames[i] {
|
||||||
|
jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix
|
||||||
|
jsonMap["level"] = i
|
||||||
|
bs, _ := json.Marshal(jsonMap)
|
||||||
|
writer = newFileWriter().(*fileLogWriter)
|
||||||
|
err := writer.Init(string(bs))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writers[i] = writer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) Destroy() {
|
||||||
|
for i := 0; i < len(f.writers); i++ {
|
||||||
|
if f.writers[i] != nil {
|
||||||
|
f.writers[i].Destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if f.fullLogWriter != nil {
|
||||||
|
f.fullLogWriter.WriteMsg(when, msg, level)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(f.writers)-1; i++ {
|
||||||
|
if f.writers[i] != nil {
|
||||||
|
if level == f.writers[i].Level {
|
||||||
|
f.writers[i].WriteMsg(when, msg, level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) Flush() {
|
||||||
|
for i := 0; i < len(f.writers); i++ {
|
||||||
|
if f.writers[i] != nil {
|
||||||
|
f.writers[i].Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFilesWriter create a FileLogWriter returning as LoggerInterface.
|
||||||
|
func newFilesWriter() Logger {
|
||||||
|
return &multiFileLogWriter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterMultiFile, newFilesWriter)
|
||||||
|
}
|
78
pkg/logs/multifile_test.go
Normal file
78
pkg/logs/multifile_test.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFiles_1(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("multifile", `{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Informational("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
fns := []string{""}
|
||||||
|
fns = append(fns, levelNames[0:]...)
|
||||||
|
name := "test"
|
||||||
|
suffix := ".log"
|
||||||
|
for _, fn := range fns {
|
||||||
|
|
||||||
|
file := name + suffix
|
||||||
|
if fn != "" {
|
||||||
|
file = name + "." + fn + suffix
|
||||||
|
}
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b := bufio.NewReader(f)
|
||||||
|
lineNum := 0
|
||||||
|
lastLine := ""
|
||||||
|
for {
|
||||||
|
line, _, err := b.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
lastLine = string(line)
|
||||||
|
lineNum++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var expected = 1
|
||||||
|
if fn == "" {
|
||||||
|
expected = LevelDebug + 1
|
||||||
|
}
|
||||||
|
if lineNum != expected {
|
||||||
|
t.Fatal(file, "has", lineNum, "lines not "+strconv.Itoa(expected)+" lines")
|
||||||
|
}
|
||||||
|
if lineNum == 1 {
|
||||||
|
if !strings.Contains(lastLine, fn) {
|
||||||
|
t.Fatal(file + " " + lastLine + " not contains the log msg " + fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
60
pkg/logs/slack.go
Normal file
60
pkg/logs/slack.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||||
|
type SLACKWriter struct {
|
||||||
|
WebhookURL string `json:"webhookurl"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSLACKWriter create jiaoliao writer.
|
||||||
|
func newSLACKWriter() Logger {
|
||||||
|
return &SLACKWriter{Level: LevelTrace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init SLACKWriter with json config string
|
||||||
|
func (s *SLACKWriter) Init(jsonconfig string) error {
|
||||||
|
return json.Unmarshal([]byte(jsonconfig), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in smtp writer.
|
||||||
|
// it will send an email with subject and only this message.
|
||||||
|
func (s *SLACKWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > s.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := fmt.Sprintf("{\"text\": \"%s %s\"}", when.Format("2006-01-02 15:04:05"), msg)
|
||||||
|
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("payload", text)
|
||||||
|
|
||||||
|
resp, err := http.PostForm(s.WebhookURL, form)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (s *SLACKWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (s *SLACKWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterSlack, newSLACKWriter)
|
||||||
|
}
|
149
pkg/logs/smtp.go
Normal file
149
pkg/logs/smtp.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
// 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/smtp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SMTPWriter implements LoggerInterface and is used to send emails via given SMTP-server.
|
||||||
|
type SMTPWriter struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
FromAddress string `json:"fromAddress"`
|
||||||
|
RecipientAddresses []string `json:"sendTos"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSMTPWriter create smtp writer.
|
||||||
|
func newSMTPWriter() Logger {
|
||||||
|
return &SMTPWriter{Level: LevelTrace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init smtp writer with json config.
|
||||||
|
// config like:
|
||||||
|
// {
|
||||||
|
// "username":"example@gmail.com",
|
||||||
|
// "password:"password",
|
||||||
|
// "host":"smtp.gmail.com:465",
|
||||||
|
// "subject":"email title",
|
||||||
|
// "fromAddress":"from@example.com",
|
||||||
|
// "sendTos":["email1","email2"],
|
||||||
|
// "level":LevelError
|
||||||
|
// }
|
||||||
|
func (s *SMTPWriter) Init(jsonconfig string) error {
|
||||||
|
return json.Unmarshal([]byte(jsonconfig), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth {
|
||||||
|
if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return smtp.PlainAuth(
|
||||||
|
"",
|
||||||
|
s.Username,
|
||||||
|
s.Password,
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error {
|
||||||
|
client, err := smtp.Dial(hostAddressWithPort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
host, _, _ := net.SplitHostPort(hostAddressWithPort)
|
||||||
|
tlsConn := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
ServerName: host,
|
||||||
|
}
|
||||||
|
if err = client.StartTLS(tlsConn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth != nil {
|
||||||
|
if err = client.Auth(auth); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = client.Mail(fromAddress); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rec := range recipients {
|
||||||
|
if err = client.Rcpt(rec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := client.Data()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(msgContent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in smtp writer.
|
||||||
|
// it will send an email with subject and only this message.
|
||||||
|
func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > s.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hp := strings.Split(s.Host, ":")
|
||||||
|
|
||||||
|
// Set up authentication information.
|
||||||
|
auth := s.getSMTPAuth(hp[0])
|
||||||
|
|
||||||
|
// Connect to the server, authenticate, set the sender and recipient,
|
||||||
|
// and send the email all in one step.
|
||||||
|
contentType := "Content-Type: text/plain" + "; charset=UTF-8"
|
||||||
|
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
|
||||||
|
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", when.Format("2006-01-02 15:04:05")) + msg)
|
||||||
|
|
||||||
|
return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (s *SMTPWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (s *SMTPWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterMail, newSMTPWriter)
|
||||||
|
}
|
27
pkg/logs/smtp_test.go
Normal file
27
pkg/logs/smtp_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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 logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSmtp(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||||
|
log.Critical("sendmail critical")
|
||||||
|
time.Sleep(time.Second * 30)
|
||||||
|
}
|
99
pkg/metric/prometheus.go
Normal file
99
pkg/metric/prometheus.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// Copyright 2020 astaxie
|
||||||
|
//
|
||||||
|
// 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 metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrometheusMiddleWare(next http.Handler) http.Handler {
|
||||||
|
summaryVec := prometheus.NewSummaryVec(prometheus.SummaryOpts{
|
||||||
|
Name: "beego",
|
||||||
|
Subsystem: "http_request",
|
||||||
|
ConstLabels: map[string]string{
|
||||||
|
"server": beego.BConfig.ServerName,
|
||||||
|
"env": beego.BConfig.RunMode,
|
||||||
|
"appname": beego.BConfig.AppName,
|
||||||
|
},
|
||||||
|
Help: "The statics info for http request",
|
||||||
|
}, []string{"pattern", "method", "status", "duration"})
|
||||||
|
|
||||||
|
prometheus.MustRegister(summaryVec)
|
||||||
|
|
||||||
|
registerBuildInfo()
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(writer http.ResponseWriter, q *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
next.ServeHTTP(writer, q)
|
||||||
|
end := time.Now()
|
||||||
|
go report(end.Sub(start), writer, q, summaryVec)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerBuildInfo() {
|
||||||
|
buildInfo := prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "beego",
|
||||||
|
Subsystem: "build_info",
|
||||||
|
Help: "The building information",
|
||||||
|
ConstLabels: map[string]string{
|
||||||
|
"appname": beego.BConfig.AppName,
|
||||||
|
"build_version": beego.BuildVersion,
|
||||||
|
"build_revision": beego.BuildGitRevision,
|
||||||
|
"build_status": beego.BuildStatus,
|
||||||
|
"build_tag": beego.BuildTag,
|
||||||
|
"build_time": strings.Replace(beego.BuildTime, "--", " ", 1),
|
||||||
|
"go_version": beego.GoVersion,
|
||||||
|
"git_branch": beego.GitBranch,
|
||||||
|
"start_time": time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
},
|
||||||
|
}, []string{})
|
||||||
|
|
||||||
|
prometheus.MustRegister(buildInfo)
|
||||||
|
buildInfo.WithLabelValues().Set(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func report(dur time.Duration, writer http.ResponseWriter, q *http.Request, vec *prometheus.SummaryVec) {
|
||||||
|
ctrl := beego.BeeApp.Handlers
|
||||||
|
ctx := ctrl.GetContext()
|
||||||
|
ctx.Reset(writer, q)
|
||||||
|
defer ctrl.GiveBackContext(ctx)
|
||||||
|
|
||||||
|
// We cannot read the status code from q.Response.StatusCode
|
||||||
|
// since the http server does not set q.Response. So q.Response is nil
|
||||||
|
// Thus, we use reflection to read the status from writer whose concrete type is http.response
|
||||||
|
responseVal := reflect.ValueOf(writer).Elem()
|
||||||
|
field := responseVal.FieldByName("status")
|
||||||
|
status := -1
|
||||||
|
if field.IsValid() && field.Kind() == reflect.Int {
|
||||||
|
status = int(field.Int())
|
||||||
|
}
|
||||||
|
ptn := "UNKNOWN"
|
||||||
|
if rt, found := ctrl.FindRouter(ctx); found {
|
||||||
|
ptn = rt.GetPattern()
|
||||||
|
} else {
|
||||||
|
logs.Warn("we can not find the router info for this request, so request will be recorded as UNKNOWN: " + q.URL.String())
|
||||||
|
}
|
||||||
|
ms := dur / time.Millisecond
|
||||||
|
vec.WithLabelValues(ptn, q.Method, strconv.Itoa(status), strconv.Itoa(int(ms))).Observe(float64(ms))
|
||||||
|
}
|
42
pkg/metric/prometheus_test.go
Normal file
42
pkg/metric/prometheus_test.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2020 astaxie
|
||||||
|
//
|
||||||
|
// 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 metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrometheusMiddleWare(t *testing.T) {
|
||||||
|
middleware := PrometheusMiddleWare(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
|
||||||
|
writer := &context.Response{}
|
||||||
|
request := &http.Request{
|
||||||
|
URL: &url.URL{
|
||||||
|
Host: "localhost",
|
||||||
|
RawPath: "/a/b/c",
|
||||||
|
},
|
||||||
|
Method: "POST",
|
||||||
|
}
|
||||||
|
vec := prometheus.NewSummaryVec(prometheus.SummaryOpts{}, []string{"pattern", "method", "status", "duration"})
|
||||||
|
|
||||||
|
report(time.Second, writer, request, vec)
|
||||||
|
middleware.ServeHTTP(writer, request)
|
||||||
|
}
|
395
pkg/migration/ddl.go
Normal file
395
pkg/migration/ddl.go
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Index struct defines the structure of Index Columns
|
||||||
|
type Index struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unique struct defines a single unique key combination
|
||||||
|
type Unique struct {
|
||||||
|
Definition string
|
||||||
|
Columns []*Column
|
||||||
|
}
|
||||||
|
|
||||||
|
//Column struct defines a single column of a table
|
||||||
|
type Column struct {
|
||||||
|
Name string
|
||||||
|
Inc string
|
||||||
|
Null string
|
||||||
|
Default string
|
||||||
|
Unsign string
|
||||||
|
DataType string
|
||||||
|
remove bool
|
||||||
|
Modify bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foreign struct defines a single foreign relationship
|
||||||
|
type Foreign struct {
|
||||||
|
ForeignTable string
|
||||||
|
ForeignColumn string
|
||||||
|
OnDelete string
|
||||||
|
OnUpdate string
|
||||||
|
Column
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenameColumn struct allows renaming of columns
|
||||||
|
type RenameColumn struct {
|
||||||
|
OldName string
|
||||||
|
OldNull string
|
||||||
|
OldDefault string
|
||||||
|
OldUnsign string
|
||||||
|
OldDataType string
|
||||||
|
NewName string
|
||||||
|
Column
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTable creates the table on system
|
||||||
|
func (m *Migration) CreateTable(tablename, engine, charset string, p ...func()) {
|
||||||
|
m.TableName = tablename
|
||||||
|
m.Engine = engine
|
||||||
|
m.Charset = charset
|
||||||
|
m.ModifyType = "create"
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlterTable set the ModifyType to alter
|
||||||
|
func (m *Migration) AlterTable(tablename string) {
|
||||||
|
m.TableName = tablename
|
||||||
|
m.ModifyType = "alter"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCol creates a new standard column and attaches it to m struct
|
||||||
|
func (m *Migration) NewCol(name string) *Column {
|
||||||
|
col := &Column{Name: name}
|
||||||
|
m.AddColumns(col)
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
//PriCol creates a new primary column and attaches it to m struct
|
||||||
|
func (m *Migration) PriCol(name string) *Column {
|
||||||
|
col := &Column{Name: name}
|
||||||
|
m.AddColumns(col)
|
||||||
|
m.AddPrimary(col)
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
//UniCol creates / appends columns to specified unique key and attaches it to m struct
|
||||||
|
func (m *Migration) UniCol(uni, name string) *Column {
|
||||||
|
col := &Column{Name: name}
|
||||||
|
m.AddColumns(col)
|
||||||
|
|
||||||
|
uniqueOriginal := &Unique{}
|
||||||
|
|
||||||
|
for _, unique := range m.Uniques {
|
||||||
|
if unique.Definition == uni {
|
||||||
|
unique.AddColumnsToUnique(col)
|
||||||
|
uniqueOriginal = unique
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if uniqueOriginal.Definition == "" {
|
||||||
|
unique := &Unique{Definition: uni}
|
||||||
|
unique.AddColumnsToUnique(col)
|
||||||
|
m.AddUnique(unique)
|
||||||
|
}
|
||||||
|
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
//ForeignCol creates a new foreign column and returns the instance of column
|
||||||
|
func (m *Migration) ForeignCol(colname, foreigncol, foreigntable string) (foreign *Foreign) {
|
||||||
|
|
||||||
|
foreign = &Foreign{ForeignColumn: foreigncol, ForeignTable: foreigntable}
|
||||||
|
foreign.Name = colname
|
||||||
|
m.AddForeign(foreign)
|
||||||
|
return foreign
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOnDelete sets the on delete of foreign
|
||||||
|
func (foreign *Foreign) SetOnDelete(del string) *Foreign {
|
||||||
|
foreign.OnDelete = "ON DELETE" + del
|
||||||
|
return foreign
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOnUpdate sets the on update of foreign
|
||||||
|
func (foreign *Foreign) SetOnUpdate(update string) *Foreign {
|
||||||
|
foreign.OnUpdate = "ON UPDATE" + update
|
||||||
|
return foreign
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove marks the columns to be removed.
|
||||||
|
//it allows reverse m to create the column.
|
||||||
|
func (c *Column) Remove() {
|
||||||
|
c.remove = true
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetAuto enables auto_increment of column (can be used once)
|
||||||
|
func (c *Column) SetAuto(inc bool) *Column {
|
||||||
|
if inc {
|
||||||
|
c.Inc = "auto_increment"
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetNullable sets the column to be null
|
||||||
|
func (c *Column) SetNullable(null bool) *Column {
|
||||||
|
if null {
|
||||||
|
c.Null = ""
|
||||||
|
|
||||||
|
} else {
|
||||||
|
c.Null = "NOT NULL"
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetDefault sets the default value, prepend with "DEFAULT "
|
||||||
|
func (c *Column) SetDefault(def string) *Column {
|
||||||
|
c.Default = "DEFAULT " + def
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetUnsigned sets the column to be unsigned int
|
||||||
|
func (c *Column) SetUnsigned(unsign bool) *Column {
|
||||||
|
if unsign {
|
||||||
|
c.Unsign = "UNSIGNED"
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetDataType sets the dataType of the column
|
||||||
|
func (c *Column) SetDataType(dataType string) *Column {
|
||||||
|
c.DataType = dataType
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOldNullable allows reverting to previous nullable on reverse ms
|
||||||
|
func (c *RenameColumn) SetOldNullable(null bool) *RenameColumn {
|
||||||
|
if null {
|
||||||
|
c.OldNull = ""
|
||||||
|
|
||||||
|
} else {
|
||||||
|
c.OldNull = "NOT NULL"
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOldDefault allows reverting to previous default on reverse ms
|
||||||
|
func (c *RenameColumn) SetOldDefault(def string) *RenameColumn {
|
||||||
|
c.OldDefault = def
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOldUnsigned allows reverting to previous unsgined on reverse ms
|
||||||
|
func (c *RenameColumn) SetOldUnsigned(unsign bool) *RenameColumn {
|
||||||
|
if unsign {
|
||||||
|
c.OldUnsign = "UNSIGNED"
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetOldDataType allows reverting to previous datatype on reverse ms
|
||||||
|
func (c *RenameColumn) SetOldDataType(dataType string) *RenameColumn {
|
||||||
|
c.OldDataType = dataType
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetPrimary adds the columns to the primary key (can only be used any number of times in only one m)
|
||||||
|
func (c *Column) SetPrimary(m *Migration) *Column {
|
||||||
|
m.Primary = append(m.Primary, c)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddColumnsToUnique adds the columns to Unique Struct
|
||||||
|
func (unique *Unique) AddColumnsToUnique(columns ...*Column) *Unique {
|
||||||
|
|
||||||
|
unique.Columns = append(unique.Columns, columns...)
|
||||||
|
|
||||||
|
return unique
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddColumns adds columns to m struct
|
||||||
|
func (m *Migration) AddColumns(columns ...*Column) *Migration {
|
||||||
|
|
||||||
|
m.Columns = append(m.Columns, columns...)
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddPrimary adds the column to primary in m struct
|
||||||
|
func (m *Migration) AddPrimary(primary *Column) *Migration {
|
||||||
|
m.Primary = append(m.Primary, primary)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddUnique adds the column to unique in m struct
|
||||||
|
func (m *Migration) AddUnique(unique *Unique) *Migration {
|
||||||
|
m.Uniques = append(m.Uniques, unique)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddForeign adds the column to foreign in m struct
|
||||||
|
func (m *Migration) AddForeign(foreign *Foreign) *Migration {
|
||||||
|
m.Foreigns = append(m.Foreigns, foreign)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddIndex adds the column to index in m struct
|
||||||
|
func (m *Migration) AddIndex(index *Index) *Migration {
|
||||||
|
m.Indexes = append(m.Indexes, index)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//RenameColumn allows renaming of columns
|
||||||
|
func (m *Migration) RenameColumn(from, to string) *RenameColumn {
|
||||||
|
rename := &RenameColumn{OldName: from, NewName: to}
|
||||||
|
m.Renames = append(m.Renames, rename)
|
||||||
|
return rename
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetSQL returns the generated sql depending on ModifyType
|
||||||
|
func (m *Migration) GetSQL() (sql string) {
|
||||||
|
sql = ""
|
||||||
|
switch m.ModifyType {
|
||||||
|
case "create":
|
||||||
|
{
|
||||||
|
sql += fmt.Sprintf("CREATE TABLE `%s` (", m.TableName)
|
||||||
|
for index, column := range m.Columns {
|
||||||
|
sql += fmt.Sprintf("\n `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
|
||||||
|
if len(m.Columns) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Primary) > 0 {
|
||||||
|
sql += fmt.Sprintf(",\n PRIMARY KEY( ")
|
||||||
|
}
|
||||||
|
for index, column := range m.Primary {
|
||||||
|
sql += fmt.Sprintf(" `%s`", column.Name)
|
||||||
|
if len(m.Primary) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if len(m.Primary) > 0 {
|
||||||
|
sql += fmt.Sprintf(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, unique := range m.Uniques {
|
||||||
|
sql += fmt.Sprintf(",\n UNIQUE KEY `%s`( ", unique.Definition)
|
||||||
|
for index, column := range unique.Columns {
|
||||||
|
sql += fmt.Sprintf(" `%s`", column.Name)
|
||||||
|
if len(unique.Columns) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sql += fmt.Sprintf(")")
|
||||||
|
}
|
||||||
|
for _, foreign := range m.Foreigns {
|
||||||
|
sql += fmt.Sprintf(",\n `%s` %s %s %s %s %s", foreign.Name, foreign.DataType, foreign.Unsign, foreign.Null, foreign.Inc, foreign.Default)
|
||||||
|
sql += fmt.Sprintf(",\n KEY `%s_%s_foreign`(`%s`),", m.TableName, foreign.Column.Name, foreign.Column.Name)
|
||||||
|
sql += fmt.Sprintf("\n CONSTRAINT `%s_%s_foreign` FOREIGN KEY (`%s`) REFERENCES `%s` (`%s`) %s %s", m.TableName, foreign.Column.Name, foreign.Column.Name, foreign.ForeignTable, foreign.ForeignColumn, foreign.OnDelete, foreign.OnUpdate)
|
||||||
|
|
||||||
|
}
|
||||||
|
sql += fmt.Sprintf(")ENGINE=%s DEFAULT CHARSET=%s;", m.Engine, m.Charset)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "alter":
|
||||||
|
{
|
||||||
|
sql += fmt.Sprintf("ALTER TABLE `%s` ", m.TableName)
|
||||||
|
for index, column := range m.Columns {
|
||||||
|
if !column.remove {
|
||||||
|
logs.Info("col")
|
||||||
|
sql += fmt.Sprintf("\n ADD `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
|
||||||
|
} else {
|
||||||
|
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Columns) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for index, column := range m.Renames {
|
||||||
|
sql += fmt.Sprintf("CHANGE COLUMN `%s` `%s` %s %s %s %s %s", column.OldName, column.NewName, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
|
||||||
|
if len(m.Renames) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, foreign := range m.Foreigns {
|
||||||
|
sql += fmt.Sprintf("ADD `%s` %s %s %s %s %s", foreign.Name, foreign.DataType, foreign.Unsign, foreign.Null, foreign.Inc, foreign.Default)
|
||||||
|
sql += fmt.Sprintf(",\n ADD KEY `%s_%s_foreign`(`%s`)", m.TableName, foreign.Column.Name, foreign.Column.Name)
|
||||||
|
sql += fmt.Sprintf(",\n ADD CONSTRAINT `%s_%s_foreign` FOREIGN KEY (`%s`) REFERENCES `%s` (`%s`) %s %s", m.TableName, foreign.Column.Name, foreign.Column.Name, foreign.ForeignTable, foreign.ForeignColumn, foreign.OnDelete, foreign.OnUpdate)
|
||||||
|
if len(m.Foreigns) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sql += ";"
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "reverse":
|
||||||
|
{
|
||||||
|
|
||||||
|
sql += fmt.Sprintf("ALTER TABLE `%s`", m.TableName)
|
||||||
|
for index, column := range m.Columns {
|
||||||
|
if column.remove {
|
||||||
|
sql += fmt.Sprintf("\n ADD `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
|
||||||
|
} else {
|
||||||
|
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
|
||||||
|
}
|
||||||
|
if len(m.Columns) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Primary) > 0 {
|
||||||
|
sql += fmt.Sprintf("\n DROP PRIMARY KEY,")
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, unique := range m.Uniques {
|
||||||
|
sql += fmt.Sprintf("\n DROP KEY `%s`", unique.Definition)
|
||||||
|
if len(m.Uniques) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
for index, column := range m.Renames {
|
||||||
|
sql += fmt.Sprintf("\n CHANGE COLUMN `%s` `%s` %s %s %s %s", column.NewName, column.OldName, column.OldDataType, column.OldUnsign, column.OldNull, column.OldDefault)
|
||||||
|
if len(m.Renames) > index+1 {
|
||||||
|
sql += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, foreign := range m.Foreigns {
|
||||||
|
sql += fmt.Sprintf("\n DROP KEY `%s_%s_foreign`", m.TableName, foreign.Column.Name)
|
||||||
|
sql += fmt.Sprintf(",\n DROP FOREIGN KEY `%s_%s_foreign`", m.TableName, foreign.Column.Name)
|
||||||
|
sql += fmt.Sprintf(",\n DROP COLUMN `%s`", foreign.Name)
|
||||||
|
}
|
||||||
|
sql += ";"
|
||||||
|
}
|
||||||
|
case "delete":
|
||||||
|
{
|
||||||
|
sql += fmt.Sprintf("DROP TABLE IF EXISTS `%s`;", m.TableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
32
pkg/migration/doc.go
Normal file
32
pkg/migration/doc.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Package migration enables you to generate migrations back and forth. It generates both migrations.
|
||||||
|
//
|
||||||
|
// //Creates a table
|
||||||
|
// m.CreateTable("tablename","InnoDB","utf8");
|
||||||
|
//
|
||||||
|
// //Alter a table
|
||||||
|
// m.AlterTable("tablename")
|
||||||
|
//
|
||||||
|
// Standard Column Methods
|
||||||
|
// * SetDataType
|
||||||
|
// * SetNullable
|
||||||
|
// * SetDefault
|
||||||
|
// * SetUnsigned (use only on integer types unless produces error)
|
||||||
|
//
|
||||||
|
// //Sets a primary column, multiple calls allowed, standard column methods available
|
||||||
|
// m.PriCol("id").SetAuto(true).SetNullable(false).SetDataType("INT(10)").SetUnsigned(true)
|
||||||
|
//
|
||||||
|
// //UniCol Can be used multiple times, allows standard Column methods. Use same "index" string to add to same index
|
||||||
|
// m.UniCol("index","column")
|
||||||
|
//
|
||||||
|
// //Standard Column Initialisation, can call .Remove() after NewCol("") on alter to remove
|
||||||
|
// m.NewCol("name").SetDataType("VARCHAR(255) COLLATE utf8_unicode_ci").SetNullable(false)
|
||||||
|
// m.NewCol("value").SetDataType("DOUBLE(8,2)").SetNullable(false)
|
||||||
|
//
|
||||||
|
// //Rename Columns , only use with Alter table, doesn't works with Create, prefix standard column methods with "Old" to
|
||||||
|
// //create a true reversible migration eg: SetOldDataType("DOUBLE(12,3)")
|
||||||
|
// m.RenameColumn("from","to")...
|
||||||
|
//
|
||||||
|
// //Foreign Columns, single columns are only supported, SetOnDelete & SetOnUpdate are available, call appropriately.
|
||||||
|
// //Supports standard column methods, automatic reverse.
|
||||||
|
// m.ForeignCol("local_col","foreign_col","foreign_table")
|
||||||
|
package migration
|
330
pkg/migration/migration.go
Normal file
330
pkg/migration/migration.go
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
// 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 is used for migration
|
||||||
|
//
|
||||||
|
// The table structure is as follow:
|
||||||
|
//
|
||||||
|
// CREATE TABLE `migrations` (
|
||||||
|
// `id_migration` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key',
|
||||||
|
// `name` varchar(255) DEFAULT NULL COMMENT 'migration name, unique',
|
||||||
|
// `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'date migrated or rolled back',
|
||||||
|
// `statements` longtext COMMENT 'SQL statements for this migration',
|
||||||
|
// `rollback_statements` longtext,
|
||||||
|
// `status` enum('update','rollback') DEFAULT NULL COMMENT 'update indicates it is a normal migration while rollback means this migration is rolled back',
|
||||||
|
// PRIMARY KEY (`id_migration`)
|
||||||
|
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/astaxie/beego/orm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// const the data format for the bee generate migration datatype
|
||||||
|
const (
|
||||||
|
DateFormat = "20060102_150405"
|
||||||
|
DBDateFormat = "2006-01-02 15:04:05"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Migrationer is an interface for all Migration struct
|
||||||
|
type Migrationer interface {
|
||||||
|
Up()
|
||||||
|
Down()
|
||||||
|
Reset()
|
||||||
|
Exec(name, status string) error
|
||||||
|
GetCreated() int64
|
||||||
|
}
|
||||||
|
|
||||||
|
//Migration defines the migrations by either SQL or DDL
|
||||||
|
type Migration struct {
|
||||||
|
sqls []string
|
||||||
|
Created string
|
||||||
|
TableName string
|
||||||
|
Engine string
|
||||||
|
Charset string
|
||||||
|
ModifyType string
|
||||||
|
Columns []*Column
|
||||||
|
Indexes []*Index
|
||||||
|
Primary []*Column
|
||||||
|
Uniques []*Unique
|
||||||
|
Foreigns []*Foreign
|
||||||
|
Renames []*RenameColumn
|
||||||
|
RemoveColumns []*Column
|
||||||
|
RemoveIndexes []*Index
|
||||||
|
RemoveUniques []*Unique
|
||||||
|
RemoveForeigns []*Foreign
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
migrationMap map[string]Migrationer
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
migrationMap = make(map[string]Migrationer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Up implement in the Inheritance struct for upgrade
|
||||||
|
func (m *Migration) Up() {
|
||||||
|
|
||||||
|
switch m.ModifyType {
|
||||||
|
case "reverse":
|
||||||
|
m.ModifyType = "alter"
|
||||||
|
case "delete":
|
||||||
|
m.ModifyType = "create"
|
||||||
|
}
|
||||||
|
m.sqls = append(m.sqls, m.GetSQL())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down implement in the Inheritance struct for down
|
||||||
|
func (m *Migration) Down() {
|
||||||
|
|
||||||
|
switch m.ModifyType {
|
||||||
|
case "alter":
|
||||||
|
m.ModifyType = "reverse"
|
||||||
|
case "create":
|
||||||
|
m.ModifyType = "delete"
|
||||||
|
}
|
||||||
|
m.sqls = append(m.sqls, m.GetSQL())
|
||||||
|
}
|
||||||
|
|
||||||
|
//Migrate adds the SQL to the execution list
|
||||||
|
func (m *Migration) Migrate(migrationType string) {
|
||||||
|
m.ModifyType = migrationType
|
||||||
|
m.sqls = append(m.sqls, m.GetSQL())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQL add sql want to execute
|
||||||
|
func (m *Migration) SQL(sql string) {
|
||||||
|
m.sqls = append(m.sqls, sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the sqls
|
||||||
|
func (m *Migration) Reset() {
|
||||||
|
m.sqls = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec execute the sql already add in the sql
|
||||||
|
func (m *Migration) Exec(name, status string) error {
|
||||||
|
o := orm.NewOrm()
|
||||||
|
for _, s := range m.sqls {
|
||||||
|
logs.Info("exec sql:", s)
|
||||||
|
r := o.Raw(s)
|
||||||
|
_, err := r.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m.addOrUpdateRecord(name, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Migration) addOrUpdateRecord(name, status string) error {
|
||||||
|
o := orm.NewOrm()
|
||||||
|
if status == "down" {
|
||||||
|
status = "rollback"
|
||||||
|
p, err := o.Raw("update migrations set status = ?, rollback_statements = ?, created_at = ? where name = ?").Prepare()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err = p.Exec(status, strings.Join(m.sqls, "; "), time.Now().Format(DBDateFormat), name)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
status = "update"
|
||||||
|
p, err := o.Raw("insert into migrations(name, created_at, statements, status) values(?,?,?,?)").Prepare()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = p.Exec(name, time.Now().Format(DBDateFormat), strings.Join(m.sqls, "; "), status)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCreated get the unixtime from the Created
|
||||||
|
func (m *Migration) GetCreated() int64 {
|
||||||
|
t, err := time.Parse(DateFormat, m.Created)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return t.Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register register the Migration in the map
|
||||||
|
func Register(name string, m Migrationer) error {
|
||||||
|
if _, ok := migrationMap[name]; ok {
|
||||||
|
return errors.New("already exist name:" + name)
|
||||||
|
}
|
||||||
|
migrationMap[name] = m
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade upgrade the migration from lasttime
|
||||||
|
func Upgrade(lasttime int64) error {
|
||||||
|
sm := sortMap(migrationMap)
|
||||||
|
i := 0
|
||||||
|
migs, _ := getAllMigrations()
|
||||||
|
for _, v := range sm {
|
||||||
|
if _, ok := migs[v.name]; !ok {
|
||||||
|
logs.Info("start upgrade", v.name)
|
||||||
|
v.m.Reset()
|
||||||
|
v.m.Up()
|
||||||
|
err := v.m.Exec(v.name, "up")
|
||||||
|
if err != nil {
|
||||||
|
logs.Error("execute error:", err)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logs.Info("end upgrade:", v.name)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logs.Info("total success upgrade:", i, " migration")
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback rollback the migration by the name
|
||||||
|
func Rollback(name string) error {
|
||||||
|
if v, ok := migrationMap[name]; ok {
|
||||||
|
logs.Info("start rollback")
|
||||||
|
v.Reset()
|
||||||
|
v.Down()
|
||||||
|
err := v.Exec(name, "down")
|
||||||
|
if err != nil {
|
||||||
|
logs.Error("execute error:", err)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logs.Info("end rollback")
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logs.Error("not exist the migrationMap name:" + name)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
return errors.New("not exist the migrationMap name:" + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset reset all migration
|
||||||
|
// run all migration's down function
|
||||||
|
func Reset() error {
|
||||||
|
sm := sortMap(migrationMap)
|
||||||
|
i := 0
|
||||||
|
for j := len(sm) - 1; j >= 0; j-- {
|
||||||
|
v := sm[j]
|
||||||
|
if isRollBack(v.name) {
|
||||||
|
logs.Info("skip the", v.name)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logs.Info("start reset:", v.name)
|
||||||
|
v.m.Reset()
|
||||||
|
v.m.Down()
|
||||||
|
err := v.m.Exec(v.name, "down")
|
||||||
|
if err != nil {
|
||||||
|
logs.Error("execute error:", err)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
logs.Info("end reset:", v.name)
|
||||||
|
}
|
||||||
|
logs.Info("total success reset:", i, " migration")
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh first Reset, then Upgrade
|
||||||
|
func Refresh() error {
|
||||||
|
err := Reset()
|
||||||
|
if err != nil {
|
||||||
|
logs.Error("execute error:", err)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = Upgrade(0)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type dataSlice []data
|
||||||
|
|
||||||
|
type data struct {
|
||||||
|
created int64
|
||||||
|
name string
|
||||||
|
m Migrationer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len is part of sort.Interface.
|
||||||
|
func (d dataSlice) Len() int {
|
||||||
|
return len(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap is part of sort.Interface.
|
||||||
|
func (d dataSlice) Swap(i, j int) {
|
||||||
|
d[i], d[j] = d[j], d[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less is part of sort.Interface. We use count as the value to sort by
|
||||||
|
func (d dataSlice) Less(i, j int) bool {
|
||||||
|
return d[i].created < d[j].created
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortMap(m map[string]Migrationer) dataSlice {
|
||||||
|
s := make(dataSlice, 0, len(m))
|
||||||
|
for k, v := range m {
|
||||||
|
d := data{}
|
||||||
|
d.created = v.GetCreated()
|
||||||
|
d.name = k
|
||||||
|
d.m = v
|
||||||
|
s = append(s, d)
|
||||||
|
}
|
||||||
|
sort.Sort(s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRollBack(name string) bool {
|
||||||
|
o := orm.NewOrm()
|
||||||
|
var maps []orm.Params
|
||||||
|
num, err := o.Raw("select * from migrations where `name` = ? order by id_migration desc", name).Values(&maps)
|
||||||
|
if err != nil {
|
||||||
|
logs.Info("get name has error", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if num <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if maps[0]["status"] == "rollback" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func getAllMigrations() (map[string]string, error) {
|
||||||
|
o := orm.NewOrm()
|
||||||
|
var maps []orm.Params
|
||||||
|
migs := make(map[string]string)
|
||||||
|
num, err := o.Raw("select * from migrations order by id_migration desc").Values(&maps)
|
||||||
|
if err != nil {
|
||||||
|
logs.Info("get name has error", err)
|
||||||
|
return migs, err
|
||||||
|
}
|
||||||
|
if num > 0 {
|
||||||
|
for _, v := range maps {
|
||||||
|
name := v["name"].(string)
|
||||||
|
migs[name] = v["status"].(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return migs, nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user