1
0
mirror of https://github.com/astaxie/beego.git synced 2025-07-11 09:21:02 +00:00

27 Commits

Author SHA1 Message Date
3b6d634f55 Merge pull request #4 from flycash/fix-bug-1.x
Fix BUG: /abc.html/aaa match /abc/aaa
2021-01-25 23:58:18 +08:00
d0847e866e Fix BUG: /abc.html/aaa match /abc/aaa 2021-01-25 23:56:52 +08:00
19e6ba8e7c Merge pull request #3 from flycash/develop-1
Remove duration from prometheus label
2020-12-26 21:56:49 +08:00
5fddd26069 Remove duration from prometheus label 2020-12-26 21:54:57 +08:00
89d4c9a3df Merge pull request #2 from flycash/develop-1
Fix generated code 4384
2020-12-26 21:32:13 +08:00
db6af39bcd Using commentRouter.go as generated file name 2020-12-22 21:29:23 +08:00
9418b243d0 Fix generated code 4384 2020-12-22 21:19:13 +08:00
3d93903f9e Merge pull request #4324 from flycash/fix4321
Expose error from SessionRegenerateID
2020-11-26 18:06:40 +08:00
2781f88173 Expose error from SessionRegenerateID 2020-11-25 21:13:04 +08:00
cbbb6bfb08 Merge pull request #4284 from flycash/develop
Upgrade version
2020-11-03 22:49:59 +08:00
b2a96234ab Upgrade version 2020-11-03 22:21:32 +08:00
db3defa76a Merge pull request #4261 from flycash/reduceCache
make stmt cache smaller
2020-10-10 22:10:43 +08:00
1dffa20435 make stmt cache smaller 2020-10-10 21:35:58 +08:00
8e37fe3b78 Merge pull request #4251 from sc0Vu/fix-typo
Fix typo
2020-10-06 19:44:27 +08:00
91e18996bd Fix typo 2020-10-06 18:20:06 +08:00
b8c1e133bf Merge pull request #4226 from vinicio/feature/same-site
session: adds CookieSameSite to ManagerConfig
2020-10-05 23:05:30 +08:00
d66321fe4e session: adds CookieSameSite config to hooks.go#registerSession 2020-10-05 11:39:20 -03:00
0b7ece44cf Merge pull request #4244 from AllenX2018/develop
testing: fix temporary create failed on Windows
2020-10-04 18:55:37 +08:00
6ffbc0a2b8 testing: fix temporary create failed on Windows
We are creating temporary files on the root directory of beego now
This PR using system temporary directory for testing.

Fixes #4243
2020-09-30 15:50:40 +08:00
26208a53e6 session: adds CookieSameSite to ManagerConfig 2020-09-15 18:05:33 -03:00
f6519b29a8 Merge pull request #4198 from CadenGuo/enhancement/orm_mysql_operator_consolidate
Enhancement/Add a new MySQL operator for strict case sensitive query
2020-09-01 21:33:41 +08:00
91410be722 orm_test:use predefined variable to check db driver 2020-08-31 15:47:37 +00:00
ff53e12191 skip strictexact operator test for db drivers other than mysql 2020-08-31 15:29:48 +00:00
60bb057783 add a new mysql operator for force case sensitie query 2020-08-31 03:40:18 +00:00
8736ffaf6f use 'BINARY' key word for exact operator for mysql db 2020-08-30 23:38:52 +08:00
f946a35acd Merge pull request #4191 from mchtech/rawseter_fielder
fix issue #3776
2020-08-26 10:49:38 +08:00
9bd3a27e80 fix #3776 2020-08-25 23:36:15 +08:00
59 changed files with 1089 additions and 172 deletions

View File

@ -8,6 +8,15 @@ It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific feature
## Quick Start ## Quick Start
#### Create `hello` directory, cd `hello` directory
mkdir hello
cd hello
#### Init module
go mod init
#### Download and install #### Download and install
go get github.com/astaxie/beego go get github.com/astaxie/beego

View File

