mirror of
https://github.com/astaxie/beego.git
synced 2025-07-11 20:31:00 +00:00
Compare commits
258 Commits
Author | SHA1 | Date | |
---|---|---|---|
053a075344 | |||
9dd7d19ce7 | |||
efe0f67388 | |||
de66d2bdfd | |||
0e4fe4d177 | |||
6d84db1e93 | |||
2486f3826a | |||
d7b8aa8b52 | |||
c7c0b01ec5 | |||
6d69047fff | |||
787ab12605 | |||
f4112accef | |||
42c394e28b | |||
5051d902fb | |||
48acfa08be | |||
39fc30b8b2 | |||
046cb248e0 | |||
31c746d9d7 | |||
38a2f32252 | |||
d55f54a8ab | |||
feb0e67fd7 | |||
a09bafbf2a | |||
03de7456ca | |||
78f2fd8d14 | |||
a048ed51a7 | |||
164a9231e8 | |||
aaa7e33778 | |||
f7008e2877 | |||
cf6e825547 | |||
38f9a3c49e | |||
f18283a517 | |||
61aec396e0 | |||
5ba9e63086 | |||
5acc56648d | |||
bc773039ca | |||
868fc2a29f | |||
81f69f12ab | |||
0711c3289f | |||
b8868d6d2d | |||
30bbc81a2e | |||
1a3f1d66c1 | |||
6bdd152d91 | |||
443c77b303 | |||
0dff771707 | |||
c9b6e4f825 | |||
abd02c7de4 | |||
eb4e0e4030 | |||
96dffcd27f | |||
0d0d87f600 | |||
2c779a4287 | |||
f25893832f | |||
af73a2d515 | |||
67a6b8723c | |||
fdccd85330 | |||
ca394fc8ab | |||
9c9ba0129f | |||
b61c91d93d | |||
f15732798f | |||
efbe655d6a | |||
27ced1d9c3 | |||
8f6bce3b87 | |||
be75f93d43 | |||
541fb181fe | |||
293b54192f | |||
0e0718d110 | |||
6fec0a7831 | |||
654ebebe3c | |||
08c3ca642e | |||
b3c46a87ac | |||
464d080518 | |||
227c04c9e6 | |||
e5d68aceed | |||
67d9241abc | |||
110dbcb31f | |||
740bf72f0c | |||
6b3b8607a0 | |||
b21c59ee70 | |||
fc2c96a177 | |||
87ba3f3cd3 | |||
b80b7b06fc | |||
ad6c97ec1b | |||
d3d97de312 | |||
bf915c3280 | |||
19c5cd130d | |||
1df2662924 | |||
f979050a45 | |||
45b68d444d | |||
732f79e758 | |||
4e954e32b8 | |||
92e81ccf50 | |||
91f2005067 | |||
7c80bf6f9d | |||
cc2c98c112 | |||
c3c0adbf55 | |||
04c305f273 | |||
8c8cf46b55 | |||
e96ae0c24a | |||
98a3cda260 | |||
1fd7fa5df7 | |||
3d3f2ed4c5 | |||
0f73050567 | |||
a40899e6be | |||
a9a15e2c54 | |||
896c258e44 | |||
6df42d63e2 | |||
33bf80b052 | |||
d5c1c0e9a4 | |||
8e61a6a6de | |||
ccaa2dd9e0 | |||
507ea757d7 | |||
9d526dfd50 | |||
ba89253e4a | |||
0d6f190e72 | |||
91b9a65db0 | |||
e96a5fb3ca | |||
f5f70f386d | |||
242efcf7fa | |||
51cc6fc257 | |||
5fb29cb772 | |||
2da894d4a7 | |||
2623b15ce0 | |||
6db9ad7002 | |||
889408136b | |||
886fefe738 | |||
768406f134 | |||
075e63b2bd | |||
0057c08a90 | |||
09b073356d | |||
3c9ed48630 | |||
65d8b4f544 | |||
6d18d4dcdd | |||
21fe2d519e | |||
9a7554fa01 | |||
37d1c13603 | |||
5ed112e946 | |||
453f112094 | |||
faa3341603 | |||
ee9cf05796 | |||
6de538b136 | |||
47c1072b78 | |||
e81f1e53bf | |||
cf92d2c6ef | |||
0507076c3f | |||
59fd3952b7 | |||
7fd80e6aa1 | |||
24fa6189b5 | |||
0bde9cbd91 | |||
122414d789 | |||
aac69674ad | |||
1a42154c64 | |||
e81cca304b | |||
07aa97aa9a | |||
94fba0b2aa | |||
80aa47f605 | |||
f16688817a | |||
2670a86005 | |||
0e369e6df8 | |||
84443b9c05 | |||
33be6803a3 | |||
aef2f1c66e | |||
619cd2d908 | |||
4613acd88e | |||
bf5c5626ab | |||
0fbbc67c3d | |||
3e1916ec3c | |||
b068a676dd | |||
ed73bdcfab | |||
ae94b705ea | |||
08fb921053 | |||
e67e57f8fb | |||
b30969704a | |||
646acc423e | |||
c3a81a23f9 | |||
103dd22151 | |||
ec6cb43711 | |||
84cb9a5986 | |||
9b8bc2aef7 | |||
9710d9e961 | |||
58bb49a78c | |||
df37739c7d | |||
a5dd5d161d | |||
6827107177 | |||
a30a89e57e | |||
d352e4abcb | |||
a948d4c1e1 | |||
b7eb3963f5 | |||
f7afb3cb75 | |||
348bf51a42 | |||
dfea2cc5f3 | |||
532eab8e1d | |||
3872382a4b | |||
4018693fbd | |||
3b829504f6 | |||
80fa51468c | |||
3504d2a4da | |||
229d8b9530 | |||
9536d460d0 | |||
e211e4839c | |||
3332dbe595 | |||
b9c8c08c03 | |||
663f22d849 | |||
fbaa4d1233 | |||
7dc8991140 | |||
0ce70b8c99 | |||
b169ea4b63 | |||
72ec4df679 | |||
b91263a254 | |||
e91afb1938 | |||
74eb613919 | |||
32d4310861 | |||
c56704f3fd | |||
c5118e9535 | |||
9b57566963 | |||
8d59e7afd1 | |||
fd733f76f0 | |||
37d4bb3df5 | |||
5697c6d7cc | |||
51a6162363 | |||
5a12b3d020 | |||
bebd2c469d | |||
d15e66a4ff | |||
c04d43695c | |||
d813334a24 | |||
9fef2f2eb4 | |||
0c746f4547 | |||
f5c8b1c6ac | |||
4921014c64 | |||
520753415f | |||
3162da131d | |||
a7354d2d08 | |||
07a9a2d0f3 | |||
c8c25549e7 | |||
6641a436a2 | |||
1dd50fb65f | |||
4bc4f77c29 | |||
ef36ecd376 | |||
c6cef853c7 | |||
33ad8d5db4 | |||
33e6d57754 | |||
afa57ca1f2 | |||
510dd02a06 | |||
166e88c103 | |||
51b6adeb24 | |||
51c19c374a | |||
b23452dc3f | |||
f61038e6bd | |||
b9117e2ff1 | |||
5a7a3da909 | |||
3f4502990a | |||
e14113aa0e | |||
47ef2b343e | |||
80dcdb8645 | |||
4e8f212069 | |||
eb71d0ea7f | |||
b24ddb953c | |||
12f8fbe37f | |||
b9e3cbbf44 | |||
3c0c87f473 |
16
.travis.yml
16
.travis.yml
@ -1,9 +1,8 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.6.4
|
- "1.9.7"
|
||||||
- 1.7.5
|
- "1.10.3"
|
||||||
- 1.8.1
|
|
||||||
services:
|
services:
|
||||||
- redis-server
|
- redis-server
|
||||||
- mysql
|
- mysql
|
||||||
@ -11,7 +10,6 @@ services:
|
|||||||
- memcached
|
- memcached
|
||||||
env:
|
env:
|
||||||
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
|
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
|
||||||
- ORM_DRIVER=mysql ORM_SOURCE="root:@/orm_test?charset=utf8"
|
|
||||||
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
||||||
before_install:
|
before_install:
|
||||||
- git clone git://github.com/ideawu/ssdb.git
|
- git clone git://github.com/ideawu/ssdb.git
|
||||||
@ -23,10 +21,11 @@ install:
|
|||||||
- go get github.com/go-sql-driver/mysql
|
- go get github.com/go-sql-driver/mysql
|
||||||
- go get github.com/mattn/go-sqlite3
|
- go get github.com/mattn/go-sqlite3
|
||||||
- go get github.com/bradfitz/gomemcache/memcache
|
- go get github.com/bradfitz/gomemcache/memcache
|
||||||
- go get github.com/garyburd/redigo/redis
|
- go get github.com/gomodule/redigo/redis
|
||||||
- go get github.com/beego/x2j
|
- go get github.com/beego/x2j
|
||||||
- go get github.com/couchbase/go-couchbase
|
- go get github.com/couchbase/go-couchbase
|
||||||
- go get github.com/beego/goyaml2
|
- go get github.com/beego/goyaml2
|
||||||
|
- go get gopkg.in/yaml.v2
|
||||||
- go get github.com/belogik/goes
|
- go get github.com/belogik/goes
|
||||||
- go get github.com/siddontang/ledisdb/config
|
- go get github.com/siddontang/ledisdb/config
|
||||||
- go get github.com/siddontang/ledisdb/ledis
|
- go get github.com/siddontang/ledisdb/ledis
|
||||||
@ -39,13 +38,14 @@ install:
|
|||||||
- go get -u github.com/mdempsky/unconvert
|
- go get -u github.com/mdempsky/unconvert
|
||||||
- go get -u github.com/gordonklaus/ineffassign
|
- go get -u github.com/gordonklaus/ineffassign
|
||||||
- go get -u github.com/golang/lint/golint
|
- go get -u github.com/golang/lint/golint
|
||||||
|
- go get -u github.com/go-redis/redis
|
||||||
before_script:
|
before_script:
|
||||||
- psql --version
|
- psql --version
|
||||||
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
|
- 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' = '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 [ '$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 "go get github.com/golang/lint/golint; golint ./...;"
|
||||||
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go tool vet .; fi"
|
- sh -c "go list ./... | grep -v vendor | xargs go vet -v"
|
||||||
- mkdir -p res/var
|
- mkdir -p res/var
|
||||||
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
|
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
|
||||||
after_script:
|
after_script:
|
||||||
@ -59,4 +59,4 @@ script:
|
|||||||
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
|
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
|
||||||
- golint ./...
|
- golint ./...
|
||||||
addons:
|
addons:
|
||||||
postgresql: "9.4"
|
postgresql: "9.6"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# Beego [](https://travis-ci.org/astaxie/beego) [](http://godoc.org/github.com/astaxie/beego) [](http://golangfoundation.org)
|
# Beego [](https://travis-ci.org/astaxie/beego) [](http://godoc.org/github.com/astaxie/beego) [](http://golangfoundation.org) [](https://goreportcard.com/report/github.com/astaxie/beego)
|
||||||
|
|
||||||
|
|
||||||
beego is used for rapid development of RESTful APIs, web apps and backend services in Go.
|
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.
|
It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding.
|
||||||
|
12
admin.go
12
admin.go
@ -76,6 +76,18 @@ func adminIndex(rw http.ResponseWriter, r *http.Request) {
|
|||||||
func qpsIndex(rw http.ResponseWriter, r *http.Request) {
|
func qpsIndex(rw http.ResponseWriter, r *http.Request) {
|
||||||
data := make(map[interface{}]interface{})
|
data := make(map[interface{}]interface{})
|
||||||
data["Content"] = toolbox.StatisticsMap.GetMap()
|
data["Content"] = toolbox.StatisticsMap.GetMap()
|
||||||
|
|
||||||
|
// do html escape before display path, avoid xss
|
||||||
|
if content, ok := (data["Content"]).(map[string]interface{}); ok {
|
||||||
|
if resultLists, ok := (content["Data"]).([][]string); ok {
|
||||||
|
for i := range resultLists {
|
||||||
|
if len(resultLists[i]) > 0 {
|
||||||
|
resultLists[i][0] = template.HTMLEscapeString(resultLists[i][0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
execTpl(rw, data, qpsTpl, defaultScriptsTpl)
|
execTpl(rw, data, qpsTpl, defaultScriptsTpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +67,8 @@ func oldMap() map[string]interface{} {
|
|||||||
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
|
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
|
||||||
m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly
|
m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly
|
||||||
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
|
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
|
||||||
|
m["BConfig.Log.EnableStaticLogs"] = BConfig.Log.EnableStaticLogs
|
||||||
|
m["BConfig.Log.AccessLogsFormat"] = BConfig.Log.AccessLogsFormat
|
||||||
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
|
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
|
||||||
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
|
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
|
||||||
return m
|
return m
|
||||||
|
151
app.go
151
app.go
@ -15,17 +15,22 @@
|
|||||||
package beego
|
package beego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/fcgi"
|
"net/http/fcgi"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/grace"
|
"github.com/astaxie/beego/grace"
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
"github.com/astaxie/beego/utils"
|
"github.com/astaxie/beego/utils"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -51,8 +56,11 @@ func NewApp() *App {
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MiddleWare function for http.Handler
|
||||||
|
type MiddleWare func(http.Handler) http.Handler
|
||||||
|
|
||||||
// Run beego application.
|
// Run beego application.
|
||||||
func (app *App) Run() {
|
func (app *App) Run(mws ...MiddleWare) {
|
||||||
addr := BConfig.Listen.HTTPAddr
|
addr := BConfig.Listen.HTTPAddr
|
||||||
|
|
||||||
if BConfig.Listen.HTTPPort != 0 {
|
if BConfig.Listen.HTTPPort != 0 {
|
||||||
@ -94,6 +102,12 @@ func (app *App) Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.Server.Handler = app.Handlers
|
app.Server.Handler = app.Handlers
|
||||||
|
for i := len(mws) - 1; i >= 0; i-- {
|
||||||
|
if mws[i] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
app.Server.Handler = mws[i](app.Server.Handler)
|
||||||
|
}
|
||||||
app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
||||||
app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
||||||
app.Server.ErrorLog = logs.GetLogger("HTTP")
|
app.Server.ErrorLog = logs.GetLogger("HTTP")
|
||||||
@ -102,9 +116,9 @@ func (app *App) Run() {
|
|||||||
if BConfig.Listen.Graceful {
|
if BConfig.Listen.Graceful {
|
||||||
httpsAddr := BConfig.Listen.HTTPSAddr
|
httpsAddr := BConfig.Listen.HTTPSAddr
|
||||||
app.Server.Addr = httpsAddr
|
app.Server.Addr = httpsAddr
|
||||||
if BConfig.Listen.EnableHTTPS {
|
if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(20 * time.Microsecond)
|
time.Sleep(1000 * time.Microsecond)
|
||||||
if BConfig.Listen.HTTPSPort != 0 {
|
if BConfig.Listen.HTTPSPort != 0 {
|
||||||
httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
||||||
app.Server.Addr = httpsAddr
|
app.Server.Addr = httpsAddr
|
||||||
@ -112,10 +126,27 @@ func (app *App) Run() {
|
|||||||
server := grace.NewServer(httpsAddr, app.Handlers)
|
server := grace.NewServer(httpsAddr, app.Handlers)
|
||||||
server.Server.ReadTimeout = app.Server.ReadTimeout
|
server.Server.ReadTimeout = app.Server.ReadTimeout
|
||||||
server.Server.WriteTimeout = app.Server.WriteTimeout
|
server.Server.WriteTimeout = app.Server.WriteTimeout
|
||||||
if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
if BConfig.Listen.EnableMutualHTTPS {
|
||||||
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
if err := server.ListenAndServeMutualTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile, BConfig.Listen.TrustCaFile); err != nil {
|
||||||
time.Sleep(100 * time.Microsecond)
|
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
endRunning <- true
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if BConfig.Listen.AutoTLS {
|
||||||
|
m := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...),
|
||||||
|
Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir),
|
||||||
|
}
|
||||||
|
app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate}
|
||||||
|
BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", ""
|
||||||
|
}
|
||||||
|
if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
||||||
|
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -139,22 +170,44 @@ func (app *App) Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run normal mode
|
// run normal mode
|
||||||
if BConfig.Listen.EnableHTTPS {
|
if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(20 * time.Microsecond)
|
time.Sleep(1000 * time.Microsecond)
|
||||||
if BConfig.Listen.HTTPSPort != 0 {
|
if BConfig.Listen.HTTPSPort != 0 {
|
||||||
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
||||||
} else if BConfig.Listen.EnableHTTP {
|
} else if BConfig.Listen.EnableHTTP {
|
||||||
BeeLogger.Info("Start https server error, confict with http.Please reset https port")
|
BeeLogger.Info("Start https server error, conflict with http. Please reset https port")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logs.Info("https server Running on https://%s", app.Server.Addr)
|
logs.Info("https server Running on https://%s", app.Server.Addr)
|
||||||
|
if BConfig.Listen.AutoTLS {
|
||||||
|
m := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...),
|
||||||
|
Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir),
|
||||||
|
}
|
||||||
|
app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate}
|
||||||
|
BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", ""
|
||||||
|
} else if BConfig.Listen.EnableMutualHTTPS {
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile)
|
||||||
|
if err != nil {
|
||||||
|
BeeLogger.Info("MutualHTTPS should provide TrustCaFile")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pool.AppendCertsFromPEM(data)
|
||||||
|
app.Server.TLSConfig = &tls.Config{
|
||||||
|
ClientCAs: pool,
|
||||||
|
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
||||||
logs.Critical("ListenAndServeTLS: ", err)
|
logs.Critical("ListenAndServeTLS: ", err)
|
||||||
time.Sleep(100 * time.Microsecond)
|
time.Sleep(100 * time.Microsecond)
|
||||||
endRunning <- true
|
endRunning <- true
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
}
|
}
|
||||||
if BConfig.Listen.EnableHTTP {
|
if BConfig.Listen.EnableHTTP {
|
||||||
go func() {
|
go func() {
|
||||||
@ -207,6 +260,84 @@ func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *A
|
|||||||
return BeeApp
|
return BeeApp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful
|
||||||
|
// in web applications that inherit most routes from a base webapp via the underscore
|
||||||
|
// import, and aim to overwrite only certain paths.
|
||||||
|
// The method parameter can be empty or "*" for all HTTP methods, or a particular
|
||||||
|
// method type (e.g. "GET" or "POST") for selective removal.
|
||||||
|
//
|
||||||
|
// Usage (replace "GET" with "*" for all methods):
|
||||||
|
// beego.UnregisterFixedRoute("/yourpreviouspath", "GET")
|
||||||
|
// beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage")
|
||||||
|
func UnregisterFixedRoute(fixedRoute string, method string) *App {
|
||||||
|
subPaths := splitPath(fixedRoute)
|
||||||
|
if method == "" || method == "*" {
|
||||||
|
for m := range HTTPMETHOD {
|
||||||
|
if _, ok := BeeApp.Handlers.routers[m]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if BeeApp.Handlers.routers[m].prefix == strings.Trim(fixedRoute, "/ ") {
|
||||||
|
findAndRemoveSingleTree(BeeApp.Handlers.routers[m])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
findAndRemoveTree(subPaths, BeeApp.Handlers.routers[m], m)
|
||||||
|
}
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
// Single HTTP method
|
||||||
|
um := strings.ToUpper(method)
|
||||||
|
if _, ok := BeeApp.Handlers.routers[um]; ok {
|
||||||
|
if BeeApp.Handlers.routers[um].prefix == strings.Trim(fixedRoute, "/ ") {
|
||||||
|
findAndRemoveSingleTree(BeeApp.Handlers.routers[um])
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
findAndRemoveTree(subPaths, BeeApp.Handlers.routers[um], um)
|
||||||
|
}
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAndRemoveTree(paths []string, entryPointTree *Tree, method string) {
|
||||||
|
for i := range entryPointTree.fixrouters {
|
||||||
|
if entryPointTree.fixrouters[i].prefix == paths[0] {
|
||||||
|
if len(paths) == 1 {
|
||||||
|
if len(entryPointTree.fixrouters[i].fixrouters) > 0 {
|
||||||
|
// If the route had children subtrees, remove just the functional leaf,
|
||||||
|
// to allow children to function as before
|
||||||
|
if len(entryPointTree.fixrouters[i].leaves) > 0 {
|
||||||
|
entryPointTree.fixrouters[i].leaves[0] = nil
|
||||||
|
entryPointTree.fixrouters[i].leaves = entryPointTree.fixrouters[i].leaves[1:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Remove the *Tree from the fixrouters slice
|
||||||
|
entryPointTree.fixrouters[i] = nil
|
||||||
|
|
||||||
|
if i == len(entryPointTree.fixrouters)-1 {
|
||||||
|
entryPointTree.fixrouters = entryPointTree.fixrouters[:i]
|
||||||
|
} else {
|
||||||
|
entryPointTree.fixrouters = append(entryPointTree.fixrouters[:i], entryPointTree.fixrouters[i+1:len(entryPointTree.fixrouters)]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
findAndRemoveTree(paths[1:], entryPointTree.fixrouters[i], method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAndRemoveSingleTree(entryPointTree *Tree) {
|
||||||
|
if entryPointTree == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(entryPointTree.fixrouters) > 0 {
|
||||||
|
// If the route had children subtrees, remove just the functional leaf,
|
||||||
|
// to allow children to function as before
|
||||||
|
if len(entryPointTree.leaves) > 0 {
|
||||||
|
entryPointTree.leaves[0] = nil
|
||||||
|
entryPointTree.leaves = entryPointTree.leaves[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Include will generate router file in the router/xxx.go from the controller's comments
|
// Include will generate router file in the router/xxx.go from the controller's comments
|
||||||
// usage:
|
// usage:
|
||||||
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})
|
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})
|
||||||
|
20
beego.go
20
beego.go
@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// VERSION represent beego web framework version.
|
// VERSION represent beego web framework version.
|
||||||
VERSION = "1.9.0"
|
VERSION = "1.10.1"
|
||||||
|
|
||||||
// DEV is for develop
|
// DEV is for develop
|
||||||
DEV = "dev"
|
DEV = "dev"
|
||||||
@ -62,11 +62,29 @@ func Run(params ...string) {
|
|||||||
if len(strs) > 1 && strs[1] != "" {
|
if len(strs) > 1 && strs[1] != "" {
|
||||||
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
|
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BConfig.Listen.Domains = params
|
||||||
}
|
}
|
||||||
|
|
||||||
BeeApp.Run()
|
BeeApp.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunWithMiddleWares Run beego application with middlewares.
|
||||||
|
func RunWithMiddleWares(addr string, mws ...MiddleWare) {
|
||||||
|
initBeforeHTTPRun()
|
||||||
|
|
||||||
|
strs := strings.Split(addr, ":")
|
||||||
|
if len(strs) > 0 && strs[0] != "" {
|
||||||
|
BConfig.Listen.HTTPAddr = strs[0]
|
||||||
|
BConfig.Listen.Domains = []string{strs[0]}
|
||||||
|
}
|
||||||
|
if len(strs) > 1 && strs[1] != "" {
|
||||||
|
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
BeeApp.Run(mws...)
|
||||||
|
}
|
||||||
|
|
||||||
func initBeforeHTTPRun() {
|
func initBeforeHTTPRun() {
|
||||||
//init hooks
|
//init hooks
|
||||||
AddAPPStartHook(
|
AddAPPStartHook(
|
||||||
|
2
cache/README.md
vendored
2
cache/README.md
vendored
@ -52,7 +52,7 @@ Configure like this:
|
|||||||
|
|
||||||
## Redis adapter
|
## Redis adapter
|
||||||
|
|
||||||
Redis adapter use the [redigo](http://github.com/garyburd/redigo) client.
|
Redis adapter use the [redigo](http://github.com/gomodule/redigo) client.
|
||||||
|
|
||||||
Configure like this:
|
Configure like this:
|
||||||
|
|
||||||
|
2
cache/cache.go
vendored
2
cache/cache.go
vendored
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package cache provide a Cache interface and some implemetn engine
|
// Package cache provide a Cache interface and some implement engine
|
||||||
// Usage:
|
// Usage:
|
||||||
//
|
//
|
||||||
// import(
|
// import(
|
||||||
|
82
cache/redis/redis.go
vendored
82
cache/redis/redis.go
vendored
@ -14,9 +14,9 @@
|
|||||||
|
|
||||||
// Package redis for cache provider
|
// Package redis for cache provider
|
||||||
//
|
//
|
||||||
// depend on github.com/garyburd/redigo/redis
|
// depend on github.com/gomodule/redigo/redis
|
||||||
//
|
//
|
||||||
// go install github.com/garyburd/redigo/redis
|
// go install github.com/gomodule/redigo/redis
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
// import(
|
// import(
|
||||||
@ -32,10 +32,11 @@ package redis
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/gomodule/redigo/redis"
|
||||||
|
|
||||||
"github.com/astaxie/beego/cache"
|
"github.com/astaxie/beego/cache"
|
||||||
)
|
)
|
||||||
@ -52,6 +53,7 @@ type Cache struct {
|
|||||||
dbNum int
|
dbNum int
|
||||||
key string
|
key string
|
||||||
password string
|
password string
|
||||||
|
maxIdle int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRedisCache create new redis cache with default collection name.
|
// NewRedisCache create new redis cache with default collection name.
|
||||||
@ -59,14 +61,23 @@ func NewRedisCache() cache.Cache {
|
|||||||
return &Cache{key: DefaultKey}
|
return &Cache{key: DefaultKey}
|
||||||
}
|
}
|
||||||
|
|
||||||
// actually do the redis cmds
|
// actually do the redis cmds, args[0] must be the key name.
|
||||||
func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return nil, errors.New("missing required arguments")
|
||||||
|
}
|
||||||
|
args[0] = rc.associate(args[0])
|
||||||
c := rc.p.Get()
|
c := rc.p.Get()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
return c.Do(commandName, args...)
|
return c.Do(commandName, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// associate with config key.
|
||||||
|
func (rc *Cache) associate(originKey interface{}) string {
|
||||||
|
return fmt.Sprintf("%s:%s", rc.key, originKey)
|
||||||
|
}
|
||||||
|
|
||||||
// Get cache from redis.
|
// Get cache from redis.
|
||||||
func (rc *Cache) Get(key string) interface{} {
|
func (rc *Cache) Get(key string) interface{} {
|
||||||
if v, err := rc.do("GET", key); err == nil {
|
if v, err := rc.do("GET", key); err == nil {
|
||||||
@ -77,57 +88,28 @@ func (rc *Cache) Get(key string) interface{} {
|
|||||||
|
|
||||||
// GetMulti get cache from redis.
|
// GetMulti get cache from redis.
|
||||||
func (rc *Cache) GetMulti(keys []string) []interface{} {
|
func (rc *Cache) GetMulti(keys []string) []interface{} {
|
||||||
size := len(keys)
|
|
||||||
var rv []interface{}
|
|
||||||
c := rc.p.Get()
|
c := rc.p.Get()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
var err error
|
var args []interface{}
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
err = c.Send("GET", key)
|
args = append(args, rc.associate(key))
|
||||||
if err != nil {
|
|
||||||
goto ERROR
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err = c.Flush(); err != nil {
|
values, err := redis.Values(c.Do("MGET", args...))
|
||||||
goto ERROR
|
if err != nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
for i := 0; i < size; i++ {
|
return values
|
||||||
if v, err := c.Receive(); err == nil {
|
|
||||||
rv = append(rv, v.([]byte))
|
|
||||||
} else {
|
|
||||||
rv = append(rv, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rv
|
|
||||||
ERROR:
|
|
||||||
rv = rv[0:0]
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
rv = append(rv, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put put cache to redis.
|
// Put put cache to redis.
|
||||||
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
|
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||||
var err error
|
_, err := rc.do("SETEX", key, int64(timeout/time.Second), val)
|
||||||
if _, err = rc.do("SETEX", key, int64(timeout/time.Second), val); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = rc.do("HSET", rc.key, key, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete delete cache in redis.
|
// Delete delete cache in redis.
|
||||||
func (rc *Cache) Delete(key string) error {
|
func (rc *Cache) Delete(key string) error {
|
||||||
var err error
|
_, err := rc.do("DEL", key)
|
||||||
if _, err = rc.do("DEL", key); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = rc.do("HDEL", rc.key, key)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,11 +119,6 @@ func (rc *Cache) IsExist(key string) bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !v {
|
|
||||||
if _, err = rc.do("HDEL", rc.key, key); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,16 +136,17 @@ func (rc *Cache) Decr(key string) error {
|
|||||||
|
|
||||||
// ClearAll clean all cache in redis. delete this redis collection.
|
// ClearAll clean all cache in redis. delete this redis collection.
|
||||||
func (rc *Cache) ClearAll() error {
|
func (rc *Cache) ClearAll() error {
|
||||||
cachedKeys, err := redis.Strings(rc.do("HKEYS", rc.key))
|
c := rc.p.Get()
|
||||||
|
defer c.Close()
|
||||||
|
cachedKeys, err := redis.Strings(c.Do("KEYS", rc.key+":*"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, str := range cachedKeys {
|
for _, str := range cachedKeys {
|
||||||
if _, err = rc.do("DEL", str); err != nil {
|
if _, err = c.Do("DEL", str); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = rc.do("DEL", rc.key)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,10 +170,14 @@ func (rc *Cache) StartAndGC(config string) error {
|
|||||||
if _, ok := cf["password"]; !ok {
|
if _, ok := cf["password"]; !ok {
|
||||||
cf["password"] = ""
|
cf["password"] = ""
|
||||||
}
|
}
|
||||||
|
if _, ok := cf["maxIdle"]; !ok {
|
||||||
|
cf["maxIdle"] = "3"
|
||||||
|
}
|
||||||
rc.key = cf["key"]
|
rc.key = cf["key"]
|
||||||
rc.conninfo = cf["conn"]
|
rc.conninfo = cf["conn"]
|
||||||
rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
|
rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
|
||||||
rc.password = cf["password"]
|
rc.password = cf["password"]
|
||||||
|
rc.maxIdle, _ = strconv.Atoi(cf["maxIdle"])
|
||||||
|
|
||||||
rc.connectInit()
|
rc.connectInit()
|
||||||
|
|
||||||
@ -229,7 +211,7 @@ func (rc *Cache) connectInit() {
|
|||||||
}
|
}
|
||||||
// initialize a new pool
|
// initialize a new pool
|
||||||
rc.p = &redis.Pool{
|
rc.p = &redis.Pool{
|
||||||
MaxIdle: 3,
|
MaxIdle: rc.maxIdle,
|
||||||
IdleTimeout: 180 * time.Second,
|
IdleTimeout: 180 * time.Second,
|
||||||
Dial: dialFunc,
|
Dial: dialFunc,
|
||||||
}
|
}
|
||||||
|
2
cache/redis/redis_test.go
vendored
2
cache/redis/redis_test.go
vendored
@ -19,7 +19,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/cache"
|
"github.com/astaxie/beego/cache"
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/gomodule/redigo/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRedisCache(t *testing.T) {
|
func TestRedisCache(t *testing.T) {
|
||||||
|
79
config.go
79
config.go
@ -49,22 +49,27 @@ type Config struct {
|
|||||||
|
|
||||||
// Listen holds for http and https related config
|
// Listen holds for http and https related config
|
||||||
type Listen struct {
|
type Listen struct {
|
||||||
Graceful bool // Graceful means use graceful module to start the server
|
Graceful bool // Graceful means use graceful module to start the server
|
||||||
ServerTimeOut int64
|
ServerTimeOut int64
|
||||||
ListenTCP4 bool
|
ListenTCP4 bool
|
||||||
EnableHTTP bool
|
EnableHTTP bool
|
||||||
HTTPAddr string
|
HTTPAddr string
|
||||||
HTTPPort int
|
HTTPPort int
|
||||||
EnableHTTPS bool
|
AutoTLS bool
|
||||||
HTTPSAddr string
|
Domains []string
|
||||||
HTTPSPort int
|
TLSCacheDir string
|
||||||
HTTPSCertFile string
|
EnableHTTPS bool
|
||||||
HTTPSKeyFile string
|
EnableMutualHTTPS bool
|
||||||
EnableAdmin bool
|
HTTPSAddr string
|
||||||
AdminAddr string
|
HTTPSPort int
|
||||||
AdminPort int
|
HTTPSCertFile string
|
||||||
EnableFcgi bool
|
HTTPSKeyFile string
|
||||||
EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O
|
TrustCaFile string
|
||||||
|
EnableAdmin bool
|
||||||
|
AdminAddr string
|
||||||
|
AdminPort int
|
||||||
|
EnableFcgi bool
|
||||||
|
EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebConfig holds web related config
|
// WebConfig holds web related config
|
||||||
@ -96,16 +101,18 @@ type SessionConfig struct {
|
|||||||
SessionAutoSetCookie bool
|
SessionAutoSetCookie bool
|
||||||
SessionDomain string
|
SessionDomain string
|
||||||
SessionDisableHTTPOnly bool // used to allow for cross domain cookies/javascript cookies.
|
SessionDisableHTTPOnly bool // used to allow for cross domain cookies/javascript cookies.
|
||||||
SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers
|
SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers
|
||||||
SessionNameInHTTPHeader string
|
SessionNameInHTTPHeader string
|
||||||
SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params
|
SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogConfig holds Log related config
|
// LogConfig holds Log related config
|
||||||
type LogConfig struct {
|
type LogConfig struct {
|
||||||
AccessLogs bool
|
AccessLogs bool
|
||||||
FileLineNum bool
|
EnableStaticLogs bool //log static files requests default: false
|
||||||
Outputs map[string]string // Store Adaptor : config
|
AccessLogsFormat string //access log format: JSON_FORMAT, APACHE_FORMAT or empty string
|
||||||
|
FileLineNum bool
|
||||||
|
Outputs map[string]string // Store Adaptor : config
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -134,9 +141,13 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
appConfigPath = filepath.Join(workPath, "conf", "app.conf")
|
var filename = "app.conf"
|
||||||
|
if os.Getenv("BEEGO_RUNMODE") != "" {
|
||||||
|
filename = os.Getenv("BEEGO_RUNMODE") + ".app.conf"
|
||||||
|
}
|
||||||
|
appConfigPath = filepath.Join(workPath, "conf", filename)
|
||||||
if !utils.FileExists(appConfigPath) {
|
if !utils.FileExists(appConfigPath) {
|
||||||
appConfigPath = filepath.Join(AppPath, "conf", "app.conf")
|
appConfigPath = filepath.Join(AppPath, "conf", filename)
|
||||||
if !utils.FileExists(appConfigPath) {
|
if !utils.FileExists(appConfigPath) {
|
||||||
AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
|
AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
|
||||||
return
|
return
|
||||||
@ -175,13 +186,18 @@ func recoverPanic(ctx *context.Context) {
|
|||||||
if BConfig.RunMode == DEV && BConfig.EnableErrorsRender {
|
if BConfig.RunMode == DEV && BConfig.EnableErrorsRender {
|
||||||
showErr(err, ctx, stack)
|
showErr(err, ctx, stack)
|
||||||
}
|
}
|
||||||
|
if ctx.Output.Status != 0 {
|
||||||
|
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
|
||||||
|
} else {
|
||||||
|
ctx.ResponseWriter.WriteHeader(500)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBConfig() *Config {
|
func newBConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
AppName: "beego",
|
AppName: "beego",
|
||||||
RunMode: DEV,
|
RunMode: PROD,
|
||||||
RouterCaseSensitive: true,
|
RouterCaseSensitive: true,
|
||||||
ServerName: "beegoServer:" + VERSION,
|
ServerName: "beegoServer:" + VERSION,
|
||||||
RecoverPanic: true,
|
RecoverPanic: true,
|
||||||
@ -196,6 +212,9 @@ func newBConfig() *Config {
|
|||||||
ServerTimeOut: 0,
|
ServerTimeOut: 0,
|
||||||
ListenTCP4: false,
|
ListenTCP4: false,
|
||||||
EnableHTTP: true,
|
EnableHTTP: true,
|
||||||
|
AutoTLS: false,
|
||||||
|
Domains: []string{},
|
||||||
|
TLSCacheDir: ".",
|
||||||
HTTPAddr: "",
|
HTTPAddr: "",
|
||||||
HTTPPort: 8080,
|
HTTPPort: 8080,
|
||||||
EnableHTTPS: false,
|
EnableHTTPS: false,
|
||||||
@ -233,15 +252,17 @@ func newBConfig() *Config {
|
|||||||
SessionCookieLifeTime: 0, //set cookie default is the browser life
|
SessionCookieLifeTime: 0, //set cookie default is the browser life
|
||||||
SessionAutoSetCookie: true,
|
SessionAutoSetCookie: true,
|
||||||
SessionDomain: "",
|
SessionDomain: "",
|
||||||
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
|
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
|
||||||
SessionNameInHTTPHeader: "Beegosessionid",
|
SessionNameInHTTPHeader: "Beegosessionid",
|
||||||
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
|
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Log: LogConfig{
|
Log: LogConfig{
|
||||||
AccessLogs: false,
|
AccessLogs: false,
|
||||||
FileLineNum: true,
|
EnableStaticLogs: false,
|
||||||
Outputs: map[string]string{"console": ""},
|
AccessLogsFormat: "APACHE_FORMAT",
|
||||||
|
FileLineNum: true,
|
||||||
|
Outputs: map[string]string{"console": ""},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,12 +150,12 @@ func ExpandValueEnv(value string) (realValue string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
key := ""
|
key := ""
|
||||||
defalutV := ""
|
defaultV := ""
|
||||||
// value start with "${"
|
// value start with "${"
|
||||||
for i := 2; i < vLen; i++ {
|
for i := 2; i < vLen; i++ {
|
||||||
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
|
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
|
||||||
key = value[2:i]
|
key = value[2:i]
|
||||||
defalutV = value[i+2 : vLen-1] // other string is default value.
|
defaultV = value[i+2 : vLen-1] // other string is default value.
|
||||||
break
|
break
|
||||||
} else if value[i] == '}' {
|
} else if value[i] == '}' {
|
||||||
key = value[2:i]
|
key = value[2:i]
|
||||||
@ -165,7 +165,7 @@ func ExpandValueEnv(value string) (realValue string) {
|
|||||||
|
|
||||||
realValue = os.Getenv(key)
|
realValue = os.Getenv(key)
|
||||||
if realValue == "" {
|
if realValue == "" {
|
||||||
realValue = defalutV
|
realValue = defaultV
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -126,7 +126,7 @@ func (c *fakeConfigContainer) SaveConfigFile(filename string) error {
|
|||||||
|
|
||||||
var _ Configer = new(fakeConfigContainer)
|
var _ Configer = new(fakeConfigContainer)
|
||||||
|
|
||||||
// NewFakeConfig return a fake Congiger
|
// NewFakeConfig return a fake Configer
|
||||||
func NewFakeConfig() Configer {
|
func NewFakeConfig() Configer {
|
||||||
return &fakeConfigContainer{
|
return &fakeConfigContainer{
|
||||||
data: make(map[string]string),
|
data: make(map[string]string),
|
||||||
|
@ -215,7 +215,7 @@ func (c *IniConfigContainer) Bool(key string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultBool returns the boolean value for a given key.
|
// DefaultBool returns the boolean value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||||
v, err := c.Bool(key)
|
v, err := c.Bool(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -230,7 +230,7 @@ func (c *IniConfigContainer) Int(key string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt returns the integer value for a given key.
|
// DefaultInt returns the integer value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
|
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
v, err := c.Int(key)
|
v, err := c.Int(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -245,7 +245,7 @@ func (c *IniConfigContainer) Int64(key string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt64 returns the int64 value for a given key.
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
v, err := c.Int64(key)
|
v, err := c.Int64(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -260,7 +260,7 @@ func (c *IniConfigContainer) Float(key string) (float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultFloat returns the float64 value for a given key.
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
v, err := c.Float(key)
|
v, err := c.Float(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -275,7 +275,7 @@ func (c *IniConfigContainer) String(key string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultString returns the string value for a given key.
|
// DefaultString returns the string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
|
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
v := c.String(key)
|
v := c.String(key)
|
||||||
if v == "" {
|
if v == "" {
|
||||||
@ -295,7 +295,7 @@ func (c *IniConfigContainer) Strings(key string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultStrings returns the []string value for a given key.
|
// DefaultStrings returns the []string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
v := c.Strings(key)
|
v := c.Strings(key)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
@ -314,7 +314,7 @@ func (c *IniConfigContainer) GetSection(section string) (map[string]string, erro
|
|||||||
|
|
||||||
// SaveConfigFile save the config into file.
|
// SaveConfigFile save the config into file.
|
||||||
//
|
//
|
||||||
// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Funcation.
|
// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Function.
|
||||||
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||||
// Write configuration file by filename.
|
// Write configuration file by filename.
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
|
@ -101,7 +101,7 @@ func (c *JSONConfigContainer) Int(key string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt returns the integer value for a given key.
|
// DefaultInt returns the integer value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int {
|
func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
if v, err := c.Int(key); err == nil {
|
if v, err := c.Int(key); err == nil {
|
||||||
return v
|
return v
|
||||||
@ -122,7 +122,7 @@ func (c *JSONConfigContainer) Int64(key string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt64 returns the int64 value for a given key.
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
if v, err := c.Int64(key); err == nil {
|
if v, err := c.Int64(key); err == nil {
|
||||||
return v
|
return v
|
||||||
@ -143,7 +143,7 @@ func (c *JSONConfigContainer) Float(key string) (float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultFloat returns the float64 value for a given key.
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
if v, err := c.Float(key); err == nil {
|
if v, err := c.Float(key); err == nil {
|
||||||
return v
|
return v
|
||||||
@ -163,7 +163,7 @@ func (c *JSONConfigContainer) String(key string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultString returns the string value for a given key.
|
// DefaultString returns the string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string {
|
func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
// TODO FIXME should not use "" to replace non existence
|
// TODO FIXME should not use "" to replace non existence
|
||||||
if v := c.String(key); v != "" {
|
if v := c.String(key); v != "" {
|
||||||
@ -182,7 +182,7 @@ func (c *JSONConfigContainer) Strings(key string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultStrings returns the []string value for a given key.
|
// DefaultStrings returns the []string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
if v := c.Strings(key); v != nil {
|
if v := c.Strings(key); v != nil {
|
||||||
return v
|
return v
|
||||||
|
@ -216,7 +216,7 @@ func TestJson(t *testing.T) {
|
|||||||
t.Error("unknown keys should return an error when expecting a Bool")
|
t.Error("unknown keys should return an error when expecting a Bool")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !jsonconf.DefaultBool("unknow", true) {
|
if !jsonconf.DefaultBool("unknown", true) {
|
||||||
t.Error("unknown keys with default value wrong")
|
t.Error("unknown keys with default value wrong")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ func (c *ConfigContainer) Int(key string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt returns the integer value for a given key.
|
// DefaultInt returns the integer value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
|
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
v, err := c.Int(key)
|
v, err := c.Int(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -117,7 +117,7 @@ func (c *ConfigContainer) Int64(key string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt64 returns the int64 value for a given key.
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
v, err := c.Int64(key)
|
v, err := c.Int64(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -133,7 +133,7 @@ func (c *ConfigContainer) Float(key string) (float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultFloat returns the float64 value for a given key.
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
v, err := c.Float(key)
|
v, err := c.Float(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -151,7 +151,7 @@ func (c *ConfigContainer) String(key string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultString returns the string value for a given key.
|
// DefaultString returns the string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
v := c.String(key)
|
v := c.String(key)
|
||||||
if v == "" {
|
if v == "" {
|
||||||
@ -170,7 +170,7 @@ func (c *ConfigContainer) Strings(key string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultStrings returns the []string value for a given key.
|
// DefaultStrings returns the []string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
v := c.Strings(key)
|
v := c.Strings(key)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
|
@ -119,7 +119,7 @@ func parseYML(buf []byte) (cnf map[string]interface{}, err error) {
|
|||||||
// ConfigContainer A Config represents the yaml configuration.
|
// ConfigContainer A Config represents the yaml configuration.
|
||||||
type ConfigContainer struct {
|
type ConfigContainer struct {
|
||||||
data map[string]interface{}
|
data map[string]interface{}
|
||||||
sync.Mutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bool returns the boolean value for a given key.
|
// Bool returns the boolean value for a given key.
|
||||||
@ -154,7 +154,7 @@ func (c *ConfigContainer) Int(key string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt returns the integer value for a given key.
|
// DefaultInt returns the integer value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
|
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
v, err := c.Int(key)
|
v, err := c.Int(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -174,7 +174,7 @@ func (c *ConfigContainer) Int64(key string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInt64 returns the int64 value for a given key.
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
v, err := c.Int64(key)
|
v, err := c.Int64(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -198,7 +198,7 @@ func (c *ConfigContainer) Float(key string) (float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultFloat returns the float64 value for a given key.
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
v, err := c.Float(key)
|
v, err := c.Float(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -218,7 +218,7 @@ func (c *ConfigContainer) String(key string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultString returns the string value for a given key.
|
// DefaultString returns the string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
v := c.String(key)
|
v := c.String(key)
|
||||||
if v == "" {
|
if v == "" {
|
||||||
@ -237,7 +237,7 @@ func (c *ConfigContainer) Strings(key string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultStrings returns the []string value for a given key.
|
// DefaultStrings returns the []string value for a given key.
|
||||||
// if err != nil return defaltval
|
// if err != nil return defaultval
|
||||||
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
v := c.Strings(key)
|
v := c.Strings(key)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
@ -285,9 +285,28 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) {
|
|||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
return nil, errors.New("key is empty")
|
return nil, errors.New("key is empty")
|
||||||
}
|
}
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
if v, ok := c.data[key]; ok {
|
keys := strings.Split(key, ".")
|
||||||
return v, nil
|
tmpData := c.data
|
||||||
|
for idx, k := range keys {
|
||||||
|
if v, ok := tmpData[k]; ok {
|
||||||
|
switch v.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
{
|
||||||
|
tmpData = v.(map[string]interface{})
|
||||||
|
if idx == len(keys) - 1 {
|
||||||
|
return tmpData, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("not exist key %q", key)
|
return nil, fmt.Errorf("not exist key %q", key)
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ func TestAssignConfig_02(t *testing.T) {
|
|||||||
|
|
||||||
jcf := &config.JSONConfig{}
|
jcf := &config.JSONConfig{}
|
||||||
bs, _ = json.Marshal(configMap)
|
bs, _ = json.Marshal(configMap)
|
||||||
ac, _ := jcf.ParseData([]byte(bs))
|
ac, _ := jcf.ParseData(bs)
|
||||||
|
|
||||||
for _, i := range []interface{}{_BConfig, &_BConfig.Listen, &_BConfig.WebConfig, &_BConfig.Log, &_BConfig.WebConfig.Session} {
|
for _, i := range []interface{}{_BConfig, &_BConfig.Listen, &_BConfig.WebConfig, &_BConfig.Log, &_BConfig.WebConfig.Session} {
|
||||||
assignSingleConfig(i, ac)
|
assignSingleConfig(i, ac)
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -36,6 +37,7 @@ var (
|
|||||||
acceptsHTMLRegex = regexp.MustCompile(`(text/html|application/xhtml\+xml)(?:,|$)`)
|
acceptsHTMLRegex = regexp.MustCompile(`(text/html|application/xhtml\+xml)(?:,|$)`)
|
||||||
acceptsXMLRegex = regexp.MustCompile(`(application/xml|text/xml)(?:,|$)`)
|
acceptsXMLRegex = regexp.MustCompile(`(application/xml|text/xml)(?:,|$)`)
|
||||||
acceptsJSONRegex = regexp.MustCompile(`(application/json)(?:,|$)`)
|
acceptsJSONRegex = regexp.MustCompile(`(application/json)(?:,|$)`)
|
||||||
|
acceptsYAMLRegex = regexp.MustCompile(`(application/x-yaml)(?:,|$)`)
|
||||||
maxParam = 50
|
maxParam = 50
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -115,9 +117,8 @@ func (input *BeegoInput) Domain() string {
|
|||||||
// if no host info in request, return localhost.
|
// if no host info in request, return localhost.
|
||||||
func (input *BeegoInput) Host() string {
|
func (input *BeegoInput) Host() string {
|
||||||
if input.Context.Request.Host != "" {
|
if input.Context.Request.Host != "" {
|
||||||
hostParts := strings.Split(input.Context.Request.Host, ":")
|
if hostPart, _, err := net.SplitHostPort(input.Context.Request.Host); err == nil {
|
||||||
if len(hostParts) > 0 {
|
return hostPart
|
||||||
return hostParts[0]
|
|
||||||
}
|
}
|
||||||
return input.Context.Request.Host
|
return input.Context.Request.Host
|
||||||
}
|
}
|
||||||
@ -203,23 +204,27 @@ func (input *BeegoInput) AcceptsXML() bool {
|
|||||||
func (input *BeegoInput) AcceptsJSON() bool {
|
func (input *BeegoInput) AcceptsJSON() bool {
|
||||||
return acceptsJSONRegex.MatchString(input.Header("Accept"))
|
return acceptsJSONRegex.MatchString(input.Header("Accept"))
|
||||||
}
|
}
|
||||||
|
// AcceptsYAML Checks if request accepts json response
|
||||||
|
func (input *BeegoInput) AcceptsYAML() bool {
|
||||||
|
return acceptsYAMLRegex.MatchString(input.Header("Accept"))
|
||||||
|
}
|
||||||
|
|
||||||
// IP returns request client ip.
|
// IP returns request client ip.
|
||||||
// if in proxy, return first proxy id.
|
// if in proxy, return first proxy id.
|
||||||
// if error, return 127.0.0.1.
|
// if error, return RemoteAddr.
|
||||||
func (input *BeegoInput) IP() string {
|
func (input *BeegoInput) IP() string {
|
||||||
ips := input.Proxy()
|
ips := input.Proxy()
|
||||||
if len(ips) > 0 && ips[0] != "" {
|
if len(ips) > 0 && ips[0] != "" {
|
||||||
rip := strings.Split(ips[0], ":")
|
rip, _, err := net.SplitHostPort(ips[0])
|
||||||
return rip[0]
|
if err != nil {
|
||||||
}
|
rip = ips[0]
|
||||||
ip := strings.Split(input.Context.Request.RemoteAddr, ":")
|
|
||||||
if len(ip) > 0 {
|
|
||||||
if ip[0] != "[" {
|
|
||||||
return ip[0]
|
|
||||||
}
|
}
|
||||||
|
return rip
|
||||||
}
|
}
|
||||||
return "127.0.0.1"
|
if ip, _, err := net.SplitHostPort(input.Context.Request.RemoteAddr); err == nil {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
return input.Context.Request.RemoteAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy returns proxy client ips slice.
|
// Proxy returns proxy client ips slice.
|
||||||
@ -253,9 +258,8 @@ func (input *BeegoInput) SubDomains() string {
|
|||||||
// Port returns request client port.
|
// Port returns request client port.
|
||||||
// when error or empty, return 80.
|
// when error or empty, return 80.
|
||||||
func (input *BeegoInput) Port() int {
|
func (input *BeegoInput) Port() int {
|
||||||
parts := strings.Split(input.Context.Request.Host, ":")
|
if _, portPart, err := net.SplitHostPort(input.Context.Request.Host); err == nil {
|
||||||
if len(parts) == 2 {
|
port, _ := strconv.Atoi(portPart)
|
||||||
port, _ := strconv.Atoi(parts[1])
|
|
||||||
return port
|
return port
|
||||||
}
|
}
|
||||||
return 80
|
return 80
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BeegoOutput does work for sending response header.
|
// BeegoOutput does work for sending response header.
|
||||||
@ -182,8 +183,8 @@ func errorRenderer(err error) Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// JSON writes json to response body.
|
// JSON writes json to response body.
|
||||||
// if coding is true, it converts utf-8 to \u0000 type.
|
// if encoding is true, it converts utf-8 to \u0000 type.
|
||||||
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) error {
|
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error {
|
||||||
output.Header("Content-Type", "application/json; charset=utf-8")
|
output.Header("Content-Type", "application/json; charset=utf-8")
|
||||||
var content []byte
|
var content []byte
|
||||||
var err error
|
var err error
|
||||||
@ -196,12 +197,26 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) e
|
|||||||
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if coding {
|
if encoding {
|
||||||
content = []byte(stringsToJSON(string(content)))
|
content = []byte(stringsToJSON(string(content)))
|
||||||
}
|
}
|
||||||
return output.Body(content)
|
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")
|
||||||
|
var content []byte
|
||||||
|
var err error
|
||||||
|
content, err = yaml.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return output.Body(content)
|
||||||
|
}
|
||||||
|
|
||||||
// JSONP writes jsonp to response body.
|
// JSONP writes jsonp to response body.
|
||||||
func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
|
func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
|
||||||
output.Header("Content-Type", "application/javascript; charset=utf-8")
|
output.Header("Content-Type", "application/javascript; charset=utf-8")
|
||||||
@ -260,7 +275,7 @@ func (output *BeegoOutput) Download(file string, filename ...string) {
|
|||||||
} else {
|
} else {
|
||||||
fName = filepath.Base(file)
|
fName = filepath.Base(file)
|
||||||
}
|
}
|
||||||
output.Header("Content-Disposition", "attachment; filename="+url.QueryEscape(fName))
|
output.Header("Content-Disposition", "attachment; filename="+url.PathEscape(fName))
|
||||||
output.Header("Content-Description", "File Transfer")
|
output.Header("Content-Description", "File Transfer")
|
||||||
output.Header("Content-Type", "application/octet-stream")
|
output.Header("Content-Type", "application/octet-stream")
|
||||||
output.Header("Content-Transfer-Encoding", "binary")
|
output.Header("Content-Transfer-Encoding", "binary")
|
||||||
@ -325,13 +340,13 @@ func (output *BeegoOutput) IsForbidden() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsNotFound returns boolean of this request is not found.
|
// IsNotFound returns boolean of this request is not found.
|
||||||
// HTTP 404 means forbidden.
|
// HTTP 404 means not found.
|
||||||
func (output *BeegoOutput) IsNotFound() bool {
|
func (output *BeegoOutput) IsNotFound() bool {
|
||||||
return output.Status == 404
|
return output.Status == 404
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsClientError returns boolean of this request client sends error data.
|
// IsClientError returns boolean of this request client sends error data.
|
||||||
// HTTP 4xx means forbidden.
|
// HTTP 4xx means client error.
|
||||||
func (output *BeegoOutput) IsClientError() bool {
|
func (output *BeegoOutput) IsClientError() bool {
|
||||||
return output.Status >= 400 && output.Status < 500
|
return output.Status >= 400 && output.Status < 500
|
||||||
}
|
}
|
||||||
@ -350,6 +365,11 @@ func stringsToJSON(str string) string {
|
|||||||
jsons.WriteRune(r)
|
jsons.WriteRune(r)
|
||||||
} else {
|
} else {
|
||||||
jsons.WriteString("\\u")
|
jsons.WriteString("\\u")
|
||||||
|
if rint < 0x100 {
|
||||||
|
jsons.WriteString("00")
|
||||||
|
} else if rint < 0x1000 {
|
||||||
|
jsons.WriteString("0")
|
||||||
|
}
|
||||||
jsons.WriteString(strconv.FormatInt(int64(rint), 16))
|
jsons.WriteString(strconv.FormatInt(int64(rint), 16))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
// MethodParamOption defines a func which apply options on a MethodParam
|
// MethodParamOption defines a func which apply options on a MethodParam
|
||||||
type MethodParamOption func(*MethodParam)
|
type MethodParamOption func(*MethodParam)
|
||||||
|
|
||||||
// IsRequired indicates that this param is required and can not be ommited from the http request
|
// IsRequired indicates that this param is required and can not be omitted from the http request
|
||||||
var IsRequired MethodParamOption = func(p *MethodParam) {
|
var IsRequired MethodParamOption = func(p *MethodParam) {
|
||||||
p.required = true
|
p.required = true
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ func checkParser(def testDefinition, t *testing.T, methodParam ...MethodParam) {
|
|||||||
}
|
}
|
||||||
convResult, err := safeConvert(reflect.ValueOf(result), toType)
|
convResult, err := safeConvert(reflect.ValueOf(result), toType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Convertion error for %v. from value: %v, toType: %v, error: %v", def.strValue, result, toType, err)
|
t.Errorf("Conversion error for %v. from value: %v, toType: %v, error: %v", def.strValue, result, toType, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(convResult.Interface(), def.expectedValue) {
|
if !reflect.DeepEqual(convResult.Interface(), def.expectedValue) {
|
||||||
|
@ -36,6 +36,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
applicationJSON = "application/json"
|
applicationJSON = "application/json"
|
||||||
applicationXML = "application/xml"
|
applicationXML = "application/xml"
|
||||||
|
applicationYAML = "application/x-yaml"
|
||||||
textXML = "text/xml"
|
textXML = "text/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -272,7 +273,22 @@ func (c *Controller) viewPath() string {
|
|||||||
|
|
||||||
// Redirect sends the redirection response to url with status code.
|
// Redirect sends the redirection response to url with status code.
|
||||||
func (c *Controller) Redirect(url string, code int) {
|
func (c *Controller) Redirect(url string, code int) {
|
||||||
|
logAccess(c.Ctx, nil, code)
|
||||||
c.Ctx.Redirect(code, url)
|
c.Ctx.Redirect(code, url)
|
||||||
|
panic(ErrAbort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
c.Data["xml"] = data
|
||||||
|
default:
|
||||||
|
c.Data["json"] = data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abort stops controller handler and show the error data if code is defined in ErrorMap or code string.
|
// Abort stops controller handler and show the error data if code is defined in ErrorMap or code string.
|
||||||
@ -347,6 +363,11 @@ func (c *Controller) ServeXML() {
|
|||||||
c.Ctx.Output.XML(c.Data["xml"], hasIndent)
|
c.Ctx.Output.XML(c.Data["xml"], hasIndent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServeXML sends xml 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
|
// ServeFormatted serve Xml OR Json, depending on the value of the Accept header
|
||||||
func (c *Controller) ServeFormatted() {
|
func (c *Controller) ServeFormatted() {
|
||||||
accept := c.Ctx.Input.Header("Accept")
|
accept := c.Ctx.Input.Header("Accept")
|
||||||
@ -355,6 +376,8 @@ func (c *Controller) ServeFormatted() {
|
|||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
case applicationXML, textXML:
|
case applicationXML, textXML:
|
||||||
c.ServeXML()
|
c.ServeXML()
|
||||||
|
case applicationYAML:
|
||||||
|
c.ServeYAML()
|
||||||
default:
|
default:
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
10
error.go
10
error.go
@ -28,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errorTypeHandler = iota
|
errorTypeHandler = iota
|
||||||
errorTypeController
|
errorTypeController
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -93,11 +93,6 @@ func showErr(err interface{}, ctx *context.Context, stack string) {
|
|||||||
"BeegoVersion": VERSION,
|
"BeegoVersion": VERSION,
|
||||||
"GoVersion": runtime.Version(),
|
"GoVersion": runtime.Version(),
|
||||||
}
|
}
|
||||||
if ctx.Output.Status != 0 {
|
|
||||||
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
|
|
||||||
} else {
|
|
||||||
ctx.ResponseWriter.WriteHeader(500)
|
|
||||||
}
|
|
||||||
t.Execute(ctx.ResponseWriter, data)
|
t.Execute(ctx.ResponseWriter, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,6 +434,9 @@ func exception(errCode string, ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func executeError(err *errorInfo, ctx *context.Context, code int) {
|
func executeError(err *errorInfo, ctx *context.Context, code int) {
|
||||||
|
//make sure to log the error in the access log
|
||||||
|
logAccess(ctx, nil, code)
|
||||||
|
|
||||||
if err.errorType == errorTypeHandler {
|
if err.errorType == errorTypeHandler {
|
||||||
ctx.ResponseWriter.WriteHeader(code)
|
ctx.ResponseWriter.WriteHeader(code)
|
||||||
err.handler(ctx.ResponseWriter, ctx.Request)
|
err.handler(ctx.ResponseWriter, ctx.Request)
|
||||||
|
@ -2,7 +2,9 @@ package grace
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -65,7 +67,7 @@ func (srv *Server) ListenAndServe() (err error) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = process.Kill()
|
err = process.Signal(syscall.SIGTERM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -114,6 +116,62 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
|
|||||||
srv.tlsInnerListener = newGraceListener(l, srv)
|
srv.tlsInnerListener = newGraceListener(l, srv)
|
||||||
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
|
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
|
||||||
|
|
||||||
|
if srv.isChild {
|
||||||
|
process, err := os.FindProcess(os.Getppid())
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = process.Signal(syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Println(os.Getpid(), srv.Addr)
|
||||||
|
return srv.Serve()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServeMutualTLS listens on the TCP network address srv.Addr and then calls
|
||||||
|
// Serve to handle requests on incoming mutual TLS connections.
|
||||||
|
func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string) (err error) {
|
||||||
|
addr := srv.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":https"
|
||||||
|
}
|
||||||
|
|
||||||
|
if srv.TLSConfig == nil {
|
||||||
|
srv.TLSConfig = &tls.Config{}
|
||||||
|
}
|
||||||
|
if srv.TLSConfig.NextProtos == nil {
|
||||||
|
srv.TLSConfig.NextProtos = []string{"http/1.1"}
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.TLSConfig.Certificates = make([]tls.Certificate, 1)
|
||||||
|
srv.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srv.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
data, err := ioutil.ReadFile(trustFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pool.AppendCertsFromPEM(data)
|
||||||
|
srv.TLSConfig.ClientCAs = pool
|
||||||
|
log.Println("Mutual HTTPS")
|
||||||
|
go srv.handleSignals()
|
||||||
|
|
||||||
|
l, 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)
|
||||||
|
|
||||||
if srv.isChild {
|
if srv.isChild {
|
||||||
process, err := os.FindProcess(os.Getppid())
|
process, err := os.FindProcess(os.Getppid())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -50,6 +50,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultSetting = BeegoHTTPSettings{
|
var defaultSetting = BeegoHTTPSettings{
|
||||||
@ -318,6 +319,34 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XMLBody adds request raw body encoding by XML.
|
||||||
|
func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
||||||
|
if b.req.Body == nil && obj != nil {
|
||||||
|
byts, err := xml.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
|
||||||
|
b.req.ContentLength = int64(len(byts))
|
||||||
|
b.req.Header.Set("Content-Type", "application/xml")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLBody adds request raw body encoding by YAML.
|
||||||
|
func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
||||||
|
if b.req.Body == nil && obj != nil {
|
||||||
|
byts, err := yaml.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
|
||||||
|
b.req.ContentLength = int64(len(byts))
|
||||||
|
b.req.Header.Set("Content-Type", "application/x+yaml")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
// JSONBody adds request raw body encoding by JSON.
|
// JSONBody adds request raw body encoding by JSON.
|
||||||
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
||||||
if b.req.Body == nil && obj != nil {
|
if b.req.Body == nil && obj != nil {
|
||||||
@ -417,12 +446,12 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.buildURL(paramBody)
|
b.buildURL(paramBody)
|
||||||
url, err := url.Parse(b.url)
|
urlParsed, err := url.Parse(b.url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.req.URL = url
|
b.req.URL = urlParsed
|
||||||
|
|
||||||
trans := b.setting.Transport
|
trans := b.setting.Transport
|
||||||
|
|
||||||
@ -432,7 +461,7 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
|
|||||||
TLSClientConfig: b.setting.TLSClientConfig,
|
TLSClientConfig: b.setting.TLSClientConfig,
|
||||||
Proxy: b.setting.Proxy,
|
Proxy: b.setting.Proxy,
|
||||||
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
|
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
|
||||||
MaxIdleConnsPerHost: -1,
|
MaxIdleConnsPerHost: 100,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if b.transport is *http.Transport then set the settings.
|
// if b.transport is *http.Transport then set the settings.
|
||||||
@ -567,6 +596,16 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error {
|
|||||||
return xml.Unmarshal(data, v)
|
return xml.Unmarshal(data, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToYAML returns the map that marshals from the body bytes as yaml in response .
|
||||||
|
// it calls Response inner.
|
||||||
|
func (b *BeegoHTTPRequest) ToYAML(v interface{}) error {
|
||||||
|
data, err := b.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return yaml.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
// Response executes request client gets response mannually.
|
// Response executes request client gets response mannually.
|
||||||
func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
|
func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
|
||||||
return b.getResponse()
|
return b.getResponse()
|
||||||
|
@ -16,48 +16,57 @@ As of now this logs support console, file,smtp and conn.
|
|||||||
|
|
||||||
First you must import it
|
First you must import it
|
||||||
|
|
||||||
import (
|
```golang
|
||||||
"github.com/astaxie/beego/logs"
|
import (
|
||||||
)
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
Then init a Log (example with console adapter)
|
Then init a Log (example with console adapter)
|
||||||
|
|
||||||
log := NewLogger(10000)
|
```golang
|
||||||
log.SetLogger("console", "")
|
log := logs.NewLogger(10000)
|
||||||
|
log.SetLogger("console", "")
|
||||||
|
```
|
||||||
|
|
||||||
> the first params stand for how many channel
|
> the first params stand for how many channel
|
||||||
|
|
||||||
Use it like this:
|
Use it like this:
|
||||||
|
|
||||||
log.Trace("trace")
|
|
||||||
log.Info("info")
|
|
||||||
log.Warn("warning")
|
|
||||||
log.Debug("debug")
|
|
||||||
log.Critical("critical")
|
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log.Trace("trace")
|
||||||
|
log.Info("info")
|
||||||
|
log.Warn("warning")
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Critical("critical")
|
||||||
|
```
|
||||||
|
|
||||||
## File adapter
|
## File adapter
|
||||||
|
|
||||||
Configure file adapter like this:
|
Configure file adapter like this:
|
||||||
|
|
||||||
log := NewLogger(10000)
|
```golang
|
||||||
log.SetLogger("file", `{"filename":"test.log"}`)
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||||
|
```
|
||||||
|
|
||||||
## Conn adapter
|
## Conn adapter
|
||||||
|
|
||||||
Configure like this:
|
Configure like this:
|
||||||
|
|
||||||
log := NewLogger(1000)
|
```golang
|
||||||
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
log := NewLogger(1000)
|
||||||
log.Info("info")
|
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||||
|
log.Info("info")
|
||||||
|
```
|
||||||
|
|
||||||
## Smtp adapter
|
## Smtp adapter
|
||||||
|
|
||||||
Configure like this:
|
Configure like this:
|
||||||
|
|
||||||
log := NewLogger(10000)
|
```golang
|
||||||
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
log := NewLogger(10000)
|
||||||
log.Critical("sendmail critical")
|
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||||
time.Sleep(time.Second * 30)
|
log.Critical("sendmail critical")
|
||||||
|
time.Sleep(time.Second * 30)
|
||||||
|
```
|
||||||
|
83
logs/accesslog.go
Normal file
83
logs/accesslog.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s"
|
||||||
|
apacheFormat = "APACHE_FORMAT"
|
||||||
|
jsonFormat = "JSON_FORMAT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessLogRecord struct for holding access log data.
|
||||||
|
type AccessLogRecord struct {
|
||||||
|
RemoteAddr string `json:"remote_addr"`
|
||||||
|
RequestTime time.Time `json:"request_time"`
|
||||||
|
RequestMethod string `json:"request_method"`
|
||||||
|
Request string `json:"request"`
|
||||||
|
ServerProtocol string `json:"server_protocol"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
BodyBytesSent int64 `json:"body_bytes_sent"`
|
||||||
|
ElapsedTime time.Duration `json:"elapsed_time"`
|
||||||
|
HTTPReferrer string `json:"http_referrer"`
|
||||||
|
HTTPUserAgent string `json:"http_user_agent"`
|
||||||
|
RemoteUser string `json:"remote_user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccessLogRecord) json() ([]byte, error) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
disableEscapeHTML(encoder)
|
||||||
|
|
||||||
|
err := encoder.Encode(r)
|
||||||
|
return buffer.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableEscapeHTML(i interface{}) {
|
||||||
|
if e, ok := i.(interface {
|
||||||
|
SetEscapeHTML(bool)
|
||||||
|
}); ok {
|
||||||
|
e.SetEscapeHTML(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessLog - Format and print access log.
|
||||||
|
func AccessLog(r *AccessLogRecord, format string) {
|
||||||
|
var msg string
|
||||||
|
switch format {
|
||||||
|
case apacheFormat:
|
||||||
|
timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05")
|
||||||
|
msg = fmt.Sprintf(apacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent,
|
||||||
|
r.ElapsedTime.Seconds(), r.HTTPReferrer, r.HTTPUserAgent)
|
||||||
|
case jsonFormat:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
jsonData, err := r.json()
|
||||||
|
if err != nil {
|
||||||
|
msg = fmt.Sprintf(`{"Error": "%s"}`, err)
|
||||||
|
} else {
|
||||||
|
msg = string(jsonData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
beeLogger.writeMsg(levelLoggerImpl, strings.TrimSpace(msg))
|
||||||
|
}
|
134
logs/file.go
134
logs/file.go
@ -21,6 +21,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -40,6 +41,9 @@ type fileLogWriter struct {
|
|||||||
MaxLines int `json:"maxlines"`
|
MaxLines int `json:"maxlines"`
|
||||||
maxLinesCurLines int
|
maxLinesCurLines int
|
||||||
|
|
||||||
|
MaxFiles int `json:"maxfiles"`
|
||||||
|
MaxFilesCurFiles int
|
||||||
|
|
||||||
// Rotate at size
|
// Rotate at size
|
||||||
MaxSize int `json:"maxsize"`
|
MaxSize int `json:"maxsize"`
|
||||||
maxSizeCurSize int
|
maxSizeCurSize int
|
||||||
@ -50,6 +54,12 @@ type fileLogWriter struct {
|
|||||||
dailyOpenDate int
|
dailyOpenDate int
|
||||||
dailyOpenTime time.Time
|
dailyOpenTime time.Time
|
||||||
|
|
||||||
|
// Rotate hourly
|
||||||
|
Hourly bool `json:"hourly"`
|
||||||
|
MaxHours int64 `json:"maxhours"`
|
||||||
|
hourlyOpenDate int
|
||||||
|
hourlyOpenTime time.Time
|
||||||
|
|
||||||
Rotate bool `json:"rotate"`
|
Rotate bool `json:"rotate"`
|
||||||
|
|
||||||
Level int `json:"level"`
|
Level int `json:"level"`
|
||||||
@ -66,25 +76,30 @@ func newFileWriter() Logger {
|
|||||||
w := &fileLogWriter{
|
w := &fileLogWriter{
|
||||||
Daily: true,
|
Daily: true,
|
||||||
MaxDays: 7,
|
MaxDays: 7,
|
||||||
|
Hourly: false,
|
||||||
|
MaxHours: 168,
|
||||||
Rotate: true,
|
Rotate: true,
|
||||||
RotatePerm: "0440",
|
RotatePerm: "0440",
|
||||||
Level: LevelTrace,
|
Level: LevelTrace,
|
||||||
Perm: "0660",
|
Perm: "0660",
|
||||||
|
MaxLines: 10000000,
|
||||||
|
MaxFiles: 999,
|
||||||
|
MaxSize: 1 << 28,
|
||||||
}
|
}
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init file logger with json config.
|
// Init file logger with json config.
|
||||||
// jsonConfig like:
|
// jsonConfig like:
|
||||||
// {
|
// {
|
||||||
// "filename":"logs/beego.log",
|
// "filename":"logs/beego.log",
|
||||||
// "maxLines":10000,
|
// "maxLines":10000,
|
||||||
// "maxsize":1024,
|
// "maxsize":1024,
|
||||||
// "daily":true,
|
// "daily":true,
|
||||||
// "maxDays":15,
|
// "maxDays":15,
|
||||||
// "rotate":true,
|
// "rotate":true,
|
||||||
// "perm":"0600"
|
// "perm":"0600"
|
||||||
// }
|
// }
|
||||||
func (w *fileLogWriter) Init(jsonConfig string) error {
|
func (w *fileLogWriter) Init(jsonConfig string) error {
|
||||||
err := json.Unmarshal([]byte(jsonConfig), w)
|
err := json.Unmarshal([]byte(jsonConfig), w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -115,10 +130,16 @@ func (w *fileLogWriter) startLogger() error {
|
|||||||
return w.initFd()
|
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) ||
|
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||||
(w.Daily && day != w.dailyOpenDate)
|
(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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,14 +148,23 @@ func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
|||||||
if level > w.Level {
|
if level > w.Level {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
h, d := formatTimeHeader(when)
|
hd, d, h := formatTimeHeader(when)
|
||||||
msg = string(h) + msg + "\n"
|
msg = string(hd) + msg + "\n"
|
||||||
if w.Rotate {
|
if w.Rotate {
|
||||||
w.RLock()
|
w.RLock()
|
||||||
if w.needRotate(len(msg), d) {
|
if w.needRotateHourly(len(msg), h) {
|
||||||
w.RUnlock()
|
w.RUnlock()
|
||||||
w.Lock()
|
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 {
|
if err := w.doRotate(when); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
}
|
}
|
||||||
@ -161,6 +191,10 @@ func (w *fileLogWriter) createLogFile() (*os.File, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filepath := path.Dir(w.Filename)
|
||||||
|
os.MkdirAll(filepath, os.FileMode(perm))
|
||||||
|
|
||||||
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
|
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
|
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
|
||||||
@ -178,11 +212,15 @@ func (w *fileLogWriter) initFd() error {
|
|||||||
w.maxSizeCurSize = int(fInfo.Size())
|
w.maxSizeCurSize = int(fInfo.Size())
|
||||||
w.dailyOpenTime = time.Now()
|
w.dailyOpenTime = time.Now()
|
||||||
w.dailyOpenDate = w.dailyOpenTime.Day()
|
w.dailyOpenDate = w.dailyOpenTime.Day()
|
||||||
|
w.hourlyOpenTime = time.Now()
|
||||||
|
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
|
||||||
w.maxLinesCurLines = 0
|
w.maxLinesCurLines = 0
|
||||||
if w.Daily {
|
if w.Hourly {
|
||||||
|
go w.hourlyRotate(w.hourlyOpenTime)
|
||||||
|
} else if w.Daily {
|
||||||
go w.dailyRotate(w.dailyOpenTime)
|
go w.dailyRotate(w.dailyOpenTime)
|
||||||
}
|
}
|
||||||
if fInfo.Size() > 0 {
|
if fInfo.Size() > 0 && w.MaxLines > 0 {
|
||||||
count, err := w.lines()
|
count, err := w.lines()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -198,7 +236,22 @@ func (w *fileLogWriter) dailyRotate(openTime time.Time) {
|
|||||||
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
||||||
<-tm.C
|
<-tm.C
|
||||||
w.Lock()
|
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 {
|
if err := w.doRotate(time.Now()); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
}
|
}
|
||||||
@ -238,8 +291,10 @@ func (w *fileLogWriter) lines() (int, error) {
|
|||||||
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||||
// file exists
|
// file exists
|
||||||
// Find the next available number
|
// Find the next available number
|
||||||
num := 1
|
num := w.MaxFilesCurFiles + 1
|
||||||
fName := ""
|
fName := ""
|
||||||
|
format := ""
|
||||||
|
var openTime time.Time
|
||||||
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
|
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -251,19 +306,26 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
|||||||
goto RESTART_LOGGER
|
goto RESTART_LOGGER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if w.Hourly {
|
||||||
|
format = "2006010215"
|
||||||
|
openTime = w.hourlyOpenTime
|
||||||
|
} else if w.Daily {
|
||||||
|
format = "2006-01-02"
|
||||||
|
openTime = w.dailyOpenTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// only when one of them be setted, then the file would be splited
|
||||||
if w.MaxLines > 0 || w.MaxSize > 0 {
|
if w.MaxLines > 0 || w.MaxSize > 0 {
|
||||||
for ; err == nil && num <= 999; num++ {
|
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)
|
_, err = os.Lstat(fName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, w.dailyOpenTime.Format("2006-01-02"), w.suffix)
|
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", openTime.Format(format), num, w.suffix)
|
||||||
_, err = os.Lstat(fName)
|
_, err = os.Lstat(fName)
|
||||||
for ; err == nil && num <= 999; num++ {
|
w.MaxFilesCurFiles = num
|
||||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
|
|
||||||
_, err = os.Lstat(fName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return error if the last file checked still existed
|
// return error if the last file checked still existed
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
|
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
|
||||||
@ -307,13 +369,21 @@ func (w *fileLogWriter) deleteOldLog() {
|
|||||||
if info == nil {
|
if info == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if w.Hourly {
|
||||||
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
|
if !info.IsDir() && info.ModTime().Add(1 * time.Hour * time.Duration(w.MaxHours)).Before(time.Now()) {
|
||||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||||
os.Remove(path)
|
os.Remove(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if w.Daily {
|
||||||
|
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
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ func TestFile2(t *testing.T) {
|
|||||||
os.Remove("test2.log")
|
os.Remove("test2.log")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileRotate_01(t *testing.T) {
|
func TestFileDailyRotate_01(t *testing.T) {
|
||||||
log := NewLogger(10000)
|
log := NewLogger(10000)
|
||||||
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||||
log.Debug("debug")
|
log.Debug("debug")
|
||||||
@ -133,28 +133,28 @@ func TestFileRotate_01(t *testing.T) {
|
|||||||
os.Remove("test3.log")
|
os.Remove("test3.log")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileRotate_02(t *testing.T) {
|
func TestFileDailyRotate_02(t *testing.T) {
|
||||||
fn1 := "rotate_day.log"
|
fn1 := "rotate_day.log"
|
||||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".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"
|
fn1 := "rotate_day.log"
|
||||||
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||||
os.Create(fn)
|
os.Create(fn)
|
||||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.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)
|
||||||
os.Remove(fn)
|
os.Remove(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileRotate_04(t *testing.T) {
|
func TestFileDailyRotate_04(t *testing.T) {
|
||||||
fn1 := "rotate_day.log"
|
fn1 := "rotate_day.log"
|
||||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||||
testFileDailyRotate(t, fn1, fn2)
|
testFileDailyRotate(t, fn1, fn2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileRotate_05(t *testing.T) {
|
func TestFileDailyRotate_05(t *testing.T) {
|
||||||
fn1 := "rotate_day.log"
|
fn1 := "rotate_day.log"
|
||||||
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||||
os.Create(fn)
|
os.Create(fn)
|
||||||
@ -162,7 +162,7 @@ func TestFileRotate_05(t *testing.T) {
|
|||||||
testFileDailyRotate(t, fn1, fn2)
|
testFileDailyRotate(t, fn1, fn2)
|
||||||
os.Remove(fn)
|
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 := NewLogger(10000)
|
||||||
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||||
log.Debug("debug")
|
log.Debug("debug")
|
||||||
@ -183,23 +183,110 @@ func TestFileRotate_06(t *testing.T) { //test file mode
|
|||||||
os.Remove(rotateName)
|
os.Remove(rotateName)
|
||||||
os.Remove("test3.log")
|
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{
|
fw := &fileLogWriter{
|
||||||
Daily: true,
|
Daily: daily,
|
||||||
MaxDays: 7,
|
MaxDays: 7,
|
||||||
|
Hourly: hourly,
|
||||||
|
MaxHours: 168,
|
||||||
Rotate: true,
|
Rotate: true,
|
||||||
Level: LevelTrace,
|
Level: LevelTrace,
|
||||||
Perm: "0660",
|
Perm: "0660",
|
||||||
RotatePerm: "0440",
|
RotatePerm: "0440",
|
||||||
}
|
}
|
||||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
|
||||||
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
if daily {
|
||||||
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||||
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug)
|
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} {
|
for _, file := range []string{fn1, fn2} {
|
||||||
_, err := os.Stat(file)
|
_, err := os.Stat(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
os.Remove(file)
|
os.Remove(file)
|
||||||
@ -239,6 +326,37 @@ func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
|
|||||||
fw.Destroy()
|
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) {
|
func exists(path string) (bool, error) {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
23
logs/log.go
23
logs/log.go
@ -47,7 +47,7 @@ import (
|
|||||||
|
|
||||||
// RFC5424 log message levels.
|
// RFC5424 log message levels.
|
||||||
const (
|
const (
|
||||||
LevelEmergency = iota
|
LevelEmergency = iota
|
||||||
LevelAlert
|
LevelAlert
|
||||||
LevelCritical
|
LevelCritical
|
||||||
LevelError
|
LevelError
|
||||||
@ -116,6 +116,7 @@ type BeeLogger struct {
|
|||||||
enableFuncCallDepth bool
|
enableFuncCallDepth bool
|
||||||
loggerFuncCallDepth int
|
loggerFuncCallDepth int
|
||||||
asynchronous bool
|
asynchronous bool
|
||||||
|
prefix string
|
||||||
msgChanLen int64
|
msgChanLen int64
|
||||||
msgChan chan *logMsg
|
msgChan chan *logMsg
|
||||||
signalChan chan string
|
signalChan chan string
|
||||||
@ -247,7 +248,7 @@ func (bl *BeeLogger) Write(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
// writeMsg will always add a '\n' character
|
// writeMsg will always add a '\n' character
|
||||||
if p[len(p)-1] == '\n' {
|
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
|
// set levelLoggerImpl to ensure all log message will be write out
|
||||||
err = bl.writeMsg(levelLoggerImpl, string(p))
|
err = bl.writeMsg(levelLoggerImpl, string(p))
|
||||||
@ -267,6 +268,9 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
|
|||||||
if len(v) > 0 {
|
if len(v) > 0 {
|
||||||
msg = fmt.Sprintf(msg, v...)
|
msg = fmt.Sprintf(msg, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg = bl.prefix + " " + msg
|
||||||
|
|
||||||
when := time.Now()
|
when := time.Now()
|
||||||
if bl.enableFuncCallDepth {
|
if bl.enableFuncCallDepth {
|
||||||
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
||||||
@ -305,6 +309,11 @@ func (bl *BeeLogger) SetLevel(l int) {
|
|||||||
bl.level = l
|
bl.level = l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLevel Get Current log message level.
|
||||||
|
func (bl *BeeLogger) GetLevel() int {
|
||||||
|
return bl.level
|
||||||
|
}
|
||||||
|
|
||||||
// SetLogFuncCallDepth set log funcCallDepth
|
// SetLogFuncCallDepth set log funcCallDepth
|
||||||
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
||||||
bl.loggerFuncCallDepth = d
|
bl.loggerFuncCallDepth = d
|
||||||
@ -320,6 +329,11 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
|||||||
bl.enableFuncCallDepth = b
|
bl.enableFuncCallDepth = b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set prefix
|
||||||
|
func (bl *BeeLogger) SetPrefix(s string) {
|
||||||
|
bl.prefix = s
|
||||||
|
}
|
||||||
|
|
||||||
// start logger chan reading.
|
// start logger chan reading.
|
||||||
// when chan is not empty, write logs.
|
// when chan is not empty, write logs.
|
||||||
func (bl *BeeLogger) startLogger() {
|
func (bl *BeeLogger) startLogger() {
|
||||||
@ -544,6 +558,11 @@ func SetLevel(l int) {
|
|||||||
beeLogger.SetLevel(l)
|
beeLogger.SetLevel(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPrefix sets the prefix
|
||||||
|
func SetPrefix(s string) {
|
||||||
|
beeLogger.SetPrefix(s)
|
||||||
|
}
|
||||||
|
|
||||||
// EnableFuncCallDepth enable log funcCallDepth
|
// EnableFuncCallDepth enable log funcCallDepth
|
||||||
func EnableFuncCallDepth(b bool) {
|
func EnableFuncCallDepth(b bool) {
|
||||||
beeLogger.enableFuncCallDepth = b
|
beeLogger.enableFuncCallDepth = b
|
||||||
|
@ -33,7 +33,7 @@ func newLogWriter(wr io.Writer) *logWriter {
|
|||||||
|
|
||||||
func (lg *logWriter) println(when time.Time, msg string) {
|
func (lg *logWriter) println(when time.Time, msg string) {
|
||||||
lg.Lock()
|
lg.Lock()
|
||||||
h, _ := formatTimeHeader(when)
|
h, _, _:= formatTimeHeader(when)
|
||||||
lg.writer.Write(append(append(h, msg...), '\n'))
|
lg.writer.Write(append(append(h, msg...), '\n'))
|
||||||
lg.Unlock()
|
lg.Unlock()
|
||||||
}
|
}
|
||||||
@ -87,13 +87,15 @@ const (
|
|||||||
mi2 = `012345678901234567890123456789012345678901234567890123456789`
|
mi2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||||
s1 = `000000000011111111112222222222333333333344444444445555555555`
|
s1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||||
s2 = `012345678901234567890123456789012345678901234567890123456789`
|
s2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
ns1 = `0123456789`
|
||||||
)
|
)
|
||||||
|
|
||||||
func formatTimeHeader(when time.Time) ([]byte, int) {
|
func formatTimeHeader(when time.Time) ([]byte, int, int) {
|
||||||
y, mo, d := when.Date()
|
y, mo, d := when.Date()
|
||||||
h, mi, s := when.Clock()
|
h, mi, s := when.Clock()
|
||||||
//len("2006/01/02 15:04:05 ")==20
|
ns := when.Nanosecond() / 1000000
|
||||||
var buf [20]byte
|
//len("2006/01/02 15:04:05.123 ")==24
|
||||||
|
var buf [24]byte
|
||||||
|
|
||||||
buf[0] = y1[y/1000%10]
|
buf[0] = y1[y/1000%10]
|
||||||
buf[1] = y2[y/100]
|
buf[1] = y2[y/100]
|
||||||
@ -114,9 +116,14 @@ func formatTimeHeader(when time.Time) ([]byte, int) {
|
|||||||
buf[16] = ':'
|
buf[16] = ':'
|
||||||
buf[17] = s1[s]
|
buf[17] = s1[s]
|
||||||
buf[18] = s2[s]
|
buf[18] = s2[s]
|
||||||
buf[19] = ' '
|
buf[19] = '.'
|
||||||
|
buf[20] = ns1[ns/100]
|
||||||
|
buf[21] = ns1[ns%100/10]
|
||||||
|
buf[22] = ns1[ns%10]
|
||||||
|
|
||||||
return buf[0:], d
|
buf[23] = ' '
|
||||||
|
|
||||||
|
return buf[0:], d, h
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -30,8 +30,8 @@ func TestFormatHeader_0(t *testing.T) {
|
|||||||
if tm.Year() >= 2100 {
|
if tm.Year() >= 2100 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
h, _ := formatTimeHeader(tm)
|
h, _, _ := formatTimeHeader(tm)
|
||||||
if tm.Format("2006/01/02 15:04:05 ") != string(h) {
|
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||||
t.Log(tm)
|
t.Log(tm)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
@ -48,8 +48,8 @@ func TestFormatHeader_1(t *testing.T) {
|
|||||||
if tm.Year() >= year+1 {
|
if tm.Year() >= year+1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
h, _ := formatTimeHeader(tm)
|
h, _, _ := formatTimeHeader(tm)
|
||||||
if tm.Format("2006/01/02 15:04:05 ") != string(h) {
|
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||||
t.Log(tm)
|
t.Log(tm)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,10 @@ func (f *multiFileLogWriter) Init(config string) error {
|
|||||||
jsonMap["level"] = i
|
jsonMap["level"] = i
|
||||||
bs, _ := json.Marshal(jsonMap)
|
bs, _ := json.Marshal(jsonMap)
|
||||||
writer = newFileWriter().(*fileLogWriter)
|
writer = newFileWriter().(*fileLogWriter)
|
||||||
writer.Init(string(bs))
|
err := writer.Init(string(bs))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
f.writers[i] = writer
|
f.writers[i] = writer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -322,7 +322,7 @@ func (m *Migration) GetSQL() (sql string) {
|
|||||||
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
|
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(m.Columns) > index {
|
if len(m.Columns) > index+1 {
|
||||||
sql += ","
|
sql += ","
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -355,7 +355,7 @@ func (m *Migration) GetSQL() (sql string) {
|
|||||||
} else {
|
} else {
|
||||||
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
|
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
|
||||||
}
|
}
|
||||||
if len(m.Columns) > index {
|
if len(m.Columns) > index+1 {
|
||||||
sql += ","
|
sql += ","
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -366,14 +366,14 @@ func (m *Migration) GetSQL() (sql string) {
|
|||||||
|
|
||||||
for index, unique := range m.Uniques {
|
for index, unique := range m.Uniques {
|
||||||
sql += fmt.Sprintf("\n DROP KEY `%s`", unique.Definition)
|
sql += fmt.Sprintf("\n DROP KEY `%s`", unique.Definition)
|
||||||
if len(m.Uniques) > index {
|
if len(m.Uniques) > index+1 {
|
||||||
sql += ","
|
sql += ","
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
for index, column := range m.Renames {
|
for index, column := range m.Renames {
|
||||||
sql += fmt.Sprintf("\n CHANGE COLUMN `%s` `%s` %s %s %s %s", column.NewName, column.OldName, column.OldDataType, column.OldUnsign, column.OldNull, column.OldDefault)
|
sql += fmt.Sprintf("\n CHANGE COLUMN `%s` `%s` %s %s %s %s", column.NewName, column.OldName, column.OldDataType, column.OldUnsign, column.OldNull, column.OldDefault)
|
||||||
if len(m.Renames) > index {
|
if len(m.Renames) > index+1 {
|
||||||
sql += ","
|
sql += ","
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@ func Register(name string, m Migrationer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade upgrate the migration from lasttime
|
// Upgrade upgrade the migration from lasttime
|
||||||
func Upgrade(lasttime int64) error {
|
func Upgrade(lasttime int64) error {
|
||||||
sm := sortMap(migrationMap)
|
sm := sortMap(migrationMap)
|
||||||
i := 0
|
i := 0
|
||||||
|
@ -61,6 +61,9 @@ func init() {
|
|||||||
|
|
||||||
// set default database
|
// set default database
|
||||||
orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
|
orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
|
||||||
|
|
||||||
|
// create table
|
||||||
|
orm.RunSyncdb("default", false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -51,12 +51,14 @@ checkColumn:
|
|||||||
switch fieldType {
|
switch fieldType {
|
||||||
case TypeBooleanField:
|
case TypeBooleanField:
|
||||||
col = T["bool"]
|
col = T["bool"]
|
||||||
case TypeCharField:
|
case TypeVarCharField:
|
||||||
if al.Driver == DRPostgres && fi.toText {
|
if al.Driver == DRPostgres && fi.toText {
|
||||||
col = T["string-text"]
|
col = T["string-text"]
|
||||||
} else {
|
} else {
|
||||||
col = fmt.Sprintf(T["string"], fieldSize)
|
col = fmt.Sprintf(T["string"], fieldSize)
|
||||||
}
|
}
|
||||||
|
case TypeCharField:
|
||||||
|
col = fmt.Sprintf(T["string-char"], fieldSize)
|
||||||
case TypeTextField:
|
case TypeTextField:
|
||||||
col = T["string-text"]
|
col = T["string-text"]
|
||||||
case TypeTimeField:
|
case TypeTimeField:
|
||||||
@ -96,13 +98,13 @@ checkColumn:
|
|||||||
}
|
}
|
||||||
case TypeJSONField:
|
case TypeJSONField:
|
||||||
if al.Driver != DRPostgres {
|
if al.Driver != DRPostgres {
|
||||||
fieldType = TypeCharField
|
fieldType = TypeVarCharField
|
||||||
goto checkColumn
|
goto checkColumn
|
||||||
}
|
}
|
||||||
col = T["json"]
|
col = T["json"]
|
||||||
case TypeJsonbField:
|
case TypeJsonbField:
|
||||||
if al.Driver != DRPostgres {
|
if al.Driver != DRPostgres {
|
||||||
fieldType = TypeCharField
|
fieldType = TypeVarCharField
|
||||||
goto checkColumn
|
goto checkColumn
|
||||||
}
|
}
|
||||||
col = T["jsonb"]
|
col = T["jsonb"]
|
||||||
@ -195,6 +197,10 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
|
|||||||
if strings.Contains(column, "%COL%") {
|
if strings.Contains(column, "%COL%") {
|
||||||
column = strings.Replace(column, "%COL%", fi.column, -1)
|
column = strings.Replace(column, "%COL%", fi.column, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fi.description != "" {
|
||||||
|
column += " " + fmt.Sprintf("COMMENT '%s'",fi.description)
|
||||||
|
}
|
||||||
|
|
||||||
columns = append(columns, column)
|
columns = append(columns, column)
|
||||||
}
|
}
|
||||||
|
12
orm/db.go
12
orm/db.go
@ -142,7 +142,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
|
|||||||
} else {
|
} else {
|
||||||
value = field.Bool()
|
value = field.Bool()
|
||||||
}
|
}
|
||||||
case TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField:
|
case TypeVarCharField, TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField:
|
||||||
if ns, ok := field.Interface().(sql.NullString); ok {
|
if ns, ok := field.Interface().(sql.NullString); ok {
|
||||||
value = nil
|
value = nil
|
||||||
if ns.Valid {
|
if ns.Valid {
|
||||||
@ -536,6 +536,8 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
|
|||||||
updates := make([]string, len(names))
|
updates := make([]string, len(names))
|
||||||
var conflitValue interface{}
|
var conflitValue interface{}
|
||||||
for i, v := range names {
|
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] = "?"
|
marks[i] = "?"
|
||||||
valueStr := argsMap[strings.ToLower(v)]
|
valueStr := argsMap[strings.ToLower(v)]
|
||||||
if v == args0 {
|
if v == args0 {
|
||||||
@ -969,6 +971,10 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
|
|||||||
}
|
}
|
||||||
query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit)
|
query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit)
|
||||||
|
|
||||||
|
if qs.forupdate {
|
||||||
|
query += " FOR UPDATE"
|
||||||
|
}
|
||||||
|
|
||||||
d.ins.ReplaceMarks(&query)
|
d.ins.ReplaceMarks(&query)
|
||||||
|
|
||||||
var rs *sql.Rows
|
var rs *sql.Rows
|
||||||
@ -1240,7 +1246,7 @@ setValue:
|
|||||||
}
|
}
|
||||||
value = b
|
value = b
|
||||||
}
|
}
|
||||||
case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
|
case fieldType == TypeVarCharField || fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
|
||||||
if str == nil {
|
if str == nil {
|
||||||
value = ToStr(val)
|
value = ToStr(val)
|
||||||
} else {
|
} else {
|
||||||
@ -1386,7 +1392,7 @@ setValue:
|
|||||||
field.SetBool(value.(bool))
|
field.SetBool(value.(bool))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
|
case fieldType == TypeVarCharField || fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
|
||||||
if isNative {
|
if isNative {
|
||||||
if ns, ok := field.Interface().(sql.NullString); ok {
|
if ns, ok := field.Interface().(sql.NullString); ok {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
|
@ -119,7 +119,7 @@ type alias struct {
|
|||||||
func detectTZ(al *alias) {
|
func detectTZ(al *alias) {
|
||||||
// orm timezone system match database
|
// orm timezone system match database
|
||||||
// default use Local
|
// default use Local
|
||||||
al.TZ = time.Local
|
al.TZ = DefaultTimeLoc
|
||||||
|
|
||||||
if al.DriverName == "sphinx" {
|
if al.DriverName == "sphinx" {
|
||||||
return
|
return
|
||||||
@ -136,7 +136,9 @@ func detectTZ(al *alias) {
|
|||||||
}
|
}
|
||||||
t, err := time.Parse("-07:00:00", tz)
|
t, err := time.Parse("-07:00:00", tz)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
al.TZ = t.Location()
|
if t.Location().String() != "" {
|
||||||
|
al.TZ = t.Location()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
|
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ var mysqlTypes = map[string]string{
|
|||||||
"pk": "NOT NULL PRIMARY KEY",
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
"bool": "bool",
|
"bool": "bool",
|
||||||
"string": "varchar(%d)",
|
"string": "varchar(%d)",
|
||||||
|
"string-char": "char(%d)",
|
||||||
"string-text": "longtext",
|
"string-text": "longtext",
|
||||||
"time.Time-date": "date",
|
"time.Time-date": "date",
|
||||||
"time.Time": "datetime",
|
"time.Time": "datetime",
|
||||||
|
@ -34,6 +34,7 @@ var oracleTypes = map[string]string{
|
|||||||
"pk": "NOT NULL PRIMARY KEY",
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
"bool": "bool",
|
"bool": "bool",
|
||||||
"string": "VARCHAR2(%d)",
|
"string": "VARCHAR2(%d)",
|
||||||
|
"string-char": "CHAR(%d)",
|
||||||
"string-text": "VARCHAR2(%d)",
|
"string-text": "VARCHAR2(%d)",
|
||||||
"time.Time-date": "DATE",
|
"time.Time-date": "DATE",
|
||||||
"time.Time": "TIMESTAMP",
|
"time.Time": "TIMESTAMP",
|
||||||
|
@ -43,6 +43,7 @@ var postgresTypes = map[string]string{
|
|||||||
"pk": "NOT NULL PRIMARY KEY",
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
"bool": "bool",
|
"bool": "bool",
|
||||||
"string": "varchar(%d)",
|
"string": "varchar(%d)",
|
||||||
|
"string-char": "char(%d)",
|
||||||
"string-text": "text",
|
"string-text": "text",
|
||||||
"time.Time-date": "date",
|
"time.Time-date": "date",
|
||||||
"time.Time": "timestamp with time zone",
|
"time.Time": "timestamp with time zone",
|
||||||
|
@ -43,6 +43,7 @@ var sqliteTypes = map[string]string{
|
|||||||
"pk": "NOT NULL PRIMARY KEY",
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
"bool": "bool",
|
"bool": "bool",
|
||||||
"string": "varchar(%d)",
|
"string": "varchar(%d)",
|
||||||
|
"string-char": "character(%d)",
|
||||||
"string-text": "text",
|
"string-text": "text",
|
||||||
"time.Time-date": "date",
|
"time.Time-date": "date",
|
||||||
"time.Time": "datetime",
|
"time.Time": "datetime",
|
||||||
|
@ -52,7 +52,7 @@ func (mc *_modelCache) all() map[string]*modelInfo {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// get orderd model info
|
// get ordered model info
|
||||||
func (mc *_modelCache) allOrdered() []*modelInfo {
|
func (mc *_modelCache) allOrdered() []*modelInfo {
|
||||||
m := make([]*modelInfo, 0, len(mc.orders))
|
m := make([]*modelInfo, 0, len(mc.orders))
|
||||||
for _, table := range mc.orders {
|
for _, table := range mc.orders {
|
||||||
|
@ -89,7 +89,7 @@ func registerModel(PrefixOrSuffix string, model interface{}, isPrefix bool) {
|
|||||||
modelCache.set(table, mi)
|
modelCache.set(table, mi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// boostrap models
|
// bootstrap models
|
||||||
func bootStrap() {
|
func bootStrap() {
|
||||||
if modelCache.done {
|
if modelCache.done {
|
||||||
return
|
return
|
||||||
@ -332,7 +332,7 @@ func RegisterModelWithSuffix(suffix string, models ...interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BootStrap bootrap models.
|
// BootStrap bootstrap models.
|
||||||
// make all model parsed and can not add more models
|
// make all model parsed and can not add more models
|
||||||
func BootStrap() {
|
func BootStrap() {
|
||||||
if modelCache.done {
|
if modelCache.done {
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
// Define the Type enum
|
// Define the Type enum
|
||||||
const (
|
const (
|
||||||
TypeBooleanField = 1 << iota
|
TypeBooleanField = 1 << iota
|
||||||
|
TypeVarCharField
|
||||||
TypeCharField
|
TypeCharField
|
||||||
TypeTextField
|
TypeTextField
|
||||||
TypeTimeField
|
TypeTimeField
|
||||||
@ -49,9 +50,9 @@ const (
|
|||||||
|
|
||||||
// Define some logic enum
|
// Define some logic enum
|
||||||
const (
|
const (
|
||||||
IsIntegerField = ^-TypePositiveBigIntegerField >> 5 << 6
|
IsIntegerField = ^-TypePositiveBigIntegerField >> 6 << 7
|
||||||
IsPositiveIntegerField = ^-TypePositiveBigIntegerField >> 9 << 10
|
IsPositiveIntegerField = ^-TypePositiveBigIntegerField >> 10 << 11
|
||||||
IsRelField = ^-RelReverseMany >> 17 << 18
|
IsRelField = ^-RelReverseMany >> 18 << 19
|
||||||
IsFieldType = ^-RelReverseMany<<1 + 1
|
IsFieldType = ^-RelReverseMany<<1 + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -85,7 +86,7 @@ func (e *BooleanField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Bool()
|
v, err := StrTo(d).Bool()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -126,7 +127,7 @@ func (e *CharField) String() string {
|
|||||||
|
|
||||||
// FieldType return the enum type
|
// FieldType return the enum type
|
||||||
func (e *CharField) FieldType() int {
|
func (e *CharField) FieldType() int {
|
||||||
return TypeCharField
|
return TypeVarCharField
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRaw set the interface to string
|
// SetRaw set the interface to string
|
||||||
@ -190,7 +191,7 @@ func (e *TimeField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := timeParse(d, formatTime)
|
v, err := timeParse(d, formatTime)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -232,7 +233,7 @@ func (e *DateField) Set(d time.Time) {
|
|||||||
*e = DateField(d)
|
*e = DateField(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String convert datatime to string
|
// String convert datetime to string
|
||||||
func (e *DateField) String() string {
|
func (e *DateField) String() string {
|
||||||
return e.Value().String()
|
return e.Value().String()
|
||||||
}
|
}
|
||||||
@ -249,7 +250,7 @@ func (e *DateField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := timeParse(d, formatDate)
|
v, err := timeParse(d, formatDate)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -272,12 +273,12 @@ var _ Fielder = new(DateField)
|
|||||||
// Takes the same extra arguments as DateField.
|
// Takes the same extra arguments as DateField.
|
||||||
type DateTimeField time.Time
|
type DateTimeField time.Time
|
||||||
|
|
||||||
// Value return the datatime value
|
// Value return the datetime value
|
||||||
func (e DateTimeField) Value() time.Time {
|
func (e DateTimeField) Value() time.Time {
|
||||||
return time.Time(e)
|
return time.Time(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set set the time.Time to datatime
|
// Set set the time.Time to datetime
|
||||||
func (e *DateTimeField) Set(d time.Time) {
|
func (e *DateTimeField) Set(d time.Time) {
|
||||||
*e = DateTimeField(d)
|
*e = DateTimeField(d)
|
||||||
}
|
}
|
||||||
@ -299,7 +300,7 @@ func (e *DateTimeField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := timeParse(d, formatDateTime)
|
v, err := timeParse(d, formatDateTime)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -309,12 +310,12 @@ func (e *DateTimeField) SetRaw(value interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawValue return the datatime value
|
// RawValue return the datetime value
|
||||||
func (e *DateTimeField) RawValue() interface{} {
|
func (e *DateTimeField) RawValue() interface{} {
|
||||||
return e.Value()
|
return e.Value()
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify datatime implement fielder
|
// verify datetime implement fielder
|
||||||
var _ Fielder = new(DateTimeField)
|
var _ Fielder = new(DateTimeField)
|
||||||
|
|
||||||
// FloatField A floating-point number represented in go by a float32 value.
|
// FloatField A floating-point number represented in go by a float32 value.
|
||||||
@ -349,9 +350,10 @@ func (e *FloatField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Float64()
|
v, err := StrTo(d).Float64()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<FloatField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<FloatField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -396,9 +398,10 @@ func (e *SmallIntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Int16()
|
v, err := StrTo(d).Int16()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<SmallIntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<SmallIntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -443,9 +446,10 @@ func (e *IntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Int32()
|
v, err := StrTo(d).Int32()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<IntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<IntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -490,9 +494,10 @@ func (e *BigIntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Int64()
|
v, err := StrTo(d).Int64()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<BigIntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<BigIntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -537,9 +542,10 @@ func (e *PositiveSmallIntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Uint16()
|
v, err := StrTo(d).Uint16()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<PositiveSmallIntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<PositiveSmallIntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -584,9 +590,10 @@ func (e *PositiveIntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Uint32()
|
v, err := StrTo(d).Uint32()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<PositiveIntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<PositiveIntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
@ -631,9 +638,10 @@ func (e *PositiveBigIntegerField) SetRaw(value interface{}) error {
|
|||||||
e.Set(d)
|
e.Set(d)
|
||||||
case string:
|
case string:
|
||||||
v, err := StrTo(d).Uint64()
|
v, err := StrTo(d).Uint64()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
e.Set(v)
|
e.Set(v)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("<PositiveBigIntegerField.SetRaw> unknown value `%s`", value)
|
return fmt.Errorf("<PositiveBigIntegerField.SetRaw> unknown value `%s`", value)
|
||||||
}
|
}
|
||||||
|
@ -136,6 +136,7 @@ type fieldInfo struct {
|
|||||||
decimals int
|
decimals int
|
||||||
isFielder bool // implement Fielder interface
|
isFielder bool // implement Fielder interface
|
||||||
onDelete string
|
onDelete string
|
||||||
|
description string
|
||||||
}
|
}
|
||||||
|
|
||||||
// new field info
|
// new field info
|
||||||
@ -244,8 +245,10 @@ checkType:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
if fieldType == TypeCharField {
|
if fieldType == TypeVarCharField {
|
||||||
switch tags["type"] {
|
switch tags["type"] {
|
||||||
|
case "char":
|
||||||
|
fieldType = TypeCharField
|
||||||
case "text":
|
case "text":
|
||||||
fieldType = TypeTextField
|
fieldType = TypeTextField
|
||||||
case "json":
|
case "json":
|
||||||
@ -298,6 +301,7 @@ checkType:
|
|||||||
fi.sf = sf
|
fi.sf = sf
|
||||||
fi.fullName = mi.fullName + mName + "." + sf.Name
|
fi.fullName = mi.fullName + mName + "." + sf.Name
|
||||||
|
|
||||||
|
fi.description = sf.Tag.Get("description")
|
||||||
fi.null = attrs["null"]
|
fi.null = attrs["null"]
|
||||||
fi.index = attrs["index"]
|
fi.index = attrs["index"]
|
||||||
fi.auto = attrs["auto"]
|
fi.auto = attrs["auto"]
|
||||||
@ -357,7 +361,7 @@ checkType:
|
|||||||
|
|
||||||
switch fieldType {
|
switch fieldType {
|
||||||
case TypeBooleanField:
|
case TypeBooleanField:
|
||||||
case TypeCharField, TypeJSONField, TypeJsonbField:
|
case TypeVarCharField, TypeCharField, TypeJSONField, TypeJsonbField:
|
||||||
if size != "" {
|
if size != "" {
|
||||||
v, e := StrTo(size).Int32()
|
v, e := StrTo(size).Int32()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
|
@ -75,7 +75,8 @@ func addModelFields(mi *modelInfo, ind reflect.Value, mName string, index []int)
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
//record current field index
|
//record current field index
|
||||||
fi.fieldIndex = append(index, i)
|
fi.fieldIndex = append(fi.fieldIndex, index...)
|
||||||
|
fi.fieldIndex = append(fi.fieldIndex, i)
|
||||||
fi.mi = mi
|
fi.mi = mi
|
||||||
fi.inModel = true
|
fi.inModel = true
|
||||||
if !mi.fields.Add(fi) {
|
if !mi.fields.Add(fi) {
|
||||||
|
@ -49,7 +49,7 @@ func (e *SliceStringField) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *SliceStringField) FieldType() int {
|
func (e *SliceStringField) FieldType() int {
|
||||||
return TypeCharField
|
return TypeVarCharField
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *SliceStringField) SetRaw(value interface{}) error {
|
func (e *SliceStringField) SetRaw(value interface{}) error {
|
||||||
@ -433,53 +433,57 @@ var (
|
|||||||
dDbBaser dbBaser
|
dDbBaser dbBaser
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
helpinfo = `need driver and source!
|
||||||
|
|
||||||
|
Default DB Drivers.
|
||||||
|
|
||||||
|
driver: url
|
||||||
|
mysql: https://github.com/go-sql-driver/mysql
|
||||||
|
sqlite3: https://github.com/mattn/go-sqlite3
|
||||||
|
postgres: https://github.com/lib/pq
|
||||||
|
tidb: https://github.com/pingcap/tidb
|
||||||
|
|
||||||
|
usage:
|
||||||
|
|
||||||
|
go get -u github.com/astaxie/beego/orm
|
||||||
|
go get -u github.com/go-sql-driver/mysql
|
||||||
|
go get -u github.com/mattn/go-sqlite3
|
||||||
|
go get -u github.com/lib/pq
|
||||||
|
go get -u github.com/pingcap/tidb
|
||||||
|
|
||||||
|
#### MySQL
|
||||||
|
mysql -u root -e 'create database orm_test;'
|
||||||
|
export ORM_DRIVER=mysql
|
||||||
|
export ORM_SOURCE="root:@/orm_test?charset=utf8"
|
||||||
|
go test -v github.com/astaxie/beego/orm
|
||||||
|
|
||||||
|
|
||||||
|
#### Sqlite3
|
||||||
|
export ORM_DRIVER=sqlite3
|
||||||
|
export ORM_SOURCE='file:memory_test?mode=memory'
|
||||||
|
go test -v github.com/astaxie/beego/orm
|
||||||
|
|
||||||
|
|
||||||
|
#### PostgreSQL
|
||||||
|
psql -c 'create database orm_test;' -U postgres
|
||||||
|
export ORM_DRIVER=postgres
|
||||||
|
export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
||||||
|
go test -v github.com/astaxie/beego/orm
|
||||||
|
|
||||||
|
#### TiDB
|
||||||
|
export ORM_DRIVER=tidb
|
||||||
|
export ORM_SOURCE='memory://test/test'
|
||||||
|
go test -v github.com/astaxie/beego/orm
|
||||||
|
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Debug, _ = StrTo(DBARGS.Debug).Bool()
|
Debug, _ = StrTo(DBARGS.Debug).Bool()
|
||||||
|
|
||||||
if DBARGS.Driver == "" || DBARGS.Source == "" {
|
if DBARGS.Driver == "" || DBARGS.Source == "" {
|
||||||
fmt.Println(`need driver and source!
|
fmt.Println(helpinfo)
|
||||||
|
|
||||||
Default DB Drivers.
|
|
||||||
|
|
||||||
driver: url
|
|
||||||
mysql: https://github.com/go-sql-driver/mysql
|
|
||||||
sqlite3: https://github.com/mattn/go-sqlite3
|
|
||||||
postgres: https://github.com/lib/pq
|
|
||||||
tidb: https://github.com/pingcap/tidb
|
|
||||||
|
|
||||||
usage:
|
|
||||||
|
|
||||||
go get -u github.com/astaxie/beego/orm
|
|
||||||
go get -u github.com/go-sql-driver/mysql
|
|
||||||
go get -u github.com/mattn/go-sqlite3
|
|
||||||
go get -u github.com/lib/pq
|
|
||||||
go get -u github.com/pingcap/tidb
|
|
||||||
|
|
||||||
#### MySQL
|
|
||||||
mysql -u root -e 'create database orm_test;'
|
|
||||||
export ORM_DRIVER=mysql
|
|
||||||
export ORM_SOURCE="root:@/orm_test?charset=utf8"
|
|
||||||
go test -v github.com/astaxie/beego/orm
|
|
||||||
|
|
||||||
|
|
||||||
#### Sqlite3
|
|
||||||
export ORM_DRIVER=sqlite3
|
|
||||||
export ORM_SOURCE='file:memory_test?mode=memory'
|
|
||||||
go test -v github.com/astaxie/beego/orm
|
|
||||||
|
|
||||||
|
|
||||||
#### PostgreSQL
|
|
||||||
psql -c 'create database orm_test;' -U postgres
|
|
||||||
export ORM_DRIVER=postgres
|
|
||||||
export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
|
||||||
go test -v github.com/astaxie/beego/orm
|
|
||||||
|
|
||||||
#### TiDB
|
|
||||||
export ORM_DRIVER=tidb
|
|
||||||
export ORM_SOURCE='memory://test/test'
|
|
||||||
go test -v github.com/astaxie/beego/orm
|
|
||||||
|
|
||||||
`)
|
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ func getFieldType(val reflect.Value) (ft int, err error) {
|
|||||||
case reflect.TypeOf(new(bool)):
|
case reflect.TypeOf(new(bool)):
|
||||||
ft = TypeBooleanField
|
ft = TypeBooleanField
|
||||||
case reflect.TypeOf(new(string)):
|
case reflect.TypeOf(new(string)):
|
||||||
ft = TypeCharField
|
ft = TypeVarCharField
|
||||||
case reflect.TypeOf(new(time.Time)):
|
case reflect.TypeOf(new(time.Time)):
|
||||||
ft = TypeDateTimeField
|
ft = TypeDateTimeField
|
||||||
default:
|
default:
|
||||||
@ -176,7 +176,7 @@ func getFieldType(val reflect.Value) (ft int, err error) {
|
|||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
ft = TypeBooleanField
|
ft = TypeBooleanField
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
ft = TypeCharField
|
ft = TypeVarCharField
|
||||||
default:
|
default:
|
||||||
if elm.Interface() == nil {
|
if elm.Interface() == nil {
|
||||||
panic(fmt.Errorf("%s is nil pointer, may be miss setting tag", val))
|
panic(fmt.Errorf("%s is nil pointer, may be miss setting tag", val))
|
||||||
@ -189,7 +189,7 @@ func getFieldType(val reflect.Value) (ft int, err error) {
|
|||||||
case sql.NullBool:
|
case sql.NullBool:
|
||||||
ft = TypeBooleanField
|
ft = TypeBooleanField
|
||||||
case sql.NullString:
|
case sql.NullString:
|
||||||
ft = TypeCharField
|
ft = TypeVarCharField
|
||||||
case time.Time:
|
case time.Time:
|
||||||
ft = TypeDateTimeField
|
ft = TypeDateTimeField
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
// Package orm provide ORM for MySQL/PostgreSQL/sqlite
|
// Package orm provide ORM for MySQL/PostgreSQL/sqlite
|
||||||
// Simple Usage
|
// Simple Usage
|
||||||
//
|
//
|
||||||
@ -52,6 +54,7 @@
|
|||||||
package orm
|
package orm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -458,11 +461,15 @@ func (o *orm) Using(name string) error {
|
|||||||
|
|
||||||
// begin transaction
|
// begin transaction
|
||||||
func (o *orm) Begin() error {
|
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 {
|
if o.isTx {
|
||||||
return ErrTxHasBegan
|
return ErrTxHasBegan
|
||||||
}
|
}
|
||||||
var tx *sql.Tx
|
var tx *sql.Tx
|
||||||
tx, err := o.db.(txer).Begin()
|
tx, err := o.db.(txer).BeginTx(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package orm
|
package orm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -150,6 +151,13 @@ func (d *dbQueryLog) Begin() (*sql.Tx, error) {
|
|||||||
return tx, err
|
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 {
|
func (d *dbQueryLog) Commit() error {
|
||||||
a := time.Now()
|
a := time.Now()
|
||||||
err := d.db.(txEnder).Commit()
|
err := d.db.(txEnder).Commit()
|
||||||
|
@ -55,16 +55,17 @@ func ColValue(opt operator, value interface{}) interface{} {
|
|||||||
|
|
||||||
// real query struct
|
// real query struct
|
||||||
type querySet struct {
|
type querySet struct {
|
||||||
mi *modelInfo
|
mi *modelInfo
|
||||||
cond *Condition
|
cond *Condition
|
||||||
related []string
|
related []string
|
||||||
relDepth int
|
relDepth int
|
||||||
limit int64
|
limit int64
|
||||||
offset int64
|
offset int64
|
||||||
groups []string
|
groups []string
|
||||||
orders []string
|
orders []string
|
||||||
distinct bool
|
distinct bool
|
||||||
orm *orm
|
forupdate bool
|
||||||
|
orm *orm
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ QuerySeter = new(querySet)
|
var _ QuerySeter = new(querySet)
|
||||||
@ -127,6 +128,12 @@ func (o querySet) Distinct() QuerySeter {
|
|||||||
return &o
|
return &o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add FOR UPDATE to SELECT
|
||||||
|
func (o querySet) ForUpdate() QuerySeter {
|
||||||
|
o.forupdate = true
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
// set relation model to query together.
|
// set relation model to query together.
|
||||||
// it will query relation models and assign to parent model.
|
// it will query relation models and assign to parent model.
|
||||||
func (o querySet) RelatedSel(params ...interface{}) QuerySeter {
|
func (o querySet) RelatedSel(params ...interface{}) QuerySeter {
|
||||||
@ -191,7 +198,11 @@ func (o *querySet) PrepareInsert() (Inserter, error) {
|
|||||||
// query all data and map to containers.
|
// query all data and map to containers.
|
||||||
// cols means the columns when querying.
|
// cols means the columns when querying.
|
||||||
func (o *querySet) All(container interface{}, cols ...string) (int64, error) {
|
func (o *querySet) All(container interface{}, cols ...string) (int64, error) {
|
||||||
return o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// query one row data and map to containers.
|
// query one row data and map to containers.
|
||||||
|
@ -12,10 +12,13 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
package orm
|
package orm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -452,9 +455,9 @@ func TestNullDataTypes(t *testing.T) {
|
|||||||
throwFail(t, AssertIs(*d.Float32Ptr, float32Ptr))
|
throwFail(t, AssertIs(*d.Float32Ptr, float32Ptr))
|
||||||
throwFail(t, AssertIs(*d.Float64Ptr, float64Ptr))
|
throwFail(t, AssertIs(*d.Float64Ptr, float64Ptr))
|
||||||
throwFail(t, AssertIs(*d.DecimalPtr, decimalPtr))
|
throwFail(t, AssertIs(*d.DecimalPtr, decimalPtr))
|
||||||
throwFail(t, AssertIs((*d.TimePtr).Format(testTime), timePtr.Format(testTime)))
|
throwFail(t, AssertIs((*d.TimePtr).UTC().Format(testTime), timePtr.UTC().Format(testTime)))
|
||||||
throwFail(t, AssertIs((*d.DatePtr).Format(testDate), datePtr.Format(testDate)))
|
throwFail(t, AssertIs((*d.DatePtr).UTC().Format(testDate), datePtr.UTC().Format(testDate)))
|
||||||
throwFail(t, AssertIs((*d.DateTimePtr).Format(testDateTime), dateTimePtr.Format(testDateTime)))
|
throwFail(t, AssertIs((*d.DateTimePtr).UTC().Format(testDateTime), dateTimePtr.UTC().Format(testDateTime)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDataCustomTypes(t *testing.T) {
|
func TestDataCustomTypes(t *testing.T) {
|
||||||
@ -1008,13 +1011,13 @@ func TestAll(t *testing.T) {
|
|||||||
|
|
||||||
qs = dORM.QueryTable("user")
|
qs = dORM.QueryTable("user")
|
||||||
num, err = qs.Filter("user_name", "nothing").All(&users)
|
num, err = qs.Filter("user_name", "nothing").All(&users)
|
||||||
throwFailNow(t, err)
|
throwFailNow(t, AssertIs(err, ErrNoRows))
|
||||||
throwFailNow(t, AssertIs(num, 0))
|
throwFailNow(t, AssertIs(num, 0))
|
||||||
|
|
||||||
var users3 []*User
|
var users3 []*User
|
||||||
qs = dORM.QueryTable("user")
|
qs = dORM.QueryTable("user")
|
||||||
num, err = qs.Filter("user_name", "nothing").All(&users3)
|
num, err = qs.Filter("user_name", "nothing").All(&users3)
|
||||||
throwFailNow(t, err)
|
throwFailNow(t, AssertIs(err, ErrNoRows))
|
||||||
throwFailNow(t, AssertIs(num, 0))
|
throwFailNow(t, AssertIs(num, 0))
|
||||||
throwFailNow(t, AssertIs(users3 == nil, false))
|
throwFailNow(t, AssertIs(users3 == nil, false))
|
||||||
}
|
}
|
||||||
@ -1990,6 +1993,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) {
|
func TestReadOrCreate(t *testing.T) {
|
||||||
u := &User{
|
u := &User{
|
||||||
UserName: "Kyle",
|
UserName: "Kyle",
|
||||||
@ -2260,6 +2323,7 @@ func TestIgnoreCaseTag(t *testing.T) {
|
|||||||
throwFail(t, AssertIs(info.fields.GetByName("Name02").column, "Name"))
|
throwFail(t, AssertIs(info.fields.GetByName("Name02").column, "Name"))
|
||||||
throwFail(t, AssertIs(info.fields.GetByName("Name03").column, "name"))
|
throwFail(t, AssertIs(info.fields.GetByName("Name03").column, "name"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInsertOrUpdate(t *testing.T) {
|
func TestInsertOrUpdate(t *testing.T) {
|
||||||
RegisterModel(new(User))
|
RegisterModel(new(User))
|
||||||
user := User{UserName: "unique_username133", Status: 1, Password: "o"}
|
user := User{UserName: "unique_username133", Status: 1, Password: "o"}
|
||||||
@ -2297,6 +2361,11 @@ func TestInsertOrUpdate(t *testing.T) {
|
|||||||
throwFailNow(t, AssertIs(user2.Status, test.Status))
|
throwFailNow(t, AssertIs(user2.Status, test.Status))
|
||||||
throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password)))
|
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 +
|
//test3 +
|
||||||
_, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status+1")
|
_, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status+1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
17
orm/types.go
17
orm/types.go
@ -15,6 +15,7 @@
|
|||||||
package orm
|
package orm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
@ -106,6 +107,17 @@ type Ormer interface {
|
|||||||
// ...
|
// ...
|
||||||
// err = o.Rollback()
|
// err = o.Rollback()
|
||||||
Begin() error
|
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 transaction
|
||||||
Commit() error
|
Commit() error
|
||||||
// rollback transaction
|
// rollback transaction
|
||||||
@ -190,6 +202,10 @@ type QuerySeter interface {
|
|||||||
// Distinct().
|
// Distinct().
|
||||||
// All(&permissions)
|
// All(&permissions)
|
||||||
Distinct() QuerySeter
|
Distinct() QuerySeter
|
||||||
|
// set FOR UPDATE to query.
|
||||||
|
// for example:
|
||||||
|
// o.QueryTable("user").Filter("uid", uid).ForUpdate().All(&users)
|
||||||
|
ForUpdate() QuerySeter
|
||||||
// return QuerySeter execution result number
|
// return QuerySeter execution result number
|
||||||
// for example:
|
// for example:
|
||||||
// num, err = qs.Filter("profile__age__gt", 28).Count()
|
// num, err = qs.Filter("profile__age__gt", 28).Count()
|
||||||
@ -397,6 +413,7 @@ type dbQuerier interface {
|
|||||||
// transaction beginner
|
// transaction beginner
|
||||||
type txer interface {
|
type txer interface {
|
||||||
Begin() (*sql.Tx, error)
|
Begin() (*sql.Tx, error)
|
||||||
|
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// transaction ending
|
// transaction ending
|
||||||
|
74
parser.go
74
parser.go
@ -114,20 +114,21 @@ type parsedParam struct {
|
|||||||
|
|
||||||
func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error {
|
func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error {
|
||||||
if f.Doc != nil {
|
if f.Doc != nil {
|
||||||
parsedComment, err := parseComment(f.Doc.List)
|
parsedComments, err := parseComment(f.Doc.List)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if parsedComment.routerPath != "" {
|
for _, parsedComment := range parsedComments {
|
||||||
key := pkgpath + ":" + controllerName
|
if parsedComment.routerPath != "" {
|
||||||
cc := ControllerComments{}
|
key := pkgpath + ":" + controllerName
|
||||||
cc.Method = f.Name.String()
|
cc := ControllerComments{}
|
||||||
cc.Router = parsedComment.routerPath
|
cc.Method = f.Name.String()
|
||||||
cc.AllowHTTPMethods = parsedComment.methods
|
cc.Router = parsedComment.routerPath
|
||||||
cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment)
|
cc.AllowHTTPMethods = parsedComment.methods
|
||||||
genInfoList[key] = append(genInfoList[key], cc)
|
cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment)
|
||||||
|
genInfoList[key] = append(genInfoList[key], cc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -177,26 +178,13 @@ func paramInPath(name, route string) bool {
|
|||||||
|
|
||||||
var routeRegex = regexp.MustCompile(`@router\s+(\S+)(?:\s+\[(\S+)\])?`)
|
var routeRegex = regexp.MustCompile(`@router\s+(\S+)(?:\s+\[(\S+)\])?`)
|
||||||
|
|
||||||
func parseComment(lines []*ast.Comment) (pc *parsedComment, err error) {
|
func parseComment(lines []*ast.Comment) (pcs []*parsedComment, err error) {
|
||||||
pc = &parsedComment{}
|
pcs = []*parsedComment{}
|
||||||
|
params := map[string]parsedParam{}
|
||||||
|
|
||||||
for _, c := range lines {
|
for _, c := range lines {
|
||||||
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
|
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
|
||||||
if strings.HasPrefix(t, "@router") {
|
if strings.HasPrefix(t, "@Param") {
|
||||||
matches := routeRegex.FindStringSubmatch(t)
|
|
||||||
if len(matches) == 3 {
|
|
||||||
pc.routerPath = matches[1]
|
|
||||||
methods := matches[2]
|
|
||||||
if methods == "" {
|
|
||||||
pc.methods = []string{"get"}
|
|
||||||
//pc.hasGet = true
|
|
||||||
} else {
|
|
||||||
pc.methods = strings.Split(methods, ",")
|
|
||||||
//pc.hasGet = strings.Contains(methods, "get")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("Router information is missing")
|
|
||||||
}
|
|
||||||
} else if strings.HasPrefix(t, "@Param") {
|
|
||||||
pv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Param")))
|
pv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Param")))
|
||||||
if len(pv) < 4 {
|
if len(pv) < 4 {
|
||||||
logs.Error("Invalid @Param format. Needs at least 4 parameters")
|
logs.Error("Invalid @Param format. Needs at least 4 parameters")
|
||||||
@ -217,17 +205,39 @@ func parseComment(lines []*ast.Comment) (pc *parsedComment, err error) {
|
|||||||
p.defValue = pv[3]
|
p.defValue = pv[3]
|
||||||
p.required, _ = strconv.ParseBool(pv[4])
|
p.required, _ = strconv.ParseBool(pv[4])
|
||||||
}
|
}
|
||||||
if pc.params == nil {
|
params[funcParamName] = p
|
||||||
pc.params = map[string]parsedParam{}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range lines {
|
||||||
|
var pc = &parsedComment{}
|
||||||
|
pc.params = params
|
||||||
|
|
||||||
|
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
|
||||||
|
if strings.HasPrefix(t, "@router") {
|
||||||
|
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
|
||||||
|
matches := routeRegex.FindStringSubmatch(t)
|
||||||
|
if len(matches) == 3 {
|
||||||
|
pc.routerPath = matches[1]
|
||||||
|
methods := matches[2]
|
||||||
|
if methods == "" {
|
||||||
|
pc.methods = []string{"get"}
|
||||||
|
//pc.hasGet = true
|
||||||
|
} else {
|
||||||
|
pc.methods = strings.Split(methods, ",")
|
||||||
|
//pc.hasGet = strings.Contains(methods, "get")
|
||||||
|
}
|
||||||
|
pcs = append(pcs, pc)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Router information is missing")
|
||||||
}
|
}
|
||||||
pc.params[funcParamName] = p
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// direct copy from bee\g_docs.go
|
// direct copy from bee\g_docs.go
|
||||||
// analisys params return []string
|
// analysis params return []string
|
||||||
// @Param query form string true "The email for login"
|
// @Param query form string true "The email for login"
|
||||||
// [query form string true "The email for login"]
|
// [query form string true "The email for login"]
|
||||||
func getparams(str string) []string {
|
func getparams(str string) []string {
|
||||||
|
164
router.go
164
router.go
@ -43,35 +43,35 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
routerTypeBeego = iota
|
routerTypeBeego = iota
|
||||||
routerTypeRESTFul
|
routerTypeRESTFul
|
||||||
routerTypeHandler
|
routerTypeHandler
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// HTTPMETHOD list the supported http methods.
|
// HTTPMETHOD list the supported http methods.
|
||||||
HTTPMETHOD = map[string]string{
|
HTTPMETHOD = map[string]bool{
|
||||||
"GET": "GET",
|
"GET": true,
|
||||||
"POST": "POST",
|
"POST": true,
|
||||||
"PUT": "PUT",
|
"PUT": true,
|
||||||
"DELETE": "DELETE",
|
"DELETE": true,
|
||||||
"PATCH": "PATCH",
|
"PATCH": true,
|
||||||
"OPTIONS": "OPTIONS",
|
"OPTIONS": true,
|
||||||
"HEAD": "HEAD",
|
"HEAD": true,
|
||||||
"TRACE": "TRACE",
|
"TRACE": true,
|
||||||
"CONNECT": "CONNECT",
|
"CONNECT": true,
|
||||||
"MKCOL": "MKCOL",
|
"MKCOL": true,
|
||||||
"COPY": "COPY",
|
"COPY": true,
|
||||||
"MOVE": "MOVE",
|
"MOVE": true,
|
||||||
"PROPFIND": "PROPFIND",
|
"PROPFIND": true,
|
||||||
"PROPPATCH": "PROPPATCH",
|
"PROPPATCH": true,
|
||||||
"LOCK": "LOCK",
|
"LOCK": true,
|
||||||
"UNLOCK": "UNLOCK",
|
"UNLOCK": true,
|
||||||
}
|
}
|
||||||
// these beego.Controller's methods shouldn't reflect to AutoRouter
|
// these beego.Controller's methods shouldn't reflect to AutoRouter
|
||||||
exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString",
|
exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString",
|
||||||
"RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJSON", "ServeJSONP",
|
"RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJSON", "ServeJSONP",
|
||||||
"ServeXML", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool",
|
"ServeYAML", "ServeXML", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool",
|
||||||
"GetFloat", "GetFile", "SaveToFile", "StartSession", "SetSession", "GetSession",
|
"GetFloat", "GetFile", "SaveToFile", "StartSession", "SetSession", "GetSession",
|
||||||
"DelSession", "SessionRegenerateID", "DestroySession", "IsAjax", "GetSecureCookie",
|
"DelSession", "SessionRegenerateID", "DestroySession", "IsAjax", "GetSecureCookie",
|
||||||
"SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml",
|
"SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml",
|
||||||
@ -117,6 +117,7 @@ type ControllerInfo struct {
|
|||||||
handler http.Handler
|
handler http.Handler
|
||||||
runFunction FilterFunc
|
runFunction FilterFunc
|
||||||
routerType int
|
routerType int
|
||||||
|
initialize func() ControllerInterface
|
||||||
methodParams []*param.MethodParam
|
methodParams []*param.MethodParam
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +170,7 @@ func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInt
|
|||||||
}
|
}
|
||||||
comma := strings.Split(colon[0], ",")
|
comma := strings.Split(colon[0], ",")
|
||||||
for _, m := range comma {
|
for _, m := range comma {
|
||||||
if _, ok := HTTPMETHOD[strings.ToUpper(m)]; m == "*" || ok {
|
if m == "*" || HTTPMETHOD[strings.ToUpper(m)] {
|
||||||
if val := reflectVal.MethodByName(colon[1]); val.IsValid() {
|
if val := reflectVal.MethodByName(colon[1]); val.IsValid() {
|
||||||
methods[strings.ToUpper(m)] = colon[1]
|
methods[strings.ToUpper(m)] = colon[1]
|
||||||
} else {
|
} else {
|
||||||
@ -187,15 +188,39 @@ func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInt
|
|||||||
route.methods = methods
|
route.methods = methods
|
||||||
route.routerType = routerTypeBeego
|
route.routerType = routerTypeBeego
|
||||||
route.controllerType = t
|
route.controllerType = t
|
||||||
|
route.initialize = func() ControllerInterface {
|
||||||
|
vc := reflect.New(route.controllerType)
|
||||||
|
execController, ok := vc.Interface().(ControllerInterface)
|
||||||
|
if !ok {
|
||||||
|
panic("controller is not ControllerInterface")
|
||||||
|
}
|
||||||
|
|
||||||
|
elemVal := reflect.ValueOf(c).Elem()
|
||||||
|
elemType := reflect.TypeOf(c).Elem()
|
||||||
|
execElem := reflect.ValueOf(execController).Elem()
|
||||||
|
|
||||||
|
numOfFields := elemVal.NumField()
|
||||||
|
for i := 0; i < numOfFields; i++ {
|
||||||
|
fieldType := elemType.Field(i)
|
||||||
|
elemField := execElem.FieldByName(fieldType.Name)
|
||||||
|
if elemField.CanSet() {
|
||||||
|
fieldVal := elemVal.Field(i)
|
||||||
|
elemField.Set(fieldVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return execController
|
||||||
|
}
|
||||||
|
|
||||||
route.methodParams = methodParams
|
route.methodParams = methodParams
|
||||||
if len(methods) == 0 {
|
if len(methods) == 0 {
|
||||||
for _, m := range HTTPMETHOD {
|
for m := range HTTPMETHOD {
|
||||||
p.addToRouter(m, pattern, route)
|
p.addToRouter(m, pattern, route)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for k := range methods {
|
for k := range methods {
|
||||||
if k == "*" {
|
if k == "*" {
|
||||||
for _, m := range HTTPMETHOD {
|
for m := range HTTPMETHOD {
|
||||||
p.addToRouter(m, pattern, route)
|
p.addToRouter(m, pattern, route)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -337,7 +362,7 @@ func (p *ControllerRegister) Any(pattern string, f FilterFunc) {
|
|||||||
// })
|
// })
|
||||||
func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
|
func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
|
||||||
method = strings.ToUpper(method)
|
method = strings.ToUpper(method)
|
||||||
if _, ok := HTTPMETHOD[method]; method != "*" && !ok {
|
if method != "*" && !HTTPMETHOD[method] {
|
||||||
panic("not support http method: " + method)
|
panic("not support http method: " + method)
|
||||||
}
|
}
|
||||||
route := &ControllerInfo{}
|
route := &ControllerInfo{}
|
||||||
@ -346,7 +371,7 @@ func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
|
|||||||
route.runFunction = f
|
route.runFunction = f
|
||||||
methods := make(map[string]string)
|
methods := make(map[string]string)
|
||||||
if method == "*" {
|
if method == "*" {
|
||||||
for _, val := range HTTPMETHOD {
|
for val := range HTTPMETHOD {
|
||||||
methods[val] = val
|
methods[val] = val
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -355,7 +380,7 @@ func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
|
|||||||
route.methods = methods
|
route.methods = methods
|
||||||
for k := range methods {
|
for k := range methods {
|
||||||
if k == "*" {
|
if k == "*" {
|
||||||
for _, m := range HTTPMETHOD {
|
for m := range HTTPMETHOD {
|
||||||
p.addToRouter(m, pattern, route)
|
p.addToRouter(m, pattern, route)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -375,7 +400,7 @@ func (p *ControllerRegister) Handler(pattern string, h http.Handler, options ...
|
|||||||
pattern = path.Join(pattern, "?:all(.*)")
|
pattern = path.Join(pattern, "?:all(.*)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, m := range HTTPMETHOD {
|
for m := range HTTPMETHOD {
|
||||||
p.addToRouter(m, pattern, route)
|
p.addToRouter(m, pattern, route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -410,7 +435,7 @@ func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface)
|
|||||||
patternFix := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name))
|
patternFix := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name))
|
||||||
patternFixInit := path.Join(prefix, controllerName, rt.Method(i).Name)
|
patternFixInit := path.Join(prefix, controllerName, rt.Method(i).Name)
|
||||||
route.pattern = pattern
|
route.pattern = pattern
|
||||||
for _, m := range HTTPMETHOD {
|
for m := range HTTPMETHOD {
|
||||||
p.addToRouter(m, pattern, route)
|
p.addToRouter(m, pattern, route)
|
||||||
p.addToRouter(m, patternInit, route)
|
p.addToRouter(m, patternInit, route)
|
||||||
p.addToRouter(m, patternFix, route)
|
p.addToRouter(m, patternFix, route)
|
||||||
@ -511,7 +536,7 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
|
|||||||
if c.routerType == routerTypeBeego &&
|
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()), controllName) {
|
||||||
find := false
|
find := false
|
||||||
if _, ok := HTTPMETHOD[strings.ToUpper(methodName)]; ok {
|
if HTTPMETHOD[strings.ToUpper(methodName)] {
|
||||||
if len(c.methods) == 0 {
|
if len(c.methods) == 0 {
|
||||||
find = true
|
find = true
|
||||||
} else if m, ok := c.methods[strings.ToUpper(methodName)]; ok && m == strings.ToUpper(methodName) {
|
} else if m, ok := c.methods[strings.ToUpper(methodName)]; ok && m == strings.ToUpper(methodName) {
|
||||||
@ -659,7 +684,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filter wrong http method
|
// filter wrong http method
|
||||||
if _, ok := HTTPMETHOD[r.Method]; !ok {
|
if !HTTPMETHOD[r.Method] {
|
||||||
http.Error(rw, "Method Not Allowed", 405)
|
http.Error(rw, "Method Not Allowed", 405)
|
||||||
goto Admin
|
goto Admin
|
||||||
}
|
}
|
||||||
@ -768,14 +793,20 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
// also defined runRouter & runMethod from filter
|
// also defined runRouter & runMethod from filter
|
||||||
if !isRunnable {
|
if !isRunnable {
|
||||||
//Invoke the request handler
|
//Invoke the request handler
|
||||||
vc := reflect.New(runRouter)
|
var execController ControllerInterface
|
||||||
execController, ok := vc.Interface().(ControllerInterface)
|
if routerInfo.initialize != nil {
|
||||||
if !ok {
|
execController = routerInfo.initialize()
|
||||||
panic("controller is not ControllerInterface")
|
} else {
|
||||||
|
vc := reflect.New(runRouter)
|
||||||
|
var ok bool
|
||||||
|
execController, ok = vc.Interface().(ControllerInterface)
|
||||||
|
if !ok {
|
||||||
|
panic("controller is not ControllerInterface")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//call the controller init function
|
//call the controller init function
|
||||||
execController.Init(context, runRouter.Name(), runMethod, vc.Interface())
|
execController.Init(context, runRouter.Name(), runMethod, execController)
|
||||||
|
|
||||||
//call prepare function
|
//call prepare function
|
||||||
execController.Prepare()
|
execController.Prepare()
|
||||||
@ -810,6 +841,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
execController.Options()
|
execController.Options()
|
||||||
default:
|
default:
|
||||||
if !execController.HandlerFunc(runMethod) {
|
if !execController.HandlerFunc(runMethod) {
|
||||||
|
vc := reflect.ValueOf(execController)
|
||||||
method := vc.MethodByName(runMethod)
|
method := vc.MethodByName(runMethod)
|
||||||
in := param.ConvertParams(methodParams, method.Type(), context)
|
in := param.ConvertParams(methodParams, method.Type(), context)
|
||||||
out := method.Call(in)
|
out := method.Call(in)
|
||||||
@ -845,17 +877,22 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Admin:
|
Admin:
|
||||||
//admin module record QPS
|
//admin module record QPS
|
||||||
|
|
||||||
|
statusCode := context.ResponseWriter.Status
|
||||||
|
if statusCode == 0 {
|
||||||
|
statusCode = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
logAccess(context, &startTime, statusCode)
|
||||||
|
|
||||||
if BConfig.Listen.EnableAdmin {
|
if BConfig.Listen.EnableAdmin {
|
||||||
timeDur := time.Since(startTime)
|
timeDur := time.Since(startTime)
|
||||||
pattern := ""
|
pattern := ""
|
||||||
if routerInfo != nil {
|
if routerInfo != nil {
|
||||||
pattern = routerInfo.pattern
|
pattern = routerInfo.pattern
|
||||||
}
|
}
|
||||||
statusCode := context.ResponseWriter.Status
|
|
||||||
if statusCode == 0 {
|
|
||||||
statusCode = 200
|
|
||||||
}
|
|
||||||
if FilterMonitorFunc(r.Method, r.URL.Path, timeDur, pattern, statusCode) {
|
if FilterMonitorFunc(r.Method, r.URL.Path, timeDur, pattern, statusCode) {
|
||||||
if runRouter != nil {
|
if runRouter != nil {
|
||||||
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, runRouter.Name(), timeDur)
|
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, runRouter.Name(), timeDur)
|
||||||
@ -865,20 +902,13 @@ Admin:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if BConfig.RunMode == DEV || BConfig.Log.AccessLogs {
|
if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs {
|
||||||
timeDur := time.Since(startTime)
|
|
||||||
var devInfo string
|
var devInfo string
|
||||||
|
timeDur := time.Since(startTime)
|
||||||
statusCode := context.ResponseWriter.Status
|
|
||||||
if statusCode == 0 {
|
|
||||||
statusCode = 200
|
|
||||||
}
|
|
||||||
|
|
||||||
iswin := (runtime.GOOS == "windows")
|
iswin := (runtime.GOOS == "windows")
|
||||||
statusColor := logs.ColorByStatus(iswin, statusCode)
|
statusColor := logs.ColorByStatus(iswin, statusCode)
|
||||||
methodColor := logs.ColorByMethod(iswin, r.Method)
|
methodColor := logs.ColorByMethod(iswin, r.Method)
|
||||||
resetColor := logs.ColorByMethod(iswin, "")
|
resetColor := logs.ColorByMethod(iswin, "")
|
||||||
|
|
||||||
if findRouter {
|
if findRouter {
|
||||||
if routerInfo != nil {
|
if routerInfo != nil {
|
||||||
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode,
|
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode,
|
||||||
@ -898,7 +928,6 @@ Admin:
|
|||||||
logs.Debug(devInfo)
|
logs.Debug(devInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call WriteHeader if status code has been set changed
|
// Call WriteHeader if status code has been set changed
|
||||||
if context.Output.Status != 0 {
|
if context.Output.Status != 0 {
|
||||||
context.ResponseWriter.WriteHeader(context.Output.Status)
|
context.ResponseWriter.WriteHeader(context.Output.Status)
|
||||||
@ -914,7 +943,7 @@ func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, ex
|
|||||||
context.RenderMethodResult(resultValue)
|
context.RenderMethodResult(resultValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !context.ResponseWriter.Started && context.Output.Status == 0 {
|
if !context.ResponseWriter.Started && len(results) > 0 && context.Output.Status == 0 {
|
||||||
context.Output.SetStatus(200)
|
context.Output.SetStatus(200)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -945,3 +974,38 @@ func toURL(params map[string]string) string {
|
|||||||
}
|
}
|
||||||
return strings.TrimRight(u, "&")
|
return strings.TrimRight(u, "&")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func logAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) {
|
||||||
|
//Skip logging if AccessLogs config is false
|
||||||
|
if !BConfig.Log.AccessLogs {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//Skip logging static requests unless EnableStaticLogs config is true
|
||||||
|
if !BConfig.Log.EnableStaticLogs && DefaultAccessLogFilter.Filter(ctx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
requestTime time.Time
|
||||||
|
elapsedTime time.Duration
|
||||||
|
r = ctx.Request
|
||||||
|
)
|
||||||
|
if startTime != nil {
|
||||||
|
requestTime = *startTime
|
||||||
|
elapsedTime = time.Since(*startTime)
|
||||||
|
}
|
||||||
|
record := &logs.AccessLogRecord{
|
||||||
|
RemoteAddr: ctx.Input.IP(),
|
||||||
|
RequestTime: requestTime,
|
||||||
|
RequestMethod: r.Method,
|
||||||
|
Request: fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto),
|
||||||
|
ServerProtocol: r.Proto,
|
||||||
|
Host: r.Host,
|
||||||
|
Status: statusCode,
|
||||||
|
ElapsedTime: elapsedTime,
|
||||||
|
HTTPReferrer: r.Header.Get("Referer"),
|
||||||
|
HTTPUserAgent: r.Header.Get("User-Agent"),
|
||||||
|
RemoteUser: r.Header.Get("Remote-User"),
|
||||||
|
BodyBytesSent: 0, //@todo this one is missing!
|
||||||
|
}
|
||||||
|
logs.AccessLog(record, BConfig.Log.AccessLogsFormat)
|
||||||
|
}
|
||||||
|
@ -695,3 +695,30 @@ func beegoResetParams(ctx *context.Context) {
|
|||||||
func beegoHandleResetParams(ctx *context.Context) {
|
func beegoHandleResetParams(ctx *context.Context) {
|
||||||
ctx.ResponseWriter.Header().Set("splat", ctx.Input.Param(":splat"))
|
ctx.ResponseWriter.Header().Set("splat", ctx.Input.Param(":splat"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// YAML
|
||||||
|
type YAMLController struct {
|
||||||
|
Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jc *YAMLController) Prepare() {
|
||||||
|
jc.Data["yaml"] = "prepare"
|
||||||
|
jc.ServeYAML()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jc *YAMLController) Get() {
|
||||||
|
jc.Data["Username"] = "astaxie"
|
||||||
|
jc.Ctx.Output.Body([]byte("ok"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLPrepare(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", "/yaml/list", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler := NewControllerRegister()
|
||||||
|
handler.Add("/yaml/list", &YAMLController{})
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
if strings.TrimSpace(w.Body.String()) != "prepare" {
|
||||||
|
t.Errorf(w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
|
|
||||||
// Package redis for session provider
|
// Package redis for session provider
|
||||||
//
|
//
|
||||||
// depend on github.com/garyburd/redigo/redis
|
// depend on github.com/gomodule/redigo/redis
|
||||||
//
|
//
|
||||||
// go install github.com/garyburd/redigo/redis
|
// go install github.com/gomodule/redigo/redis
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
// import(
|
// import(
|
||||||
@ -24,10 +24,10 @@
|
|||||||
// "github.com/astaxie/beego/session"
|
// "github.com/astaxie/beego/session"
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// func init() {
|
// func init() {
|
||||||
// globalSessions, _ = session.NewManager("redis", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070"}``)
|
// globalSessions, _ = session.NewManager("redis", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070"}``)
|
||||||
// go globalSessions.GC()
|
// go globalSessions.GC()
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// more docs: http://beego.me/docs/module/session.md
|
// more docs: http://beego.me/docs/module/session.md
|
||||||
package redis
|
package redis
|
||||||
@ -37,10 +37,11 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/session"
|
"github.com/astaxie/beego/session"
|
||||||
|
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/gomodule/redigo/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
var redispder = &Provider{}
|
var redispder = &Provider{}
|
||||||
@ -118,8 +119,8 @@ type Provider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SessionInit init redis session
|
// SessionInit init redis session
|
||||||
// savepath like redis server addr,pool size,password,dbnum
|
// savepath like redis server addr,pool size,password,dbnum,IdleTimeout second
|
||||||
// e.g. 127.0.0.1:6379,100,astaxie,0
|
// e.g. 127.0.0.1:6379,100,astaxie,0,30
|
||||||
func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
|
func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
|
||||||
rp.maxlifetime = maxlifetime
|
rp.maxlifetime = maxlifetime
|
||||||
configs := strings.Split(savePath, ",")
|
configs := strings.Split(savePath, ",")
|
||||||
@ -149,24 +150,39 @@ func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
|
|||||||
} else {
|
} else {
|
||||||
rp.dbNum = 0
|
rp.dbNum = 0
|
||||||
}
|
}
|
||||||
rp.poollist = redis.NewPool(func() (redis.Conn, error) {
|
var idleTimeout time.Duration = 0
|
||||||
c, err := redis.Dial("tcp", rp.savePath)
|
if len(configs) > 4 {
|
||||||
if err != nil {
|
timeout, err := strconv.Atoi(configs[4])
|
||||||
return nil, err
|
if err == nil && timeout > 0 {
|
||||||
|
idleTimeout = time.Duration(timeout) * time.Second
|
||||||
}
|
}
|
||||||
if rp.password != "" {
|
}
|
||||||
if _, err = c.Do("AUTH", rp.password); err != nil {
|
rp.poollist = &redis.Pool{
|
||||||
c.Close()
|
Dial: func() (redis.Conn, error) {
|
||||||
|
c, err := redis.Dial("tcp", rp.savePath)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
if rp.password != "" {
|
||||||
_, err = c.Do("SELECT", rp.dbNum)
|
if _, err = c.Do("AUTH", rp.password); err != nil {
|
||||||
if err != nil {
|
c.Close()
|
||||||
c.Close()
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
}
|
||||||
return c, err
|
// some redis proxy such as twemproxy is not support select command
|
||||||
}, rp.poolsize)
|
if rp.dbNum > 0 {
|
||||||
|
_, err = c.Do("SELECT", rp.dbNum)
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
},
|
||||||
|
MaxIdle: rp.poolsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
rp.poollist.IdleTimeout = idleTimeout
|
||||||
|
|
||||||
return rp.poollist.Get().Err()
|
return rp.poollist.Get().Err()
|
||||||
}
|
}
|
||||||
|
220
session/redis_cluster/redis_cluster.go
Normal file
220
session/redis_cluster/redis_cluster.go
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
// 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_cluster"
|
||||||
|
// "github.com/astaxie/beego/session"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// globalSessions, _ = session.NewManager("redis_cluster", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070;127.0.0.1:7071"}``)
|
||||||
|
// go globalSessions.GC()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// more docs: http://beego.me/docs/module/session.md
|
||||||
|
package redis_cluster
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"github.com/astaxie/beego/session"
|
||||||
|
rediss "github.com/go-redis/redis"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var redispder = &Provider{}
|
||||||
|
|
||||||
|
// MaxPoolSize redis_cluster max pool size
|
||||||
|
var MaxPoolSize = 1000
|
||||||
|
|
||||||
|
// SessionStore redis_cluster session store
|
||||||
|
type SessionStore struct {
|
||||||
|
p *rediss.ClusterClient
|
||||||
|
sid string
|
||||||
|
lock sync.RWMutex
|
||||||
|
values map[interface{}]interface{}
|
||||||
|
maxlifetime int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set value in redis_cluster 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_cluster 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_cluster 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_cluster session
|
||||||
|
func (rs *SessionStore) Flush() error {
|
||||||
|
rs.lock.Lock()
|
||||||
|
defer rs.lock.Unlock()
|
||||||
|
rs.values = make(map[interface{}]interface{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionID get redis_cluster session id
|
||||||
|
func (rs *SessionStore) SessionID() string {
|
||||||
|
return rs.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionRelease save session values to redis_cluster
|
||||||
|
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_cluster session provider
|
||||||
|
type Provider struct {
|
||||||
|
maxlifetime int64
|
||||||
|
savePath string
|
||||||
|
poolsize int
|
||||||
|
password string
|
||||||
|
dbNum int
|
||||||
|
poollist *rediss.ClusterClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionInit init redis_cluster session
|
||||||
|
// savepath like redis server addr,pool size,password,dbnum
|
||||||
|
// e.g. 127.0.0.1:6379;127.0.0.1:6380,100,test,0
|
||||||
|
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 = MaxPoolSize
|
||||||
|
} else {
|
||||||
|
rp.poolsize = poolsize
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rp.poolsize = MaxPoolSize
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
rp.poollist = rediss.NewClusterClient(&rediss.ClusterOptions{
|
||||||
|
Addrs: strings.Split(rp.savePath, ";"),
|
||||||
|
Password: rp.password,
|
||||||
|
PoolSize: rp.poolsize,
|
||||||
|
})
|
||||||
|
return rp.poollist.Ping().Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionRead read redis_cluster 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 != rediss.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_cluster 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_cluster 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_cluster", redispder)
|
||||||
|
}
|
@ -78,6 +78,8 @@ func (fs *FileSessionStore) SessionID() string {
|
|||||||
|
|
||||||
// SessionRelease Write file session to local file with Gob string
|
// SessionRelease Write file session to local file with Gob string
|
||||||
func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) {
|
func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) {
|
||||||
|
filepder.lock.Lock()
|
||||||
|
defer filepder.lock.Unlock()
|
||||||
b, err := EncodeGob(fs.values)
|
b, err := EncodeGob(fs.values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SLogger.Println(err)
|
SLogger.Println(err)
|
||||||
@ -164,7 +166,7 @@ func (fp *FileProvider) SessionRead(sid string) (Store, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SessionExist Check file session exist.
|
// SessionExist Check file session exist.
|
||||||
// it checkes the file named from sid exist or not.
|
// it checks the file named from sid exist or not.
|
||||||
func (fp *FileProvider) SessionExist(sid string) bool {
|
func (fp *FileProvider) SessionExist(sid string) bool {
|
||||||
filepder.lock.Lock()
|
filepder.lock.Lock()
|
||||||
defer filepder.lock.Unlock()
|
defer filepder.lock.Unlock()
|
||||||
|
@ -149,7 +149,7 @@ func decodeCookie(block cipher.Block, hashKey, name, value string, gcmaxlifetime
|
|||||||
// 2. Verify MAC. Value is "date|value|mac".
|
// 2. Verify MAC. Value is "date|value|mac".
|
||||||
parts := bytes.SplitN(b, []byte("|"), 3)
|
parts := bytes.SplitN(b, []byte("|"), 3)
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
return nil, errors.New("Decode: invalid value %v")
|
return nil, errors.New("Decode: invalid value format")
|
||||||
}
|
}
|
||||||
|
|
||||||
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])]...)
|
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])]...)
|
||||||
|
@ -74,7 +74,7 @@ func serverStaticRouter(ctx *context.Context) {
|
|||||||
if enableCompress {
|
if enableCompress {
|
||||||
acceptEncoding = context.ParseEncoding(ctx.Request)
|
acceptEncoding = context.ParseEncoding(ctx.Request)
|
||||||
}
|
}
|
||||||
b, n, sch, err := openFile(filePath, fileInfo, acceptEncoding)
|
b, n, sch, reader, err := openFile(filePath, fileInfo, acceptEncoding)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if BConfig.RunMode == DEV {
|
if BConfig.RunMode == DEV {
|
||||||
logs.Warn("Can't compress the file:", filePath, err)
|
logs.Warn("Can't compress the file:", filePath, err)
|
||||||
@ -89,47 +89,53 @@ func serverStaticRouter(ctx *context.Context) {
|
|||||||
ctx.Output.Header("Content-Length", strconv.FormatInt(sch.size, 10))
|
ctx.Output.Header("Content-Length", strconv.FormatInt(sch.size, 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
http.ServeContent(ctx.ResponseWriter, ctx.Request, filePath, sch.modTime, sch)
|
http.ServeContent(ctx.ResponseWriter, ctx.Request, filePath, sch.modTime, reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
type serveContentHolder struct {
|
type serveContentHolder struct {
|
||||||
*bytes.Reader
|
data []byte
|
||||||
modTime time.Time
|
modTime time.Time
|
||||||
size int64
|
size int64
|
||||||
encoding string
|
encoding string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type serveContentReader struct {
|
||||||
|
*bytes.Reader
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
staticFileMap = make(map[string]*serveContentHolder)
|
staticFileMap = make(map[string]*serveContentHolder)
|
||||||
mapLock sync.RWMutex
|
mapLock sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, error) {
|
func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, *serveContentReader, error) {
|
||||||
mapKey := acceptEncoding + ":" + filePath
|
mapKey := acceptEncoding + ":" + filePath
|
||||||
mapLock.RLock()
|
mapLock.RLock()
|
||||||
mapFile := staticFileMap[mapKey]
|
mapFile := staticFileMap[mapKey]
|
||||||
mapLock.RUnlock()
|
mapLock.RUnlock()
|
||||||
if isOk(mapFile, fi) {
|
if isOk(mapFile, fi) {
|
||||||
return mapFile.encoding != "", mapFile.encoding, mapFile, nil
|
reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
|
||||||
|
return mapFile.encoding != "", mapFile.encoding, mapFile, reader, nil
|
||||||
}
|
}
|
||||||
mapLock.Lock()
|
mapLock.Lock()
|
||||||
defer mapLock.Unlock()
|
defer mapLock.Unlock()
|
||||||
if mapFile = staticFileMap[mapKey]; !isOk(mapFile, fi) {
|
if mapFile = staticFileMap[mapKey]; !isOk(mapFile, fi) {
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", nil, err
|
return false, "", nil, nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
var bufferWriter bytes.Buffer
|
var bufferWriter bytes.Buffer
|
||||||
_, n, err := context.WriteFile(acceptEncoding, &bufferWriter, file)
|
_, n, err := context.WriteFile(acceptEncoding, &bufferWriter, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", nil, err
|
return false, "", nil, nil, err
|
||||||
}
|
}
|
||||||
mapFile = &serveContentHolder{Reader: bytes.NewReader(bufferWriter.Bytes()), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), encoding: n}
|
mapFile = &serveContentHolder{data: bufferWriter.Bytes(), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), encoding: n}
|
||||||
staticFileMap[mapKey] = mapFile
|
staticFileMap[mapKey] = mapFile
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapFile.encoding != "", mapFile.encoding, mapFile, nil
|
reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
|
||||||
|
return mapFile.encoding != "", mapFile.encoding, mapFile, reader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isOk(s *serveContentHolder, fi os.FileInfo) bool {
|
func isOk(s *serveContentHolder, fi os.FileInfo) bool {
|
||||||
|
@ -16,7 +16,7 @@ var licenseFile = filepath.Join(currentWorkDir, "LICENSE")
|
|||||||
|
|
||||||
func testOpenFile(encoding string, content []byte, t *testing.T) {
|
func testOpenFile(encoding string, content []byte, t *testing.T) {
|
||||||
fi, _ := os.Stat(licenseFile)
|
fi, _ := os.Stat(licenseFile)
|
||||||
b, n, sch, err := openFile(licenseFile, fi, encoding)
|
b, n, sch, reader, err := openFile(licenseFile, fi, encoding)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@ -24,7 +24,7 @@ func testOpenFile(encoding string, content []byte, t *testing.T) {
|
|||||||
|
|
||||||
t.Log("open static file encoding "+n, b)
|
t.Log("open static file encoding "+n, b)
|
||||||
|
|
||||||
assetOpenFileAndContent(sch, content, t)
|
assetOpenFileAndContent(sch, reader, content, t)
|
||||||
}
|
}
|
||||||
func TestOpenStaticFile_1(t *testing.T) {
|
func TestOpenStaticFile_1(t *testing.T) {
|
||||||
file, _ := os.Open(licenseFile)
|
file, _ := os.Open(licenseFile)
|
||||||
@ -53,13 +53,13 @@ func TestOpenStaticFileDeflate_1(t *testing.T) {
|
|||||||
testOpenFile("deflate", content, t)
|
testOpenFile("deflate", content, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func assetOpenFileAndContent(sch *serveContentHolder, content []byte, t *testing.T) {
|
func assetOpenFileAndContent(sch *serveContentHolder, reader *serveContentReader, content []byte, t *testing.T) {
|
||||||
t.Log(sch.size, len(content))
|
t.Log(sch.size, len(content))
|
||||||
if sch.size != int64(len(content)) {
|
if sch.size != int64(len(content)) {
|
||||||
t.Log("static content file size not same")
|
t.Log("static content file size not same")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
bs, _ := ioutil.ReadAll(sch)
|
bs, _ := ioutil.ReadAll(reader)
|
||||||
for i, v := range content {
|
for i, v := range content {
|
||||||
if v != bs[i] {
|
if v != bs[i] {
|
||||||
t.Log("content not same")
|
t.Log("content not same")
|
||||||
|
@ -121,6 +121,8 @@ type Schema struct {
|
|||||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||||
Items *Schema `json:"items,omitempty" yaml:"items,omitempty"`
|
Items *Schema `json:"items,omitempty" yaml:"items,omitempty"`
|
||||||
Properties map[string]Propertie `json:"properties,omitempty" yaml:"properties,omitempty"`
|
Properties map[string]Propertie `json:"properties,omitempty" yaml:"properties,omitempty"`
|
||||||
|
Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"`
|
||||||
|
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification
|
// Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification
|
||||||
@ -130,7 +132,7 @@ type Propertie struct {
|
|||||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||||
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
|
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
|
||||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||||
Example string `json:"example,omitempty" yaml:"example,omitempty"`
|
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
|
||||||
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
|
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
|
||||||
Format string `json:"format,omitempty" yaml:"format,omitempty"`
|
Format string `json:"format,omitempty" yaml:"format,omitempty"`
|
||||||
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
|
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
|
||||||
@ -141,7 +143,7 @@ type Propertie struct {
|
|||||||
|
|
||||||
// Response as they are returned from executing this operation.
|
// Response as they are returned from executing this operation.
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
Description string `json:"description" yaml:"description"`
|
||||||
Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"`
|
Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"`
|
||||||
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -218,9 +218,10 @@ func BuildTemplate(dir string, files ...string) error {
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Error("parse template err:", file, err)
|
logs.Error("parse template err:", file, err)
|
||||||
} else {
|
templatesLock.Unlock()
|
||||||
beeTemplates[file] = t
|
return err
|
||||||
}
|
}
|
||||||
|
beeTemplates[file] = t
|
||||||
templatesLock.Unlock()
|
templatesLock.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package beego
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -84,24 +85,24 @@ func DateFormat(t time.Time, layout string) (datestring string) {
|
|||||||
var datePatterns = []string{
|
var datePatterns = []string{
|
||||||
// year
|
// year
|
||||||
"Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003
|
"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
|
// month
|
||||||
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12
|
"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
|
"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", "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
|
"F", "January", // A full textual representation of a month, such as January or March January through December
|
||||||
|
|
||||||
// day
|
// day
|
||||||
"d", "02", // Day of the month, 2 digits with leading zeros 01 to 31
|
"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
|
// 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
|
"l", "Monday", // A full textual representation of the day of the week Sunday through Saturday
|
||||||
|
|
||||||
// time
|
// 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
|
"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", "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
|
"H", "15", // 24-hour format of an hour with leading zeros 00 through 23
|
||||||
@ -207,14 +208,12 @@ func Htmlquote(text string) string {
|
|||||||
'<'&">'
|
'<'&">'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
text = strings.Replace(text, "&", "&", -1) // Must be done first!
|
text = html.EscapeString(text)
|
||||||
text = strings.Replace(text, "<", "<", -1)
|
text = strings.NewReplacer(
|
||||||
text = strings.Replace(text, ">", ">", -1)
|
`“`, "“",
|
||||||
text = strings.Replace(text, "'", "'", -1)
|
`”`, "”",
|
||||||
text = strings.Replace(text, "\"", """, -1)
|
` `, " ",
|
||||||
text = strings.Replace(text, "“", "“", -1)
|
).Replace(text)
|
||||||
text = strings.Replace(text, "”", "”", -1)
|
|
||||||
text = strings.Replace(text, " ", " ", -1)
|
|
||||||
|
|
||||||
return strings.TrimSpace(text)
|
return strings.TrimSpace(text)
|
||||||
}
|
}
|
||||||
@ -228,17 +227,7 @@ func Htmlunquote(text string) string {
|
|||||||
'<\\'&">'
|
'<\\'&">'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// strings.Replace(s, old, new, n)
|
text = html.UnescapeString(text)
|
||||||
// 在s字符串中,把old字符串替换为new字符串,n表示替换的次数,小于0表示全部替换
|
|
||||||
|
|
||||||
text = strings.Replace(text, " ", " ", -1)
|
|
||||||
text = strings.Replace(text, "”", "”", -1)
|
|
||||||
text = strings.Replace(text, "“", "“", -1)
|
|
||||||
text = strings.Replace(text, """, "\"", -1)
|
|
||||||
text = strings.Replace(text, "'", "'", -1)
|
|
||||||
text = strings.Replace(text, ">", ">", -1)
|
|
||||||
text = strings.Replace(text, "<", "<", -1)
|
|
||||||
text = strings.Replace(text, "&", "&", -1) // Must be done last!
|
|
||||||
|
|
||||||
return strings.TrimSpace(text)
|
return strings.TrimSpace(text)
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ func TestCompareRelated(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHtmlquote(t *testing.T) {
|
func TestHtmlquote(t *testing.T) {
|
||||||
h := `<' ”“&">`
|
h := `<' ”“&">`
|
||||||
s := `<' ”“&">`
|
s := `<' ”“&">`
|
||||||
if Htmlquote(s) != h {
|
if Htmlquote(s) != h {
|
||||||
t.Error("should be equal")
|
t.Error("should be equal")
|
||||||
@ -102,8 +102,8 @@ func TestHtmlquote(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHtmlunquote(t *testing.T) {
|
func TestHtmlunquote(t *testing.T) {
|
||||||
h := `<' ”“&">`
|
h := `<' ”“&">`
|
||||||
s := `<' ”“&">`
|
s := `<' ”“&">`
|
||||||
if Htmlunquote(h) != s {
|
if Htmlunquote(h) != s {
|
||||||
t.Error("should be equal")
|
t.Error("should be equal")
|
||||||
}
|
}
|
||||||
|
2
tree.go
2
tree.go
@ -28,7 +28,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Tree has three elements: FixRouter/wildcard/leaves
|
// Tree has three elements: FixRouter/wildcard/leaves
|
||||||
// fixRouter sotres Fixed Router
|
// fixRouter stores Fixed Router
|
||||||
// wildcard stores params
|
// wildcard stores params
|
||||||
// leaves store the endpoint information
|
// leaves store the endpoint information
|
||||||
type Tree struct {
|
type Tree struct {
|
||||||
|
226
unregroute_test.go
Normal file
226
unregroute_test.go
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
// The unregroute_test.go contains tests for the unregister route
|
||||||
|
// functionality, that allows overriding route paths in children project
|
||||||
|
// that embed parent routers.
|
||||||
|
//
|
||||||
|
|
||||||
|
const contentRootOriginal = "ok-original-root"
|
||||||
|
const contentLevel1Original = "ok-original-level1"
|
||||||
|
const contentLevel2Original = "ok-original-level2"
|
||||||
|
|
||||||
|
const contentRootReplacement = "ok-replacement-root"
|
||||||
|
const contentLevel1Replacement = "ok-replacement-level1"
|
||||||
|
const contentLevel2Replacement = "ok-replacement-level2"
|
||||||
|
|
||||||
|
// TestPreUnregController will supply content for the original routes,
|
||||||
|
// before unregistration
|
||||||
|
type TestPreUnregController struct {
|
||||||
|
Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestPreUnregController) GetFixedRoot() {
|
||||||
|
tc.Ctx.Output.Body([]byte(contentRootOriginal))
|
||||||
|
}
|
||||||
|
func (tc *TestPreUnregController) GetFixedLevel1() {
|
||||||
|
tc.Ctx.Output.Body([]byte(contentLevel1Original))
|
||||||
|
}
|
||||||
|
func (tc *TestPreUnregController) GetFixedLevel2() {
|
||||||
|
tc.Ctx.Output.Body([]byte(contentLevel2Original))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPostUnregController will supply content for the overriding routes,
|
||||||
|
// after the original ones are unregistered.
|
||||||
|
type TestPostUnregController struct {
|
||||||
|
Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestPostUnregController) GetFixedRoot() {
|
||||||
|
tc.Ctx.Output.Body([]byte(contentRootReplacement))
|
||||||
|
}
|
||||||
|
func (tc *TestPostUnregController) GetFixedLevel1() {
|
||||||
|
tc.Ctx.Output.Body([]byte(contentLevel1Replacement))
|
||||||
|
}
|
||||||
|
func (tc *TestPostUnregController) GetFixedLevel2() {
|
||||||
|
tc.Ctx.Output.Body([]byte(contentLevel2Replacement))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUnregisterFixedRouteRoot replaces just the root fixed route path.
|
||||||
|
// In this case, for a path like "/level1/level2" or "/level1", those actions
|
||||||
|
// should remain intact, and continue to serve the original content.
|
||||||
|
func TestUnregisterFixedRouteRoot(t *testing.T) {
|
||||||
|
|
||||||
|
var method = "GET"
|
||||||
|
|
||||||
|
handler := NewControllerRegister()
|
||||||
|
handler.Add("/", &TestPreUnregController{}, "get:GetFixedRoot")
|
||||||
|
handler.Add("/level1", &TestPreUnregController{}, "get:GetFixedLevel1")
|
||||||
|
handler.Add("/level1/level2", &TestPreUnregController{}, "get:GetFixedLevel2")
|
||||||
|
|
||||||
|
// Test original root
|
||||||
|
testHelperFnContentCheck(t, handler, "Test original root",
|
||||||
|
method, "/", contentRootOriginal)
|
||||||
|
|
||||||
|
// Test original level 1
|
||||||
|
testHelperFnContentCheck(t, handler, "Test original level 1",
|
||||||
|
method, "/level1", contentLevel1Original)
|
||||||
|
|
||||||
|
// Test original level 2
|
||||||
|
testHelperFnContentCheck(t, handler, "Test original level 2",
|
||||||
|
method, "/level1/level2", contentLevel2Original)
|
||||||
|
|
||||||
|
// Remove only the root path
|
||||||
|
findAndRemoveSingleTree(handler.routers[method])
|
||||||
|
|
||||||
|
// Replace the root path TestPreUnregController action with the action from
|
||||||
|
// TestPostUnregController
|
||||||
|
handler.Add("/", &TestPostUnregController{}, "get:GetFixedRoot")
|
||||||
|
|
||||||
|
// Test replacement root (expect change)
|
||||||
|
testHelperFnContentCheck(t, handler, "Test replacement root (expect change)", method, "/", contentRootReplacement)
|
||||||
|
|
||||||
|
// Test level 1 (expect no change from the original)
|
||||||
|
testHelperFnContentCheck(t, handler, "Test level 1 (expect no change from the original)", method, "/level1", contentLevel1Original)
|
||||||
|
|
||||||
|
// Test level 2 (expect no change from the original)
|
||||||
|
testHelperFnContentCheck(t, handler, "Test level 2 (expect no change from the original)", method, "/level1/level2", contentLevel2Original)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUnregisterFixedRouteLevel1 replaces just the "/level1" fixed route path.
|
||||||
|
// In this case, for a path like "/level1/level2" or "/", those actions
|
||||||
|
// should remain intact, and continue to serve the original content.
|
||||||
|
func TestUnregisterFixedRouteLevel1(t *testing.T) {
|
||||||
|
|
||||||
|
var method = "GET"
|
||||||
|
|
||||||
|
handler := NewControllerRegister()
|
||||||
|
handler.Add("/", &TestPreUnregController{}, "get:GetFixedRoot")
|
||||||
|
handler.Add("/level1", &TestPreUnregController{}, "get:GetFixedLevel1")
|
||||||
|
handler.Add("/level1/level2", &TestPreUnregController{}, "get:GetFixedLevel2")
|
||||||
|
|
||||||
|
// Test original root
|
||||||
|
testHelperFnContentCheck(t, handler,
|
||||||
|
"TestUnregisterFixedRouteLevel1.Test original root",
|
||||||
|
method, "/", contentRootOriginal)
|
||||||
|
|
||||||
|
// Test original level 1
|
||||||
|
testHelperFnContentCheck(t, handler,
|
||||||
|
"TestUnregisterFixedRouteLevel1.Test original level 1",
|
||||||
|
method, "/level1", contentLevel1Original)
|
||||||
|
|
||||||
|
// Test original level 2
|
||||||
|
testHelperFnContentCheck(t, handler,
|
||||||
|
"TestUnregisterFixedRouteLevel1.Test original level 2",
|
||||||
|
method, "/level1/level2", contentLevel2Original)
|
||||||
|
|
||||||
|
// Remove only the level1 path
|
||||||
|
subPaths := splitPath("/level1")
|
||||||
|
if handler.routers[method].prefix == strings.Trim("/level1", "/ ") {
|
||||||
|
findAndRemoveSingleTree(handler.routers[method])
|
||||||
|
} else {
|
||||||
|
findAndRemoveTree(subPaths, handler.routers[method], method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the "level1" path TestPreUnregController action with the action from
|
||||||
|
// TestPostUnregController
|
||||||
|
handler.Add("/level1", &TestPostUnregController{}, "get:GetFixedLevel1")
|
||||||
|
|
||||||
|
// Test replacement root (expect no change from the original)
|
||||||
|
testHelperFnContentCheck(t, handler, "Test replacement root (expect no change from the original)", method, "/", contentRootOriginal)
|
||||||
|
|
||||||
|
// Test level 1 (expect change)
|
||||||
|
testHelperFnContentCheck(t, handler, "Test level 1 (expect change)", method, "/level1", contentLevel1Replacement)
|
||||||
|
|
||||||
|
// Test level 2 (expect no change from the original)
|
||||||
|
testHelperFnContentCheck(t, handler, "Test level 2 (expect no change from the original)", method, "/level1/level2", contentLevel2Original)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUnregisterFixedRouteLevel2 unregisters just the "/level1/level2" fixed
|
||||||
|
// route path. In this case, for a path like "/level1" or "/", those actions
|
||||||
|
// should remain intact, and continue to serve the original content.
|
||||||
|
func TestUnregisterFixedRouteLevel2(t *testing.T) {
|
||||||
|
|
||||||
|
var method = "GET"
|
||||||
|
|
||||||
|
handler := NewControllerRegister()
|
||||||
|
handler.Add("/", &TestPreUnregController{}, "get:GetFixedRoot")
|
||||||
|
handler.Add("/level1", &TestPreUnregController{}, "get:GetFixedLevel1")
|
||||||
|
handler.Add("/level1/level2", &TestPreUnregController{}, "get:GetFixedLevel2")
|
||||||
|
|
||||||
|
// Test original root
|
||||||
|
testHelperFnContentCheck(t, handler,
|
||||||
|
"TestUnregisterFixedRouteLevel1.Test original root",
|
||||||
|
method, "/", contentRootOriginal)
|
||||||
|
|
||||||
|
// Test original level 1
|
||||||
|
testHelperFnContentCheck(t, handler,
|
||||||
|
"TestUnregisterFixedRouteLevel1.Test original level 1",
|
||||||
|
method, "/level1", contentLevel1Original)
|
||||||
|
|
||||||
|
// Test original level 2
|
||||||
|
testHelperFnContentCheck(t, handler,
|
||||||
|
"TestUnregisterFixedRouteLevel1.Test original level 2",
|
||||||
|
method, "/level1/level2", contentLevel2Original)
|
||||||
|
|
||||||
|
// Remove only the level2 path
|
||||||
|
subPaths := splitPath("/level1/level2")
|
||||||
|
if handler.routers[method].prefix == strings.Trim("/level1/level2", "/ ") {
|
||||||
|
findAndRemoveSingleTree(handler.routers[method])
|
||||||
|
} else {
|
||||||
|
findAndRemoveTree(subPaths, handler.routers[method], method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the "/level1/level2" path TestPreUnregController action with the action from
|
||||||
|
// TestPostUnregController
|
||||||
|
handler.Add("/level1/level2", &TestPostUnregController{}, "get:GetFixedLevel2")
|
||||||
|
|
||||||
|
// Test replacement root (expect no change from the original)
|
||||||
|
testHelperFnContentCheck(t, handler, "Test replacement root (expect no change from the original)", method, "/", contentRootOriginal)
|
||||||
|
|
||||||
|
// Test level 1 (expect no change from the original)
|
||||||
|
testHelperFnContentCheck(t, handler, "Test level 1 (expect no change from the original)", method, "/level1", contentLevel1Original)
|
||||||
|
|
||||||
|
// Test level 2 (expect change)
|
||||||
|
testHelperFnContentCheck(t, handler, "Test level 2 (expect change)", method, "/level1/level2", contentLevel2Replacement)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHelperFnContentCheck(t *testing.T, handler *ControllerRegister,
|
||||||
|
testName, method, path, expectedBodyContent string) {
|
||||||
|
|
||||||
|
r, err := http.NewRequest(method, path, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("httpRecorderBodyTest NewRequest error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
body := w.Body.String()
|
||||||
|
if body != expectedBodyContent {
|
||||||
|
t.Errorf("%s: expected [%s], got [%s];", testName, expectedBodyContent, body)
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,22 @@ func TestSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReSet(t *testing.T) {
|
||||||
|
safeMap := NewBeeMap()
|
||||||
|
if ok := safeMap.Set("astaxie", 1); !ok {
|
||||||
|
t.Error("expected", true, "got", false)
|
||||||
|
}
|
||||||
|
// set diff value
|
||||||
|
if ok := safeMap.Set("astaxie", -1); !ok {
|
||||||
|
t.Error("expected", true, "got", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set same value
|
||||||
|
if ok := safeMap.Set("astaxie", -1); ok {
|
||||||
|
t.Error("expected", false, "got", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCheck(t *testing.T) {
|
func TestCheck(t *testing.T) {
|
||||||
if exists := safeMap.Check("astaxie"); !exists {
|
if exists := safeMap.Check("astaxie"); !exists {
|
||||||
t.Error("expected", true, "got", false)
|
t.Error("expected", true, "got", false)
|
||||||
@ -50,6 +66,21 @@ func TestDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestItems(t *testing.T) {
|
||||||
|
safeMap := NewBeeMap()
|
||||||
|
safeMap.Set("astaxie", "hello")
|
||||||
|
for k, v := range safeMap.Items() {
|
||||||
|
key := k.(string)
|
||||||
|
value := v.(string)
|
||||||
|
if key != "astaxie" {
|
||||||
|
t.Error("expected the key should be astaxie")
|
||||||
|
}
|
||||||
|
if value != "hello" {
|
||||||
|
t.Error("expected the value should be hello")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCount(t *testing.T) {
|
func TestCount(t *testing.T) {
|
||||||
if count := safeMap.Count(); count != 0 {
|
if count := safeMap.Count(); count != 0 {
|
||||||
t.Error("expected count to be", 0, "got", count)
|
t.Error("expected count to be", 0, "got", count)
|
||||||
|
@ -112,7 +112,7 @@ type Validation struct {
|
|||||||
RequiredFirst bool
|
RequiredFirst bool
|
||||||
|
|
||||||
Errors []*Error
|
Errors []*Error
|
||||||
ErrorsMap map[string]*Error
|
ErrorsMap map[string][]*Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear Clean all ValidationError.
|
// Clear Clean all ValidationError.
|
||||||
@ -129,7 +129,7 @@ func (v *Validation) HasErrors() bool {
|
|||||||
// ErrorMap Return the errors mapped by key.
|
// ErrorMap Return the errors mapped by key.
|
||||||
// If there are multiple validation errors associated with a single key, the
|
// If there are multiple validation errors associated with a single key, the
|
||||||
// first one "wins". (Typically the first validation will be the more basic).
|
// first one "wins". (Typically the first validation will be the more basic).
|
||||||
func (v *Validation) ErrorMap() map[string]*Error {
|
func (v *Validation) ErrorMap() map[string][]*Error {
|
||||||
return v.ErrorsMap
|
return v.ErrorsMap
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +245,21 @@ func (v *Validation) ZipCode(obj interface{}, key string) *Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validation) apply(chk Validator, obj interface{}) *Result {
|
func (v *Validation) apply(chk Validator, obj interface{}) *Result {
|
||||||
if chk.IsSatisfied(obj) {
|
if nil == obj {
|
||||||
|
if chk.IsSatisfied(obj) {
|
||||||
|
return &Result{Ok: true}
|
||||||
|
}
|
||||||
|
} else if reflect.TypeOf(obj).Kind() == reflect.Ptr {
|
||||||
|
if reflect.ValueOf(obj).IsNil() {
|
||||||
|
if chk.IsSatisfied(nil) {
|
||||||
|
return &Result{Ok: true}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if chk.IsSatisfied(reflect.ValueOf(obj).Elem().Interface()) {
|
||||||
|
return &Result{Ok: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if chk.IsSatisfied(obj) {
|
||||||
return &Result{Ok: true}
|
return &Result{Ok: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,14 +292,35 @@ func (v *Validation) apply(chk Validator, obj interface{}) *Result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddError adds independent error message for the provided key
|
||||||
|
func (v *Validation) AddError(key, message string) {
|
||||||
|
Name := key
|
||||||
|
Field := ""
|
||||||
|
|
||||||
|
parts := strings.Split(key, ".")
|
||||||
|
if len(parts) == 2 {
|
||||||
|
Field = parts[0]
|
||||||
|
Name = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
err := &Error{
|
||||||
|
Message: message,
|
||||||
|
Key: key,
|
||||||
|
Name: Name,
|
||||||
|
Field: Field,
|
||||||
|
}
|
||||||
|
v.setError(err)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Validation) setError(err *Error) {
|
func (v *Validation) setError(err *Error) {
|
||||||
v.Errors = append(v.Errors, err)
|
v.Errors = append(v.Errors, err)
|
||||||
if v.ErrorsMap == nil {
|
if v.ErrorsMap == nil {
|
||||||
v.ErrorsMap = make(map[string]*Error)
|
v.ErrorsMap = make(map[string][]*Error)
|
||||||
}
|
}
|
||||||
if _, ok := v.ErrorsMap[err.Field]; !ok {
|
if _, ok := v.ErrorsMap[err.Field]; !ok {
|
||||||
v.ErrorsMap[err.Field] = err
|
v.ErrorsMap[err.Field] = []*Error{}
|
||||||
}
|
}
|
||||||
|
v.ErrorsMap[err.Field] = append(v.ErrorsMap[err.Field], err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetError Set error message for one field in ValidationError
|
// SetError Set error message for one field in ValidationError
|
||||||
@ -330,13 +365,24 @@ func (v *Validation) Valid(obj interface{}) (b bool, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasReuired bool
|
var hasRequired bool
|
||||||
for _, vf := range vfs {
|
for _, vf := range vfs {
|
||||||
if vf.Name == "Required" {
|
if vf.Name == "Required" {
|
||||||
hasReuired = true
|
hasRequired = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasReuired && v.RequiredFirst && len(objV.Field(i).String()) == 0 {
|
currentField := objV.Field(i).Interface()
|
||||||
|
if objV.Field(i).Kind() == reflect.Ptr {
|
||||||
|
if objV.Field(i).IsNil() {
|
||||||
|
currentField = ""
|
||||||
|
} else {
|
||||||
|
currentField = objV.Field(i).Elem().Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
chk := Required{""}.IsSatisfied(currentField)
|
||||||
|
if !hasRequired && v.RequiredFirst && !chk {
|
||||||
if _, ok := CanSkipFuncs[vf.Name]; ok {
|
if _, ok := CanSkipFuncs[vf.Name]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -393,3 +439,9 @@ func (v *Validation) RecursiveValid(objc interface{}) (bool, error) {
|
|||||||
}
|
}
|
||||||
return pass, err
|
return pass, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Validation) CanSkipAlso(skipFunc string) {
|
||||||
|
if _, ok := CanSkipFuncs[skipFunc]; !ok {
|
||||||
|
CanSkipFuncs[skipFunc] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -442,3 +442,122 @@ func TestSkipValid(t *testing.T) {
|
|||||||
t.Fatal("validation should be passed")
|
t.Fatal("validation should be passed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPointer(t *testing.T) {
|
||||||
|
type User struct {
|
||||||
|
ID int
|
||||||
|
|
||||||
|
Email *string `valid:"Email"`
|
||||||
|
ReqEmail *string `valid:"Required;Email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
u := User{
|
||||||
|
ReqEmail: nil,
|
||||||
|
Email: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
valid := Validation{}
|
||||||
|
b, err := valid.Valid(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
t.Fatal("validation should not be passed")
|
||||||
|
}
|
||||||
|
|
||||||
|
validEmail := "a@a.com"
|
||||||
|
u = User{
|
||||||
|
ReqEmail: &validEmail,
|
||||||
|
Email: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
valid = Validation{RequiredFirst: true}
|
||||||
|
b, err = valid.Valid(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !b {
|
||||||
|
t.Fatal("validation should be passed")
|
||||||
|
}
|
||||||
|
|
||||||
|
u = User{
|
||||||
|
ReqEmail: &validEmail,
|
||||||
|
Email: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
valid = Validation{}
|
||||||
|
b, err = valid.Valid(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
t.Fatal("validation should not be passed")
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidEmail := "a@a"
|
||||||
|
u = User{
|
||||||
|
ReqEmail: &validEmail,
|
||||||
|
Email: &invalidEmail,
|
||||||
|
}
|
||||||
|
|
||||||
|
valid = Validation{RequiredFirst: true}
|
||||||
|
b, err = valid.Valid(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
t.Fatal("validation should not be passed")
|
||||||
|
}
|
||||||
|
|
||||||
|
u = User{
|
||||||
|
ReqEmail: &validEmail,
|
||||||
|
Email: &invalidEmail,
|
||||||
|
}
|
||||||
|
|
||||||
|
valid = Validation{}
|
||||||
|
b, err = valid.Valid(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
t.Fatal("validation should not be passed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
u := User{
|
||||||
|
ReqEmail: "a@a.com",
|
||||||
|
Email: "",
|
||||||
|
MatchRange: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
valid := Validation{RequiredFirst: true}
|
||||||
|
b, err := valid.Valid(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
t.Fatal("validation should not be passed")
|
||||||
|
}
|
||||||
|
|
||||||
|
valid = Validation{RequiredFirst: true}
|
||||||
|
valid.CanSkipAlso("Range")
|
||||||
|
b, err = valid.Valid(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !b {
|
||||||
|
t.Fatal("validation should be passed")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
27
vendor/golang.org/x/net/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/net/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/net/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/net/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.
|
54
vendor/golang.org/x/net/context/context.go
generated
vendored
Normal file
54
vendor/golang.org/x/net/context/context.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2014 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 context defines the Context type, which carries deadlines,
|
||||||
|
// cancelation signals, and other request-scoped values across API boundaries
|
||||||
|
// and between processes.
|
||||||
|
//
|
||||||
|
// Incoming requests to a server should create a Context, and outgoing calls to
|
||||||
|
// servers should accept a Context. The chain of function calls between must
|
||||||
|
// propagate the Context, optionally replacing it with a modified copy created
|
||||||
|
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
||||||
|
//
|
||||||
|
// Programs that use Contexts should follow these rules to keep interfaces
|
||||||
|
// consistent across packages and enable static analysis tools to check context
|
||||||
|
// propagation:
|
||||||
|
//
|
||||||
|
// Do not store Contexts inside a struct type; instead, pass a Context
|
||||||
|
// explicitly to each function that needs it. The Context should be the first
|
||||||
|
// parameter, typically named ctx:
|
||||||
|
//
|
||||||
|
// func DoSomething(ctx context.Context, arg Arg) error {
|
||||||
|
// // ... use ctx ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
||||||
|
// if you are unsure about which Context to use.
|
||||||
|
//
|
||||||
|
// Use context Values only for request-scoped data that transits processes and
|
||||||
|
// APIs, not for passing optional parameters to functions.
|
||||||
|
//
|
||||||
|
// The same Context may be passed to functions running in different goroutines;
|
||||||
|
// Contexts are safe for simultaneous use by multiple goroutines.
|
||||||
|
//
|
||||||
|
// See http://blog.golang.org/context for example code for a server that uses
|
||||||
|
// Contexts.
|
||||||
|
package context // import "golang.org/x/net/context"
|
||||||
|
|
||||||
|
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||||
|
// values, and has no deadline. It is typically used by the main function,
|
||||||
|
// initialization, and tests, and as the top-level Context for incoming
|
||||||
|
// requests.
|
||||||
|
func Background() Context {
|
||||||
|
return background
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
||||||
|
// it's unclear which Context to use or it is not yet available (because the
|
||||||
|
// surrounding function has not yet been extended to accept a Context
|
||||||
|
// parameter). TODO is recognized by static analysis tools that determine
|
||||||
|
// whether Contexts are propagated correctly in a program.
|
||||||
|
func TODO() Context {
|
||||||
|
return todo
|
||||||
|
}
|
72
vendor/golang.org/x/net/context/go17.go
generated
vendored
Normal file
72
vendor/golang.org/x/net/context/go17.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context" // standard library's context, as of Go 1.7
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
todo = context.TODO()
|
||||||
|
background = context.Background()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||||
|
var Canceled = context.Canceled
|
||||||
|
|
||||||
|
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||||
|
// deadline passes.
|
||||||
|
var DeadlineExceeded = context.DeadlineExceeded
|
||||||
|
|
||||||
|
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||||
|
// context's Done channel is closed when the returned cancel function is called
|
||||||
|
// or when the parent context's Done channel is closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete.
|
||||||
|
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||||
|
ctx, f := context.WithCancel(parent)
|
||||||
|
return ctx, CancelFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||||
|
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||||
|
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||||
|
// context's Done channel is closed when the deadline expires, when the returned
|
||||||
|
// cancel function is called, or when the parent context's Done channel is
|
||||||
|
// closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete.
|
||||||
|
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||||
|
ctx, f := context.WithDeadline(parent, deadline)
|
||||||
|
return ctx, CancelFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete:
|
||||||
|
//
|
||||||
|
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||||
|
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||||
|
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||||
|
// return slowOperation(ctx)
|
||||||
|
// }
|
||||||
|
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||||
|
return WithDeadline(parent, time.Now().Add(timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValue returns a copy of parent in which the value associated with key is
|
||||||
|
// val.
|
||||||
|
//
|
||||||
|
// Use context Values only for request-scoped data that transits processes and
|
||||||
|
// APIs, not for passing optional parameters to functions.
|
||||||
|
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
||||||
|
return context.WithValue(parent, key, val)
|
||||||
|
}
|
20
vendor/golang.org/x/net/context/go19.go
generated
vendored
Normal file
20
vendor/golang.org/x/net/context/go19.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build go1.9
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import "context" // standard library's context, as of Go 1.7
|
||||||
|
|
||||||
|
// A Context carries a deadline, a cancelation signal, and other values across
|
||||||
|
// API boundaries.
|
||||||
|
//
|
||||||
|
// Context's methods may be called by multiple goroutines simultaneously.
|
||||||
|
type Context = context.Context
|
||||||
|
|
||||||
|
// A CancelFunc tells an operation to abandon its work.
|
||||||
|
// A CancelFunc does not wait for the work to stop.
|
||||||
|
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||||
|
type CancelFunc = context.CancelFunc
|
300
vendor/golang.org/x/net/context/pre_go17.go
generated
vendored
Normal file
300
vendor/golang.org/x/net/context/pre_go17.go
generated
vendored
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
// +build !go1.7
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
||||||
|
// struct{}, since vars of this type must have distinct addresses.
|
||||||
|
type emptyCtx int
|
||||||
|
|
||||||
|
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*emptyCtx) Done() <-chan struct{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*emptyCtx) Err() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*emptyCtx) Value(key interface{}) interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *emptyCtx) String() string {
|
||||||
|
switch e {
|
||||||
|
case background:
|
||||||
|
return "context.Background"
|
||||||
|
case todo:
|
||||||
|
return "context.TODO"
|
||||||
|
}
|
||||||
|
return "unknown empty Context"
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
background = new(emptyCtx)
|
||||||
|
todo = new(emptyCtx)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||||
|
var Canceled = errors.New("context canceled")
|
||||||
|
|
||||||
|
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||||
|
// deadline passes.
|
||||||
|
var DeadlineExceeded = errors.New("context deadline exceeded")
|
||||||
|
|
||||||
|
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||||
|
// context's Done channel is closed when the returned cancel function is called
|
||||||
|
// or when the parent context's Done channel is closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete.
|
||||||
|
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||||
|
c := newCancelCtx(parent)
|
||||||
|
propagateCancel(parent, c)
|
||||||
|
return c, func() { c.cancel(true, Canceled) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCancelCtx returns an initialized cancelCtx.
|
||||||
|
func newCancelCtx(parent Context) *cancelCtx {
|
||||||
|
return &cancelCtx{
|
||||||
|
Context: parent,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// propagateCancel arranges for child to be canceled when parent is.
|
||||||
|
func propagateCancel(parent Context, child canceler) {
|
||||||
|
if parent.Done() == nil {
|
||||||
|
return // parent is never canceled
|
||||||
|
}
|
||||||
|
if p, ok := parentCancelCtx(parent); ok {
|
||||||
|
p.mu.Lock()
|
||||||
|
if p.err != nil {
|
||||||
|
// parent has already been canceled
|
||||||
|
child.cancel(false, p.err)
|
||||||
|
} else {
|
||||||
|
if p.children == nil {
|
||||||
|
p.children = make(map[canceler]bool)
|
||||||
|
}
|
||||||
|
p.children[child] = true
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-parent.Done():
|
||||||
|
child.cancel(false, parent.Err())
|
||||||
|
case <-child.Done():
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parentCancelCtx follows a chain of parent references until it finds a
|
||||||
|
// *cancelCtx. This function understands how each of the concrete types in this
|
||||||
|
// package represents its parent.
|
||||||
|
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
|
||||||
|
for {
|
||||||
|
switch c := parent.(type) {
|
||||||
|
case *cancelCtx:
|
||||||
|
return c, true
|
||||||
|
case *timerCtx:
|
||||||
|
return c.cancelCtx, true
|
||||||
|
case *valueCtx:
|
||||||
|
parent = c.Context
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeChild removes a context from its parent.
|
||||||
|
func removeChild(parent Context, child canceler) {
|
||||||
|
p, ok := parentCancelCtx(parent)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.mu.Lock()
|
||||||
|
if p.children != nil {
|
||||||
|
delete(p.children, child)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A canceler is a context type that can be canceled directly. The
|
||||||
|
// implementations are *cancelCtx and *timerCtx.
|
||||||
|
type canceler interface {
|
||||||
|
cancel(removeFromParent bool, err error)
|
||||||
|
Done() <-chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
||||||
|
// that implement canceler.
|
||||||
|
type cancelCtx struct {
|
||||||
|
Context
|
||||||
|
|
||||||
|
done chan struct{} // closed by the first cancel call.
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
children map[canceler]bool // set to nil by the first cancel call
|
||||||
|
err error // set to non-nil by the first cancel call
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cancelCtx) Done() <-chan struct{} {
|
||||||
|
return c.done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cancelCtx) Err() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cancelCtx) String() string {
|
||||||
|
return fmt.Sprintf("%v.WithCancel", c.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancel closes c.done, cancels each of c's children, and, if
|
||||||
|
// removeFromParent is true, removes c from its parent's children.
|
||||||
|
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
|
||||||
|
if err == nil {
|
||||||
|
panic("context: internal error: missing cancel error")
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.err != nil {
|
||||||
|
c.mu.Unlock()
|
||||||
|
return // already canceled
|
||||||
|
}
|
||||||
|
c.err = err
|
||||||
|
close(c.done)
|
||||||
|
for child := range c.children {
|
||||||
|
// NOTE: acquiring the child's lock while holding parent's lock.
|
||||||
|
child.cancel(false, err)
|
||||||
|
}
|
||||||
|
c.children = nil
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
if removeFromParent {
|
||||||
|
removeChild(c.Context, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||||
|
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||||
|
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||||
|
// context's Done channel is closed when the deadline expires, when the returned
|
||||||
|
// cancel function is called, or when the parent context's Done channel is
|
||||||
|
// closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete.
|
||||||
|
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||||
|
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
|
||||||
|
// The current deadline is already sooner than the new one.
|
||||||
|
return WithCancel(parent)
|
||||||
|
}
|
||||||
|
c := &timerCtx{
|
||||||
|
cancelCtx: newCancelCtx(parent),
|
||||||
|
deadline: deadline,
|
||||||
|
}
|
||||||
|
propagateCancel(parent, c)
|
||||||
|
d := deadline.Sub(time.Now())
|
||||||
|
if d <= 0 {
|
||||||
|
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
||||||
|
return c, func() { c.cancel(true, Canceled) }
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if c.err == nil {
|
||||||
|
c.timer = time.AfterFunc(d, func() {
|
||||||
|
c.cancel(true, DeadlineExceeded)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c, func() { c.cancel(true, Canceled) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
||||||
|
// implement Done and Err. It implements cancel by stopping its timer then
|
||||||
|
// delegating to cancelCtx.cancel.
|
||||||
|
type timerCtx struct {
|
||||||
|
*cancelCtx
|
||||||
|
timer *time.Timer // Under cancelCtx.mu.
|
||||||
|
|
||||||
|
deadline time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return c.deadline, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *timerCtx) String() string {
|
||||||
|
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *timerCtx) cancel(removeFromParent bool, err error) {
|
||||||
|
c.cancelCtx.cancel(false, err)
|
||||||
|
if removeFromParent {
|
||||||
|
// Remove this timerCtx from its parent cancelCtx's children.
|
||||||
|
removeChild(c.cancelCtx.Context, c)
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.timer != nil {
|
||||||
|
c.timer.Stop()
|
||||||
|
c.timer = nil
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this Context complete:
|
||||||
|
//
|
||||||
|
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||||
|
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||||
|
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||||
|
// return slowOperation(ctx)
|
||||||
|
// }
|
||||||
|
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||||
|
return WithDeadline(parent, time.Now().Add(timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValue returns a copy of parent in which the value associated with key is
|
||||||
|
// val.
|
||||||
|
//
|
||||||
|
// Use context Values only for request-scoped data that transits processes and
|
||||||
|
// APIs, not for passing optional parameters to functions.
|
||||||
|
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
||||||
|
return &valueCtx{parent, key, val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A valueCtx carries a key-value pair. It implements Value for that key and
|
||||||
|
// delegates all other calls to the embedded Context.
|
||||||
|
type valueCtx struct {
|
||||||
|
Context
|
||||||
|
key, val interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *valueCtx) String() string {
|
||||||
|
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *valueCtx) Value(key interface{}) interface{} {
|
||||||
|
if c.key == key {
|
||||||
|
return c.val
|
||||||
|
}
|
||||||
|
return c.Context.Value(key)
|
||||||
|
}
|
109
vendor/golang.org/x/net/context/pre_go19.go
generated
vendored
Normal file
109
vendor/golang.org/x/net/context/pre_go19.go
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
// +build !go1.9
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// A Context carries a deadline, a cancelation signal, and other values across
|
||||||
|
// API boundaries.
|
||||||
|
//
|
||||||
|
// Context's methods may be called by multiple goroutines simultaneously.
|
||||||
|
type Context interface {
|
||||||
|
// Deadline returns the time when work done on behalf of this context
|
||||||
|
// should be canceled. Deadline returns ok==false when no deadline is
|
||||||
|
// set. Successive calls to Deadline return the same results.
|
||||||
|
Deadline() (deadline time.Time, ok bool)
|
||||||
|
|
||||||
|
// Done returns a channel that's closed when work done on behalf of this
|
||||||
|
// context should be canceled. Done may return nil if this context can
|
||||||
|
// never be canceled. Successive calls to Done return the same value.
|
||||||
|
//
|
||||||
|
// WithCancel arranges for Done to be closed when cancel is called;
|
||||||
|
// WithDeadline arranges for Done to be closed when the deadline
|
||||||
|
// expires; WithTimeout arranges for Done to be closed when the timeout
|
||||||
|
// elapses.
|
||||||
|
//
|
||||||
|
// Done is provided for use in select statements:
|
||||||
|
//
|
||||||
|
// // Stream generates values with DoSomething and sends them to out
|
||||||
|
// // until DoSomething returns an error or ctx.Done is closed.
|
||||||
|
// func Stream(ctx context.Context, out chan<- Value) error {
|
||||||
|
// for {
|
||||||
|
// v, err := DoSomething(ctx)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// select {
|
||||||
|
// case <-ctx.Done():
|
||||||
|
// return ctx.Err()
|
||||||
|
// case out <- v:
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// See http://blog.golang.org/pipelines for more examples of how to use
|
||||||
|
// a Done channel for cancelation.
|
||||||
|
Done() <-chan struct{}
|
||||||
|
|
||||||
|
// Err returns a non-nil error value after Done is closed. Err returns
|
||||||
|
// Canceled if the context was canceled or DeadlineExceeded if the
|
||||||
|
// context's deadline passed. No other values for Err are defined.
|
||||||
|
// After Done is closed, successive calls to Err return the same value.
|
||||||
|
Err() error
|
||||||
|
|
||||||
|
// Value returns the value associated with this context for key, or nil
|
||||||
|
// if no value is associated with key. Successive calls to Value with
|
||||||
|
// the same key returns the same result.
|
||||||
|
//
|
||||||
|
// Use context values only for request-scoped data that transits
|
||||||
|
// processes and API boundaries, not for passing optional parameters to
|
||||||
|
// functions.
|
||||||
|
//
|
||||||
|
// A key identifies a specific value in a Context. Functions that wish
|
||||||
|
// to store values in Context typically allocate a key in a global
|
||||||
|
// variable then use that key as the argument to context.WithValue and
|
||||||
|
// Context.Value. A key can be any type that supports equality;
|
||||||
|
// packages should define keys as an unexported type to avoid
|
||||||
|
// collisions.
|
||||||
|
//
|
||||||
|
// Packages that define a Context key should provide type-safe accessors
|
||||||
|
// for the values stores using that key:
|
||||||
|
//
|
||||||
|
// // Package user defines a User type that's stored in Contexts.
|
||||||
|
// package user
|
||||||
|
//
|
||||||
|
// import "golang.org/x/net/context"
|
||||||
|
//
|
||||||
|
// // User is the type of value stored in the Contexts.
|
||||||
|
// type User struct {...}
|
||||||
|
//
|
||||||
|
// // key is an unexported type for keys defined in this package.
|
||||||
|
// // This prevents collisions with keys defined in other packages.
|
||||||
|
// type key int
|
||||||
|
//
|
||||||
|
// // userKey is the key for user.User values in Contexts. It is
|
||||||
|
// // unexported; clients use user.NewContext and user.FromContext
|
||||||
|
// // instead of using this key directly.
|
||||||
|
// var userKey key = 0
|
||||||
|
//
|
||||||
|
// // NewContext returns a new Context that carries value u.
|
||||||
|
// func NewContext(ctx context.Context, u *User) context.Context {
|
||||||
|
// return context.WithValue(ctx, userKey, u)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // FromContext returns the User value stored in ctx, if any.
|
||||||
|
// func FromContext(ctx context.Context) (*User, bool) {
|
||||||
|
// u, ok := ctx.Value(userKey).(*User)
|
||||||
|
// return u, ok
|
||||||
|
// }
|
||||||
|
Value(key interface{}) interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A CancelFunc tells an operation to abandon its work.
|
||||||
|
// A CancelFunc does not wait for the work to stop.
|
||||||
|
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||||
|
type CancelFunc func()
|
202
vendor/google.golang.org/appengine/LICENSE
generated
vendored
Normal file
202
vendor/google.golang.org/appengine/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
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.
|
62
vendor/google.golang.org/appengine/cloudsql/cloudsql.go
generated
vendored
Normal file
62
vendor/google.golang.org/appengine/cloudsql/cloudsql.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2013 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package cloudsql exposes access to Google Cloud SQL databases.
|
||||||
|
|
||||||
|
This package does not work in App Engine "flexible environment".
|
||||||
|
|
||||||
|
This package is intended for MySQL drivers to make App Engine-specific
|
||||||
|
connections. Applications should use this package through database/sql:
|
||||||
|
Select a pure Go MySQL driver that supports this package, and use sql.Open
|
||||||
|
with protocol "cloudsql" and an address of the Cloud SQL instance.
|
||||||
|
|
||||||
|
A Go MySQL driver that has been tested to work well with Cloud SQL
|
||||||
|
is the go-sql-driver:
|
||||||
|
import "database/sql"
|
||||||
|
import _ "github.com/go-sql-driver/mysql"
|
||||||
|
|
||||||
|
db, err := sql.Open("mysql", "user@cloudsql(project-id:instance-name)/dbname")
|
||||||
|
|
||||||
|
|
||||||
|
Another driver that works well with Cloud SQL is the mymysql driver:
|
||||||
|
import "database/sql"
|
||||||
|
import _ "github.com/ziutek/mymysql/godrv"
|
||||||
|
|
||||||
|
db, err := sql.Open("mymysql", "cloudsql:instance-name*dbname/user/password")
|
||||||
|
|
||||||
|
|
||||||
|
Using either of these drivers, you can perform a standard SQL query.
|
||||||
|
This example assumes there is a table named 'users' with
|
||||||
|
columns 'first_name' and 'last_name':
|
||||||
|
|
||||||
|
rows, err := db.Query("SELECT first_name, last_name FROM users")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(ctx, "db.Query: %v", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var firstName string
|
||||||
|
var lastName string
|
||||||
|
if err := rows.Scan(&firstName, &lastName); err != nil {
|
||||||
|
log.Errorf(ctx, "rows.Scan: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Infof(ctx, "First: %v - Last: %v", firstName, lastName)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
log.Errorf(ctx, "Row error: %v", err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package cloudsql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dial connects to the named Cloud SQL instance.
|
||||||
|
func Dial(instance string) (net.Conn, error) {
|
||||||
|
return connect(instance)
|
||||||
|
}
|
17
vendor/google.golang.org/appengine/cloudsql/cloudsql_classic.go
generated
vendored
Normal file
17
vendor/google.golang.org/appengine/cloudsql/cloudsql_classic.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2013 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package cloudsql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"appengine/cloudsql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func connect(instance string) (net.Conn, error) {
|
||||||
|
return cloudsql.Dial(instance)
|
||||||
|
}
|
16
vendor/google.golang.org/appengine/cloudsql/cloudsql_vm.go
generated
vendored
Normal file
16
vendor/google.golang.org/appengine/cloudsql/cloudsql_vm.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2013 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package cloudsql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func connect(instance string) (net.Conn, error) {
|
||||||
|
return nil, errors.New(`cloudsql: not supported in App Engine "flexible environment"`)
|
||||||
|
}
|
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.
|
31
vendor/gopkg.in/yaml.v2/LICENSE.libyaml
generated
vendored
Normal file
31
vendor/gopkg.in/yaml.v2/LICENSE.libyaml
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
The following files were ported to Go from C files of libyaml, and thus
|
||||||
|
are still covered by their original copyright and license:
|
||||||
|
|
||||||
|
apic.go
|
||||||
|
emitterc.go
|
||||||
|
parserc.go
|
||||||
|
readerc.go
|
||||||
|
scannerc.go
|
||||||
|
writerc.go
|
||||||
|
yamlh.go
|
||||||
|
yamlprivateh.go
|
||||||
|
|
||||||
|
Copyright (c) 2006 Kirill Simonov
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
13
vendor/gopkg.in/yaml.v2/NOTICE
generated
vendored
Normal file
13
vendor/gopkg.in/yaml.v2/NOTICE
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Copyright 2011-2016 Canonical Ltd.
|
||||||
|
|
||||||
|
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