mirror of
https://github.com/astaxie/beego.git
synced 2025-07-12 16:21:01 +00:00
Compare commits
175 Commits
Author | SHA1 | Date | |
---|---|---|---|
f7f390dfec | |||
8f7246e17b | |||
c8f6e0f156 | |||
0207caab6f | |||
d629c1d3d0 | |||
817650aa33 | |||
ba1232dfaf | |||
64d4f6518b | |||
9f05db8475 | |||
b275d7c6f5 | |||
73770fbe22 | |||
fc11169ee3 | |||
b54589fa9d | |||
2af0c569a5 | |||
27b7a8f743 | |||
c143a6ec19 | |||
e619d83990 | |||
27b452cd95 | |||
b776e43962 | |||
cb89cd577d | |||
6b777f0c5e | |||
491238ce7d | |||
d9bb1a3592 | |||
9c6775c22c | |||
4d70b22f96 | |||
d943d16d52 | |||
bbb6f31f16 | |||
364cacf659 | |||
21586586ba | |||
e1d7bc8826 | |||
499ee09d4b | |||
970f0b460c | |||
9280683935 | |||
a58c8180e8 | |||
b9852df51c | |||
8e71d31dbe | |||
db06e954b5 | |||
3abd01799d | |||
ae37689314 | |||
40974365e6 | |||
edbaa080f1 | |||
d56491ab3a | |||
9fd7acf663 | |||
2dca48f26e | |||
4138fe0217 | |||
245762f7d9 | |||
18bdec951d | |||
9252301fa0 | |||
1053b63bbc | |||
3bd6caae0a | |||
d8734cf58d | |||
51161361db | |||
6da0cdb9e2 | |||
9f070c622b | |||
738e22e389 | |||
69fc22f0df | |||
4cd7177ece | |||
8e618192c2 | |||
3415a5b091 | |||
dfe055c47c | |||
74b22649c8 | |||
4255630564 | |||
7e3b5e5307 | |||
0222b8d693 | |||
23268b788a | |||
ee4fd60e4d | |||
4414659df4 | |||
68ccd8e5a4 | |||
6f802b0a05 | |||
743628a946 | |||
d90ce15707 | |||
23457ed2a0 | |||
d7791ba837 | |||
676595213f | |||
d446b5b011 | |||
2ba12ad1e1 | |||
8cc57e2fc8 | |||
322b208566 | |||
fd610d6777 | |||
1148359570 | |||
55b1d6a897 | |||
c4c9a50c42 | |||
a311d712a5 | |||
bb5351bb9f | |||
26130a5df6 | |||
2c9363d29b | |||
91886a4547 | |||
a7e60c93dc | |||
5b1705b2d6 | |||
34940d00c0 | |||
1a6ea693b5 | |||
2e51c124f1 | |||
1d8afdc9c9 | |||
1592e9c04d | |||
bf6b0d3e1f | |||
af71289c25 | |||
b1efae6ff8 | |||
92f3de4027 | |||
185089299c | |||
59c1e74e13 | |||
9bb9855153 | |||
519602a553 | |||
740a526105 | |||
98289cd8de | |||
56e2143630 | |||
c0cfb5277c | |||
b29700c3c3 | |||
47be2fadb5 | |||
1d72629334 | |||
b4880c5e1d | |||
c0bb5b9237 | |||
1e1068e81c | |||
6c3e274b6e | |||
f56bdb6284 | |||
d8fa118727 | |||
0afd04ec6f | |||
973306b28d | |||
da8c3c3910 | |||
0c1bb6409a | |||
cddb4fdb60 | |||
e1d20aea5d | |||
4498a02c15 | |||
5534258e22 | |||
73650e1f2b | |||
d0e7dd686b | |||
dc58ec8316 | |||
6d72fc63ab | |||
dd4afac5dc | |||
e229a4762f | |||
ecb27f34e6 | |||
d55997e520 | |||
44c8534477 | |||
44a39a6b3e | |||
cc5ca458ab | |||
6f6b412709 | |||
642a69de02 | |||
466f3c49c1 | |||
d1c9cb2281 | |||
9c9ffa202a | |||
56dfe418dd | |||
7caeb91f9b | |||
47848fa77b | |||
e0e8b98622 | |||
5c84ada389 | |||
ac6203b81b | |||
5e1e618d0f | |||
a5124a1d45 | |||
e675594e46 | |||
d02e32fa51 | |||
8a82e25e85 | |||
49c0f8906f | |||
9261c80509 | |||
217e24815b | |||
840fd3b64f | |||
eedaea2fea | |||
a002f78443 | |||
cdf9ff401f | |||
caa260f053 | |||
5fa55ca2d3 | |||
260b5b1951 | |||
54ae4bc25b | |||
162bee1b43 | |||
533b00ae56 | |||
d3cdebbee2 | |||
06b5c7f644 | |||
185ee872c4 | |||
74e0af4a9a | |||
8aa9455900 | |||
2d26f7df2f | |||
3d6408cfc2 | |||
223f57bb4c | |||
c4aa33fb1b | |||
20cc5b261e | |||
7ec2a077d9 | |||
9f70561f21 |
13
admin.go
13
admin.go
@ -19,9 +19,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/grace"
|
||||||
"github.com/astaxie/beego/toolbox"
|
"github.com/astaxie/beego/toolbox"
|
||||||
"github.com/astaxie/beego/utils"
|
"github.com/astaxie/beego/utils"
|
||||||
)
|
)
|
||||||
@ -333,7 +335,6 @@ func profIndex(rw http.ResponseWriter, r *http.Request) {
|
|||||||
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
|
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
|
||||||
}
|
}
|
||||||
tmpl.Execute(rw, data)
|
tmpl.Execute(rw, data)
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,8 +460,14 @@ func (admin *adminApp) Run() {
|
|||||||
http.Handle(p, f)
|
http.Handle(p, f)
|
||||||
}
|
}
|
||||||
BeeLogger.Info("Admin server Running on %s", addr)
|
BeeLogger.Info("Admin server Running on %s", addr)
|
||||||
err := http.ListenAndServe(addr, nil)
|
|
||||||
|
var err error
|
||||||
|
if Graceful {
|
||||||
|
err = grace.ListenAndServe(addr, nil)
|
||||||
|
} else {
|
||||||
|
err = http.ListenAndServe(addr, nil)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
BeeLogger.Critical("Admin ListenAndServe: ", err)
|
BeeLogger.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
40
app.go
40
app.go
@ -22,6 +22,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/grace"
|
||||||
"github.com/astaxie/beego/utils"
|
"github.com/astaxie/beego/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -75,6 +76,44 @@ func (app *App) Run() {
|
|||||||
}
|
}
|
||||||
err = fcgi.Serve(l, app.Handlers)
|
err = fcgi.Serve(l, app.Handlers)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if Graceful {
|
||||||
|
app.Server.Addr = addr
|
||||||
|
app.Server.Handler = app.Handlers
|
||||||
|
app.Server.ReadTimeout = time.Duration(HttpServerTimeOut) * time.Second
|
||||||
|
app.Server.WriteTimeout = time.Duration(HttpServerTimeOut) * time.Second
|
||||||
|
if EnableHttpTLS {
|
||||||
|
go func() {
|
||||||
|
time.Sleep(20 * time.Microsecond)
|
||||||
|
if HttpsPort != 0 {
|
||||||
|
addr = fmt.Sprintf("%s:%d", HttpAddr, HttpsPort)
|
||||||
|
app.Server.Addr = addr
|
||||||
|
}
|
||||||
|
server := grace.NewServer(addr, app.Handlers)
|
||||||
|
server.Server = app.Server
|
||||||
|
err := server.ListenAndServeTLS(HttpCertFile, HttpKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
BeeLogger.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if EnableHttpListen {
|
||||||
|
go func() {
|
||||||
|
server := grace.NewServer(addr, app.Handlers)
|
||||||
|
server.Server = app.Server
|
||||||
|
if ListenTCP4 && HttpAddr == "" {
|
||||||
|
server.Network = "tcp4"
|
||||||
|
}
|
||||||
|
err := server.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
BeeLogger.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
app.Server.Addr = addr
|
app.Server.Addr = addr
|
||||||
app.Server.Handler = app.Handlers
|
app.Server.Handler = app.Handlers
|
||||||
@ -128,5 +167,6 @@ func (app *App) Run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
<-endRunning
|
<-endRunning
|
||||||
}
|
}
|
||||||
|
4
beego.go
4
beego.go
@ -37,7 +37,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// beego web framework version.
|
// beego web framework version.
|
||||||
const VERSION = "1.4.3"
|
const VERSION = "1.5.0"
|
||||||
|
|
||||||
type hookfunc func() error //hook function to run
|
type hookfunc func() error //hook function to run
|
||||||
var hooks []hookfunc //hook function slice to store the hookfunc
|
var hooks []hookfunc //hook function slice to store the hookfunc
|
||||||
@ -336,7 +336,7 @@ func initBeforeHttpRun() {
|
|||||||
// this function is for test package init
|
// this function is for test package init
|
||||||
func TestBeegoInit(apppath string) {
|
func TestBeegoInit(apppath string) {
|
||||||
AppPath = apppath
|
AppPath = apppath
|
||||||
RunMode = "test"
|
os.Setenv("BEEGO_RUNMODE", "test")
|
||||||
AppConfigPath = filepath.Join(AppPath, "conf", "app.conf")
|
AppConfigPath = filepath.Join(AppPath, "conf", "app.conf")
|
||||||
err := ParseConfig()
|
err := ParseConfig()
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
2
cache/cache.go
vendored
2
cache/cache.go
vendored
@ -47,6 +47,8 @@ import (
|
|||||||
type Cache interface {
|
type Cache interface {
|
||||||
// get cached value by key.
|
// get cached value by key.
|
||||||
Get(key string) interface{}
|
Get(key string) interface{}
|
||||||
|
// GetMulti is a batch version of Get.
|
||||||
|
GetMulti(keys []string) []interface{}
|
||||||
// set cached value with key and expire time.
|
// set cached value with key and expire time.
|
||||||
Put(key string, val interface{}, timeout int64) error
|
Put(key string, val interface{}, timeout int64) error
|
||||||
// delete cached value by key.
|
// delete cached value by key.
|
||||||
|
51
cache/cache_test.go
vendored
51
cache/cache_test.go
vendored
@ -65,6 +65,35 @@ func TestCache(t *testing.T) {
|
|||||||
if bm.IsExist("astaxie") {
|
if bm.IsExist("astaxie") {
|
||||||
t.Error("delete err")
|
t.Error("delete err")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie", "author", 10); 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", 10); 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) {
|
func TestFileCache(t *testing.T) {
|
||||||
@ -102,6 +131,7 @@ func TestFileCache(t *testing.T) {
|
|||||||
if bm.IsExist("astaxie") {
|
if bm.IsExist("astaxie") {
|
||||||
t.Error("delete err")
|
t.Error("delete err")
|
||||||
}
|
}
|
||||||
|
|
||||||
//test string
|
//test string
|
||||||
if err = bm.Put("astaxie", "author", 10); err != nil {
|
if err = bm.Put("astaxie", "author", 10); err != nil {
|
||||||
t.Error("set Error", err)
|
t.Error("set Error", err)
|
||||||
@ -109,9 +139,28 @@ func TestFileCache(t *testing.T) {
|
|||||||
if !bm.IsExist("astaxie") {
|
if !bm.IsExist("astaxie") {
|
||||||
t.Error("check err")
|
t.Error("check err")
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := bm.Get("astaxie"); v.(string) != "author" {
|
if v := bm.Get("astaxie"); v.(string) != "author" {
|
||||||
t.Error("get err")
|
t.Error("get err")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie1", "author1", 10); 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")
|
os.RemoveAll("cache")
|
||||||
}
|
}
|
||||||
|
10
cache/file.go
vendored
10
cache/file.go
vendored
@ -132,6 +132,16 @@ func (fc *FileCache) Get(key string) interface{} {
|
|||||||
return to.Data
|
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.
|
// Put value into file cache.
|
||||||
// timeout means how long to keep this file, unit of ms.
|
// timeout means how long to keep this file, unit of ms.
|
||||||
// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever.
|
// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever.
|
||||||
|
26
cache/memcache/memcache.go
vendored
26
cache/memcache/memcache.go
vendored
@ -63,6 +63,32 @@ func (rc *MemcacheCache) Get(key string) interface{} {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get value from memcache.
|
||||||
|
func (rc *MemcacheCache) 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, string(v.Value))
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
} else {
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
rv = append(rv, err)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// put value to memcache. only support string.
|
// put value to memcache. only support string.
|
||||||
func (rc *MemcacheCache) Put(key string, val interface{}, timeout int64) error {
|
func (rc *MemcacheCache) Put(key string, val interface{}, timeout int64) error {
|
||||||
if rc.conn == nil {
|
if rc.conn == nil {
|
||||||
|
106
cache/memcache/memcache_test.go
vendored
Normal file
106
cache/memcache/memcache_test.go
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRedisCache(t *testing.T) {
|
||||||
|
bm, err := cache.NewCache("memcache", `{"conn": "127.0.0.1:11211"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
if err = bm.Put("astaxie", "1", 10); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if err = bm.Put("astaxie", "1", 10); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := strconv.Atoi(bm.Get("astaxie").(string)); 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(bm.Get("astaxie").(string)); 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(bm.Get("astaxie").(string)); 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", 10); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie").(string); v != "author" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie1", "author1", 10); 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test clear all
|
||||||
|
if err = bm.ClearAll(); err != nil {
|
||||||
|
t.Error("clear all err")
|
||||||
|
}
|
||||||
|
}
|
12
cache/memory.go
vendored
12
cache/memory.go
vendored
@ -64,6 +64,16 @@ func (bc *MemoryCache) Get(name string) interface{} {
|
|||||||
return nil
|
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.
|
// Put cache to memory.
|
||||||
// if expired is 0, it will be cleaned by next gc operation ( default gc clock is 1 minute).
|
// if expired is 0, it will be cleaned by next gc operation ( default gc clock is 1 minute).
|
||||||
func (bc *MemoryCache) Put(name string, value interface{}, expired int64) error {
|
func (bc *MemoryCache) Put(name string, value interface{}, expired int64) error {
|
||||||
@ -202,7 +212,7 @@ func (bc *MemoryCache) vaccuum() {
|
|||||||
if bc.items == nil {
|
if bc.items == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for name, _ := range bc.items {
|
for name := range bc.items {
|
||||||
bc.item_expired(name)
|
bc.item_expired(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
51
cache/redis/redis.go
vendored
51
cache/redis/redis.go
vendored
@ -51,6 +51,7 @@ type RedisCache struct {
|
|||||||
conninfo string
|
conninfo string
|
||||||
dbNum int
|
dbNum int
|
||||||
key string
|
key string
|
||||||
|
password string
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new redis cache with default collection name.
|
// create new redis cache with default collection name.
|
||||||
@ -74,6 +75,39 @@ func (rc *RedisCache) Get(key string) interface{} {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMulti get cache from redis.
|
||||||
|
func (rc *RedisCache) GetMulti(keys []string) []interface{} {
|
||||||
|
size := len(keys)
|
||||||
|
var rv []interface{}
|
||||||
|
c := rc.p.Get()
|
||||||
|
defer c.Close()
|
||||||
|
var err error
|
||||||
|
for _, key := range keys {
|
||||||
|
err = c.Send("GET", key)
|
||||||
|
if err != nil {
|
||||||
|
goto ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = c.Flush(); err != nil {
|
||||||
|
goto ERROR
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
if v, err := c.Receive(); err == nil {
|
||||||
|
rv = append(rv, v.([]byte))
|
||||||
|
} else {
|
||||||
|
rv = append(rv, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
ERROR:
|
||||||
|
rv = rv[0:0]
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
rv = append(rv, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
// put cache to redis.
|
// put cache to redis.
|
||||||
func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error {
|
func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error {
|
||||||
var err error
|
var err error
|
||||||
@ -149,16 +183,20 @@ func (rc *RedisCache) StartAndGC(config string) error {
|
|||||||
if _, ok := cf["key"]; !ok {
|
if _, ok := cf["key"]; !ok {
|
||||||
cf["key"] = DefaultKey
|
cf["key"] = DefaultKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := cf["conn"]; !ok {
|
if _, ok := cf["conn"]; !ok {
|
||||||
return errors.New("config has no conn key")
|
return errors.New("config has no conn key")
|
||||||
}
|
}
|
||||||
if _, ok := cf["dbNum"]; !ok {
|
if _, ok := cf["dbNum"]; !ok {
|
||||||
cf["dbNum"] = "0"
|
cf["dbNum"] = "0"
|
||||||
}
|
}
|
||||||
|
if _, ok := cf["password"]; !ok {
|
||||||
|
cf["password"] = ""
|
||||||
|
}
|
||||||
rc.key = cf["key"]
|
rc.key = cf["key"]
|
||||||
rc.conninfo = cf["conn"]
|
rc.conninfo = cf["conn"]
|
||||||
rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
|
rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
|
||||||
|
rc.password = cf["password"]
|
||||||
|
|
||||||
rc.connectInit()
|
rc.connectInit()
|
||||||
|
|
||||||
c := rc.p.Get()
|
c := rc.p.Get()
|
||||||
@ -171,6 +209,17 @@ func (rc *RedisCache) StartAndGC(config string) error {
|
|||||||
func (rc *RedisCache) connectInit() {
|
func (rc *RedisCache) connectInit() {
|
||||||
dialFunc := func() (c redis.Conn, err error) {
|
dialFunc := func() (c redis.Conn, err error) {
|
||||||
c, err = redis.Dial("tcp", rc.conninfo)
|
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)
|
_, selecterr := c.Do("SELECT", rc.dbNum)
|
||||||
if selecterr != nil {
|
if selecterr != nil {
|
||||||
c.Close()
|
c.Close()
|
||||||
|
21
cache/redis/redis_test.go
vendored
21
cache/redis/redis_test.go
vendored
@ -67,6 +67,7 @@ func TestRedisCache(t *testing.T) {
|
|||||||
if bm.IsExist("astaxie") {
|
if bm.IsExist("astaxie") {
|
||||||
t.Error("delete err")
|
t.Error("delete err")
|
||||||
}
|
}
|
||||||
|
|
||||||
//test string
|
//test string
|
||||||
if err = bm.Put("astaxie", "author", 10); err != nil {
|
if err = bm.Put("astaxie", "author", 10); err != nil {
|
||||||
t.Error("set Error", err)
|
t.Error("set Error", err)
|
||||||
@ -78,6 +79,26 @@ func TestRedisCache(t *testing.T) {
|
|||||||
if v, _ := redis.String(bm.Get("astaxie"), err); v != "author" {
|
if v, _ := redis.String(bm.Get("astaxie"), err); v != "author" {
|
||||||
t.Error("get err")
|
t.Error("get err")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie1", "author1", 10); 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
|
// test clear all
|
||||||
if err = bm.ClearAll(); err != nil {
|
if err = bm.ClearAll(); err != nil {
|
||||||
t.Error("clear all err")
|
t.Error("clear all err")
|
||||||
|
44
config.go
44
config.go
@ -82,6 +82,7 @@ var (
|
|||||||
EnableDocs bool // enable generate docs & server docs API Swagger
|
EnableDocs bool // enable generate docs & server docs API Swagger
|
||||||
RouterCaseSensitive bool // router case sensitive default is true
|
RouterCaseSensitive bool // router case sensitive default is true
|
||||||
AccessLogs bool // print access logs, default is false
|
AccessLogs bool // print access logs, default is false
|
||||||
|
Graceful bool // use graceful start the server
|
||||||
)
|
)
|
||||||
|
|
||||||
type beegoAppConfig struct {
|
type beegoAppConfig struct {
|
||||||
@ -98,6 +99,10 @@ func newAppConfig(AppConfigProvider, AppConfigPath string) (*beegoAppConfig, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *beegoAppConfig) Set(key, val string) error {
|
func (b *beegoAppConfig) Set(key, val string) error {
|
||||||
|
err := b.innerConfig.Set(RunMode+"::"+key, val)
|
||||||
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return b.innerConfig.Set(key, val)
|
return b.innerConfig.Set(key, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,27 +155,51 @@ func (b *beegoAppConfig) Float(key string) (float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *beegoAppConfig) DefaultString(key string, defaultval string) string {
|
func (b *beegoAppConfig) DefaultString(key string, defaultval string) string {
|
||||||
return b.innerConfig.DefaultString(key, defaultval)
|
v := b.String(key)
|
||||||
|
if v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *beegoAppConfig) DefaultStrings(key string, defaultval []string) []string {
|
func (b *beegoAppConfig) DefaultStrings(key string, defaultval []string) []string {
|
||||||
return b.innerConfig.DefaultStrings(key, defaultval)
|
v := b.Strings(key)
|
||||||
|
if len(v) != 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *beegoAppConfig) DefaultInt(key string, defaultval int) int {
|
func (b *beegoAppConfig) DefaultInt(key string, defaultval int) int {
|
||||||
return b.innerConfig.DefaultInt(key, defaultval)
|
v, err := b.Int(key)
|
||||||
|
if err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *beegoAppConfig) DefaultInt64(key string, defaultval int64) int64 {
|
func (b *beegoAppConfig) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
return b.innerConfig.DefaultInt64(key, defaultval)
|
v, err := b.Int64(key)
|
||||||
|
if err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *beegoAppConfig) DefaultBool(key string, defaultval bool) bool {
|
func (b *beegoAppConfig) DefaultBool(key string, defaultval bool) bool {
|
||||||
return b.innerConfig.DefaultBool(key, defaultval)
|
v, err := b.Bool(key)
|
||||||
|
if err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *beegoAppConfig) DefaultFloat(key string, defaultval float64) float64 {
|
func (b *beegoAppConfig) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
return b.innerConfig.DefaultFloat(key, defaultval)
|
v, err := b.Float(key)
|
||||||
|
if err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *beegoAppConfig) DIY(key string) (interface{}, error) {
|
func (b *beegoAppConfig) DIY(key string) (interface{}, error) {
|
||||||
@ -481,5 +510,8 @@ func ParseConfig() (err error) {
|
|||||||
if casesensitive, err := AppConfig.Bool("RouterCaseSensitive"); err == nil {
|
if casesensitive, err := AppConfig.Bool("RouterCaseSensitive"); err == nil {
|
||||||
RouterCaseSensitive = casesensitive
|
RouterCaseSensitive = casesensitive
|
||||||
}
|
}
|
||||||
|
if graceful, err := AppConfig.Bool("Graceful"); err == nil {
|
||||||
|
Graceful = graceful
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -300,7 +300,32 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
|
// Save default section at first place
|
||||||
|
if dt, ok := c.data[DEFAULT_SECTION]; ok {
|
||||||
|
for key, val := range dt {
|
||||||
|
if key != " " {
|
||||||
|
// Write key comments.
|
||||||
|
if v, ok := c.keyComment[key]; ok {
|
||||||
|
if _, err = buf.WriteString(string(bNumComment) + 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 {
|
for section, dt := range c.data {
|
||||||
|
if section != DEFAULT_SECTION {
|
||||||
// Write section comments.
|
// Write section comments.
|
||||||
if v, ok := c.sectionComment[section]; ok {
|
if v, ok := c.sectionComment[section]; ok {
|
||||||
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
|
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
|
||||||
@ -308,12 +333,10 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if section != DEFAULT_SECTION {
|
|
||||||
// Write section name.
|
// Write section name.
|
||||||
if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
|
if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for key, val := range dt {
|
for key, val := range dt {
|
||||||
if key != " " {
|
if key != " " {
|
||||||
@ -336,6 +359,7 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err = buf.WriteTo(f); err != nil {
|
if _, err = buf.WriteTo(f); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -21,12 +21,21 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego/session"
|
"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)(?:,|$)`)
|
||||||
|
)
|
||||||
|
|
||||||
// BeegoInput operates the http request header, data, cookie and body.
|
// BeegoInput operates the http request header, data, cookie and body.
|
||||||
// it also contains router params and current session.
|
// it also contains router params and current session.
|
||||||
type BeegoInput struct {
|
type BeegoInput struct {
|
||||||
@ -163,6 +172,21 @@ func (input *BeegoInput) IsUpload() bool {
|
|||||||
return strings.Contains(input.Header("Content-Type"), "multipart/form-data")
|
return strings.Contains(input.Header("Content-Type"), "multipart/form-data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if request accepts html response
|
||||||
|
func (input *BeegoInput) AcceptsHtml() bool {
|
||||||
|
return acceptsHtmlRegex.MatchString(input.Header("Accept"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if request accepts xml response
|
||||||
|
func (input *BeegoInput) AcceptsXml() bool {
|
||||||
|
return acceptsXmlRegex.MatchString(input.Header("Accept"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if request accepts json response
|
||||||
|
func (input *BeegoInput) AcceptsJson() bool {
|
||||||
|
return acceptsJsonRegex.MatchString(input.Header("Accept"))
|
||||||
|
}
|
||||||
|
|
||||||
// IP returns request client ip.
|
// IP returns request client ip.
|
||||||
// if in proxy, return first proxy id.
|
// if in proxy, return first proxy id.
|
||||||
// if error, return 127.0.0.1.
|
// if error, return 127.0.0.1.
|
||||||
@ -306,7 +330,7 @@ func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error {
|
|||||||
// Bind data from request.Form[key] to dest
|
// 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
|
// 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 id int beegoInput.Bind(&id, "id") id ==123
|
||||||
// var isok bool beegoInput.Bind(&isok, "isok") id ==true
|
// var isok bool beegoInput.Bind(&isok, "isok") isok ==true
|
||||||
// var ft float64 beegoInput.Bind(&ft, "ft") ft ==1.2
|
// var ft float64 beegoInput.Bind(&ft, "ft") ft ==1.2
|
||||||
// ol := make([]int, 0, 2) beegoInput.Bind(&ol, "ol") ol ==[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]
|
// ul := make([]string, 0, 2) beegoInput.Bind(&ul, "ul") ul ==[str array]
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BeegoOutput does work for sending response header.
|
// BeegoOutput does work for sending response header.
|
||||||
@ -98,23 +99,25 @@ func (output *BeegoOutput) Body(content []byte) {
|
|||||||
func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) {
|
func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value))
|
fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value))
|
||||||
|
|
||||||
|
//fix cookie not work in IE
|
||||||
if len(others) > 0 {
|
if len(others) > 0 {
|
||||||
switch v := others[0].(type) {
|
switch v := others[0].(type) {
|
||||||
case int:
|
case int:
|
||||||
if v > 0 {
|
if v > 0 {
|
||||||
fmt.Fprintf(&b, "; Max-Age=%d", v)
|
fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(v) * time.Second).UTC().Format(time.RFC1123), v)
|
||||||
} else if v < 0 {
|
} else if v < 0 {
|
||||||
fmt.Fprintf(&b, "; Max-Age=0")
|
fmt.Fprintf(&b, "; Max-Age=0")
|
||||||
}
|
}
|
||||||
case int64:
|
case int64:
|
||||||
if v > 0 {
|
if v > 0 {
|
||||||
fmt.Fprintf(&b, "; Max-Age=%d", v)
|
fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(v) * time.Second).UTC().Format(time.RFC1123), v)
|
||||||
} else if v < 0 {
|
} else if v < 0 {
|
||||||
fmt.Fprintf(&b, "; Max-Age=0")
|
fmt.Fprintf(&b, "; Max-Age=0")
|
||||||
}
|
}
|
||||||
case int32:
|
case int32:
|
||||||
if v > 0 {
|
if v > 0 {
|
||||||
fmt.Fprintf(&b, "; Max-Age=%d", v)
|
fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(v) * time.Second).UTC().Format(time.RFC1123), v)
|
||||||
} else if v < 0 {
|
} else if v < 0 {
|
||||||
fmt.Fprintf(&b, "; Max-Age=0")
|
fmt.Fprintf(&b, "; Max-Age=0")
|
||||||
}
|
}
|
||||||
|
107
controller.go
107
controller.go
@ -406,106 +406,90 @@ func (c *Controller) GetStrings(key string, def ...[]string) []string {
|
|||||||
|
|
||||||
// GetInt returns input as an int or the default value while it's present and input is blank
|
// 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) {
|
func (c *Controller) GetInt(key string, def ...int) (int, error) {
|
||||||
var defv int
|
|
||||||
if len(def) > 0 {
|
|
||||||
defv = def[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if strv := c.Ctx.Input.Query(key); strv != "" {
|
if strv := c.Ctx.Input.Query(key); strv != "" {
|
||||||
return strconv.Atoi(strv)
|
return strconv.Atoi(strv)
|
||||||
|
} else if len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
} else {
|
} else {
|
||||||
return defv, nil
|
return strconv.Atoi(strv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt8 return input as an int8 or the default value while it's present and input is blank
|
// 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) {
|
func (c *Controller) GetInt8(key string, def ...int8) (int8, error) {
|
||||||
var defv int8
|
|
||||||
if len(def) > 0 {
|
|
||||||
defv = def[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if strv := c.Ctx.Input.Query(key); strv != "" {
|
if strv := c.Ctx.Input.Query(key); strv != "" {
|
||||||
i64, err := strconv.ParseInt(strv, 10, 8)
|
i64, err := strconv.ParseInt(strv, 10, 8)
|
||||||
i8 := int8(i64)
|
i8 := int8(i64)
|
||||||
return i8, err
|
return i8, err
|
||||||
|
} else if len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
} else {
|
} else {
|
||||||
return defv, nil
|
i64, err := strconv.ParseInt(strv, 10, 8)
|
||||||
|
i8 := int8(i64)
|
||||||
|
return i8, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt16 returns input as an int16 or the default value while it's present and input is blank
|
// 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) {
|
func (c *Controller) GetInt16(key string, def ...int16) (int16, error) {
|
||||||
var defv int16
|
|
||||||
if len(def) > 0 {
|
|
||||||
defv = def[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if strv := c.Ctx.Input.Query(key); strv != "" {
|
if strv := c.Ctx.Input.Query(key); strv != "" {
|
||||||
i64, err := strconv.ParseInt(strv, 10, 16)
|
i64, err := strconv.ParseInt(strv, 10, 16)
|
||||||
i16 := int16(i64)
|
i16 := int16(i64)
|
||||||
|
|
||||||
return i16, err
|
return i16, err
|
||||||
|
} else if len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
} else {
|
} else {
|
||||||
return defv, nil
|
i64, err := strconv.ParseInt(strv, 10, 16)
|
||||||
|
i16 := int16(i64)
|
||||||
|
return i16, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt32 returns input as an int32 or the default value while it's present and input is blank
|
// 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) {
|
func (c *Controller) GetInt32(key string, def ...int32) (int32, error) {
|
||||||
var defv int32
|
|
||||||
if len(def) > 0 {
|
|
||||||
defv = def[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if strv := c.Ctx.Input.Query(key); strv != "" {
|
if strv := c.Ctx.Input.Query(key); strv != "" {
|
||||||
i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 32)
|
i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 32)
|
||||||
i32 := int32(i64)
|
i32 := int32(i64)
|
||||||
return i32, err
|
return i32, err
|
||||||
|
} else if len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
} else {
|
} else {
|
||||||
return defv, nil
|
i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 32)
|
||||||
|
i32 := int32(i64)
|
||||||
|
return i32, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt64 returns input value as int64 or the default value while it's present and input is blank.
|
// 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) {
|
func (c *Controller) GetInt64(key string, def ...int64) (int64, error) {
|
||||||
var defv int64
|
|
||||||
if len(def) > 0 {
|
|
||||||
defv = def[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if strv := c.Ctx.Input.Query(key); strv != "" {
|
if strv := c.Ctx.Input.Query(key); strv != "" {
|
||||||
return strconv.ParseInt(strv, 10, 64)
|
return strconv.ParseInt(strv, 10, 64)
|
||||||
|
} else if len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
} else {
|
} else {
|
||||||
return defv, nil
|
return strconv.ParseInt(strv, 10, 64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBool returns input value as bool or the default value while it's present and input is blank.
|
// 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) {
|
func (c *Controller) GetBool(key string, def ...bool) (bool, error) {
|
||||||
var defv bool
|
|
||||||
if len(def) > 0 {
|
|
||||||
defv = def[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if strv := c.Ctx.Input.Query(key); strv != "" {
|
if strv := c.Ctx.Input.Query(key); strv != "" {
|
||||||
return strconv.ParseBool(strv)
|
return strconv.ParseBool(strv)
|
||||||
|
} else if len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
} else {
|
} else {
|
||||||
return defv, nil
|
return strconv.ParseBool(strv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFloat returns input value as float64 or the default value while it's present and input is blank.
|
// 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) {
|
func (c *Controller) GetFloat(key string, def ...float64) (float64, error) {
|
||||||
var defv float64
|
|
||||||
if len(def) > 0 {
|
|
||||||
defv = def[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if strv := c.Ctx.Input.Query(key); strv != "" {
|
if strv := c.Ctx.Input.Query(key); strv != "" {
|
||||||
return strconv.ParseFloat(c.Ctx.Input.Query(key), 64)
|
return strconv.ParseFloat(strv, 64)
|
||||||
|
} else if len(def) > 0 {
|
||||||
|
return def[0], nil
|
||||||
} else {
|
} else {
|
||||||
return defv, nil
|
return strconv.ParseFloat(strv, 64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,6 +499,41 @@ func (c *Controller) GetFile(key string) (multipart.File, *multipart.FileHeader,
|
|||||||
return c.Ctx.Request.FormFile(key)
|
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) {
|
||||||
|
files, ok := c.Ctx.Request.MultipartForm.File[key]
|
||||||
|
if ok {
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
return nil, http.ErrMissingFile
|
||||||
|
}
|
||||||
|
|
||||||
// SaveToFile saves uploaded file to new path.
|
// SaveToFile saves uploaded file to new path.
|
||||||
// it only operates the first one of mutil-upload form file field.
|
// it only operates the first one of mutil-upload form file field.
|
||||||
func (c *Controller) SaveToFile(fromfile, tofile string) error {
|
func (c *Controller) SaveToFile(fromfile, tofile string) error {
|
||||||
|
14
error.go
14
error.go
@ -92,7 +92,7 @@ func showErr(err interface{}, ctx *context.Context, Stack string) {
|
|||||||
data["Stack"] = Stack
|
data["Stack"] = Stack
|
||||||
data["BeegoVersion"] = VERSION
|
data["BeegoVersion"] = VERSION
|
||||||
data["GoVersion"] = runtime.Version()
|
data["GoVersion"] = runtime.Version()
|
||||||
ctx.Output.SetStatus(500)
|
ctx.ResponseWriter.WriteHeader(500)
|
||||||
t.Execute(ctx.ResponseWriter, data)
|
t.Execute(ctx.ResponseWriter, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,24 +439,26 @@ func exception(errcode string, ctx *context.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
code = 503
|
code = 503
|
||||||
}
|
}
|
||||||
ctx.ResponseWriter.WriteHeader(code)
|
|
||||||
if h, ok := ErrorMaps[errcode]; ok {
|
if h, ok := ErrorMaps[errcode]; ok {
|
||||||
executeError(h, ctx)
|
executeError(h, ctx, code)
|
||||||
return
|
return
|
||||||
} else if h, ok := ErrorMaps["503"]; ok {
|
} else if h, ok := ErrorMaps["503"]; ok {
|
||||||
executeError(h, ctx)
|
executeError(h, ctx, code)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
ctx.ResponseWriter.WriteHeader(code)
|
||||||
ctx.WriteString(errcode)
|
ctx.WriteString(errcode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeError(err *errorInfo, ctx *context.Context) {
|
func executeError(err *errorInfo, ctx *context.Context, code int) {
|
||||||
if err.errorType == errorTypeHandler {
|
if err.errorType == errorTypeHandler {
|
||||||
|
ctx.ResponseWriter.WriteHeader(code)
|
||||||
err.handler(ctx.ResponseWriter, ctx.Request)
|
err.handler(ctx.ResponseWriter, ctx.Request)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err.errorType == errorTypeController {
|
if err.errorType == errorTypeController {
|
||||||
|
ctx.Output.SetStatus(code)
|
||||||
//Invoke the request handler
|
//Invoke the request handler
|
||||||
vc := reflect.New(err.controllerType)
|
vc := reflect.New(err.controllerType)
|
||||||
execController, ok := vc.Interface().(ControllerInterface)
|
execController, ok := vc.Interface().(ControllerInterface)
|
||||||
@ -476,13 +478,11 @@ func executeError(err *errorInfo, ctx *context.Context) {
|
|||||||
method.Call(in)
|
method.Call(in)
|
||||||
|
|
||||||
//render template
|
//render template
|
||||||
if ctx.Output.Status == 0 {
|
|
||||||
if AutoRender {
|
if AutoRender {
|
||||||
if err := execController.Render(); err != nil {
|
if err := execController.Render(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// finish all runrouter. release resource
|
// finish all runrouter. release resource
|
||||||
execController.Finish()
|
execController.Finish()
|
||||||
|
16
filter.go
16
filter.go
@ -16,11 +16,12 @@ package beego
|
|||||||
|
|
||||||
import "github.com/astaxie/beego/context"
|
import "github.com/astaxie/beego/context"
|
||||||
|
|
||||||
// FilterFunc defines filter function type.
|
// FilterFunc defines a filter function which is invoked before the controller handler is executed.
|
||||||
type FilterFunc func(*context.Context)
|
type FilterFunc func(*context.Context)
|
||||||
|
|
||||||
// FilterRouter defines filter operation before controller handler execution.
|
// FilterRouter defines a filter operation which is invoked before the controller handler is executed.
|
||||||
// it can match patterned url and do filter function when action arrives.
|
// It can match the URL against a pattern, and execute a filter function
|
||||||
|
// when a request with a matching URL arrives.
|
||||||
type FilterRouter struct {
|
type FilterRouter struct {
|
||||||
filterFunc FilterFunc
|
filterFunc FilterFunc
|
||||||
tree *Tree
|
tree *Tree
|
||||||
@ -28,10 +29,11 @@ type FilterRouter struct {
|
|||||||
returnOnOutput bool
|
returnOnOutput bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidRouter check current request is valid for this filter.
|
// ValidRouter checks if the current request is matched by this filter.
|
||||||
// if matched, returns parsed params in this request by defined filter router pattern.
|
// If the request is matched, the values of the URL parameters defined
|
||||||
func (f *FilterRouter) ValidRouter(router string) (bool, map[string]string) {
|
// by the filter pattern are also returned.
|
||||||
isok, params := f.tree.Match(router)
|
func (f *FilterRouter) ValidRouter(url string) (bool, map[string]string) {
|
||||||
|
isok, params := f.tree.Match(url)
|
||||||
if isok == nil {
|
if isok == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
13
grace/conn.go
Normal file
13
grace/conn.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package grace
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
type graceConn struct {
|
||||||
|
net.Conn
|
||||||
|
server *graceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c graceConn) Close() error {
|
||||||
|
c.server.wg.Done()
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
150
grace/grace.go
Normal file
150
grace/grace.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// 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", mux1)
|
||||||
|
// 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 (
|
||||||
|
PRE_SIGNAL = iota
|
||||||
|
POST_SIGNAL
|
||||||
|
|
||||||
|
STATE_INIT
|
||||||
|
STATE_RUNNING
|
||||||
|
STATE_SHUTTING_DOWN
|
||||||
|
STATE_TERMINATE
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
regLock *sync.Mutex
|
||||||
|
runningServers map[string]*graceServer
|
||||||
|
runningServersOrder []string
|
||||||
|
socketPtrOffsetMap map[string]uint
|
||||||
|
runningServersForked bool
|
||||||
|
|
||||||
|
DefaultReadTimeOut time.Duration
|
||||||
|
DefaultWriteTimeOut time.Duration
|
||||||
|
DefaultMaxHeaderBytes int
|
||||||
|
DefaultTimeout time.Duration
|
||||||
|
|
||||||
|
isChild bool
|
||||||
|
socketOrder string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
regLock = &sync.Mutex{}
|
||||||
|
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")
|
||||||
|
runningServers = make(map[string]*graceServer)
|
||||||
|
runningServersOrder = []string{}
|
||||||
|
socketPtrOffsetMap = make(map[string]uint)
|
||||||
|
|
||||||
|
DefaultMaxHeaderBytes = 0
|
||||||
|
|
||||||
|
DefaultTimeout = 60 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer returns a new graceServer.
|
||||||
|
func NewServer(addr string, handler http.Handler) (srv *graceServer) {
|
||||||
|
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 = &graceServer{
|
||||||
|
wg: sync.WaitGroup{},
|
||||||
|
sigChan: make(chan os.Signal),
|
||||||
|
isChild: isChild,
|
||||||
|
SignalHooks: map[int]map[os.Signal][]func(){
|
||||||
|
PRE_SIGNAL: map[os.Signal][]func(){
|
||||||
|
syscall.SIGHUP: []func(){},
|
||||||
|
syscall.SIGINT: []func(){},
|
||||||
|
syscall.SIGTERM: []func(){},
|
||||||
|
},
|
||||||
|
POST_SIGNAL: map[os.Signal][]func(){
|
||||||
|
syscall.SIGHUP: []func(){},
|
||||||
|
syscall.SIGINT: []func(){},
|
||||||
|
syscall.SIGTERM: []func(){},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state: STATE_INIT,
|
||||||
|
Network: "tcp",
|
||||||
|
}
|
||||||
|
srv.Server = &http.Server{}
|
||||||
|
srv.Server.Addr = addr
|
||||||
|
srv.Server.ReadTimeout = DefaultReadTimeOut
|
||||||
|
srv.Server.WriteTimeout = DefaultWriteTimeOut
|
||||||
|
srv.Server.MaxHeaderBytes = DefaultMaxHeaderBytes
|
||||||
|
srv.Server.Handler = handler
|
||||||
|
|
||||||
|
runningServersOrder = append(runningServersOrder, addr)
|
||||||
|
runningServers[addr] = srv
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// refer http.ListenAndServe
|
||||||
|
func ListenAndServe(addr string, handler http.Handler) error {
|
||||||
|
server := NewServer(addr, handler)
|
||||||
|
return server.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// refer http.ListenAndServeTLS
|
||||||
|
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error {
|
||||||
|
server := NewServer(addr, handler)
|
||||||
|
return server.ListenAndServeTLS(certFile, keyFile)
|
||||||
|
}
|
62
grace/listener.go
Normal file
62
grace/listener.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package grace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type graceListener struct {
|
||||||
|
net.Listener
|
||||||
|
stop chan error
|
||||||
|
stopped bool
|
||||||
|
server *graceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGraceListener(l net.Listener, srv *graceServer) (el *graceListener) {
|
||||||
|
el = &graceListener{
|
||||||
|
Listener: l,
|
||||||
|
stop: make(chan error),
|
||||||
|
server: srv,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
_ = <-el.stop
|
||||||
|
el.stopped = true
|
||||||
|
el.stop <- el.Listener.Close()
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gl *graceListener) Accept() (c net.Conn, err error) {
|
||||||
|
tc, err := gl.Listener.(*net.TCPListener).AcceptTCP()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.SetKeepAlive(true)
|
||||||
|
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||||
|
|
||||||
|
c = graceConn{
|
||||||
|
Conn: tc,
|
||||||
|
server: gl.server,
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.server.wg.Add(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (el *graceListener) Close() error {
|
||||||
|
if el.stopped {
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
el.stop <- nil
|
||||||
|
return <-el.stop
|
||||||
|
}
|
||||||
|
|
||||||
|
func (el *graceListener) File() *os.File {
|
||||||
|
// returns a dup(2) - FD_CLOEXEC flag *not* set
|
||||||
|
tl := el.Listener.(*net.TCPListener)
|
||||||
|
fl, _ := tl.File()
|
||||||
|
return fl
|
||||||
|
}
|
292
grace/server.go
Normal file
292
grace/server.go
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
package grace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type graceServer struct {
|
||||||
|
*http.Server
|
||||||
|
GraceListener net.Listener
|
||||||
|
SignalHooks map[int]map[os.Signal][]func()
|
||||||
|
tlsInnerListener *graceListener
|
||||||
|
wg sync.WaitGroup
|
||||||
|
sigChan chan os.Signal
|
||||||
|
isChild bool
|
||||||
|
state uint8
|
||||||
|
Network string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 *graceServer) Serve() (err error) {
|
||||||
|
srv.state = STATE_RUNNING
|
||||||
|
err = srv.Server.Serve(srv.GraceListener)
|
||||||
|
log.Println(syscall.Getpid(), "Waiting for connections to finish...")
|
||||||
|
srv.wg.Wait()
|
||||||
|
srv.state = STATE_TERMINATE
|
||||||
|
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 *graceServer) ListenAndServe() (err error) {
|
||||||
|
addr := srv.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":http"
|
||||||
|
}
|
||||||
|
|
||||||
|
go srv.handleSignals()
|
||||||
|
|
||||||
|
l, err := srv.getListener(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.GraceListener = newGraceListener(l, srv)
|
||||||
|
|
||||||
|
if srv.isChild {
|
||||||
|
process, err := os.FindProcess(os.Getppid())
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = process.Kill()
|
||||||
|
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 *graceServer) ListenAndServeTLS(certFile, keyFile string) (err error) {
|
||||||
|
addr := srv.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":https"
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &tls.Config{}
|
||||||
|
if srv.TLSConfig != nil {
|
||||||
|
*config = *srv.TLSConfig
|
||||||
|
}
|
||||||
|
if config.NextProtos == nil {
|
||||||
|
config.NextProtos = []string{"http/1.1"}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Certificates = make([]tls.Certificate, 1)
|
||||||
|
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go srv.handleSignals()
|
||||||
|
|
||||||
|
l, err := srv.getListener(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.tlsInnerListener = newGraceListener(l, srv)
|
||||||
|
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, config)
|
||||||
|
|
||||||
|
if srv.isChild {
|
||||||
|
process, err := os.FindProcess(os.Getppid())
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = process.Kill()
|
||||||
|
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 *graceServer) getListener(laddr string) (l net.Listener, err error) {
|
||||||
|
if srv.isChild {
|
||||||
|
var ptrOffset uint = 0
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSignals listens for os Signals and calls any hooked in function that the
|
||||||
|
// user had registered with the signal.
|
||||||
|
func (srv *graceServer) handleSignals() {
|
||||||
|
var sig os.Signal
|
||||||
|
|
||||||
|
signal.Notify(
|
||||||
|
srv.sigChan,
|
||||||
|
syscall.SIGHUP,
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
)
|
||||||
|
|
||||||
|
pid := syscall.Getpid()
|
||||||
|
for {
|
||||||
|
sig = <-srv.sigChan
|
||||||
|
srv.signalHooks(PRE_SIGNAL, 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(POST_SIGNAL, sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *graceServer) signalHooks(ppFlag int, sig os.Signal) {
|
||||||
|
if _, notSet := srv.SignalHooks[ppFlag][sig]; !notSet {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, f := range srv.SignalHooks[ppFlag][sig] {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 *graceServer) shutdown() {
|
||||||
|
if srv.state != STATE_RUNNING {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.state = STATE_SHUTTING_DOWN
|
||||||
|
if DefaultTimeout >= 0 {
|
||||||
|
go srv.serverTimeout(DefaultTimeout)
|
||||||
|
}
|
||||||
|
err := srv.GraceListener.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(syscall.Getpid(), "Listener.Close() error:", err)
|
||||||
|
} else {
|
||||||
|
log.Println(syscall.Getpid(), srv.GraceListener.Addr(), "Listener closed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverTimeout forces the server to shutdown in a given timeout - whether it
|
||||||
|
// finished outstanding requests or not. if Read/WriteTimeout are not set or the
|
||||||
|
// max header size is very big a connection could hang
|
||||||
|
func (srv *graceServer) serverTimeout(d time.Duration) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Println("WaitGroup at 0", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if srv.state != STATE_SHUTTING_DOWN {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(d)
|
||||||
|
log.Println("[STOP - Hammer Time] Forcefully shutting down parent")
|
||||||
|
for {
|
||||||
|
if srv.state == STATE_TERMINATE {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
srv.wg.Done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *graceServer) 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 {
|
||||||
|
switch srvPtr.GraceListener.(type) {
|
||||||
|
case *graceListener:
|
||||||
|
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.GraceListener.(*graceListener).File()
|
||||||
|
default:
|
||||||
|
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.tlsInnerListener.File()
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
@ -32,6 +32,7 @@ package httplib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
@ -50,7 +51,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultSetting = BeegoHttpSettings{false, "beegoServer", 60 * time.Second, 60 * time.Second, nil, nil, nil, false}
|
var defaultSetting = BeegoHttpSettings{
|
||||||
|
UserAgent: "beegoServer",
|
||||||
|
ConnectTimeout: 60 * time.Second,
|
||||||
|
ReadWriteTimeout: 60 * time.Second,
|
||||||
|
Gzip: true,
|
||||||
|
DumpBody: true,
|
||||||
|
}
|
||||||
|
|
||||||
var defaultCookieJar http.CookieJar
|
var defaultCookieJar http.CookieJar
|
||||||
var settingMutex sync.Mutex
|
var settingMutex sync.Mutex
|
||||||
|
|
||||||
@ -75,41 +83,53 @@ func SetDefaultSetting(setting BeegoHttpSettings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return *BeegoHttpRequest with specific method
|
// return *BeegoHttpRequest with specific method
|
||||||
func newBeegoRequest(url, method string) *BeegoHttpRequest {
|
func NewBeegoRequest(rawurl, method string) *BeegoHttpRequest {
|
||||||
var resp http.Response
|
var resp http.Response
|
||||||
|
u, err := url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
req := http.Request{
|
req := http.Request{
|
||||||
|
URL: u,
|
||||||
Method: method,
|
Method: method,
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
Proto: "HTTP/1.1",
|
Proto: "HTTP/1.1",
|
||||||
ProtoMajor: 1,
|
ProtoMajor: 1,
|
||||||
ProtoMinor: 1,
|
ProtoMinor: 1,
|
||||||
}
|
}
|
||||||
return &BeegoHttpRequest{url, &req, map[string]string{}, map[string]string{}, defaultSetting, &resp, nil}
|
return &BeegoHttpRequest{
|
||||||
|
url: rawurl,
|
||||||
|
req: &req,
|
||||||
|
params: map[string]string{},
|
||||||
|
files: map[string]string{},
|
||||||
|
setting: defaultSetting,
|
||||||
|
resp: &resp,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns *BeegoHttpRequest with GET method.
|
// Get returns *BeegoHttpRequest with GET method.
|
||||||
func Get(url string) *BeegoHttpRequest {
|
func Get(url string) *BeegoHttpRequest {
|
||||||
return newBeegoRequest(url, "GET")
|
return NewBeegoRequest(url, "GET")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post returns *BeegoHttpRequest with POST method.
|
// Post returns *BeegoHttpRequest with POST method.
|
||||||
func Post(url string) *BeegoHttpRequest {
|
func Post(url string) *BeegoHttpRequest {
|
||||||
return newBeegoRequest(url, "POST")
|
return NewBeegoRequest(url, "POST")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put returns *BeegoHttpRequest with PUT method.
|
// Put returns *BeegoHttpRequest with PUT method.
|
||||||
func Put(url string) *BeegoHttpRequest {
|
func Put(url string) *BeegoHttpRequest {
|
||||||
return newBeegoRequest(url, "PUT")
|
return NewBeegoRequest(url, "PUT")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete returns *BeegoHttpRequest DELETE method.
|
// Delete returns *BeegoHttpRequest DELETE method.
|
||||||
func Delete(url string) *BeegoHttpRequest {
|
func Delete(url string) *BeegoHttpRequest {
|
||||||
return newBeegoRequest(url, "DELETE")
|
return NewBeegoRequest(url, "DELETE")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Head returns *BeegoHttpRequest with HEAD method.
|
// Head returns *BeegoHttpRequest with HEAD method.
|
||||||
func Head(url string) *BeegoHttpRequest {
|
func Head(url string) *BeegoHttpRequest {
|
||||||
return newBeegoRequest(url, "HEAD")
|
return NewBeegoRequest(url, "HEAD")
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeegoHttpSettings
|
// BeegoHttpSettings
|
||||||
@ -122,6 +142,8 @@ type BeegoHttpSettings struct {
|
|||||||
Proxy func(*http.Request) (*url.URL, error)
|
Proxy func(*http.Request) (*url.URL, error)
|
||||||
Transport http.RoundTripper
|
Transport http.RoundTripper
|
||||||
EnableCookie bool
|
EnableCookie bool
|
||||||
|
Gzip bool
|
||||||
|
DumpBody bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeegoHttpRequest provides more useful methods for requesting one url than http.Request.
|
// BeegoHttpRequest provides more useful methods for requesting one url than http.Request.
|
||||||
@ -133,6 +155,12 @@ type BeegoHttpRequest struct {
|
|||||||
setting BeegoHttpSettings
|
setting BeegoHttpSettings
|
||||||
resp *http.Response
|
resp *http.Response
|
||||||
body []byte
|
body []byte
|
||||||
|
dump []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// get request
|
||||||
|
func (b *BeegoHttpRequest) GetRequest() *http.Request {
|
||||||
|
return b.req
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change request settings
|
// Change request settings
|
||||||
@ -165,6 +193,17 @@ func (b *BeegoHttpRequest) Debug(isdebug bool) *BeegoHttpRequest {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dump Body.
|
||||||
|
func (b *BeegoHttpRequest) DumpBody(isdump bool) *BeegoHttpRequest {
|
||||||
|
b.setting.DumpBody = isdump
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the DumpRequest
|
||||||
|
func (b *BeegoHttpRequest) DumpRequest() []byte {
|
||||||
|
return b.dump
|
||||||
|
}
|
||||||
|
|
||||||
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
|
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
|
||||||
func (b *BeegoHttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHttpRequest {
|
func (b *BeegoHttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHttpRequest {
|
||||||
b.setting.ConnectTimeout = connectTimeout
|
b.setting.ConnectTimeout = connectTimeout
|
||||||
@ -184,6 +223,12 @@ func (b *BeegoHttpRequest) Header(key, value string) *BeegoHttpRequest {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set HOST
|
||||||
|
func (b *BeegoHttpRequest) SetHost(host string) *BeegoHttpRequest {
|
||||||
|
b.req.Host = host
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// Set the protocol version for incoming requests.
|
// Set the protocol version for incoming requests.
|
||||||
// Client requests always use HTTP/1.1.
|
// Client requests always use HTTP/1.1.
|
||||||
func (b *BeegoHttpRequest) SetProtocolVersion(vers string) *BeegoHttpRequest {
|
func (b *BeegoHttpRequest) SetProtocolVersion(vers string) *BeegoHttpRequest {
|
||||||
@ -253,6 +298,21 @@ func (b *BeegoHttpRequest) Body(data interface{}) *BeegoHttpRequest {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JsonBody adds request raw body encoding by JSON.
|
||||||
|
func (b *BeegoHttpRequest) JsonBody(obj interface{}) (*BeegoHttpRequest, error) {
|
||||||
|
if b.req.Body == nil && obj != nil {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
enc := json.NewEncoder(buf)
|
||||||
|
if err := enc.Encode(obj); err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
b.req.Body = ioutil.NopCloser(buf)
|
||||||
|
b.req.ContentLength = int64(buf.Len())
|
||||||
|
b.req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BeegoHttpRequest) buildUrl(paramBody string) {
|
func (b *BeegoHttpRequest) buildUrl(paramBody string) {
|
||||||
// build GET url with query string
|
// build GET url with query string
|
||||||
if b.req.Method == "GET" && len(paramBody) > 0 {
|
if b.req.Method == "GET" && len(paramBody) > 0 {
|
||||||
@ -264,8 +324,8 @@ func (b *BeegoHttpRequest) buildUrl(paramBody string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// build POST url and body
|
// build POST/PUT/PATCH url and body
|
||||||
if b.req.Method == "POST" && b.req.Body == nil {
|
if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH") && b.req.Body == nil {
|
||||||
// with files
|
// with files
|
||||||
if len(b.files) > 0 {
|
if len(b.files) > 0 {
|
||||||
pr, pw := io.Pipe()
|
pr, pw := io.Pipe()
|
||||||
@ -310,6 +370,15 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
|
|||||||
if b.resp.StatusCode != 0 {
|
if b.resp.StatusCode != 0 {
|
||||||
return b.resp, nil
|
return b.resp, nil
|
||||||
}
|
}
|
||||||
|
resp, err := b.SendOut()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.resp = resp
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BeegoHttpRequest) SendOut() (*http.Response, error) {
|
||||||
var paramBody string
|
var paramBody string
|
||||||
if len(b.params) > 0 {
|
if len(b.params) > 0 {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@ -373,19 +442,13 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b.setting.ShowDebug {
|
if b.setting.ShowDebug {
|
||||||
dump, err := httputil.DumpRequest(b.req, true)
|
dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println(err.Error())
|
log.Println(err.Error())
|
||||||
}
|
}
|
||||||
println(string(dump))
|
b.dump = dump
|
||||||
}
|
}
|
||||||
|
return client.Do(b.req)
|
||||||
resp, err := client.Do(b.req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b.resp = resp
|
|
||||||
return resp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the body string in response.
|
// String returns the body string in response.
|
||||||
@ -413,11 +476,16 @@ func (b *BeegoHttpRequest) Bytes() ([]byte, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
b.body, err = ioutil.ReadAll(resp.Body)
|
if b.setting.Gzip && resp.Header.Get("Content-Encoding") == "gzip" {
|
||||||
|
reader, err := gzip.NewReader(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return b.body, nil
|
b.body, err = ioutil.ReadAll(reader)
|
||||||
|
} else {
|
||||||
|
b.body, err = ioutil.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
|
return b.body, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToFile saves the body data in response to one file.
|
// ToFile saves the body data in response to one file.
|
||||||
@ -473,7 +541,7 @@ func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, ad
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn.SetDeadline(time.Now().Add(rwTimeout))
|
err = conn.SetDeadline(time.Now().Add(rwTimeout))
|
||||||
return conn, nil
|
return conn, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
log.go
10
log.go
@ -78,9 +78,9 @@ func Warning(v ...interface{}) {
|
|||||||
BeeLogger.Warning(generateFmtStr(len(v)), v...)
|
BeeLogger.Warning(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0.
|
// compatibility alias for Warning()
|
||||||
func Warn(v ...interface{}) {
|
func Warn(v ...interface{}) {
|
||||||
Warning(v...)
|
BeeLogger.Warn(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Notice(v ...interface{}) {
|
func Notice(v ...interface{}) {
|
||||||
@ -92,9 +92,9 @@ func Informational(v ...interface{}) {
|
|||||||
BeeLogger.Informational(generateFmtStr(len(v)), v...)
|
BeeLogger.Informational(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0.
|
// compatibility alias for Warning()
|
||||||
func Info(v ...interface{}) {
|
func Info(v ...interface{}) {
|
||||||
Informational(v...)
|
BeeLogger.Info(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug logs a message at debug level.
|
// Debug logs a message at debug level.
|
||||||
@ -103,7 +103,7 @@ func Debug(v ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trace logs a message at trace level.
|
// Trace logs a message at trace level.
|
||||||
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0.
|
// compatibility alias for Warning()
|
||||||
func Trace(v ...interface{}) {
|
func Trace(v ...interface{}) {
|
||||||
BeeLogger.Trace(generateFmtStr(len(v)), v...)
|
BeeLogger.Trace(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
76
logs/es/es.go
Normal file
76
logs/es/es.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package es
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/belogik/goes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewES() logs.LoggerInterface {
|
||||||
|
cw := &esLogger{
|
||||||
|
Level: logs.LevelDebug,
|
||||||
|
}
|
||||||
|
return cw
|
||||||
|
}
|
||||||
|
|
||||||
|
type esLogger struct {
|
||||||
|
*goes.Connection
|
||||||
|
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 if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
conn := goes.NewConnection(host, port)
|
||||||
|
el.Connection = conn
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (el *esLogger) WriteMsg(msg string, level int) error {
|
||||||
|
if level > el.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t := time.Now()
|
||||||
|
vals := make(map[string]interface{})
|
||||||
|
vals["@timestamp"] = t.Format(time.RFC3339)
|
||||||
|
vals["@msg"] = msg
|
||||||
|
d := goes.Document{
|
||||||
|
Index: fmt.Sprintf("%04d.%02d.%02d", t.Year(), t.Month(), t.Day()),
|
||||||
|
Type: "logs",
|
||||||
|
Fields: vals,
|
||||||
|
}
|
||||||
|
_, err := el.Index(d, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (el *esLogger) Destroy() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (el *esLogger) Flush() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logs.Register("es", NewES)
|
||||||
|
}
|
85
logs/log.go
85
logs/log.go
@ -92,6 +92,7 @@ type BeeLogger struct {
|
|||||||
level int
|
level int
|
||||||
enableFuncCallDepth bool
|
enableFuncCallDepth bool
|
||||||
loggerFuncCallDepth int
|
loggerFuncCallDepth int
|
||||||
|
asynchronous bool
|
||||||
msg chan *logMsg
|
msg chan *logMsg
|
||||||
outputs map[string]LoggerInterface
|
outputs map[string]LoggerInterface
|
||||||
}
|
}
|
||||||
@ -110,7 +111,11 @@ func NewLogger(channellen int64) *BeeLogger {
|
|||||||
bl.loggerFuncCallDepth = 2
|
bl.loggerFuncCallDepth = 2
|
||||||
bl.msg = make(chan *logMsg, channellen)
|
bl.msg = make(chan *logMsg, channellen)
|
||||||
bl.outputs = make(map[string]LoggerInterface)
|
bl.outputs = make(map[string]LoggerInterface)
|
||||||
//bl.SetLogger("console", "") // default output to console
|
return bl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) Async() *BeeLogger {
|
||||||
|
bl.asynchronous = true
|
||||||
go bl.startLogger()
|
go bl.startLogger()
|
||||||
return bl
|
return bl
|
||||||
}
|
}
|
||||||
@ -148,26 +153,30 @@ func (bl *BeeLogger) DelLogger(adaptername string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
|
func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
|
||||||
if loglevel > bl.level {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
lm := new(logMsg)
|
lm := new(logMsg)
|
||||||
lm.level = loglevel
|
lm.level = loglevel
|
||||||
if bl.enableFuncCallDepth {
|
if bl.enableFuncCallDepth {
|
||||||
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
||||||
if _, filename := path.Split(file); filename == "log.go" && (line == 97 || line == 83) {
|
if !ok {
|
||||||
_, file, line, ok = runtime.Caller(bl.loggerFuncCallDepth + 1)
|
file = "???"
|
||||||
|
line = 0
|
||||||
}
|
}
|
||||||
if ok {
|
|
||||||
_, filename := path.Split(file)
|
_, filename := path.Split(file)
|
||||||
lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg)
|
lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg)
|
||||||
} else {
|
} else {
|
||||||
lm.msg = msg
|
lm.msg = msg
|
||||||
}
|
}
|
||||||
} else {
|
if bl.asynchronous {
|
||||||
lm.msg = msg
|
|
||||||
}
|
|
||||||
bl.msg <- lm
|
bl.msg <- lm
|
||||||
|
} else {
|
||||||
|
for name, l := range bl.outputs {
|
||||||
|
err := l.WriteMsg(lm.msg, lm.level)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("unable to WriteMsg to adapter:", name, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +193,11 @@ func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
|||||||
bl.loggerFuncCallDepth = d
|
bl.loggerFuncCallDepth = d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get log funcCallDepth for wrapper
|
||||||
|
func (bl *BeeLogger) GetLogFuncCallDepth() int {
|
||||||
|
return bl.loggerFuncCallDepth
|
||||||
|
}
|
||||||
|
|
||||||
// enable log funcCallDepth
|
// enable log funcCallDepth
|
||||||
func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
||||||
bl.enableFuncCallDepth = b
|
bl.enableFuncCallDepth = b
|
||||||
@ -207,71 +221,104 @@ func (bl *BeeLogger) startLogger() {
|
|||||||
|
|
||||||
// Log EMERGENCY level message.
|
// Log EMERGENCY level message.
|
||||||
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
|
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
|
||||||
|
if LevelEmergency > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := fmt.Sprintf("[M] "+format, v...)
|
msg := fmt.Sprintf("[M] "+format, v...)
|
||||||
bl.writerMsg(LevelEmergency, msg)
|
bl.writerMsg(LevelEmergency, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log ALERT level message.
|
// Log ALERT level message.
|
||||||
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
|
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
|
||||||
|
if LevelAlert > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := fmt.Sprintf("[A] "+format, v...)
|
msg := fmt.Sprintf("[A] "+format, v...)
|
||||||
bl.writerMsg(LevelAlert, msg)
|
bl.writerMsg(LevelAlert, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log CRITICAL level message.
|
// Log CRITICAL level message.
|
||||||
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
|
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
|
||||||
|
if LevelCritical > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := fmt.Sprintf("[C] "+format, v...)
|
msg := fmt.Sprintf("[C] "+format, v...)
|
||||||
bl.writerMsg(LevelCritical, msg)
|
bl.writerMsg(LevelCritical, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log ERROR level message.
|
// Log ERROR level message.
|
||||||
func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
||||||
|
if LevelError > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := fmt.Sprintf("[E] "+format, v...)
|
msg := fmt.Sprintf("[E] "+format, v...)
|
||||||
bl.writerMsg(LevelError, msg)
|
bl.writerMsg(LevelError, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log WARNING level message.
|
// Log WARNING level message.
|
||||||
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
||||||
|
if LevelWarning > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := fmt.Sprintf("[W] "+format, v...)
|
msg := fmt.Sprintf("[W] "+format, v...)
|
||||||
bl.writerMsg(LevelWarning, msg)
|
bl.writerMsg(LevelWarning, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log NOTICE level message.
|
// Log NOTICE level message.
|
||||||
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
|
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
|
||||||
|
if LevelNotice > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := fmt.Sprintf("[N] "+format, v...)
|
msg := fmt.Sprintf("[N] "+format, v...)
|
||||||
bl.writerMsg(LevelNotice, msg)
|
bl.writerMsg(LevelNotice, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log INFORMATIONAL level message.
|
// Log INFORMATIONAL level message.
|
||||||
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
||||||
|
if LevelInformational > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := fmt.Sprintf("[I] "+format, v...)
|
msg := fmt.Sprintf("[I] "+format, v...)
|
||||||
bl.writerMsg(LevelInformational, msg)
|
bl.writerMsg(LevelInformational, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log DEBUG level message.
|
// Log DEBUG level message.
|
||||||
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
|
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
|
||||||
|
if LevelDebug > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := fmt.Sprintf("[D] "+format, v...)
|
msg := fmt.Sprintf("[D] "+format, v...)
|
||||||
bl.writerMsg(LevelDebug, msg)
|
bl.writerMsg(LevelDebug, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log WARN level message.
|
// Log WARN level message.
|
||||||
//
|
// compatibility alias for Warning()
|
||||||
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0.
|
|
||||||
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
|
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
|
||||||
bl.Warning(format, v...)
|
if LevelWarning > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf("[W] "+format, v...)
|
||||||
|
bl.writerMsg(LevelWarning, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log INFO level message.
|
// Log INFO level message.
|
||||||
//
|
// compatibility alias for Informational()
|
||||||
// Deprecated: compatibility alias for Informational(), Will be removed in 1.5.0.
|
|
||||||
func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
||||||
bl.Informational(format, v...)
|
if LevelInformational > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf("[I] "+format, v...)
|
||||||
|
bl.writerMsg(LevelInformational, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log TRACE level message.
|
// Log TRACE level message.
|
||||||
//
|
// compatibility alias for Debug()
|
||||||
// Deprecated: compatibility alias for Debug(), Will be removed in 1.5.0.
|
|
||||||
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||||
bl.Debug(format, v...)
|
if LevelDebug > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf("[D] "+format, v...)
|
||||||
|
bl.writerMsg(LevelDebug, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush all chan data.
|
// flush all chan data.
|
||||||
|
1
mime.go
1
mime.go
@ -40,6 +40,7 @@ var mimemaps map[string]string = map[string]string{
|
|||||||
".ani": "application/x-navi-animation",
|
".ani": "application/x-navi-animation",
|
||||||
".aos": "application/x-nokia-9000-communicator-add-on-software",
|
".aos": "application/x-nokia-9000-communicator-add-on-software",
|
||||||
".aps": "application/mime",
|
".aps": "application/mime",
|
||||||
|
".apk": "application/vnd.android.package-archive",
|
||||||
".arc": "application/x-arc-compressed",
|
".arc": "application/x-arc-compressed",
|
||||||
".arj": "application/arj",
|
".arj": "application/arj",
|
||||||
".art": "image/x-jg",
|
".art": "image/x-jg",
|
||||||
|
14
orm/db.go
14
orm/db.go
@ -44,6 +44,8 @@ var (
|
|||||||
"gte": true,
|
"gte": true,
|
||||||
"lt": true,
|
"lt": true,
|
||||||
"lte": true,
|
"lte": true,
|
||||||
|
"eq": true,
|
||||||
|
"nq": true,
|
||||||
"startswith": true,
|
"startswith": true,
|
||||||
"endswith": true,
|
"endswith": true,
|
||||||
"istartswith": true,
|
"istartswith": true,
|
||||||
@ -324,7 +326,7 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
|
|||||||
query := fmt.Sprintf("SELECT %s%s%s FROM %s%s%s WHERE %s%s%s = ?", Q, sels, Q, Q, mi.table, Q, Q, wheres, Q)
|
query := fmt.Sprintf("SELECT %s%s%s FROM %s%s%s WHERE %s%s%s = ?", Q, sels, Q, Q, mi.table, Q, Q, wheres, Q)
|
||||||
|
|
||||||
refs := make([]interface{}, colsNum)
|
refs := make([]interface{}, colsNum)
|
||||||
for i, _ := range refs {
|
for i := range refs {
|
||||||
var ref interface{}
|
var ref interface{}
|
||||||
refs[i] = &ref
|
refs[i] = &ref
|
||||||
}
|
}
|
||||||
@ -423,7 +425,7 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s
|
|||||||
Q := d.ins.TableQuote()
|
Q := d.ins.TableQuote()
|
||||||
|
|
||||||
marks := make([]string, len(names))
|
marks := make([]string, len(names))
|
||||||
for i, _ := range marks {
|
for i := range marks {
|
||||||
marks[i] = "?"
|
marks[i] = "?"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -693,7 +695,7 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
marks := make([]string, len(args))
|
marks := make([]string, len(args))
|
||||||
for i, _ := range marks {
|
for i := range marks {
|
||||||
marks[i] = "?"
|
marks[i] = "?"
|
||||||
}
|
}
|
||||||
sql := fmt.Sprintf("IN (%s)", strings.Join(marks, ", "))
|
sql := fmt.Sprintf("IN (%s)", strings.Join(marks, ", "))
|
||||||
@ -824,7 +826,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
|
|||||||
}
|
}
|
||||||
|
|
||||||
refs := make([]interface{}, colsNum)
|
refs := make([]interface{}, colsNum)
|
||||||
for i, _ := range refs {
|
for i := range refs {
|
||||||
var ref interface{}
|
var ref interface{}
|
||||||
refs[i] = &ref
|
refs[i] = &ref
|
||||||
}
|
}
|
||||||
@ -964,7 +966,7 @@ func (d *dbBase) GenerateOperatorSql(mi *modelInfo, fi *fieldInfo, operator stri
|
|||||||
switch operator {
|
switch operator {
|
||||||
case "in":
|
case "in":
|
||||||
marks := make([]string, len(params))
|
marks := make([]string, len(params))
|
||||||
for i, _ := range marks {
|
for i := range marks {
|
||||||
marks[i] = "?"
|
marks[i] = "?"
|
||||||
}
|
}
|
||||||
sql = fmt.Sprintf("IN (%s)", strings.Join(marks, ", "))
|
sql = fmt.Sprintf("IN (%s)", strings.Join(marks, ", "))
|
||||||
@ -1460,7 +1462,7 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond
|
|||||||
}
|
}
|
||||||
|
|
||||||
refs := make([]interface{}, len(cols))
|
refs := make([]interface{}, len(cols))
|
||||||
for i, _ := range refs {
|
for i := range refs {
|
||||||
var ref interface{}
|
var ref interface{}
|
||||||
refs[i] = &ref
|
refs[i] = &ref
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@ var mysqlOperators = map[string]string{
|
|||||||
"gte": ">= ?",
|
"gte": ">= ?",
|
||||||
"lt": "< ?",
|
"lt": "< ?",
|
||||||
"lte": "<= ?",
|
"lte": "<= ?",
|
||||||
|
"eq": "= ?",
|
||||||
|
"ne": "!= ?",
|
||||||
"startswith": "LIKE BINARY ?",
|
"startswith": "LIKE BINARY ?",
|
||||||
"endswith": "LIKE BINARY ?",
|
"endswith": "LIKE BINARY ?",
|
||||||
"istartswith": "LIKE ?",
|
"istartswith": "LIKE ?",
|
||||||
|
@ -29,6 +29,8 @@ var postgresOperators = map[string]string{
|
|||||||
"gte": ">= ?",
|
"gte": ">= ?",
|
||||||
"lt": "< ?",
|
"lt": "< ?",
|
||||||
"lte": "<= ?",
|
"lte": "<= ?",
|
||||||
|
"eq": "= ?",
|
||||||
|
"ne": "!= ?",
|
||||||
"startswith": "LIKE ?",
|
"startswith": "LIKE ?",
|
||||||
"endswith": "LIKE ?",
|
"endswith": "LIKE ?",
|
||||||
"istartswith": "LIKE UPPER(?)",
|
"istartswith": "LIKE UPPER(?)",
|
||||||
|
@ -29,6 +29,8 @@ var sqliteOperators = map[string]string{
|
|||||||
"gte": ">= ?",
|
"gte": ">= ?",
|
||||||
"lt": "< ?",
|
"lt": "< ?",
|
||||||
"lte": "<= ?",
|
"lte": "<= ?",
|
||||||
|
"eq": "= ?",
|
||||||
|
"ne": "!= ?",
|
||||||
"startswith": "LIKE ? ESCAPE '\\'",
|
"startswith": "LIKE ? ESCAPE '\\'",
|
||||||
"endswith": "LIKE ? ESCAPE '\\'",
|
"endswith": "LIKE ? ESCAPE '\\'",
|
||||||
"istartswith": "LIKE ? ESCAPE '\\'",
|
"istartswith": "LIKE ? ESCAPE '\\'",
|
||||||
|
@ -242,14 +242,14 @@ type User struct {
|
|||||||
|
|
||||||
func (u *User) TableIndex() [][]string {
|
func (u *User) TableIndex() [][]string {
|
||||||
return [][]string{
|
return [][]string{
|
||||||
[]string{"Id", "UserName"},
|
{"Id", "UserName"},
|
||||||
[]string{"Id", "Created"},
|
{"Id", "Created"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) TableUnique() [][]string {
|
func (u *User) TableUnique() [][]string {
|
||||||
return [][]string{
|
return [][]string{
|
||||||
[]string{"UserName", "Email"},
|
{"UserName", "Email"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,7 +287,7 @@ type Post struct {
|
|||||||
|
|
||||||
func (u *Post) TableIndex() [][]string {
|
func (u *Post) TableIndex() [][]string {
|
||||||
return [][]string{
|
return [][]string{
|
||||||
[]string{"Id", "Created"},
|
{"Id", "Created"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,14 +115,13 @@ func (o querySet) OrderBy(exprs ...string) QuerySeter {
|
|||||||
// set relation model to query together.
|
// set relation model to query together.
|
||||||
// it will query relation models and assign to parent model.
|
// it will query relation models and assign to parent model.
|
||||||
func (o querySet) RelatedSel(params ...interface{}) QuerySeter {
|
func (o querySet) RelatedSel(params ...interface{}) QuerySeter {
|
||||||
var related []string
|
|
||||||
if len(params) == 0 {
|
if len(params) == 0 {
|
||||||
o.relDepth = DefaultRelsDepth
|
o.relDepth = DefaultRelsDepth
|
||||||
} else {
|
} else {
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
switch val := p.(type) {
|
switch val := p.(type) {
|
||||||
case string:
|
case string:
|
||||||
related = append(o.related, val)
|
o.related = append(o.related, val)
|
||||||
case int:
|
case int:
|
||||||
o.relDepth = val
|
o.relDepth = val
|
||||||
default:
|
default:
|
||||||
@ -130,7 +129,6 @@ func (o querySet) RelatedSel(params ...interface{}) QuerySeter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
o.related = related
|
|
||||||
return &o
|
return &o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,7 +585,7 @@ func (o *rawSet) readValues(container interface{}, needCols []string) (int64, er
|
|||||||
|
|
||||||
cols = columns
|
cols = columns
|
||||||
refs = make([]interface{}, len(cols))
|
refs = make([]interface{}, len(cols))
|
||||||
for i, _ := range refs {
|
for i := range refs {
|
||||||
var ref sql.NullString
|
var ref sql.NullString
|
||||||
refs[i] = &ref
|
refs[i] = &ref
|
||||||
|
|
||||||
@ -711,7 +711,7 @@ func (o *rawSet) queryRowsTo(container interface{}, keyCol, valueCol string) (in
|
|||||||
} else {
|
} else {
|
||||||
cols = columns
|
cols = columns
|
||||||
refs = make([]interface{}, len(cols))
|
refs = make([]interface{}, len(cols))
|
||||||
for i, _ := range refs {
|
for i := range refs {
|
||||||
if keyCol == cols[i] {
|
if keyCol == cols[i] {
|
||||||
keyIndex = i
|
keyIndex = i
|
||||||
}
|
}
|
||||||
|
@ -586,29 +586,29 @@ func TestInsertTestData(t *testing.T) {
|
|||||||
throwFail(t, AssertIs(id, 4))
|
throwFail(t, AssertIs(id, 4))
|
||||||
|
|
||||||
tags := []*Tag{
|
tags := []*Tag{
|
||||||
&Tag{Name: "golang", BestPost: &Post{Id: 2}},
|
{Name: "golang", BestPost: &Post{Id: 2}},
|
||||||
&Tag{Name: "example"},
|
{Name: "example"},
|
||||||
&Tag{Name: "format"},
|
{Name: "format"},
|
||||||
&Tag{Name: "c++"},
|
{Name: "c++"},
|
||||||
}
|
}
|
||||||
|
|
||||||
posts := []*Post{
|
posts := []*Post{
|
||||||
&Post{User: users[0], Tags: []*Tag{tags[0]}, Title: "Introduction", Content: `Go is a new language. Although it borrows ideas from existing languages, it has unusual properties that make effective Go programs different in character from programs written in its relatives. A straightforward translation of a C++ or Java program into Go is unlikely to produce a satisfactory result—Java programs are written in Java, not Go. On the other hand, thinking about the problem from a Go perspective could produce a successful but quite different program. In other words, to write Go well, it's important to understand its properties and idioms. It's also important to know the established conventions for programming in Go, such as naming, formatting, program construction, and so on, so that programs you write will be easy for other Go programmers to understand.
|
{User: users[0], Tags: []*Tag{tags[0]}, Title: "Introduction", Content: `Go is a new language. Although it borrows ideas from existing languages, it has unusual properties that make effective Go programs different in character from programs written in its relatives. A straightforward translation of a C++ or Java program into Go is unlikely to produce a satisfactory result—Java programs are written in Java, not Go. On the other hand, thinking about the problem from a Go perspective could produce a successful but quite different program. In other words, to write Go well, it's important to understand its properties and idioms. It's also important to know the established conventions for programming in Go, such as naming, formatting, program construction, and so on, so that programs you write will be easy for other Go programmers to understand.
|
||||||
This document gives tips for writing clear, idiomatic Go code. It augments the language specification, the Tour of Go, and How to Write Go Code, all of which you should read first.`},
|
This document gives tips for writing clear, idiomatic Go code. It augments the language specification, the Tour of Go, and How to Write Go Code, all of which you should read first.`},
|
||||||
&Post{User: users[1], Tags: []*Tag{tags[0], tags[1]}, Title: "Examples", Content: `The Go package sources are intended to serve not only as the core library but also as examples of how to use the language. Moreover, many of the packages contain working, self-contained executable examples you can run directly from the golang.org web site, such as this one (click on the word "Example" to open it up). If you have a question about how to approach a problem or how something might be implemented, the documentation, code and examples in the library can provide answers, ideas and background.`},
|
{User: users[1], Tags: []*Tag{tags[0], tags[1]}, Title: "Examples", Content: `The Go package sources are intended to serve not only as the core library but also as examples of how to use the language. Moreover, many of the packages contain working, self-contained executable examples you can run directly from the golang.org web site, such as this one (click on the word "Example" to open it up). If you have a question about how to approach a problem or how something might be implemented, the documentation, code and examples in the library can provide answers, ideas and background.`},
|
||||||
&Post{User: users[1], Tags: []*Tag{tags[0], tags[2]}, Title: "Formatting", Content: `Formatting issues are the most contentious but the least consequential. People can adapt to different formatting styles but it's better if they don't have to, and less time is devoted to the topic if everyone adheres to the same style. The problem is how to approach this Utopia without a long prescriptive style guide.
|
{User: users[1], Tags: []*Tag{tags[0], tags[2]}, Title: "Formatting", Content: `Formatting issues are the most contentious but the least consequential. People can adapt to different formatting styles but it's better if they don't have to, and less time is devoted to the topic if everyone adheres to the same style. The problem is how to approach this Utopia without a long prescriptive style guide.
|
||||||
With Go we take an unusual approach and let the machine take care of most formatting issues. The gofmt program (also available as go fmt, which operates at the package level rather than source file level) reads a Go program and emits the source in a standard style of indentation and vertical alignment, retaining and if necessary reformatting comments. If you want to know how to handle some new layout situation, run gofmt; if the answer doesn't seem right, rearrange your program (or file a bug about gofmt), don't work around it.`},
|
With Go we take an unusual approach and let the machine take care of most formatting issues. The gofmt program (also available as go fmt, which operates at the package level rather than source file level) reads a Go program and emits the source in a standard style of indentation and vertical alignment, retaining and if necessary reformatting comments. If you want to know how to handle some new layout situation, run gofmt; if the answer doesn't seem right, rearrange your program (or file a bug about gofmt), don't work around it.`},
|
||||||
&Post{User: users[2], Tags: []*Tag{tags[3]}, Title: "Commentary", Content: `Go provides C-style /* */ block comments and C++-style // line comments. Line comments are the norm; block comments appear mostly as package comments, but are useful within an expression or to disable large swaths of code.
|
{User: users[2], Tags: []*Tag{tags[3]}, Title: "Commentary", Content: `Go provides C-style /* */ block comments and C++-style // line comments. Line comments are the norm; block comments appear mostly as package comments, but are useful within an expression or to disable large swaths of code.
|
||||||
The program—and web server—godoc processes Go source files to extract documentation about the contents of the package. Comments that appear before top-level declarations, with no intervening newlines, are extracted along with the declaration to serve as explanatory text for the item. The nature and style of these comments determines the quality of the documentation godoc produces.`},
|
The program—and web server—godoc processes Go source files to extract documentation about the contents of the package. Comments that appear before top-level declarations, with no intervening newlines, are extracted along with the declaration to serve as explanatory text for the item. The nature and style of these comments determines the quality of the documentation godoc produces.`},
|
||||||
}
|
}
|
||||||
|
|
||||||
comments := []*Comment{
|
comments := []*Comment{
|
||||||
&Comment{Post: posts[0], Content: "a comment"},
|
{Post: posts[0], Content: "a comment"},
|
||||||
&Comment{Post: posts[1], Content: "yes"},
|
{Post: posts[1], Content: "yes"},
|
||||||
&Comment{Post: posts[1]},
|
{Post: posts[1]},
|
||||||
&Comment{Post: posts[1]},
|
{Post: posts[1]},
|
||||||
&Comment{Post: posts[2]},
|
{Post: posts[2]},
|
||||||
&Comment{Post: posts[2]},
|
{Post: posts[2]},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
@ -1248,7 +1248,7 @@ func TestQueryM2M(t *testing.T) {
|
|||||||
post := Post{Id: 4}
|
post := Post{Id: 4}
|
||||||
m2m := dORM.QueryM2M(&post, "Tags")
|
m2m := dORM.QueryM2M(&post, "Tags")
|
||||||
|
|
||||||
tag1 := []*Tag{&Tag{Name: "TestTag1"}, &Tag{Name: "TestTag2"}}
|
tag1 := []*Tag{{Name: "TestTag1"}, {Name: "TestTag2"}}
|
||||||
tag2 := &Tag{Name: "TestTag3"}
|
tag2 := &Tag{Name: "TestTag3"}
|
||||||
tag3 := []interface{}{&Tag{Name: "TestTag4"}}
|
tag3 := []interface{}{&Tag{Name: "TestTag4"}}
|
||||||
|
|
||||||
@ -1311,7 +1311,7 @@ func TestQueryM2M(t *testing.T) {
|
|||||||
|
|
||||||
m2m = dORM.QueryM2M(&tag, "Posts")
|
m2m = dORM.QueryM2M(&tag, "Posts")
|
||||||
|
|
||||||
post1 := []*Post{&Post{Title: "TestPost1"}, &Post{Title: "TestPost2"}}
|
post1 := []*Post{{Title: "TestPost1"}, {Title: "TestPost2"}}
|
||||||
post2 := &Post{Title: "TestPost3"}
|
post2 := &Post{Title: "TestPost3"}
|
||||||
post3 := []interface{}{&Post{Title: "TestPost4"}}
|
post3 := []interface{}{&Post{Title: "TestPost4"}}
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ func snakeString(s string) string {
|
|||||||
}
|
}
|
||||||
data = append(data, d)
|
data = append(data, d)
|
||||||
}
|
}
|
||||||
return strings.ToLower(string(data[:len(data)]))
|
return strings.ToLower(string(data[:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// camel string, xx_yy to XxYy
|
// camel string, xx_yy to XxYy
|
||||||
@ -220,7 +220,7 @@ func camelString(s string) string {
|
|||||||
}
|
}
|
||||||
data = append(data, d)
|
data = append(data, d)
|
||||||
}
|
}
|
||||||
return string(data[:len(data)])
|
return string(data[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
type argString []string
|
type argString []string
|
||||||
|
11
parser.go
11
parser.go
@ -24,6 +24,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils"
|
"github.com/astaxie/beego/utils"
|
||||||
@ -36,7 +37,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
{{.globalinfo}}
|
{{.globalinfo}}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -129,7 +130,13 @@ func genRouterCode() {
|
|||||||
os.Mkdir(path.Join(workPath, "routers"), 0755)
|
os.Mkdir(path.Join(workPath, "routers"), 0755)
|
||||||
Info("generate router from comments")
|
Info("generate router from comments")
|
||||||
var globalinfo string
|
var globalinfo string
|
||||||
for k, cList := range genInfoList {
|
sortKey := make([]string, 0)
|
||||||
|
for k, _ := range genInfoList {
|
||||||
|
sortKey = append(sortKey, k)
|
||||||
|
}
|
||||||
|
sort.Strings(sortKey)
|
||||||
|
for _, k := range sortKey {
|
||||||
|
cList := genInfoList[k]
|
||||||
for _, c := range cList {
|
for _, c := range cList {
|
||||||
allmethod := "nil"
|
allmethod := "nil"
|
||||||
if len(c.AllowHTTPMethods) > 0 {
|
if len(c.AllowHTTPMethods) > 0 {
|
||||||
|
51
router.go
51
router.go
@ -88,7 +88,7 @@ func (l *logFilter) Filter(ctx *beecontext.Context) bool {
|
|||||||
if requestPath == "/favicon.ico" || requestPath == "/robots.txt" {
|
if requestPath == "/favicon.ico" || requestPath == "/robots.txt" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for prefix, _ := range StaticDir {
|
for prefix := range StaticDir {
|
||||||
if strings.HasPrefix(requestPath, prefix) {
|
if strings.HasPrefix(requestPath, prefix) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingM
|
|||||||
p.addToRouter(m, pattern, route)
|
p.addToRouter(m, pattern, route)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for k, _ := range methods {
|
for k := range methods {
|
||||||
if k == "*" {
|
if k == "*" {
|
||||||
for _, m := range HTTPMETHOD {
|
for _, m := range HTTPMETHOD {
|
||||||
p.addToRouter(m, pattern, route)
|
p.addToRouter(m, pattern, route)
|
||||||
@ -332,7 +332,7 @@ func (p *ControllerRegistor) AddMethod(method, pattern string, f FilterFunc) {
|
|||||||
methods[strings.ToUpper(method)] = strings.ToUpper(method)
|
methods[strings.ToUpper(method)] = strings.ToUpper(method)
|
||||||
}
|
}
|
||||||
route.methods = methods
|
route.methods = methods
|
||||||
for k, _ := range methods {
|
for k := range methods {
|
||||||
if k == "*" {
|
if k == "*" {
|
||||||
for _, m := range HTTPMETHOD {
|
for _, m := range HTTPMETHOD {
|
||||||
p.addToRouter(m, pattern, route)
|
p.addToRouter(m, pattern, route)
|
||||||
@ -611,17 +611,21 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
if p.enableFilter {
|
if p.enableFilter {
|
||||||
if l, ok := p.filters[pos]; ok {
|
if l, ok := p.filters[pos]; ok {
|
||||||
for _, filterR := range l {
|
for _, filterR := range l {
|
||||||
if ok, p := filterR.ValidRouter(urlPath); ok {
|
if filterR.returnOnOutput && w.started {
|
||||||
context.Input.Params = p
|
return true
|
||||||
|
}
|
||||||
|
if ok, params := filterR.ValidRouter(urlPath); ok {
|
||||||
|
for k, v := range params {
|
||||||
|
context.Input.Params[k] = v
|
||||||
|
}
|
||||||
filterR.filterFunc(context)
|
filterR.filterFunc(context)
|
||||||
|
}
|
||||||
if filterR.returnOnOutput && w.started {
|
if filterR.returnOnOutput && w.started {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -857,13 +861,12 @@ func (p *ControllerRegistor) recoverPanic(context *beecontext.Context) {
|
|||||||
if err == USERSTOPRUN {
|
if err == USERSTOPRUN {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if RunMode == "dev" {
|
|
||||||
if !RecoverPanic {
|
if !RecoverPanic {
|
||||||
panic(err)
|
panic(err)
|
||||||
} else {
|
} else {
|
||||||
if ErrorsShow {
|
if ErrorsShow {
|
||||||
if handler, ok := ErrorMaps[fmt.Sprint(err)]; ok {
|
if _, ok := ErrorMaps[fmt.Sprint(err)]; ok {
|
||||||
executeError(handler, context)
|
exception(fmt.Sprint(err), context)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -878,35 +881,9 @@ func (p *ControllerRegistor) recoverPanic(context *beecontext.Context) {
|
|||||||
Critical(fmt.Sprintf("%s:%d", file, line))
|
Critical(fmt.Sprintf("%s:%d", file, line))
|
||||||
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
|
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
|
||||||
}
|
}
|
||||||
|
if RunMode == "dev" {
|
||||||
showErr(err, context, stack)
|
showErr(err, context, stack)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if !RecoverPanic {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
// in production model show all infomation
|
|
||||||
if ErrorsShow {
|
|
||||||
if handler, ok := ErrorMaps[fmt.Sprint(err)]; ok {
|
|
||||||
executeError(handler, context)
|
|
||||||
return
|
|
||||||
} else if handler, ok := ErrorMaps["503"]; ok {
|
|
||||||
executeError(handler, context)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
context.WriteString(fmt.Sprint(err))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Critical("the request url is ", context.Input.Url())
|
|
||||||
Critical("Handler crashed with error", err)
|
|
||||||
for i := 1; ; i++ {
|
|
||||||
_, file, line, ok := runtime.Caller(i)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
Critical(fmt.Sprintf("%s:%d", file, line))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,7 +444,7 @@ func TestFilterAfterExec(t *testing.T) {
|
|||||||
mux := NewControllerRegister()
|
mux := NewControllerRegister()
|
||||||
mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput)
|
mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput)
|
||||||
mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput)
|
mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput)
|
||||||
mux.InsertFilter(url, AfterExec, beegoAfterExec1)
|
mux.InsertFilter(url, AfterExec, beegoAfterExec1, false)
|
||||||
|
|
||||||
mux.Get(url, beegoFilterFunc)
|
mux.Get(url, beegoFilterFunc)
|
||||||
|
|
||||||
@ -506,7 +506,7 @@ func TestFilterFinishRouterMultiFirstOnly(t *testing.T) {
|
|||||||
url := "/finishRouterMultiFirstOnly"
|
url := "/finishRouterMultiFirstOnly"
|
||||||
|
|
||||||
mux := NewControllerRegister()
|
mux := NewControllerRegister()
|
||||||
mux.InsertFilter(url, FinishRouter, beegoFinishRouter1)
|
mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, false)
|
||||||
mux.InsertFilter(url, FinishRouter, beegoFinishRouter2)
|
mux.InsertFilter(url, FinishRouter, beegoFinishRouter2)
|
||||||
|
|
||||||
mux.Get(url, beegoFilterFunc)
|
mux.Get(url, beegoFilterFunc)
|
||||||
@ -534,7 +534,7 @@ func TestFilterFinishRouterMulti(t *testing.T) {
|
|||||||
|
|
||||||
mux := NewControllerRegister()
|
mux := NewControllerRegister()
|
||||||
mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, false)
|
mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, false)
|
||||||
mux.InsertFilter(url, FinishRouter, beegoFinishRouter2)
|
mux.InsertFilter(url, FinishRouter, beegoFinishRouter2, false)
|
||||||
|
|
||||||
mux.Get(url, beegoFilterFunc)
|
mux.Get(url, beegoFilterFunc)
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ type LedisProvider struct {
|
|||||||
func (lp *LedisProvider) SessionInit(maxlifetime int64, savePath string) error {
|
func (lp *LedisProvider) SessionInit(maxlifetime int64, savePath string) error {
|
||||||
var err error
|
var err error
|
||||||
lp.maxlifetime = maxlifetime
|
lp.maxlifetime = maxlifetime
|
||||||
configs := strings.Split(savepath, ",")
|
configs := strings.Split(savePath, ",")
|
||||||
if len(configs) == 1 {
|
if len(configs) == 1 {
|
||||||
lp.savePath = configs[0]
|
lp.savePath = configs[0]
|
||||||
} else if len(configs) == 2 {
|
} else if len(configs) == 2 {
|
||||||
|
@ -129,8 +129,9 @@ func (rp *MemProvider) SessionRead(sid string) (session.SessionStore, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
item, err := client.Get(sid)
|
item, err := client.Get(sid)
|
||||||
if err != nil {
|
if err != nil && err == memcache.ErrCacheMiss {
|
||||||
return nil, err
|
rs := &MemcacheSessionStore{sid: sid, values: make(map[interface{}]interface{}), maxlifetime: rp.maxlifetime}
|
||||||
|
return rs, nil
|
||||||
}
|
}
|
||||||
var kv map[interface{}]interface{}
|
var kv map[interface{}]interface{}
|
||||||
if len(item.Value) == 0 {
|
if len(item.Value) == 0 {
|
||||||
@ -141,7 +142,6 @@ func (rp *MemProvider) SessionRead(sid string) (session.SessionStore, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rs := &MemcacheSessionStore{sid: sid, values: kv, maxlifetime: rp.maxlifetime}
|
rs := &MemcacheSessionStore{sid: sid, values: kv, maxlifetime: rp.maxlifetime}
|
||||||
return rs, nil
|
return rs, nil
|
||||||
}
|
}
|
||||||
@ -179,7 +179,6 @@ func (rp *MemProvider) SessionRegenerate(oldsid, sid string) (session.SessionSto
|
|||||||
} else {
|
} else {
|
||||||
client.Delete(oldsid)
|
client.Delete(oldsid)
|
||||||
item.Key = sid
|
item.Key = sid
|
||||||
item.Value = item.Value
|
|
||||||
item.Expiration = int32(rp.maxlifetime)
|
item.Expiration = int32(rp.maxlifetime)
|
||||||
client.Set(item)
|
client.Set(item)
|
||||||
contain = item.Value
|
contain = item.Value
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
// mysql session support need create table as sql:
|
// mysql session support need create table as sql:
|
||||||
// CREATE TABLE `session` (
|
// CREATE TABLE `session` (
|
||||||
// `session_key` char(64) NOT NULL,
|
// `session_key` char(64) NOT NULL,
|
||||||
// session_data` blob,
|
// `session_data` blob,
|
||||||
// `session_expiry` int(11) unsigned NOT NULL,
|
// `session_expiry` int(11) unsigned NOT NULL,
|
||||||
// PRIMARY KEY (`session_key`)
|
// PRIMARY KEY (`session_key`)
|
||||||
// ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
// ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||||
|
@ -145,7 +145,7 @@ func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error {
|
|||||||
rp.password = configs[2]
|
rp.password = configs[2]
|
||||||
}
|
}
|
||||||
if len(configs) > 3 {
|
if len(configs) > 3 {
|
||||||
dbnum, err := strconv.Atoi(configs[1])
|
dbnum, err := strconv.Atoi(configs[3])
|
||||||
if err != nil || dbnum < 0 {
|
if err != nil || dbnum < 0 {
|
||||||
rp.dbNum = 0
|
rp.dbNum = 0
|
||||||
} else {
|
} else {
|
||||||
|
@ -143,14 +143,17 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se
|
|||||||
return nil, errs
|
return nil, errs
|
||||||
}
|
}
|
||||||
session, err = manager.provider.SessionRead(sid)
|
session, err = manager.provider.SessionRead(sid)
|
||||||
cookie = &http.Cookie{Name: manager.config.CookieName,
|
cookie = &http.Cookie{
|
||||||
|
Name: manager.config.CookieName,
|
||||||
Value: url.QueryEscape(sid),
|
Value: url.QueryEscape(sid),
|
||||||
Path: "/",
|
Path: "/",
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: manager.config.Secure,
|
Secure: manager.isSecure(r),
|
||||||
Domain: manager.config.Domain}
|
Domain: manager.config.Domain,
|
||||||
if manager.config.CookieLifeTime >= 0 {
|
}
|
||||||
|
if manager.config.CookieLifeTime > 0 {
|
||||||
cookie.MaxAge = manager.config.CookieLifeTime
|
cookie.MaxAge = manager.config.CookieLifeTime
|
||||||
|
cookie.Expires = time.Now().Add(time.Duration(manager.config.CookieLifeTime) * time.Second)
|
||||||
}
|
}
|
||||||
if manager.config.EnableSetCookie {
|
if manager.config.EnableSetCookie {
|
||||||
http.SetCookie(w, cookie)
|
http.SetCookie(w, cookie)
|
||||||
@ -169,14 +172,17 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
session, err = manager.provider.SessionRead(sid)
|
session, err = manager.provider.SessionRead(sid)
|
||||||
cookie = &http.Cookie{Name: manager.config.CookieName,
|
cookie = &http.Cookie{
|
||||||
|
Name: manager.config.CookieName,
|
||||||
Value: url.QueryEscape(sid),
|
Value: url.QueryEscape(sid),
|
||||||
Path: "/",
|
Path: "/",
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: manager.config.Secure,
|
Secure: manager.isSecure(r),
|
||||||
Domain: manager.config.Domain}
|
Domain: manager.config.Domain,
|
||||||
if manager.config.CookieLifeTime >= 0 {
|
}
|
||||||
|
if manager.config.CookieLifeTime > 0 {
|
||||||
cookie.MaxAge = manager.config.CookieLifeTime
|
cookie.MaxAge = manager.config.CookieLifeTime
|
||||||
|
cookie.Expires = time.Now().Add(time.Duration(manager.config.CookieLifeTime) * time.Second)
|
||||||
}
|
}
|
||||||
if manager.config.EnableSetCookie {
|
if manager.config.EnableSetCookie {
|
||||||
http.SetCookie(w, cookie)
|
http.SetCookie(w, cookie)
|
||||||
@ -231,7 +237,7 @@ func (manager *Manager) SessionRegenerateId(w http.ResponseWriter, r *http.Reque
|
|||||||
Value: url.QueryEscape(sid),
|
Value: url.QueryEscape(sid),
|
||||||
Path: "/",
|
Path: "/",
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: manager.config.Secure,
|
Secure: manager.isSecure(r),
|
||||||
Domain: manager.config.Domain,
|
Domain: manager.config.Domain,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -241,8 +247,9 @@ func (manager *Manager) SessionRegenerateId(w http.ResponseWriter, r *http.Reque
|
|||||||
cookie.HttpOnly = true
|
cookie.HttpOnly = true
|
||||||
cookie.Path = "/"
|
cookie.Path = "/"
|
||||||
}
|
}
|
||||||
if manager.config.CookieLifeTime >= 0 {
|
if manager.config.CookieLifeTime > 0 {
|
||||||
cookie.MaxAge = manager.config.CookieLifeTime
|
cookie.MaxAge = manager.config.CookieLifeTime
|
||||||
|
cookie.Expires = time.Now().Add(time.Duration(manager.config.CookieLifeTime) * time.Second)
|
||||||
}
|
}
|
||||||
http.SetCookie(w, cookie)
|
http.SetCookie(w, cookie)
|
||||||
r.AddCookie(cookie)
|
r.AddCookie(cookie)
|
||||||
@ -267,3 +274,17 @@ func (manager *Manager) sessionId(r *http.Request) (string, error) {
|
|||||||
}
|
}
|
||||||
return hex.EncodeToString(b), nil
|
return hex.EncodeToString(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set cookie with https.
|
||||||
|
func (manager *Manager) isSecure(req *http.Request) bool {
|
||||||
|
if !manager.config.Secure {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if req.URL.Scheme != "" {
|
||||||
|
return req.URL.Scheme == "https"
|
||||||
|
}
|
||||||
|
if req.TLS == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -58,7 +58,7 @@ func serverStaticRouter(ctx *context.Context) {
|
|||||||
finfo, err := os.Stat(file)
|
finfo, err := os.Stat(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if RunMode == "dev" {
|
if RunMode == "dev" {
|
||||||
Warn(err)
|
Warn("Can't find the file:", file, err)
|
||||||
}
|
}
|
||||||
http.NotFound(ctx.ResponseWriter, ctx.Request)
|
http.NotFound(ctx.ResponseWriter, ctx.Request)
|
||||||
return
|
return
|
||||||
|
26
tree.go
26
tree.go
@ -70,18 +70,23 @@ func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg st
|
|||||||
} else {
|
} else {
|
||||||
regexpStr = "/" + regexpStr
|
regexpStr = "/" + regexpStr
|
||||||
}
|
}
|
||||||
|
} else if reg != "" {
|
||||||
|
if seg == "*.*" {
|
||||||
|
regexpStr = "([^.]+).(.+)"
|
||||||
} else {
|
} else {
|
||||||
for _, w := range wildcards {
|
for _, w := range params {
|
||||||
if w == "." || w == ":" {
|
if w == "." || w == ":" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
regexpStr = "([^/]+)/" + regexpStr
|
regexpStr = "([^/]+)/" + regexpStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reg = strings.Trim(reg+regexpStr, "/")
|
}
|
||||||
|
reg = strings.Trim(reg+"/"+regexpStr, "/")
|
||||||
filterTreeWithPrefix(tree, append(wildcards, params...), reg)
|
filterTreeWithPrefix(tree, append(wildcards, params...), reg)
|
||||||
t.wildcard = tree
|
t.wildcard = tree
|
||||||
} else {
|
} else {
|
||||||
|
reg = strings.Trim(reg+"/"+regexpStr, "/")
|
||||||
filterTreeWithPrefix(tree, append(wildcards, params...), reg)
|
filterTreeWithPrefix(tree, append(wildcards, params...), reg)
|
||||||
t.fixrouters[seg] = tree
|
t.fixrouters[seg] = tree
|
||||||
}
|
}
|
||||||
@ -104,23 +109,23 @@ func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg st
|
|||||||
rr = rr + "([^/]+)/"
|
rr = rr + "([^/]+)/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
regexpStr = rr + regexpStr + "/"
|
regexpStr = rr + regexpStr
|
||||||
} else {
|
} else {
|
||||||
regexpStr = "/" + regexpStr + "/"
|
regexpStr = "/" + regexpStr
|
||||||
}
|
}
|
||||||
|
} else if reg != "" {
|
||||||
|
if seg == "*.*" {
|
||||||
|
regexpStr = "([^.]+).(.+)"
|
||||||
} else {
|
} else {
|
||||||
for _, w := range wildcards {
|
for _, w := range params {
|
||||||
if w == "." || w == ":" {
|
if w == "." || w == ":" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if w == ":splat" {
|
|
||||||
regexpStr = "(.+)/" + regexpStr
|
|
||||||
} else {
|
|
||||||
regexpStr = "([^/]+)/" + regexpStr
|
regexpStr = "([^/]+)/" + regexpStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reg = reg + regexpStr
|
reg = strings.TrimRight(strings.TrimRight(reg, "/")+"/"+regexpStr, "/")
|
||||||
t.wildcard.addtree(segments[1:], tree, append(wildcards, params...), reg)
|
t.wildcard.addtree(segments[1:], tree, append(wildcards, params...), reg)
|
||||||
} else {
|
} else {
|
||||||
subTree := NewTree()
|
subTree := NewTree()
|
||||||
@ -140,7 +145,7 @@ func filterTreeWithPrefix(t *Tree, wildcards []string, reg string) {
|
|||||||
if reg != "" {
|
if reg != "" {
|
||||||
if l.regexps != nil {
|
if l.regexps != nil {
|
||||||
l.wildcards = append(wildcards, l.wildcards...)
|
l.wildcards = append(wildcards, l.wildcards...)
|
||||||
l.regexps = regexp.MustCompile("^" + reg + strings.Trim(l.regexps.String(), "^$") + "$")
|
l.regexps = regexp.MustCompile("^" + reg + "/" + strings.Trim(l.regexps.String(), "^$") + "$")
|
||||||
} else {
|
} else {
|
||||||
for _, v := range l.wildcards {
|
for _, v := range l.wildcards {
|
||||||
if v == ":" || v == "." {
|
if v == ":" || v == "." {
|
||||||
@ -248,7 +253,6 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string,
|
|||||||
regexpStr = "/([^/]+)" + regexpStr
|
regexpStr = "/([^/]+)" + regexpStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
t.wildcard.addseg(segments[1:], route, append(wildcards, params...), reg+regexpStr)
|
t.wildcard.addseg(segments[1:], route, append(wildcards, params...), reg+regexpStr)
|
||||||
} else {
|
} else {
|
||||||
|
44
tree_test.go
44
tree_test.go
@ -148,6 +148,50 @@ func TestAddTree2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddTree3(t *testing.T) {
|
||||||
|
tr := NewTree()
|
||||||
|
tr.AddRouter("/create", "astaxie")
|
||||||
|
tr.AddRouter("/shop/:sd/account", "astaxie")
|
||||||
|
t3 := NewTree()
|
||||||
|
t3.AddTree("/table/:num", tr)
|
||||||
|
obj, param := t3.Match("/table/123/shop/123/account")
|
||||||
|
if obj == nil || obj.(string) != "astaxie" {
|
||||||
|
t.Fatal("/table/:num/shop/:sd/account can't get obj ")
|
||||||
|
}
|
||||||
|
if param == nil {
|
||||||
|
t.Fatal("get param error")
|
||||||
|
}
|
||||||
|
if param[":num"] != "123" || param[":sd"] != "123" {
|
||||||
|
t.Fatal("get :num :sd param error")
|
||||||
|
}
|
||||||
|
obj, param = t3.Match("/table/123/create")
|
||||||
|
if obj == nil || obj.(string) != "astaxie" {
|
||||||
|
t.Fatal("/table/:num/create can't get obj ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddTree4(t *testing.T) {
|
||||||
|
tr := NewTree()
|
||||||
|
tr.AddRouter("/create", "astaxie")
|
||||||
|
tr.AddRouter("/shop/:sd/:account", "astaxie")
|
||||||
|
t4 := NewTree()
|
||||||
|
t4.AddTree("/:info:int/:num/:id", tr)
|
||||||
|
obj, param := t4.Match("/12/123/456/shop/123/account")
|
||||||
|
if obj == nil || obj.(string) != "astaxie" {
|
||||||
|
t.Fatal("/:info:int/:num/:id/shop/:sd/:account can't get obj ")
|
||||||
|
}
|
||||||
|
if param == nil {
|
||||||
|
t.Fatal("get param error")
|
||||||
|
}
|
||||||
|
if param[":info"] != "12" || param[":num"] != "123" || param[":id"] != "456" || param[":sd"] != "123" || param[":account"] != "account" {
|
||||||
|
t.Fatal("get :info :num :id :sd :account param error")
|
||||||
|
}
|
||||||
|
obj, param = t4.Match("/12/123/456/create")
|
||||||
|
if obj == nil || obj.(string) != "astaxie" {
|
||||||
|
t.Fatal("/:info:int/:num/:id/create can't get obj ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSplitPath(t *testing.T) {
|
func TestSplitPath(t *testing.T) {
|
||||||
a := splitPath("")
|
a := splitPath("")
|
||||||
if len(a) != 0 {
|
if len(a) != 0 {
|
||||||
|
19
utils/captcha/LICENSE
Normal file
19
utils/captcha/LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2011-2014 Dmitry Chestnykh <dmitry@codingrobots.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
@ -132,15 +132,6 @@ func (c *Captcha) Handler(ctx *context.Context) {
|
|||||||
|
|
||||||
key := c.key(id)
|
key := c.key(id)
|
||||||
|
|
||||||
if v, ok := c.store.Get(key).([]byte); ok {
|
|
||||||
chars = v
|
|
||||||
} else {
|
|
||||||
ctx.Output.SetStatus(404)
|
|
||||||
ctx.WriteString("captcha not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// reload captcha
|
|
||||||
if len(ctx.Input.Query("reload")) > 0 {
|
if len(ctx.Input.Query("reload")) > 0 {
|
||||||
chars = c.genRandChars()
|
chars = c.genRandChars()
|
||||||
if err := c.store.Put(key, chars, c.Expiration); err != nil {
|
if err := c.store.Put(key, chars, c.Expiration); err != nil {
|
||||||
@ -149,6 +140,14 @@ func (c *Captcha) Handler(ctx *context.Context) {
|
|||||||
beego.Error("Reload Create Captcha Error:", err)
|
beego.Error("Reload Create Captcha Error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if v, ok := c.store.Get(key).([]byte); ok {
|
||||||
|
chars = v
|
||||||
|
} else {
|
||||||
|
ctx.Output.SetStatus(404)
|
||||||
|
ctx.WriteString("captcha not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img := NewImage(chars, c.StdWidth, c.StdHeight)
|
img := NewImage(chars, c.StdWidth, c.StdHeight)
|
||||||
|
@ -292,7 +292,7 @@ func qpEscape(dest []byte, c byte) {
|
|||||||
const nums = "0123456789ABCDEF"
|
const nums = "0123456789ABCDEF"
|
||||||
dest[0] = '='
|
dest[0] = '='
|
||||||
dest[1] = nums[(c&0xf0)>>4]
|
dest[1] = nums[(c&0xf0)>>4]
|
||||||
dest[2] = nums[(c&0xf)]
|
dest[2] = nums[(c & 0xf)]
|
||||||
}
|
}
|
||||||
|
|
||||||
// headerToBytes enumerates the key and values in the header, and writes the results to the IO Writer
|
// headerToBytes enumerates the key and values in the header, and writes the results to the IO Writer
|
||||||
|
@ -92,18 +92,18 @@ func (p *Paginator) Pages() []int {
|
|||||||
case page >= pageNums-4 && pageNums > 9:
|
case page >= pageNums-4 && pageNums > 9:
|
||||||
start := pageNums - 9 + 1
|
start := pageNums - 9 + 1
|
||||||
pages = make([]int, 9)
|
pages = make([]int, 9)
|
||||||
for i, _ := range pages {
|
for i := range pages {
|
||||||
pages[i] = start + i
|
pages[i] = start + i
|
||||||
}
|
}
|
||||||
case page >= 5 && pageNums > 9:
|
case page >= 5 && pageNums > 9:
|
||||||
start := page - 5 + 1
|
start := page - 5 + 1
|
||||||
pages = make([]int, int(math.Min(9, float64(page+4+1))))
|
pages = make([]int, int(math.Min(9, float64(page+4+1))))
|
||||||
for i, _ := range pages {
|
for i := range pages {
|
||||||
pages[i] = start + i
|
pages[i] = start + i
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
pages = make([]int, int(math.Min(9, float64(pageNums))))
|
pages = make([]int, int(math.Min(9, float64(pageNums))))
|
||||||
for i, _ := range pages {
|
for i := range pages {
|
||||||
pages[i] = i + 1
|
pages[i] = i + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ func SliceRandList(min, max int) []int {
|
|||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
rand.Seed(int64(t0.Nanosecond()))
|
rand.Seed(int64(t0.Nanosecond()))
|
||||||
list := rand.Perm(length)
|
list := rand.Perm(length)
|
||||||
for index, _ := range list {
|
for index := range list {
|
||||||
list[index] += min
|
list[index] += min
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
|
@ -137,7 +137,7 @@ func getRegFuncs(tag, key string) (vfs []ValidFunc, str string, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
vfs = []ValidFunc{ValidFunc{"Match", []interface{}{reg, key + ".Match"}}}
|
vfs = []ValidFunc{{"Match", []interface{}{reg, key + ".Match"}}}
|
||||||
str = strings.TrimSpace(tag[:index]) + strings.TrimSpace(tag[end+len("/)"):])
|
str = strings.TrimSpace(tag[:index]) + strings.TrimSpace(tag[end+len("/)"):])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,7 @@ type Validation struct {
|
|||||||
// Clean all ValidationError.
|
// Clean all ValidationError.
|
||||||
func (v *Validation) Clear() {
|
func (v *Validation) Clear() {
|
||||||
v.Errors = []*ValidationError{}
|
v.Errors = []*ValidationError{}
|
||||||
|
v.ErrorsMap = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has ValidationError nor not.
|
// Has ValidationError nor not.
|
||||||
@ -333,3 +334,41 @@ func (v *Validation) Valid(obj interface{}) (b bool, err error) {
|
|||||||
|
|
||||||
return !v.HasErrors(), nil
|
return !v.HasErrors(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recursively validate a struct.
|
||||||
|
// Step1: Validate by v.Valid
|
||||||
|
// Step2: If pass on step1, then reflect obj's fields
|
||||||
|
// Step3: Do the Recursively validation to all struct or struct pointer fields
|
||||||
|
func (v *Validation) RecursiveValid(objc interface{}) (bool, error) {
|
||||||
|
//Step 1: validate obj itself firstly
|
||||||
|
// fails if objc is not struct
|
||||||
|
pass, err := v.Valid(objc)
|
||||||
|
if err != nil || false == pass {
|
||||||
|
return pass, err // Stop recursive validation
|
||||||
|
} else { //pass
|
||||||
|
// Step 2: Validate struct's struct fields
|
||||||
|
objT := reflect.TypeOf(objc)
|
||||||
|
objV := reflect.ValueOf(objc)
|
||||||
|
|
||||||
|
if isStructPtr(objT) {
|
||||||
|
objT = objT.Elem()
|
||||||
|
objV = objV.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < objT.NumField(); i++ {
|
||||||
|
|
||||||
|
t := objT.Field(i).Type
|
||||||
|
|
||||||
|
// Recursive applies to struct or pointer to structs fields
|
||||||
|
if isStruct(t) || isStructPtr(t) {
|
||||||
|
// Step 3: do the recursive validation
|
||||||
|
// Only valid the Public field recursively
|
||||||
|
if objV.Field(i).CanInterface() {
|
||||||
|
pass, err = v.RecursiveValid(objV.Field(i).Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pass, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -26,6 +26,12 @@ func TestRequired(t *testing.T) {
|
|||||||
if valid.Required(nil, "nil").Ok {
|
if valid.Required(nil, "nil").Ok {
|
||||||
t.Error("nil object should be false")
|
t.Error("nil object should be false")
|
||||||
}
|
}
|
||||||
|
if !valid.Required(true, "bool").Ok {
|
||||||
|
t.Error("Bool value should always return true")
|
||||||
|
}
|
||||||
|
if !valid.Required(false, "bool").Ok {
|
||||||
|
t.Error("Bool value should always return true")
|
||||||
|
}
|
||||||
if valid.Required("", "string").Ok {
|
if valid.Required("", "string").Ok {
|
||||||
t.Error("\"'\" string should be false")
|
t.Error("\"'\" string should be false")
|
||||||
}
|
}
|
||||||
@ -343,3 +349,33 @@ func TestValid(t *testing.T) {
|
|||||||
t.Errorf("Message key should be `Name.Match` but got %s", valid.Errors[0].Key)
|
t.Errorf("Message key should be `Name.Match` but got %s", valid.Errors[0].Key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRecursiveValid(t *testing.T) {
|
||||||
|
type User struct {
|
||||||
|
Id int
|
||||||
|
Name string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"`
|
||||||
|
Age int `valid:"Required;Range(1, 140)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnonymouseUser struct {
|
||||||
|
Id2 int
|
||||||
|
Name2 string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"`
|
||||||
|
Age2 int `valid:"Required;Range(1, 140)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
Password string `valid:"Required"`
|
||||||
|
U User
|
||||||
|
AnonymouseUser
|
||||||
|
}
|
||||||
|
valid := Validation{}
|
||||||
|
|
||||||
|
u := Account{Password: "abc123_", U: User{}}
|
||||||
|
b, err := valid.RecursiveValid(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
t.Error("validation should not be passed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -64,8 +64,8 @@ func (r Required) IsSatisfied(obj interface{}) bool {
|
|||||||
if str, ok := obj.(string); ok {
|
if str, ok := obj.(string); ok {
|
||||||
return len(str) > 0
|
return len(str) > 0
|
||||||
}
|
}
|
||||||
if b, ok := obj.(bool); ok {
|
if _, ok := obj.(bool); ok {
|
||||||
return b
|
return true
|
||||||
}
|
}
|
||||||
if i, ok := obj.(int); ok {
|
if i, ok := obj.(int); ok {
|
||||||
return i != 0
|
return i != 0
|
||||||
@ -457,7 +457,7 @@ func (b Base64) GetLimitValue() interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// just for chinese mobile phone number
|
// just for chinese mobile phone number
|
||||||
var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|(47)|[8][012356789]))\\d{8}$")
|
var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][67]|[4][579]))\\d{8}$")
|
||||||
|
|
||||||
type Mobile struct {
|
type Mobile struct {
|
||||||
Match
|
Match
|
||||||
|
Reference in New Issue
Block a user