@ -21,6 +21,7 @@ import (
"net/http" "net/http"
"os" "os"
"reflect" "reflect"
"strconv"
"text/template" "text/template"
"time" "time"
@ -71,7 +72,7 @@ func init() {
// AdminIndex is the default http.Handler for admin module. // AdminIndex is the default http.Handler for admin module.
// it matches url pattern "/". // it matches url pattern "/".
func adminIndex(rw http.ResponseWriter, _ *http.Request) { func adminIndex(rw http.ResponseWriter, _ *http.Request) {
execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl) writeTemplate(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
} }
// QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter. // QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter.
@ -91,7 +92,7 @@ func qpsIndex(rw http.ResponseWriter, _ *http.Request) {
} }
} }
execTpl(rw, data, qpsTpl, defaultScriptsTpl) writeTemplate(rw, data, qpsTpl, defaultScriptsTpl)
} }
// ListConf is the http.Handler of displaying all beego configuration values as key/value pair. // ListConf is the http.Handler of displaying all beego configuration values as key/value pair.
@ -128,7 +129,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
} }
data["Content"] = content data["Content"] = content
data["Title"] = "Routers" data["Title"] = "Routers"
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl) writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl)
case "filter": case "filter":
var ( var (
content = M{ content = M{
@ -171,7 +172,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
data["Content"] = content data["Content"] = content
data["Title"] = "Filters" data["Title"] = "Filters"
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl) writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl)
default: default:
rw.Write([]byte("command not support")) rw.Write([]byte("command not support"))
} }
@ -279,9 +280,7 @@ func profIndex(rw http.ResponseWriter, r *http.Request) {
http.Error(rw, err.Error(), http.StatusInternalServerError) http.Error(rw, err.Error(), http.StatusInternalServerError)
return return
} }
writeJSON(rw, dataJSON)
rw.Header().Set("Content-Type", "application/json")
rw.Write(dataJSON)
return return
} }
@ -290,12 +289,12 @@ func profIndex(rw http.ResponseWriter, r *http.Request) {
if command == "gc summary" { if command == "gc summary" {
defaultTpl = gcAjaxTpl defaultTpl = gcAjaxTpl
} }
execTpl(rw, data, profillingTpl, defaultTpl) writeTemplate(rw, data, profillingTpl, defaultTpl)
} }
// Healthcheck is a http.Handler calling health checking and showing the result. // Healthcheck is a http.Handler calling health checking and showing the result.
// it's in "/healthcheck" pattern in admin module. // it's in "/healthcheck" pattern in admin module.
func healthcheck(rw http.ResponseWriter, _ *http.Request) { func healthcheck(rw http.ResponseWriter, r *http.Request) {
var ( var (
result []string result []string
data = make(map[interface{}]interface{}) data = make(map[interface{}]interface{})
@ -322,10 +321,49 @@ func healthcheck(rw http.ResponseWriter, _ *http.Request) {
*resultList = append(*resultList, result) *resultList = append(*resultList, result)
} }
queryParams := r.URL.Query()
jsonFlag := queryParams.Get("json")
shouldReturnJSON, _ := strconv.ParseBool(jsonFlag)
if shouldReturnJSON {
response := buildHealthCheckResponseList(resultList)
jsonResponse, err := json.Marshal(response)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
} else {
writeJSON(rw, jsonResponse)
}
return
}
content["Data"] = resultList content["Data"] = resultList
data["Content"] = content data["Content"] = content
data["Title"] = "Health Check" data["Title"] = "Health Check"
execTpl(rw, data, healthCheckTpl, defaultScriptsTpl)
writeTemplate(rw, data, healthCheckTpl, defaultScriptsTpl)
}
func buildHealthCheckResponseList(healthCheckResults *[][]string) []map[string]interface{} {
response := make([]map[string]interface{}, len(*healthCheckResults))
for i, healthCheckResult := range *healthCheckResults {
currentResultMap := make(map[string]interface{})
currentResultMap["name"] = healthCheckResult[0]
currentResultMap["message"] = healthCheckResult[1]
currentResultMap["status"] = healthCheckResult[2]
response[i] = currentResultMap
}
return response
}
func writeJSON(rw http.ResponseWriter, jsonData []byte) {
rw.Header().Set("Content-Type", "application/json")
rw.Write(jsonData)
} }
// TaskStatus is a http.Handler with running task status (task name, status and the last execution). // TaskStatus is a http.Handler with running task status (task name, status and the last execution).
@ -371,10 +409,10 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
content["Data"] = resultList content["Data"] = resultList
data["Content"] = content data["Content"] = content
data["Title"] = "Tasks" data["Title"] = "Tasks"
execTpl(rw, data, tasksTpl, defaultScriptsTpl) writeTemplate(rw, data, tasksTpl, defaultScriptsTpl)
} }
func execTpl(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) { func writeTemplate(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) {
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
for _, tpl := range tpls { for _, tpl := range tpls {
tmpl = template.Must(tmpl.Parse(tpl)) tmpl = template.Must(tmpl.Parse(tpl))

View File

@ -1,10 +1,32 @@
package beego package beego
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing" "testing"
"github.com/astaxie/beego/toolbox"
) )
type SampleDatabaseCheck struct {
}
type SampleCacheCheck struct {
}
func (dc *SampleDatabaseCheck) Check() error {
return nil
}
func (cc *SampleCacheCheck) Check() error {
return errors.New("no cache detected")
}
func TestList_01(t *testing.T) { func TestList_01(t *testing.T) {
m := make(M) m := make(M)
list("BConfig", BConfig, m) list("BConfig", BConfig, m)
@ -75,3 +97,143 @@ func oldMap() M {
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
return m return m
} }
func TestWriteJSON(t *testing.T) {
t.Log("Testing the adding of JSON to the response")
w := httptest.NewRecorder()
originalBody := []int{1, 2, 3}
res, _ := json.Marshal(originalBody)
writeJSON(w, res)
decodedBody := []int{}
err := json.NewDecoder(w.Body).Decode(&decodedBody)
if err != nil {
t.Fatal("Could not decode response body into slice.")
}
for i := range decodedBody {
if decodedBody[i] != originalBody[i] {
t.Fatalf("Expected %d but got %d in decoded body slice", originalBody[i], decodedBody[i])
}
}
}
func TestHealthCheckHandlerDefault(t *testing.T) {
endpointPath := "/healthcheck"
toolbox.AddHealthCheck("database", &SampleDatabaseCheck{})
toolbox.AddHealthCheck("cache", &SampleCacheCheck{})
req, err := http.NewRequest("GET", endpointPath, nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(healthcheck)
handler.ServeHTTP(w, req)
if status := w.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
if !strings.Contains(w.Body.String(), "database") {
t.Errorf("Expected 'database' in generated template.")
}
}
func TestBuildHealthCheckResponseList(t *testing.T) {
healthCheckResults := [][]string{
[]string{
"error",
"Database",
"Error occurred while starting the db",
},
[]string{
"success",
"Cache",
"Cache started successfully",
},
}
responseList := buildHealthCheckResponseList(&healthCheckResults)
if len(responseList) != len(healthCheckResults) {
t.Errorf("invalid response map length: got %d want %d",
len(responseList), len(healthCheckResults))
}
responseFields := []string{"name", "message", "status"}
for _, response := range responseList {
for _, field := range responseFields {
_, ok := response[field]
if !ok {
t.Errorf("expected %s to be in the response %v", field, response)
}
}
}
}
func TestHealthCheckHandlerReturnsJSON(t *testing.T) {
toolbox.AddHealthCheck("database", &SampleDatabaseCheck{})
toolbox.AddHealthCheck("cache", &SampleCacheCheck{})
req, err := http.NewRequest("GET", "/healthcheck?json=true", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(healthcheck)
handler.ServeHTTP(w, req)
if status := w.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
decodedResponseBody := []map[string]interface{}{}
expectedResponseBody := []map[string]interface{}{}
expectedJSONString := []byte(`
[
{
"message":"database",
"name":"success",
"status":"OK"
},
{
"message":"cache",
"name":"error",
"status":"no cache detected"
}
]
`)
json.Unmarshal(expectedJSONString, &expectedResponseBody)
json.Unmarshal(w.Body.Bytes(), &decodedResponseBody)
if len(expectedResponseBody) != len(decodedResponseBody) {
t.Errorf("invalid response map length: got %d want %d",
len(decodedResponseBody), len(expectedResponseBody))
}
if !reflect.DeepEqual(decodedResponseBody, expectedResponseBody) {
t.Errorf("handler returned unexpected body: got %v want %v",
decodedResponseBody, expectedResponseBody)
}
}

2
app.go
View File

@ -197,7 +197,7 @@ func (app *App) Run(mws ...MiddleWare) {
pool.AppendCertsFromPEM(data) pool.AppendCertsFromPEM(data)
app.Server.TLSConfig = &tls.Config{ app.Server.TLSConfig = &tls.Config{
ClientCAs: pool, ClientCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert, ClientAuth: BConfig.Listen.ClientAuth,
} }
} }
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil { if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {

View File

@ -23,7 +23,7 @@ import (
const ( const (
// VERSION represent beego web framework version. // VERSION represent beego web framework version.
VERSION = "1.12.2" VERSION = "1.12.3"
// DEV is for develop // DEV is for develop
DEV = "dev" DEV = "dev"

View File

@ -15,11 +15,11 @@
package beego package beego
var ( var (
BuildVersion string BuildVersion string
BuildGitRevision string BuildGitRevision string
BuildStatus string BuildStatus string
BuildTag string BuildTag string
BuildTime string BuildTime string
GoVersion string GoVersion string

View File

@ -38,8 +38,9 @@ import (
"github.com/gomodule/redigo/redis" "github.com/gomodule/redigo/redis"
"github.com/astaxie/beego/cache"
"strings" "strings"
"github.com/astaxie/beego/cache"
) )
var ( var (
@ -57,7 +58,7 @@ type Cache struct {
maxIdle int maxIdle int
//the timeout to a value less than the redis server's timeout. //the timeout to a value less than the redis server's timeout.
timeout time.Duration timeout time.Duration
} }
// NewRedisCache create new redis cache with default collection name. // NewRedisCache create new redis cache with default collection name.

View File

@ -15,7 +15,9 @@
package beego package beego
import ( import (
"crypto/tls"
"fmt" "fmt"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@ -65,6 +67,7 @@ type Listen struct {
HTTPSCertFile string HTTPSCertFile string
HTTPSKeyFile string HTTPSKeyFile string
TrustCaFile string TrustCaFile string
ClientAuth tls.ClientAuthType
EnableAdmin bool EnableAdmin bool
AdminAddr string AdminAddr string
AdminPort int AdminPort int
@ -106,6 +109,7 @@ type SessionConfig struct {
SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers
SessionNameInHTTPHeader string SessionNameInHTTPHeader string
SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params
SessionCookieSameSite http.SameSite
} }
// LogConfig holds Log related config // LogConfig holds Log related config
@ -150,6 +154,9 @@ func init() {
filename = os.Getenv("BEEGO_RUNMODE") + ".app.conf" filename = os.Getenv("BEEGO_RUNMODE") + ".app.conf"
} }
appConfigPath = filepath.Join(WorkPath, "conf", filename) appConfigPath = filepath.Join(WorkPath, "conf", filename)
if configPath := os.Getenv("BEEGO_CONFIG_PATH"); configPath != "" {
appConfigPath = configPath
}
if !utils.FileExists(appConfigPath) { if !utils.FileExists(appConfigPath) {
appConfigPath = filepath.Join(AppPath, "conf", filename) appConfigPath = filepath.Join(AppPath, "conf", filename)
if !utils.FileExists(appConfigPath) { if !utils.FileExists(appConfigPath) {
@ -231,6 +238,7 @@ func newBConfig() *Config {
AdminPort: 8088, AdminPort: 8088,
EnableFcgi: false, EnableFcgi: false,
EnableStdIo: false, EnableStdIo: false,
ClientAuth: tls.RequireAndVerifyClientCert,
}, },
WebConfig: WebConfig{ WebConfig: WebConfig{
AutoRender: true, AutoRender: true,
@ -261,6 +269,7 @@ func newBConfig() *Config {
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
SessionNameInHTTPHeader: "Beegosessionid", SessionNameInHTTPHeader: "Beegosessionid",
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
SessionCookieSameSite: http.SameSiteDefaultMode,
}, },
}, },
Log: LogConfig{ Log: LogConfig{

View File

@ -145,7 +145,7 @@ httpport = 8080
# enable db # enable db
[dbinfo] [dbinfo]
# db type name # db type name
# suport mysql,sqlserver # support mysql,sqlserver
name = mysql name = mysql
` `
@ -161,7 +161,7 @@ httpport=8080
# enable db # enable db
[dbinfo] [dbinfo]
# db type name # db type name
# suport mysql,sqlserver # support mysql,sqlserver
name=mysql name=mysql
` `
) )

View File

@ -296,7 +296,7 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) {
case map[string]interface{}: case map[string]interface{}:
{ {
tmpData = v.(map[string]interface{}) tmpData = v.(map[string]interface{})
if idx == len(keys) - 1 { if idx == len(keys)-1 {
return tmpData, nil return tmpData, nil
} }
} }

View File

@ -150,7 +150,7 @@ func (ctx *Context) XSRFToken(key string, expire int64) string {
token, ok := ctx.GetSecureCookie(key, "_xsrf") token, ok := ctx.GetSecureCookie(key, "_xsrf")
if !ok { if !ok {
token = string(utils.RandomCreateBytes(32)) token = string(utils.RandomCreateBytes(32))
ctx.SetSecureCookie(key, "_xsrf", token, expire) ctx.SetSecureCookie(key, "_xsrf", token, expire, "", "", true, true)
} }
ctx._xsrfToken = token ctx._xsrfToken = token
} }

View File

@ -17,7 +17,10 @@ package context
import ( import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestXsrfReset_01(t *testing.T) { func TestXsrfReset_01(t *testing.T) {
@ -44,4 +47,8 @@ func TestXsrfReset_01(t *testing.T) {
if token == c._xsrfToken { if token == c._xsrfToken {
t.FailNow() t.FailNow()
} }
ck := c.ResponseWriter.Header().Get("Set-Cookie")
assert.True(t, strings.Contains(ck, "Secure"))
assert.True(t, strings.Contains(ck, "HttpOnly"))
} }

View File

@ -333,8 +333,14 @@ func (input *BeegoInput) Query(key string) string {
return val return val
} }
if input.Context.Request.Form == nil { if input.Context.Request.Form == nil {
input.Context.Request.ParseForm() input.dataLock.Lock()
if input.Context.Request.Form == nil {
input.Context.Request.ParseForm()
}
input.dataLock.Unlock()
} }
input.dataLock.RLock()
defer input.dataLock.RUnlock()
return input.Context.Request.Form.Get(key) return input.Context.Request.Form.Get(key)
} }

View File

@ -205,3 +205,13 @@ func TestParams(t *testing.T) {
} }
} }
func BenchmarkQuery(b *testing.B) {
beegoInput := NewInput()
beegoInput.Context = NewContext()
beegoInput.Context.Request, _ = http.NewRequest("POST", "http://www.example.com/?q=foo", nil)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
beegoInput.Query("q")
}
})
}

View File

@ -1,8 +1,10 @@
package param package param
import "testing" import (
import "reflect" "reflect"
import "time" "testing"
"time"
)
type testDefinition struct { type testDefinition struct {
strValue string strValue string

View File

@ -255,7 +255,7 @@ func (c *Controller) RenderString() (string, error) {
// RenderBytes returns the bytes of rendered template string. Do not send out response. // RenderBytes returns the bytes of rendered template string. Do not send out response.
func (c *Controller) RenderBytes() ([]byte, error) { func (c *Controller) RenderBytes() ([]byte, error) {
buf, err := c.renderTemplate() buf, err := c.renderTemplate()
//if the controller has set layout, then first get the tplName's content set the content to the layout // if the controller has set layout, then first get the tplName's content set the content to the layout
if err == nil && c.Layout != "" { if err == nil && c.Layout != "" {
c.Data["LayoutContent"] = template.HTML(buf.String()) c.Data["LayoutContent"] = template.HTML(buf.String())
@ -642,12 +642,13 @@ func (c *Controller) DelSession(name interface{}) {
// SessionRegenerateID regenerates session id for this session. // SessionRegenerateID regenerates session id for this session.
// the session data have no changes. // the session data have no changes.
func (c *Controller) SessionRegenerateID() { func (c *Controller) SessionRegenerateID() (err error) {
if c.CruSession != nil { if c.CruSession != nil {
c.CruSession.SessionRelease(c.Ctx.ResponseWriter) c.CruSession.SessionRelease(c.Ctx.ResponseWriter)
} }
c.CruSession = GlobalSessions.SessionRegenerateID(c.Ctx.ResponseWriter, c.Ctx.Request) c.CruSession, err = GlobalSessions.SessionRegenerateID(c.Ctx.ResponseWriter, c.Ctx.Request)
c.Ctx.Input.CruSession = c.CruSession c.Ctx.Input.CruSession = c.CruSession
return
} }
// DestroySession cleans session data and session cookie. // DestroySession cleans session data and session cookie.

View File

@ -19,9 +19,10 @@ import (
"strconv" "strconv"
"testing" "testing"
"github.com/astaxie/beego/context"
"os" "os"
"path/filepath" "path/filepath"
"github.com/astaxie/beego/context"
) )
func TestGetInt(t *testing.T) { func TestGetInt(t *testing.T) {
@ -125,8 +126,8 @@ func TestGetUint64(t *testing.T) {
} }
func TestAdditionalViewPaths(t *testing.T) { func TestAdditionalViewPaths(t *testing.T) {
dir1 := "_beeTmp" dir1 := tmpDir("TestAdditionalViewPaths1")
dir2 := "_beeTmp2" dir2 := tmpDir("TestAdditionalViewPaths2")
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
defer os.RemoveAll(dir2) defer os.RemoveAll(dir2)

View File

@ -28,7 +28,7 @@ import (
) )
const ( const (
errorTypeHandler = iota errorTypeHandler = iota
errorTypeController errorTypeController
) )
@ -359,6 +359,20 @@ func gatewayTimeout(rw http.ResponseWriter, r *http.Request) {
) )
} }
// show 413 Payload Too Large
func payloadTooLarge(rw http.ResponseWriter, r *http.Request) {
responseError(rw, r,
413,
`<br>The page you have requested is unavailable.
<br>Perhaps you are here because:<br><br>
<ul>
<br>The request entity is larger than limits defined by server.
<br>Please change the request entity and try again.
</ul>
`,
)
}
func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) { func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := M{ data := M{

3
go.mod
View File

@ -22,6 +22,7 @@ require (
github.com/lib/pq v1.0.0 github.com/lib/pq v1.0.0
github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/pelletier/go-toml v1.2.0 // indirect github.com/pelletier/go-toml v1.2.0 // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.7.0 github.com/prometheus/client_golang v1.7.0
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec
@ -29,7 +30,7 @@ require (
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c // indirect github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c // indirect
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b // indirect github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b // indirect
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8
) )

11
go.sum
View File

@ -53,7 +53,6 @@ github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d h1:xy93KVe+KrIIwWDEAfQBdIfsiHJkepbYsDr+VY3g9/o=
github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -114,11 +113,10 @@ github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pingcap/tidb v2.0.11+incompatible/go.mod h1:I8C6jrPINP2rrVunTRd7C9fRRhQrtR43S1/CL5ix/yQ=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@ -164,9 +162,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -175,7 +171,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -189,8 +184,6 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=

View File

@ -34,6 +34,7 @@ func registerDefaultErrorHandler() error {
"504": gatewayTimeout, "504": gatewayTimeout,
"417": invalidxsrf, "417": invalidxsrf,
"422": missingxsrf, "422": missingxsrf,
"413": payloadTooLarge,
} }
for e, h := range m { for e, h := range m {
if _, ok := ErrorMaps[e]; !ok { if _, ok := ErrorMaps[e]; !ok {
@ -60,6 +61,7 @@ func registerSession() error {
conf.EnableSidInHTTPHeader = BConfig.WebConfig.Session.SessionEnableSidInHTTPHeader conf.EnableSidInHTTPHeader = BConfig.WebConfig.Session.SessionEnableSidInHTTPHeader
conf.SessionNameInHTTPHeader = BConfig.WebConfig.Session.SessionNameInHTTPHeader conf.SessionNameInHTTPHeader = BConfig.WebConfig.Session.SessionNameInHTTPHeader
conf.EnableSidInURLQuery = BConfig.WebConfig.Session.SessionEnableSidInURLQuery conf.EnableSidInURLQuery = BConfig.WebConfig.Session.SessionEnableSidInURLQuery
conf.CookieSameSite = BConfig.WebConfig.Session.SessionCookieSameSite
} else { } else {
if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil { if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil {
return err return err

View File

@ -144,6 +144,7 @@ type BeegoHTTPSettings struct {
Gzip bool Gzip bool
DumpBody bool DumpBody bool
Retries int // if set to -1 means will retry forever Retries int // if set to -1 means will retry forever
RetryDelay time.Duration
} }
// BeegoHTTPRequest provides more useful methods for requesting one url than http.Request. // BeegoHTTPRequest provides more useful methods for requesting one url than http.Request.
@ -202,6 +203,11 @@ func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest {
return b return b
} }
func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest {
b.setting.RetryDelay = delay
return b
}
// DumpBody setting whether need to Dump the Body. // DumpBody setting whether need to Dump the Body.
func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest { func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest {
b.setting.DumpBody = isdump b.setting.DumpBody = isdump
@ -512,11 +518,13 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
// retries default value is 0, it will run once. // retries default value is 0, it will run once.
// retries equal to -1, it will run forever until success // retries equal to -1, it will run forever until success
// retries is setted, it will retries fixed times. // retries is setted, it will retries fixed times.
// Sleeps for a 400ms in between calls to reduce spam
for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ { for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ {
resp, err = client.Do(b.req) resp, err = client.Do(b.req)
if err == nil { if err == nil {
break break
} }
time.Sleep(b.setting.RetryDelay)
} }
return resp, err return resp, err
} }

View File

@ -15,6 +15,7 @@
package httplib package httplib
import ( import (
"errors"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
@ -33,6 +34,34 @@ func TestResponse(t *testing.T) {
t.Log(resp) t.Log(resp)
} }
func TestDoRequest(t *testing.T) {
req := Get("https://goolnk.com/33BD2j")
retryAmount := 1
req.Retries(1)
req.RetryDelay(1400 * time.Millisecond)
retryDelay := 1400 * time.Millisecond
req.setting.CheckRedirect = func(redirectReq *http.Request, redirectVia []*http.Request) error {
return errors.New("Redirect triggered")
}
startTime := time.Now().UnixNano() / int64(time.Millisecond)
_, err := req.Response()
if err == nil {
t.Fatal("Response should have yielded an error")
}
endTime := time.Now().UnixNano() / int64(time.Millisecond)
elapsedTime := endTime - startTime
delayedTime := int64(retryAmount) * retryDelay.Milliseconds()
if elapsedTime < delayedTime {
t.Errorf("Not enough retries. Took %dms. Delay was meant to take %dms", elapsedTime, delayedTime)
}
}
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
req := Get("http://httpbin.org/get") req := Get("http://httpbin.org/get")
b, err := req.Bytes() b, err := req.Bytes()

View File

@ -16,9 +16,9 @@ package logs
import ( import (
"bytes" "bytes"
"strings"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"time" "time"
) )

View File

@ -63,7 +63,10 @@ func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error {
defer c.innerWriter.Close() defer c.innerWriter.Close()
} }
c.lg.writeln(when, msg) _, err := c.lg.writeln(when, msg)
if err != nil {
return err
}
return nil return nil
} }
@ -101,7 +104,6 @@ func (c *connWriter) connect() error {
func (c *connWriter) needToConnectOnMsg() bool { func (c *connWriter) needToConnectOnMsg() bool {
if c.Reconnect { if c.Reconnect {
c.Reconnect = false
return true return true
} }

View File

@ -15,11 +15,65 @@
package logs package logs
import ( import (
"net"
"os"
"testing" "testing"
) )
// ConnTCPListener takes a TCP listener and accepts n TCP connections
// Returns connections using connChan
func connTCPListener(t *testing.T, n int, ln net.Listener, connChan chan<- net.Conn) {
// Listen and accept n incoming connections
for i := 0; i < n; i++ {
conn, err := ln.Accept()
if err != nil {
t.Log("Error accepting connection: ", err.Error())
os.Exit(1)
}
// Send accepted connection to channel
connChan <- conn
}
ln.Close()
close(connChan)
}
func TestConn(t *testing.T) { func TestConn(t *testing.T) {
log := NewLogger(1000) log := NewLogger(1000)
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
log.Informational("informational") log.Informational("informational")
} }
func TestReconnect(t *testing.T) {
// Setup connection listener
newConns := make(chan net.Conn)
connNum := 2
ln, err := net.Listen("tcp", ":6002")
if err != nil {
t.Log("Error listening:", err.Error())
os.Exit(1)
}
go connTCPListener(t, connNum, ln, newConns)
// Setup logger
log := NewLogger(1000)
log.SetPrefix("test")
log.SetLogger(AdapterConn, `{"net":"tcp","reconnect":true,"level":6,"addr":":6002"}`)
log.Informational("informational 1")
// Refuse first connection
first := <-newConns
first.Close()
// Send another log after conn closed
log.Informational("informational 2")
// Check if there was a second connection attempt
select {
case second := <-newConns:
second.Close()
default:
t.Error("Did not reconnect")
}
}

View File

@ -373,21 +373,21 @@ func (w *fileLogWriter) deleteOldLog() {
if info == nil { if info == nil {
return return
} }
if w.Hourly { if w.Hourly {
if !info.IsDir() && info.ModTime().Add(1 * time.Hour * time.Duration(w.MaxHours)).Before(time.Now()) { if !info.IsDir() && info.ModTime().Add(1*time.Hour*time.Duration(w.MaxHours)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) { strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path) os.Remove(path)
} }
} }
} else if w.Daily { } else if w.Daily {
if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) { if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) { strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path) os.Remove(path)
} }
} }
} }
return return
}) })
} }

View File

@ -186,7 +186,7 @@ func TestFileDailyRotate_06(t *testing.T) { //test file mode
func TestFileHourlyRotate_01(t *testing.T) { func TestFileHourlyRotate_01(t *testing.T) {
log := NewLogger(10000) log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`) log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`)
log.Debug("debug") log.Debug("debug")
log.Info("info") log.Info("info")
log.Notice("notice") log.Notice("notice")
@ -237,7 +237,7 @@ func TestFileHourlyRotate_05(t *testing.T) {
func TestFileHourlyRotate_06(t *testing.T) { //test file mode func TestFileHourlyRotate_06(t *testing.T) { //test file mode
log := NewLogger(10000) log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`) log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`)
log.Debug("debug") log.Debug("debug")
log.Info("info") log.Info("info")
log.Notice("notice") log.Notice("notice")
@ -269,19 +269,19 @@ func testFileRotate(t *testing.T, fn1, fn2 string, daily, hourly bool) {
RotatePerm: "0440", RotatePerm: "0440",
} }
if daily { if daily {
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1)) fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour) fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
fw.dailyOpenDate = fw.dailyOpenTime.Day() fw.dailyOpenDate = fw.dailyOpenTime.Day()
} }
if hourly { if hourly {
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1)) fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour) fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
fw.hourlyOpenDate = fw.hourlyOpenTime.Day() fw.hourlyOpenDate = fw.hourlyOpenTime.Day()
} }
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug) fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug)
for _, file := range []string{fn1, fn2} { for _, file := range []string{fn1, fn2} {
_, err := os.Stat(file) _, err := os.Stat(file)
@ -328,8 +328,8 @@ func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
func testFileHourlyRotate(t *testing.T, fn1, fn2 string) { func testFileHourlyRotate(t *testing.T, fn1, fn2 string) {
fw := &fileLogWriter{ fw := &fileLogWriter{
Hourly: true, Hourly: true,
MaxHours: 168, MaxHours: 168,
Rotate: true, Rotate: true,
Level: LevelTrace, Level: LevelTrace,
Perm: "0660", Perm: "0660",

View File

@ -30,11 +30,12 @@ func newLogWriter(wr io.Writer) *logWriter {
return &logWriter{writer: wr} return &logWriter{writer: wr}
} }
func (lg *logWriter) writeln(when time.Time, msg string) { func (lg *logWriter) writeln(when time.Time, msg string) (int, error) {
lg.Lock() lg.Lock()
h, _, _ := formatTimeHeader(when) h, _, _ := formatTimeHeader(when)
lg.writer.Write(append(append(h, msg...), '\n')) n, err := lg.writer.Write(append(append(h, msg...), '\n'))
lg.Unlock() lg.Unlock()
return n, err
} }
const ( const (

View File

@ -37,7 +37,7 @@ func PrometheusMiddleWare(next http.Handler) http.Handler {
"appname": beego.BConfig.AppName, "appname": beego.BConfig.AppName,
}, },
Help: "The statics info for http request", Help: "The statics info for http request",
}, []string{"pattern", "method", "status", "duration"}) }, []string{"pattern", "method", "status"})
prometheus.MustRegister(summaryVec) prometheus.MustRegister(summaryVec)
@ -57,15 +57,15 @@ func registerBuildInfo() {
Subsystem: "build_info", Subsystem: "build_info",
Help: "The building information", Help: "The building information",
ConstLabels: map[string]string{ ConstLabels: map[string]string{
"appname": beego.BConfig.AppName, "appname": beego.BConfig.AppName,
"build_version": beego.BuildVersion, "build_version": beego.BuildVersion,
"build_revision": beego.BuildGitRevision, "build_revision": beego.BuildGitRevision,
"build_status": beego.BuildStatus, "build_status": beego.BuildStatus,
"build_tag": beego.BuildTag, "build_tag": beego.BuildTag,
"build_time": strings.Replace(beego.BuildTime, "--", " ", 1), "build_time": strings.Replace(beego.BuildTime, "--", " ", 1),
"go_version": beego.GoVersion, "go_version": beego.GoVersion,
"git_branch": beego.GitBranch, "git_branch": beego.GitBranch,
"start_time": time.Now().Format("2006-01-02 15:04:05"), "start_time": time.Now().Format("2006-01-02 15:04:05"),
}, },
}, []string{}) }, []string{})
@ -95,5 +95,5 @@ func report(dur time.Duration, writer http.ResponseWriter, q *http.Request, vec
logs.Warn("we can not find the router info for this request, so request will be recorded as UNKNOWN: " + q.URL.String()) logs.Warn("we can not find the router info for this request, so request will be recorded as UNKNOWN: " + q.URL.String())
} }
ms := dur / time.Millisecond ms := dur / time.Millisecond
vec.WithLabelValues(ptn, q.Method, strconv.Itoa(status), strconv.Itoa(int(ms))).Observe(float64(ms)) vec.WithLabelValues(ptn, q.Method, strconv.Itoa(status)).Observe(float64(ms))
} }

