mirror of
https://github.com/astaxie/beego.git
synced 2025-07-11 14:31:02 +00:00
Compare commits
306 Commits
Author | SHA1 | Date | |
---|---|---|---|
32ee728078 | |||
de7ce2f9b0 | |||
d6c2a9fd4b | |||
582a4fa34b | |||
99647986de | |||
5bcde306ea | |||
2909ff3366 | |||
40078cba2c | |||
5d0c0a03d7 | |||
8cfd7f5c19 | |||
62d96c2e93 | |||
e844058aed | |||
1eab6bb32a | |||
c265d32c36 | |||
06692c3e27 | |||
394a73c75f | |||
cbcde8bd1f | |||
b17e49e6aa | |||
873f62edff | |||
cc0eacbe02 | |||
649c5c861d | |||
206a7ed1fc | |||
3c046a4dbf | |||
f2be6af2ca | |||
804b9769e0 | |||
585df01899 | |||
1a529c061c | |||
fcacfc08e3 | |||
a0ca3d61d6 | |||
39bd40e512 | |||
8748de95c7 | |||
0939e8e493 | |||
6a33feee46 | |||
58b2ac702c | |||
175714f69a | |||
d5b70118a3 | |||
a9629f707e | |||
6123c72752 | |||
56afa5c2bf | |||
0c576dac82 | |||
8462372c03 | |||
20ff97d53d | |||
8ce5b6cc52 | |||
afb787d49d | |||
0b165b78a1 | |||
fa97488bdc | |||
3086081ec0 | |||
dfab44c24a | |||
1900246054 | |||
e980f92c63 | |||
ce3800e3ef | |||
e3d668f450 | |||
75b4bc5896 | |||
c0ecf32d17 | |||
3155f07ccd | |||
02bead5097 | |||
e8b29c9fd1 | |||
610f27d684 | |||
59466f6678 | |||
3ddd8f860e | |||
8535ec0819 | |||
f294121ab7 | |||
0b8ebaf387 | |||
3b00cfccec | |||
005391be81 | |||
bc8fffe347 | |||
914bbfd710 | |||
be31bd2bbd | |||
95ff817019 | |||
ea91e7638c | |||
4564e9810c | |||
44a1a8f6be | |||
9cecb22170 | |||
7693502aaa | |||
c0fae547e9 | |||
0ba77a0d87 | |||
661dcbb6ca | |||
578440a18d | |||
93485df3d2 | |||
121fab61f1 | |||
915eec7943 | |||
8432a1c758 | |||
4a5e108527 | |||
6dd5171fdf | |||
c2b6cb5c3a | |||
1f93040af6 | |||
52f8ccd06c | |||
3b86feab8a | |||
aba51d99a1 | |||
8454e8417e | |||
422e8285b5 | |||
bb6ca6b100 | |||
1483c1f545 | |||
3d6a68de77 | |||
387d387080 | |||
94ed35e781 | |||
8995b291a9 | |||
1942438b22 | |||
40d653e659 | |||
ddcd28e67f | |||
65f587d5e9 | |||
2fefd8cbbf | |||
ba17bdd366 | |||
c998e52cc0 | |||
3224369ac9 | |||
67666dbe0f | |||
d7430eb921 | |||
26a6b426f1 | |||
6f35ce67f7 | |||
3406d58797 | |||
7925458fc0 | |||
b6854aaf9f | |||
656f595226 | |||
280aaf9d3b | |||
7abdb05f91 | |||
af4464ce58 | |||
6ca0978777 | |||
8506194d2c | |||
3bd7614ade | |||
bd1b421491 | |||
920207f72c | |||
12fdc04f1b | |||
d0c744ae6a | |||
dc07fa7085 | |||
1c893996c0 | |||
a9ffc2a078 | |||
712bbfe575 | |||
0145fe3486 | |||
2a579eb27c | |||
5b42afa324 | |||
97713849a1 | |||
abc9c38224 | |||
f237ff049a | |||
00264650b5 | |||
2956d33bab | |||
c3eca637fb | |||
0d54bbff02 | |||
e65a9cbc00 | |||
3ed82c0882 | |||
475feb7e24 | |||
30b80cba92 | |||
fe519bd2a0 | |||
f508f8d959 | |||
e295c3c7c3 | |||
313be996cd | |||
2ae480556d | |||
7173fd7490 | |||
d792536c23 | |||
80aabdd372 | |||
6892369cc6 | |||
5b80a56c36 | |||
bf15535a5b | |||
edb1c52dee | |||
6e16b8cdcf | |||
24215fb3eb | |||
d02699a189 | |||
2034d1b101 | |||
5fe19d639f | |||
1dea80d4ea | |||
28f0008075 | |||
ffe1b00baf | |||
d2c289193a | |||
f867583256 | |||
98dfb92e17 | |||
c8f22be675 | |||
f03a7d1128 | |||
6da4a66c20 | |||
5e4241fc87 | |||
bf468c8d0c | |||
0d77a3f8d2 | |||
1b6edafc96 | |||
8152ade1b6 | |||
6350f8b904 | |||
0c5398a19c | |||
4b656268d3 | |||
10729a1fc5 | |||
cdb3ef808f | |||
a5a2471f2c | |||
6282747f6d | |||
d5fd5cad38 | |||
fab7c6b6d0 | |||
42ade6aa49 | |||
55d9b69cd9 | |||
2a8d6f943f | |||
6b0155c4fb | |||
e22a5143bc | |||
a17eb54515 | |||
d5cf1050db | |||
b021686521 | |||
b0e2bbce2a | |||
7a50ea7e36 | |||
3070cfc60b | |||
e56d1b718f | |||
c4c3067a31 | |||
81346fe641 | |||
1a66ad56c6 | |||
31f2adb79d | |||
f514ae309b | |||
277d3d98e3 | |||
2c46877b36 | |||
cfe54a02c5 | |||
55f390d08a | |||
cf31222643 | |||
9e036bcab5 | |||
d02170e3cb | |||
2d6f1af1a5 | |||
430457609f | |||
0333e26b3e | |||
d0d28566b9 | |||
872b787e6c | |||
01a99edf80 | |||
6050d37d2a | |||
876dce8e54 | |||
24885c28f2 | |||
5ea04bdfd3 | |||
9fdc1eaf3a | |||
6b5a70d246 | |||
8391d26220 | |||
f193e313a3 | |||
9ac4928113 | |||
9865779f14 | |||
aa6d0f9f0b | |||
68b0bd98fd | |||
3447798494 | |||
ca1b96f986 | |||
771fe35431 | |||
2f00ad1602 | |||
f740b71ded | |||
7aae58a543 | |||
c8da875f83 | |||
501d8a97f6 | |||
736e66fcda | |||
d3ad810f16 | |||
abc8b78065 | |||
f64e6b72e9 | |||
4e83b4400a | |||
f6f61513a1 | |||
8217817a0b | |||
833f54d818 | |||
706c086bc5 | |||
187add9b84 | |||
5b8e468a13 | |||
dff9c8f5fa | |||
21a8623002 | |||
ea9c5822e6 | |||
ad0d166d46 | |||
2c2ace9a60 | |||
8134a89e81 | |||
e342a0099f | |||
c4ed5030da | |||
7b9c24567d | |||
6906c5ce30 | |||
e4605f232b | |||
6092e737a1 | |||
0e4d954fa7 | |||
1cbba4d56f | |||
cf5d1f3f3c | |||
1097ac3682 | |||
755cc98ef7 | |||
5c407ff2e3 | |||
8f455ef199 | |||
7e0649d661 | |||
1a3dcb4f84 | |||
b606f1f73f | |||
8241f219fd | |||
dea45a3d6c | |||
34a812d45f | |||
842336834f | |||
58fe012446 | |||
bf0d40bca6 | |||
48e6658eca | |||
1bd3fb7a33 | |||
d86410a631 | |||
6b62502b99 | |||
053a075344 | |||
9dd7d19ce7 | |||
efe0f67388 | |||
de66d2bdfd | |||
0e4fe4d177 | |||
6d84db1e93 | |||
2486f3826a | |||
d7b8aa8b52 | |||
c7c0b01ec5 | |||
6d69047fff | |||
787ab12605 | |||
f4112accef | |||
42c394e28b | |||
5051d902fb | |||
48acfa08be | |||
39fc30b8b2 | |||
046cb248e0 | |||
31c746d9d7 | |||
38a2f32252 | |||
d55f54a8ab | |||
feb0e67fd7 | |||
5acc56648d | |||
b8868d6d2d | |||
30bbc81a2e | |||
1a3f1d66c1 | |||
6bdd152d91 | |||
443c77b303 | |||
0dff771707 | |||
1a42154c64 | |||
e81cca304b | |||
07aa97aa9a | |||
7886e69236 |
@ -1,4 +0,0 @@
|
||||
github.com/astaxie/beego/*/*:S1012
|
||||
github.com/astaxie/beego/*:S1012
|
||||
github.com/astaxie/beego/*/*:S1007
|
||||
github.com/astaxie/beego/*:S1007
|
32
.travis.yml
32
.travis.yml
@ -1,17 +1,26 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.9.2"
|
||||
- "1.10.3"
|
||||
- "1.11.x"
|
||||
services:
|
||||
- redis-server
|
||||
- mysql
|
||||
- postgresql
|
||||
- memcached
|
||||
env:
|
||||
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
|
||||
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
||||
global:
|
||||
- GO_REPO_FULLNAME="github.com/astaxie/beego"
|
||||
matrix:
|
||||
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
|
||||
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
||||
before_install:
|
||||
# link the local repo with ${GOPATH}/src/<namespace>/<repo>
|
||||
- GO_REPO_NAMESPACE=${GO_REPO_FULLNAME%/*}
|
||||
# relies on GOPATH to contain only one directory...
|
||||
- mkdir -p ${GOPATH}/src/${GO_REPO_NAMESPACE}
|
||||
- ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH}/src/${GO_REPO_FULLNAME}
|
||||
- cd ${GOPATH}/src/${GO_REPO_FULLNAME}
|
||||
# get and build ssdb
|
||||
- git clone git://github.com/ideawu/ssdb.git
|
||||
- cd ssdb
|
||||
- make
|
||||
@ -34,7 +43,10 @@ install:
|
||||
- go get github.com/gogo/protobuf/proto
|
||||
- go get github.com/Knetic/govaluate
|
||||
- go get github.com/casbin/casbin
|
||||
- go get -u honnef.co/go/tools/cmd/gosimple
|
||||
- go get github.com/elazarl/go-bindata-assetfs
|
||||
- go get github.com/OwnLocal/goes
|
||||
- go get github.com/shiena/ansicolor
|
||||
- go get -u honnef.co/go/tools/cmd/staticcheck
|
||||
- go get -u github.com/mdempsky/unconvert
|
||||
- go get -u github.com/gordonklaus/ineffassign
|
||||
- go get -u github.com/golang/lint/golint
|
||||
@ -44,19 +56,19 @@ before_script:
|
||||
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
|
||||
- sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi"
|
||||
- sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi"
|
||||
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go get github.com/golang/lint/golint; golint ./...; fi"
|
||||
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go tool vet .; fi"
|
||||
- sh -c "go get github.com/golang/lint/golint; golint ./...;"
|
||||
- sh -c "go list ./... | grep -v vendor | xargs go vet -v"
|
||||
- mkdir -p res/var
|
||||
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
|
||||
after_script:
|
||||
-killall -w ssdb-server
|
||||
- killall -w ssdb-server
|
||||
- rm -rf ./res/var/*
|
||||
script:
|
||||
- go test -v ./...
|
||||
- gosimple -ignore "$(cat .gosimpleignore)" $(go list ./... | grep -v /vendor/)
|
||||
- staticcheck -show-ignored -checks "-ST1017,-U1000,-ST1005,-S1034,-S1012,-SA4006,-SA6005,-SA1019,-SA1024"
|
||||
- unconvert $(go list ./... | grep -v /vendor/)
|
||||
- ineffassign .
|
||||
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
|
||||
- golint ./...
|
||||
addons:
|
||||
postgresql: "9.4"
|
||||
postgresql: "9.6"
|
||||
|
@ -4,6 +4,8 @@
|
||||
beego is used for rapid development of RESTful APIs, web apps and backend services in Go.
|
||||
It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding.
|
||||
|
||||
Response time ranking: [web-frameworks](https://github.com/the-benchmarker/web-frameworks).
|
||||
|
||||
###### More info at [beego.me](http://beego.me).
|
||||
|
||||
## Quick Start
|
||||
|
35
admin.go
35
admin.go
@ -20,11 +20,10 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"reflect"
|
||||
|
||||
"github.com/astaxie/beego/grace"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/toolbox"
|
||||
@ -35,7 +34,7 @@ import (
|
||||
var beeAdminApp *adminApp
|
||||
|
||||
// FilterMonitorFunc is default monitor filter when admin module is enable.
|
||||
// if this func returns, admin module records qbs for this request by condition of this function logic.
|
||||
// if this func returns, admin module records qps for this request by condition of this function logic.
|
||||
// usage:
|
||||
// func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool {
|
||||
// if method == "POST" {
|
||||
@ -67,18 +66,18 @@ func init() {
|
||||
|
||||
// AdminIndex is the default http.Handler for admin module.
|
||||
// it matches url pattern "/".
|
||||
func adminIndex(rw http.ResponseWriter, r *http.Request) {
|
||||
func adminIndex(rw http.ResponseWriter, _ *http.Request) {
|
||||
execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
|
||||
}
|
||||
|
||||
// QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter.
|
||||
// it's registered with url pattern "/qbs" in admin module.
|
||||
func qpsIndex(rw http.ResponseWriter, r *http.Request) {
|
||||
// QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter.
|
||||
// it's registered with url pattern "/qps" in admin module.
|
||||
func qpsIndex(rw http.ResponseWriter, _ *http.Request) {
|
||||
data := make(map[interface{}]interface{})
|
||||
data["Content"] = toolbox.StatisticsMap.GetMap()
|
||||
|
||||
// do html escape before display path, avoid xss
|
||||
if content, ok := (data["Content"]).(map[string]interface{}); ok {
|
||||
if content, ok := (data["Content"]).(M); ok {
|
||||
if resultLists, ok := (content["Data"]).([][]string); ok {
|
||||
for i := range resultLists {
|
||||
if len(resultLists[i]) > 0 {
|
||||
@ -104,7 +103,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
|
||||
data := make(map[interface{}]interface{})
|
||||
switch command {
|
||||
case "conf":
|
||||
m := make(map[string]interface{})
|
||||
m := make(M)
|
||||
list("BConfig", BConfig, m)
|
||||
m["AppConfigPath"] = appConfigPath
|
||||
m["AppConfigProvider"] = appConfigProvider
|
||||
@ -128,14 +127,14 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
|
||||
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
|
||||
case "filter":
|
||||
var (
|
||||
content = map[string]interface{}{
|
||||
content = M{
|
||||
"Fields": []string{
|
||||
"Router Pattern",
|
||||
"Filter Function",
|
||||
},
|
||||
}
|
||||
filterTypes = []string{}
|
||||
filterTypeData = make(map[string]interface{})
|
||||
filterTypeData = make(M)
|
||||
)
|
||||
|
||||
if BeeApp.Handlers.enableFilter {
|
||||
@ -173,7 +172,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func list(root string, p interface{}, m map[string]interface{}) {
|
||||
func list(root string, p interface{}, m M) {
|
||||
pt := reflect.TypeOf(p)
|
||||
pv := reflect.ValueOf(p)
|
||||
if pt.Kind() == reflect.Ptr {
|
||||
@ -196,11 +195,11 @@ func list(root string, p interface{}, m map[string]interface{}) {
|
||||
}
|
||||
|
||||
// PrintTree prints all registered routers.
|
||||
func PrintTree() map[string]interface{} {
|
||||
func PrintTree() M {
|
||||
var (
|
||||
content = map[string]interface{}{}
|
||||
content = M{}
|
||||
methods = []string{}
|
||||
methodsData = make(map[string]interface{})
|
||||
methodsData = make(M)
|
||||
)
|
||||
for method, t := range BeeApp.Handlers.routers {
|
||||
|
||||
@ -291,12 +290,12 @@ func profIndex(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Healthcheck is a http.Handler calling health checking and showing the result.
|
||||
// it's in "/healthcheck" pattern in admin module.
|
||||
func healthcheck(rw http.ResponseWriter, req *http.Request) {
|
||||
func healthcheck(rw http.ResponseWriter, _ *http.Request) {
|
||||
var (
|
||||
result []string
|
||||
data = make(map[interface{}]interface{})
|
||||
resultList = new([][]string)
|
||||
content = map[string]interface{}{
|
||||
content = M{
|
||||
"Fields": []string{"Name", "Message", "Status"},
|
||||
}
|
||||
)
|
||||
@ -344,7 +343,7 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
// List Tasks
|
||||
content := make(map[string]interface{})
|
||||
content := make(M)
|
||||
resultList := new([][]string)
|
||||
var fields = []string{
|
||||
"Task Name",
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func TestList_01(t *testing.T) {
|
||||
m := make(map[string]interface{})
|
||||
m := make(M)
|
||||
list("BConfig", BConfig, m)
|
||||
t.Log(m)
|
||||
om := oldMap()
|
||||
@ -18,8 +18,8 @@ func TestList_01(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func oldMap() map[string]interface{} {
|
||||
m := make(map[string]interface{})
|
||||
func oldMap() M {
|
||||
m := make(M)
|
||||
m["BConfig.AppName"] = BConfig.AppName
|
||||
m["BConfig.RunMode"] = BConfig.RunMode
|
||||
m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive
|
||||
|
4
app.go
4
app.go
@ -176,7 +176,7 @@ func (app *App) Run(mws ...MiddleWare) {
|
||||
if BConfig.Listen.HTTPSPort != 0 {
|
||||
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
||||
} else if BConfig.Listen.EnableHTTP {
|
||||
BeeLogger.Info("Start https server error, conflict with http. Please reset https port")
|
||||
logs.Info("Start https server error, conflict with http. Please reset https port")
|
||||
return
|
||||
}
|
||||
logs.Info("https server Running on https://%s", app.Server.Addr)
|
||||
@ -192,7 +192,7 @@ func (app *App) Run(mws ...MiddleWare) {
|
||||
pool := x509.NewCertPool()
|
||||
data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile)
|
||||
if err != nil {
|
||||
BeeLogger.Info("MutualHTTPS should provide TrustCaFile")
|
||||
logs.Info("MutualHTTPS should provide TrustCaFile")
|
||||
return
|
||||
}
|
||||
pool.AppendCertsFromPEM(data)
|
||||
|
7
beego.go
7
beego.go
@ -23,7 +23,7 @@ import (
|
||||
|
||||
const (
|
||||
// VERSION represent beego web framework version.
|
||||
VERSION = "1.10.0"
|
||||
VERSION = "1.12.0"
|
||||
|
||||
// DEV is for develop
|
||||
DEV = "dev"
|
||||
@ -31,7 +31,10 @@ const (
|
||||
PROD = "prod"
|
||||
)
|
||||
|
||||
//hook function to run
|
||||
// M is Map shortcut
|
||||
type M map[string]interface{}
|
||||
|
||||
// Hook function to run
|
||||
type hookfunc func() error
|
||||
|
||||
var (
|
||||
|
25
cache/cache_test.go
vendored
25
cache/cache_test.go
vendored
@ -16,10 +16,33 @@ package cache
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCacheIncr(t *testing.T) {
|
||||
bm, err := NewCache("memory", `{"interval":20}`)
|
||||
if err != nil {
|
||||
t.Error("init err")
|
||||
}
|
||||
//timeoutDuration := 10 * time.Second
|
||||
|
||||
bm.Put("edwardhey", 0, time.Second*20)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(10)
|
||||
for i := 0; i < 10; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
bm.Incr("edwardhey")
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if bm.Get("edwardhey").(int) != 10 {
|
||||
t.Error("Incr err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
bm, err := NewCache("memory", `{"interval":20}`)
|
||||
if err != nil {
|
||||
@ -98,7 +121,7 @@ func TestCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileCache(t *testing.T) {
|
||||
bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}`)
|
||||
bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`)
|
||||
if err != nil {
|
||||
t.Error("init err")
|
||||
}
|
||||
|
17
cache/file.go
vendored
17
cache/file.go
vendored
@ -62,11 +62,14 @@ func NewFileCache() Cache {
|
||||
}
|
||||
|
||||
// StartAndGC will start and begin gc for file cache.
|
||||
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}
|
||||
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}
|
||||
func (fc *FileCache) StartAndGC(config string) error {
|
||||
|
||||
var cfg map[string]string
|
||||
json.Unmarshal([]byte(config), &cfg)
|
||||
cfg := make(map[string]string)
|
||||
err := json.Unmarshal([]byte(config), &cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := cfg["CachePath"]; !ok {
|
||||
cfg["CachePath"] = FileCachePath
|
||||
}
|
||||
@ -142,12 +145,12 @@ func (fc *FileCache) GetMulti(keys []string) []interface{} {
|
||||
|
||||
// Put value into file cache.
|
||||
// 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 fc.EmbedExpiry(default is 0), cache this item forever.
|
||||
func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||
gob.Register(val)
|
||||
|
||||
item := FileCacheItem{Data: val}
|
||||
if timeout == FileCacheEmbedExpiry {
|
||||
if timeout == time.Duration(fc.EmbedExpiry) {
|
||||
item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
|
||||
} else {
|
||||
item.Expired = time.Now().Add(timeout)
|
||||
@ -179,7 +182,7 @@ func (fc *FileCache) Incr(key string) error {
|
||||
} else {
|
||||
incr = data.(int) + 1
|
||||
}
|
||||
fc.Put(key, incr, FileCacheEmbedExpiry)
|
||||
fc.Put(key, incr, time.Duration(fc.EmbedExpiry))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -192,7 +195,7 @@ func (fc *FileCache) Decr(key string) error {
|
||||
} else {
|
||||
decr = data.(int) - 1
|
||||
}
|
||||
fc.Put(key, decr, FileCacheEmbedExpiry)
|
||||
fc.Put(key, decr, time.Duration(fc.EmbedExpiry))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
2
cache/memcache/memcache.go
vendored
2
cache/memcache/memcache.go
vendored
@ -146,7 +146,7 @@ func (rc *Cache) IsExist(key string) bool {
|
||||
}
|
||||
}
|
||||
_, err := rc.conn.Get(key)
|
||||
return !(err != nil)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// ClearAll clear all cached in memcache.
|
||||
|
52
cache/memory.go
vendored
52
cache/memory.go
vendored
@ -110,25 +110,25 @@ func (bc *MemoryCache) Delete(name string) error {
|
||||
// Incr increase cache counter in memory.
|
||||
// it supports int,int32,int64,uint,uint32,uint64.
|
||||
func (bc *MemoryCache) Incr(key string) error {
|
||||
bc.RLock()
|
||||
defer bc.RUnlock()
|
||||
bc.Lock()
|
||||
defer bc.Unlock()
|
||||
itm, ok := bc.items[key]
|
||||
if !ok {
|
||||
return errors.New("key not exist")
|
||||
}
|
||||
switch itm.val.(type) {
|
||||
switch val := itm.val.(type) {
|
||||
case int:
|
||||
itm.val = itm.val.(int) + 1
|
||||
itm.val = val + 1
|
||||
case int32:
|
||||
itm.val = itm.val.(int32) + 1
|
||||
itm.val = val + 1
|
||||
case int64:
|
||||
itm.val = itm.val.(int64) + 1
|
||||
itm.val = val + 1
|
||||
case uint:
|
||||
itm.val = itm.val.(uint) + 1
|
||||
itm.val = val + 1
|
||||
case uint32:
|
||||
itm.val = itm.val.(uint32) + 1
|
||||
itm.val = val + 1
|
||||
case uint64:
|
||||
itm.val = itm.val.(uint64) + 1
|
||||
itm.val = val + 1
|
||||
default:
|
||||
return errors.New("item val is not (u)int (u)int32 (u)int64")
|
||||
}
|
||||
@ -137,34 +137,34 @@ func (bc *MemoryCache) Incr(key string) error {
|
||||
|
||||
// Decr decrease counter in memory.
|
||||
func (bc *MemoryCache) Decr(key string) error {
|
||||
bc.RLock()
|
||||
defer bc.RUnlock()
|
||||
bc.Lock()
|
||||
defer bc.Unlock()
|
||||
itm, ok := bc.items[key]
|
||||
if !ok {
|
||||
return errors.New("key not exist")
|
||||
}
|
||||
switch itm.val.(type) {
|
||||
switch val := itm.val.(type) {
|
||||
case int:
|
||||
itm.val = itm.val.(int) - 1
|
||||
itm.val = val - 1
|
||||
case int64:
|
||||
itm.val = itm.val.(int64) - 1
|
||||
itm.val = val - 1
|
||||
case int32:
|
||||
itm.val = itm.val.(int32) - 1
|
||||
itm.val = val - 1
|
||||
case uint:
|
||||
if itm.val.(uint) > 0 {
|
||||
itm.val = itm.val.(uint) - 1
|
||||
if val > 0 {
|
||||
itm.val = val - 1
|
||||
} else {
|
||||
return errors.New("item val is less than 0")
|
||||
}
|
||||
case uint32:
|
||||
if itm.val.(uint32) > 0 {
|
||||
itm.val = itm.val.(uint32) - 1
|
||||
if val > 0 {
|
||||
itm.val = val - 1
|
||||
} else {
|
||||
return errors.New("item val is less than 0")
|
||||
}
|
||||
case uint64:
|
||||
if itm.val.(uint64) > 0 {
|
||||
itm.val = itm.val.(uint64) - 1
|
||||
if val > 0 {
|
||||
itm.val = val - 1
|
||||
} else {
|
||||
return errors.New("item val is less than 0")
|
||||
}
|
||||
@ -203,13 +203,17 @@ func (bc *MemoryCache) StartAndGC(config string) error {
|
||||
dur := time.Duration(cf["interval"]) * time.Second
|
||||
bc.Every = cf["interval"]
|
||||
bc.dur = dur
|
||||
go bc.vaccuum()
|
||||
go bc.vacuum()
|
||||
return nil
|
||||
}
|
||||
|
||||
// check expiration.
|
||||
func (bc *MemoryCache) vaccuum() {
|
||||
if bc.Every < 1 {
|
||||
func (bc *MemoryCache) vacuum() {
|
||||
bc.RLock()
|
||||
every := bc.Every
|
||||
bc.RUnlock()
|
||||
|
||||
if every < 1 {
|
||||
return
|
||||
}
|
||||
for {
|
||||
|
9
cache/redis/redis.go
vendored
9
cache/redis/redis.go
vendored
@ -39,6 +39,7 @@ import (
|
||||
"github.com/gomodule/redigo/redis"
|
||||
|
||||
"github.com/astaxie/beego/cache"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -164,6 +165,14 @@ func (rc *Cache) StartAndGC(config string) error {
|
||||
if _, ok := cf["conn"]; !ok {
|
||||
return errors.New("config has no conn key")
|
||||
}
|
||||
|
||||
// Format redis://<password>@<host>:<port>
|
||||
cf["conn"] = strings.Replace(cf["conn"], "redis://", "", 1)
|
||||
if i := strings.Index(cf["conn"], "@"); i > -1 {
|
||||
cf["password"] = cf["conn"][0:i]
|
||||
cf["conn"] = cf["conn"][i+1:]
|
||||
}
|
||||
|
||||
if _, ok := cf["dbNum"]; !ok {
|
||||
cf["dbNum"] = "0"
|
||||
}
|
||||
|
@ -78,15 +78,37 @@ func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, e
|
||||
}
|
||||
}
|
||||
section := defaultSection
|
||||
tmpBuf := bytes.NewBuffer(nil)
|
||||
for {
|
||||
line, _, err := buf.ReadLine()
|
||||
if err == io.EOF {
|
||||
tmpBuf.Reset()
|
||||
|
||||
shouldBreak := false
|
||||
for {
|
||||
tmp, isPrefix, err := buf.ReadLine()
|
||||
if err == io.EOF {
|
||||
shouldBreak = true
|
||||
break
|
||||
}
|
||||
|
||||
//It might be a good idea to throw a error on all unknonw errors?
|
||||
if _, ok := err.(*os.PathError); ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tmpBuf.Write(tmp)
|
||||
if isPrefix {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isPrefix {
|
||||
break
|
||||
}
|
||||
}
|
||||
if shouldBreak {
|
||||
break
|
||||
}
|
||||
//It might be a good idea to throw a error on all unknonw errors?
|
||||
if _, ok := err.(*os.PathError); ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
line := tmpBuf.Bytes()
|
||||
line = bytes.TrimSpace(line)
|
||||
if bytes.Equal(line, bEmpty) {
|
||||
continue
|
||||
|
@ -97,7 +97,7 @@ func parseYML(buf []byte) (cnf map[string]interface{}, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
data, err := goyaml2.Read(bytes.NewBuffer(buf))
|
||||
data, err := goyaml2.Read(bytes.NewReader(buf))
|
||||
if err != nil {
|
||||
log.Println("Goyaml2 ERR>", string(buf), err)
|
||||
return
|
||||
@ -290,12 +290,15 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) {
|
||||
|
||||
keys := strings.Split(key, ".")
|
||||
tmpData := c.data
|
||||
for _, k := range keys {
|
||||
for idx, k := range keys {
|
||||
if v, ok := tmpData[k]; ok {
|
||||
switch v.(type) {
|
||||
case map[string]interface{}:
|
||||
{
|
||||
tmpData = v.(map[string]interface{})
|
||||
if idx == len(keys) - 1 {
|
||||
return tmpData, nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
{
|
||||
|
@ -48,15 +48,15 @@ func TestAssignConfig_02(t *testing.T) {
|
||||
_BConfig := &Config{}
|
||||
bs, _ := json.Marshal(newBConfig())
|
||||
|
||||
jsonMap := map[string]interface{}{}
|
||||
jsonMap := M{}
|
||||
json.Unmarshal(bs, &jsonMap)
|
||||
|
||||
configMap := map[string]interface{}{}
|
||||
configMap := M{}
|
||||
for k, v := range jsonMap {
|
||||
if reflect.TypeOf(v).Kind() == reflect.Map {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
for k1, v1 := range v.(M) {
|
||||
if reflect.TypeOf(v1).Kind() == reflect.Map {
|
||||
for k2, v2 := range v1.(map[string]interface{}) {
|
||||
for k2, v2 := range v1.(M) {
|
||||
configMap[k2] = v2
|
||||
}
|
||||
} else {
|
||||
|
@ -38,6 +38,14 @@ import (
|
||||
"github.com/astaxie/beego/utils"
|
||||
)
|
||||
|
||||
//commonly used mime-types
|
||||
const (
|
||||
ApplicationJSON = "application/json"
|
||||
ApplicationXML = "application/xml"
|
||||
ApplicationYAML = "application/x-yaml"
|
||||
TextXML = "text/xml"
|
||||
)
|
||||
|
||||
// NewContext return the Context with Input and Output
|
||||
func NewContext() *Context {
|
||||
return &Context{
|
||||
@ -193,6 +201,7 @@ type Response struct {
|
||||
http.ResponseWriter
|
||||
Started bool
|
||||
Status int
|
||||
Elapsed time.Duration
|
||||
}
|
||||
|
||||
func (r *Response) reset(rw http.ResponseWriter) {
|
||||
@ -244,3 +253,11 @@ func (r *Response) CloseNotify() <-chan bool {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pusher http.Pusher
|
||||
func (r *Response) Pusher() (pusher http.Pusher) {
|
||||
if pusher, ok := r.ResponseWriter.(http.Pusher); ok {
|
||||
return pusher
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/astaxie/beego/session"
|
||||
)
|
||||
@ -49,6 +50,7 @@ type BeegoInput struct {
|
||||
pnames []string
|
||||
pvalues []string
|
||||
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
|
||||
dataLock sync.RWMutex
|
||||
RequestBody []byte
|
||||
RunMethod string
|
||||
RunController reflect.Type
|
||||
@ -204,6 +206,7 @@ func (input *BeegoInput) AcceptsXML() bool {
|
||||
func (input *BeegoInput) AcceptsJSON() bool {
|
||||
return acceptsJSONRegex.MatchString(input.Header("Accept"))
|
||||
}
|
||||
|
||||
// AcceptsYAML Checks if request accepts json response
|
||||
func (input *BeegoInput) AcceptsYAML() bool {
|
||||
return acceptsYAMLRegex.MatchString(input.Header("Accept"))
|
||||
@ -377,6 +380,8 @@ func (input *BeegoInput) CopyBody(MaxMemory int64) []byte {
|
||||
|
||||
// Data return the implicit data in the input
|
||||
func (input *BeegoInput) Data() map[interface{}]interface{} {
|
||||
input.dataLock.Lock()
|
||||
defer input.dataLock.Unlock()
|
||||
if input.data == nil {
|
||||
input.data = make(map[interface{}]interface{})
|
||||
}
|
||||
@ -385,6 +390,8 @@ func (input *BeegoInput) Data() map[interface{}]interface{} {
|
||||
|
||||
// GetData returns the stored data in this context.
|
||||
func (input *BeegoInput) GetData(key interface{}) interface{} {
|
||||
input.dataLock.Lock()
|
||||
defer input.dataLock.Unlock()
|
||||
if v, ok := input.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
@ -394,6 +401,8 @@ func (input *BeegoInput) GetData(key interface{}) interface{} {
|
||||
// SetData stores data with given key in this context.
|
||||
// This data are only available in this context.
|
||||
func (input *BeegoInput) SetData(key, val interface{}) {
|
||||
input.dataLock.Lock()
|
||||
defer input.dataLock.Unlock()
|
||||
if input.data == nil {
|
||||
input.data = make(map[interface{}]interface{})
|
||||
}
|
||||
|
@ -30,7 +30,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// BeegoOutput does work for sending response header.
|
||||
@ -203,10 +204,9 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool)
|
||||
return output.Body(content)
|
||||
}
|
||||
|
||||
|
||||
// YAML writes yaml to response body.
|
||||
func (output *BeegoOutput) YAML(data interface{}) error {
|
||||
output.Header("Content-Type", "application/application/x-yaml; charset=utf-8")
|
||||
output.Header("Content-Type", "application/x-yaml; charset=utf-8")
|
||||
var content []byte
|
||||
var err error
|
||||
content, err = yaml.Marshal(data)
|
||||
@ -260,6 +260,19 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error {
|
||||
return output.Body(content)
|
||||
}
|
||||
|
||||
// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header
|
||||
func (output *BeegoOutput) ServeFormatted(data interface{}, hasIndent bool, hasEncode ...bool) {
|
||||
accept := output.Context.Input.Header("Accept")
|
||||
switch accept {
|
||||
case ApplicationYAML:
|
||||
output.YAML(data)
|
||||
case ApplicationXML, TextXML:
|
||||
output.XML(data, hasIndent)
|
||||
default:
|
||||
output.JSON(data, hasIndent, len(hasEncode) > 0 && hasEncode[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Download forces response for download file.
|
||||
// it prepares the download response header automatically.
|
||||
func (output *BeegoOutput) Download(file string, filename ...string) {
|
||||
@ -275,7 +288,20 @@ func (output *BeegoOutput) Download(file string, filename ...string) {
|
||||
} else {
|
||||
fName = filepath.Base(file)
|
||||
}
|
||||
output.Header("Content-Disposition", "attachment; filename="+url.PathEscape(fName))
|
||||
//https://tools.ietf.org/html/rfc6266#section-4.3
|
||||
fn := url.PathEscape(fName)
|
||||
if fName == fn {
|
||||
fn = "filename=" + fn
|
||||
} else {
|
||||
/**
|
||||
The parameters "filename" and "filename*" differ only in that
|
||||
"filename*" uses the encoding defined in [RFC5987], allowing the use
|
||||
of characters not present in the ISO-8859-1 character set
|
||||
([ISO-8859-1]).
|
||||
*/
|
||||
fn = "filename=" + fName + "; filename*=utf-8''" + fn
|
||||
}
|
||||
output.Header("Content-Disposition", "attachment; "+fn)
|
||||
output.Header("Content-Description", "File Transfer")
|
||||
output.Header("Content-Type", "application/octet-stream")
|
||||
output.Header("Content-Transfer-Encoding", "binary")
|
||||
|
128
controller.go
128
controller.go
@ -17,6 +17,7 @@ package beego
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
@ -32,25 +33,44 @@ import (
|
||||
"github.com/astaxie/beego/session"
|
||||
)
|
||||
|
||||
//commonly used mime-types
|
||||
const (
|
||||
applicationJSON = "application/json"
|
||||
applicationXML = "application/xml"
|
||||
applicationYAML = "application/x-yaml"
|
||||
textXML = "text/xml"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAbort custom error when user stop request handler manually.
|
||||
ErrAbort = errors.New("User stop run")
|
||||
ErrAbort = errors.New("user stop run")
|
||||
// GlobalControllerRouter store comments with controller. pkgpath+controller:comments
|
||||
GlobalControllerRouter = make(map[string][]ControllerComments)
|
||||
)
|
||||
|
||||
// ControllerFilter store the filter for controller
|
||||
type ControllerFilter struct {
|
||||
Pattern string
|
||||
Pos int
|
||||
Filter FilterFunc
|
||||
ReturnOnOutput bool
|
||||
ResetParams bool
|
||||
}
|
||||
|
||||
// ControllerFilterComments store the comment for controller level filter
|
||||
type ControllerFilterComments struct {
|
||||
Pattern string
|
||||
Pos int
|
||||
Filter string // NOQA
|
||||
ReturnOnOutput bool
|
||||
ResetParams bool
|
||||
}
|
||||
|
||||
// ControllerImportComments store the import comment for controller needed
|
||||
type ControllerImportComments struct {
|
||||
ImportPath string
|
||||
ImportAlias string
|
||||
}
|
||||
|
||||
// ControllerComments store the comment for the controller method
|
||||
type ControllerComments struct {
|
||||
Method string
|
||||
Router string
|
||||
Filters []*ControllerFilter
|
||||
ImportComments []*ControllerImportComments
|
||||
FilterComments []*ControllerFilterComments
|
||||
AllowHTTPMethods []string
|
||||
Params []map[string]string
|
||||
MethodParams []*param.MethodParam
|
||||
@ -74,7 +94,6 @@ type Controller struct {
|
||||
controllerName string
|
||||
actionName string
|
||||
methodMapping map[string]func() //method:routertree
|
||||
gotofunc string
|
||||
AppController interface{}
|
||||
|
||||
// template data
|
||||
@ -106,6 +125,7 @@ type ControllerInterface interface {
|
||||
Head()
|
||||
Patch()
|
||||
Options()
|
||||
Trace()
|
||||
Finish()
|
||||
Render() error
|
||||
XSRFToken() string
|
||||
@ -137,37 +157,59 @@ func (c *Controller) Finish() {}
|
||||
|
||||
// Get adds a request function to handle GET request.
|
||||
func (c *Controller) Get() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
// Post adds a request function to handle POST request.
|
||||
func (c *Controller) Post() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
// Delete adds a request function to handle DELETE request.
|
||||
func (c *Controller) Delete() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
// Put adds a request function to handle PUT request.
|
||||
func (c *Controller) Put() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
// Head adds a request function to handle HEAD request.
|
||||
func (c *Controller) Head() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
// Patch adds a request function to handle PATCH request.
|
||||
func (c *Controller) Patch() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
// Options adds a request function to handle OPTIONS request.
|
||||
func (c *Controller) Options() {
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
// Trace adds a request function to handle Trace request.
|
||||
// this method SHOULD NOT be overridden.
|
||||
// https://tools.ietf.org/html/rfc7231#section-4.3.8
|
||||
// The TRACE method requests a remote, application-level loop-back of
|
||||
// the request message. The final recipient of the request SHOULD
|
||||
// reflect the message received, excluding some fields described below,
|
||||
// back to the client as the message body of a 200 (OK) response with a
|
||||
// Content-Type of "message/http" (Section 8.3.1 of [RFC7230]).
|
||||
func (c *Controller) Trace() {
|
||||
ts := func(h http.Header) (hs string) {
|
||||
for k, v := range h {
|
||||
hs += fmt.Sprintf("\r\n%s: %s", k, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
hs := fmt.Sprintf("\r\nTRACE %s %s%s\r\n", c.Ctx.Request.RequestURI, c.Ctx.Request.Proto, ts(c.Ctx.Request.Header))
|
||||
c.Ctx.Output.Header("Content-Type", "message/http")
|
||||
c.Ctx.Output.Header("Content-Length", fmt.Sprint(len(hs)))
|
||||
c.Ctx.Output.Header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
c.Ctx.WriteString(hs)
|
||||
}
|
||||
|
||||
// HandlerFunc call function with the name
|
||||
@ -273,18 +315,17 @@ func (c *Controller) viewPath() string {
|
||||
|
||||
// Redirect sends the redirection response to url with status code.
|
||||
func (c *Controller) Redirect(url string, code int) {
|
||||
logAccess(c.Ctx, nil, code)
|
||||
LogAccess(c.Ctx, nil, code)
|
||||
c.Ctx.Redirect(code, url)
|
||||
panic(ErrAbort)
|
||||
}
|
||||
|
||||
// Set the data depending on the accepted
|
||||
// SetData set the data depending on the accepted
|
||||
func (c *Controller) SetData(data interface{}) {
|
||||
accept := c.Ctx.Input.Header("Accept")
|
||||
switch accept {
|
||||
case applicationJSON:
|
||||
c.Data["json"] = data
|
||||
case applicationXML, textXML:
|
||||
case context.ApplicationYAML:
|
||||
c.Data["yaml"] = data
|
||||
case context.ApplicationXML, context.TextXML:
|
||||
c.Data["xml"] = data
|
||||
default:
|
||||
c.Data["json"] = data
|
||||
@ -333,54 +374,35 @@ func (c *Controller) URLFor(endpoint string, values ...interface{}) string {
|
||||
// ServeJSON sends a json response with encoding charset.
|
||||
func (c *Controller) ServeJSON(encoding ...bool) {
|
||||
var (
|
||||
hasIndent = true
|
||||
hasEncoding = false
|
||||
hasIndent = BConfig.RunMode != PROD
|
||||
hasEncoding = len(encoding) > 0 && encoding[0]
|
||||
)
|
||||
if BConfig.RunMode == PROD {
|
||||
hasIndent = false
|
||||
}
|
||||
if len(encoding) > 0 && encoding[0] {
|
||||
hasEncoding = true
|
||||
}
|
||||
|
||||
c.Ctx.Output.JSON(c.Data["json"], hasIndent, hasEncoding)
|
||||
}
|
||||
|
||||
// ServeJSONP sends a jsonp response.
|
||||
func (c *Controller) ServeJSONP() {
|
||||
hasIndent := true
|
||||
if BConfig.RunMode == PROD {
|
||||
hasIndent = false
|
||||
}
|
||||
hasIndent := BConfig.RunMode != PROD
|
||||
c.Ctx.Output.JSONP(c.Data["jsonp"], hasIndent)
|
||||
}
|
||||
|
||||
// ServeXML sends xml response.
|
||||
func (c *Controller) ServeXML() {
|
||||
hasIndent := true
|
||||
if BConfig.RunMode == PROD {
|
||||
hasIndent = false
|
||||
}
|
||||
hasIndent := BConfig.RunMode != PROD
|
||||
c.Ctx.Output.XML(c.Data["xml"], hasIndent)
|
||||
}
|
||||
|
||||
// ServeXML sends xml response.
|
||||
// ServeYAML sends yaml response.
|
||||
func (c *Controller) ServeYAML() {
|
||||
c.Ctx.Output.YAML(c.Data["yaml"])
|
||||
}
|
||||
|
||||
// ServeFormatted serve Xml OR Json, depending on the value of the Accept header
|
||||
func (c *Controller) ServeFormatted() {
|
||||
accept := c.Ctx.Input.Header("Accept")
|
||||
switch accept {
|
||||
case applicationJSON:
|
||||
c.ServeJSON()
|
||||
case applicationXML, textXML:
|
||||
c.ServeXML()
|
||||
case applicationYAML:
|
||||
c.ServeYAML()
|
||||
default:
|
||||
c.ServeJSON()
|
||||
}
|
||||
// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header
|
||||
func (c *Controller) ServeFormatted(encoding ...bool) {
|
||||
hasIndent := BConfig.RunMode != PROD
|
||||
hasEncoding := len(encoding) > 0 && encoding[0]
|
||||
c.Ctx.Output.ServeFormatted(c.Data, hasIndent, hasEncoding)
|
||||
}
|
||||
|
||||
// Input returns the input data map from POST or PUT request body and query string.
|
||||
|
4
error.go
4
error.go
@ -361,7 +361,7 @@ func gatewayTimeout(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) {
|
||||
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||
data := map[string]interface{}{
|
||||
data := M{
|
||||
"Title": http.StatusText(errCode),
|
||||
"BeegoVersion": VERSION,
|
||||
"Content": template.HTML(errContent),
|
||||
@ -435,7 +435,7 @@ func exception(errCode string, ctx *context.Context) {
|
||||
|
||||
func executeError(err *errorInfo, ctx *context.Context, code int) {
|
||||
//make sure to log the error in the access log
|
||||
logAccess(ctx, nil, code)
|
||||
LogAccess(ctx, nil, code)
|
||||
|
||||
if err.errorType == errorTypeHandler {
|
||||
ctx.ResponseWriter.WriteHeader(code)
|
||||
|
74
fs.go
Normal file
74
fs.go
Normal file
@ -0,0 +1,74 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type FileSystem struct {
|
||||
}
|
||||
|
||||
func (d FileSystem) Open(name string) (http.File, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
|
||||
// Walk walks the file tree rooted at root in filesystem, calling walkFn for each file or
|
||||
// directory in the tree, including root. All errors that arise visiting files
|
||||
// and directories are filtered by walkFn.
|
||||
func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error {
|
||||
|
||||
f, err := fs.Open(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
err = walkFn(root, nil, err)
|
||||
} else {
|
||||
err = walk(fs, root, info, walkFn)
|
||||
}
|
||||
if err == filepath.SkipDir {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// walk recursively descends path, calling walkFn.
|
||||
func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
||||
var err error
|
||||
if !info.IsDir() {
|
||||
return walkFn(path, info, nil)
|
||||
}
|
||||
|
||||
dir, err := fs.Open(path)
|
||||
if err != nil {
|
||||
if err1 := walkFn(path, info, err); err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer dir.Close()
|
||||
dirs, err := dir.Readdir(-1)
|
||||
err1 := walkFn(path, info, err)
|
||||
// If err != nil, walk can't walk into this directory.
|
||||
// err1 != nil means walkFn want walk to skip this directory or stop walking.
|
||||
// Therefore, if one of err and err1 isn't nil, walk will return.
|
||||
if err != nil || err1 != nil {
|
||||
// The caller's behavior is controlled by the return value, which is decided
|
||||
// by walkFn. walkFn may ignore err and return nil.
|
||||
// If walkFn returns SkipDir, it will be handled by the caller.
|
||||
// So walk should return whatever walkFn returns.
|
||||
return err1
|
||||
}
|
||||
|
||||
for _, fileInfo := range dirs {
|
||||
filename := filepath.Join(path, fileInfo.Name())
|
||||
if err = walk(fs, filename, fileInfo, walkFn); err != nil {
|
||||
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
39
go.mod
Normal file
39
go.mod
Normal file
@ -0,0 +1,39 @@
|
||||
module github.com/astaxie/beego
|
||||
|
||||
require (
|
||||
github.com/Knetic/govaluate v3.0.0+incompatible // indirect
|
||||
github.com/OwnLocal/goes v1.0.0
|
||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd
|
||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542
|
||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
|
||||
github.com/casbin/casbin v1.7.0
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58
|
||||
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb
|
||||
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c // indirect
|
||||
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a // indirect
|
||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0
|
||||
github.com/go-redis/redis v6.14.2+incompatible
|
||||
github.com/go-sql-driver/mysql v1.4.1
|
||||
github.com/gogo/protobuf v1.1.1
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
github.com/lib/pq v1.0.0
|
||||
github.com/mattn/go-sqlite3 v1.10.0
|
||||
github.com/pelletier/go-toml v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.8.0 // indirect
|
||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
|
||||
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373
|
||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect
|
||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec
|
||||
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c // indirect
|
||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b // indirect
|
||||
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a // indirect
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
)
|
||||
|
||||
replace golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 => github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85
|
||||
|
||||
replace gopkg.in/yaml.v2 v2.2.1 => github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d
|
68
go.sum
Normal file
68
go.sum
Normal file
@ -0,0 +1,68 @@
|
||||
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
|
||||
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
|
||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd h1:jZtX5jh5IOMu0fpOTC3ayh6QGSPJ/KWOv1lgPvbRw1M=
|
||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542 h1:nYXb+3jF6Oq/j8R/y90XrKpreCxIalBWfeyeKymgOPk=
|
||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
|
||||
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff h1:/kO0p2RTGLB8R5gub7ps0GmYpB2O8LXEoPq8tzFDCUI=
|
||||
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
|
||||
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb h1:w3RapLhkA5+km9Z8vUkC6VCaskduJXvXwJg5neKnfDU=
|
||||
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
|
||||
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c h1:K4FIibkr4//ziZKOKmt4RL0YImuTjLLBtwElf+F2lSQ=
|
||||
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
|
||||
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a h1:Y5XsLCEhtEI8qbD9RP3Qlv5FXdTDHxZM9UPUnMRgBp8=
|
||||
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
|
||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
|
||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
|
||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
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/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/golang/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:B7ZbAFz7NOmvpUE5RGtu3u0WIizy5GdvbNpEf4RPnWs=
|
||||
github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:uZvAcrsnNaCxlh1HorK5dUQHGmEKPh2H/Rl1kehswPo=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
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/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/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
|
||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373 h1:p6IxqQMjab30l4lb9mmkIkkcE1yv6o0SKbPhW5pxqHI=
|
||||
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
|
||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs=
|
||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec h1:q6XVwXmKvCRHRqesF3cSv6lNqqHi0QWOvgDlSohg8UA=
|
||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
|
||||
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c h1:3eGShk3EQf5gJCYW+WzA0TEJQd37HLOmlYF7N0YJwv0=
|
||||
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b h1:0Ve0/CCjiAiyKddUMUn3RwIGlq2iTW4GuVzyoKBYO/8=
|
||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
||||
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
|
||||
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
@ -1,39 +0,0 @@
|
||||
package grace
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type graceConn struct {
|
||||
net.Conn
|
||||
server *Server
|
||||
m sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (c *graceConn) Close() (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
switch x := r.(type) {
|
||||
case string:
|
||||
err = errors.New(x)
|
||||
case error:
|
||||
err = x
|
||||
default:
|
||||
err = errors.New("Unknown panic")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
c.m.Lock()
|
||||
if c.closed {
|
||||
c.m.Unlock()
|
||||
return
|
||||
}
|
||||
c.server.wg.Done()
|
||||
c.closed = true
|
||||
c.m.Unlock()
|
||||
return c.Conn.Close()
|
||||
}
|
@ -78,7 +78,7 @@ var (
|
||||
DefaultReadTimeOut time.Duration
|
||||
// DefaultWriteTimeOut is the HTTP Write timeout
|
||||
DefaultWriteTimeOut time.Duration
|
||||
// DefaultMaxHeaderBytes is the Max HTTP Herder size, default is 0, no limit
|
||||
// DefaultMaxHeaderBytes is the Max HTTP Header size, default is 0, no limit
|
||||
DefaultMaxHeaderBytes int
|
||||
// DefaultTimeout is the shutdown server's timeout. default is 60s
|
||||
DefaultTimeout = 60 * time.Second
|
||||
@ -122,7 +122,6 @@ func NewServer(addr string, handler http.Handler) (srv *Server) {
|
||||
}
|
||||
|
||||
srv = &Server{
|
||||
wg: sync.WaitGroup{},
|
||||
sigChan: make(chan os.Signal),
|
||||
isChild: isChild,
|
||||
SignalHooks: map[int]map[os.Signal][]func(){
|
||||
@ -137,20 +136,21 @@ func NewServer(addr string, handler http.Handler) (srv *Server) {
|
||||
syscall.SIGTERM: {},
|
||||
},
|
||||
},
|
||||
state: StateInit,
|
||||
Network: "tcp",
|
||||
state: StateInit,
|
||||
Network: "tcp",
|
||||
terminalChan: make(chan error), //no cache channel
|
||||
}
|
||||
srv.Server = &http.Server{
|
||||
Addr: addr,
|
||||
ReadTimeout: DefaultReadTimeOut,
|
||||
WriteTimeout: DefaultWriteTimeOut,
|
||||
MaxHeaderBytes: DefaultMaxHeaderBytes,
|
||||
Handler: handler,
|
||||
}
|
||||
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
|
||||
return srv
|
||||
}
|
||||
|
||||
// ListenAndServe refer http.ListenAndServe
|
||||
|
@ -1,62 +0,0 @@
|
||||
package grace
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type graceListener struct {
|
||||
net.Listener
|
||||
stop chan error
|
||||
stopped bool
|
||||
server *Server
|
||||
}
|
||||
|
||||
func newGraceListener(l net.Listener, srv *Server) (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 (gl *graceListener) Close() error {
|
||||
if gl.stopped {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
gl.stop <- nil
|
||||
return <-gl.stop
|
||||
}
|
||||
|
||||
func (gl *graceListener) File() *os.File {
|
||||
// returns a dup(2) - FD_CLOEXEC flag *not* set
|
||||
tl := gl.Listener.(*net.TCPListener)
|
||||
fl, _ := tl.File()
|
||||
return fl
|
||||
}
|
110
grace/server.go
110
grace/server.go
@ -1,6 +1,7 @@
|
||||
package grace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
@ -12,7 +13,6 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
@ -20,14 +20,13 @@ import (
|
||||
// Server embedded http.Server
|
||||
type Server 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
|
||||
ln net.Listener
|
||||
SignalHooks map[int]map[os.Signal][]func()
|
||||
sigChan chan os.Signal
|
||||
isChild bool
|
||||
state uint8
|
||||
Network string
|
||||
terminalChan chan error
|
||||
}
|
||||
|
||||
// Serve accepts incoming connections on the Listener l,
|
||||
@ -35,11 +34,19 @@ type Server struct {
|
||||
// The service goroutines read requests and then call srv.Handler to reply to them.
|
||||
func (srv *Server) Serve() (err error) {
|
||||
srv.state = StateRunning
|
||||
err = srv.Server.Serve(srv.GraceListener)
|
||||
log.Println(syscall.Getpid(), "Waiting for connections to finish...")
|
||||
srv.wg.Wait()
|
||||
srv.state = StateTerminate
|
||||
return
|
||||
defer func() { srv.state = StateTerminate }()
|
||||
|
||||
// When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS
|
||||
// immediately return ErrServerClosed. Make sure the program doesn't exit
|
||||
// and waits instead for Shutdown to return.
|
||||
if err = srv.Server.Serve(srv.ln); err != nil && err != http.ErrServerClosed {
|
||||
log.Println(syscall.Getpid(), "Server.Serve() error:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println(syscall.Getpid(), srv.ln.Addr(), "Listener closed.")
|
||||
// wait for Shutdown to return
|
||||
return <-srv.terminalChan
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the TCP network address srv.Addr and then calls Serve
|
||||
@ -53,14 +60,12 @@ func (srv *Server) ListenAndServe() (err error) {
|
||||
|
||||
go srv.handleSignals()
|
||||
|
||||
l, err := srv.getListener(addr)
|
||||
srv.ln, 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 {
|
||||
@ -107,14 +112,12 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
|
||||
|
||||
go srv.handleSignals()
|
||||
|
||||
l, err := srv.getListener(addr)
|
||||
ln, err := srv.getListener(addr)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
srv.tlsInnerListener = newGraceListener(l, srv)
|
||||
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
|
||||
srv.ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig)
|
||||
|
||||
if srv.isChild {
|
||||
process, err := os.FindProcess(os.Getppid())
|
||||
@ -127,6 +130,7 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Println(os.Getpid(), srv.Addr)
|
||||
return srv.Serve()
|
||||
}
|
||||
@ -163,14 +167,12 @@ func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string)
|
||||
log.Println("Mutual HTTPS")
|
||||
go srv.handleSignals()
|
||||
|
||||
l, err := srv.getListener(addr)
|
||||
ln, err := srv.getListener(addr)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
srv.tlsInnerListener = newGraceListener(l, srv)
|
||||
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
|
||||
srv.ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig)
|
||||
|
||||
if srv.isChild {
|
||||
process, err := os.FindProcess(os.Getppid())
|
||||
@ -183,6 +185,7 @@ func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Println(os.Getpid(), srv.Addr)
|
||||
return srv.Serve()
|
||||
}
|
||||
@ -213,6 +216,20 @@ func (srv *Server) getListener(laddr string) (l net.Listener, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
// handleSignals listens for os Signals and calls any hooked in function that the
|
||||
// user had registered with the signal.
|
||||
func (srv *Server) handleSignals() {
|
||||
@ -265,37 +282,14 @@ func (srv *Server) shutdown() {
|
||||
}
|
||||
|
||||
srv.state = StateShuttingDown
|
||||
log.Println(syscall.Getpid(), "Waiting for connections to finish...")
|
||||
ctx := context.Background()
|
||||
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 *Server) serverTimeout(d time.Duration) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Println("WaitGroup at 0", r)
|
||||
}
|
||||
}()
|
||||
if srv.state != StateShuttingDown {
|
||||
return
|
||||
}
|
||||
time.Sleep(d)
|
||||
log.Println("[STOP - Hammer Time] Forcefully shutting down parent")
|
||||
for {
|
||||
if srv.state == StateTerminate {
|
||||
break
|
||||
}
|
||||
srv.wg.Done()
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cancel()
|
||||
}
|
||||
srv.terminalChan <- srv.Server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (srv *Server) fork() (err error) {
|
||||
@ -309,12 +303,8 @@ func (srv *Server) fork() (err error) {
|
||||
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()
|
||||
}
|
||||
f, _ := srvPtr.ln.(*net.TCPListener).File()
|
||||
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = f
|
||||
orderArgs[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.Server.Addr
|
||||
}
|
||||
|
||||
|
2
hooks.go
2
hooks.go
@ -11,7 +11,7 @@ import (
|
||||
"github.com/astaxie/beego/session"
|
||||
)
|
||||
|
||||
//
|
||||
// register MIME type with content type
|
||||
func registerMime() error {
|
||||
for k, v := range mimemaps {
|
||||
mime.AddExtensionType(k, v)
|
||||
|
@ -16,6 +16,8 @@ package httplib
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -161,7 +163,16 @@ func TestWithSetting(t *testing.T) {
|
||||
var setting BeegoHTTPSettings
|
||||
setting.EnableCookie = true
|
||||
setting.UserAgent = v
|
||||
setting.Transport = nil
|
||||
setting.Transport = &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 50,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
setting.ReadWriteTimeout = 5 * time.Second
|
||||
SetDefaultSetting(setting)
|
||||
|
||||
@ -195,10 +206,16 @@ func TestToJson(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(ip.Origin)
|
||||
|
||||
if n := strings.Count(ip.Origin, "."); n != 3 {
|
||||
ips := strings.Split(ip.Origin, ",")
|
||||
if len(ips) == 0 {
|
||||
t.Fatal("response is not valid ip")
|
||||
}
|
||||
for i := range ips {
|
||||
if net.ParseIP(strings.TrimSpace(ips[i])).To4() == nil {
|
||||
t.Fatal("response is not valid ip")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestToFile(t *testing.T) {
|
||||
|
16
log.go
16
log.go
@ -21,6 +21,7 @@ import (
|
||||
)
|
||||
|
||||
// Log levels to control the logging output.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
const (
|
||||
LevelEmergency = iota
|
||||
LevelAlert
|
||||
@ -33,75 +34,90 @@ const (
|
||||
)
|
||||
|
||||
// BeeLogger references the used application logger.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
var BeeLogger = logs.GetBeeLogger()
|
||||
|
||||
// SetLevel sets the global log level used by the simple logger.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func SetLevel(l int) {
|
||||
logs.SetLevel(l)
|
||||
}
|
||||
|
||||
// SetLogFuncCall set the CallDepth, default is 3
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func SetLogFuncCall(b bool) {
|
||||
logs.SetLogFuncCall(b)
|
||||
}
|
||||
|
||||
// SetLogger sets a new logger.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func SetLogger(adaptername string, config string) error {
|
||||
return logs.SetLogger(adaptername, config)
|
||||
}
|
||||
|
||||
// Emergency logs a message at emergency level.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func Emergency(v ...interface{}) {
|
||||
logs.Emergency(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Alert logs a message at alert level.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func Alert(v ...interface{}) {
|
||||
logs.Alert(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Critical logs a message at critical level.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func Critical(v ...interface{}) {
|
||||
logs.Critical(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Error logs a message at error level.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func Error(v ...interface{}) {
|
||||
logs.Error(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Warning logs a message at warning level.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func Warning(v ...interface{}) {
|
||||
logs.Warning(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Warn compatibility alias for Warning()
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func Warn(v ...interface{}) {
|
||||
logs.Warn(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Notice logs a message at notice level.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func Notice(v ...interface{}) {
|
||||
logs.Notice(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Informational logs a message at info level.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func Informational(v ...interface{}) {
|
||||
logs.Informational(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Info compatibility alias for Warning()
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func Info(v ...interface{}) {
|
||||
logs.Info(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Debug logs a message at debug level.
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func Debug(v ...interface{}) {
|
||||
logs.Debug(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Trace logs a message at trace level.
|
||||
// compatibility alias for Warning()
|
||||
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||
func Trace(v ...interface{}) {
|
||||
logs.Trace(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package logs
|
||||
|
||||
import "io"
|
||||
|
||||
type ansiColorWriter struct {
|
||||
w io.Writer
|
||||
mode outputMode
|
||||
}
|
||||
|
||||
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
|
||||
return cw.w.Write(p)
|
||||
}
|
@ -1,428 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
csiState int
|
||||
parseResult int
|
||||
)
|
||||
|
||||
const (
|
||||
outsideCsiCode csiState = iota
|
||||
firstCsiCode
|
||||
secondCsiCode
|
||||
)
|
||||
|
||||
const (
|
||||
noConsole parseResult = iota
|
||||
changedColor
|
||||
unknown
|
||||
)
|
||||
|
||||
type ansiColorWriter struct {
|
||||
w io.Writer
|
||||
mode outputMode
|
||||
state csiState
|
||||
paramStartBuf bytes.Buffer
|
||||
paramBuf bytes.Buffer
|
||||
}
|
||||
|
||||
const (
|
||||
firstCsiChar byte = '\x1b'
|
||||
secondeCsiChar byte = '['
|
||||
separatorChar byte = ';'
|
||||
sgrCode byte = 'm'
|
||||
)
|
||||
|
||||
const (
|
||||
foregroundBlue = uint16(0x0001)
|
||||
foregroundGreen = uint16(0x0002)
|
||||
foregroundRed = uint16(0x0004)
|
||||
foregroundIntensity = uint16(0x0008)
|
||||
backgroundBlue = uint16(0x0010)
|
||||
backgroundGreen = uint16(0x0020)
|
||||
backgroundRed = uint16(0x0040)
|
||||
backgroundIntensity = uint16(0x0080)
|
||||
underscore = uint16(0x8000)
|
||||
|
||||
foregroundMask = foregroundBlue | foregroundGreen | foregroundRed | foregroundIntensity
|
||||
backgroundMask = backgroundBlue | backgroundGreen | backgroundRed | backgroundIntensity
|
||||
)
|
||||
|
||||
const (
|
||||
ansiReset = "0"
|
||||
ansiIntensityOn = "1"
|
||||
ansiIntensityOff = "21"
|
||||
ansiUnderlineOn = "4"
|
||||
ansiUnderlineOff = "24"
|
||||
ansiBlinkOn = "5"
|
||||
ansiBlinkOff = "25"
|
||||
|
||||
ansiForegroundBlack = "30"
|
||||
ansiForegroundRed = "31"
|
||||
ansiForegroundGreen = "32"
|
||||
ansiForegroundYellow = "33"
|
||||
ansiForegroundBlue = "34"
|
||||
ansiForegroundMagenta = "35"
|
||||
ansiForegroundCyan = "36"
|
||||
ansiForegroundWhite = "37"
|
||||
ansiForegroundDefault = "39"
|
||||
|
||||
ansiBackgroundBlack = "40"
|
||||
ansiBackgroundRed = "41"
|
||||
ansiBackgroundGreen = "42"
|
||||
ansiBackgroundYellow = "43"
|
||||
ansiBackgroundBlue = "44"
|
||||
ansiBackgroundMagenta = "45"
|
||||
ansiBackgroundCyan = "46"
|
||||
ansiBackgroundWhite = "47"
|
||||
ansiBackgroundDefault = "49"
|
||||
|
||||
ansiLightForegroundGray = "90"
|
||||
ansiLightForegroundRed = "91"
|
||||
ansiLightForegroundGreen = "92"
|
||||
ansiLightForegroundYellow = "93"
|
||||
ansiLightForegroundBlue = "94"
|
||||
ansiLightForegroundMagenta = "95"
|
||||
ansiLightForegroundCyan = "96"
|
||||
ansiLightForegroundWhite = "97"
|
||||
|
||||
ansiLightBackgroundGray = "100"
|
||||
ansiLightBackgroundRed = "101"
|
||||
ansiLightBackgroundGreen = "102"
|
||||
ansiLightBackgroundYellow = "103"
|
||||
ansiLightBackgroundBlue = "104"
|
||||
ansiLightBackgroundMagenta = "105"
|
||||
ansiLightBackgroundCyan = "106"
|
||||
ansiLightBackgroundWhite = "107"
|
||||
)
|
||||
|
||||
type drawType int
|
||||
|
||||
const (
|
||||
foreground drawType = iota
|
||||
background
|
||||
)
|
||||
|
||||
type winColor struct {
|
||||
code uint16
|
||||
drawType drawType
|
||||
}
|
||||
|
||||
var colorMap = map[string]winColor{
|
||||
ansiForegroundBlack: {0, foreground},
|
||||
ansiForegroundRed: {foregroundRed, foreground},
|
||||
ansiForegroundGreen: {foregroundGreen, foreground},
|
||||
ansiForegroundYellow: {foregroundRed | foregroundGreen, foreground},
|
||||
ansiForegroundBlue: {foregroundBlue, foreground},
|
||||
ansiForegroundMagenta: {foregroundRed | foregroundBlue, foreground},
|
||||
ansiForegroundCyan: {foregroundGreen | foregroundBlue, foreground},
|
||||
ansiForegroundWhite: {foregroundRed | foregroundGreen | foregroundBlue, foreground},
|
||||
ansiForegroundDefault: {foregroundRed | foregroundGreen | foregroundBlue, foreground},
|
||||
|
||||
ansiBackgroundBlack: {0, background},
|
||||
ansiBackgroundRed: {backgroundRed, background},
|
||||
ansiBackgroundGreen: {backgroundGreen, background},
|
||||
ansiBackgroundYellow: {backgroundRed | backgroundGreen, background},
|
||||
ansiBackgroundBlue: {backgroundBlue, background},
|
||||
ansiBackgroundMagenta: {backgroundRed | backgroundBlue, background},
|
||||
ansiBackgroundCyan: {backgroundGreen | backgroundBlue, background},
|
||||
ansiBackgroundWhite: {backgroundRed | backgroundGreen | backgroundBlue, background},
|
||||
ansiBackgroundDefault: {0, background},
|
||||
|
||||
ansiLightForegroundGray: {foregroundIntensity, foreground},
|
||||
ansiLightForegroundRed: {foregroundIntensity | foregroundRed, foreground},
|
||||
ansiLightForegroundGreen: {foregroundIntensity | foregroundGreen, foreground},
|
||||
ansiLightForegroundYellow: {foregroundIntensity | foregroundRed | foregroundGreen, foreground},
|
||||
ansiLightForegroundBlue: {foregroundIntensity | foregroundBlue, foreground},
|
||||
ansiLightForegroundMagenta: {foregroundIntensity | foregroundRed | foregroundBlue, foreground},
|
||||
ansiLightForegroundCyan: {foregroundIntensity | foregroundGreen | foregroundBlue, foreground},
|
||||
ansiLightForegroundWhite: {foregroundIntensity | foregroundRed | foregroundGreen | foregroundBlue, foreground},
|
||||
|
||||
ansiLightBackgroundGray: {backgroundIntensity, background},
|
||||
ansiLightBackgroundRed: {backgroundIntensity | backgroundRed, background},
|
||||
ansiLightBackgroundGreen: {backgroundIntensity | backgroundGreen, background},
|
||||
ansiLightBackgroundYellow: {backgroundIntensity | backgroundRed | backgroundGreen, background},
|
||||
ansiLightBackgroundBlue: {backgroundIntensity | backgroundBlue, background},
|
||||
ansiLightBackgroundMagenta: {backgroundIntensity | backgroundRed | backgroundBlue, background},
|
||||
ansiLightBackgroundCyan: {backgroundIntensity | backgroundGreen | backgroundBlue, background},
|
||||
ansiLightBackgroundWhite: {backgroundIntensity | backgroundRed | backgroundGreen | backgroundBlue, background},
|
||||
}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
|
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
defaultAttr *textAttributes
|
||||
)
|
||||
|
||||
func init() {
|
||||
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
||||
if screenInfo != nil {
|
||||
colorMap[ansiForegroundDefault] = winColor{
|
||||
screenInfo.WAttributes & (foregroundRed | foregroundGreen | foregroundBlue),
|
||||
foreground,
|
||||
}
|
||||
colorMap[ansiBackgroundDefault] = winColor{
|
||||
screenInfo.WAttributes & (backgroundRed | backgroundGreen | backgroundBlue),
|
||||
background,
|
||||
}
|
||||
defaultAttr = convertTextAttr(screenInfo.WAttributes)
|
||||
}
|
||||
}
|
||||
|
||||
type coord struct {
|
||||
X, Y int16
|
||||
}
|
||||
|
||||
type smallRect struct {
|
||||
Left, Top, Right, Bottom int16
|
||||
}
|
||||
|
||||
type consoleScreenBufferInfo struct {
|
||||
DwSize coord
|
||||
DwCursorPosition coord
|
||||
WAttributes uint16
|
||||
SrWindow smallRect
|
||||
DwMaximumWindowSize coord
|
||||
}
|
||||
|
||||
func getConsoleScreenBufferInfo(hConsoleOutput uintptr) *consoleScreenBufferInfo {
|
||||
var csbi consoleScreenBufferInfo
|
||||
ret, _, _ := procGetConsoleScreenBufferInfo.Call(
|
||||
hConsoleOutput,
|
||||
uintptr(unsafe.Pointer(&csbi)))
|
||||
if ret == 0 {
|
||||
return nil
|
||||
}
|
||||
return &csbi
|
||||
}
|
||||
|
||||
func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool {
|
||||
ret, _, _ := procSetConsoleTextAttribute.Call(
|
||||
hConsoleOutput,
|
||||
uintptr(wAttributes))
|
||||
return ret != 0
|
||||
}
|
||||
|
||||
type textAttributes struct {
|
||||
foregroundColor uint16
|
||||
backgroundColor uint16
|
||||
foregroundIntensity uint16
|
||||
backgroundIntensity uint16
|
||||
underscore uint16
|
||||
otherAttributes uint16
|
||||
}
|
||||
|
||||
func convertTextAttr(winAttr uint16) *textAttributes {
|
||||
fgColor := winAttr & (foregroundRed | foregroundGreen | foregroundBlue)
|
||||
bgColor := winAttr & (backgroundRed | backgroundGreen | backgroundBlue)
|
||||
fgIntensity := winAttr & foregroundIntensity
|
||||
bgIntensity := winAttr & backgroundIntensity
|
||||
underline := winAttr & underscore
|
||||
otherAttributes := winAttr &^ (foregroundMask | backgroundMask | underscore)
|
||||
return &textAttributes{fgColor, bgColor, fgIntensity, bgIntensity, underline, otherAttributes}
|
||||
}
|
||||
|
||||
func convertWinAttr(textAttr *textAttributes) uint16 {
|
||||
var winAttr uint16
|
||||
winAttr |= textAttr.foregroundColor
|
||||
winAttr |= textAttr.backgroundColor
|
||||
winAttr |= textAttr.foregroundIntensity
|
||||
winAttr |= textAttr.backgroundIntensity
|
||||
winAttr |= textAttr.underscore
|
||||
winAttr |= textAttr.otherAttributes
|
||||
return winAttr
|
||||
}
|
||||
|
||||
func changeColor(param []byte) parseResult {
|
||||
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
||||
if screenInfo == nil {
|
||||
return noConsole
|
||||
}
|
||||
|
||||
winAttr := convertTextAttr(screenInfo.WAttributes)
|
||||
strParam := string(param)
|
||||
if len(strParam) <= 0 {
|
||||
strParam = "0"
|
||||
}
|
||||
csiParam := strings.Split(strParam, string(separatorChar))
|
||||
for _, p := range csiParam {
|
||||
c, ok := colorMap[p]
|
||||
switch {
|
||||
case !ok:
|
||||
switch p {
|
||||
case ansiReset:
|
||||
winAttr.foregroundColor = defaultAttr.foregroundColor
|
||||
winAttr.backgroundColor = defaultAttr.backgroundColor
|
||||
winAttr.foregroundIntensity = defaultAttr.foregroundIntensity
|
||||
winAttr.backgroundIntensity = defaultAttr.backgroundIntensity
|
||||
winAttr.underscore = 0
|
||||
winAttr.otherAttributes = 0
|
||||
case ansiIntensityOn:
|
||||
winAttr.foregroundIntensity = foregroundIntensity
|
||||
case ansiIntensityOff:
|
||||
winAttr.foregroundIntensity = 0
|
||||
case ansiUnderlineOn:
|
||||
winAttr.underscore = underscore
|
||||
case ansiUnderlineOff:
|
||||
winAttr.underscore = 0
|
||||
case ansiBlinkOn:
|
||||
winAttr.backgroundIntensity = backgroundIntensity
|
||||
case ansiBlinkOff:
|
||||
winAttr.backgroundIntensity = 0
|
||||
default:
|
||||
// unknown code
|
||||
}
|
||||
case c.drawType == foreground:
|
||||
winAttr.foregroundColor = c.code
|
||||
case c.drawType == background:
|
||||
winAttr.backgroundColor = c.code
|
||||
}
|
||||
}
|
||||
winTextAttribute := convertWinAttr(winAttr)
|
||||
setConsoleTextAttribute(uintptr(syscall.Stdout), winTextAttribute)
|
||||
|
||||
return changedColor
|
||||
}
|
||||
|
||||
func parseEscapeSequence(command byte, param []byte) parseResult {
|
||||
if defaultAttr == nil {
|
||||
return noConsole
|
||||
}
|
||||
|
||||
switch command {
|
||||
case sgrCode:
|
||||
return changeColor(param)
|
||||
default:
|
||||
return unknown
|
||||
}
|
||||
}
|
||||
|
||||
func (cw *ansiColorWriter) flushBuffer() (int, error) {
|
||||
return cw.flushTo(cw.w)
|
||||
}
|
||||
|
||||
func (cw *ansiColorWriter) resetBuffer() (int, error) {
|
||||
return cw.flushTo(nil)
|
||||
}
|
||||
|
||||
func (cw *ansiColorWriter) flushTo(w io.Writer) (int, error) {
|
||||
var n1, n2 int
|
||||
var err error
|
||||
|
||||
startBytes := cw.paramStartBuf.Bytes()
|
||||
cw.paramStartBuf.Reset()
|
||||
if w != nil {
|
||||
n1, err = cw.w.Write(startBytes)
|
||||
if err != nil {
|
||||
return n1, err
|
||||
}
|
||||
} else {
|
||||
n1 = len(startBytes)
|
||||
}
|
||||
paramBytes := cw.paramBuf.Bytes()
|
||||
cw.paramBuf.Reset()
|
||||
if w != nil {
|
||||
n2, err = cw.w.Write(paramBytes)
|
||||
if err != nil {
|
||||
return n1 + n2, err
|
||||
}
|
||||
} else {
|
||||
n2 = len(paramBytes)
|
||||
}
|
||||
return n1 + n2, nil
|
||||
}
|
||||
|
||||
func isParameterChar(b byte) bool {
|
||||
return ('0' <= b && b <= '9') || b == separatorChar
|
||||
}
|
||||
|
||||
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
|
||||
var r, nw, first, last int
|
||||
if cw.mode != DiscardNonColorEscSeq {
|
||||
cw.state = outsideCsiCode
|
||||
cw.resetBuffer()
|
||||
}
|
||||
|
||||
var err error
|
||||
for i, ch := range p {
|
||||
switch cw.state {
|
||||
case outsideCsiCode:
|
||||
if ch == firstCsiChar {
|
||||
cw.paramStartBuf.WriteByte(ch)
|
||||
cw.state = firstCsiCode
|
||||
}
|
||||
case firstCsiCode:
|
||||
switch ch {
|
||||
case firstCsiChar:
|
||||
cw.paramStartBuf.WriteByte(ch)
|
||||
break
|
||||
case secondeCsiChar:
|
||||
cw.paramStartBuf.WriteByte(ch)
|
||||
cw.state = secondCsiCode
|
||||
last = i - 1
|
||||
default:
|
||||
cw.resetBuffer()
|
||||
cw.state = outsideCsiCode
|
||||
}
|
||||
case secondCsiCode:
|
||||
if isParameterChar(ch) {
|
||||
cw.paramBuf.WriteByte(ch)
|
||||
} else {
|
||||
nw, err = cw.w.Write(p[first:last])
|
||||
r += nw
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
first = i + 1
|
||||
result := parseEscapeSequence(ch, cw.paramBuf.Bytes())
|
||||
if result == noConsole || (cw.mode == OutputNonColorEscSeq && result == unknown) {
|
||||
cw.paramBuf.WriteByte(ch)
|
||||
nw, err := cw.flushBuffer()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r += nw
|
||||
} else {
|
||||
n, _ := cw.resetBuffer()
|
||||
// Add one more to the size of the buffer for the last ch
|
||||
r += n + 1
|
||||
}
|
||||
|
||||
cw.state = outsideCsiCode
|
||||
}
|
||||
default:
|
||||
cw.state = outsideCsiCode
|
||||
}
|
||||
}
|
||||
|
||||
if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode {
|
||||
nw, err = cw.w.Write(p[first:])
|
||||
r += nw
|
||||
}
|
||||
|
||||
return r, err
|
||||
}
|
@ -1,294 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var GetConsoleScreenBufferInfo = getConsoleScreenBufferInfo
|
||||
|
||||
func ChangeColor(color uint16) {
|
||||
setConsoleTextAttribute(uintptr(syscall.Stdout), color)
|
||||
}
|
||||
|
||||
func ResetColor() {
|
||||
ChangeColor(uint16(0x0007))
|
||||
}
|
||||
|
||||
func TestWritePlanText(t *testing.T) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w := NewAnsiColorWriter(inner)
|
||||
expected := "plain text"
|
||||
fmt.Fprintf(w, expected)
|
||||
actual := inner.String()
|
||||
if actual != expected {
|
||||
t.Errorf("Get %q, want %q", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteParseText(t *testing.T) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w := NewAnsiColorWriter(inner)
|
||||
|
||||
inputTail := "\x1b[0mtail text"
|
||||
expectedTail := "tail text"
|
||||
fmt.Fprintf(w, inputTail)
|
||||
actualTail := inner.String()
|
||||
inner.Reset()
|
||||
if actualTail != expectedTail {
|
||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
||||
}
|
||||
|
||||
inputHead := "head text\x1b[0m"
|
||||
expectedHead := "head text"
|
||||
fmt.Fprintf(w, inputHead)
|
||||
actualHead := inner.String()
|
||||
inner.Reset()
|
||||
if actualHead != expectedHead {
|
||||
t.Errorf("Get %q, want %q", actualHead, expectedHead)
|
||||
}
|
||||
|
||||
inputBothEnds := "both ends \x1b[0m text"
|
||||
expectedBothEnds := "both ends text"
|
||||
fmt.Fprintf(w, inputBothEnds)
|
||||
actualBothEnds := inner.String()
|
||||
inner.Reset()
|
||||
if actualBothEnds != expectedBothEnds {
|
||||
t.Errorf("Get %q, want %q", actualBothEnds, expectedBothEnds)
|
||||
}
|
||||
|
||||
inputManyEsc := "\x1b\x1b\x1b\x1b[0m many esc"
|
||||
expectedManyEsc := "\x1b\x1b\x1b many esc"
|
||||
fmt.Fprintf(w, inputManyEsc)
|
||||
actualManyEsc := inner.String()
|
||||
inner.Reset()
|
||||
if actualManyEsc != expectedManyEsc {
|
||||
t.Errorf("Get %q, want %q", actualManyEsc, expectedManyEsc)
|
||||
}
|
||||
|
||||
expectedSplit := "split text"
|
||||
for _, ch := range "split \x1b[0m text" {
|
||||
fmt.Fprintf(w, string(ch))
|
||||
}
|
||||
actualSplit := inner.String()
|
||||
inner.Reset()
|
||||
if actualSplit != expectedSplit {
|
||||
t.Errorf("Get %q, want %q", actualSplit, expectedSplit)
|
||||
}
|
||||
}
|
||||
|
||||
type screenNotFoundError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func writeAnsiColor(expectedText, colorCode string) (actualText string, actualAttributes uint16, err error) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w := NewAnsiColorWriter(inner)
|
||||
fmt.Fprintf(w, "\x1b[%sm%s", colorCode, expectedText)
|
||||
|
||||
actualText = inner.String()
|
||||
screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
||||
if screenInfo != nil {
|
||||
actualAttributes = screenInfo.WAttributes
|
||||
} else {
|
||||
err = &screenNotFoundError{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type testParam struct {
|
||||
text string
|
||||
attributes uint16
|
||||
ansiColor string
|
||||
}
|
||||
|
||||
func TestWriteAnsiColorText(t *testing.T) {
|
||||
screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
||||
if screenInfo == nil {
|
||||
t.Fatal("Could not get ConsoleScreenBufferInfo")
|
||||
}
|
||||
defer ChangeColor(screenInfo.WAttributes)
|
||||
defaultFgColor := screenInfo.WAttributes & uint16(0x0007)
|
||||
defaultBgColor := screenInfo.WAttributes & uint16(0x0070)
|
||||
defaultFgIntensity := screenInfo.WAttributes & uint16(0x0008)
|
||||
defaultBgIntensity := screenInfo.WAttributes & uint16(0x0080)
|
||||
|
||||
fgParam := []testParam{
|
||||
{"foreground black ", uint16(0x0000 | 0x0000), "30"},
|
||||
{"foreground red ", uint16(0x0004 | 0x0000), "31"},
|
||||
{"foreground green ", uint16(0x0002 | 0x0000), "32"},
|
||||
{"foreground yellow ", uint16(0x0006 | 0x0000), "33"},
|
||||
{"foreground blue ", uint16(0x0001 | 0x0000), "34"},
|
||||
{"foreground magenta", uint16(0x0005 | 0x0000), "35"},
|
||||
{"foreground cyan ", uint16(0x0003 | 0x0000), "36"},
|
||||
{"foreground white ", uint16(0x0007 | 0x0000), "37"},
|
||||
{"foreground default", defaultFgColor | 0x0000, "39"},
|
||||
{"foreground light gray ", uint16(0x0000 | 0x0008 | 0x0000), "90"},
|
||||
{"foreground light red ", uint16(0x0004 | 0x0008 | 0x0000), "91"},
|
||||
{"foreground light green ", uint16(0x0002 | 0x0008 | 0x0000), "92"},
|
||||
{"foreground light yellow ", uint16(0x0006 | 0x0008 | 0x0000), "93"},
|
||||
{"foreground light blue ", uint16(0x0001 | 0x0008 | 0x0000), "94"},
|
||||
{"foreground light magenta", uint16(0x0005 | 0x0008 | 0x0000), "95"},
|
||||
{"foreground light cyan ", uint16(0x0003 | 0x0008 | 0x0000), "96"},
|
||||
{"foreground light white ", uint16(0x0007 | 0x0008 | 0x0000), "97"},
|
||||
}
|
||||
|
||||
bgParam := []testParam{
|
||||
{"background black ", uint16(0x0007 | 0x0000), "40"},
|
||||
{"background red ", uint16(0x0007 | 0x0040), "41"},
|
||||
{"background green ", uint16(0x0007 | 0x0020), "42"},
|
||||
{"background yellow ", uint16(0x0007 | 0x0060), "43"},
|
||||
{"background blue ", uint16(0x0007 | 0x0010), "44"},
|
||||
{"background magenta", uint16(0x0007 | 0x0050), "45"},
|
||||
{"background cyan ", uint16(0x0007 | 0x0030), "46"},
|
||||
{"background white ", uint16(0x0007 | 0x0070), "47"},
|
||||
{"background default", uint16(0x0007) | defaultBgColor, "49"},
|
||||
{"background light gray ", uint16(0x0007 | 0x0000 | 0x0080), "100"},
|
||||
{"background light red ", uint16(0x0007 | 0x0040 | 0x0080), "101"},
|
||||
{"background light green ", uint16(0x0007 | 0x0020 | 0x0080), "102"},
|
||||
{"background light yellow ", uint16(0x0007 | 0x0060 | 0x0080), "103"},
|
||||
{"background light blue ", uint16(0x0007 | 0x0010 | 0x0080), "104"},
|
||||
{"background light magenta", uint16(0x0007 | 0x0050 | 0x0080), "105"},
|
||||
{"background light cyan ", uint16(0x0007 | 0x0030 | 0x0080), "106"},
|
||||
{"background light white ", uint16(0x0007 | 0x0070 | 0x0080), "107"},
|
||||
}
|
||||
|
||||
resetParam := []testParam{
|
||||
{"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, "0"},
|
||||
{"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, ""},
|
||||
}
|
||||
|
||||
boldParam := []testParam{
|
||||
{"bold on", uint16(0x0007 | 0x0008), "1"},
|
||||
{"bold off", uint16(0x0007), "21"},
|
||||
}
|
||||
|
||||
underscoreParam := []testParam{
|
||||
{"underscore on", uint16(0x0007 | 0x8000), "4"},
|
||||
{"underscore off", uint16(0x0007), "24"},
|
||||
}
|
||||
|
||||
blinkParam := []testParam{
|
||||
{"blink on", uint16(0x0007 | 0x0080), "5"},
|
||||
{"blink off", uint16(0x0007), "25"},
|
||||
}
|
||||
|
||||
mixedParam := []testParam{
|
||||
{"both black, bold, underline, blink", uint16(0x0000 | 0x0000 | 0x0008 | 0x8000 | 0x0080), "30;40;1;4;5"},
|
||||
{"both red, bold, underline, blink", uint16(0x0004 | 0x0040 | 0x0008 | 0x8000 | 0x0080), "31;41;1;4;5"},
|
||||
{"both green, bold, underline, blink", uint16(0x0002 | 0x0020 | 0x0008 | 0x8000 | 0x0080), "32;42;1;4;5"},
|
||||
{"both yellow, bold, underline, blink", uint16(0x0006 | 0x0060 | 0x0008 | 0x8000 | 0x0080), "33;43;1;4;5"},
|
||||
{"both blue, bold, underline, blink", uint16(0x0001 | 0x0010 | 0x0008 | 0x8000 | 0x0080), "34;44;1;4;5"},
|
||||
{"both magenta, bold, underline, blink", uint16(0x0005 | 0x0050 | 0x0008 | 0x8000 | 0x0080), "35;45;1;4;5"},
|
||||
{"both cyan, bold, underline, blink", uint16(0x0003 | 0x0030 | 0x0008 | 0x8000 | 0x0080), "36;46;1;4;5"},
|
||||
{"both white, bold, underline, blink", uint16(0x0007 | 0x0070 | 0x0008 | 0x8000 | 0x0080), "37;47;1;4;5"},
|
||||
{"both default, bold, underline, blink", uint16(defaultFgColor | defaultBgColor | 0x0008 | 0x8000 | 0x0080), "39;49;1;4;5"},
|
||||
}
|
||||
|
||||
assertTextAttribute := func(expectedText string, expectedAttributes uint16, ansiColor string) {
|
||||
actualText, actualAttributes, err := writeAnsiColor(expectedText, ansiColor)
|
||||
if actualText != expectedText {
|
||||
t.Errorf("Get %q, want %q", actualText, expectedText)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("Could not get ConsoleScreenBufferInfo")
|
||||
}
|
||||
if actualAttributes != expectedAttributes {
|
||||
t.Errorf("Text: %q, Get 0x%04x, want 0x%04x", expectedText, actualAttributes, expectedAttributes)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range fgParam {
|
||||
ResetColor()
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
for _, v := range bgParam {
|
||||
ChangeColor(uint16(0x0070 | 0x0007))
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
for _, v := range resetParam {
|
||||
ChangeColor(uint16(0x0000 | 0x0070 | 0x0008))
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
ResetColor()
|
||||
for _, v := range boldParam {
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
ResetColor()
|
||||
for _, v := range underscoreParam {
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
ResetColor()
|
||||
for _, v := range blinkParam {
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
for _, v := range mixedParam {
|
||||
ResetColor()
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreUnknownSequences(t *testing.T) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w := NewModeAnsiColorWriter(inner, OutputNonColorEscSeq)
|
||||
|
||||
inputText := "\x1b[=decpath mode"
|
||||
expectedTail := inputText
|
||||
fmt.Fprintf(w, inputText)
|
||||
actualTail := inner.String()
|
||||
inner.Reset()
|
||||
if actualTail != expectedTail {
|
||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
||||
}
|
||||
|
||||
inputText = "\x1b[=tailing esc and bracket\x1b["
|
||||
expectedTail = inputText
|
||||
fmt.Fprintf(w, inputText)
|
||||
actualTail = inner.String()
|
||||
inner.Reset()
|
||||
if actualTail != expectedTail {
|
||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
||||
}
|
||||
|
||||
inputText = "\x1b[?tailing esc\x1b"
|
||||
expectedTail = inputText
|
||||
fmt.Fprintf(w, inputText)
|
||||
actualTail = inner.String()
|
||||
inner.Reset()
|
||||
if actualTail != expectedTail {
|
||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
||||
}
|
||||
|
||||
inputText = "\x1b[1h;3punended color code invalid\x1b3"
|
||||
expectedTail = inputText
|
||||
fmt.Fprintf(w, inputText)
|
||||
actualTail = inner.String()
|
||||
inner.Reset()
|
||||
if actualTail != expectedTail {
|
||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
||||
}
|
||||
}
|
@ -63,7 +63,7 @@ func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
defer c.innerWriter.Close()
|
||||
}
|
||||
|
||||
c.lg.println(when, msg)
|
||||
c.lg.writeln(when, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -17,8 +17,10 @@ package logs
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shiena/ansicolor"
|
||||
)
|
||||
|
||||
// brush is a color join function
|
||||
@ -54,9 +56,9 @@ type consoleWriter struct {
|
||||
// NewConsole create ConsoleWriter returning as LoggerInterface.
|
||||
func NewConsole() Logger {
|
||||
cw := &consoleWriter{
|
||||
lg: newLogWriter(os.Stdout),
|
||||
lg: newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)),
|
||||
Level: LevelDebug,
|
||||
Colorful: runtime.GOOS != "windows",
|
||||
Colorful: true,
|
||||
}
|
||||
return cw
|
||||
}
|
||||
@ -67,11 +69,7 @@ func (c *consoleWriter) Init(jsonConfig string) error {
|
||||
if len(jsonConfig) == 0 {
|
||||
return nil
|
||||
}
|
||||
err := json.Unmarshal([]byte(jsonConfig), c)
|
||||
if runtime.GOOS == "windows" {
|
||||
c.Colorful = false
|
||||
}
|
||||
return err
|
||||
return json.Unmarshal([]byte(jsonConfig), c)
|
||||
}
|
||||
|
||||
// WriteMsg write message in console.
|
||||
@ -80,9 +78,9 @@ func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
return nil
|
||||
}
|
||||
if c.Colorful {
|
||||
msg = colors[level](msg)
|
||||
msg = strings.Replace(msg, levelPrefix[level], colors[level](levelPrefix[level]), 1)
|
||||
}
|
||||
c.lg.println(when, msg)
|
||||
c.lg.writeln(when, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,8 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/OwnLocal/goes"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/belogik/goes"
|
||||
)
|
||||
|
||||
// NewES return a LoggerInterface
|
||||
@ -21,7 +21,7 @@ func NewES() logs.Logger {
|
||||
}
|
||||
|
||||
type esLogger struct {
|
||||
*goes.Connection
|
||||
*goes.Client
|
||||
DSN string `json:"dsn"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
@ -41,8 +41,8 @@ func (el *esLogger) Init(jsonconfig string) error {
|
||||
} else if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||
return err
|
||||
} else {
|
||||
conn := goes.NewConnection(host, port)
|
||||
el.Connection = conn
|
||||
conn := goes.NewClient(host, port)
|
||||
el.Client = conn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -78,3 +78,4 @@ func (el *esLogger) Flush() {
|
||||
func init() {
|
||||
logs.Register(logs.AdapterEs, NewES)
|
||||
}
|
||||
|
||||
|
111
logs/file.go
111
logs/file.go
@ -54,6 +54,12 @@ type fileLogWriter struct {
|
||||
dailyOpenDate int
|
||||
dailyOpenTime time.Time
|
||||
|
||||
// Rotate hourly
|
||||
Hourly bool `json:"hourly"`
|
||||
MaxHours int64 `json:"maxhours"`
|
||||
hourlyOpenDate int
|
||||
hourlyOpenTime time.Time
|
||||
|
||||
Rotate bool `json:"rotate"`
|
||||
|
||||
Level int `json:"level"`
|
||||
@ -70,6 +76,8 @@ func newFileWriter() Logger {
|
||||
w := &fileLogWriter{
|
||||
Daily: true,
|
||||
MaxDays: 7,
|
||||
Hourly: false,
|
||||
MaxHours: 168,
|
||||
Rotate: true,
|
||||
RotatePerm: "0440",
|
||||
Level: LevelTrace,
|
||||
@ -83,15 +91,15 @@ func newFileWriter() Logger {
|
||||
|
||||
// Init file logger with json config.
|
||||
// jsonConfig like:
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxLines":10000,
|
||||
// "maxsize":1024,
|
||||
// "daily":true,
|
||||
// "maxDays":15,
|
||||
// "rotate":true,
|
||||
// "perm":"0600"
|
||||
// }
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxLines":10000,
|
||||
// "maxsize":1024,
|
||||
// "daily":true,
|
||||
// "maxDays":15,
|
||||
// "rotate":true,
|
||||
// "perm":"0600"
|
||||
// }
|
||||
func (w *fileLogWriter) Init(jsonConfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonConfig), w)
|
||||
if err != nil {
|
||||
@ -122,10 +130,16 @@ func (w *fileLogWriter) startLogger() error {
|
||||
return w.initFd()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) needRotate(size int, day int) bool {
|
||||
func (w *fileLogWriter) needRotateDaily(size int, day int) bool {
|
||||
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||
(w.Daily && day != w.dailyOpenDate)
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) needRotateHourly(size int, hour int) bool {
|
||||
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||
(w.Hourly && hour != w.hourlyOpenDate)
|
||||
|
||||
}
|
||||
|
||||
@ -134,14 +148,23 @@ func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > w.Level {
|
||||
return nil
|
||||
}
|
||||
h, d := formatTimeHeader(when)
|
||||
msg = string(h) + msg + "\n"
|
||||
hd, d, h := formatTimeHeader(when)
|
||||
msg = string(hd) + msg + "\n"
|
||||
if w.Rotate {
|
||||
w.RLock()
|
||||
if w.needRotate(len(msg), d) {
|
||||
if w.needRotateHourly(len(msg), h) {
|
||||
w.RUnlock()
|
||||
w.Lock()
|
||||
if w.needRotate(len(msg), d) {
|
||||
if w.needRotateHourly(len(msg), h) {
|
||||
if err := w.doRotate(when); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
} else if w.needRotateDaily(len(msg), d) {
|
||||
w.RUnlock()
|
||||
w.Lock()
|
||||
if w.needRotateDaily(len(msg), d) {
|
||||
if err := w.doRotate(when); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
@ -189,8 +212,12 @@ func (w *fileLogWriter) initFd() error {
|
||||
w.maxSizeCurSize = int(fInfo.Size())
|
||||
w.dailyOpenTime = time.Now()
|
||||
w.dailyOpenDate = w.dailyOpenTime.Day()
|
||||
w.hourlyOpenTime = time.Now()
|
||||
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
|
||||
w.maxLinesCurLines = 0
|
||||
if w.Daily {
|
||||
if w.Hourly {
|
||||
go w.hourlyRotate(w.hourlyOpenTime)
|
||||
} else if w.Daily {
|
||||
go w.dailyRotate(w.dailyOpenTime)
|
||||
}
|
||||
if fInfo.Size() > 0 && w.MaxLines > 0 {
|
||||
@ -209,7 +236,22 @@ func (w *fileLogWriter) dailyRotate(openTime time.Time) {
|
||||
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
||||
<-tm.C
|
||||
w.Lock()
|
||||
if w.needRotate(0, time.Now().Day()) {
|
||||
if w.needRotateDaily(0, time.Now().Day()) {
|
||||
if err := w.doRotate(time.Now()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) hourlyRotate(openTime time.Time) {
|
||||
y, m, d := openTime.Add(1 * time.Hour).Date()
|
||||
h, _, _ := openTime.Add(1 * time.Hour).Clock()
|
||||
nextHour := time.Date(y, m, d, h, 0, 0, 0, openTime.Location())
|
||||
tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100))
|
||||
<-tm.C
|
||||
w.Lock()
|
||||
if w.needRotateHourly(0, time.Now().Hour()) {
|
||||
if err := w.doRotate(time.Now()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
@ -251,6 +293,8 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||
// Find the next available number
|
||||
num := w.MaxFilesCurFiles + 1
|
||||
fName := ""
|
||||
format := ""
|
||||
var openTime time.Time
|
||||
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -262,17 +306,26 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||
goto RESTART_LOGGER
|
||||
}
|
||||
|
||||
if w.Hourly {
|
||||
format = "2006010215"
|
||||
openTime = w.hourlyOpenTime
|
||||
} else if w.Daily {
|
||||
format = "2006-01-02"
|
||||
openTime = w.dailyOpenTime
|
||||
}
|
||||
|
||||
// only when one of them be setted, then the file would be splited
|
||||
if w.MaxLines > 0 || w.MaxSize > 0 {
|
||||
for ; err == nil && num <= w.MaxFiles; num++ {
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format(format), num, w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
}
|
||||
} else {
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", openTime.Format(format), num, w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
w.MaxFilesCurFiles = num
|
||||
}
|
||||
|
||||
// return error if the last file checked still existed
|
||||
if err == nil {
|
||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
|
||||
@ -316,13 +369,21 @@ func (w *fileLogWriter) deleteOldLog() {
|
||||
if info == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
if w.Hourly {
|
||||
if !info.IsDir() && info.ModTime().Add(1 * time.Hour * time.Duration(w.MaxHours)).Before(time.Now()) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
} else if w.Daily {
|
||||
if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ func TestFile2(t *testing.T) {
|
||||
os.Remove("test2.log")
|
||||
}
|
||||
|
||||
func TestFileRotate_01(t *testing.T) {
|
||||
func TestFileDailyRotate_01(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
@ -133,28 +133,28 @@ func TestFileRotate_01(t *testing.T) {
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func TestFileRotate_02(t *testing.T) {
|
||||
func TestFileDailyRotate_02(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2)
|
||||
testFileRotate(t, fn1, fn2, true, false)
|
||||
}
|
||||
|
||||
func TestFileRotate_03(t *testing.T) {
|
||||
func TestFileDailyRotate_03(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2)
|
||||
testFileRotate(t, fn1, fn2, true, false)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func TestFileRotate_04(t *testing.T) {
|
||||
func TestFileDailyRotate_04(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileDailyRotate(t, fn1, fn2)
|
||||
}
|
||||
|
||||
func TestFileRotate_05(t *testing.T) {
|
||||
func TestFileDailyRotate_05(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||
os.Create(fn)
|
||||
@ -162,7 +162,7 @@ func TestFileRotate_05(t *testing.T) {
|
||||
testFileDailyRotate(t, fn1, fn2)
|
||||
os.Remove(fn)
|
||||
}
|
||||
func TestFileRotate_06(t *testing.T) { //test file mode
|
||||
func TestFileDailyRotate_06(t *testing.T) { //test file mode
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
@ -183,19 +183,105 @@ func TestFileRotate_06(t *testing.T) { //test file mode
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
func testFileRotate(t *testing.T, fn1, fn2 string) {
|
||||
|
||||
func TestFileHourlyRotate_01(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
|
||||
b, err := exists(rotateName)
|
||||
if !b || err != nil {
|
||||
os.Remove("test3.log")
|
||||
t.Fatal("rotate not generated")
|
||||
}
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_02(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2, false, true)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_03(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2, false, true)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_04(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileHourlyRotate(t, fn1, fn2)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_05(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileHourlyRotate(t, fn1, fn2)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_06(t *testing.T) { //test file mode
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
|
||||
s, _ := os.Lstat(rotateName)
|
||||
if s.Mode() != 0440 {
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
t.Fatal("rotate file mode error")
|
||||
}
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func testFileRotate(t *testing.T, fn1, fn2 string, daily, hourly bool) {
|
||||
fw := &fileLogWriter{
|
||||
Daily: true,
|
||||
Daily: daily,
|
||||
MaxDays: 7,
|
||||
Hourly: hourly,
|
||||
MaxHours: 168,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
RotatePerm: "0440",
|
||||
}
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug)
|
||||
|
||||
if daily {
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||
}
|
||||
|
||||
if hourly {
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
|
||||
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
|
||||
fw.hourlyOpenDate = fw.hourlyOpenTime.Day()
|
||||
}
|
||||
|
||||
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug)
|
||||
|
||||
for _, file := range []string{fn1, fn2} {
|
||||
_, err := os.Stat(file)
|
||||
@ -240,6 +326,37 @@ func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
|
||||
fw.Destroy()
|
||||
}
|
||||
|
||||
func testFileHourlyRotate(t *testing.T, fn1, fn2 string) {
|
||||
fw := &fileLogWriter{
|
||||
Hourly: true,
|
||||
MaxHours: 168,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
RotatePerm: "0440",
|
||||
}
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
|
||||
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
|
||||
fw.hourlyOpenDate = fw.hourlyOpenTime.Hour()
|
||||
hour, _ := time.ParseInLocation("2006010215", time.Now().Format("2006010215"), fw.hourlyOpenTime.Location())
|
||||
hour = hour.Add(-1 * time.Second)
|
||||
fw.hourlyRotate(hour)
|
||||
for _, file := range []string{fn1, fn2} {
|
||||
_, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if len(content) > 0 {
|
||||
t.FailNow()
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
fw.Destroy()
|
||||
}
|
||||
func exists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
|
12
logs/log.go
12
logs/log.go
@ -47,7 +47,7 @@ import (
|
||||
|
||||
// RFC5424 log message levels.
|
||||
const (
|
||||
LevelEmergency = iota
|
||||
LevelEmergency = iota
|
||||
LevelAlert
|
||||
LevelCritical
|
||||
LevelError
|
||||
@ -92,7 +92,7 @@ type Logger interface {
|
||||
}
|
||||
|
||||
var adapters = make(map[string]newLoggerFunc)
|
||||
var levelPrefix = [LevelDebug + 1]string{"[M] ", "[A] ", "[C] ", "[E] ", "[W] ", "[N] ", "[I] ", "[D] "}
|
||||
var levelPrefix = [LevelDebug + 1]string{"[M]", "[A]", "[C]", "[E]", "[W]", "[N]", "[I]", "[D]"}
|
||||
|
||||
// Register makes a log provide available by the provided name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
@ -187,12 +187,12 @@ func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
|
||||
}
|
||||
}
|
||||
|
||||
log, ok := adapters[adapterName]
|
||||
logAdapter, ok := adapters[adapterName]
|
||||
if !ok {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||
}
|
||||
|
||||
lg := log()
|
||||
lg := logAdapter()
|
||||
err := lg.Init(config)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
|
||||
@ -248,7 +248,7 @@ func (bl *BeeLogger) Write(p []byte) (n int, err error) {
|
||||
}
|
||||
// writeMsg will always add a '\n' character
|
||||
if p[len(p)-1] == '\n' {
|
||||
p = p[0: len(p)-1]
|
||||
p = p[0 : len(p)-1]
|
||||
}
|
||||
// set levelLoggerImpl to ensure all log message will be write out
|
||||
err = bl.writeMsg(levelLoggerImpl, string(p))
|
||||
@ -287,7 +287,7 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
|
||||
// set to emergency to ensure all log will be print out correctly
|
||||
logLevel = LevelEmergency
|
||||
} else {
|
||||
msg = levelPrefix[logLevel] + msg
|
||||
msg = levelPrefix[logLevel] + " " + msg
|
||||
}
|
||||
|
||||
if bl.asynchronous {
|
||||
|
131
logs/logger.go
131
logs/logger.go
@ -15,9 +15,8 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -31,47 +30,13 @@ func newLogWriter(wr io.Writer) *logWriter {
|
||||
return &logWriter{writer: wr}
|
||||
}
|
||||
|
||||
func (lg *logWriter) println(when time.Time, msg string) {
|
||||
func (lg *logWriter) writeln(when time.Time, msg string) {
|
||||
lg.Lock()
|
||||
h, _ := formatTimeHeader(when)
|
||||
h, _, _ := formatTimeHeader(when)
|
||||
lg.writer.Write(append(append(h, msg...), '\n'))
|
||||
lg.Unlock()
|
||||
}
|
||||
|
||||
type outputMode int
|
||||
|
||||
// DiscardNonColorEscSeq supports the divided color escape sequence.
|
||||
// But non-color escape sequence is not output.
|
||||
// Please use the OutputNonColorEscSeq If you want to output a non-color
|
||||
// escape sequences such as ncurses. However, it does not support the divided
|
||||
// color escape sequence.
|
||||
const (
|
||||
_ outputMode = iota
|
||||
DiscardNonColorEscSeq
|
||||
OutputNonColorEscSeq
|
||||
)
|
||||
|
||||
// NewAnsiColorWriter creates and initializes a new ansiColorWriter
|
||||
// using io.Writer w as its initial contents.
|
||||
// In the console of Windows, which change the foreground and background
|
||||
// colors of the text by the escape sequence.
|
||||
// In the console of other systems, which writes to w all text.
|
||||
func NewAnsiColorWriter(w io.Writer) io.Writer {
|
||||
return NewModeAnsiColorWriter(w, DiscardNonColorEscSeq)
|
||||
}
|
||||
|
||||
// NewModeAnsiColorWriter create and initializes a new ansiColorWriter
|
||||
// by specifying the outputMode.
|
||||
func NewModeAnsiColorWriter(w io.Writer, mode outputMode) io.Writer {
|
||||
if _, ok := w.(*ansiColorWriter); !ok {
|
||||
return &ansiColorWriter{
|
||||
w: w,
|
||||
mode: mode,
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
const (
|
||||
y1 = `0123456789`
|
||||
y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||
@ -90,7 +55,7 @@ const (
|
||||
ns1 = `0123456789`
|
||||
)
|
||||
|
||||
func formatTimeHeader(when time.Time) ([]byte, int) {
|
||||
func formatTimeHeader(when time.Time) ([]byte, int, int) {
|
||||
y, mo, d := when.Date()
|
||||
h, mi, s := when.Clock()
|
||||
ns := when.Nanosecond() / 1000000
|
||||
@ -123,7 +88,7 @@ func formatTimeHeader(when time.Time) ([]byte, int) {
|
||||
|
||||
buf[23] = ' '
|
||||
|
||||
return buf[0:], d
|
||||
return buf[0:], d, h
|
||||
}
|
||||
|
||||
var (
|
||||
@ -146,63 +111,65 @@ var (
|
||||
reset = string([]byte{27, 91, 48, 109})
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
var colorMap map[string]string
|
||||
|
||||
func initColor() {
|
||||
if runtime.GOOS == "windows" {
|
||||
green = w32Green
|
||||
white = w32White
|
||||
yellow = w32Yellow
|
||||
red = w32Red
|
||||
blue = w32Blue
|
||||
magenta = w32Magenta
|
||||
cyan = w32Cyan
|
||||
}
|
||||
colorMap = map[string]string{
|
||||
//by color
|
||||
"green": green,
|
||||
"white": white,
|
||||
"yellow": yellow,
|
||||
"red": red,
|
||||
//by method
|
||||
"GET": blue,
|
||||
"POST": cyan,
|
||||
"PUT": yellow,
|
||||
"DELETE": red,
|
||||
"PATCH": green,
|
||||
"HEAD": magenta,
|
||||
"OPTIONS": white,
|
||||
}
|
||||
}
|
||||
|
||||
// ColorByStatus return color by http code
|
||||
// 2xx return Green
|
||||
// 3xx return White
|
||||
// 4xx return Yellow
|
||||
// 5xx return Red
|
||||
func ColorByStatus(cond bool, code int) string {
|
||||
func ColorByStatus(code int) string {
|
||||
once.Do(initColor)
|
||||
switch {
|
||||
case code >= 200 && code < 300:
|
||||
return map[bool]string{true: green, false: w32Green}[cond]
|
||||
return colorMap["green"]
|
||||
case code >= 300 && code < 400:
|
||||
return map[bool]string{true: white, false: w32White}[cond]
|
||||
return colorMap["white"]
|
||||
case code >= 400 && code < 500:
|
||||
return map[bool]string{true: yellow, false: w32Yellow}[cond]
|
||||
return colorMap["yellow"]
|
||||
default:
|
||||
return map[bool]string{true: red, false: w32Red}[cond]
|
||||
return colorMap["red"]
|
||||
}
|
||||
}
|
||||
|
||||
// ColorByMethod return color by http code
|
||||
// GET return Blue
|
||||
// POST return Cyan
|
||||
// PUT return Yellow
|
||||
// DELETE return Red
|
||||
// PATCH return Green
|
||||
// HEAD return Magenta
|
||||
// OPTIONS return WHITE
|
||||
func ColorByMethod(cond bool, method string) string {
|
||||
switch method {
|
||||
case "GET":
|
||||
return map[bool]string{true: blue, false: w32Blue}[cond]
|
||||
case "POST":
|
||||
return map[bool]string{true: cyan, false: w32Cyan}[cond]
|
||||
case "PUT":
|
||||
return map[bool]string{true: yellow, false: w32Yellow}[cond]
|
||||
case "DELETE":
|
||||
return map[bool]string{true: red, false: w32Red}[cond]
|
||||
case "PATCH":
|
||||
return map[bool]string{true: green, false: w32Green}[cond]
|
||||
case "HEAD":
|
||||
return map[bool]string{true: magenta, false: w32Magenta}[cond]
|
||||
case "OPTIONS":
|
||||
return map[bool]string{true: white, false: w32White}[cond]
|
||||
default:
|
||||
return reset
|
||||
func ColorByMethod(method string) string {
|
||||
once.Do(initColor)
|
||||
if c := colorMap[method]; c != "" {
|
||||
return c
|
||||
}
|
||||
return reset
|
||||
}
|
||||
|
||||
// Guard Mutex to guarantee atomic of W32Debug(string) function
|
||||
var mu sync.Mutex
|
||||
|
||||
// W32Debug Helper method to output colored logs in Windows terminals
|
||||
func W32Debug(msg string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
current := time.Now()
|
||||
w := NewAnsiColorWriter(os.Stdout)
|
||||
|
||||
fmt.Fprintf(w, "[beego] %v %s\n", current.Format("2006/01/02 - 15:04:05"), msg)
|
||||
// ResetColor return reset color
|
||||
func ResetColor() string {
|
||||
return reset
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@ -30,7 +29,7 @@ func TestFormatHeader_0(t *testing.T) {
|
||||
if tm.Year() >= 2100 {
|
||||
break
|
||||
}
|
||||
h, _ := formatTimeHeader(tm)
|
||||
h, _, _ := formatTimeHeader(tm)
|
||||
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||
t.Log(tm)
|
||||
t.FailNow()
|
||||
@ -48,7 +47,7 @@ func TestFormatHeader_1(t *testing.T) {
|
||||
if tm.Year() >= year+1 {
|
||||
break
|
||||
}
|
||||
h, _ := formatTimeHeader(tm)
|
||||
h, _, _ := formatTimeHeader(tm)
|
||||
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||
t.Log(tm)
|
||||
t.FailNow()
|
||||
@ -56,20 +55,3 @@ func TestFormatHeader_1(t *testing.T) {
|
||||
tm = tm.Add(dur)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAnsiColor1(t *testing.T) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w := NewAnsiColorWriter(inner)
|
||||
if w == inner {
|
||||
t.Errorf("Get %#v, want %#v", w, inner)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAnsiColor2(t *testing.T) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w1 := NewAnsiColorWriter(inner)
|
||||
w2 := NewAnsiColorWriter(w1)
|
||||
if w1 != w2 {
|
||||
t.Errorf("Get %#v, want %#v", w1, w2)
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ package migration
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
// Index struct defines the structure of Index Columns
|
||||
@ -316,7 +316,7 @@ func (m *Migration) GetSQL() (sql string) {
|
||||
sql += fmt.Sprintf("ALTER TABLE `%s` ", m.TableName)
|
||||
for index, column := range m.Columns {
|
||||
if !column.remove {
|
||||
beego.BeeLogger.Info("col")
|
||||
logs.Info("col")
|
||||
sql += fmt.Sprintf("\n ADD `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
|
||||
} else {
|
||||
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
|
||||
|
@ -176,8 +176,9 @@ func Register(name string, m Migrationer) error {
|
||||
func Upgrade(lasttime int64) error {
|
||||
sm := sortMap(migrationMap)
|
||||
i := 0
|
||||
migs, _ := getAllMigrations()
|
||||
for _, v := range sm {
|
||||
if v.created > lasttime {
|
||||
if _, ok := migs[v.name]; !ok {
|
||||
logs.Info("start upgrade", v.name)
|
||||
v.m.Reset()
|
||||
v.m.Up()
|
||||
@ -310,3 +311,20 @@ func isRollBack(name string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
func getAllMigrations() (map[string]string, error) {
|
||||
o := orm.NewOrm()
|
||||
var maps []orm.Params
|
||||
migs := make(map[string]string)
|
||||
num, err := o.Raw("select * from migrations order by id_migration desc").Values(&maps)
|
||||
if err != nil {
|
||||
logs.Info("get name has error", err)
|
||||
return migs, err
|
||||
}
|
||||
if num > 0 {
|
||||
for _, v := range maps {
|
||||
name := v["name"].(string)
|
||||
migs[name] = v["status"].(string)
|
||||
}
|
||||
}
|
||||
return migs, nil
|
||||
}
|
||||
|
@ -207,11 +207,11 @@ func (n *Namespace) Include(cList ...ControllerInterface) *Namespace {
|
||||
func (n *Namespace) Namespace(ns ...*Namespace) *Namespace {
|
||||
for _, ni := range ns {
|
||||
for k, v := range ni.handlers.routers {
|
||||
if t, ok := n.handlers.routers[k]; ok {
|
||||
if _, ok := n.handlers.routers[k]; ok {
|
||||
addPrefix(v, ni.prefix)
|
||||
n.handlers.routers[k].AddTree(ni.prefix, v)
|
||||
} else {
|
||||
t = NewTree()
|
||||
t := NewTree()
|
||||
t.AddTree(ni.prefix, v)
|
||||
addPrefix(t, ni.prefix)
|
||||
n.handlers.routers[k] = t
|
||||
@ -236,11 +236,11 @@ func (n *Namespace) Namespace(ns ...*Namespace) *Namespace {
|
||||
func AddNamespace(nl ...*Namespace) {
|
||||
for _, n := range nl {
|
||||
for k, v := range n.handlers.routers {
|
||||
if t, ok := BeeApp.Handlers.routers[k]; ok {
|
||||
if _, ok := BeeApp.Handlers.routers[k]; ok {
|
||||
addPrefix(v, n.prefix)
|
||||
BeeApp.Handlers.routers[k].AddTree(n.prefix, v)
|
||||
} else {
|
||||
t = NewTree()
|
||||
t := NewTree()
|
||||
t.AddTree(n.prefix, v)
|
||||
addPrefix(t, n.prefix)
|
||||
BeeApp.Handlers.routers[k] = t
|
||||
|
71
orm/db.go
71
orm/db.go
@ -536,6 +536,8 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
|
||||
updates := make([]string, len(names))
|
||||
var conflitValue interface{}
|
||||
for i, v := range names {
|
||||
// identifier in database may not be case-sensitive, so quote it
|
||||
v = fmt.Sprintf("%s%s%s", Q, v, Q)
|
||||
marks[i] = "?"
|
||||
valueStr := argsMap[strings.ToLower(v)]
|
||||
if v == args0 {
|
||||
@ -619,6 +621,31 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var findAutoNowAdd, findAutoNow bool
|
||||
var index int
|
||||
for i, col := range setNames {
|
||||
if mi.fields.GetByColumn(col).autoNowAdd {
|
||||
index = i
|
||||
findAutoNowAdd = true
|
||||
}
|
||||
if mi.fields.GetByColumn(col).autoNow {
|
||||
findAutoNow = true
|
||||
}
|
||||
}
|
||||
if findAutoNowAdd {
|
||||
setNames = append(setNames[0:index], setNames[index+1:]...)
|
||||
setValues = append(setValues[0:index], setValues[index+1:]...)
|
||||
}
|
||||
|
||||
if !findAutoNow {
|
||||
for col, info := range mi.fields.columns {
|
||||
if info.autoNow {
|
||||
setNames = append(setNames, col)
|
||||
setValues = append(setValues, time.Now())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setValues = append(setValues, pkValue)
|
||||
|
||||
Q := d.ins.TableQuote()
|
||||
@ -760,7 +787,13 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
|
||||
}
|
||||
|
||||
d.ins.ReplaceMarks(&query)
|
||||
res, err := q.Exec(query, values...)
|
||||
var err error
|
||||
var res sql.Result
|
||||
if qs != nil && qs.forContext {
|
||||
res, err = q.ExecContext(qs.ctx, query, values...)
|
||||
} else {
|
||||
res, err = q.Exec(query, values...)
|
||||
}
|
||||
if err == nil {
|
||||
return res.RowsAffected()
|
||||
}
|
||||
@ -849,11 +882,16 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
|
||||
for i := range marks {
|
||||
marks[i] = "?"
|
||||
}
|
||||
sql := fmt.Sprintf("IN (%s)", strings.Join(marks, ", "))
|
||||
query = fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s %s", Q, mi.table, Q, Q, mi.fields.pk.column, Q, sql)
|
||||
sqlIn := fmt.Sprintf("IN (%s)", strings.Join(marks, ", "))
|
||||
query = fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s %s", Q, mi.table, Q, Q, mi.fields.pk.column, Q, sqlIn)
|
||||
|
||||
d.ins.ReplaceMarks(&query)
|
||||
res, err := q.Exec(query, args...)
|
||||
var res sql.Result
|
||||
if qs != nil && qs.forContext {
|
||||
res, err = q.ExecContext(qs.ctx, query, args...)
|
||||
} else {
|
||||
res, err = q.Exec(query, args...)
|
||||
}
|
||||
if err == nil {
|
||||
num, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
@ -926,7 +964,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
|
||||
maps[fi.column] = true
|
||||
}
|
||||
} else {
|
||||
panic(fmt.Errorf("wrong field/column name `%s`", col))
|
||||
return 0, fmt.Errorf("wrong field/column name `%s`", col)
|
||||
}
|
||||
}
|
||||
if hasRel {
|
||||
@ -976,11 +1014,18 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
|
||||
d.ins.ReplaceMarks(&query)
|
||||
|
||||
var rs *sql.Rows
|
||||
r, err := q.Query(query, args...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
var err error
|
||||
if qs != nil && qs.forContext {
|
||||
rs, err = q.QueryContext(qs.ctx, query, args...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
} else {
|
||||
rs, err = q.Query(query, args...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
rs = r
|
||||
|
||||
refs := make([]interface{}, colsNum)
|
||||
for i := range refs {
|
||||
@ -1109,8 +1154,12 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition
|
||||
|
||||
d.ins.ReplaceMarks(&query)
|
||||
|
||||
row := q.QueryRow(query, args...)
|
||||
|
||||
var row *sql.Row
|
||||
if qs != nil && qs.forContext {
|
||||
row = q.QueryRowContext(qs.ctx, query, args...)
|
||||
} else {
|
||||
row = q.QueryRow(query, args...)
|
||||
}
|
||||
err = row.Scan(&cnt)
|
||||
return
|
||||
}
|
||||
|
103
orm/db_alias.go
103
orm/db_alias.go
@ -15,6 +15,7 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@ -103,6 +104,96 @@ func (ac *_dbCache) getDefault() (al *alias) {
|
||||
return
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
*sync.RWMutex
|
||||
DB *sql.DB
|
||||
stmts map[string]*sql.Stmt
|
||||
}
|
||||
|
||||
func (d *DB) Begin() (*sql.Tx, error) {
|
||||
return d.DB.Begin()
|
||||
}
|
||||
|
||||
func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
|
||||
return d.DB.BeginTx(ctx, opts)
|
||||
}
|
||||
|
||||
func (d *DB) getStmt(query string) (*sql.Stmt, error) {
|
||||
d.RLock()
|
||||
if stmt, ok := d.stmts[query]; ok {
|
||||
d.RUnlock()
|
||||
return stmt, nil
|
||||
}
|
||||
d.RUnlock()
|
||||
|
||||
stmt, err := d.Prepare(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.Lock()
|
||||
d.stmts[query] = stmt
|
||||
d.Unlock()
|
||||
return stmt, nil
|
||||
}
|
||||
|
||||
func (d *DB) Prepare(query string) (*sql.Stmt, error) {
|
||||
return d.DB.Prepare(query)
|
||||
}
|
||||
|
||||
func (d *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||
return d.DB.PrepareContext(ctx, query)
|
||||
}
|
||||
|
||||
func (d *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
stmt, err := d.getStmt(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stmt.Exec(args...)
|
||||
}
|
||||
|
||||
func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||
stmt, err := d.getStmt(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stmt.ExecContext(ctx, args...)
|
||||
}
|
||||
|
||||
func (d *DB) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
stmt, err := d.getStmt(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stmt.Query(args...)
|
||||
}
|
||||
|
||||
func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||
stmt, err := d.getStmt(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stmt.QueryContext(ctx, args...)
|
||||
}
|
||||
|
||||
func (d *DB) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||
stmt, err := d.getStmt(query)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return stmt.QueryRow(args...)
|
||||
|
||||
}
|
||||
|
||||
func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||
|
||||
stmt, err := d.getStmt(query)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return stmt.QueryRowContext(ctx, args)
|
||||
}
|
||||
|
||||
type alias struct {
|
||||
Name string
|
||||
Driver DriverType
|
||||
@ -110,7 +201,7 @@ type alias struct {
|
||||
DataSource string
|
||||
MaxIdleConns int
|
||||
MaxOpenConns int
|
||||
DB *sql.DB
|
||||
DB *DB
|
||||
DbBaser dbBaser
|
||||
TZ *time.Location
|
||||
Engine string
|
||||
@ -176,7 +267,11 @@ func addAliasWthDB(aliasName, driverName string, db *sql.DB) (*alias, error) {
|
||||
al := new(alias)
|
||||
al.Name = aliasName
|
||||
al.DriverName = driverName
|
||||
al.DB = db
|
||||
al.DB = &DB{
|
||||
RWMutex: new(sync.RWMutex),
|
||||
DB: db,
|
||||
stmts: make(map[string]*sql.Stmt),
|
||||
}
|
||||
|
||||
if dr, ok := drivers[driverName]; ok {
|
||||
al.DbBaser = dbBasers[dr]
|
||||
@ -272,7 +367,7 @@ func SetDataBaseTZ(aliasName string, tz *time.Location) error {
|
||||
func SetMaxIdleConns(aliasName string, maxIdleConns int) {
|
||||
al := getDbAlias(aliasName)
|
||||
al.MaxIdleConns = maxIdleConns
|
||||
al.DB.SetMaxIdleConns(maxIdleConns)
|
||||
al.DB.DB.SetMaxIdleConns(maxIdleConns)
|
||||
}
|
||||
|
||||
// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name
|
||||
@ -296,7 +391,7 @@ func GetDB(aliasNames ...string) (*sql.DB, error) {
|
||||
}
|
||||
al, ok := dataBaseCache.get(name)
|
||||
if ok {
|
||||
return al.DB, nil
|
||||
return al.DB.DB, nil
|
||||
}
|
||||
return nil, fmt.Errorf("DataBase of alias name `%s` not found", name)
|
||||
}
|
||||
|
@ -372,7 +372,13 @@ func (t *dbTables) getCondSQL(cond *Condition, sub bool, tz *time.Location) (whe
|
||||
operator = "exact"
|
||||
}
|
||||
|
||||
operSQL, args := t.base.GenerateOperatorSQL(mi, fi, operator, p.args, tz)
|
||||
var operSQL string
|
||||
var args []interface{}
|
||||
if p.isRaw {
|
||||
operSQL = p.sql
|
||||
} else {
|
||||
operSQL, args = t.base.GenerateOperatorSQL(mi, fi, operator, p.args, tz)
|
||||
}
|
||||
|
||||
leftCol := fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q)
|
||||
t.base.GenerateOperatorLeftCol(fi, operator, &leftCol)
|
||||
|
@ -335,11 +335,11 @@ func RegisterModelWithSuffix(suffix string, models ...interface{}) {
|
||||
// BootStrap bootstrap models.
|
||||
// make all model parsed and can not add more models
|
||||
func BootStrap() {
|
||||
modelCache.Lock()
|
||||
defer modelCache.Unlock()
|
||||
if modelCache.done {
|
||||
return
|
||||
}
|
||||
modelCache.Lock()
|
||||
defer modelCache.Unlock()
|
||||
bootStrap()
|
||||
modelCache.done = true
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ checkType:
|
||||
fi.sf = sf
|
||||
fi.fullName = mi.fullName + mName + "." + sf.Name
|
||||
|
||||
fi.description = sf.Tag.Get("description")
|
||||
fi.description = tags["description"]
|
||||
fi.null = attrs["null"]
|
||||
fi.index = attrs["index"]
|
||||
fi.auto = attrs["auto"]
|
||||
|
@ -44,6 +44,7 @@ var supportTag = map[string]int{
|
||||
"decimals": 2,
|
||||
"on_delete": 2,
|
||||
"type": 2,
|
||||
"description": 2,
|
||||
}
|
||||
|
||||
// get reflect.Type name with package path.
|
||||
@ -65,7 +66,7 @@ func getTableName(val reflect.Value) string {
|
||||
return snakeString(reflect.Indirect(val).Type().Name())
|
||||
}
|
||||
|
||||
// get table engine, mysiam or innodb.
|
||||
// get table engine, myisam or innodb.
|
||||
func getTableEngine(val reflect.Value) string {
|
||||
fun := val.MethodByName("TableEngine")
|
||||
if fun.IsValid() {
|
||||
@ -109,7 +110,7 @@ func getTableUnique(val reflect.Value) [][]string {
|
||||
func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col string) string {
|
||||
column := col
|
||||
if col == "" {
|
||||
column = snakeString(sf.Name)
|
||||
column = nameStrategyMap[nameStrategy](sf.Name)
|
||||
}
|
||||
switch ft {
|
||||
case RelForeignKey, RelOneToOne:
|
||||
|
30
orm/orm.go
30
orm/orm.go
@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
// Package orm provide ORM for MySQL/PostgreSQL/sqlite
|
||||
// Simple Usage
|
||||
//
|
||||
@ -52,11 +54,13 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -69,7 +73,7 @@ const (
|
||||
var (
|
||||
Debug = false
|
||||
DebugLog = NewLog(os.Stdout)
|
||||
DefaultRowsLimit = 1000
|
||||
DefaultRowsLimit = -1
|
||||
DefaultRelsDepth = 2
|
||||
DefaultTimeLoc = time.Local
|
||||
ErrTxHasBegan = errors.New("<Ormer.Begin> transaction already begin")
|
||||
@ -422,7 +426,7 @@ func (o *orm) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
|
||||
func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
|
||||
var name string
|
||||
if table, ok := ptrStructOrTableName.(string); ok {
|
||||
name = snakeString(table)
|
||||
name = nameStrategyMap[defaultNameStrategy](table)
|
||||
if mi, ok := modelCache.get(name); ok {
|
||||
qs = newQuerySet(o, mi)
|
||||
}
|
||||
@ -458,11 +462,15 @@ func (o *orm) Using(name string) error {
|
||||
|
||||
// begin transaction
|
||||
func (o *orm) Begin() error {
|
||||
return o.BeginTx(context.Background(), nil)
|
||||
}
|
||||
|
||||
func (o *orm) BeginTx(ctx context.Context, opts *sql.TxOptions) error {
|
||||
if o.isTx {
|
||||
return ErrTxHasBegan
|
||||
}
|
||||
var tx *sql.Tx
|
||||
tx, err := o.db.(txer).Begin()
|
||||
tx, err := o.db.(txer).BeginTx(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -515,6 +523,15 @@ func (o *orm) Driver() Driver {
|
||||
return driver(o.alias.Name)
|
||||
}
|
||||
|
||||
// return sql.DBStats for current database
|
||||
func (o *orm) DBStats() *sql.DBStats {
|
||||
if o.alias != nil && o.alias.DB != nil {
|
||||
stats := o.alias.DB.DB.Stats()
|
||||
return &stats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewOrm create new orm
|
||||
func NewOrm() Ormer {
|
||||
BootStrap() // execute only once
|
||||
@ -541,6 +558,13 @@ func NewOrmWithDB(driverName, aliasName string, db *sql.DB) (Ormer, error) {
|
||||
|
||||
al.Name = aliasName
|
||||
al.DriverName = driverName
|
||||
al.DB = &DB{
|
||||
RWMutex: new(sync.RWMutex),
|
||||
DB: db,
|
||||
stmts: make(map[string]*sql.Stmt),
|
||||
}
|
||||
|
||||
detectTZ(al)
|
||||
|
||||
o := new(orm)
|
||||
o.alias = al
|
||||
|
@ -31,6 +31,8 @@ type condValue struct {
|
||||
isOr bool
|
||||
isNot bool
|
||||
isCond bool
|
||||
isRaw bool
|
||||
sql string
|
||||
}
|
||||
|
||||
// Condition struct.
|
||||
@ -45,6 +47,15 @@ func NewCondition() *Condition {
|
||||
return c
|
||||
}
|
||||
|
||||
// Raw add raw sql to condition
|
||||
func (c Condition) Raw(expr string, sql string) *Condition {
|
||||
if len(sql) == 0 {
|
||||
panic(fmt.Errorf("<Condition.Raw> sql cannot empty"))
|
||||
}
|
||||
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), sql: sql, isRaw: true})
|
||||
return &c
|
||||
}
|
||||
|
||||
// And add expression to condition
|
||||
func (c Condition) And(expr string, args ...interface{}) *Condition {
|
||||
if expr == "" || len(args) == 0 {
|
||||
|
@ -15,6 +15,7 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -28,6 +29,9 @@ type Log struct {
|
||||
*log.Logger
|
||||
}
|
||||
|
||||
//costomer log func
|
||||
var LogFunc func(query map[string]interface{})
|
||||
|
||||
// NewLog set io.Writer to create a Logger.
|
||||
func NewLog(out io.Writer) *Log {
|
||||
d := new(Log)
|
||||
@ -36,12 +40,15 @@ func NewLog(out io.Writer) *Log {
|
||||
}
|
||||
|
||||
func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error, args ...interface{}) {
|
||||
var logMap = make(map[string]interface{})
|
||||
sub := time.Now().Sub(t) / 1e5
|
||||
elsp := float64(int(sub)) / 10.0
|
||||
logMap["cost_time"] = elsp
|
||||
flag := " OK"
|
||||
if err != nil {
|
||||
flag = "FAIL"
|
||||
}
|
||||
logMap["flag"] = flag
|
||||
con := fmt.Sprintf(" -[Queries/%s] - [%s / %11s / %7.1fms] - [%s]", alias.Name, flag, operaton, elsp, query)
|
||||
cons := make([]string, 0, len(args))
|
||||
for _, arg := range args {
|
||||
@ -53,6 +60,10 @@ func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error
|
||||
if err != nil {
|
||||
con += " - " + err.Error()
|
||||
}
|
||||
logMap["sql"] = fmt.Sprintf("%s-`%s`", query, strings.Join(cons, "`, `"))
|
||||
if LogFunc != nil{
|
||||
LogFunc(logMap)
|
||||
}
|
||||
DebugLog.Println(con)
|
||||
}
|
||||
|
||||
@ -122,6 +133,13 @@ func (d *dbQueryLog) Prepare(query string) (*sql.Stmt, error) {
|
||||
return stmt, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||
a := time.Now()
|
||||
stmt, err := d.db.PrepareContext(ctx, query)
|
||||
debugLogQueies(d.alias, "db.Prepare", query, a, err)
|
||||
return stmt, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
a := time.Now()
|
||||
res, err := d.db.Exec(query, args...)
|
||||
@ -129,6 +147,13 @@ func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||
a := time.Now()
|
||||
res, err := d.db.ExecContext(ctx, query, args...)
|
||||
debugLogQueies(d.alias, "db.Exec", query, a, err, args...)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
a := time.Now()
|
||||
res, err := d.db.Query(query, args...)
|
||||
@ -136,6 +161,13 @@ func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||
a := time.Now()
|
||||
res, err := d.db.QueryContext(ctx, query, args...)
|
||||
debugLogQueies(d.alias, "db.Query", query, a, err, args...)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||
a := time.Now()
|
||||
res := d.db.QueryRow(query, args...)
|
||||
@ -143,6 +175,13 @@ func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||
return res
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||
a := time.Now()
|
||||
res := d.db.QueryRowContext(ctx, query, args...)
|
||||
debugLogQueies(d.alias, "db.QueryRow", query, a, nil, args...)
|
||||
return res
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) Begin() (*sql.Tx, error) {
|
||||
a := time.Now()
|
||||
tx, err := d.db.(txer).Begin()
|
||||
@ -150,6 +189,13 @@ func (d *dbQueryLog) Begin() (*sql.Tx, error) {
|
||||
return tx, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
|
||||
a := time.Now()
|
||||
tx, err := d.db.(txer).BeginTx(ctx, opts)
|
||||
debugLogQueies(d.alias, "db.BeginTx", "START TRANSACTION", a, err)
|
||||
return tx, err
|
||||
}
|
||||
|
||||
func (d *dbQueryLog) Commit() error {
|
||||
a := time.Now()
|
||||
err := d.db.(txEnder).Commit()
|
||||
|
@ -15,6 +15,7 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
@ -55,17 +56,19 @@ func ColValue(opt operator, value interface{}) interface{} {
|
||||
|
||||
// real query struct
|
||||
type querySet struct {
|
||||
mi *modelInfo
|
||||
cond *Condition
|
||||
related []string
|
||||
relDepth int
|
||||
limit int64
|
||||
offset int64
|
||||
groups []string
|
||||
orders []string
|
||||
distinct bool
|
||||
forupdate bool
|
||||
orm *orm
|
||||
mi *modelInfo
|
||||
cond *Condition
|
||||
related []string
|
||||
relDepth int
|
||||
limit int64
|
||||
offset int64
|
||||
groups []string
|
||||
orders []string
|
||||
distinct bool
|
||||
forupdate bool
|
||||
orm *orm
|
||||
ctx context.Context
|
||||
forContext bool
|
||||
}
|
||||
|
||||
var _ QuerySeter = new(querySet)
|
||||
@ -79,6 +82,15 @@ func (o querySet) Filter(expr string, args ...interface{}) QuerySeter {
|
||||
return &o
|
||||
}
|
||||
|
||||
// add raw sql to querySeter.
|
||||
func (o querySet) FilterRaw(expr string, sql string) QuerySeter {
|
||||
if o.cond == nil {
|
||||
o.cond = NewCondition()
|
||||
}
|
||||
o.cond = o.cond.Raw(expr, sql)
|
||||
return &o
|
||||
}
|
||||
|
||||
// add NOT condition to querySeter.
|
||||
func (o querySet) Exclude(expr string, args ...interface{}) QuerySeter {
|
||||
if o.cond == nil {
|
||||
@ -198,11 +210,7 @@ func (o *querySet) PrepareInsert() (Inserter, error) {
|
||||
// query all data and map to containers.
|
||||
// cols means the columns when querying.
|
||||
func (o *querySet) All(container interface{}, cols ...string) (int64, error) {
|
||||
num, err := o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
|
||||
if num == 0 {
|
||||
return 0, ErrNoRows
|
||||
}
|
||||
return num, err
|
||||
return o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
|
||||
}
|
||||
|
||||
// query one row data and map to containers.
|
||||
@ -270,6 +278,13 @@ func (o *querySet) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string)
|
||||
panic(ErrNotImplement)
|
||||
}
|
||||
|
||||
// set context to QuerySeter.
|
||||
func (o querySet) WithContext(ctx context.Context) QuerySeter {
|
||||
o.ctx = ctx
|
||||
o.forContext = true
|
||||
return &o
|
||||
}
|
||||
|
||||
// create new QuerySeter.
|
||||
func newQuerySet(orm *orm, mi *modelInfo) QuerySeter {
|
||||
o := new(querySet)
|
||||
|
@ -150,8 +150,10 @@ func (o *rawSet) setFieldValue(ind reflect.Value, value interface{}) {
|
||||
case reflect.Struct:
|
||||
if value == nil {
|
||||
ind.Set(reflect.Zero(ind.Type()))
|
||||
|
||||
} else if _, ok := ind.Interface().(time.Time); ok {
|
||||
return
|
||||
}
|
||||
switch ind.Interface().(type) {
|
||||
case time.Time:
|
||||
var str string
|
||||
switch d := value.(type) {
|
||||
case time.Time:
|
||||
@ -178,7 +180,25 @@ func (o *rawSet) setFieldValue(ind reflect.Value, value interface{}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
case sql.NullString, sql.NullInt64, sql.NullFloat64, sql.NullBool:
|
||||
indi := reflect.New(ind.Type()).Interface()
|
||||
sc, ok := indi.(sql.Scanner)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
err := sc.Scan(value)
|
||||
if err == nil {
|
||||
ind.Set(reflect.Indirect(reflect.ValueOf(sc)))
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
if value == nil {
|
||||
ind.Set(reflect.Zero(ind.Type()))
|
||||
break
|
||||
}
|
||||
ind.Set(reflect.New(ind.Type().Elem()))
|
||||
o.setFieldValue(reflect.Indirect(ind), value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,7 +378,7 @@ func (o *rawSet) QueryRow(containers ...interface{}) error {
|
||||
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
|
||||
var col string
|
||||
if col = tags["column"]; col == "" {
|
||||
col = snakeString(fe.Name)
|
||||
col = nameStrategyMap[nameStrategy](fe.Name)
|
||||
}
|
||||
if v, ok := columnsMp[col]; ok {
|
||||
value := reflect.ValueOf(v).Elem().Interface()
|
||||
@ -509,7 +529,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
|
||||
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
|
||||
var col string
|
||||
if col = tags["column"]; col == "" {
|
||||
col = snakeString(fe.Name)
|
||||
col = nameStrategyMap[nameStrategy](fe.Name)
|
||||
}
|
||||
if v, ok := columnsMp[col]; ok {
|
||||
value := reflect.ValueOf(v).Elem().Interface()
|
||||
|
156
orm/orm_test.go
156
orm/orm_test.go
@ -12,10 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@ -452,9 +455,18 @@ func TestNullDataTypes(t *testing.T) {
|
||||
throwFail(t, AssertIs(*d.Float32Ptr, float32Ptr))
|
||||
throwFail(t, AssertIs(*d.Float64Ptr, float64Ptr))
|
||||
throwFail(t, AssertIs(*d.DecimalPtr, decimalPtr))
|
||||
throwFail(t, AssertIs((*d.TimePtr).Format(testTime), timePtr.Format(testTime)))
|
||||
throwFail(t, AssertIs((*d.DatePtr).Format(testDate), datePtr.Format(testDate)))
|
||||
throwFail(t, AssertIs((*d.DateTimePtr).Format(testDateTime), dateTimePtr.Format(testDateTime)))
|
||||
throwFail(t, AssertIs((*d.TimePtr).UTC().Format(testTime), timePtr.UTC().Format(testTime)))
|
||||
throwFail(t, AssertIs((*d.DatePtr).UTC().Format(testDate), datePtr.UTC().Format(testDate)))
|
||||
throwFail(t, AssertIs((*d.DateTimePtr).UTC().Format(testDateTime), dateTimePtr.UTC().Format(testDateTime)))
|
||||
|
||||
// test support for pointer fields using RawSeter.QueryRows()
|
||||
var dnList []*DataNull
|
||||
Q := dDbBaser.TableQuote()
|
||||
num, err = dORM.Raw(fmt.Sprintf("SELECT * FROM %sdata_null%s where id=?", Q, Q), 3).QueryRows(&dnList)
|
||||
throwFailNow(t, err)
|
||||
throwFailNow(t, AssertIs(num, 1))
|
||||
equal := reflect.DeepEqual(*dnList[0], d)
|
||||
throwFailNow(t, AssertIs(equal, true))
|
||||
}
|
||||
|
||||
func TestDataCustomTypes(t *testing.T) {
|
||||
@ -896,6 +908,18 @@ func TestOperators(t *testing.T) {
|
||||
num, err = qs.Filter("id__between", []int{2, 3}).Count()
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 2))
|
||||
|
||||
num, err = qs.FilterRaw("user_name", "= 'slene'").Count()
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 1))
|
||||
|
||||
num, err = qs.FilterRaw("status", "IN (1, 2)").Count()
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 2))
|
||||
|
||||
num, err = qs.FilterRaw("profile_id", "IN (SELECT id FROM user_profile WHERE age=30)").Count()
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 1))
|
||||
}
|
||||
|
||||
func TestSetCond(t *testing.T) {
|
||||
@ -921,6 +945,11 @@ func TestSetCond(t *testing.T) {
|
||||
num, err = qs.SetCond(cond4).Count()
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 3))
|
||||
|
||||
cond5 := cond.Raw("user_name", "= 'slene'").OrNotCond(cond.And("user_name", "slene"))
|
||||
num, err = qs.SetCond(cond5).Count()
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 3))
|
||||
}
|
||||
|
||||
func TestLimit(t *testing.T) {
|
||||
@ -1008,13 +1037,13 @@ func TestAll(t *testing.T) {
|
||||
|
||||
qs = dORM.QueryTable("user")
|
||||
num, err = qs.Filter("user_name", "nothing").All(&users)
|
||||
throwFailNow(t, AssertIs(err, ErrNoRows))
|
||||
throwFailNow(t, err)
|
||||
throwFailNow(t, AssertIs(num, 0))
|
||||
|
||||
var users3 []*User
|
||||
qs = dORM.QueryTable("user")
|
||||
num, err = qs.Filter("user_name", "nothing").All(&users3)
|
||||
throwFailNow(t, AssertIs(err, ErrNoRows))
|
||||
throwFailNow(t, err)
|
||||
throwFailNow(t, AssertIs(num, 0))
|
||||
throwFailNow(t, AssertIs(users3 == nil, false))
|
||||
}
|
||||
@ -1659,6 +1688,31 @@ func TestRawQueryRow(t *testing.T) {
|
||||
throwFail(t, AssertIs(uid, 4))
|
||||
throwFail(t, AssertIs(*status, 3))
|
||||
throwFail(t, AssertIs(pid, nil))
|
||||
|
||||
// test for sql.Null* fields
|
||||
nData := &DataNull{
|
||||
NullString: sql.NullString{String: "test sql.null", Valid: true},
|
||||
NullBool: sql.NullBool{Bool: true, Valid: true},
|
||||
NullInt64: sql.NullInt64{Int64: 42, Valid: true},
|
||||
NullFloat64: sql.NullFloat64{Float64: 42.42, Valid: true},
|
||||
}
|
||||
newId, err := dORM.Insert(nData)
|
||||
throwFailNow(t, err)
|
||||
|
||||
var nd *DataNull
|
||||
query = fmt.Sprintf("SELECT * FROM %sdata_null%s where id=?", Q, Q)
|
||||
err = dORM.Raw(query, newId).QueryRow(&nd)
|
||||
throwFailNow(t, err)
|
||||
|
||||
throwFailNow(t, AssertNot(nd, nil))
|
||||
throwFail(t, AssertIs(nd.NullBool.Valid, true))
|
||||
throwFail(t, AssertIs(nd.NullBool.Bool, true))
|
||||
throwFail(t, AssertIs(nd.NullString.Valid, true))
|
||||
throwFail(t, AssertIs(nd.NullString.String, "test sql.null"))
|
||||
throwFail(t, AssertIs(nd.NullInt64.Valid, true))
|
||||
throwFail(t, AssertIs(nd.NullInt64.Int64, 42))
|
||||
throwFail(t, AssertIs(nd.NullFloat64.Valid, true))
|
||||
throwFail(t, AssertIs(nd.NullFloat64.Float64, 42.42))
|
||||
}
|
||||
|
||||
// user_profile table
|
||||
@ -1751,6 +1805,32 @@ func TestQueryRows(t *testing.T) {
|
||||
throwFailNow(t, AssertIs(l[1].UserName, "astaxie"))
|
||||
throwFailNow(t, AssertIs(l[1].Age, 30))
|
||||
|
||||
// test for sql.Null* fields
|
||||
nData := &DataNull{
|
||||
NullString: sql.NullString{String: "test sql.null", Valid: true},
|
||||
NullBool: sql.NullBool{Bool: true, Valid: true},
|
||||
NullInt64: sql.NullInt64{Int64: 42, Valid: true},
|
||||
NullFloat64: sql.NullFloat64{Float64: 42.42, Valid: true},
|
||||
}
|
||||
newId, err := dORM.Insert(nData)
|
||||
throwFailNow(t, err)
|
||||
|
||||
var nDataList []*DataNull
|
||||
query = fmt.Sprintf("SELECT * FROM %sdata_null%s where id=?", Q, Q)
|
||||
num, err = dORM.Raw(query, newId).QueryRows(&nDataList)
|
||||
throwFailNow(t, err)
|
||||
throwFailNow(t, AssertIs(num, 1))
|
||||
|
||||
nd := nDataList[0]
|
||||
throwFailNow(t, AssertNot(nd, nil))
|
||||
throwFail(t, AssertIs(nd.NullBool.Valid, true))
|
||||
throwFail(t, AssertIs(nd.NullBool.Bool, true))
|
||||
throwFail(t, AssertIs(nd.NullString.Valid, true))
|
||||
throwFail(t, AssertIs(nd.NullString.String, "test sql.null"))
|
||||
throwFail(t, AssertIs(nd.NullInt64.Valid, true))
|
||||
throwFail(t, AssertIs(nd.NullInt64.Int64, 42))
|
||||
throwFail(t, AssertIs(nd.NullFloat64.Valid, true))
|
||||
throwFail(t, AssertIs(nd.NullFloat64.Float64, 42.42))
|
||||
}
|
||||
|
||||
func TestRawValues(t *testing.T) {
|
||||
@ -1990,6 +2070,66 @@ func TestTransaction(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestTransactionIsolationLevel(t *testing.T) {
|
||||
// this test worked when database support transaction isolation level
|
||||
if IsSqlite {
|
||||
return
|
||||
}
|
||||
|
||||
o1 := NewOrm()
|
||||
o2 := NewOrm()
|
||||
|
||||
// start two transaction with isolation level repeatable read
|
||||
err := o1.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
|
||||
throwFail(t, err)
|
||||
err = o2.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
|
||||
throwFail(t, err)
|
||||
|
||||
// o1 insert tag
|
||||
var tag Tag
|
||||
tag.Name = "test-transaction"
|
||||
id, err := o1.Insert(&tag)
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(id > 0, true))
|
||||
|
||||
// o2 query tag table, no result
|
||||
num, err := o2.QueryTable("tag").Filter("name", "test-transaction").Count()
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 0))
|
||||
|
||||
// o1 commit
|
||||
o1.Commit()
|
||||
|
||||
// o2 query tag table, still no result
|
||||
num, err = o2.QueryTable("tag").Filter("name", "test-transaction").Count()
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 0))
|
||||
|
||||
// o2 commit and query tag table, get the result
|
||||
o2.Commit()
|
||||
num, err = o2.QueryTable("tag").Filter("name", "test-transaction").Count()
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 1))
|
||||
|
||||
num, err = o1.QueryTable("tag").Filter("name", "test-transaction").Delete()
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 1))
|
||||
}
|
||||
|
||||
func TestBeginTxWithContextCanceled(t *testing.T) {
|
||||
o := NewOrm()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
o.BeginTx(ctx, nil)
|
||||
id, err := o.Insert(&Tag{Name: "test-context"})
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(id > 0, true))
|
||||
|
||||
// cancel the context before commit to make it error
|
||||
cancel()
|
||||
err = o.Commit()
|
||||
throwFail(t, AssertIs(err, context.Canceled))
|
||||
}
|
||||
|
||||
func TestReadOrCreate(t *testing.T) {
|
||||
u := &User{
|
||||
UserName: "Kyle",
|
||||
@ -2260,6 +2400,7 @@ func TestIgnoreCaseTag(t *testing.T) {
|
||||
throwFail(t, AssertIs(info.fields.GetByName("Name02").column, "Name"))
|
||||
throwFail(t, AssertIs(info.fields.GetByName("Name03").column, "name"))
|
||||
}
|
||||
|
||||
func TestInsertOrUpdate(t *testing.T) {
|
||||
RegisterModel(new(User))
|
||||
user := User{UserName: "unique_username133", Status: 1, Password: "o"}
|
||||
@ -2297,6 +2438,11 @@ func TestInsertOrUpdate(t *testing.T) {
|
||||
throwFailNow(t, AssertIs(user2.Status, test.Status))
|
||||
throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password)))
|
||||
}
|
||||
|
||||
//postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values
|
||||
if IsPostgres {
|
||||
return
|
||||
}
|
||||
//test3 +
|
||||
_, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status+1")
|
||||
if err != nil {
|
||||
|
28
orm/types.go
28
orm/types.go
@ -15,6 +15,7 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"reflect"
|
||||
"time"
|
||||
@ -54,7 +55,7 @@ type Ormer interface {
|
||||
// for example:
|
||||
// user := new(User)
|
||||
// id, err = Ormer.Insert(user)
|
||||
// user must a pointer and Insert will set user's pk field
|
||||
// user must be a pointer and Insert will set user's pk field
|
||||
Insert(interface{}) (int64, error)
|
||||
// mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value")
|
||||
// if colu type is integer : can use(+-*/), string : convert(colu,"value")
|
||||
@ -106,6 +107,17 @@ type Ormer interface {
|
||||
// ...
|
||||
// err = o.Rollback()
|
||||
Begin() error
|
||||
// begin transaction with provided context and option
|
||||
// the provided context is used until the transaction is committed or rolled back.
|
||||
// if the context is canceled, the transaction will be rolled back.
|
||||
// the provided TxOptions is optional and may be nil if defaults should be used.
|
||||
// if a non-default isolation level is used that the driver doesn't support, an error will be returned.
|
||||
// for example:
|
||||
// o := NewOrm()
|
||||
// err := o.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
|
||||
// ...
|
||||
// err = o.Rollback()
|
||||
BeginTx(ctx context.Context, opts *sql.TxOptions) error
|
||||
// commit transaction
|
||||
Commit() error
|
||||
// rollback transaction
|
||||
@ -116,6 +128,7 @@ type Ormer interface {
|
||||
// // update user testing's name to slene
|
||||
Raw(query string, args ...interface{}) RawSeter
|
||||
Driver() Driver
|
||||
DBStats() *sql.DBStats
|
||||
}
|
||||
|
||||
// Inserter insert prepared statement
|
||||
@ -135,6 +148,11 @@ type QuerySeter interface {
|
||||
// // time compare
|
||||
// qs.Filter("created", time.Now())
|
||||
Filter(string, ...interface{}) QuerySeter
|
||||
// add raw sql to querySeter.
|
||||
// for example:
|
||||
// qs.FilterRaw("user_id IN (SELECT id FROM profile WHERE age>=18)")
|
||||
// //sql-> WHERE user_id IN (SELECT id FROM profile WHERE age>=18)
|
||||
FilterRaw(string, string) QuerySeter
|
||||
// add NOT condition to querySeter.
|
||||
// have the same usage as Filter
|
||||
Exclude(string, ...interface{}) QuerySeter
|
||||
@ -378,16 +396,23 @@ type RawSeter interface {
|
||||
type stmtQuerier interface {
|
||||
Close() error
|
||||
Exec(args ...interface{}) (sql.Result, error)
|
||||
//ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error)
|
||||
Query(args ...interface{}) (*sql.Rows, error)
|
||||
//QueryContext(args ...interface{}) (*sql.Rows, error)
|
||||
QueryRow(args ...interface{}) *sql.Row
|
||||
//QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
// db querier
|
||||
type dbQuerier interface {
|
||||
Prepare(query string) (*sql.Stmt, error)
|
||||
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
|
||||
Exec(query string, args ...interface{}) (sql.Result, error)
|
||||
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
|
||||
QueryRow(query string, args ...interface{}) *sql.Row
|
||||
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
// type DB interface {
|
||||
@ -401,6 +426,7 @@ type dbQuerier interface {
|
||||
// transaction beginner
|
||||
type txer interface {
|
||||
Begin() (*sql.Tx, error)
|
||||
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
|
||||
}
|
||||
|
||||
// transaction ending
|
||||
|
43
orm/utils.go
43
orm/utils.go
@ -23,6 +23,18 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type fn func(string) string
|
||||
|
||||
var (
|
||||
nameStrategyMap = map[string]fn{
|
||||
defaultNameStrategy: snakeString,
|
||||
SnakeAcronymNameStrategy: snakeStringWithAcronym,
|
||||
}
|
||||
defaultNameStrategy = "snakeString"
|
||||
SnakeAcronymNameStrategy = "snakeStringWithAcronym"
|
||||
nameStrategy = defaultNameStrategy
|
||||
)
|
||||
|
||||
// StrTo is the target string
|
||||
type StrTo string
|
||||
|
||||
@ -198,7 +210,28 @@ func ToInt64(value interface{}) (d int64) {
|
||||
return
|
||||
}
|
||||
|
||||
// snake string, XxYy to xx_yy , XxYY to xx_yy
|
||||
func snakeStringWithAcronym(s string) string {
|
||||
data := make([]byte, 0, len(s)*2)
|
||||
num := len(s)
|
||||
for i := 0; i < num; i++ {
|
||||
d := s[i]
|
||||
before := false
|
||||
after := false
|
||||
if i > 0 {
|
||||
before = s[i-1] >= 'a' && s[i-1] <= 'z'
|
||||
}
|
||||
if i+1 < num {
|
||||
after = s[i+1] >= 'a' && s[i+1] <= 'z'
|
||||
}
|
||||
if i > 0 && d >= 'A' && d <= 'Z' && (before || after) {
|
||||
data = append(data, '_')
|
||||
}
|
||||
data = append(data, d)
|
||||
}
|
||||
return strings.ToLower(string(data[:]))
|
||||
}
|
||||
|
||||
// snake string, XxYy to xx_yy , XxYY to xx_y_y
|
||||
func snakeString(s string) string {
|
||||
data := make([]byte, 0, len(s)*2)
|
||||
j := false
|
||||
@ -216,6 +249,14 @@ func snakeString(s string) string {
|
||||
return strings.ToLower(string(data[:]))
|
||||
}
|
||||
|
||||
// SetNameStrategy set different name strategy
|
||||
func SetNameStrategy(s string) {
|
||||
if SnakeAcronymNameStrategy != s {
|
||||
nameStrategy = defaultNameStrategy
|
||||
}
|
||||
nameStrategy = s
|
||||
}
|
||||
|
||||
// camel string, xx_yy to XxYy
|
||||
func camelString(s string) string {
|
||||
data := make([]byte, 0, len(s))
|
||||
|
@ -34,3 +34,37 @@ func TestCamelString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnakeString(t *testing.T) {
|
||||
camel := []string{"PicUrl", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "XyXX"}
|
||||
snake := []string{"pic_url", "hello_world", "hello_world", "hel_l_o_word", "pic_url1", "xy_x_x"}
|
||||
|
||||
answer := make(map[string]string)
|
||||
for i, v := range camel {
|
||||
answer[v] = snake[i]
|
||||
}
|
||||
|
||||
for _, v := range camel {
|
||||
res := snakeString(v)
|
||||
if res != answer[v] {
|
||||
t.Error("Unit Test Fail:", v, res, answer[v])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnakeStringWithAcronym(t *testing.T) {
|
||||
camel := []string{"ID", "PicURL", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "XyXX"}
|
||||
snake := []string{"id", "pic_url", "hello_world", "hello_world", "hel_lo_word", "pic_url1", "xy_xx"}
|
||||
|
||||
answer := make(map[string]string)
|
||||
for i, v := range camel {
|
||||
answer[v] = snake[i]
|
||||
}
|
||||
|
||||
for _, v := range camel {
|
||||
res := snakeStringWithAcronym(v)
|
||||
if res != answer[v] {
|
||||
t.Error("Unit Test Fail:", v, res, answer[v])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
214
parser.go
214
parser.go
@ -35,11 +35,11 @@ import (
|
||||
"github.com/astaxie/beego/utils"
|
||||
)
|
||||
|
||||
var globalRouterTemplate = `package routers
|
||||
var globalRouterTemplate = `package {{.routersDir}}
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/context/param"
|
||||
"github.com/astaxie/beego/context/param"{{.globalimport}}
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -52,6 +52,22 @@ var (
|
||||
commentFilename string
|
||||
pkgLastupdate map[string]int64
|
||||
genInfoList map[string][]ControllerComments
|
||||
|
||||
routerHooks = map[string]int{
|
||||
"beego.BeforeStatic": BeforeStatic,
|
||||
"beego.BeforeRouter": BeforeRouter,
|
||||
"beego.BeforeExec": BeforeExec,
|
||||
"beego.AfterExec": AfterExec,
|
||||
"beego.FinishRouter": FinishRouter,
|
||||
}
|
||||
|
||||
routerHooksMapping = map[int]string{
|
||||
BeforeStatic: "beego.BeforeStatic",
|
||||
BeforeRouter: "beego.BeforeRouter",
|
||||
BeforeExec: "beego.BeforeExec",
|
||||
AfterExec: "beego.AfterExec",
|
||||
FinishRouter: "beego.FinishRouter",
|
||||
}
|
||||
)
|
||||
|
||||
const commentPrefix = "commentsRouter_"
|
||||
@ -102,6 +118,20 @@ type parsedComment struct {
|
||||
routerPath string
|
||||
methods []string
|
||||
params map[string]parsedParam
|
||||
filters []parsedFilter
|
||||
imports []parsedImport
|
||||
}
|
||||
|
||||
type parsedImport struct {
|
||||
importPath string
|
||||
importAlias string
|
||||
}
|
||||
|
||||
type parsedFilter struct {
|
||||
pattern string
|
||||
pos int
|
||||
filter string
|
||||
params []bool
|
||||
}
|
||||
|
||||
type parsedParam struct {
|
||||
@ -126,6 +156,8 @@ func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error {
|
||||
cc.Router = parsedComment.routerPath
|
||||
cc.AllowHTTPMethods = parsedComment.methods
|
||||
cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment)
|
||||
cc.FilterComments = buildFilters(parsedComment.filters)
|
||||
cc.ImportComments = buildImports(parsedComment.imports)
|
||||
genInfoList[key] = append(genInfoList[key], cc)
|
||||
}
|
||||
}
|
||||
@ -133,6 +165,48 @@ func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildImports(pis []parsedImport) []*ControllerImportComments {
|
||||
var importComments []*ControllerImportComments
|
||||
|
||||
for _, pi := range pis {
|
||||
importComments = append(importComments, &ControllerImportComments{
|
||||
ImportPath: pi.importPath,
|
||||
ImportAlias: pi.importAlias,
|
||||
})
|
||||
}
|
||||
|
||||
return importComments
|
||||
}
|
||||
|
||||
func buildFilters(pfs []parsedFilter) []*ControllerFilterComments {
|
||||
var filterComments []*ControllerFilterComments
|
||||
|
||||
for _, pf := range pfs {
|
||||
var (
|
||||
returnOnOutput bool
|
||||
resetParams bool
|
||||
)
|
||||
|
||||
if len(pf.params) >= 1 {
|
||||
returnOnOutput = pf.params[0]
|
||||
}
|
||||
|
||||
if len(pf.params) >= 2 {
|
||||
resetParams = pf.params[1]
|
||||
}
|
||||
|
||||
filterComments = append(filterComments, &ControllerFilterComments{
|
||||
Filter: pf.filter,
|
||||
Pattern: pf.pattern,
|
||||
Pos: pf.pos,
|
||||
ReturnOnOutput: returnOnOutput,
|
||||
ResetParams: resetParams,
|
||||
})
|
||||
}
|
||||
|
||||
return filterComments
|
||||
}
|
||||
|
||||
func buildMethodParams(funcParams []*ast.Field, pc *parsedComment) []*param.MethodParam {
|
||||
result := make([]*param.MethodParam, 0, len(funcParams))
|
||||
for _, fparam := range funcParams {
|
||||
@ -181,6 +255,8 @@ var routeRegex = regexp.MustCompile(`@router\s+(\S+)(?:\s+\[(\S+)\])?`)
|
||||
func parseComment(lines []*ast.Comment) (pcs []*parsedComment, err error) {
|
||||
pcs = []*parsedComment{}
|
||||
params := map[string]parsedParam{}
|
||||
filters := []parsedFilter{}
|
||||
imports := []parsedImport{}
|
||||
|
||||
for _, c := range lines {
|
||||
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
|
||||
@ -209,9 +285,69 @@ func parseComment(lines []*ast.Comment) (pcs []*parsedComment, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range lines {
|
||||
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
|
||||
if strings.HasPrefix(t, "@Import") {
|
||||
iv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Import")))
|
||||
if len(iv) == 0 || len(iv) > 2 {
|
||||
logs.Error("Invalid @Import format. Only accepts 1 or 2 parameters")
|
||||
continue
|
||||
}
|
||||
|
||||
p := parsedImport{}
|
||||
p.importPath = iv[0]
|
||||
|
||||
if len(iv) == 2 {
|
||||
p.importAlias = iv[1]
|
||||
}
|
||||
|
||||
imports = append(imports, p)
|
||||
}
|
||||
}
|
||||
|
||||
filterLoop:
|
||||
for _, c := range lines {
|
||||
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
|
||||
if strings.HasPrefix(t, "@Filter") {
|
||||
fv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Filter")))
|
||||
if len(fv) < 3 {
|
||||
logs.Error("Invalid @Filter format. Needs at least 3 parameters")
|
||||
continue filterLoop
|
||||
}
|
||||
|
||||
p := parsedFilter{}
|
||||
p.pattern = fv[0]
|
||||
posName := fv[1]
|
||||
if pos, exists := routerHooks[posName]; exists {
|
||||
p.pos = pos
|
||||
} else {
|
||||
logs.Error("Invalid @Filter pos: ", posName)
|
||||
continue filterLoop
|
||||
}
|
||||
|
||||
p.filter = fv[2]
|
||||
fvParams := fv[3:]
|
||||
for _, fvParam := range fvParams {
|
||||
switch fvParam {
|
||||
case "true":
|
||||
p.params = append(p.params, true)
|
||||
case "false":
|
||||
p.params = append(p.params, false)
|
||||
default:
|
||||
logs.Error("Invalid @Filter param: ", fvParam)
|
||||
continue filterLoop
|
||||
}
|
||||
}
|
||||
|
||||
filters = append(filters, p)
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range lines {
|
||||
var pc = &parsedComment{}
|
||||
pc.params = params
|
||||
pc.filters = filters
|
||||
pc.imports = imports
|
||||
|
||||
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
|
||||
if strings.HasPrefix(t, "@router") {
|
||||
@ -276,8 +412,9 @@ func genRouterCode(pkgRealpath string) {
|
||||
os.Mkdir(getRouterDir(pkgRealpath), 0755)
|
||||
logs.Info("generate router from comments")
|
||||
var (
|
||||
globalinfo string
|
||||
sortKey []string
|
||||
globalinfo string
|
||||
globalimport string
|
||||
sortKey []string
|
||||
)
|
||||
for k := range genInfoList {
|
||||
sortKey = append(sortKey, k)
|
||||
@ -295,6 +432,7 @@ func genRouterCode(pkgRealpath string) {
|
||||
}
|
||||
allmethod = strings.TrimRight(allmethod, ",") + "}"
|
||||
}
|
||||
|
||||
params := "nil"
|
||||
if len(c.Params) > 0 {
|
||||
params = "[]map[string]string{"
|
||||
@ -305,6 +443,7 @@ func genRouterCode(pkgRealpath string) {
|
||||
}
|
||||
params = strings.TrimRight(params, ",") + "}"
|
||||
}
|
||||
|
||||
methodParams := "param.Make("
|
||||
if len(c.MethodParams) > 0 {
|
||||
lines := make([]string, 0, len(c.MethodParams))
|
||||
@ -316,24 +455,72 @@ func genRouterCode(pkgRealpath string) {
|
||||
",\n "
|
||||
}
|
||||
methodParams += ")"
|
||||
|
||||
imports := ""
|
||||
if len(c.ImportComments) > 0 {
|
||||
for _, i := range c.ImportComments {
|
||||
var s string
|
||||
if i.ImportAlias != "" {
|
||||
s = fmt.Sprintf(`
|
||||
%s "%s"`, i.ImportAlias, i.ImportPath)
|
||||
} else {
|
||||
s = fmt.Sprintf(`
|
||||
"%s"`, i.ImportPath)
|
||||
}
|
||||
if !strings.Contains(globalimport, s) {
|
||||
imports += s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filters := ""
|
||||
if len(c.FilterComments) > 0 {
|
||||
for _, f := range c.FilterComments {
|
||||
filters += fmt.Sprintf(` &beego.ControllerFilter{
|
||||
Pattern: "%s",
|
||||
Pos: %s,
|
||||
Filter: %s,
|
||||
ReturnOnOutput: %v,
|
||||
ResetParams: %v,
|
||||
},`, f.Pattern, routerHooksMapping[f.Pos], f.Filter, f.ReturnOnOutput, f.ResetParams)
|
||||
}
|
||||
}
|
||||
|
||||
if filters == "" {
|
||||
filters = "nil"
|
||||
} else {
|
||||
filters = fmt.Sprintf(`[]*beego.ControllerFilter{
|
||||
%s
|
||||
}`, filters)
|
||||
}
|
||||
|
||||
globalimport += imports
|
||||
|
||||
globalinfo = globalinfo + `
|
||||
beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
|
||||
beego.ControllerComments{
|
||||
Method: "` + strings.TrimSpace(c.Method) + `",
|
||||
` + "Router: `" + c.Router + "`" + `,
|
||||
AllowHTTPMethods: ` + allmethod + `,
|
||||
MethodParams: ` + methodParams + `,
|
||||
Params: ` + params + `})
|
||||
beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
|
||||
beego.ControllerComments{
|
||||
Method: "` + strings.TrimSpace(c.Method) + `",
|
||||
` + "Router: `" + c.Router + "`" + `,
|
||||
AllowHTTPMethods: ` + allmethod + `,
|
||||
MethodParams: ` + methodParams + `,
|
||||
Filters: ` + filters + `,
|
||||
Params: ` + params + `})
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
if globalinfo != "" {
|
||||
f, err := os.Create(filepath.Join(getRouterDir(pkgRealpath), commentFilename))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
f.WriteString(strings.Replace(globalRouterTemplate, "{{.globalinfo}}", globalinfo, -1))
|
||||
|
||||
routersDir := AppConfig.DefaultString("routersdir", "routers")
|
||||
content := strings.Replace(globalRouterTemplate, "{{.globalinfo}}", globalinfo, -1)
|
||||
content = strings.Replace(content, "{{.routersDir}}", routersDir, -1)
|
||||
content = strings.Replace(content, "{{.globalimport}}", globalimport, -1)
|
||||
f.WriteString(content)
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,7 +576,8 @@ func getpathTime(pkgRealpath string) (lastupdate int64, err error) {
|
||||
func getRouterDir(pkgRealpath string) string {
|
||||
dir := filepath.Dir(pkgRealpath)
|
||||
for {
|
||||
d := filepath.Join(dir, "routers")
|
||||
routersDir := AppConfig.DefaultString("routersdir", "routers")
|
||||
d := filepath.Join(dir, routersDir)
|
||||
if utils.FileExists(d) {
|
||||
return d
|
||||
}
|
||||
|
@ -72,8 +72,8 @@ import (
|
||||
// AppIDToAppSecret is used to get appsecret throw appid
|
||||
type AppIDToAppSecret func(string) string
|
||||
|
||||
// APIBaiscAuth use the basic appid/appkey as the AppIdToAppSecret
|
||||
func APIBaiscAuth(appid, appkey string) beego.FilterFunc {
|
||||
// APIBasicAuth use the basic appid/appkey as the AppIdToAppSecret
|
||||
func APIBasicAuth(appid, appkey string) beego.FilterFunc {
|
||||
ft := func(aid string) string {
|
||||
if aid == appid {
|
||||
return appkey
|
||||
@ -83,6 +83,11 @@ func APIBaiscAuth(appid, appkey string) beego.FilterFunc {
|
||||
return APISecretAuth(ft, 300)
|
||||
}
|
||||
|
||||
// APIBaiscAuth calls APIBasicAuth for previous callers
|
||||
func APIBaiscAuth(appid, appkey string) beego.FilterFunc {
|
||||
return APIBasicAuth(appid, appkey)
|
||||
}
|
||||
|
||||
// APISecretAuth use AppIdToAppSecret verify and
|
||||
func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc {
|
||||
return func(ctx *context.Context) {
|
||||
|
119
router.go
119
router.go
@ -15,12 +15,12 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -43,7 +43,7 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
routerTypeBeego = iota
|
||||
routerTypeBeego = iota
|
||||
routerTypeRESTFul
|
||||
routerTypeHandler
|
||||
)
|
||||
@ -133,14 +133,15 @@ type ControllerRegister struct {
|
||||
|
||||
// NewControllerRegister returns a new ControllerRegister.
|
||||
func NewControllerRegister() *ControllerRegister {
|
||||
cr := &ControllerRegister{
|
||||
return &ControllerRegister{
|
||||
routers: make(map[string]*Tree),
|
||||
policies: make(map[string]*Tree),
|
||||
pool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return beecontext.NewContext()
|
||||
},
|
||||
},
|
||||
}
|
||||
cr.pool.New = func() interface{} {
|
||||
return beecontext.NewContext()
|
||||
}
|
||||
return cr
|
||||
}
|
||||
|
||||
// Add controller handler and pattern rules to ControllerRegister.
|
||||
@ -277,6 +278,10 @@ func (p *ControllerRegister) Include(cList ...ControllerInterface) {
|
||||
key := t.PkgPath() + ":" + t.Name()
|
||||
if comm, ok := GlobalControllerRouter[key]; ok {
|
||||
for _, a := range comm {
|
||||
for _, f := range a.Filters {
|
||||
p.InsertFilter(f.Pattern, f.Pos, f.Filter, f.ReturnOnOutput, f.ResetParams)
|
||||
}
|
||||
|
||||
p.addWithMethodParams(a.Router, c, a.MethodParams, strings.Join(a.AllowHTTPMethods, ",")+":"+a.Method)
|
||||
}
|
||||
}
|
||||
@ -474,8 +479,7 @@ func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter Filter
|
||||
// add Filter into
|
||||
func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) (err error) {
|
||||
if pos < BeforeStatic || pos > FinishRouter {
|
||||
err = fmt.Errorf("can not find your filter position")
|
||||
return
|
||||
return errors.New("can not find your filter position")
|
||||
}
|
||||
p.enableFilter = true
|
||||
p.filters[pos] = append(p.filters[pos], mr)
|
||||
@ -505,10 +509,10 @@ func (p *ControllerRegister) URLFor(endpoint string, values ...interface{}) stri
|
||||
}
|
||||
}
|
||||
}
|
||||
controllName := strings.Join(paths[:len(paths)-1], "/")
|
||||
controllerName := strings.Join(paths[:len(paths)-1], "/")
|
||||
methodName := paths[len(paths)-1]
|
||||
for m, t := range p.routers {
|
||||
ok, url := p.geturl(t, "/", controllName, methodName, params, m)
|
||||
ok, url := p.getURL(t, "/", controllerName, methodName, params, m)
|
||||
if ok {
|
||||
return url
|
||||
}
|
||||
@ -516,17 +520,17 @@ func (p *ControllerRegister) URLFor(endpoint string, values ...interface{}) stri
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName string, params map[string]string, httpMethod string) (bool, string) {
|
||||
func (p *ControllerRegister) getURL(t *Tree, url, controllerName, methodName string, params map[string]string, httpMethod string) (bool, string) {
|
||||
for _, subtree := range t.fixrouters {
|
||||
u := path.Join(url, subtree.prefix)
|
||||
ok, u := p.geturl(subtree, u, controllName, methodName, params, httpMethod)
|
||||
ok, u := p.getURL(subtree, u, controllerName, methodName, params, httpMethod)
|
||||
if ok {
|
||||
return ok, u
|
||||
}
|
||||
}
|
||||
if t.wildcard != nil {
|
||||
u := path.Join(url, urlPlaceholder)
|
||||
ok, u := p.geturl(t.wildcard, u, controllName, methodName, params, httpMethod)
|
||||
ok, u := p.getURL(t.wildcard, u, controllerName, methodName, params, httpMethod)
|
||||
if ok {
|
||||
return ok, u
|
||||
}
|
||||
@ -534,7 +538,7 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
|
||||
for _, l := range t.leaves {
|
||||
if c, ok := l.runObject.(*ControllerInfo); ok {
|
||||
if c.routerType == routerTypeBeego &&
|
||||
strings.HasSuffix(path.Join(c.controllerType.PkgPath(), c.controllerType.Name()), controllName) {
|
||||
strings.HasSuffix(path.Join(c.controllerType.PkgPath(), c.controllerType.Name()), controllerName) {
|
||||
find := false
|
||||
if HTTPMETHOD[strings.ToUpper(methodName)] {
|
||||
if len(c.methods) == 0 {
|
||||
@ -573,18 +577,18 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
|
||||
}
|
||||
}
|
||||
}
|
||||
canskip := false
|
||||
canSkip := false
|
||||
for _, v := range l.wildcards {
|
||||
if v == ":" {
|
||||
canskip = true
|
||||
canSkip = true
|
||||
continue
|
||||
}
|
||||
if u, ok := params[v]; ok {
|
||||
delete(params, v)
|
||||
url = strings.Replace(url, urlPlaceholder, u, 1)
|
||||
} else {
|
||||
if canskip {
|
||||
canskip = false
|
||||
if canSkip {
|
||||
canSkip = false
|
||||
continue
|
||||
}
|
||||
return false, ""
|
||||
@ -593,27 +597,27 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
|
||||
return true, url + toURL(params)
|
||||
}
|
||||
var i int
|
||||
var startreg bool
|
||||
regurl := ""
|
||||
var startReg bool
|
||||
regURL := ""
|
||||
for _, v := range strings.Trim(l.regexps.String(), "^$") {
|
||||
if v == '(' {
|
||||
startreg = true
|
||||
startReg = true
|
||||
continue
|
||||
} else if v == ')' {
|
||||
startreg = false
|
||||
startReg = false
|
||||
if v, ok := params[l.wildcards[i]]; ok {
|
||||
delete(params, l.wildcards[i])
|
||||
regurl = regurl + v
|
||||
regURL = regURL + v
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else if !startreg {
|
||||
regurl = string(append([]rune(regurl), v))
|
||||
} else if !startReg {
|
||||
regURL = string(append([]rune(regURL), v))
|
||||
}
|
||||
}
|
||||
if l.regexps.MatchString(regurl) {
|
||||
ps := strings.Split(regurl, "/")
|
||||
if l.regexps.MatchString(regURL) {
|
||||
ps := strings.Split(regURL, "/")
|
||||
for _, p := range ps {
|
||||
url = strings.Replace(url, urlPlaceholder, p, 1)
|
||||
}
|
||||
@ -685,7 +689,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
|
||||
// filter wrong http method
|
||||
if !HTTPMETHOD[r.Method] {
|
||||
http.Error(rw, "Method Not Allowed", 405)
|
||||
exception("405", context)
|
||||
goto Admin
|
||||
}
|
||||
|
||||
@ -774,7 +778,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
runRouter = routerInfo.controllerType
|
||||
methodParams = routerInfo.methodParams
|
||||
method := r.Method
|
||||
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPost {
|
||||
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPut {
|
||||
method = http.MethodPut
|
||||
}
|
||||
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodDelete {
|
||||
@ -794,7 +798,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
if !isRunnable {
|
||||
//Invoke the request handler
|
||||
var execController ControllerInterface
|
||||
if routerInfo.initialize != nil {
|
||||
if routerInfo != nil && routerInfo.initialize != nil {
|
||||
execController = routerInfo.initialize()
|
||||
} else {
|
||||
vc := reflect.New(runRouter)
|
||||
@ -839,6 +843,8 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
execController.Patch()
|
||||
case http.MethodOptions:
|
||||
execController.Options()
|
||||
case http.MethodTrace:
|
||||
execController.Trace()
|
||||
default:
|
||||
if !execController.HandlerFunc(runMethod) {
|
||||
vc := reflect.ValueOf(execController)
|
||||
@ -877,56 +883,46 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
Admin:
|
||||
//admin module record QPS
|
||||
//admin module record QPS
|
||||
|
||||
statusCode := context.ResponseWriter.Status
|
||||
if statusCode == 0 {
|
||||
statusCode = 200
|
||||
}
|
||||
|
||||
logAccess(context, &startTime, statusCode)
|
||||
LogAccess(context, &startTime, statusCode)
|
||||
|
||||
timeDur := time.Since(startTime)
|
||||
context.ResponseWriter.Elapsed = timeDur
|
||||
if BConfig.Listen.EnableAdmin {
|
||||
timeDur := time.Since(startTime)
|
||||
pattern := ""
|
||||
if routerInfo != nil {
|
||||
pattern = routerInfo.pattern
|
||||
}
|
||||
|
||||
if FilterMonitorFunc(r.Method, r.URL.Path, timeDur, pattern, statusCode) {
|
||||
routerName := ""
|
||||
if runRouter != nil {
|
||||
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, runRouter.Name(), timeDur)
|
||||
} else {
|
||||
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, "", timeDur)
|
||||
routerName = runRouter.Name()
|
||||
}
|
||||
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, routerName, timeDur)
|
||||
}
|
||||
}
|
||||
|
||||
if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs {
|
||||
var devInfo string
|
||||
timeDur := time.Since(startTime)
|
||||
iswin := (runtime.GOOS == "windows")
|
||||
statusColor := logs.ColorByStatus(iswin, statusCode)
|
||||
methodColor := logs.ColorByMethod(iswin, r.Method)
|
||||
resetColor := logs.ColorByMethod(iswin, "")
|
||||
if findRouter {
|
||||
if routerInfo != nil {
|
||||
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode,
|
||||
resetColor, timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path,
|
||||
routerInfo.pattern)
|
||||
} else {
|
||||
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor,
|
||||
timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path)
|
||||
}
|
||||
} else {
|
||||
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor,
|
||||
timeDur.String(), "nomatch", methodColor, r.Method, resetColor, r.URL.Path)
|
||||
}
|
||||
if iswin {
|
||||
logs.W32Debug(devInfo)
|
||||
} else {
|
||||
logs.Debug(devInfo)
|
||||
match := map[bool]string{true: "match", false: "nomatch"}
|
||||
devInfo := fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s",
|
||||
context.Input.IP(),
|
||||
logs.ColorByStatus(statusCode), statusCode, logs.ResetColor(),
|
||||
timeDur.String(),
|
||||
match[findRouter],
|
||||
logs.ColorByMethod(r.Method), r.Method, logs.ResetColor(),
|
||||
r.URL.Path)
|
||||
if routerInfo != nil {
|
||||
devInfo += fmt.Sprintf(" r:%s", routerInfo.pattern)
|
||||
}
|
||||
|
||||
logs.Debug(devInfo)
|
||||
}
|
||||
// Call WriteHeader if status code has been set changed
|
||||
if context.Output.Status != 0 {
|
||||
@ -975,7 +971,8 @@ func toURL(params map[string]string) string {
|
||||
return strings.TrimRight(u, "&")
|
||||
}
|
||||
|
||||
func logAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) {
|
||||
// LogAccess logging info HTTP Access
|
||||
func LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) {
|
||||
//Skip logging if AccessLogs config is false
|
||||
if !BConfig.Log.AccessLogs {
|
||||
return
|
||||
|
@ -71,10 +71,6 @@ func (tc *TestController) GetEmptyBody() {
|
||||
tc.Ctx.Output.Body(res)
|
||||
}
|
||||
|
||||
type ResStatus struct {
|
||||
Code int
|
||||
Msg string
|
||||
}
|
||||
|
||||
type JSONController struct {
|
||||
Controller
|
||||
@ -475,7 +471,7 @@ func TestParamResetFilter(t *testing.T) {
|
||||
// a response header of `Splat`. The expectation here is that that Header
|
||||
// value should match what the _request's_ router set, not the filter's.
|
||||
|
||||
headers := rw.HeaderMap
|
||||
headers := rw.Result().Header
|
||||
if len(headers["Splat"]) != 1 {
|
||||
t.Errorf(
|
||||
"%s: There was an error in the test. Splat param not set in Header",
|
||||
@ -660,25 +656,16 @@ func beegoBeforeRouter1(ctx *context.Context) {
|
||||
ctx.WriteString("|BeforeRouter1")
|
||||
}
|
||||
|
||||
func beegoBeforeRouter2(ctx *context.Context) {
|
||||
ctx.WriteString("|BeforeRouter2")
|
||||
}
|
||||
|
||||
func beegoBeforeExec1(ctx *context.Context) {
|
||||
ctx.WriteString("|BeforeExec1")
|
||||
}
|
||||
|
||||
func beegoBeforeExec2(ctx *context.Context) {
|
||||
ctx.WriteString("|BeforeExec2")
|
||||
}
|
||||
|
||||
func beegoAfterExec1(ctx *context.Context) {
|
||||
ctx.WriteString("|AfterExec1")
|
||||
}
|
||||
|
||||
func beegoAfterExec2(ctx *context.Context) {
|
||||
ctx.WriteString("|AfterExec2")
|
||||
}
|
||||
|
||||
func beegoFinishRouter1(ctx *context.Context) {
|
||||
ctx.WriteString("|FinishRouter1")
|
||||
|
@ -133,7 +133,7 @@ func (lp *Provider) SessionRead(sid string) (session.Store, error) {
|
||||
// SessionExist check ledis session exist by sid
|
||||
func (lp *Provider) SessionExist(sid string) bool {
|
||||
count, _ := c.Exists([]byte(sid))
|
||||
return !(count == 0)
|
||||
return count != 0
|
||||
}
|
||||
|
||||
// SessionRegenerate generate new sid for ledis session
|
||||
|
@ -128,9 +128,12 @@ func (rp *MemProvider) SessionRead(sid string) (session.Store, error) {
|
||||
}
|
||||
}
|
||||
item, err := client.Get(sid)
|
||||
if err != nil && err == memcache.ErrCacheMiss {
|
||||
rs := &SessionStore{sid: sid, values: make(map[interface{}]interface{}), maxlifetime: rp.maxlifetime}
|
||||
return rs, nil
|
||||
if err != nil {
|
||||
if err == memcache.ErrCacheMiss {
|
||||
rs := &SessionStore{sid: sid, values: make(map[interface{}]interface{}), maxlifetime: rp.maxlifetime}
|
||||
return rs, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
var kv map[interface{}]interface{}
|
||||
if len(item.Value) == 0 {
|
||||
|
@ -170,7 +170,7 @@ func (mp *Provider) SessionExist(sid string) bool {
|
||||
row := c.QueryRow("select session_data from "+TableName+" where session_key=?", sid)
|
||||
var sessiondata []byte
|
||||
err := row.Scan(&sessiondata)
|
||||
return !(err == sql.ErrNoRows)
|
||||
return err != sql.ErrNoRows
|
||||
}
|
||||
|
||||
// SessionRegenerate generate new sid for mysql session
|
||||
|
@ -184,7 +184,7 @@ func (mp *Provider) SessionExist(sid string) bool {
|
||||
row := c.QueryRow("select session_data from session where session_key=$1", sid)
|
||||
var sessiondata []byte
|
||||
err := row.Scan(&sessiondata)
|
||||
return !(err == sql.ErrNoRows)
|
||||
return err != sql.ErrNoRows
|
||||
}
|
||||
|
||||
// SessionRegenerate generate new sid for postgresql session
|
||||
|
234
session/redis_sentinel/sess_redis_sentinel.go
Normal file
234
session/redis_sentinel/sess_redis_sentinel.go
Normal file
@ -0,0 +1,234 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package redis for session provider
|
||||
//
|
||||
// depend on github.com/go-redis/redis
|
||||
//
|
||||
// go install github.com/go-redis/redis
|
||||
//
|
||||
// Usage:
|
||||
// import(
|
||||
// _ "github.com/astaxie/beego/session/redis_sentinel"
|
||||
// "github.com/astaxie/beego/session"
|
||||
// )
|
||||
//
|
||||
// func init() {
|
||||
// globalSessions, _ = session.NewManager("redis_sentinel", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:26379;127.0.0.2:26379"}``)
|
||||
// go globalSessions.GC()
|
||||
// }
|
||||
//
|
||||
// more detail about params: please check the notes on the function SessionInit in this package
|
||||
package redis_sentinel
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/session"
|
||||
"github.com/go-redis/redis"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var redispder = &Provider{}
|
||||
|
||||
// DefaultPoolSize redis_sentinel default pool size
|
||||
var DefaultPoolSize = 100
|
||||
|
||||
// SessionStore redis_sentinel session store
|
||||
type SessionStore struct {
|
||||
p *redis.Client
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
values map[interface{}]interface{}
|
||||
maxlifetime int64
|
||||
}
|
||||
|
||||
// Set value in redis_sentinel session
|
||||
func (rs *SessionStore) Set(key, value interface{}) error {
|
||||
rs.lock.Lock()
|
||||
defer rs.lock.Unlock()
|
||||
rs.values[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get value in redis_sentinel session
|
||||
func (rs *SessionStore) Get(key interface{}) interface{} {
|
||||
rs.lock.RLock()
|
||||
defer rs.lock.RUnlock()
|
||||
if v, ok := rs.values[key]; ok {
|
||||
return v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete value in redis_sentinel session
|
||||
func (rs *SessionStore) Delete(key interface{}) error {
|
||||
rs.lock.Lock()
|
||||
defer rs.lock.Unlock()
|
||||
delete(rs.values, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush clear all values in redis_sentinel session
|
||||
func (rs *SessionStore) Flush() error {
|
||||
rs.lock.Lock()
|
||||
defer rs.lock.Unlock()
|
||||
rs.values = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// SessionID get redis_sentinel session id
|
||||
func (rs *SessionStore) SessionID() string {
|
||||
return rs.sid
|
||||
}
|
||||
|
||||
// SessionRelease save session values to redis_sentinel
|
||||
func (rs *SessionStore) SessionRelease(w http.ResponseWriter) {
|
||||
b, err := session.EncodeGob(rs.values)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c := rs.p
|
||||
c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime)*time.Second)
|
||||
}
|
||||
|
||||
// Provider redis_sentinel session provider
|
||||
type Provider struct {
|
||||
maxlifetime int64
|
||||
savePath string
|
||||
poolsize int
|
||||
password string
|
||||
dbNum int
|
||||
poollist *redis.Client
|
||||
masterName string
|
||||
}
|
||||
|
||||
// SessionInit init redis_sentinel session
|
||||
// savepath like redis sentinel addr,pool size,password,dbnum,masterName
|
||||
// e.g. 127.0.0.1:26379;127.0.0.2:26379,100,1qaz2wsx,0,mymaster
|
||||
func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
|
||||
rp.maxlifetime = maxlifetime
|
||||
configs := strings.Split(savePath, ",")
|
||||
if len(configs) > 0 {
|
||||
rp.savePath = configs[0]
|
||||
}
|
||||
if len(configs) > 1 {
|
||||
poolsize, err := strconv.Atoi(configs[1])
|
||||
if err != nil || poolsize < 0 {
|
||||
rp.poolsize = DefaultPoolSize
|
||||
} else {
|
||||
rp.poolsize = poolsize
|
||||
}
|
||||
} else {
|
||||
rp.poolsize = DefaultPoolSize
|
||||
}
|
||||
if len(configs) > 2 {
|
||||
rp.password = configs[2]
|
||||
}
|
||||
if len(configs) > 3 {
|
||||
dbnum, err := strconv.Atoi(configs[3])
|
||||
if err != nil || dbnum < 0 {
|
||||
rp.dbNum = 0
|
||||
} else {
|
||||
rp.dbNum = dbnum
|
||||
}
|
||||
} else {
|
||||
rp.dbNum = 0
|
||||
}
|
||||
if len(configs) > 4 {
|
||||
if configs[4] != "" {
|
||||
rp.masterName = configs[4]
|
||||
} else {
|
||||
rp.masterName = "mymaster"
|
||||
}
|
||||
} else {
|
||||
rp.masterName = "mymaster"
|
||||
}
|
||||
|
||||
rp.poollist = redis.NewFailoverClient(&redis.FailoverOptions{
|
||||
SentinelAddrs: strings.Split(rp.savePath, ";"),
|
||||
Password: rp.password,
|
||||
PoolSize: rp.poolsize,
|
||||
DB: rp.dbNum,
|
||||
MasterName: rp.masterName,
|
||||
})
|
||||
|
||||
return rp.poollist.Ping().Err()
|
||||
}
|
||||
|
||||
// SessionRead read redis_sentinel session by sid
|
||||
func (rp *Provider) SessionRead(sid string) (session.Store, error) {
|
||||
var kv map[interface{}]interface{}
|
||||
kvs, err := rp.poollist.Get(sid).Result()
|
||||
if err != nil && err != redis.Nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(kvs) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
if kv, err = session.DecodeGob([]byte(kvs)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
rs := &SessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// SessionExist check redis_sentinel session exist by sid
|
||||
func (rp *Provider) SessionExist(sid string) bool {
|
||||
c := rp.poollist
|
||||
if existed, err := c.Exists(sid).Result(); err != nil || existed == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SessionRegenerate generate new sid for redis_sentinel session
|
||||
func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
|
||||
c := rp.poollist
|
||||
|
||||
if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 {
|
||||
// oldsid doesn't exists, set the new sid directly
|
||||
// ignore error here, since if it return error
|
||||
// the existed value will be 0
|
||||
c.Set(sid, "", time.Duration(rp.maxlifetime)*time.Second)
|
||||
} else {
|
||||
c.Rename(oldsid, sid)
|
||||
c.Expire(sid, time.Duration(rp.maxlifetime)*time.Second)
|
||||
}
|
||||
return rp.SessionRead(sid)
|
||||
}
|
||||
|
||||
// SessionDestroy delete redis session by id
|
||||
func (rp *Provider) SessionDestroy(sid string) error {
|
||||
c := rp.poollist
|
||||
c.Del(sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SessionGC Impelment method, no used.
|
||||
func (rp *Provider) SessionGC() {
|
||||
}
|
||||
|
||||
// SessionAll return all activeSession
|
||||
func (rp *Provider) SessionAll() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
session.Register("redis_sentinel", redispder)
|
||||
}
|
90
session/redis_sentinel/sess_redis_sentinel_test.go
Normal file
90
session/redis_sentinel/sess_redis_sentinel_test.go
Normal file
@ -0,0 +1,90 @@
|
||||
package redis_sentinel
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/astaxie/beego/session"
|
||||
)
|
||||
|
||||
func TestRedisSentinel(t *testing.T) {
|
||||
sessionConfig := &session.ManagerConfig{
|
||||
CookieName: "gosessionid",
|
||||
EnableSetCookie: true,
|
||||
Gclifetime: 3600,
|
||||
Maxlifetime: 3600,
|
||||
Secure: false,
|
||||
CookieLifeTime: 3600,
|
||||
ProviderConfig: "127.0.0.1:6379,100,,0,master",
|
||||
}
|
||||
globalSessions, e := session.NewManager("redis_sentinel", sessionConfig)
|
||||
if e != nil {
|
||||
t.Log(e)
|
||||
return
|
||||
}
|
||||
//todo test if e==nil
|
||||
go globalSessions.GC()
|
||||
|
||||
r, _ := http.NewRequest("GET", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
sess, err := globalSessions.SessionStart(w, r)
|
||||
if err != nil {
|
||||
t.Fatal("session start failed:", err)
|
||||
}
|
||||
defer sess.SessionRelease(w)
|
||||
|
||||
// SET AND GET
|
||||
err = sess.Set("username", "astaxie")
|
||||
if err != nil {
|
||||
t.Fatal("set username failed:", err)
|
||||
}
|
||||
username := sess.Get("username")
|
||||
if username != "astaxie" {
|
||||
t.Fatal("get username failed")
|
||||
}
|
||||
|
||||
// DELETE
|
||||
err = sess.Delete("username")
|
||||
if err != nil {
|
||||
t.Fatal("delete username failed:", err)
|
||||
}
|
||||
username = sess.Get("username")
|
||||
if username != nil {
|
||||
t.Fatal("delete username failed")
|
||||
}
|
||||
|
||||
// FLUSH
|
||||
err = sess.Set("username", "astaxie")
|
||||
if err != nil {
|
||||
t.Fatal("set failed:", err)
|
||||
}
|
||||
err = sess.Set("password", "1qaz2wsx")
|
||||
if err != nil {
|
||||
t.Fatal("set failed:", err)
|
||||
}
|
||||
username = sess.Get("username")
|
||||
if username != "astaxie" {
|
||||
t.Fatal("get username failed")
|
||||
}
|
||||
password := sess.Get("password")
|
||||
if password != "1qaz2wsx" {
|
||||
t.Fatal("get password failed")
|
||||
}
|
||||
err = sess.Flush()
|
||||
if err != nil {
|
||||
t.Fatal("flush failed:", err)
|
||||
}
|
||||
username = sess.Get("username")
|
||||
if username != nil {
|
||||
t.Fatal("flush failed")
|
||||
}
|
||||
password = sess.Get("password")
|
||||
if password != nil {
|
||||
t.Fatal("flush failed")
|
||||
}
|
||||
|
||||
sess.SessionRelease(w)
|
||||
|
||||
}
|
@ -19,8 +19,10 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"errors"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -127,6 +129,12 @@ func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error {
|
||||
// if file is not exist, create it.
|
||||
// the file path is generated from sid string.
|
||||
func (fp *FileProvider) SessionRead(sid string) (Store, error) {
|
||||
if strings.ContainsAny(sid, "./") {
|
||||
return nil, nil
|
||||
}
|
||||
if len(sid) < 2 {
|
||||
return nil, errors.New("length of the sid is less than 2")
|
||||
}
|
||||
filepder.lock.Lock()
|
||||
defer filepder.lock.Unlock()
|
||||
|
||||
|
@ -81,6 +81,15 @@ func Register(name string, provide Provider) {
|
||||
provides[name] = provide
|
||||
}
|
||||
|
||||
//GetProvider
|
||||
func GetProvider(name string) (Provider, error) {
|
||||
provider, ok := provides[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", name)
|
||||
}
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// ManagerConfig define the session config
|
||||
type ManagerConfig struct {
|
||||
CookieName string `json:"cookieName"`
|
||||
@ -96,6 +105,7 @@ type ManagerConfig struct {
|
||||
EnableSidInHTTPHeader bool `json:"EnableSidInHTTPHeader"`
|
||||
SessionNameInHTTPHeader string `json:"SessionNameInHTTPHeader"`
|
||||
EnableSidInURLQuery bool `json:"EnableSidInURLQuery"`
|
||||
SessionIDPrefix string `json:"sessionIDPrefix"`
|
||||
}
|
||||
|
||||
// Manager contains Provider and its configuration.
|
||||
@ -153,6 +163,11 @@ func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetProvider return current manager's provider
|
||||
func (manager *Manager) GetProvider() Provider {
|
||||
return manager.provider
|
||||
}
|
||||
|
||||
// getSid retrieves session identifier from HTTP Request.
|
||||
// First try to retrieve id by reading from cookie, session cookie name is configurable,
|
||||
// if not exist, then retrieve id from querying parameters.
|
||||
@ -331,7 +346,7 @@ func (manager *Manager) sessionID() (string, error) {
|
||||
if n != len(b) || err != nil {
|
||||
return "", fmt.Errorf("Could not successfully read from the system CSPRNG")
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
return manager.config.SessionIDPrefix + hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// Set cookie with https.
|
||||
|
@ -178,7 +178,7 @@ func searchFile(ctx *context.Context) (string, os.FileInfo, error) {
|
||||
if !strings.Contains(requestPath, prefix) {
|
||||
continue
|
||||
}
|
||||
if len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' {
|
||||
if prefix != "/" && len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' {
|
||||
continue
|
||||
}
|
||||
filePath := path.Join(staticDir, requestPath[len(prefix):])
|
||||
|
72
template.go
72
template.go
@ -20,6 +20,7 @@ import (
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@ -37,9 +38,10 @@ var (
|
||||
beeViewPathTemplates = make(map[string]map[string]*template.Template)
|
||||
templatesLock sync.RWMutex
|
||||
// beeTemplateExt stores the template extension which will build
|
||||
beeTemplateExt = []string{"tpl", "html"}
|
||||
beeTemplateExt = []string{"tpl", "html", "gohtml"}
|
||||
// beeTemplatePreprocessors stores associations of extension -> preprocessor handler
|
||||
beeTemplateEngines = map[string]templatePreProcessor{}
|
||||
beeTemplateFS = defaultFSFunc
|
||||
)
|
||||
|
||||
// ExecuteTemplate applies the template with name to the specified data object,
|
||||
@ -181,12 +183,17 @@ func lockViewPaths() {
|
||||
// BuildTemplate will build all template files in a directory.
|
||||
// it makes beego can render any template file in view directory.
|
||||
func BuildTemplate(dir string, files ...string) error {
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
var err error
|
||||
fs := beeTemplateFS()
|
||||
f, err := fs.Open(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return errors.New("dir open err")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
beeTemplates, ok := beeViewPathTemplates[dir]
|
||||
if !ok {
|
||||
panic("Unknown view path: " + dir)
|
||||
@ -195,11 +202,11 @@ func BuildTemplate(dir string, files ...string) error {
|
||||
root: dir,
|
||||
files: make(map[string][]string),
|
||||
}
|
||||
err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
|
||||
err = Walk(fs, dir, func(path string, f os.FileInfo, err error) error {
|
||||
return self.visit(path, f, err)
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("filepath.Walk() returned %v\n", err)
|
||||
fmt.Printf("Walk() returned %v\n", err)
|
||||
return err
|
||||
}
|
||||
buildAllFiles := len(files) == 0
|
||||
@ -210,11 +217,11 @@ func BuildTemplate(dir string, files ...string) error {
|
||||
ext := filepath.Ext(file)
|
||||
var t *template.Template
|
||||
if len(ext) == 0 {
|
||||
t, err = getTemplate(self.root, file, v...)
|
||||
t, err = getTemplate(self.root, fs, file, v...)
|
||||
} else if fn, ok := beeTemplateEngines[ext[1:]]; ok {
|
||||
t, err = fn(self.root, file, beegoTplFuncMap)
|
||||
} else {
|
||||
t, err = getTemplate(self.root, file, v...)
|
||||
t, err = getTemplate(self.root, fs, file, v...)
|
||||
}
|
||||
if err != nil {
|
||||
logs.Error("parse template err:", file, err)
|
||||
@ -229,20 +236,23 @@ func BuildTemplate(dir string, files ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTplDeep(root, file, parent string, t *template.Template) (*template.Template, [][]string, error) {
|
||||
func getTplDeep(root string, fs http.FileSystem, file string, parent string, t *template.Template) (*template.Template, [][]string, error) {
|
||||
var fileAbsPath string
|
||||
var rParent string
|
||||
if filepath.HasPrefix(file, "../") {
|
||||
var err error
|
||||
if strings.HasPrefix(file, "../") {
|
||||
rParent = filepath.Join(filepath.Dir(parent), file)
|
||||
fileAbsPath = filepath.Join(root, filepath.Dir(parent), file)
|
||||
} else {
|
||||
rParent = file
|
||||
fileAbsPath = filepath.Join(root, file)
|
||||
}
|
||||
if e := utils.FileExists(fileAbsPath); !e {
|
||||
f, err := fs.Open(fileAbsPath)
|
||||
if err != nil {
|
||||
panic("can't find template file:" + file)
|
||||
}
|
||||
data, err := ioutil.ReadFile(fileAbsPath)
|
||||
defer f.Close()
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, [][]string{}, err
|
||||
}
|
||||
@ -261,7 +271,7 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp
|
||||
if !HasTemplateExt(m[1]) {
|
||||
continue
|
||||
}
|
||||
_, _, err = getTplDeep(root, m[1], rParent, t)
|
||||
_, _, err = getTplDeep(root, fs, m[1], rParent, t)
|
||||
if err != nil {
|
||||
return nil, [][]string{}, err
|
||||
}
|
||||
@ -270,14 +280,14 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp
|
||||
return t, allSub, nil
|
||||
}
|
||||
|
||||
func getTemplate(root, file string, others ...string) (t *template.Template, err error) {
|
||||
func getTemplate(root string, fs http.FileSystem, file string, others ...string) (t *template.Template, err error) {
|
||||
t = template.New(file).Delims(BConfig.WebConfig.TemplateLeft, BConfig.WebConfig.TemplateRight).Funcs(beegoTplFuncMap)
|
||||
var subMods [][]string
|
||||
t, subMods, err = getTplDeep(root, file, "", t)
|
||||
t, subMods, err = getTplDeep(root, fs, file, "", t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err = _getTemplate(t, root, subMods, others...)
|
||||
t, err = _getTemplate(t, root, fs, subMods, others...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -285,7 +295,7 @@ func getTemplate(root, file string, others ...string) (t *template.Template, err
|
||||
return
|
||||
}
|
||||
|
||||
func _getTemplate(t0 *template.Template, root string, subMods [][]string, others ...string) (t *template.Template, err error) {
|
||||
func _getTemplate(t0 *template.Template, root string, fs http.FileSystem, subMods [][]string, others ...string) (t *template.Template, err error) {
|
||||
t = t0
|
||||
for _, m := range subMods {
|
||||
if len(m) == 2 {
|
||||
@ -297,11 +307,11 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others
|
||||
for _, otherFile := range others {
|
||||
if otherFile == m[1] {
|
||||
var subMods1 [][]string
|
||||
t, subMods1, err = getTplDeep(root, otherFile, "", t)
|
||||
t, subMods1, err = getTplDeep(root, fs, otherFile, "", t)
|
||||
if err != nil {
|
||||
logs.Trace("template parse file err:", err)
|
||||
} else if len(subMods1) > 0 {
|
||||
t, err = _getTemplate(t, root, subMods1, others...)
|
||||
t, err = _getTemplate(t, root, fs, subMods1, others...)
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -310,8 +320,16 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others
|
||||
for _, otherFile := range others {
|
||||
var data []byte
|
||||
fileAbsPath := filepath.Join(root, otherFile)
|
||||
data, err = ioutil.ReadFile(fileAbsPath)
|
||||
f, err := fs.Open(fileAbsPath)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
logs.Trace("template file parse error, not success open file:", err)
|
||||
continue
|
||||
}
|
||||
data, err = ioutil.ReadAll(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
logs.Trace("template file parse error, not success read file:", err)
|
||||
continue
|
||||
}
|
||||
reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"")
|
||||
@ -319,11 +337,14 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others
|
||||
for _, sub := range allSub {
|
||||
if len(sub) == 2 && sub[1] == m[1] {
|
||||
var subMods1 [][]string
|
||||
t, subMods1, err = getTplDeep(root, otherFile, "", t)
|
||||
t, subMods1, err = getTplDeep(root, fs, otherFile, "", t)
|
||||
if err != nil {
|
||||
logs.Trace("template parse file err:", err)
|
||||
} else if len(subMods1) > 0 {
|
||||
t, err = _getTemplate(t, root, subMods1, others...)
|
||||
t, err = _getTemplate(t, root, fs, subMods1, others...)
|
||||
if err != nil {
|
||||
logs.Trace("template parse file err:", err)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -335,6 +356,17 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others
|
||||
return
|
||||
}
|
||||
|
||||
type templateFSFunc func() http.FileSystem
|
||||
|
||||
func defaultFSFunc() http.FileSystem {
|
||||
return FileSystem{}
|
||||
}
|
||||
|
||||
// SetTemplateFSFunc set default filesystem function
|
||||
func SetTemplateFSFunc(fnt templateFSFunc) {
|
||||
beeTemplateFS = fnt
|
||||
}
|
||||
|
||||
// SetViewsPath sets view directory path in beego application.
|
||||
func SetViewsPath(path string) *App {
|
||||
BConfig.WebConfig.ViewsPath = path
|
||||
|
@ -16,6 +16,9 @@ package beego
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/astaxie/beego/testdata"
|
||||
"github.com/elazarl/go-bindata-assetfs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@ -256,3 +259,58 @@ func TestTemplateLayout(t *testing.T) {
|
||||
}
|
||||
os.RemoveAll(dir)
|
||||
}
|
||||
|
||||
type TestingFileSystem struct {
|
||||
assetfs *assetfs.AssetFS
|
||||
}
|
||||
|
||||
func (d TestingFileSystem) Open(name string) (http.File, error) {
|
||||
return d.assetfs.Open(name)
|
||||
}
|
||||
|
||||
var outputBinData = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>beego welcome template</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<h1>Hello, blocks!</h1>
|
||||
|
||||
|
||||
<h1>Hello, astaxie!</h1>
|
||||
|
||||
|
||||
|
||||
<h2>Hello</h2>
|
||||
<p> This is SomeVar: val</p>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
func TestFsBinData(t *testing.T) {
|
||||
SetTemplateFSFunc(func() http.FileSystem {
|
||||
return TestingFileSystem{&assetfs.AssetFS{Asset: testdata.Asset, AssetDir: testdata.AssetDir, AssetInfo: testdata.AssetInfo}}
|
||||
})
|
||||
dir := "views"
|
||||
if err := AddViewPath("views"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
beeTemplates := beeViewPathTemplates[dir]
|
||||
if len(beeTemplates) != 3 {
|
||||
t.Fatalf("should be 3 but got %v", len(beeTemplates))
|
||||
}
|
||||
if err := beeTemplates["index.tpl"].ExecuteTemplate(os.Stdout, "index.tpl", map[string]string{"Title": "Hello", "SomeVar": "val"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := bytes.NewBufferString("")
|
||||
if err := beeTemplates["index.tpl"].ExecuteTemplate(out, "index.tpl", map[string]string{"Title": "Hello", "SomeVar": "val"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if out.String() != outputBinData {
|
||||
t.Log(out.String())
|
||||
t.Fatal("Compare failed")
|
||||
}
|
||||
}
|
||||
|
@ -55,21 +55,21 @@ func Substr(s string, start, length int) string {
|
||||
// HTML2str returns escaping text convert from html.
|
||||
func HTML2str(html string) string {
|
||||
|
||||
re, _ := regexp.Compile(`\<[\S\s]+?\>`)
|
||||
re := regexp.MustCompile(`\<[\S\s]+?\>`)
|
||||
html = re.ReplaceAllStringFunc(html, strings.ToLower)
|
||||
|
||||
//remove STYLE
|
||||
re, _ = regexp.Compile(`\<style[\S\s]+?\</style\>`)
|
||||
re = regexp.MustCompile(`\<style[\S\s]+?\</style\>`)
|
||||
html = re.ReplaceAllString(html, "")
|
||||
|
||||
//remove SCRIPT
|
||||
re, _ = regexp.Compile(`\<script[\S\s]+?\</script\>`)
|
||||
re = regexp.MustCompile(`\<script[\S\s]+?\</script\>`)
|
||||
html = re.ReplaceAllString(html, "")
|
||||
|
||||
re, _ = regexp.Compile(`\<[\S\s]+?\>`)
|
||||
re = regexp.MustCompile(`\<[\S\s]+?\>`)
|
||||
html = re.ReplaceAllString(html, "\n")
|
||||
|
||||
re, _ = regexp.Compile(`\s{2,}`)
|
||||
re = regexp.MustCompile(`\s{2,}`)
|
||||
html = re.ReplaceAllString(html, "\n")
|
||||
|
||||
return strings.TrimSpace(html)
|
||||
@ -85,24 +85,24 @@ func DateFormat(t time.Time, layout string) (datestring string) {
|
||||
var datePatterns = []string{
|
||||
// year
|
||||
"Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003
|
||||
"y", "06", //A two digit representation of a year Examples: 99 or 03
|
||||
"y", "06", //A two digit representation of a year Examples: 99 or 03
|
||||
|
||||
// month
|
||||
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12
|
||||
"n", "1", // Numeric representation of a month, without leading zeros 1 through 12
|
||||
"M", "Jan", // A short textual representation of a month, three letters Jan through Dec
|
||||
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12
|
||||
"n", "1", // Numeric representation of a month, without leading zeros 1 through 12
|
||||
"M", "Jan", // A short textual representation of a month, three letters Jan through Dec
|
||||
"F", "January", // A full textual representation of a month, such as January or March January through December
|
||||
|
||||
// day
|
||||
"d", "02", // Day of the month, 2 digits with leading zeros 01 to 31
|
||||
"j", "2", // Day of the month without leading zeros 1 to 31
|
||||
"j", "2", // Day of the month without leading zeros 1 to 31
|
||||
|
||||
// week
|
||||
"D", "Mon", // A textual representation of a day, three letters Mon through Sun
|
||||
"D", "Mon", // A textual representation of a day, three letters Mon through Sun
|
||||
"l", "Monday", // A full textual representation of the day of the week Sunday through Saturday
|
||||
|
||||
// time
|
||||
"g", "3", // 12-hour format of an hour without leading zeros 1 through 12
|
||||
"g", "3", // 12-hour format of an hour without leading zeros 1 through 12
|
||||
"G", "15", // 24-hour format of an hour without leading zeros 0 through 23
|
||||
"h", "03", // 12-hour format of an hour with leading zeros 01 through 12
|
||||
"H", "15", // 24-hour format of an hour with leading zeros 00 through 23
|
||||
@ -172,7 +172,7 @@ func GetConfig(returnType, key string, defaultVal interface{}) (value interface{
|
||||
case "DIY":
|
||||
value, err = AppConfig.DIY(key)
|
||||
default:
|
||||
err = errors.New("Config keys must be of type String, Bool, Int, Int64, Float, or DIY")
|
||||
err = errors.New("config keys must be of type String, Bool, Int, Int64, Float, or DIY")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -297,9 +297,21 @@ func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) e
|
||||
tag = tags[0]
|
||||
}
|
||||
|
||||
value := form.Get(tag)
|
||||
if len(value) == 0 {
|
||||
continue
|
||||
formValues := form[tag]
|
||||
var value string
|
||||
if len(formValues) == 0 {
|
||||
defaultValue := fieldT.Tag.Get("default")
|
||||
if defaultValue != "" {
|
||||
value = defaultValue
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(formValues) == 1 {
|
||||
value = formValues[0]
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch fieldT.Type.Kind() {
|
||||
@ -349,6 +361,8 @@ func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) e
|
||||
if len(value) >= 25 {
|
||||
value = value[:25]
|
||||
t, err = time.ParseInLocation(time.RFC3339, value, time.Local)
|
||||
} else if strings.HasSuffix(strings.ToUpper(value), "Z") {
|
||||
t, err = time.ParseInLocation(time.RFC3339, value, time.Local)
|
||||
} else if len(value) >= 19 {
|
||||
if strings.Contains(value, "T") {
|
||||
value = value[:19]
|
||||
@ -692,7 +706,7 @@ func ge(arg1, arg2 interface{}) (bool, error) {
|
||||
|
||||
// MapGet getting value from map by keys
|
||||
// usage:
|
||||
// Data["m"] = map[string]interface{} {
|
||||
// Data["m"] = M{
|
||||
// "a": 1,
|
||||
// "1": map[string]float64{
|
||||
// "c": 4,
|
||||
|
@ -111,7 +111,7 @@ func TestHtmlunquote(t *testing.T) {
|
||||
|
||||
func TestParseForm(t *testing.T) {
|
||||
type ExtendInfo struct {
|
||||
Hobby string `form:"hobby"`
|
||||
Hobby []string `form:"hobby"`
|
||||
Memo string
|
||||
}
|
||||
|
||||
@ -146,7 +146,7 @@ func TestParseForm(t *testing.T) {
|
||||
"date": []string{"2014-11-12"},
|
||||
"organization": []string{"beego"},
|
||||
"title": []string{"CXO"},
|
||||
"hobby": []string{"Basketball"},
|
||||
"hobby": []string{"", "Basketball", "Football"},
|
||||
"memo": []string{"nothing"},
|
||||
}
|
||||
if err := ParseForm(form, u); err == nil {
|
||||
@ -186,8 +186,14 @@ func TestParseForm(t *testing.T) {
|
||||
if u.Title != "CXO" {
|
||||
t.Errorf("Title should equal `CXO`, but got `%v`", u.Title)
|
||||
}
|
||||
if u.Hobby != "Basketball" {
|
||||
t.Errorf("Hobby should equal `Basketball`, but got `%v`", u.Hobby)
|
||||
if u.Hobby[0] != "" {
|
||||
t.Errorf("Hobby should equal ``, but got `%v`", u.Hobby[0])
|
||||
}
|
||||
if u.Hobby[1] != "Basketball" {
|
||||
t.Errorf("Hobby should equal `Basketball`, but got `%v`", u.Hobby[1])
|
||||
}
|
||||
if u.Hobby[2] != "Football" {
|
||||
t.Errorf("Hobby should equal `Football`, but got `%v`", u.Hobby[2])
|
||||
}
|
||||
if len(u.Memo) != 0 {
|
||||
t.Errorf("Memo's length should equal 0 but got %v", len(u.Memo))
|
||||
@ -197,7 +203,6 @@ func TestParseForm(t *testing.T) {
|
||||
func TestRenderForm(t *testing.T) {
|
||||
type user struct {
|
||||
ID int `form:"-"`
|
||||
tag string `form:"tag"`
|
||||
Name interface{} `form:"username"`
|
||||
Age int `form:"age,text,年龄:"`
|
||||
Sex string
|
||||
@ -329,7 +334,7 @@ func TestMapGet(t *testing.T) {
|
||||
}
|
||||
|
||||
// test 2 level map
|
||||
m2 := map[string]interface{}{
|
||||
m2 := M{
|
||||
"1": map[string]float64{
|
||||
"2": 3.5,
|
||||
},
|
||||
@ -344,11 +349,11 @@ func TestMapGet(t *testing.T) {
|
||||
}
|
||||
|
||||
// test 5 level map
|
||||
m5 := map[string]interface{}{
|
||||
"1": map[string]interface{}{
|
||||
"2": map[string]interface{}{
|
||||
"3": map[string]interface{}{
|
||||
"4": map[string]interface{}{
|
||||
m5 := M{
|
||||
"1": M{
|
||||
"2": M{
|
||||
"3": M{
|
||||
"4": M{
|
||||
"5": 1.2,
|
||||
},
|
||||
},
|
||||
|
2
testdata/Makefile
vendored
Normal file
2
testdata/Makefile
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
build_view:
|
||||
$(GOPATH)/bin/go-bindata-assetfs -pkg testdata views/...
|
296
testdata/bindata.go
vendored
Normal file
296
testdata/bindata.go
vendored
Normal file
@ -0,0 +1,296 @@
|
||||
// Code generated by go-bindata.
|
||||
// sources:
|
||||
// views/blocks/block.tpl
|
||||
// views/header.tpl
|
||||
// views/index.tpl
|
||||
// DO NOT EDIT!
|
||||
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"github.com/elazarl/go-bindata-assetfs"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, gz)
|
||||
clErr := gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
if clErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
type bindataFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
func (fi bindataFileInfo) Name() string {
|
||||
return fi.name
|
||||
}
|
||||
func (fi bindataFileInfo) Size() int64 {
|
||||
return fi.size
|
||||
}
|
||||
func (fi bindataFileInfo) Mode() os.FileMode {
|
||||
return fi.mode
|
||||
}
|
||||
func (fi bindataFileInfo) ModTime() time.Time {
|
||||
return fi.modTime
|
||||
}
|
||||
func (fi bindataFileInfo) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
func (fi bindataFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _viewsBlocksBlockTpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xaa\xae\x4e\x49\x4d\xcb\xcc\x4b\x55\x50\x4a\xca\xc9\x4f\xce\x56\xaa\xad\xe5\xb2\xc9\x30\xb4\xf3\x48\xcd\xc9\xc9\xd7\x51\x00\x8b\x15\x2b\xda\xe8\x67\x18\xda\x71\x55\x57\xa7\xe6\xa5\xd4\xd6\x02\x02\x00\x00\xff\xff\xfd\xa1\x7a\xf6\x32\x00\x00\x00")
|
||||
|
||||
func viewsBlocksBlockTplBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_viewsBlocksBlockTpl,
|
||||
"views/blocks/block.tpl",
|
||||
)
|
||||
}
|
||||
|
||||
func viewsBlocksBlockTpl() (*asset, error) {
|
||||
bytes, err := viewsBlocksBlockTplBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "views/blocks/block.tpl", size: 50, mode: os.FileMode(436), modTime: time.Unix(1541431067, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _viewsHeaderTpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xaa\xae\x4e\x49\x4d\xcb\xcc\x4b\x55\x50\xca\x48\x4d\x4c\x49\x2d\x52\xaa\xad\xe5\xb2\xc9\x30\xb4\xf3\x48\xcd\xc9\xc9\xd7\x51\x48\x2c\x2e\x49\xac\xc8\x4c\x55\xb4\xd1\xcf\x30\xb4\xe3\xaa\xae\x4e\xcd\x4b\xa9\xad\x05\x04\x00\x00\xff\xff\xe4\x12\x47\x01\x34\x00\x00\x00")
|
||||
|
||||
func viewsHeaderTplBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_viewsHeaderTpl,
|
||||
"views/header.tpl",
|
||||
)
|
||||
}
|
||||
|
||||
func viewsHeaderTpl() (*asset, error) {
|
||||
bytes, err := viewsHeaderTplBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "views/header.tpl", size: 52, mode: os.FileMode(436), modTime: time.Unix(1541431067, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _viewsIndexTpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\x8f\xbd\x8a\xc3\x30\x10\x84\x6b\xeb\x29\xe6\xfc\x00\x16\xb8\x3c\x16\x35\x77\xa9\x13\x88\x09\xa4\xf4\xcf\x12\x99\x48\x48\xd8\x82\x10\x84\xde\x3d\xc8\x8a\x8b\x90\x6a\xa4\xd9\x6f\xd8\x59\xfa\xf9\x3f\xfe\x75\xd7\xd3\x01\x3a\x58\xa3\x04\x15\x01\x48\x73\x3f\xe5\x07\x40\x61\x0e\x86\xd5\xc0\x7c\x73\x78\xb0\x19\x9d\x65\x04\xb6\xde\xf4\x81\x49\x96\x69\x8e\xc8\x3d\x43\x83\x9b\x9e\x4a\x88\x2a\xc6\x9d\x43\x3d\x18\x37\xde\xeb\x94\x3e\xdd\x1c\xe1\xe5\xcb\xde\xe0\x55\x6e\xd2\x04\x6f\x32\x20\x2a\xd2\xad\x8a\x11\x4d\x97\x57\x22\x25\x92\xba\x55\xa2\x22\xaf\xd0\xe9\x79\xc5\xbc\xe2\xec\x2c\x5f\xfa\xe5\x17\x99\x7b\x7f\x36\xd2\x97\x8a\xa5\x19\xc9\x72\xe7\x2b\x00\x00\xff\xff\xb2\x39\xca\x9f\xff\x00\x00\x00")
|
||||
|
||||
func viewsIndexTplBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_viewsIndexTpl,
|
||||
"views/index.tpl",
|
||||
)
|
||||
}
|
||||
|
||||
func viewsIndexTpl() (*asset, error) {
|
||||
bytes, err := viewsIndexTplBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "views/index.tpl", size: 255, mode: os.FileMode(436), modTime: time.Unix(1541434906, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.bytes, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
a, err := Asset(name)
|
||||
if err != nil {
|
||||
panic("asset: Asset(" + name + "): " + err.Error())
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.info, nil
|
||||
}
|
||||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
for name := range _bindata {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"views/blocks/block.tpl": viewsBlocksBlockTpl,
|
||||
"views/header.tpl": viewsHeaderTpl,
|
||||
"views/index.tpl": viewsIndexTpl,
|
||||
}
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
// following hierarchy:
|
||||
// data/
|
||||
// foo.txt
|
||||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(cannonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.Func != nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
rv := make([]string, 0, len(node.Children))
|
||||
for childName := range node.Children {
|
||||
rv = append(rv, childName)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type bintree struct {
|
||||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"views": &bintree{nil, map[string]*bintree{
|
||||
"blocks": &bintree{nil, map[string]*bintree{
|
||||
"block.tpl": &bintree{viewsBlocksBlockTpl, map[string]*bintree{}},
|
||||
}},
|
||||
"header.tpl": &bintree{viewsHeaderTpl, map[string]*bintree{}},
|
||||
"index.tpl": &bintree{viewsIndexTpl, map[string]*bintree{}},
|
||||
}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := AssetInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
if err != nil {
|
||||
return RestoreAsset(dir, name)
|
||||
}
|
||||
// Dir
|
||||
for _, child := range children {
|
||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
||||
|
||||
func assetFS() *assetfs.AssetFS {
|
||||
assetInfo := func(path string) (os.FileInfo, error) {
|
||||
return os.Stat(path)
|
||||
}
|
||||
for k := range _bintree.Children {
|
||||
return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: assetInfo, Prefix: k}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
3
testdata/views/blocks/block.tpl
vendored
Normal file
3
testdata/views/blocks/block.tpl
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{{define "block"}}
|
||||
<h1>Hello, blocks!</h1>
|
||||
{{end}}
|
3
testdata/views/header.tpl
vendored
Normal file
3
testdata/views/header.tpl
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{{define "header"}}
|
||||
<h1>Hello, astaxie!</h1>
|
||||
{{end}}
|
15
testdata/views/index.tpl
vendored
Normal file
15
testdata/views/index.tpl
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>beego welcome template</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{{template "block"}}
|
||||
{{template "header"}}
|
||||
{{template "blocks/block.tpl"}}
|
||||
|
||||
<h2>{{ .Title }}</h2>
|
||||
<p> This is SomeVar: {{ .SomeVar }}</p>
|
||||
</body>
|
||||
</html>
|
@ -20,6 +20,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -32,6 +33,7 @@ type bounds struct {
|
||||
// The bounds for each field.
|
||||
var (
|
||||
AdminTaskList map[string]Tasker
|
||||
taskLock sync.Mutex
|
||||
stop chan bool
|
||||
changed chan bool
|
||||
isstart bool
|
||||
@ -389,6 +391,8 @@ func dayMatches(s *Schedule, t time.Time) bool {
|
||||
|
||||
// StartTask start all tasks
|
||||
func StartTask() {
|
||||
taskLock.Lock()
|
||||
defer taskLock.Unlock()
|
||||
if isstart {
|
||||
//If already started, no need to start another goroutine.
|
||||
return
|
||||
@ -428,6 +432,9 @@ func run() {
|
||||
continue
|
||||
case <-changed:
|
||||
now = time.Now().Local()
|
||||
for _, t := range AdminTaskList {
|
||||
t.SetNext(now)
|
||||
}
|
||||
continue
|
||||
case <-stop:
|
||||
return
|
||||
@ -437,6 +444,8 @@ func run() {
|
||||
|
||||
// StopTask stop all tasks
|
||||
func StopTask() {
|
||||
taskLock.Lock()
|
||||
defer taskLock.Unlock()
|
||||
if isstart {
|
||||
isstart = false
|
||||
stop <- true
|
||||
@ -446,6 +455,9 @@ func StopTask() {
|
||||
|
||||
// AddTask add task with name
|
||||
func AddTask(taskname string, t Tasker) {
|
||||
taskLock.Lock()
|
||||
defer taskLock.Unlock()
|
||||
t.SetNext(time.Now().Local())
|
||||
AdminTaskList[taskname] = t
|
||||
if isstart {
|
||||
changed <- true
|
||||
@ -454,6 +466,8 @@ func AddTask(taskname string, t Tasker) {
|
||||
|
||||
// DeleteTask delete task with name
|
||||
func DeleteTask(taskname string) {
|
||||
taskLock.Lock()
|
||||
defer taskLock.Unlock()
|
||||
delete(AdminTaskList, taskname)
|
||||
if isstart {
|
||||
changed <- true
|
||||
|
@ -162,7 +162,7 @@ func (e *Email) Bytes() ([]byte, error) {
|
||||
|
||||
// AttachFile Add attach file to the send mail
|
||||
func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
|
||||
if len(args) < 1 && len(args) > 2 {
|
||||
if len(args) < 1 || len(args) > 2 { // change && to ||
|
||||
err = errors.New("Must specify a file name and number of parameters can not exceed at least two")
|
||||
return
|
||||
}
|
||||
@ -183,7 +183,7 @@ func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
|
||||
// Attach is used to attach content from an io.Reader to the email.
|
||||
// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type.
|
||||
func (e *Email) Attach(r io.Reader, filename string, args ...string) (a *Attachment, err error) {
|
||||
if len(args) < 1 && len(args) > 2 {
|
||||
if len(args) < 1 || len(args) > 2 { // change && to ||
|
||||
err = errors.New("Must specify the file type and number of parameters can not exceed at least two")
|
||||
return
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ func TestNewBeeMap(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
safeMap = NewBeeMap()
|
||||
if ok := safeMap.Set("astaxie", 1); !ok {
|
||||
t.Error("expected", true, "got", false)
|
||||
}
|
||||
|
@ -3,19 +3,78 @@ package utils
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetGOPATHs returns all paths in GOPATH variable.
|
||||
func GetGOPATHs() []string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
if gopath == "" && strings.Compare(runtime.Version(), "go1.8") >= 0 {
|
||||
if gopath == "" && compareGoVersion(runtime.Version(), "go1.8") >= 0 {
|
||||
gopath = defaultGOPATH()
|
||||
}
|
||||
return filepath.SplitList(gopath)
|
||||
}
|
||||
|
||||
func compareGoVersion(a, b string) int {
|
||||
reg := regexp.MustCompile("^\\d*")
|
||||
|
||||
a = strings.TrimPrefix(a, "go")
|
||||
b = strings.TrimPrefix(b, "go")
|
||||
|
||||
versionsA := strings.Split(a, ".")
|
||||
versionsB := strings.Split(b, ".")
|
||||
|
||||
for i := 0; i < len(versionsA) && i < len(versionsB); i++ {
|
||||
versionA := versionsA[i]
|
||||
versionB := versionsB[i]
|
||||
|
||||
vA, err := strconv.Atoi(versionA)
|
||||
if err != nil {
|
||||
str := reg.FindString(versionA)
|
||||
if str != "" {
|
||||
vA, _ = strconv.Atoi(str)
|
||||
} else {
|
||||
vA = -1
|
||||
}
|
||||
}
|
||||
|
||||
vB, err := strconv.Atoi(versionB)
|
||||
if err != nil {
|
||||
str := reg.FindString(versionB)
|
||||
if str != "" {
|
||||
vB, _ = strconv.Atoi(str)
|
||||
} else {
|
||||
vB = -1
|
||||
}
|
||||
}
|
||||
|
||||
if vA > vB {
|
||||
// vA = 12, vB = 8
|
||||
return 1
|
||||
} else if vA < vB {
|
||||
// vA = 6, vB = 8
|
||||
return -1
|
||||
} else if vA == -1 {
|
||||
// vA = rc1, vB = rc3
|
||||
return strings.Compare(versionA, versionB)
|
||||
}
|
||||
|
||||
// vA = vB = 8
|
||||
continue
|
||||
}
|
||||
|
||||
if len(versionsA) > len(versionsB) {
|
||||
return 1
|
||||
} else if len(versionsA) == len(versionsB) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func defaultGOPATH() string {
|
||||
env := "HOME"
|
||||
if runtime.GOOS == "windows" {
|
||||
|
36
utils/utils_test.go
Normal file
36
utils/utils_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompareGoVersion(t *testing.T) {
|
||||
targetVersion := "go1.8"
|
||||
if compareGoVersion("go1.12.4", targetVersion) != 1 {
|
||||
t.Error("should be 1")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.8.7", targetVersion) != 1 {
|
||||
t.Error("should be 1")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.8", targetVersion) != 0 {
|
||||
t.Error("should be 0")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.7.6", targetVersion) != -1 {
|
||||
t.Error("should be -1")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.12.1rc1", targetVersion) != 1 {
|
||||
t.Error("should be 1")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.8rc1", targetVersion) != 0 {
|
||||
t.Error("should be 0")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.7rc1", targetVersion) != -1 {
|
||||
t.Error("should be -1")
|
||||
}
|
||||
}
|
@ -268,6 +268,18 @@ func TestMobile(t *testing.T) {
|
||||
if !valid.Mobile("+8614700008888", "mobile").Ok {
|
||||
t.Error("\"+8614700008888\" is a valid mobile phone number should be true")
|
||||
}
|
||||
if !valid.Mobile("17300008888", "mobile").Ok {
|
||||
t.Error("\"17300008888\" is a valid mobile phone number should be true")
|
||||
}
|
||||
if !valid.Mobile("+8617100008888", "mobile").Ok {
|
||||
t.Error("\"+8617100008888\" is a valid mobile phone number should be true")
|
||||
}
|
||||
if !valid.Mobile("8617500008888", "mobile").Ok {
|
||||
t.Error("\"8617500008888\" is a valid mobile phone number should be true")
|
||||
}
|
||||
if valid.Mobile("8617400008888", "mobile").Ok {
|
||||
t.Error("\"8617400008888\" is a valid mobile phone number should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTel(t *testing.T) {
|
||||
@ -453,7 +465,7 @@ func TestPointer(t *testing.T) {
|
||||
|
||||
u := User{
|
||||
ReqEmail: nil,
|
||||
Email: nil,
|
||||
Email: nil,
|
||||
}
|
||||
|
||||
valid := Validation{}
|
||||
@ -468,7 +480,7 @@ func TestPointer(t *testing.T) {
|
||||
validEmail := "a@a.com"
|
||||
u = User{
|
||||
ReqEmail: &validEmail,
|
||||
Email: nil,
|
||||
Email: nil,
|
||||
}
|
||||
|
||||
valid = Validation{RequiredFirst: true}
|
||||
@ -482,7 +494,7 @@ func TestPointer(t *testing.T) {
|
||||
|
||||
u = User{
|
||||
ReqEmail: &validEmail,
|
||||
Email: nil,
|
||||
Email: nil,
|
||||
}
|
||||
|
||||
valid = Validation{}
|
||||
@ -497,7 +509,7 @@ func TestPointer(t *testing.T) {
|
||||
invalidEmail := "a@a"
|
||||
u = User{
|
||||
ReqEmail: &validEmail,
|
||||
Email: &invalidEmail,
|
||||
Email: &invalidEmail,
|
||||
}
|
||||
|
||||
valid = Validation{RequiredFirst: true}
|
||||
@ -511,7 +523,7 @@ func TestPointer(t *testing.T) {
|
||||
|
||||
u = User{
|
||||
ReqEmail: &validEmail,
|
||||
Email: &invalidEmail,
|
||||
Email: &invalidEmail,
|
||||
}
|
||||
|
||||
valid = Validation{}
|
||||
@ -524,19 +536,18 @@ func TestPointer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestCanSkipAlso(t *testing.T) {
|
||||
type User struct {
|
||||
ID int
|
||||
|
||||
Email string `valid:"Email"`
|
||||
ReqEmail string `valid:"Required;Email"`
|
||||
MatchRange int `valid:"Range(10, 20)"`
|
||||
Email string `valid:"Email"`
|
||||
ReqEmail string `valid:"Required;Email"`
|
||||
MatchRange int `valid:"Range(10, 20)"`
|
||||
}
|
||||
|
||||
u := User{
|
||||
ReqEmail: "a@a.com",
|
||||
Email: "",
|
||||
ReqEmail: "a@a.com",
|
||||
Email: "",
|
||||
MatchRange: 0,
|
||||
}
|
||||
|
||||
@ -560,4 +571,3 @@ func TestCanSkipAlso(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -632,7 +632,7 @@ func (b Base64) GetLimitValue() interface{} {
|
||||
}
|
||||
|
||||
// just for chinese mobile phone number
|
||||
var mobilePattern = regexp.MustCompile(`^((\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][06789]|[4][579]))\d{8}$`)
|
||||
var mobilePattern = regexp.MustCompile(`^((\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][01356789]|[4][579]))\d{8}$`)
|
||||
|
||||
// Mobile check struct
|
||||
type Mobile struct {
|
||||
|
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
921
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
Normal file
921
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
Normal file
@ -0,0 +1,921 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package acme provides an implementation of the
|
||||
// Automatic Certificate Management Environment (ACME) spec.
|
||||
// See https://tools.ietf.org/html/draft-ietf-acme-acme-02 for details.
|
||||
//
|
||||
// Most common scenarios will want to use autocert subdirectory instead,
|
||||
// which provides automatic access to certificates from Let's Encrypt
|
||||
// and any other ACME-based CA.
|
||||
//
|
||||
// This package is a work in progress and makes no API stability promises.
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
|
||||
LetsEncryptURL = "https://acme-v01.api.letsencrypt.org/directory"
|
||||
|
||||
// ALPNProto is the ALPN protocol name used by a CA server when validating
|
||||
// tls-alpn-01 challenges.
|
||||
//
|
||||
// Package users must ensure their servers can negotiate the ACME ALPN
|
||||
// in order for tls-alpn-01 challenge verifications to succeed.
|
||||
ALPNProto = "acme-tls/1"
|
||||
)
|
||||
|
||||
// idPeACMEIdentifierV1 is the OID for the ACME extension for the TLS-ALPN challenge.
|
||||
var idPeACMEIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1}
|
||||
|
||||
const (
|
||||
maxChainLen = 5 // max depth and breadth of a certificate chain
|
||||
maxCertSize = 1 << 20 // max size of a certificate, in bytes
|
||||
|
||||
// Max number of collected nonces kept in memory.
|
||||
// Expect usual peak of 1 or 2.
|
||||
maxNonces = 100
|
||||
)
|
||||
|
||||
// Client is an ACME client.
|
||||
// The only required field is Key. An example of creating a client with a new key
|
||||
// is as follows:
|
||||
//
|
||||
// key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// client := &Client{Key: key}
|
||||
//
|
||||
type Client struct {
|
||||
// Key is the account key used to register with a CA and sign requests.
|
||||
// Key.Public() must return a *rsa.PublicKey or *ecdsa.PublicKey.
|
||||
Key crypto.Signer
|
||||
|
||||
// HTTPClient optionally specifies an HTTP client to use
|
||||
// instead of http.DefaultClient.
|
||||
HTTPClient *http.Client
|
||||
|
||||
// DirectoryURL points to the CA directory endpoint.
|
||||
// If empty, LetsEncryptURL is used.
|
||||
// Mutating this value after a successful call of Client's Discover method
|
||||
// will have no effect.
|
||||
DirectoryURL string
|
||||
|
||||
// RetryBackoff computes the duration after which the nth retry of a failed request
|
||||
// should occur. The value of n for the first call on failure is 1.
|
||||
// The values of r and resp are the request and response of the last failed attempt.
|
||||
// If the returned value is negative or zero, no more retries are done and an error
|
||||
// is returned to the caller of the original method.
|
||||
//
|
||||
// Requests which result in a 4xx client error are not retried,
|
||||
// except for 400 Bad Request due to "bad nonce" errors and 429 Too Many Requests.
|
||||
//
|
||||
// If RetryBackoff is nil, a truncated exponential backoff algorithm
|
||||
// with the ceiling of 10 seconds is used, where each subsequent retry n
|
||||
// is done after either ("Retry-After" + jitter) or (2^n seconds + jitter),
|
||||
// preferring the former if "Retry-After" header is found in the resp.
|
||||
// The jitter is a random value up to 1 second.
|
||||
RetryBackoff func(n int, r *http.Request, resp *http.Response) time.Duration
|
||||
|
||||
dirMu sync.Mutex // guards writes to dir
|
||||
dir *Directory // cached result of Client's Discover method
|
||||
|
||||
noncesMu sync.Mutex
|
||||
nonces map[string]struct{} // nonces collected from previous responses
|
||||
}
|
||||
|
||||
// Discover performs ACME server discovery using c.DirectoryURL.
|
||||
//
|
||||
// It caches successful result. So, subsequent calls will not result in
|
||||
// a network round-trip. This also means mutating c.DirectoryURL after successful call
|
||||
// of this method will have no effect.
|
||||
func (c *Client) Discover(ctx context.Context) (Directory, error) {
|
||||
c.dirMu.Lock()
|
||||
defer c.dirMu.Unlock()
|
||||
if c.dir != nil {
|
||||
return *c.dir, nil
|
||||
}
|
||||
|
||||
dirURL := c.DirectoryURL
|
||||
if dirURL == "" {
|
||||
dirURL = LetsEncryptURL
|
||||
}
|
||||
res, err := c.get(ctx, dirURL, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
c.addNonce(res.Header)
|
||||
|
||||
var v struct {
|
||||
Reg string `json:"new-reg"`
|
||||
Authz string `json:"new-authz"`
|
||||
Cert string `json:"new-cert"`
|
||||
Revoke string `json:"revoke-cert"`
|
||||
Meta struct {
|
||||
Terms string `json:"terms-of-service"`
|
||||
Website string `json:"website"`
|
||||
CAA []string `json:"caa-identities"`
|
||||
}
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
c.dir = &Directory{
|
||||
RegURL: v.Reg,
|
||||
AuthzURL: v.Authz,
|
||||
CertURL: v.Cert,
|
||||
RevokeURL: v.Revoke,
|
||||
Terms: v.Meta.Terms,
|
||||
Website: v.Meta.Website,
|
||||
CAA: v.Meta.CAA,
|
||||
}
|
||||
return *c.dir, nil
|
||||
}
|
||||
|
||||
// CreateCert requests a new certificate using the Certificate Signing Request csr encoded in DER format.
|
||||
// The exp argument indicates the desired certificate validity duration. CA may issue a certificate
|
||||
// with a different duration.
|
||||
// If the bundle argument is true, the returned value will also contain the CA (issuer) certificate chain.
|
||||
//
|
||||
// In the case where CA server does not provide the issued certificate in the response,
|
||||
// CreateCert will poll certURL using c.FetchCert, which will result in additional round-trips.
|
||||
// In such a scenario, the caller can cancel the polling with ctx.
|
||||
//
|
||||
// CreateCert returns an error if the CA's response or chain was unreasonably large.
|
||||
// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
|
||||
func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
CSR string `json:"csr"`
|
||||
NotBefore string `json:"notBefore,omitempty"`
|
||||
NotAfter string `json:"notAfter,omitempty"`
|
||||
}{
|
||||
Resource: "new-cert",
|
||||
CSR: base64.RawURLEncoding.EncodeToString(csr),
|
||||
}
|
||||
now := timeNow()
|
||||
req.NotBefore = now.Format(time.RFC3339)
|
||||
if exp > 0 {
|
||||
req.NotAfter = now.Add(exp).Format(time.RFC3339)
|
||||
}
|
||||
|
||||
res, err := c.post(ctx, c.Key, c.dir.CertURL, req, wantStatus(http.StatusCreated))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
curl := res.Header.Get("Location") // cert permanent URL
|
||||
if res.ContentLength == 0 {
|
||||
// no cert in the body; poll until we get it
|
||||
cert, err := c.FetchCert(ctx, curl, bundle)
|
||||
return cert, curl, err
|
||||
}
|
||||
// slurp issued cert and CA chain, if requested
|
||||
cert, err := c.responseCert(ctx, res, bundle)
|
||||
return cert, curl, err
|
||||
}
|
||||
|
||||
// FetchCert retrieves already issued certificate from the given url, in DER format.
|
||||
// It retries the request until the certificate is successfully retrieved,
|
||||
// context is cancelled by the caller or an error response is received.
|
||||
//
|
||||
// The returned value will also contain the CA (issuer) certificate if the bundle argument is true.
|
||||
//
|
||||
// FetchCert returns an error if the CA's response or chain was unreasonably large.
|
||||
// Callers are encouraged to parse the returned value to ensure the certificate is valid
|
||||
// and has expected features.
|
||||
func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.responseCert(ctx, res, bundle)
|
||||
}
|
||||
|
||||
// RevokeCert revokes a previously issued certificate cert, provided in DER format.
|
||||
//
|
||||
// The key argument, used to sign the request, must be authorized
|
||||
// to revoke the certificate. It's up to the CA to decide which keys are authorized.
|
||||
// For instance, the key pair of the certificate may be authorized.
|
||||
// If the key is nil, c.Key is used instead.
|
||||
func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := &struct {
|
||||
Resource string `json:"resource"`
|
||||
Cert string `json:"certificate"`
|
||||
Reason int `json:"reason"`
|
||||
}{
|
||||
Resource: "revoke-cert",
|
||||
Cert: base64.RawURLEncoding.EncodeToString(cert),
|
||||
Reason: int(reason),
|
||||
}
|
||||
if key == nil {
|
||||
key = c.Key
|
||||
}
|
||||
res, err := c.post(ctx, key, c.dir.RevokeURL, body, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AcceptTOS always returns true to indicate the acceptance of a CA's Terms of Service
|
||||
// during account registration. See Register method of Client for more details.
|
||||
func AcceptTOS(tosURL string) bool { return true }
|
||||
|
||||
// Register creates a new account registration by following the "new-reg" flow.
|
||||
// It returns the registered account. The account is not modified.
|
||||
//
|
||||
// The registration may require the caller to agree to the CA's Terms of Service (TOS).
|
||||
// If so, and the account has not indicated the acceptance of the terms (see Account for details),
|
||||
// Register calls prompt with a TOS URL provided by the CA. Prompt should report
|
||||
// whether the caller agrees to the terms. To always accept the terms, the caller can use AcceptTOS.
|
||||
func (c *Client) Register(ctx context.Context, a *Account, prompt func(tosURL string) bool) (*Account, error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var err error
|
||||
if a, err = c.doReg(ctx, c.dir.RegURL, "new-reg", a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var accept bool
|
||||
if a.CurrentTerms != "" && a.CurrentTerms != a.AgreedTerms {
|
||||
accept = prompt(a.CurrentTerms)
|
||||
}
|
||||
if accept {
|
||||
a.AgreedTerms = a.CurrentTerms
|
||||
a, err = c.UpdateReg(ctx, a)
|
||||
}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// GetReg retrieves an existing registration.
|
||||
// The url argument is an Account URI.
|
||||
func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) {
|
||||
a, err := c.doReg(ctx, url, "reg", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.URI = url
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// UpdateReg updates an existing registration.
|
||||
// It returns an updated account copy. The provided account is not modified.
|
||||
func (c *Client) UpdateReg(ctx context.Context, a *Account) (*Account, error) {
|
||||
uri := a.URI
|
||||
a, err := c.doReg(ctx, uri, "reg", a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.URI = uri
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Authorize performs the initial step in an authorization flow.
|
||||
// The caller will then need to choose from and perform a set of returned
|
||||
// challenges using c.Accept in order to successfully complete authorization.
|
||||
//
|
||||
// If an authorization has been previously granted, the CA may return
|
||||
// a valid authorization (Authorization.Status is StatusValid). If so, the caller
|
||||
// need not fulfill any challenge and can proceed to requesting a certificate.
|
||||
func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization, error) {
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type authzID struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Identifier authzID `json:"identifier"`
|
||||
}{
|
||||
Resource: "new-authz",
|
||||
Identifier: authzID{Type: "dns", Value: domain},
|
||||
}
|
||||
res, err := c.post(ctx, c.Key, c.dir.AuthzURL, req, wantStatus(http.StatusCreated))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var v wireAuthz
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||
}
|
||||
if v.Status != StatusPending && v.Status != StatusValid {
|
||||
return nil, fmt.Errorf("acme: unexpected status: %s", v.Status)
|
||||
}
|
||||
return v.authorization(res.Header.Get("Location")), nil
|
||||
}
|
||||
|
||||
// GetAuthorization retrieves an authorization identified by the given URL.
|
||||
//
|
||||
// If a caller needs to poll an authorization until its status is final,
|
||||
// see the WaitAuthorization method.
|
||||
func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorization, error) {
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
var v wireAuthz
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||
}
|
||||
return v.authorization(url), nil
|
||||
}
|
||||
|
||||
// RevokeAuthorization relinquishes an existing authorization identified
|
||||
// by the given URL.
|
||||
// The url argument is an Authorization.URI value.
|
||||
//
|
||||
// If successful, the caller will be required to obtain a new authorization
|
||||
// using the Authorize method before being able to request a new certificate
|
||||
// for the domain associated with the authorization.
|
||||
//
|
||||
// It does not revoke existing certificates.
|
||||
func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Status string `json:"status"`
|
||||
Delete bool `json:"delete"`
|
||||
}{
|
||||
Resource: "authz",
|
||||
Status: "deactivated",
|
||||
Delete: true,
|
||||
}
|
||||
res, err := c.post(ctx, c.Key, url, req, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitAuthorization polls an authorization at the given URL
|
||||
// until it is in one of the final states, StatusValid or StatusInvalid,
|
||||
// the ACME CA responded with a 4xx error code, or the context is done.
|
||||
//
|
||||
// It returns a non-nil Authorization only if its Status is StatusValid.
|
||||
// In all other cases WaitAuthorization returns an error.
|
||||
// If the Status is StatusInvalid, the returned error is of type *AuthorizationError.
|
||||
func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) {
|
||||
for {
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var raw wireAuthz
|
||||
err = json.NewDecoder(res.Body).Decode(&raw)
|
||||
res.Body.Close()
|
||||
switch {
|
||||
case err != nil:
|
||||
// Skip and retry.
|
||||
case raw.Status == StatusValid:
|
||||
return raw.authorization(url), nil
|
||||
case raw.Status == StatusInvalid:
|
||||
return nil, raw.error(url)
|
||||
}
|
||||
|
||||
// Exponential backoff is implemented in c.get above.
|
||||
// This is just to prevent continuously hitting the CA
|
||||
// while waiting for a final authorization status.
|
||||
d := retryAfter(res.Header.Get("Retry-After"))
|
||||
if d == 0 {
|
||||
// Given that the fastest challenges TLS-SNI and HTTP-01
|
||||
// require a CA to make at least 1 network round trip
|
||||
// and most likely persist a challenge state,
|
||||
// this default delay seems reasonable.
|
||||
d = time.Second
|
||||
}
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
return nil, ctx.Err()
|
||||
case <-t.C:
|
||||
// Retry.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetChallenge retrieves the current status of an challenge.
|
||||
//
|
||||
// A client typically polls a challenge status using this method.
|
||||
func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) {
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
v := wireChallenge{URI: url}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||
}
|
||||
return v.challenge(), nil
|
||||
}
|
||||
|
||||
// Accept informs the server that the client accepts one of its challenges
|
||||
// previously obtained with c.Authorize.
|
||||
//
|
||||
// The server will then perform the validation asynchronously.
|
||||
func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error) {
|
||||
auth, err := keyAuth(c.Key.Public(), chal.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Type string `json:"type"`
|
||||
Auth string `json:"keyAuthorization"`
|
||||
}{
|
||||
Resource: "challenge",
|
||||
Type: chal.Type,
|
||||
Auth: auth,
|
||||
}
|
||||
res, err := c.post(ctx, c.Key, chal.URI, req, wantStatus(
|
||||
http.StatusOK, // according to the spec
|
||||
http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md)
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var v wireChallenge
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||
}
|
||||
return v.challenge(), nil
|
||||
}
|
||||
|
||||
// DNS01ChallengeRecord returns a DNS record value for a dns-01 challenge response.
|
||||
// A TXT record containing the returned value must be provisioned under
|
||||
// "_acme-challenge" name of the domain being validated.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
func (c *Client) DNS01ChallengeRecord(token string) (string, error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b := sha256.Sum256([]byte(ka))
|
||||
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
||||
}
|
||||
|
||||
// HTTP01ChallengeResponse returns the response for an http-01 challenge.
|
||||
// Servers should respond with the value to HTTP requests at the URL path
|
||||
// provided by HTTP01ChallengePath to validate the challenge and prove control
|
||||
// over a domain name.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
func (c *Client) HTTP01ChallengeResponse(token string) (string, error) {
|
||||
return keyAuth(c.Key.Public(), token)
|
||||
}
|
||||
|
||||
// HTTP01ChallengePath returns the URL path at which the response for an http-01 challenge
|
||||
// should be provided by the servers.
|
||||
// The response value can be obtained with HTTP01ChallengeResponse.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
func (c *Client) HTTP01ChallengePath(token string) string {
|
||||
return "/.well-known/acme-challenge/" + token
|
||||
}
|
||||
|
||||
// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name.
|
||||
//
|
||||
// The implementation is incomplete in that the returned value is a single certificate,
|
||||
// computed only for Z0 of the key authorization. ACME CAs are expected to update
|
||||
// their implementations to use the newer version, TLS-SNI-02.
|
||||
// For more details on TLS-SNI-01 see https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.3.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
// and the public part is used to specify the signee.
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name of the TLS ClientHello matches exactly the returned name value.
|
||||
func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
b := sha256.Sum256([]byte(ka))
|
||||
h := hex.EncodeToString(b[:])
|
||||
name = fmt.Sprintf("%s.%s.acme.invalid", h[:32], h[32:])
|
||||
cert, err = tlsChallengeCert([]string{name}, opt)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
return cert, name, nil
|
||||
}
|
||||
|
||||
// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name. For more details on TLS-SNI-02 see
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
// and the public part is used to specify the signee.
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name in the TLS ClientHello matches exactly the returned name value.
|
||||
func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||
b := sha256.Sum256([]byte(token))
|
||||
h := hex.EncodeToString(b[:])
|
||||
sanA := fmt.Sprintf("%s.%s.token.acme.invalid", h[:32], h[32:])
|
||||
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
b = sha256.Sum256([]byte(ka))
|
||||
h = hex.EncodeToString(b[:])
|
||||
sanB := fmt.Sprintf("%s.%s.ka.acme.invalid", h[:32], h[32:])
|
||||
|
||||
cert, err = tlsChallengeCert([]string{sanA, sanB}, opt)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
return cert, sanA, nil
|
||||
}
|
||||
|
||||
// TLSALPN01ChallengeCert creates a certificate for TLS-ALPN-01 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name. For more details on TLS-ALPN-01 see
|
||||
// https://tools.ietf.org/html/draft-shoemaker-acme-tls-alpn-00#section-3
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
// and the public part is used to specify the signee.
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name in the TLS ClientHello matches the domain, and the special acme-tls/1 ALPN protocol
|
||||
// has been specified.
|
||||
func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption) (cert tls.Certificate, err error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
shasum := sha256.Sum256([]byte(ka))
|
||||
extValue, err := asn1.Marshal(shasum[:])
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
acmeExtension := pkix.Extension{
|
||||
Id: idPeACMEIdentifierV1,
|
||||
Critical: true,
|
||||
Value: extValue,
|
||||
}
|
||||
|
||||
tmpl := defaultTLSChallengeCertTemplate()
|
||||
|
||||
var newOpt []CertOption
|
||||
for _, o := range opt {
|
||||
switch o := o.(type) {
|
||||
case *certOptTemplate:
|
||||
t := *(*x509.Certificate)(o) // shallow copy is ok
|
||||
tmpl = &t
|
||||
default:
|
||||
newOpt = append(newOpt, o)
|
||||
}
|
||||
}
|
||||
tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, acmeExtension)
|
||||
newOpt = append(newOpt, WithTemplate(tmpl))
|
||||
return tlsChallengeCert([]string{domain}, newOpt)
|
||||
}
|
||||
|
||||
// doReg sends all types of registration requests.
|
||||
// The type of request is identified by typ argument, which is a "resource"
|
||||
// in the ACME spec terms.
|
||||
//
|
||||
// A non-nil acct argument indicates whether the intention is to mutate data
|
||||
// of the Account. Only Contact and Agreement of its fields are used
|
||||
// in such cases.
|
||||
func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Account) (*Account, error) {
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
Agreement string `json:"agreement,omitempty"`
|
||||
}{
|
||||
Resource: typ,
|
||||
}
|
||||
if acct != nil {
|
||||
req.Contact = acct.Contact
|
||||
req.Agreement = acct.AgreedTerms
|
||||
}
|
||||
res, err := c.post(ctx, c.Key, url, req, wantStatus(
|
||||
http.StatusOK, // updates and deletes
|
||||
http.StatusCreated, // new account creation
|
||||
http.StatusAccepted, // Let's Encrypt divergent implementation
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var v struct {
|
||||
Contact []string
|
||||
Agreement string
|
||||
Authorizations string
|
||||
Certificates string
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("acme: invalid response: %v", err)
|
||||
}
|
||||
var tos string
|
||||
if v := linkHeader(res.Header, "terms-of-service"); len(v) > 0 {
|
||||
tos = v[0]
|
||||
}
|
||||
var authz string
|
||||
if v := linkHeader(res.Header, "next"); len(v) > 0 {
|
||||
authz = v[0]
|
||||
}
|
||||
return &Account{
|
||||
URI: res.Header.Get("Location"),
|
||||
Contact: v.Contact,
|
||||
AgreedTerms: v.Agreement,
|
||||
CurrentTerms: tos,
|
||||
Authz: authz,
|
||||
Authorizations: v.Authorizations,
|
||||
Certificates: v.Certificates,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// popNonce returns a nonce value previously stored with c.addNonce
|
||||
// or fetches a fresh one from the given URL.
|
||||
func (c *Client) popNonce(ctx context.Context, url string) (string, error) {
|
||||
c.noncesMu.Lock()
|
||||
defer c.noncesMu.Unlock()
|
||||
if len(c.nonces) == 0 {
|
||||
return c.fetchNonce(ctx, url)
|
||||
}
|
||||
var nonce string
|
||||
for nonce = range c.nonces {
|
||||
delete(c.nonces, nonce)
|
||||
break
|
||||
}
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
// clearNonces clears any stored nonces
|
||||
func (c *Client) clearNonces() {
|
||||
c.noncesMu.Lock()
|
||||
defer c.noncesMu.Unlock()
|
||||
c.nonces = make(map[string]struct{})
|
||||
}
|
||||
|
||||
// addNonce stores a nonce value found in h (if any) for future use.
|
||||
func (c *Client) addNonce(h http.Header) {
|
||||
v := nonceFromHeader(h)
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
c.noncesMu.Lock()
|
||||
defer c.noncesMu.Unlock()
|
||||
if len(c.nonces) >= maxNonces {
|
||||
return
|
||||
}
|
||||
if c.nonces == nil {
|
||||
c.nonces = make(map[string]struct{})
|
||||
}
|
||||
c.nonces[v] = struct{}{}
|
||||
}
|
||||
|
||||
func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) {
|
||||
r, err := http.NewRequest("HEAD", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := c.doNoRetry(ctx, r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
nonce := nonceFromHeader(resp.Header)
|
||||
if nonce == "" {
|
||||
if resp.StatusCode > 299 {
|
||||
return "", responseError(resp)
|
||||
}
|
||||
return "", errors.New("acme: nonce not found")
|
||||
}
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
func nonceFromHeader(h http.Header) string {
|
||||
return h.Get("Replay-Nonce")
|
||||
}
|
||||
|
||||
func (c *Client) responseCert(ctx context.Context, res *http.Response, bundle bool) ([][]byte, error) {
|
||||
b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: response stream: %v", err)
|
||||
}
|
||||
if len(b) > maxCertSize {
|
||||
return nil, errors.New("acme: certificate is too big")
|
||||
}
|
||||
cert := [][]byte{b}
|
||||
if !bundle {
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// Append CA chain cert(s).
|
||||
// At least one is required according to the spec:
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-6.3.1
|
||||
up := linkHeader(res.Header, "up")
|
||||
if len(up) == 0 {
|
||||
return nil, errors.New("acme: rel=up link not found")
|
||||
}
|
||||
if len(up) > maxChainLen {
|
||||
return nil, errors.New("acme: rel=up link is too large")
|
||||
}
|
||||
for _, url := range up {
|
||||
cc, err := c.chainCert(ctx, url, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert = append(cert, cc...)
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// chainCert fetches CA certificate chain recursively by following "up" links.
|
||||
// Each recursive call increments the depth by 1, resulting in an error
|
||||
// if the recursion level reaches maxChainLen.
|
||||
//
|
||||
// First chainCert call starts with depth of 0.
|
||||
func (c *Client) chainCert(ctx context.Context, url string, depth int) ([][]byte, error) {
|
||||
if depth >= maxChainLen {
|
||||
return nil, errors.New("acme: certificate chain is too deep")
|
||||
}
|
||||
|
||||
res, err := c.get(ctx, url, wantStatus(http.StatusOK))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b) > maxCertSize {
|
||||
return nil, errors.New("acme: certificate is too big")
|
||||
}
|
||||
chain := [][]byte{b}
|
||||
|
||||
uplink := linkHeader(res.Header, "up")
|
||||
if len(uplink) > maxChainLen {
|
||||
return nil, errors.New("acme: certificate chain is too large")
|
||||
}
|
||||
for _, up := range uplink {
|
||||
cc, err := c.chainCert(ctx, up, depth+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chain = append(chain, cc...)
|
||||
}
|
||||
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
// linkHeader returns URI-Reference values of all Link headers
|
||||
// with relation-type rel.
|
||||
// See https://tools.ietf.org/html/rfc5988#section-5 for details.
|
||||
func linkHeader(h http.Header, rel string) []string {
|
||||
var links []string
|
||||
for _, v := range h["Link"] {
|
||||
parts := strings.Split(v, ";")
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
if !strings.HasPrefix(p, "rel=") {
|
||||
continue
|
||||
}
|
||||
if v := strings.Trim(p[4:], `"`); v == rel {
|
||||
links = append(links, strings.Trim(parts[0], "<>"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
// keyAuth generates a key authorization string for a given token.
|
||||
func keyAuth(pub crypto.PublicKey, token string) (string, error) {
|
||||
th, err := JWKThumbprint(pub)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", token, th), nil
|
||||
}
|
||||
|
||||
// defaultTLSChallengeCertTemplate is a template used to create challenge certs for TLS challenges.
|
||||
func defaultTLSChallengeCertTemplate() *x509.Certificate {
|
||||
return &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(24 * time.Hour),
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
}
|
||||
|
||||
// tlsChallengeCert creates a temporary certificate for TLS-SNI challenges
|
||||
// with the given SANs and auto-generated public/private key pair.
|
||||
// The Subject Common Name is set to the first SAN to aid debugging.
|
||||
// To create a cert with a custom key pair, specify WithKey option.
|
||||
func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
var key crypto.Signer
|
||||
tmpl := defaultTLSChallengeCertTemplate()
|
||||
for _, o := range opt {
|
||||
switch o := o.(type) {
|
||||
case *certOptKey:
|
||||
if key != nil {
|
||||
return tls.Certificate{}, errors.New("acme: duplicate key option")
|
||||
}
|
||||
key = o.key
|
||||
case *certOptTemplate:
|
||||
t := *(*x509.Certificate)(o) // shallow copy is ok
|
||||
tmpl = &t
|
||||
default:
|
||||
// package's fault, if we let this happen:
|
||||
panic(fmt.Sprintf("unsupported option type %T", o))
|
||||
}
|
||||
}
|
||||
if key == nil {
|
||||
var err error
|
||||
if key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader); err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
}
|
||||
tmpl.DNSNames = san
|
||||
if len(san) > 0 {
|
||||
tmpl.Subject.CommonName = san[0]
|
||||
}
|
||||
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
return tls.Certificate{
|
||||
Certificate: [][]byte{der},
|
||||
PrivateKey: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// encodePEM returns b encoded as PEM with block of type typ.
|
||||
func encodePEM(typ string, b []byte) []byte {
|
||||
pb := &pem.Block{Type: typ, Bytes: b}
|
||||
return pem.EncodeToMemory(pb)
|
||||
}
|
||||
|
||||
// timeNow is useful for testing for fixed current time.
|
||||
var timeNow = time.Now
|
1127
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
Normal file
1127
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
130
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
Normal file
130
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// ErrCacheMiss is returned when a certificate is not found in cache.
|
||||
var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
|
||||
|
||||
// Cache is used by Manager to store and retrieve previously obtained certificates
|
||||
// and other account data as opaque blobs.
|
||||
//
|
||||
// Cache implementations should not rely on the key naming pattern. Keys can
|
||||
// include any printable ASCII characters, except the following: \/:*?"<>|
|
||||
type Cache interface {
|
||||
// Get returns a certificate data for the specified key.
|
||||
// If there's no such key, Get returns ErrCacheMiss.
|
||||
Get(ctx context.Context, key string) ([]byte, error)
|
||||
|
||||
// Put stores the data in the cache under the specified key.
|
||||
// Underlying implementations may use any data storage format,
|
||||
// as long as the reverse operation, Get, results in the original data.
|
||||
Put(ctx context.Context, key string, data []byte) error
|
||||
|
||||
// Delete removes a certificate data from the cache under the specified key.
|
||||
// If there's no such key in the cache, Delete returns nil.
|
||||
Delete(ctx context.Context, key string) error
|
||||
}
|
||||
|
||||
// DirCache implements Cache using a directory on the local filesystem.
|
||||
// If the directory does not exist, it will be created with 0700 permissions.
|
||||
type DirCache string
|
||||
|
||||
// Get reads a certificate data from the specified file name.
|
||||
func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
|
||||
name = filepath.Join(string(d), name)
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
done = make(chan struct{})
|
||||
)
|
||||
go func() {
|
||||
data, err = ioutil.ReadFile(name)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return nil, ErrCacheMiss
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
// Put writes the certificate data to the specified file name.
|
||||
// The file will be created with 0600 permissions.
|
||||
func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
|
||||
if err := os.MkdirAll(string(d), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var err error
|
||||
go func() {
|
||||
defer close(done)
|
||||
var tmp string
|
||||
if tmp, err = d.writeTempFile(name, data); err != nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Don't overwrite the file if the context was canceled.
|
||||
default:
|
||||
newName := filepath.Join(string(d), name)
|
||||
err = os.Rename(tmp, newName)
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete removes the specified file name.
|
||||
func (d DirCache) Delete(ctx context.Context, name string) error {
|
||||
name = filepath.Join(string(d), name)
|
||||
var (
|
||||
err error
|
||||
done = make(chan struct{})
|
||||
)
|
||||
go func() {
|
||||
err = os.Remove(name)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeTempFile writes b to a temporary file, closes the file and returns its path.
|
||||
func (d DirCache) writeTempFile(prefix string, b []byte) (string, error) {
|
||||
// TempFile uses 0600 permissions
|
||||
f, err := ioutil.TempFile(string(d), prefix)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := f.Write(b); err != nil {
|
||||
f.Close()
|
||||
return "", err
|
||||
}
|
||||
return f.Name(), f.Close()
|
||||
}
|
157
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
Normal file
157
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewListener returns a net.Listener that listens on the standard TLS
|
||||
// port (443) on all interfaces and returns *tls.Conn connections with
|
||||
// LetsEncrypt certificates for the provided domain or domains.
|
||||
//
|
||||
// It enables one-line HTTPS servers:
|
||||
//
|
||||
// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
|
||||
//
|
||||
// NewListener is a convenience function for a common configuration.
|
||||
// More complex or custom configurations can use the autocert.Manager
|
||||
// type instead.
|
||||
//
|
||||
// Use of this function implies acceptance of the LetsEncrypt Terms of
|
||||
// Service. If domains is not empty, the provided domains are passed
|
||||
// to HostWhitelist. If domains is empty, the listener will do
|
||||
// LetsEncrypt challenges for any requested domain, which is not
|
||||
// recommended.
|
||||
//
|
||||
// Certificates are cached in a "golang-autocert" directory under an
|
||||
// operating system-specific cache or temp directory. This may not
|
||||
// be suitable for servers spanning multiple machines.
|
||||
//
|
||||
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
||||
// should only be used with servers that support HTTP/2.
|
||||
//
|
||||
// The returned Listener also enables TCP keep-alives on the accepted
|
||||
// connections. The returned *tls.Conn are returned before their TLS
|
||||
// handshake has completed.
|
||||
func NewListener(domains ...string) net.Listener {
|
||||
m := &Manager{
|
||||
Prompt: AcceptTOS,
|
||||
}
|
||||
if len(domains) > 0 {
|
||||
m.HostPolicy = HostWhitelist(domains...)
|
||||
}
|
||||
dir := cacheDir()
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
log.Printf("warning: autocert.NewListener not using a cache: %v", err)
|
||||
} else {
|
||||
m.Cache = DirCache(dir)
|
||||
}
|
||||
return m.Listener()
|
||||
}
|
||||
|
||||
// Listener listens on the standard TLS port (443) on all interfaces
|
||||
// and returns a net.Listener returning *tls.Conn connections.
|
||||
//
|
||||
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
||||
// should only be used with servers that support HTTP/2.
|
||||
//
|
||||
// The returned Listener also enables TCP keep-alives on the accepted
|
||||
// connections. The returned *tls.Conn are returned before their TLS
|
||||
// handshake has completed.
|
||||
//
|
||||
// Unlike NewListener, it is the caller's responsibility to initialize
|
||||
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
|
||||
func (m *Manager) Listener() net.Listener {
|
||||
ln := &listener{
|
||||
m: m,
|
||||
conf: m.TLSConfig(),
|
||||
}
|
||||
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
|
||||
return ln
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
m *Manager
|
||||
conf *tls.Config
|
||||
|
||||
tcpListener net.Listener
|
||||
tcpListenErr error
|
||||
}
|
||||
|
||||
func (ln *listener) Accept() (net.Conn, error) {
|
||||
if ln.tcpListenErr != nil {
|
||||
return nil, ln.tcpListenErr
|
||||
}
|
||||
conn, err := ln.tcpListener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tcpConn := conn.(*net.TCPConn)
|
||||
|
||||
// Because Listener is a convenience function, help out with
|
||||
// this too. This is not possible for the caller to set once
|
||||
// we return a *tcp.Conn wrapping an inaccessible net.Conn.
|
||||
// If callers don't want this, they can do things the manual
|
||||
// way and tweak as needed. But this is what net/http does
|
||||
// itself, so copy that. If net/http changes, we can change
|
||||
// here too.
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(3 * time.Minute)
|
||||
|
||||
return tls.Server(tcpConn, ln.conf), nil
|
||||
}
|
||||
|
||||
func (ln *listener) Addr() net.Addr {
|
||||
if ln.tcpListener != nil {
|
||||
return ln.tcpListener.Addr()
|
||||
}
|
||||
// net.Listen failed. Return something non-nil in case callers
|
||||
// call Addr before Accept:
|
||||
return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
|
||||
}
|
||||
|
||||
func (ln *listener) Close() error {
|
||||
if ln.tcpListenErr != nil {
|
||||
return ln.tcpListenErr
|
||||
}
|
||||
return ln.tcpListener.Close()
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
||||
}
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
}
|
||||
return "/"
|
||||
}
|
||||
|
||||
func cacheDir() string {
|
||||
const base = "golang-autocert"
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return filepath.Join(homeDir(), "Library", "Caches", base)
|
||||
case "windows":
|
||||
for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
|
||||
if v := os.Getenv(ev); v != "" {
|
||||
return filepath.Join(v, base)
|
||||
}
|
||||
}
|
||||
// Worst case:
|
||||
return filepath.Join(homeDir(), base)
|
||||
}
|
||||
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
|
||||
return filepath.Join(xdg, base)
|
||||
}
|
||||
return filepath.Join(homeDir(), ".cache", base)
|
||||
}
|
141
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
Normal file
141
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// renewJitter is the maximum deviation from Manager.RenewBefore.
|
||||
const renewJitter = time.Hour
|
||||
|
||||
// domainRenewal tracks the state used by the periodic timers
|
||||
// renewing a single domain's cert.
|
||||
type domainRenewal struct {
|
||||
m *Manager
|
||||
ck certKey
|
||||
key crypto.Signer
|
||||
|
||||
timerMu sync.Mutex
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
// start starts a cert renewal timer at the time
|
||||
// defined by the certificate expiration time exp.
|
||||
//
|
||||
// If the timer is already started, calling start is a noop.
|
||||
func (dr *domainRenewal) start(exp time.Time) {
|
||||
dr.timerMu.Lock()
|
||||
defer dr.timerMu.Unlock()
|
||||
if dr.timer != nil {
|
||||
return
|
||||
}
|
||||
dr.timer = time.AfterFunc(dr.next(exp), dr.renew)
|
||||
}
|
||||
|
||||
// stop stops the cert renewal timer.
|
||||
// If the timer is already stopped, calling stop is a noop.
|
||||
func (dr *domainRenewal) stop() {
|
||||
dr.timerMu.Lock()
|
||||
defer dr.timerMu.Unlock()
|
||||
if dr.timer == nil {
|
||||
return
|
||||
}
|
||||
dr.timer.Stop()
|
||||
dr.timer = nil
|
||||
}
|
||||
|
||||
// renew is called periodically by a timer.
|
||||
// The first renew call is kicked off by dr.start.
|
||||
func (dr *domainRenewal) renew() {
|
||||
dr.timerMu.Lock()
|
||||
defer dr.timerMu.Unlock()
|
||||
if dr.timer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer cancel()
|
||||
// TODO: rotate dr.key at some point?
|
||||
next, err := dr.do(ctx)
|
||||
if err != nil {
|
||||
next = renewJitter / 2
|
||||
next += time.Duration(pseudoRand.int63n(int64(next)))
|
||||
}
|
||||
dr.timer = time.AfterFunc(next, dr.renew)
|
||||
testDidRenewLoop(next, err)
|
||||
}
|
||||
|
||||
// updateState locks and replaces the relevant Manager.state item with the given
|
||||
// state. It additionally updates dr.key with the given state's key.
|
||||
func (dr *domainRenewal) updateState(state *certState) {
|
||||
dr.m.stateMu.Lock()
|
||||
defer dr.m.stateMu.Unlock()
|
||||
dr.key = state.key
|
||||
dr.m.state[dr.ck] = state
|
||||
}
|
||||
|
||||
// do is similar to Manager.createCert but it doesn't lock a Manager.state item.
|
||||
// Instead, it requests a new certificate independently and, upon success,
|
||||
// replaces dr.m.state item with a new one and updates cache for the given domain.
|
||||
//
|
||||
// It may lock and update the Manager.state if the expiration date of the currently
|
||||
// cached cert is far enough in the future.
|
||||
//
|
||||
// The returned value is a time interval after which the renewal should occur again.
|
||||
func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
||||
// a race is likely unavoidable in a distributed environment
|
||||
// but we try nonetheless
|
||||
if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil {
|
||||
next := dr.next(tlscert.Leaf.NotAfter)
|
||||
if next > dr.m.renewBefore()+renewJitter {
|
||||
signer, ok := tlscert.PrivateKey.(crypto.Signer)
|
||||
if ok {
|
||||
state := &certState{
|
||||
key: signer,
|
||||
cert: tlscert.Certificate,
|
||||
leaf: tlscert.Leaf,
|
||||
}
|
||||
dr.updateState(state)
|
||||
return next, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.ck)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
state := &certState{
|
||||
key: dr.key,
|
||||
cert: der,
|
||||
leaf: leaf,
|
||||
}
|
||||
tlscert, err := state.tlscert()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := dr.m.cachePut(ctx, dr.ck, tlscert); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dr.updateState(state)
|
||||
return dr.next(leaf.NotAfter), nil
|
||||
}
|
||||
|
||||
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
|
||||
d := expiry.Sub(timeNow()) - dr.m.renewBefore()
|
||||
// add a bit of randomness to renew deadline
|
||||
n := pseudoRand.int63n(int64(renewJitter))
|
||||
d -= time.Duration(n)
|
||||
if d < 0 {
|
||||
return 0
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
var testDidRenewLoop = func(next time.Duration, err error) {}
|
281
vendor/golang.org/x/crypto/acme/http.go
generated
vendored
Normal file
281
vendor/golang.org/x/crypto/acme/http.go
generated
vendored
Normal file
@ -0,0 +1,281 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// retryTimer encapsulates common logic for retrying unsuccessful requests.
|
||||
// It is not safe for concurrent use.
|
||||
type retryTimer struct {
|
||||
// backoffFn provides backoff delay sequence for retries.
|
||||
// See Client.RetryBackoff doc comment.
|
||||
backoffFn func(n int, r *http.Request, res *http.Response) time.Duration
|
||||
// n is the current retry attempt.
|
||||
n int
|
||||
}
|
||||
|
||||
func (t *retryTimer) inc() {
|
||||
t.n++
|
||||
}
|
||||
|
||||
// backoff pauses the current goroutine as described in Client.RetryBackoff.
|
||||
func (t *retryTimer) backoff(ctx context.Context, r *http.Request, res *http.Response) error {
|
||||
d := t.backoffFn(t.n, r, res)
|
||||
if d <= 0 {
|
||||
return fmt.Errorf("acme: no more retries for %s; tried %d time(s)", r.URL, t.n)
|
||||
}
|
||||
wakeup := time.NewTimer(d)
|
||||
defer wakeup.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-wakeup.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) retryTimer() *retryTimer {
|
||||
f := c.RetryBackoff
|
||||
if f == nil {
|
||||
f = defaultBackoff
|
||||
}
|
||||
return &retryTimer{backoffFn: f}
|
||||
}
|
||||
|
||||
// defaultBackoff provides default Client.RetryBackoff implementation
|
||||
// using a truncated exponential backoff algorithm,
|
||||
// as described in Client.RetryBackoff.
|
||||
//
|
||||
// The n argument is always bounded between 1 and 30.
|
||||
// The returned value is always greater than 0.
|
||||
func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
|
||||
const max = 10 * time.Second
|
||||
var jitter time.Duration
|
||||
if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
|
||||
// Set the minimum to 1ms to avoid a case where
|
||||
// an invalid Retry-After value is parsed into 0 below,
|
||||
// resulting in the 0 returned value which would unintentionally
|
||||
// stop the retries.
|
||||
jitter = (1 + time.Duration(x.Int64())) * time.Millisecond
|
||||
}
|
||||
if v, ok := res.Header["Retry-After"]; ok {
|
||||
return retryAfter(v[0]) + jitter
|
||||
}
|
||||
|
||||
if n < 1 {
|
||||
n = 1
|
||||
}
|
||||
if n > 30 {
|
||||
n = 30
|
||||
}
|
||||
d := time.Duration(1<<uint(n-1))*time.Second + jitter
|
||||
if d > max {
|
||||
return max
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// retryAfter parses a Retry-After HTTP header value,
|
||||
// trying to convert v into an int (seconds) or use http.ParseTime otherwise.
|
||||
// It returns zero value if v cannot be parsed.
|
||||
func retryAfter(v string) time.Duration {
|
||||
if i, err := strconv.Atoi(v); err == nil {
|
||||
return time.Duration(i) * time.Second
|
||||
}
|
||||
t, err := http.ParseTime(v)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return t.Sub(timeNow())
|
||||
}
|
||||
|
||||
// resOkay is a function that reports whether the provided response is okay.
|
||||
// It is expected to keep the response body unread.
|
||||
type resOkay func(*http.Response) bool
|
||||
|
||||
// wantStatus returns a function which reports whether the code
|
||||
// matches the status code of a response.
|
||||
func wantStatus(codes ...int) resOkay {
|
||||
return func(res *http.Response) bool {
|
||||
for _, code := range codes {
|
||||
if code == res.StatusCode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// get issues an unsigned GET request to the specified URL.
|
||||
// It returns a non-error value only when ok reports true.
|
||||
//
|
||||
// get retries unsuccessful attempts according to c.RetryBackoff
|
||||
// until the context is done or a non-retriable error is received.
|
||||
func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
|
||||
retry := c.retryTimer()
|
||||
for {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := c.doNoRetry(ctx, req)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case ok(res):
|
||||
return res, nil
|
||||
case isRetriable(res.StatusCode):
|
||||
retry.inc()
|
||||
resErr := responseError(res)
|
||||
res.Body.Close()
|
||||
// Ignore the error value from retry.backoff
|
||||
// and return the one from last retry, as received from the CA.
|
||||
if retry.backoff(ctx, req, res) != nil {
|
||||
return nil, resErr
|
||||
}
|
||||
default:
|
||||
defer res.Body.Close()
|
||||
return nil, responseError(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// post issues a signed POST request in JWS format using the provided key
|
||||
// to the specified URL.
|
||||
// It returns a non-error value only when ok reports true.
|
||||
//
|
||||
// post retries unsuccessful attempts according to c.RetryBackoff
|
||||
// until the context is done or a non-retriable error is received.
|
||||
// It uses postNoRetry to make individual requests.
|
||||
func (c *Client) post(ctx context.Context, key crypto.Signer, url string, body interface{}, ok resOkay) (*http.Response, error) {
|
||||
retry := c.retryTimer()
|
||||
for {
|
||||
res, req, err := c.postNoRetry(ctx, key, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok(res) {
|
||||
return res, nil
|
||||
}
|
||||
resErr := responseError(res)
|
||||
res.Body.Close()
|
||||
switch {
|
||||
// Check for bad nonce before isRetriable because it may have been returned
|
||||
// with an unretriable response code such as 400 Bad Request.
|
||||
case isBadNonce(resErr):
|
||||
// Consider any previously stored nonce values to be invalid.
|
||||
c.clearNonces()
|
||||
case !isRetriable(res.StatusCode):
|
||||
return nil, resErr
|
||||
}
|
||||
retry.inc()
|
||||
// Ignore the error value from retry.backoff
|
||||
// and return the one from last retry, as received from the CA.
|
||||
if err := retry.backoff(ctx, req, res); err != nil {
|
||||
return nil, resErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// postNoRetry signs the body with the given key and POSTs it to the provided url.
|
||||
// The body argument must be JSON-serializable.
|
||||
// It is used by c.post to retry unsuccessful attempts.
|
||||
func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, *http.Request, error) {
|
||||
nonce, err := c.popNonce(ctx, url)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
b, err := jwsEncodeJSON(body, key, nonce)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/jose+json")
|
||||
res, err := c.doNoRetry(ctx, req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
c.addNonce(res.Header)
|
||||
return res, req, nil
|
||||
}
|
||||
|
||||
// doNoRetry issues a request req, replacing its context (if any) with ctx.
|
||||
func (c *Client) doNoRetry(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||
res, err := c.httpClient().Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Prefer the unadorned context error.
|
||||
// (The acme package had tests assuming this, previously from ctxhttp's
|
||||
// behavior, predating net/http supporting contexts natively)
|
||||
// TODO(bradfitz): reconsider this in the future. But for now this
|
||||
// requires no test updates.
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *Client) httpClient() *http.Client {
|
||||
if c.HTTPClient != nil {
|
||||
return c.HTTPClient
|
||||
}
|
||||
return http.DefaultClient
|
||||
}
|
||||
|
||||
// isBadNonce reports whether err is an ACME "badnonce" error.
|
||||
func isBadNonce(err error) bool {
|
||||
// According to the spec badNonce is urn:ietf:params:acme:error:badNonce.
|
||||
// However, ACME servers in the wild return their versions of the error.
|
||||
// See https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-5.4
|
||||
// and https://github.com/letsencrypt/boulder/blob/0e07eacb/docs/acme-divergences.md#section-66.
|
||||
ae, ok := err.(*Error)
|
||||
return ok && strings.HasSuffix(strings.ToLower(ae.ProblemType), ":badnonce")
|
||||
}
|
||||
|
||||
// isRetriable reports whether a request can be retried
|
||||
// based on the response status code.
|
||||
//
|
||||
// Note that a "bad nonce" error is returned with a non-retriable 400 Bad Request code.
|
||||
// Callers should parse the response and check with isBadNonce.
|
||||
func isRetriable(code int) bool {
|
||||
return code <= 399 || code >= 500 || code == http.StatusTooManyRequests
|
||||
}
|
||||
|
||||
// responseError creates an error of Error type from resp.
|
||||
func responseError(resp *http.Response) error {
|
||||
// don't care if ReadAll returns an error:
|
||||
// json.Unmarshal will fail in that case anyway
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
e := &wireError{Status: resp.StatusCode}
|
||||
if err := json.Unmarshal(b, e); err != nil {
|
||||
// this is not a regular error response:
|
||||
// populate detail with anything we received,
|
||||
// e.Status will already contain HTTP response code value
|
||||
e.Detail = string(b)
|
||||
if e.Detail == "" {
|
||||
e.Detail = resp.Status
|
||||
}
|
||||
}
|
||||
return e.error(resp.Header)
|
||||
}
|
153
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
Normal file
153
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
_ "crypto/sha512" // need for EC keys
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
||||
// The result is serialized in JSON format.
|
||||
// See https://tools.ietf.org/html/rfc7515#section-7.
|
||||
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, nonce string) ([]byte, error) {
|
||||
jwk, err := jwkEncode(key.Public())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
alg, sha := jwsHasher(key)
|
||||
if alg == "" || !sha.Available() {
|
||||
return nil, ErrUnsupportedKey
|
||||
}
|
||||
phead := fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q}`, alg, jwk, nonce)
|
||||
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
|
||||
cs, err := json.Marshal(claimset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := base64.RawURLEncoding.EncodeToString(cs)
|
||||
hash := sha.New()
|
||||
hash.Write([]byte(phead + "." + payload))
|
||||
sig, err := jwsSign(key, sha, hash.Sum(nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enc := struct {
|
||||
Protected string `json:"protected"`
|
||||
Payload string `json:"payload"`
|
||||
Sig string `json:"signature"`
|
||||
}{
|
||||
Protected: phead,
|
||||
Payload: payload,
|
||||
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
||||
}
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
|
||||
// The result is also suitable for creating a JWK thumbprint.
|
||||
// https://tools.ietf.org/html/rfc7517
|
||||
func jwkEncode(pub crypto.PublicKey) (string, error) {
|
||||
switch pub := pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.3.1
|
||||
n := pub.N
|
||||
e := big.NewInt(int64(pub.E))
|
||||
// Field order is important.
|
||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
|
||||
base64.RawURLEncoding.EncodeToString(e.Bytes()),
|
||||
base64.RawURLEncoding.EncodeToString(n.Bytes()),
|
||||
), nil
|
||||
case *ecdsa.PublicKey:
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1
|
||||
p := pub.Curve.Params()
|
||||
n := p.BitSize / 8
|
||||
if p.BitSize%8 != 0 {
|
||||
n++
|
||||
}
|
||||
x := pub.X.Bytes()
|
||||
if n > len(x) {
|
||||
x = append(make([]byte, n-len(x)), x...)
|
||||
}
|
||||
y := pub.Y.Bytes()
|
||||
if n > len(y) {
|
||||
y = append(make([]byte, n-len(y)), y...)
|
||||
}
|
||||
// Field order is important.
|
||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||
return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
|
||||
p.Name,
|
||||
base64.RawURLEncoding.EncodeToString(x),
|
||||
base64.RawURLEncoding.EncodeToString(y),
|
||||
), nil
|
||||
}
|
||||
return "", ErrUnsupportedKey
|
||||
}
|
||||
|
||||
// jwsSign signs the digest using the given key.
|
||||
// It returns ErrUnsupportedKey if the key type is unknown.
|
||||
// The hash is used only for RSA keys.
|
||||
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
|
||||
switch key := key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return key.Sign(rand.Reader, digest, hash)
|
||||
case *ecdsa.PrivateKey:
|
||||
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rb, sb := r.Bytes(), s.Bytes()
|
||||
size := key.Params().BitSize / 8
|
||||
if size%8 > 0 {
|
||||
size++
|
||||
}
|
||||
sig := make([]byte, size*2)
|
||||
copy(sig[size-len(rb):], rb)
|
||||
copy(sig[size*2-len(sb):], sb)
|
||||
return sig, nil
|
||||
}
|
||||
return nil, ErrUnsupportedKey
|
||||
}
|
||||
|
||||
// jwsHasher indicates suitable JWS algorithm name and a hash function
|
||||
// to use for signing a digest with the provided key.
|
||||
// It returns ("", 0) if the key is not supported.
|
||||
func jwsHasher(key crypto.Signer) (string, crypto.Hash) {
|
||||
switch key := key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return "RS256", crypto.SHA256
|
||||
case *ecdsa.PrivateKey:
|
||||
switch key.Params().Name {
|
||||
case "P-256":
|
||||
return "ES256", crypto.SHA256
|
||||
case "P-384":
|
||||
return "ES384", crypto.SHA384
|
||||
case "P-521":
|
||||
return "ES512", crypto.SHA512
|
||||
}
|
||||
}
|
||||
return "", 0
|
||||
}
|
||||
|
||||
// JWKThumbprint creates a JWK thumbprint out of pub
|
||||
// as specified in https://tools.ietf.org/html/rfc7638.
|
||||
func JWKThumbprint(pub crypto.PublicKey) (string, error) {
|
||||
jwk, err := jwkEncode(pub)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b := sha256.Sum256([]byte(jwk))
|
||||
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
||||
}
|
329
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
Normal file
329
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
Normal file
@ -0,0 +1,329 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ACME server response statuses used to describe Authorization and Challenge states.
|
||||
const (
|
||||
StatusUnknown = "unknown"
|
||||
StatusPending = "pending"
|
||||
StatusProcessing = "processing"
|
||||
StatusValid = "valid"
|
||||
StatusInvalid = "invalid"
|
||||
StatusRevoked = "revoked"
|
||||
)
|
||||
|
||||
// CRLReasonCode identifies the reason for a certificate revocation.
|
||||
type CRLReasonCode int
|
||||
|
||||
// CRL reason codes as defined in RFC 5280.
|
||||
const (
|
||||
CRLReasonUnspecified CRLReasonCode = 0
|
||||
CRLReasonKeyCompromise CRLReasonCode = 1
|
||||
CRLReasonCACompromise CRLReasonCode = 2
|
||||
CRLReasonAffiliationChanged CRLReasonCode = 3
|
||||
CRLReasonSuperseded CRLReasonCode = 4
|
||||
CRLReasonCessationOfOperation CRLReasonCode = 5
|
||||
CRLReasonCertificateHold CRLReasonCode = 6
|
||||
CRLReasonRemoveFromCRL CRLReasonCode = 8
|
||||
CRLReasonPrivilegeWithdrawn CRLReasonCode = 9
|
||||
CRLReasonAACompromise CRLReasonCode = 10
|
||||
)
|
||||
|
||||
// ErrUnsupportedKey is returned when an unsupported key type is encountered.
|
||||
var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
|
||||
|
||||
// Error is an ACME error, defined in Problem Details for HTTP APIs doc
|
||||
// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
|
||||
type Error struct {
|
||||
// StatusCode is The HTTP status code generated by the origin server.
|
||||
StatusCode int
|
||||
// ProblemType is a URI reference that identifies the problem type,
|
||||
// typically in a "urn:acme:error:xxx" form.
|
||||
ProblemType string
|
||||
// Detail is a human-readable explanation specific to this occurrence of the problem.
|
||||
Detail string
|
||||
// Header is the original server error response headers.
|
||||
// It may be nil.
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
||||
}
|
||||
|
||||
// AuthorizationError indicates that an authorization for an identifier
|
||||
// did not succeed.
|
||||
// It contains all errors from Challenge items of the failed Authorization.
|
||||
type AuthorizationError struct {
|
||||
// URI uniquely identifies the failed Authorization.
|
||||
URI string
|
||||
|
||||
// Identifier is an AuthzID.Value of the failed Authorization.
|
||||
Identifier string
|
||||
|
||||
// Errors is a collection of non-nil error values of Challenge items
|
||||
// of the failed Authorization.
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (a *AuthorizationError) Error() string {
|
||||
e := make([]string, len(a.Errors))
|
||||
for i, err := range a.Errors {
|
||||
e[i] = err.Error()
|
||||
}
|
||||
return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
|
||||
}
|
||||
|
||||
// RateLimit reports whether err represents a rate limit error and
|
||||
// any Retry-After duration returned by the server.
|
||||
//
|
||||
// See the following for more details on rate limiting:
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6
|
||||
func RateLimit(err error) (time.Duration, bool) {
|
||||
e, ok := err.(*Error)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
// Some CA implementations may return incorrect values.
|
||||
// Use case-insensitive comparison.
|
||||
if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") {
|
||||
return 0, false
|
||||
}
|
||||
if e.Header == nil {
|
||||
return 0, true
|
||||
}
|
||||
return retryAfter(e.Header.Get("Retry-After")), true
|
||||
}
|
||||
|
||||
// Account is a user account. It is associated with a private key.
|
||||
type Account struct {
|
||||
// URI is the account unique ID, which is also a URL used to retrieve
|
||||
// account data from the CA.
|
||||
URI string
|
||||
|
||||
// Contact is a slice of contact info used during registration.
|
||||
Contact []string
|
||||
|
||||
// The terms user has agreed to.
|
||||
// A value not matching CurrentTerms indicates that the user hasn't agreed
|
||||
// to the actual Terms of Service of the CA.
|
||||
AgreedTerms string
|
||||
|
||||
// Actual terms of a CA.
|
||||
CurrentTerms string
|
||||
|
||||
// Authz is the authorization URL used to initiate a new authz flow.
|
||||
Authz string
|
||||
|
||||
// Authorizations is a URI from which a list of authorizations
|
||||
// granted to this account can be fetched via a GET request.
|
||||
Authorizations string
|
||||
|
||||
// Certificates is a URI from which a list of certificates
|
||||
// issued for this account can be fetched via a GET request.
|
||||
Certificates string
|
||||
}
|
||||
|
||||
// Directory is ACME server discovery data.
|
||||
type Directory struct {
|
||||
// RegURL is an account endpoint URL, allowing for creating new
|
||||
// and modifying existing accounts.
|
||||
RegURL string
|
||||
|
||||
// AuthzURL is used to initiate Identifier Authorization flow.
|
||||
AuthzURL string
|
||||
|
||||
// CertURL is a new certificate issuance endpoint URL.
|
||||
CertURL string
|
||||
|
||||
// RevokeURL is used to initiate a certificate revocation flow.
|
||||
RevokeURL string
|
||||
|
||||
// Term is a URI identifying the current terms of service.
|
||||
Terms string
|
||||
|
||||
// Website is an HTTP or HTTPS URL locating a website
|
||||
// providing more information about the ACME server.
|
||||
Website string
|
||||
|
||||
// CAA consists of lowercase hostname elements, which the ACME server
|
||||
// recognises as referring to itself for the purposes of CAA record validation
|
||||
// as defined in RFC6844.
|
||||
CAA []string
|
||||
}
|
||||
|
||||
// Challenge encodes a returned CA challenge.
|
||||
// Its Error field may be non-nil if the challenge is part of an Authorization
|
||||
// with StatusInvalid.
|
||||
type Challenge struct {
|
||||
// Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01".
|
||||
Type string
|
||||
|
||||
// URI is where a challenge response can be posted to.
|
||||
URI string
|
||||
|
||||
// Token is a random value that uniquely identifies the challenge.
|
||||
Token string
|
||||
|
||||
// Status identifies the status of this challenge.
|
||||
Status string
|
||||
|
||||
// Error indicates the reason for an authorization failure
|
||||
// when this challenge was used.
|
||||
// The type of a non-nil value is *Error.
|
||||
Error error
|
||||
}
|
||||
|
||||
// Authorization encodes an authorization response.
|
||||
type Authorization struct {
|
||||
// URI uniquely identifies a authorization.
|
||||
URI string
|
||||
|
||||
// Status identifies the status of an authorization.
|
||||
Status string
|
||||
|
||||
// Identifier is what the account is authorized to represent.
|
||||
Identifier AuthzID
|
||||
|
||||
// Challenges that the client needs to fulfill in order to prove possession
|
||||
// of the identifier (for pending authorizations).
|
||||
// For final authorizations, the challenges that were used.
|
||||
Challenges []*Challenge
|
||||
|
||||
// A collection of sets of challenges, each of which would be sufficient
|
||||
// to prove possession of the identifier.
|
||||
// Clients must complete a set of challenges that covers at least one set.
|
||||
// Challenges are identified by their indices in the challenges array.
|
||||
// If this field is empty, the client needs to complete all challenges.
|
||||
Combinations [][]int
|
||||
}
|
||||
|
||||
// AuthzID is an identifier that an account is authorized to represent.
|
||||
type AuthzID struct {
|
||||
Type string // The type of identifier, e.g. "dns".
|
||||
Value string // The identifier itself, e.g. "example.org".
|
||||
}
|
||||
|
||||
// wireAuthz is ACME JSON representation of Authorization objects.
|
||||
type wireAuthz struct {
|
||||
Status string
|
||||
Challenges []wireChallenge
|
||||
Combinations [][]int
|
||||
Identifier struct {
|
||||
Type string
|
||||
Value string
|
||||
}
|
||||
}
|
||||
|
||||
func (z *wireAuthz) authorization(uri string) *Authorization {
|
||||
a := &Authorization{
|
||||
URI: uri,
|
||||
Status: z.Status,
|
||||
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
|
||||
Combinations: z.Combinations, // shallow copy
|
||||
Challenges: make([]*Challenge, len(z.Challenges)),
|
||||
}
|
||||
for i, v := range z.Challenges {
|
||||
a.Challenges[i] = v.challenge()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (z *wireAuthz) error(uri string) *AuthorizationError {
|
||||
err := &AuthorizationError{
|
||||
URI: uri,
|
||||
Identifier: z.Identifier.Value,
|
||||
}
|
||||
for _, raw := range z.Challenges {
|
||||
if raw.Error != nil {
|
||||
err.Errors = append(err.Errors, raw.Error.error(nil))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// wireChallenge is ACME JSON challenge representation.
|
||||
type wireChallenge struct {
|
||||
URI string `json:"uri"`
|
||||
Type string
|
||||
Token string
|
||||
Status string
|
||||
Error *wireError
|
||||
}
|
||||
|
||||
func (c *wireChallenge) challenge() *Challenge {
|
||||
v := &Challenge{
|
||||
URI: c.URI,
|
||||
Type: c.Type,
|
||||
Token: c.Token,
|
||||
Status: c.Status,
|
||||
}
|
||||
if v.Status == "" {
|
||||
v.Status = StatusPending
|
||||
}
|
||||
if c.Error != nil {
|
||||
v.Error = c.Error.error(nil)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// wireError is a subset of fields of the Problem Details object
|
||||
// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
|
||||
type wireError struct {
|
||||
Status int
|
||||
Type string
|
||||
Detail string
|
||||
}
|
||||
|
||||
func (e *wireError) error(h http.Header) *Error {
|
||||
return &Error{
|
||||
StatusCode: e.Status,
|
||||
ProblemType: e.Type,
|
||||
Detail: e.Detail,
|
||||
Header: h,
|
||||
}
|
||||
}
|
||||
|
||||
// CertOption is an optional argument type for the TLS ChallengeCert methods for
|
||||
// customizing a temporary certificate for TLS-based challenges.
|
||||
type CertOption interface {
|
||||
privateCertOpt()
|
||||
}
|
||||
|
||||
// WithKey creates an option holding a private/public key pair.
|
||||
// The private part signs a certificate, and the public part represents the signee.
|
||||
func WithKey(key crypto.Signer) CertOption {
|
||||
return &certOptKey{key}
|
||||
}
|
||||
|
||||
type certOptKey struct {
|
||||
key crypto.Signer
|
||||
}
|
||||
|
||||
func (*certOptKey) privateCertOpt() {}
|
||||
|
||||
// WithTemplate creates an option for specifying a certificate template.
|
||||
// See x509.CreateCertificate for template usage details.
|
||||
//
|
||||
// In TLS ChallengeCert methods, the template is also used as parent,
|
||||
// resulting in a self-signed certificate.
|
||||
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
|
||||
func WithTemplate(t *x509.Certificate) CertOption {
|
||||
return (*certOptTemplate)(t)
|
||||
}
|
||||
|
||||
type certOptTemplate x509.Certificate
|
||||
|
||||
func (*certOptTemplate) privateCertOpt() {}
|
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
||||
2898 / PKCS #5 v2.0.
|
||||
|
||||
A key derivation function is useful when encrypting data based on a password
|
||||
or any other not-fully-random data. It uses a pseudorandom function to derive
|
||||
a secure encryption key based on the password.
|
||||
|
||||
While v2.0 of the standard defines only one pseudorandom function to use,
|
||||
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
||||
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
||||
choose, you can pass the `New` functions from the different SHA packages to
|
||||
pbkdf2.Key.
|
||||
*/
|
||||
package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Key derives a key from the password, salt and iteration count, returning a
|
||||
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||
// the supplied hash function.
|
||||
//
|
||||
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||
// doing:
|
||||
//
|
||||
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||
//
|
||||
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||
// RFC.
|
||||
//
|
||||
// Using a higher iteration count will increase the cost of an exhaustive
|
||||
// search but will also make derivation proportionally slower.
|
||||
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||
prf := hmac.New(h, password)
|
||||
hashLen := prf.Size()
|
||||
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||
|
||||
var buf [4]byte
|
||||
dk := make([]byte, 0, numBlocks*hashLen)
|
||||
U := make([]byte, hashLen)
|
||||
for block := 1; block <= numBlocks; block++ {
|
||||
// N.B.: || means concatenation, ^ means XOR
|
||||
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||
// U_1 = PRF(password, salt || uint(i))
|
||||
prf.Reset()
|
||||
prf.Write(salt)
|
||||
buf[0] = byte(block >> 24)
|
||||
buf[1] = byte(block >> 16)
|
||||
buf[2] = byte(block >> 8)
|
||||
buf[3] = byte(block)
|
||||
prf.Write(buf[:4])
|
||||
dk = prf.Sum(dk)
|
||||
T := dk[len(dk)-hashLen:]
|
||||
copy(U, T)
|
||||
|
||||
// U_n = PRF(password, U_(n-1))
|
||||
for n := 2; n <= iter; n++ {
|
||||
prf.Reset()
|
||||
prf.Write(U)
|
||||
U = U[:0]
|
||||
U = prf.Sum(U)
|
||||
for x := range U {
|
||||
T[x] ^= U[x]
|
||||
}
|
||||
}
|
||||
}
|
||||
return dk[:keyLen]
|
||||
}
|
201
vendor/gopkg.in/yaml.v2/LICENSE
generated
vendored
Normal file
201
vendor/gopkg.in/yaml.v2/LICENSE
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user