View File

@ -35,7 +35,7 @@ func TestPrometheusMiddleWare(t *testing.T) {
}, },
Method: "POST", Method: "POST",
} }
vec := prometheus.NewSummaryVec(prometheus.SummaryOpts{}, []string{"pattern", "method", "status", "duration"}) vec := prometheus.NewSummaryVec(prometheus.SummaryOpts{}, []string{"pattern", "method", "status"})
report(time.Second, writer, request, vec) report(time.Second, writer, request, vec)
middleware.ServeHTTP(writer, request) middleware.ServeHTTP(writer, request)

View File

@ -197,9 +197,9 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
if strings.Contains(column, "%COL%") { if strings.Contains(column, "%COL%") {
column = strings.Replace(column, "%COL%", fi.column, -1) column = strings.Replace(column, "%COL%", fi.column, -1)
} }
if fi.description != "" && al.Driver!=DRSqlite { if fi.description != "" && al.Driver != DRSqlite {
column += " " + fmt.Sprintf("COMMENT '%s'",fi.description) column += " " + fmt.Sprintf("COMMENT '%s'", fi.description)
} }
columns = append(columns, column) columns = append(columns, column)

View File

@ -36,10 +36,11 @@ var (
var ( var (
operators = map[string]bool{ operators = map[string]bool{
"exact": true, "exact": true,
"iexact": true, "iexact": true,
"contains": true, "strictexact": true,
"icontains": true, "contains": true,
"icontains": true,
// "regex": true, // "regex": true,
// "iregex": true, // "iregex": true,
"gt": true, "gt": true,
@ -702,6 +703,13 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
return 0, err return 0, err
} }
if num > 0 { if num > 0 {
if mi.fields.pk.auto {
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(0)
} else {
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0)
}
}
err := d.deleteRels(q, mi, args, tz) err := d.deleteRels(q, mi, args, tz)
if err != nil { if err != nil {
return num, err return num, err
@ -1195,7 +1203,7 @@ func (d *dbBase) GenerateOperatorSQL(mi *modelInfo, fi *fieldInfo, operator stri
} }
sql = d.ins.OperatorSQL(operator) sql = d.ins.OperatorSQL(operator)
switch operator { switch operator {
case "exact": case "exact", "strictexact":
if arg == nil { if arg == nil {
params[0] = "IS NULL" params[0] = "IS NULL"
} }

View File

@ -18,10 +18,11 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
lru "github.com/hashicorp/golang-lru"
"reflect" "reflect"
"sync" "sync"
"time" "time"
lru "github.com/hashicorp/golang-lru"
) )
// DriverType database driver constant int. // DriverType database driver constant int.
@ -424,8 +425,7 @@ func GetDB(aliasNames ...string) (*sql.DB, error) {
} }
type stmtDecorator struct { type stmtDecorator struct {
wg sync.WaitGroup wg sync.WaitGroup
lastUse int64
stmt *sql.Stmt stmt *sql.Stmt
} }
@ -433,9 +433,12 @@ func (s *stmtDecorator) getStmt() *sql.Stmt {
return s.stmt return s.stmt
} }
// acquire will add one
// since this method will be used inside read lock scope,
// so we can not do more things here
// we should think about refactor this
func (s *stmtDecorator) acquire() { func (s *stmtDecorator) acquire() {
s.wg.Add(1) s.wg.Add(1)
s.lastUse = time.Now().Unix()
} }
func (s *stmtDecorator) release() { func (s *stmtDecorator) release() {
@ -453,12 +456,13 @@ func (s *stmtDecorator) destroy() {
func newStmtDecorator(sqlStmt *sql.Stmt) *stmtDecorator { func newStmtDecorator(sqlStmt *sql.Stmt) *stmtDecorator {
return &stmtDecorator{ return &stmtDecorator{
stmt: sqlStmt, stmt: sqlStmt,
lastUse: time.Now().Unix(),
} }
} }
func newStmtDecoratorLruWithEvict() *lru.Cache { func newStmtDecoratorLruWithEvict() *lru.Cache {
cache, _ := lru.NewWithEvict(1000, func(key interface{}, value interface{}) { // temporarily solution
// we fixed this problem in v2.x
cache, _ := lru.NewWithEvict(50, func(key interface{}, value interface{}) {
value.(*stmtDecorator).destroy() value.(*stmtDecorator).destroy()
}) })
return cache return cache

View File

@ -22,10 +22,11 @@ import (
// mysql operators. // mysql operators.
var mysqlOperators = map[string]string{ var mysqlOperators = map[string]string{
"exact": "= ?", "exact": "= ?",
"iexact": "LIKE ?", "iexact": "LIKE ?",
"contains": "LIKE BINARY ?", "strictexact": "= BINARY ?",
"icontains": "LIKE ?", "contains": "LIKE BINARY ?",
"icontains": "LIKE ?",
// "regex": "REGEXP BINARY ?", // "regex": "REGEXP BINARY ?",
// "iregex": "REGEXP ?", // "iregex": "REGEXP ?",
"gt": "> ?", "gt": "> ?",

View File

@ -53,18 +53,24 @@ func (e *SliceStringField) FieldType() int {
} }
func (e *SliceStringField) SetRaw(value interface{}) error { func (e *SliceStringField) SetRaw(value interface{}) error {
switch d := value.(type) { f := func(str string) {
case []string: if len(str) > 0 {
e.Set(d) parts := strings.Split(str, ",")
case string:
if len(d) > 0 {
parts := strings.Split(d, ",")
v := make([]string, 0, len(parts)) v := make([]string, 0, len(parts))
for _, p := range parts { for _, p := range parts {
v = append(v, strings.TrimSpace(p)) v = append(v, strings.TrimSpace(p))
} }
e.Set(v) e.Set(v)
} }
}
switch d := value.(type) {
case []string:
e.Set(d)
case string:
f(d)
case []byte:
f(string(d))
default: default:
return fmt.Errorf("<SliceStringField.SetRaw> unknown value `%v`", value) return fmt.Errorf("<SliceStringField.SetRaw> unknown value `%v`", value)
} }
@ -96,6 +102,8 @@ func (e *JSONFieldTest) SetRaw(value interface{}) error {
switch d := value.(type) { switch d := value.(type) {
case string: case string:
return json.Unmarshal([]byte(d), e) return json.Unmarshal([]byte(d), e)
case []byte:
return json.Unmarshal(d, e)
default: default:
return fmt.Errorf("<JSONField.SetRaw> unknown value `%v`", value) return fmt.Errorf("<JSONField.SetRaw> unknown value `%v`", value)
} }

View File

@ -242,7 +242,13 @@ func (o *orm) Update(md interface{}, cols ...string) (int64, error) {
func (o *orm) Delete(md interface{}, cols ...string) (int64, error) { func (o *orm) Delete(md interface{}, cols ...string) (int64, error) {
mi, ind := o.getMiInd(md, true) mi, ind := o.getMiInd(md, true)
num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ, cols) num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ, cols)
return num, err if err != nil {
return num, err
}
if num > 0 {
o.setPk(mi, ind, 0)
}
return num, nil
} }
// create a models to models queryer // create a models to models queryer

View File

@ -61,7 +61,7 @@ func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error
con += " - " + err.Error() con += " - " + err.Error()
} }
logMap["sql"] = fmt.Sprintf("%s-`%s`", query, strings.Join(cons, "`, `")) logMap["sql"] = fmt.Sprintf("%s-`%s`", query, strings.Join(cons, "`, `"))
if LogFunc != nil{ if LogFunc != nil {
LogFunc(logMap) LogFunc(logMap)
} }
DebugLog.Println(con) DebugLog.Println(con)

View File

@ -19,6 +19,8 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"time" "time"
"github.com/pkg/errors"
) )
// raw sql string prepared statement // raw sql string prepared statement
@ -368,7 +370,15 @@ func (o *rawSet) QueryRow(containers ...interface{}) error {
field.Set(mf) field.Set(mf)
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex) field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
} }
o.setFieldValue(field, value) if fi.isFielder {
fd := field.Addr().Interface().(Fielder)
err := fd.SetRaw(value)
if err != nil {
return errors.Errorf("set raw error:%s", err)
}
} else {
o.setFieldValue(field, value)
}
} }
} }
} else { } else {
@ -509,7 +519,15 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
field.Set(mf) field.Set(mf)
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex) field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
} }
o.setFieldValue(field, value) if fi.isFielder {
fd := field.Addr().Interface().(Fielder)
err := fd.SetRaw(value)
if err != nil {
return 0, errors.Errorf("set raw error:%s", err)
}
} else {
o.setFieldValue(field, value)
}
} }
} }
} else { } else {

View File

@ -769,6 +769,20 @@ func TestCustomField(t *testing.T) {
throwFailNow(t, AssertIs(user.Extra.Name, "beego")) throwFailNow(t, AssertIs(user.Extra.Name, "beego"))
throwFailNow(t, AssertIs(user.Extra.Data, "orm")) throwFailNow(t, AssertIs(user.Extra.Data, "orm"))
var users []User
Q := dDbBaser.TableQuote()
n, err := dORM.Raw(fmt.Sprintf("SELECT * FROM %suser%s where id=?", Q, Q), 2).QueryRows(&users)
throwFailNow(t, err)
throwFailNow(t, AssertIs(n, 1))
throwFailNow(t, AssertIs(users[0].Extra.Name, "beego"))
throwFailNow(t, AssertIs(users[0].Extra.Data, "orm"))
user = User{}
err = dORM.Raw(fmt.Sprintf("SELECT * FROM %suser%s where id=?", Q, Q), 2).QueryRow(&user)
throwFailNow(t, err)
throwFailNow(t, AssertIs(user.Extra.Name, "beego"))
throwFailNow(t, AssertIs(user.Extra.Data, "orm"))
} }
func TestExpr(t *testing.T) { func TestExpr(t *testing.T) {
@ -808,6 +822,17 @@ func TestOperators(t *testing.T) {
throwFail(t, err) throwFail(t, err)
throwFail(t, AssertIs(num, 1)) throwFail(t, AssertIs(num, 1))
if IsMysql {
// Now only mysql support `strictexact`
num, err = qs.Filter("user_name__strictexact", "Slene").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 0))
num, err = qs.Filter("user_name__strictexact", "slene").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
}
num, err = qs.Filter("user_name__contains", "e").Count() num, err = qs.Filter("user_name__contains", "e").Count()
throwFail(t, err) throwFail(t, err)
throwFail(t, AssertIs(num, 2)) throwFail(t, AssertIs(num, 2))

View File

@ -49,7 +49,6 @@ func init() {
var ( var (
lastupdateFilename = "lastupdate.tmp" lastupdateFilename = "lastupdate.tmp"
commentFilename string
pkgLastupdate map[string]int64 pkgLastupdate map[string]int64
genInfoList map[string][]ControllerComments genInfoList map[string][]ControllerComments
@ -70,16 +69,13 @@ var (
} }
) )
const commentPrefix = "commentsRouter_" const commentFilename = "commentsRouter.go"
func init() { func init() {
pkgLastupdate = make(map[string]int64) pkgLastupdate = make(map[string]int64)
} }
func parserPkg(pkgRealpath, pkgpath string) error { func parserPkg(pkgRealpath, pkgpath string) error {
rep := strings.NewReplacer("\\", "_", "/", "_", ".", "_")
commentFilename, _ = filepath.Rel(AppPath, pkgRealpath)
commentFilename = commentPrefix + rep.Replace(commentFilename) + ".go"
if !compareFile(pkgRealpath) { if !compareFile(pkgRealpath) {
logs.Info(pkgRealpath + " no changed") logs.Info(pkgRealpath + " no changed")
return nil return nil
@ -102,7 +98,10 @@ func parserPkg(pkgRealpath, pkgpath string) error {
if specDecl.Recv != nil { if specDecl.Recv != nil {
exp, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr) // Check that the type is correct first beforing throwing to parser exp, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr) // Check that the type is correct first beforing throwing to parser
if ok { if ok {
parserComments(specDecl, fmt.Sprint(exp.X), pkgpath) err = parserComments(specDecl, fmt.Sprint(exp.X), pkgpath)
if err != nil {
return err
}
} }
} }
} }
@ -500,7 +499,7 @@ func genRouterCode(pkgRealpath string) {
beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"], beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
beego.ControllerComments{ beego.ControllerComments{
Method: "` + strings.TrimSpace(c.Method) + `", Method: "` + strings.TrimSpace(c.Method) + `",
` + `Router: "` + c.Router + `"` + `, ` + "Router: `" + c.Router + "`" + `,
AllowHTTPMethods: ` + allmethod + `, AllowHTTPMethods: ` + allmethod + `,
MethodParams: ` + methodParams + `, MethodParams: ` + methodParams + `,
Filters: ` + filters + `, Filters: ` + filters + `,

View File

@ -40,10 +40,11 @@
package authz package authz
import ( import (
"net/http"
"github.com/astaxie/beego" "github.com/astaxie/beego"
"github.com/astaxie/beego/context" "github.com/astaxie/beego/context"
"github.com/casbin/casbin" "github.com/casbin/casbin"
"net/http"
) )
// NewAuthorizer returns the authorizer. // NewAuthorizer returns the authorizer.

View File

@ -15,13 +15,14 @@
package authz package authz
import ( import (
"net/http"
"net/http/httptest"
"testing"
"github.com/astaxie/beego" "github.com/astaxie/beego"
"github.com/astaxie/beego/context" "github.com/astaxie/beego/context"
"github.com/astaxie/beego/plugins/auth" "github.com/astaxie/beego/plugins/auth"
"github.com/casbin/casbin" "github.com/casbin/casbin"
"net/http"
"net/http/httptest"
"testing"
) )
func testRequest(t *testing.T, handler *beego.ControllerRegister, user string, path string, method string, code int) { func testRequest(t *testing.T, handler *beego.ControllerRegister, user string, path string, method string, code int) {

View File

@ -742,6 +742,12 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
if r.Method != http.MethodGet && r.Method != http.MethodHead { if r.Method != http.MethodGet && r.Method != http.MethodHead {
if BConfig.CopyRequestBody && !context.Input.IsUpload() { if BConfig.CopyRequestBody && !context.Input.IsUpload() {
// connection will close if the incoming data are larger (RFC 7231, 6.5.11)
if r.ContentLength > BConfig.MaxMemory {
logs.Error(errors.New("payload too large"))
exception("413", context)
goto Admin
}
context.Input.CopyBody(BConfig.MaxMemory) context.Input.CopyBody(BConfig.MaxMemory)
} }
context.Input.ParseFormOrMulitForm(BConfig.MaxMemory) context.Input.ParseFormOrMulitForm(BConfig.MaxMemory)

View File

@ -15,6 +15,7 @@
package beego package beego
import ( import (
"bytes"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
@ -71,7 +72,6 @@ func (tc *TestController) GetEmptyBody() {
tc.Ctx.Output.Body(res) tc.Ctx.Output.Body(res)
} }
type JSONController struct { type JSONController struct {
Controller Controller
} }
@ -656,17 +656,14 @@ func beegoBeforeRouter1(ctx *context.Context) {
ctx.WriteString("|BeforeRouter1") ctx.WriteString("|BeforeRouter1")
} }
func beegoBeforeExec1(ctx *context.Context) { func beegoBeforeExec1(ctx *context.Context) {
ctx.WriteString("|BeforeExec1") ctx.WriteString("|BeforeExec1")
} }
func beegoAfterExec1(ctx *context.Context) { func beegoAfterExec1(ctx *context.Context) {
ctx.WriteString("|AfterExec1") ctx.WriteString("|AfterExec1")
} }
func beegoFinishRouter1(ctx *context.Context) { func beegoFinishRouter1(ctx *context.Context) {
ctx.WriteString("|FinishRouter1") ctx.WriteString("|FinishRouter1")
} }
@ -709,3 +706,27 @@ func TestYAMLPrepare(t *testing.T) {
t.Errorf(w.Body.String()) t.Errorf(w.Body.String())
} }
} }
func TestRouterEntityTooLargeCopyBody(t *testing.T) {
_MaxMemory := BConfig.MaxMemory
_CopyRequestBody := BConfig.CopyRequestBody
BConfig.CopyRequestBody = true
BConfig.MaxMemory = 20
b := bytes.NewBuffer([]byte("barbarbarbarbarbarbarbarbarbar"))
r, _ := http.NewRequest("POST", "/user/123", b)
w := httptest.NewRecorder()
handler := NewControllerRegister()
handler.Post("/user/:id", func(ctx *context.Context) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
})
handler.ServeHTTP(w, r)
BConfig.CopyRequestBody = _CopyRequestBody
BConfig.MaxMemory = _MaxMemory
if w.Code != http.StatusRequestEntityTooLarge {
t.Errorf("TestRouterRequestEntityTooLarge can't run")
}
}

View File

@ -31,14 +31,16 @@
// //
// more docs: http://beego.me/docs/module/session.md // more docs: http://beego.me/docs/module/session.md
package redis_cluster package redis_cluster
import ( import (
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/astaxie/beego/session" "github.com/astaxie/beego/session"
rediss "github.com/go-redis/redis" rediss "github.com/go-redis/redis"
"time"
) )
var redispder = &Provider{} var redispder = &Provider{}
@ -101,7 +103,7 @@ func (rs *SessionStore) SessionRelease(w http.ResponseWriter) {
return return
} }
c := rs.p c := rs.p
c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime) * time.Second) c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime)*time.Second)
} }
// Provider redis_cluster session provider // Provider redis_cluster session provider
@ -146,10 +148,10 @@ func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
} else { } else {
rp.dbNum = 0 rp.dbNum = 0
} }
rp.poollist = rediss.NewClusterClient(&rediss.ClusterOptions{ rp.poollist = rediss.NewClusterClient(&rediss.ClusterOptions{
Addrs: strings.Split(rp.savePath, ";"), Addrs: strings.Split(rp.savePath, ";"),
Password: rp.password, Password: rp.password,
PoolSize: rp.poolsize, PoolSize: rp.poolsize,
}) })
return rp.poollist.Ping().Err() return rp.poollist.Ping().Err()
@ -186,15 +188,15 @@ func (rp *Provider) SessionExist(sid string) bool {
// SessionRegenerate generate new sid for redis_cluster session // SessionRegenerate generate new sid for redis_cluster session
func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
c := rp.poollist c := rp.poollist
if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 { if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 {
// oldsid doesn't exists, set the new sid directly // oldsid doesn't exists, set the new sid directly
// ignore error here, since if it return error // ignore error here, since if it return error
// the existed value will be 0 // the existed value will be 0
c.Set(sid, "", time.Duration(rp.maxlifetime) * time.Second) c.Set(sid, "", time.Duration(rp.maxlifetime)*time.Second)
} else { } else {
c.Rename(oldsid, sid) c.Rename(oldsid, sid)
c.Expire(sid, time.Duration(rp.maxlifetime) * time.Second) c.Expire(sid, time.Duration(rp.maxlifetime)*time.Second)
} }
return rp.SessionRead(sid) return rp.SessionRead(sid)
} }

View File

@ -33,13 +33,14 @@
package redis_sentinel package redis_sentinel
import ( import (
"github.com/astaxie/beego/session"
"github.com/go-redis/redis"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/astaxie/beego/session"
"github.com/go-redis/redis"
) )
var redispder = &Provider{} var redispder = &Provider{}

View File

@ -15,11 +15,11 @@
package session package session
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"errors"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
@ -180,6 +180,11 @@ func (fp *FileProvider) SessionExist(sid string) bool {
filepder.lock.Lock() filepder.lock.Lock()
defer filepder.lock.Unlock() defer filepder.lock.Unlock()
if len(sid) < 2 {
SLogger.Println("min length of session id is 2", sid)
return false
}
_, err := os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid)) _, err := os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
return err == nil return err == nil
} }

386
session/sess_file_test.go Normal file
View File

@ -0,0 +1,386 @@
// 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 session
import (
"fmt"
"os"
"sync"
"testing"
"time"
)
const sid = "Session_id"
const sidNew = "Session_id_new"
const sessionPath = "./_session_runtime"
var (
mutex sync.Mutex
)
func TestFileProvider_SessionInit(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
if fp.maxlifetime != 180 {
t.Error()
}
if fp.savePath != sessionPath {
t.Error()
}
}
func TestFileProvider_SessionExist(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
if fp.SessionExist(sid) {
t.Error()
}
_, err := fp.SessionRead(sid)
if err != nil {
t.Error(err)
}
if !fp.SessionExist(sid) {
t.Error()
}
}
func TestFileProvider_SessionExist2(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
if fp.SessionExist(sid) {
t.Error()
}
if fp.SessionExist("") {
t.Error()
}
if fp.SessionExist("1") {
t.Error()
}
}
func TestFileProvider_SessionRead(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
s, err := fp.SessionRead(sid)
if err != nil {
t.Error(err)
}
_ = s.Set("sessionValue", 18975)
v := s.Get("sessionValue")
if v.(int) != 18975 {
t.Error()
}
}
func TestFileProvider_SessionRead1(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
_, err := fp.SessionRead("")
if err == nil {
t.Error(err)
}
_, err = fp.SessionRead("1")
if err == nil {
t.Error(err)
}
}
func TestFileProvider_SessionAll(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
sessionCount := 546
for i := 1; i <= sessionCount; i++ {
_, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i))
if err != nil {
t.Error(err)
}
}
if fp.SessionAll() != sessionCount {
t.Error()
}
}
func TestFileProvider_SessionRegenerate(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
_, err := fp.SessionRead(sid)
if err != nil {
t.Error(err)
}
if !fp.SessionExist(sid) {
t.Error()
}
_, err = fp.SessionRegenerate(sid, sidNew)
if err != nil {
t.Error(err)
}
if fp.SessionExist(sid) {
t.Error()
}
if !fp.SessionExist(sidNew) {
t.Error()
}
}
func TestFileProvider_SessionDestroy(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
_, err := fp.SessionRead(sid)
if err != nil {
t.Error(err)
}
if !fp.SessionExist(sid) {
t.Error()
}
err = fp.SessionDestroy(sid)
if err != nil {
t.Error(err)
}
if fp.SessionExist(sid) {
t.Error()
}
}
func TestFileProvider_SessionGC(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(1, sessionPath)
sessionCount := 412
for i := 1; i <= sessionCount; i++ {
_, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i))
if err != nil {
t.Error(err)
}
}
time.Sleep(2 * time.Second)
fp.SessionGC()
if fp.SessionAll() != 0 {
t.Error()
}
}
func TestFileSessionStore_Set(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
sessionCount := 100
s, _ := fp.SessionRead(sid)
for i := 1; i <= sessionCount; i++ {
err := s.Set(i, i)
if err != nil {
t.Error(err)
}
}
}
func TestFileSessionStore_Get(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
sessionCount := 100
s, _ := fp.SessionRead(sid)
for i := 1; i <= sessionCount; i++ {
_ = s.Set(i, i)
v := s.Get(i)
if v.(int) != i {
t.Error()
}
}
}
func TestFileSessionStore_Delete(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
s, _ := fp.SessionRead(sid)
s.Set("1", 1)
if s.Get("1") == nil {
t.Error()
}
s.Delete("1")
if s.Get("1") != nil {
t.Error()
}
}
func TestFileSessionStore_Flush(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
sessionCount := 100
s, _ := fp.SessionRead(sid)
for i := 1; i <= sessionCount; i++ {
_ = s.Set(i, i)
}
_ = s.Flush()
for i := 1; i <= sessionCount; i++ {
if s.Get(i) != nil {
t.Error()
}
}
}
func TestFileSessionStore_SessionID(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
sessionCount := 85
for i := 1; i <= sessionCount; i++ {
s, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i))
if err != nil {
t.Error(err)
}
if s.SessionID() != fmt.Sprintf("%s_%d", sid, i) {
t.Error(err)
}
}
}
func TestFileSessionStore_SessionRelease(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
filepder.savePath = sessionPath
sessionCount := 85
for i := 1; i <= sessionCount; i++ {
s, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i))
if err != nil {
t.Error(err)
}
s.Set(i, i)
s.SessionRelease(nil)
}
for i := 1; i <= sessionCount; i++ {
s, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i))
if err != nil {
t.Error(err)
}
if s.Get(i).(int) != i {
t.Error()
}
}
}

View File

@ -92,20 +92,21 @@ func GetProvider(name string) (Provider, error) {
// ManagerConfig define the session config // ManagerConfig define the session config
type ManagerConfig struct { type ManagerConfig struct {
CookieName string `json:"cookieName"` CookieName string `json:"cookieName"`
EnableSetCookie bool `json:"enableSetCookie,omitempty"` EnableSetCookie bool `json:"enableSetCookie,omitempty"`
Gclifetime int64 `json:"gclifetime"` Gclifetime int64 `json:"gclifetime"`
Maxlifetime int64 `json:"maxLifetime"` Maxlifetime int64 `json:"maxLifetime"`
DisableHTTPOnly bool `json:"disableHTTPOnly"` DisableHTTPOnly bool `json:"disableHTTPOnly"`
Secure bool `json:"secure"` Secure bool `json:"secure"`
CookieLifeTime int `json:"cookieLifeTime"` CookieLifeTime int `json:"cookieLifeTime"`
ProviderConfig string `json:"providerConfig"` ProviderConfig string `json:"providerConfig"`
Domain string `json:"domain"` Domain string `json:"domain"`
SessionIDLength int64 `json:"sessionIDLength"` SessionIDLength int64 `json:"sessionIDLength"`
EnableSidInHTTPHeader bool `json:"EnableSidInHTTPHeader"` EnableSidInHTTPHeader bool `json:"EnableSidInHTTPHeader"`
SessionNameInHTTPHeader string `json:"SessionNameInHTTPHeader"` SessionNameInHTTPHeader string `json:"SessionNameInHTTPHeader"`
EnableSidInURLQuery bool `json:"EnableSidInURLQuery"` EnableSidInURLQuery bool `json:"EnableSidInURLQuery"`
SessionIDPrefix string `json:"sessionIDPrefix"` SessionIDPrefix string `json:"sessionIDPrefix"`
CookieSameSite http.SameSite `json:"cookieSameSite"`
} }
// Manager contains Provider and its configuration. // Manager contains Provider and its configuration.
@ -232,6 +233,7 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se
HttpOnly: !manager.config.DisableHTTPOnly, HttpOnly: !manager.config.DisableHTTPOnly,
Secure: manager.isSecure(r), Secure: manager.isSecure(r),
Domain: manager.config.Domain, Domain: manager.config.Domain,
SameSite: manager.config.CookieSameSite,
} }
if manager.config.CookieLifeTime > 0 { if manager.config.CookieLifeTime > 0 {
cookie.MaxAge = manager.config.CookieLifeTime cookie.MaxAge = manager.config.CookieLifeTime
@ -271,7 +273,9 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
HttpOnly: !manager.config.DisableHTTPOnly, HttpOnly: !manager.config.DisableHTTPOnly,
Expires: expiration, Expires: expiration,
MaxAge: -1, MaxAge: -1,
Domain: manager.config.Domain} Domain: manager.config.Domain,
SameSite: manager.config.CookieSameSite,
}
http.SetCookie(w, cookie) http.SetCookie(w, cookie)
} }
@ -291,25 +295,36 @@ func (manager *Manager) GC() {
} }
// SessionRegenerateID Regenerate a session id for this SessionStore who's id is saving in http request. // SessionRegenerateID Regenerate a session id for this SessionStore who's id is saving in http request.
func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Request) (session Store) { func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Request) (Store, error) {
sid, err := manager.sessionID() sid, err := manager.sessionID()
if err != nil { if err != nil {
return return nil, err
} }
var session Store
cookie, err := r.Cookie(manager.config.CookieName) cookie, err := r.Cookie(manager.config.CookieName)
if err != nil || cookie.Value == "" { if err != nil || cookie.Value == "" {
//delete old cookie // delete old cookie
session, _ = manager.provider.SessionRead(sid) session, err = manager.provider.SessionRead(sid)
if err != nil {
return nil, err
}
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: !manager.config.DisableHTTPOnly, HttpOnly: !manager.config.DisableHTTPOnly,
Secure: manager.isSecure(r), Secure: manager.isSecure(r),
Domain: manager.config.Domain, Domain: manager.config.Domain,
SameSite: manager.config.CookieSameSite,
} }
} else { } else {
oldsid, _ := url.QueryUnescape(cookie.Value) oldsid, err := url.QueryUnescape(cookie.Value)
session, _ = manager.provider.SessionRegenerate(oldsid, sid) if err != nil {
return nil, err
}
session, err = manager.provider.SessionRegenerate(oldsid, sid)
if err != nil {
return nil, err
}
cookie.Value = url.QueryEscape(sid) cookie.Value = url.QueryEscape(sid)
cookie.HttpOnly = true cookie.HttpOnly = true
cookie.Path = "/" cookie.Path = "/"
@ -328,7 +343,7 @@ func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Reque
w.Header().Set(manager.config.SessionNameInHTTPHeader, sid) w.Header().Set(manager.config.SessionNameInHTTPHeader, sid)
} }
return return session, nil
} }
// GetActiveSession Get all active sessions count number. // GetActiveSession Get all active sessions count number.

View File

@ -202,7 +202,7 @@ func searchFile(ctx *context.Context) (string, os.FileInfo, error) {
if !strings.Contains(requestPath, prefix) { if !strings.Contains(requestPath, prefix) {
continue continue
} }
if prefix != "/" && len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' { if prefix != "/" && len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' {
continue continue
} }
filePath := path.Join(staticDir, requestPath[len(prefix):]) filePath := path.Join(staticDir, requestPath[len(prefix):])

View File

@ -16,12 +16,13 @@ package beego
import ( import (
"bytes" "bytes"
"github.com/astaxie/beego/testdata"
"github.com/elazarl/go-bindata-assetfs"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/astaxie/beego/testdata"
"github.com/elazarl/go-bindata-assetfs"
) )
var header = `{{define "header"}} var header = `{{define "header"}}
@ -45,8 +46,12 @@ var block = `{{define "block"}}
<h1>Hello, blocks!</h1> <h1>Hello, blocks!</h1>
{{end}}` {{end}}`
func tmpDir(s string) string {
return filepath.Join(os.TempDir(), s)
}
func TestTemplate(t *testing.T) { func TestTemplate(t *testing.T) {
dir := "_beeTmp" dir := tmpDir("TestTemplate")
files := []string{ files := []string{
"header.tpl", "header.tpl",
"index.tpl", "index.tpl",
@ -107,7 +112,7 @@ var user = `<!DOCTYPE html>
` `
func TestRelativeTemplate(t *testing.T) { func TestRelativeTemplate(t *testing.T) {
dir := "_beeTmp" dir := tmpDir("TestRelativeTemplate")
//Just add dir to known viewPaths //Just add dir to known viewPaths
if err := AddViewPath(dir); err != nil { if err := AddViewPath(dir); err != nil {
@ -218,7 +223,7 @@ var output = `<!DOCTYPE html>
` `
func TestTemplateLayout(t *testing.T) { func TestTemplateLayout(t *testing.T) {
dir := "_beeTmp" dir := tmpDir("TestTemplateLayout")
files := []string{ files := []string{
"add.tpl", "add.tpl",
"layout_blog.tpl", "layout_blog.tpl",

View File

@ -362,7 +362,7 @@ func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) e
value = value[:25] value = value[:25]
t, err = time.ParseInLocation(time.RFC3339, value, time.Local) t, err = time.ParseInLocation(time.RFC3339, value, time.Local)
} else if strings.HasSuffix(strings.ToUpper(value), "Z") { } else if strings.HasSuffix(strings.ToUpper(value), "Z") {
t, err = time.ParseInLocation(time.RFC3339, value, time.Local) t, err = time.ParseInLocation(time.RFC3339, value, time.Local)
} else if len(value) >= 19 { } else if len(value) >= 19 {
if strings.Contains(value, "T") { if strings.Contains(value, "T") {
value = value[:19] value = value[:19]

14
test.sh Normal file
View File

@ -0,0 +1,14 @@
#!/bin/bash
docker-compose -f test_docker_compose.yaml up -d
export ORM_DRIVER=mysql
export TZ=UTC
export ORM_SOURCE="beego:test@tcp(localhost:13306)/orm_test?charset=utf8"
go test ./...
# clear all container
docker-compose -f test_docker_compose.yaml down

39
test_docker_compose.yaml Normal file
View File

@ -0,0 +1,39 @@
version: "3.8"
services:
redis:
container_name: "beego-redis"
image: redis
environment:
- ALLOW_EMPTY_PASSWORD=yes
ports:
- "6379:6379"
mysql:
container_name: "beego-mysql"
image: mysql:5.7.30
ports:
- "13306:3306"
environment:
- MYSQL_ROOT_PASSWORD=1q2w3e
- MYSQL_DATABASE=orm_test
- MYSQL_USER=beego
- MYSQL_PASSWORD=test
postgresql:
container_name: "beego-postgresql"
image: bitnami/postgresql:latest
ports:
- "5432:5432"
environment:
- ALLOW_EMPTY_PASSWORD=yes
ssdb:
container_name: "beego-ssdb"
image: wendal/ssdb
ports:
- "8888:8888"
memcache:
container_name: "beego-memcache"
image: memcached
ports:
- "11211:11211"

3
testdata/bindata.go vendored
View File

@ -11,13 +11,14 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"fmt" "fmt"
"github.com/elazarl/go-bindata-assetfs"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/elazarl/go-bindata-assetfs"
) )
func bindataRead(data []byte, name string) ([]byte, error) { func bindataRead(data []byte, name string) ([]byte, error) {

View File

@ -341,7 +341,7 @@ func (t *Tree) match(treePattern string, pattern string, wildcardValues []string
if runObject == nil && len(t.fixrouters) > 0 { if runObject == nil && len(t.fixrouters) > 0 {
// Filter the .json .xml .html extension // Filter the .json .xml .html extension
for _, str := range allowSuffixExt { for _, str := range allowSuffixExt {
if strings.HasSuffix(seg, str) { if strings.HasSuffix(seg, str) && strings.HasSuffix(treePattern, seg) {
for _, subTree := range t.fixrouters { for _, subTree := range t.fixrouters {
if subTree.prefix == seg[:len(seg)-len(str)] { if subTree.prefix == seg[:len(seg)-len(str)] {
runObject = subTree.match(treePattern, pattern, wildcardValues, ctx) runObject = subTree.match(treePattern, pattern, wildcardValues, ctx)

View File

@ -213,7 +213,7 @@ func parseFunc(vfunc, key string, label string) (v ValidFunc, err error) {
return return
} }
tParams, err := trim(name, key+"."+ name + "." + label, params) tParams, err := trim(name, key+"."+name+"."+label, params)
if err != nil { if err != nil {
return return
} }

View File

@ -16,13 +16,14 @@ package validation
import ( import (
"fmt" "fmt"
"github.com/astaxie/beego/logs"
"reflect" "reflect"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/astaxie/beego/logs"
) )
// CanSkipFuncs will skip valid if RequiredFirst is true and the struct field's value is empty // CanSkipFuncs will skip valid if RequiredFirst is true and the struct field's value is empty