正在显示
21 个修改的文件
包含
4895 行增加
和
0 行删除
vendor/github.com/astaxie/beego/.gitignore
0 → 100644
vendor/github.com/astaxie/beego/.travis.yml
0 → 100644
| 1 | +language: go | ||
| 2 | + | ||
| 3 | +go: | ||
| 4 | + - "1.9.2" | ||
| 5 | + - "1.10.3" | ||
| 6 | +services: | ||
| 7 | + - redis-server | ||
| 8 | + - mysql | ||
| 9 | + - postgresql | ||
| 10 | + - memcached | ||
| 11 | +env: | ||
| 12 | + - ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db | ||
| 13 | + - ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable" | ||
| 14 | +before_install: | ||
| 15 | + - git clone git://github.com/ideawu/ssdb.git | ||
| 16 | + - cd ssdb | ||
| 17 | + - make | ||
| 18 | + - cd .. | ||
| 19 | +install: | ||
| 20 | + - go get github.com/lib/pq | ||
| 21 | + - go get github.com/go-sql-driver/mysql | ||
| 22 | + - go get github.com/mattn/go-sqlite3 | ||
| 23 | + - go get github.com/bradfitz/gomemcache/memcache | ||
| 24 | + - go get github.com/gomodule/redigo/redis | ||
| 25 | + - go get github.com/beego/x2j | ||
| 26 | + - go get github.com/couchbase/go-couchbase | ||
| 27 | + - go get github.com/beego/goyaml2 | ||
| 28 | + - go get gopkg.in/yaml.v2 | ||
| 29 | + - go get github.com/belogik/goes | ||
| 30 | + - go get github.com/siddontang/ledisdb/config | ||
| 31 | + - go get github.com/siddontang/ledisdb/ledis | ||
| 32 | + - go get github.com/ssdb/gossdb/ssdb | ||
| 33 | + - go get github.com/cloudflare/golz4 | ||
| 34 | + - go get github.com/gogo/protobuf/proto | ||
| 35 | + - go get github.com/Knetic/govaluate | ||
| 36 | + - go get github.com/casbin/casbin | ||
| 37 | + - go get -u honnef.co/go/tools/cmd/gosimple | ||
| 38 | + - go get -u github.com/mdempsky/unconvert | ||
| 39 | + - go get -u github.com/gordonklaus/ineffassign | ||
| 40 | + - go get -u github.com/golang/lint/golint | ||
| 41 | + - go get -u github.com/go-redis/redis | ||
| 42 | +before_script: | ||
| 43 | + - psql --version | ||
| 44 | + - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" | ||
| 45 | + - sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi" | ||
| 46 | + - sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi" | ||
| 47 | + - sh -c "if [ $(go version) == *1.[5-9]* ]; then go get github.com/golang/lint/golint; golint ./...; fi" | ||
| 48 | + - sh -c "if [ $(go version) == *1.[5-9]* ]; then go tool vet .; fi" | ||
| 49 | + - mkdir -p res/var | ||
| 50 | + - ./ssdb/ssdb-server ./ssdb/ssdb.conf -d | ||
| 51 | +after_script: | ||
| 52 | + -killall -w ssdb-server | ||
| 53 | + - rm -rf ./res/var/* | ||
| 54 | +script: | ||
| 55 | + - go test -v ./... | ||
| 56 | + - gosimple -ignore "$(cat .gosimpleignore)" $(go list ./... | grep -v /vendor/) | ||
| 57 | + - unconvert $(go list ./... | grep -v /vendor/) | ||
| 58 | + - ineffassign . | ||
| 59 | + - find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s | ||
| 60 | + - golint ./... | ||
| 61 | +addons: | ||
| 62 | + postgresql: "9.4" |
| 1 | +# Contributing to beego | ||
| 2 | + | ||
| 3 | +beego is an open source project. | ||
| 4 | + | ||
| 5 | +It is the work of hundreds of contributors. We appreciate your help! | ||
| 6 | + | ||
| 7 | +Here are instructions to get you started. They are probably not perfect, | ||
| 8 | +please let us know if anything feels wrong or incomplete. | ||
| 9 | + | ||
| 10 | +## Contribution guidelines | ||
| 11 | + | ||
| 12 | +### Pull requests | ||
| 13 | + | ||
| 14 | +First of all. beego follow the gitflow. So please send you pull request | ||
| 15 | +to **develop** branch. We will close the pull request to master branch. | ||
| 16 | + | ||
| 17 | +We are always happy to receive pull requests, and do our best to | ||
| 18 | +review them as fast as possible. Not sure if that typo is worth a pull | ||
| 19 | +request? Do it! We will appreciate it. | ||
| 20 | + | ||
| 21 | +If your pull request is not accepted on the first try, don't be | ||
| 22 | +discouraged! Sometimes we can make a mistake, please do more explaining | ||
| 23 | +for us. We will appreciate it. | ||
| 24 | + | ||
| 25 | +We're trying very hard to keep beego simple and fast. We don't want it | ||
| 26 | +to do everything for everybody. This means that we might decide against | ||
| 27 | +incorporating a new feature. But we will give you some advice on how to | ||
| 28 | +do it in other way. | ||
| 29 | + | ||
| 30 | +### Create issues | ||
| 31 | + | ||
| 32 | +Any significant improvement should be documented as [a GitHub | ||
| 33 | +issue](https://github.com/astaxie/beego/issues) before anybody | ||
| 34 | +starts working on it. | ||
| 35 | + | ||
| 36 | +Also when filing an issue, make sure to answer these five questions: | ||
| 37 | + | ||
| 38 | +- What version of beego are you using (bee version)? | ||
| 39 | +- What operating system and processor architecture are you using? | ||
| 40 | +- What did you do? | ||
| 41 | +- What did you expect to see? | ||
| 42 | +- What did you see instead? | ||
| 43 | + | ||
| 44 | +### but check existing issues and docs first! | ||
| 45 | + | ||
| 46 | +Please take a moment to check that an issue doesn't already exist | ||
| 47 | +documenting your bug report or improvement proposal. If it does, it | ||
| 48 | +never hurts to add a quick "+1" or "I have this problem too". This will | ||
| 49 | +help prioritize the most common problems and requests. | ||
| 50 | + | ||
| 51 | +Also if you don't know how to use it. please make sure you have read though | ||
| 52 | +the docs in http://beego.me/docs |
vendor/github.com/astaxie/beego/LICENSE
0 → 100644
| 1 | +Copyright 2014 astaxie | ||
| 2 | + | ||
| 3 | +Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +you may not use this file except in compliance with the License. | ||
| 5 | +You may obtain a copy of the License at | ||
| 6 | + | ||
| 7 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + | ||
| 9 | +Unless required by applicable law or agreed to in writing, software | ||
| 10 | +distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +See the License for the specific language governing permissions and | ||
| 13 | +limitations under the License. |
vendor/github.com/astaxie/beego/README.md
0 → 100644
| 1 | +# 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) | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +beego is used for rapid development of RESTful APIs, web apps and backend services in Go. | ||
| 5 | +It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding. | ||
| 6 | + | ||
| 7 | +###### More info at [beego.me](http://beego.me). | ||
| 8 | + | ||
| 9 | +## Quick Start | ||
| 10 | + | ||
| 11 | +#### Download and install | ||
| 12 | + | ||
| 13 | + go get github.com/astaxie/beego | ||
| 14 | + | ||
| 15 | +#### Create file `hello.go` | ||
| 16 | +```go | ||
| 17 | +package main | ||
| 18 | + | ||
| 19 | +import "github.com/astaxie/beego" | ||
| 20 | + | ||
| 21 | +func main(){ | ||
| 22 | + beego.Run() | ||
| 23 | +} | ||
| 24 | +``` | ||
| 25 | +#### Build and run | ||
| 26 | + | ||
| 27 | + go build hello.go | ||
| 28 | + ./hello | ||
| 29 | + | ||
| 30 | +#### Go to [http://localhost:8080](http://localhost:8080) | ||
| 31 | + | ||
| 32 | +Congratulations! You've just built your first **beego** app. | ||
| 33 | + | ||
| 34 | +###### Please see [Documentation](http://beego.me/docs) for more. | ||
| 35 | + | ||
| 36 | +## Features | ||
| 37 | + | ||
| 38 | +* RESTful support | ||
| 39 | +* MVC architecture | ||
| 40 | +* Modularity | ||
| 41 | +* Auto API documents | ||
| 42 | +* Annotation router | ||
| 43 | +* Namespace | ||
| 44 | +* Powerful development tools | ||
| 45 | +* Full stack for Web & API | ||
| 46 | + | ||
| 47 | +## Documentation | ||
| 48 | + | ||
| 49 | +* [English](http://beego.me/docs/intro/) | ||
| 50 | +* [中文文档](http://beego.me/docs/intro/) | ||
| 51 | +* [Русский](http://beego.me/docs/intro/) | ||
| 52 | + | ||
| 53 | +## Community | ||
| 54 | + | ||
| 55 | +* [http://beego.me/community](http://beego.me/community) | ||
| 56 | +* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232) | ||
| 57 | + | ||
| 58 | +## License | ||
| 59 | + | ||
| 60 | +beego source code is licensed under the Apache Licence, Version 2.0 | ||
| 61 | +(http://www.apache.org/licenses/LICENSE-2.0.html). |
vendor/github.com/astaxie/beego/admin.go
0 → 100644
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +package beego | ||
| 16 | + | ||
| 17 | +import ( | ||
| 18 | + "bytes" | ||
| 19 | + "encoding/json" | ||
| 20 | + "fmt" | ||
| 21 | + "net/http" | ||
| 22 | + "os" | ||
| 23 | + "text/template" | ||
| 24 | + "time" | ||
| 25 | + | ||
| 26 | + "reflect" | ||
| 27 | + | ||
| 28 | + "github.com/astaxie/beego/grace" | ||
| 29 | + "github.com/astaxie/beego/logs" | ||
| 30 | + "github.com/astaxie/beego/toolbox" | ||
| 31 | + "github.com/astaxie/beego/utils" | ||
| 32 | +) | ||
| 33 | + | ||
| 34 | +// BeeAdminApp is the default adminApp used by admin module. | ||
| 35 | +var beeAdminApp *adminApp | ||
| 36 | + | ||
| 37 | +// FilterMonitorFunc is default monitor filter when admin module is enable. | ||
| 38 | +// if this func returns, admin module records qbs for this request by condition of this function logic. | ||
| 39 | +// usage: | ||
| 40 | +// func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool { | ||
| 41 | +// if method == "POST" { | ||
| 42 | +// return false | ||
| 43 | +// } | ||
| 44 | +// if t.Nanoseconds() < 100 { | ||
| 45 | +// return false | ||
| 46 | +// } | ||
| 47 | +// if strings.HasPrefix(requestPath, "/astaxie") { | ||
| 48 | +// return false | ||
| 49 | +// } | ||
| 50 | +// return true | ||
| 51 | +// } | ||
| 52 | +// beego.FilterMonitorFunc = MyFilterMonitor. | ||
| 53 | +var FilterMonitorFunc func(string, string, time.Duration, string, int) bool | ||
| 54 | + | ||
| 55 | +func init() { | ||
| 56 | + beeAdminApp = &adminApp{ | ||
| 57 | + routers: make(map[string]http.HandlerFunc), | ||
| 58 | + } | ||
| 59 | + beeAdminApp.Route("/", adminIndex) | ||
| 60 | + beeAdminApp.Route("/qps", qpsIndex) | ||
| 61 | + beeAdminApp.Route("/prof", profIndex) | ||
| 62 | + beeAdminApp.Route("/healthcheck", healthcheck) | ||
| 63 | + beeAdminApp.Route("/task", taskStatus) | ||
| 64 | + beeAdminApp.Route("/listconf", listConf) | ||
| 65 | + FilterMonitorFunc = func(string, string, time.Duration, string, int) bool { return true } | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +// AdminIndex is the default http.Handler for admin module. | ||
| 69 | +// it matches url pattern "/". | ||
| 70 | +func adminIndex(rw http.ResponseWriter, r *http.Request) { | ||
| 71 | + execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl) | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +// QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter. | ||
| 75 | +// it's registered with url pattern "/qbs" in admin module. | ||
| 76 | +func qpsIndex(rw http.ResponseWriter, r *http.Request) { | ||
| 77 | + data := make(map[interface{}]interface{}) | ||
| 78 | + data["Content"] = toolbox.StatisticsMap.GetMap() | ||
| 79 | + | ||
| 80 | + // do html escape before display path, avoid xss | ||
| 81 | + if content, ok := (data["Content"]).(map[string]interface{}); ok { | ||
| 82 | + if resultLists, ok := (content["Data"]).([][]string); ok { | ||
| 83 | + for i := range resultLists { | ||
| 84 | + if len(resultLists[i]) > 0 { | ||
| 85 | + resultLists[i][0] = template.HTMLEscapeString(resultLists[i][0]) | ||
| 86 | + } | ||
| 87 | + } | ||
| 88 | + } | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + execTpl(rw, data, qpsTpl, defaultScriptsTpl) | ||
| 92 | +} | ||
| 93 | + | ||
| 94 | +// ListConf is the http.Handler of displaying all beego configuration values as key/value pair. | ||
| 95 | +// it's registered with url pattern "/listconf" in admin module. | ||
| 96 | +func listConf(rw http.ResponseWriter, r *http.Request) { | ||
| 97 | + r.ParseForm() | ||
| 98 | + command := r.Form.Get("command") | ||
| 99 | + if command == "" { | ||
| 100 | + rw.Write([]byte("command not support")) | ||
| 101 | + return | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + data := make(map[interface{}]interface{}) | ||
| 105 | + switch command { | ||
| 106 | + case "conf": | ||
| 107 | + m := make(map[string]interface{}) | ||
| 108 | + list("BConfig", BConfig, m) | ||
| 109 | + m["AppConfigPath"] = appConfigPath | ||
| 110 | + m["AppConfigProvider"] = appConfigProvider | ||
| 111 | + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) | ||
| 112 | + tmpl = template.Must(tmpl.Parse(configTpl)) | ||
| 113 | + tmpl = template.Must(tmpl.Parse(defaultScriptsTpl)) | ||
| 114 | + | ||
| 115 | + data["Content"] = m | ||
| 116 | + | ||
| 117 | + tmpl.Execute(rw, data) | ||
| 118 | + | ||
| 119 | + case "router": | ||
| 120 | + content := PrintTree() | ||
| 121 | + content["Fields"] = []string{ | ||
| 122 | + "Router Pattern", | ||
| 123 | + "Methods", | ||
| 124 | + "Controller", | ||
| 125 | + } | ||
| 126 | + data["Content"] = content | ||
| 127 | + data["Title"] = "Routers" | ||
| 128 | + execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl) | ||
| 129 | + case "filter": | ||
| 130 | + var ( | ||
| 131 | + content = map[string]interface{}{ | ||
| 132 | + "Fields": []string{ | ||
| 133 | + "Router Pattern", | ||
| 134 | + "Filter Function", | ||
| 135 | + }, | ||
| 136 | + } | ||
| 137 | + filterTypes = []string{} | ||
| 138 | + filterTypeData = make(map[string]interface{}) | ||
| 139 | + ) | ||
| 140 | + | ||
| 141 | + if BeeApp.Handlers.enableFilter { | ||
| 142 | + var filterType string | ||
| 143 | + for k, fr := range map[int]string{ | ||
| 144 | + BeforeStatic: "Before Static", | ||
| 145 | + BeforeRouter: "Before Router", | ||
| 146 | + BeforeExec: "Before Exec", | ||
| 147 | + AfterExec: "After Exec", | ||
| 148 | + FinishRouter: "Finish Router"} { | ||
| 149 | + if bf := BeeApp.Handlers.filters[k]; len(bf) > 0 { | ||
| 150 | + filterType = fr | ||
| 151 | + filterTypes = append(filterTypes, filterType) | ||
| 152 | + resultList := new([][]string) | ||
| 153 | + for _, f := range bf { | ||
| 154 | + var result = []string{ | ||
| 155 | + f.pattern, | ||
| 156 | + utils.GetFuncName(f.filterFunc), | ||
| 157 | + } | ||
| 158 | + *resultList = append(*resultList, result) | ||
| 159 | + } | ||
| 160 | + filterTypeData[filterType] = resultList | ||
| 161 | + } | ||
| 162 | + } | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + content["Data"] = filterTypeData | ||
| 166 | + content["Methods"] = filterTypes | ||
| 167 | + | ||
| 168 | + data["Content"] = content | ||
| 169 | + data["Title"] = "Filters" | ||
| 170 | + execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl) | ||
| 171 | + default: | ||
| 172 | + rw.Write([]byte("command not support")) | ||
| 173 | + } | ||
| 174 | +} | ||
| 175 | + | ||
| 176 | +func list(root string, p interface{}, m map[string]interface{}) { | ||
| 177 | + pt := reflect.TypeOf(p) | ||
| 178 | + pv := reflect.ValueOf(p) | ||
| 179 | + if pt.Kind() == reflect.Ptr { | ||
| 180 | + pt = pt.Elem() | ||
| 181 | + pv = pv.Elem() | ||
| 182 | + } | ||
| 183 | + for i := 0; i < pv.NumField(); i++ { | ||
| 184 | + var key string | ||
| 185 | + if root == "" { | ||
| 186 | + key = pt.Field(i).Name | ||
| 187 | + } else { | ||
| 188 | + key = root + "." + pt.Field(i).Name | ||
| 189 | + } | ||
| 190 | + if pv.Field(i).Kind() == reflect.Struct { | ||
| 191 | + list(key, pv.Field(i).Interface(), m) | ||
| 192 | + } else { | ||
| 193 | + m[key] = pv.Field(i).Interface() | ||
| 194 | + } | ||
| 195 | + } | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +// PrintTree prints all registered routers. | ||
| 199 | +func PrintTree() map[string]interface{} { | ||
| 200 | + var ( | ||
| 201 | + content = map[string]interface{}{} | ||
| 202 | + methods = []string{} | ||
| 203 | + methodsData = make(map[string]interface{}) | ||
| 204 | + ) | ||
| 205 | + for method, t := range BeeApp.Handlers.routers { | ||
| 206 | + | ||
| 207 | + resultList := new([][]string) | ||
| 208 | + | ||
| 209 | + printTree(resultList, t) | ||
| 210 | + | ||
| 211 | + methods = append(methods, method) | ||
| 212 | + methodsData[method] = resultList | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + content["Data"] = methodsData | ||
| 216 | + content["Methods"] = methods | ||
| 217 | + return content | ||
| 218 | +} | ||
| 219 | + | ||
| 220 | +func printTree(resultList *[][]string, t *Tree) { | ||
| 221 | + for _, tr := range t.fixrouters { | ||
| 222 | + printTree(resultList, tr) | ||
| 223 | + } | ||
| 224 | + if t.wildcard != nil { | ||
| 225 | + printTree(resultList, t.wildcard) | ||
| 226 | + } | ||
| 227 | + for _, l := range t.leaves { | ||
| 228 | + if v, ok := l.runObject.(*ControllerInfo); ok { | ||
| 229 | + if v.routerType == routerTypeBeego { | ||
| 230 | + var result = []string{ | ||
| 231 | + v.pattern, | ||
| 232 | + fmt.Sprintf("%s", v.methods), | ||
| 233 | + v.controllerType.String(), | ||
| 234 | + } | ||
| 235 | + *resultList = append(*resultList, result) | ||
| 236 | + } else if v.routerType == routerTypeRESTFul { | ||
| 237 | + var result = []string{ | ||
| 238 | + v.pattern, | ||
| 239 | + fmt.Sprintf("%s", v.methods), | ||
| 240 | + "", | ||
| 241 | + } | ||
| 242 | + *resultList = append(*resultList, result) | ||
| 243 | + } else if v.routerType == routerTypeHandler { | ||
| 244 | + var result = []string{ | ||
| 245 | + v.pattern, | ||
| 246 | + "", | ||
| 247 | + "", | ||
| 248 | + } | ||
| 249 | + *resultList = append(*resultList, result) | ||
| 250 | + } | ||
| 251 | + } | ||
| 252 | + } | ||
| 253 | +} | ||
| 254 | + | ||
| 255 | +// ProfIndex is a http.Handler for showing profile command. | ||
| 256 | +// it's in url pattern "/prof" in admin module. | ||
| 257 | +func profIndex(rw http.ResponseWriter, r *http.Request) { | ||
| 258 | + r.ParseForm() | ||
| 259 | + command := r.Form.Get("command") | ||
| 260 | + if command == "" { | ||
| 261 | + return | ||
| 262 | + } | ||
| 263 | + | ||
| 264 | + var ( | ||
| 265 | + format = r.Form.Get("format") | ||
| 266 | + data = make(map[interface{}]interface{}) | ||
| 267 | + result bytes.Buffer | ||
| 268 | + ) | ||
| 269 | + toolbox.ProcessInput(command, &result) | ||
| 270 | + data["Content"] = result.String() | ||
| 271 | + | ||
| 272 | + if format == "json" && command == "gc summary" { | ||
| 273 | + dataJSON, err := json.Marshal(data) | ||
| 274 | + if err != nil { | ||
| 275 | + http.Error(rw, err.Error(), http.StatusInternalServerError) | ||
| 276 | + return | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + rw.Header().Set("Content-Type", "application/json") | ||
| 280 | + rw.Write(dataJSON) | ||
| 281 | + return | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + data["Title"] = command | ||
| 285 | + defaultTpl := defaultScriptsTpl | ||
| 286 | + if command == "gc summary" { | ||
| 287 | + defaultTpl = gcAjaxTpl | ||
| 288 | + } | ||
| 289 | + execTpl(rw, data, profillingTpl, defaultTpl) | ||
| 290 | +} | ||
| 291 | + | ||
| 292 | +// Healthcheck is a http.Handler calling health checking and showing the result. | ||
| 293 | +// it's in "/healthcheck" pattern in admin module. | ||
| 294 | +func healthcheck(rw http.ResponseWriter, req *http.Request) { | ||
| 295 | + var ( | ||
| 296 | + result []string | ||
| 297 | + data = make(map[interface{}]interface{}) | ||
| 298 | + resultList = new([][]string) | ||
| 299 | + content = map[string]interface{}{ | ||
| 300 | + "Fields": []string{"Name", "Message", "Status"}, | ||
| 301 | + } | ||
| 302 | + ) | ||
| 303 | + | ||
| 304 | + for name, h := range toolbox.AdminCheckList { | ||
| 305 | + if err := h.Check(); err != nil { | ||
| 306 | + result = []string{ | ||
| 307 | + "error", | ||
| 308 | + name, | ||
| 309 | + err.Error(), | ||
| 310 | + } | ||
| 311 | + } else { | ||
| 312 | + result = []string{ | ||
| 313 | + "success", | ||
| 314 | + name, | ||
| 315 | + "OK", | ||
| 316 | + } | ||
| 317 | + } | ||
| 318 | + *resultList = append(*resultList, result) | ||
| 319 | + } | ||
| 320 | + | ||
| 321 | + content["Data"] = resultList | ||
| 322 | + data["Content"] = content | ||
| 323 | + data["Title"] = "Health Check" | ||
| 324 | + execTpl(rw, data, healthCheckTpl, defaultScriptsTpl) | ||
| 325 | +} | ||
| 326 | + | ||
| 327 | +// TaskStatus is a http.Handler with running task status (task name, status and the last execution). | ||
| 328 | +// it's in "/task" pattern in admin module. | ||
| 329 | +func taskStatus(rw http.ResponseWriter, req *http.Request) { | ||
| 330 | + data := make(map[interface{}]interface{}) | ||
| 331 | + | ||
| 332 | + // Run Task | ||
| 333 | + req.ParseForm() | ||
| 334 | + taskname := req.Form.Get("taskname") | ||
| 335 | + if taskname != "" { | ||
| 336 | + if t, ok := toolbox.AdminTaskList[taskname]; ok { | ||
| 337 | + if err := t.Run(); err != nil { | ||
| 338 | + data["Message"] = []string{"error", fmt.Sprintf("%s", err)} | ||
| 339 | + } | ||
| 340 | + data["Message"] = []string{"success", fmt.Sprintf("%s run success,Now the Status is <br>%s", taskname, t.GetStatus())} | ||
| 341 | + } else { | ||
| 342 | + data["Message"] = []string{"warning", fmt.Sprintf("there's no task which named: %s", taskname)} | ||
| 343 | + } | ||
| 344 | + } | ||
| 345 | + | ||
| 346 | + // List Tasks | ||
| 347 | + content := make(map[string]interface{}) | ||
| 348 | + resultList := new([][]string) | ||
| 349 | + var fields = []string{ | ||
| 350 | + "Task Name", | ||
| 351 | + "Task Spec", | ||
| 352 | + "Task Status", | ||
| 353 | + "Last Time", | ||
| 354 | + "", | ||
| 355 | + } | ||
| 356 | + for tname, tk := range toolbox.AdminTaskList { | ||
| 357 | + result := []string{ | ||
| 358 | + tname, | ||
| 359 | + tk.GetSpec(), | ||
| 360 | + tk.GetStatus(), | ||
| 361 | + tk.GetPrev().String(), | ||
| 362 | + } | ||
| 363 | + *resultList = append(*resultList, result) | ||
| 364 | + } | ||
| 365 | + | ||
| 366 | + content["Fields"] = fields | ||
| 367 | + content["Data"] = resultList | ||
| 368 | + data["Content"] = content | ||
| 369 | + data["Title"] = "Tasks" | ||
| 370 | + execTpl(rw, data, tasksTpl, defaultScriptsTpl) | ||
| 371 | +} | ||
| 372 | + | ||
| 373 | +func execTpl(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) { | ||
| 374 | + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) | ||
| 375 | + for _, tpl := range tpls { | ||
| 376 | + tmpl = template.Must(tmpl.Parse(tpl)) | ||
| 377 | + } | ||
| 378 | + tmpl.Execute(rw, data) | ||
| 379 | +} | ||
| 380 | + | ||
| 381 | +// adminApp is an http.HandlerFunc map used as beeAdminApp. | ||
| 382 | +type adminApp struct { | ||
| 383 | + routers map[string]http.HandlerFunc | ||
| 384 | +} | ||
| 385 | + | ||
| 386 | +// Route adds http.HandlerFunc to adminApp with url pattern. | ||
| 387 | +func (admin *adminApp) Route(pattern string, f http.HandlerFunc) { | ||
| 388 | + admin.routers[pattern] = f | ||
| 389 | +} | ||
| 390 | + | ||
| 391 | +// Run adminApp http server. | ||
| 392 | +// Its addr is defined in configuration file as adminhttpaddr and adminhttpport. | ||
| 393 | +func (admin *adminApp) Run() { | ||
| 394 | + if len(toolbox.AdminTaskList) > 0 { | ||
| 395 | + toolbox.StartTask() | ||
| 396 | + } | ||
| 397 | + addr := BConfig.Listen.AdminAddr | ||
| 398 | + | ||
| 399 | + if BConfig.Listen.AdminPort != 0 { | ||
| 400 | + addr = fmt.Sprintf("%s:%d", BConfig.Listen.AdminAddr, BConfig.Listen.AdminPort) | ||
| 401 | + } | ||
| 402 | + for p, f := range admin.routers { | ||
| 403 | + http.Handle(p, f) | ||
| 404 | + } | ||
| 405 | + logs.Info("Admin server Running on %s", addr) | ||
| 406 | + | ||
| 407 | + var err error | ||
| 408 | + if BConfig.Listen.Graceful { | ||
| 409 | + err = grace.ListenAndServe(addr, nil) | ||
| 410 | + } else { | ||
| 411 | + err = http.ListenAndServe(addr, nil) | ||
| 412 | + } | ||
| 413 | + if err != nil { | ||
| 414 | + logs.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid())) | ||
| 415 | + } | ||
| 416 | +} |
vendor/github.com/astaxie/beego/adminui.go
0 → 100644
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +package beego | ||
| 16 | + | ||
| 17 | +var indexTpl = ` | ||
| 18 | +{{define "content"}} | ||
| 19 | +<h1>Beego Admin Dashboard</h1> | ||
| 20 | +<p> | ||
| 21 | +For detail usage please check our document: | ||
| 22 | +</p> | ||
| 23 | +<p> | ||
| 24 | +<a target="_blank" href="http://beego.me/docs/module/toolbox.md">Toolbox</a> | ||
| 25 | +</p> | ||
| 26 | +<p> | ||
| 27 | +<a target="_blank" href="http://beego.me/docs/advantage/monitor.md">Live Monitor</a> | ||
| 28 | +</p> | ||
| 29 | +{{.Content}} | ||
| 30 | +{{end}}` | ||
| 31 | + | ||
| 32 | +var profillingTpl = ` | ||
| 33 | +{{define "content"}} | ||
| 34 | +<h1>{{.Title}}</h1> | ||
| 35 | +<pre id="content"> | ||
| 36 | +<div>{{.Content}}</div> | ||
| 37 | +</pre> | ||
| 38 | +{{end}}` | ||
| 39 | + | ||
| 40 | +var defaultScriptsTpl = `` | ||
| 41 | + | ||
| 42 | +var gcAjaxTpl = ` | ||
| 43 | +{{define "scripts"}} | ||
| 44 | +<script type="text/javascript"> | ||
| 45 | + var app = app || {}; | ||
| 46 | +(function() { | ||
| 47 | + app.$el = $('#content'); | ||
| 48 | + app.getGc = function() { | ||
| 49 | + var that = this; | ||
| 50 | + $.ajax("/prof?command=gc%20summary&format=json").done(function(data) { | ||
| 51 | + that.$el.append($('<p>' + data.Content + '</p>')); | ||
| 52 | + }); | ||
| 53 | + }; | ||
| 54 | + $(document).ready(function() { | ||
| 55 | + setInterval(function() { | ||
| 56 | + app.getGc(); | ||
| 57 | + }, 3000); | ||
| 58 | + }); | ||
| 59 | +})(); | ||
| 60 | +</script> | ||
| 61 | +{{end}} | ||
| 62 | +` | ||
| 63 | + | ||
| 64 | +var qpsTpl = `{{define "content"}} | ||
| 65 | +<h1>Requests statistics</h1> | ||
| 66 | +<table class="table table-striped table-hover "> | ||
| 67 | + <thead> | ||
| 68 | + <tr> | ||
| 69 | + {{range .Content.Fields}} | ||
| 70 | + <th> | ||
| 71 | + {{.}} | ||
| 72 | + </th> | ||
| 73 | + {{end}} | ||
| 74 | + </tr> | ||
| 75 | + </thead> | ||
| 76 | + | ||
| 77 | + <tbody> | ||
| 78 | + {{range $i, $elem := .Content.Data}} | ||
| 79 | + | ||
| 80 | + <tr> | ||
| 81 | + <td>{{index $elem 0}}</td> | ||
| 82 | + <td>{{index $elem 1}}</td> | ||
| 83 | + <td>{{index $elem 2}}</td> | ||
| 84 | + <td data-order="{{index $elem 3}}">{{index $elem 4}}</td> | ||
| 85 | + <td data-order="{{index $elem 5}}">{{index $elem 6}}</td> | ||
| 86 | + <td data-order="{{index $elem 7}}">{{index $elem 8}}</td> | ||
| 87 | + <td data-order="{{index $elem 9}}">{{index $elem 10}}</td> | ||
| 88 | + </tr> | ||
| 89 | + {{end}} | ||
| 90 | + </tbody> | ||
| 91 | + | ||
| 92 | +</table> | ||
| 93 | +{{end}}` | ||
| 94 | + | ||
| 95 | +var configTpl = ` | ||
| 96 | +{{define "content"}} | ||
| 97 | +<h1>Configurations</h1> | ||
| 98 | +<pre> | ||
| 99 | +{{range $index, $elem := .Content}} | ||
| 100 | +{{$index}}={{$elem}} | ||
| 101 | +{{end}} | ||
| 102 | +</pre> | ||
| 103 | +{{end}} | ||
| 104 | +` | ||
| 105 | + | ||
| 106 | +var routerAndFilterTpl = `{{define "content"}} | ||
| 107 | + | ||
| 108 | + | ||
| 109 | +<h1>{{.Title}}</h1> | ||
| 110 | + | ||
| 111 | +{{range .Content.Methods}} | ||
| 112 | + | ||
| 113 | +<div class="panel panel-default"> | ||
| 114 | +<div class="panel-heading lead success"><strong>{{.}}</strong></div> | ||
| 115 | +<div class="panel-body"> | ||
| 116 | +<table class="table table-striped table-hover "> | ||
| 117 | + <thead> | ||
| 118 | + <tr> | ||
| 119 | + {{range $.Content.Fields}} | ||
| 120 | + <th> | ||
| 121 | + {{.}} | ||
| 122 | + </th> | ||
| 123 | + {{end}} | ||
| 124 | + </tr> | ||
| 125 | + </thead> | ||
| 126 | + | ||
| 127 | + <tbody> | ||
| 128 | + {{$slice := index $.Content.Data .}} | ||
| 129 | + {{range $i, $elem := $slice}} | ||
| 130 | + | ||
| 131 | + <tr> | ||
| 132 | + {{range $elem}} | ||
| 133 | + <td> | ||
| 134 | + {{.}} | ||
| 135 | + </td> | ||
| 136 | + {{end}} | ||
| 137 | + </tr> | ||
| 138 | + | ||
| 139 | + {{end}} | ||
| 140 | + </tbody> | ||
| 141 | + | ||
| 142 | +</table> | ||
| 143 | +</div> | ||
| 144 | +</div> | ||
| 145 | +{{end}} | ||
| 146 | + | ||
| 147 | + | ||
| 148 | +{{end}}` | ||
| 149 | + | ||
| 150 | +var tasksTpl = `{{define "content"}} | ||
| 151 | + | ||
| 152 | +<h1>{{.Title}}</h1> | ||
| 153 | + | ||
| 154 | +{{if .Message }} | ||
| 155 | +{{ $messageType := index .Message 0}} | ||
| 156 | +<p class="message | ||
| 157 | +{{if eq "error" $messageType}} | ||
| 158 | +bg-danger | ||
| 159 | +{{else if eq "success" $messageType}} | ||
| 160 | +bg-success | ||
| 161 | +{{else}} | ||
| 162 | +bg-warning | ||
| 163 | +{{end}} | ||
| 164 | +"> | ||
| 165 | +{{index .Message 1}} | ||
| 166 | +</p> | ||
| 167 | +{{end}} | ||
| 168 | + | ||
| 169 | + | ||
| 170 | +<table class="table table-striped table-hover "> | ||
| 171 | +<thead> | ||
| 172 | +<tr> | ||
| 173 | +{{range .Content.Fields}} | ||
| 174 | +<th> | ||
| 175 | +{{.}} | ||
| 176 | +</th> | ||
| 177 | +{{end}} | ||
| 178 | +</tr> | ||
| 179 | +</thead> | ||
| 180 | + | ||
| 181 | +<tbody> | ||
| 182 | +{{range $i, $slice := .Content.Data}} | ||
| 183 | +<tr> | ||
| 184 | + {{range $slice}} | ||
| 185 | + <td> | ||
| 186 | + {{.}} | ||
| 187 | + </td> | ||
| 188 | + {{end}} | ||
| 189 | + <td> | ||
| 190 | + <a class="btn btn-primary btn-sm" href="/task?taskname={{index $slice 0}}">Run</a> | ||
| 191 | + </td> | ||
| 192 | +</tr> | ||
| 193 | +{{end}} | ||
| 194 | +</tbody> | ||
| 195 | +</table> | ||
| 196 | + | ||
| 197 | +{{end}}` | ||
| 198 | + | ||
| 199 | +var healthCheckTpl = ` | ||
| 200 | +{{define "content"}} | ||
| 201 | + | ||
| 202 | +<h1>{{.Title}}</h1> | ||
| 203 | +<table class="table table-striped table-hover "> | ||
| 204 | +<thead> | ||
| 205 | +<tr> | ||
| 206 | +{{range .Content.Fields}} | ||
| 207 | + <th> | ||
| 208 | + {{.}} | ||
| 209 | + </th> | ||
| 210 | +{{end}} | ||
| 211 | +</tr> | ||
| 212 | +</thead> | ||
| 213 | +<tbody> | ||
| 214 | +{{range $i, $slice := .Content.Data}} | ||
| 215 | + {{ $header := index $slice 0}} | ||
| 216 | + {{ if eq "success" $header}} | ||
| 217 | + <tr class="success"> | ||
| 218 | + {{else if eq "error" $header}} | ||
| 219 | + <tr class="danger"> | ||
| 220 | + {{else}} | ||
| 221 | + <tr> | ||
| 222 | + {{end}} | ||
| 223 | + {{range $j, $elem := $slice}} | ||
| 224 | + {{if ne $j 0}} | ||
| 225 | + <td> | ||
| 226 | + {{$elem}} | ||
| 227 | + </td> | ||
| 228 | + {{end}} | ||
| 229 | + {{end}} | ||
| 230 | + <td> | ||
| 231 | + {{$header}} | ||
| 232 | + </td> | ||
| 233 | + </tr> | ||
| 234 | +{{end}} | ||
| 235 | + | ||
| 236 | +</tbody> | ||
| 237 | +</table> | ||
| 238 | +{{end}}` | ||
| 239 | + | ||
| 240 | +// The base dashboardTpl | ||
| 241 | +var dashboardTpl = ` | ||
| 242 | +<!DOCTYPE html> | ||
| 243 | +<html lang="en"> | ||
| 244 | +<head> | ||
| 245 | +<!-- Meta, title, CSS, favicons, etc. --> | ||
| 246 | +<meta charset="utf-8"> | ||
| 247 | +<meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
| 248 | +<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 249 | + | ||
| 250 | +<title> | ||
| 251 | + | ||
| 252 | +Welcome to Beego Admin Dashboard | ||
| 253 | + | ||
| 254 | +</title> | ||
| 255 | + | ||
| 256 | +<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"> | ||
| 257 | +<link href="//cdn.datatables.net/plug-ins/725b2a2115b/integration/bootstrap/3/dataTables.bootstrap.css" rel="stylesheet"> | ||
| 258 | + | ||
| 259 | +<style type="text/css"> | ||
| 260 | +ul.nav li.dropdown:hover > ul.dropdown-menu { | ||
| 261 | + display: block; | ||
| 262 | +} | ||
| 263 | +#logo { | ||
| 264 | + width: 102px; | ||
| 265 | + height: 32px; | ||
| 266 | + margin-top: 5px; | ||
| 267 | +} | ||
| 268 | +.message { | ||
| 269 | + padding: 15px; | ||
| 270 | +} | ||
| 271 | +</style> | ||
| 272 | + | ||
| 273 | +</head> | ||
| 274 | +<body> | ||
| 275 | + | ||
| 276 | +<header class="navbar navbar-default navbar-static-top bs-docs-nav" id="top" role="banner"> | ||
| 277 | +<div class="container"> | ||
| 278 | +<div class="navbar-header"> | ||
| 279 | +<button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".bs-navbar-collapse"> | ||
| 280 | +<span class="sr-only">Toggle navigation</span> | ||
| 281 | +<span class="icon-bar"></span> | ||
| 282 | +<span class="icon-bar"></span> | ||
| 283 | +<span class="icon-bar"></span> | ||
| 284 | +</button> | ||
| 285 | + | ||
| 286 | +<a href="/"> | ||
| 287 | +<img id="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPYAAABNCAYAAACVH5l+AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAV/QAAFf0BzXBRYQAAABZ0RVh0Q3JlYXRpb24gVGltZQAxMi8xMy8xM+ovEHIAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAQAklEQVR4nO2de7RcVXnAfzM3MckmhLdKtZYqoimtERVtXSjFNvYE6rsnFMFKy2MJC4tGdNECalFsKxRQ5GGDClUjzS6lltcuUmiryxRqFaEaCkgVqAkSakKSneTm3rn949uHOTNzzp1zZs5j7mT/1pqVmzMze39z73xnf/vb36OBx+OZFRuEvwusBp4fu7wROE0ZfUs9UiUzMzMDQLNmOTyeucB1dCo17v/XVS5JRrxiezz9OSDn9drxiu3xjCFesT2eMcQrtsczhnjF9njGEK/YHs8Y4hXb4xlDvGJ7PGOIV2yPZwzxiu3x9OfpnNdrxyu2x9Ofk5HY8Dgb3fWRpFG3AB6Ppzh8EojHM8Z4xfZ4xhCv2B7PGOIV2+MZQ+bVLYDHUzU2CE8AXg+8GHguMAW0gOmEn6PH7q6fdwOTsceurp+fca+1wJPAz4GfKaM3V/EZvVfcs0dhg/AC4D3AgcAixGrNqgczOZ9ruOuTiGLfBVygjH4is8A5ibziXrE9eww2CI8C1gL7INZq9P2PFLBIorGjcacRBf82sFIZvbXg+WQyp9jzbBBuRj7osDwFPALcA9wC3K2MbhUw7rMUKGsWHgMuUUZfMcib55Ksg2CDcCnwR8CbgJcCe2d5G3AzcJYyelOJ4vVOHISLgEuB/YAJqlvUonkmgAXAa4HfR4ojDo0NwiXAIcAPldFT0fUinWcHAb8BfAC4E3jQBuHxBY5fNS8CPmuD8Ji6BclAZbLaIFxgg/AzwH8B5wCvIptSAyjgeOCvShIvkW1vflcDkfXltL/zMxU/QJR8EXCWDcIDh/lMNgjn2SD8MLAB+D6w3gbhC6Lny/SKvxS4wQbhGhuEqsR5yua36xYgB6XK6v6OtwN/zHDfnaOLkSgbzWbz5cDpwHySla5VwCNpzDTlPgSxdgbCBuGrgXuBTyM3S4BDgS/tWLGyAdV4xU8AltggfJsyerqC+YrG1i1ADkqT1QbhBLI/LcIq2FnAGJlwJviFwL7uUlzJQH5n6921pntM9Pk3crg1ux646w3apvd8es3++cApNgi/pox+PMdnUcAngLPd+N0sB04FVld13HUc8FHgYxXNVxSTwI11C5GRsmW9APk7FsHagsbJwnGIhRApQuQom0FuMOcpo68pY2IbhPsD/wwcRvtmECn5wcCHEesny1i/A1yDrPazcfGOFSu/1khx8rSABzJJL+znBJ0/y2t2A0uV0T/KMW4HBcmahUngIeAKZfQ9gwwwl2Tthw3Co5GjmjTz+xngx/T3LD8J/CNwTRXWm9vH3gr8Cr2KPQ3co4wue/tyKvAXtE3m+I1lM3CsMvr7s7z/AOBy4KQc0y5LW7G3KqNfmWOgyExYDnwcSHrvfMSBcUaecTOQW9YamUuyAs8qxxrSlXo14uWerE6qzKyivVpGRHvfzYhJWzZrgROB19DpuANYDPypDcL3KqN7tic2COcDdwO/lmO+B4D1hTnPlNFWGf115AOkmVrvtkG4oKg5PeWydfk7G8D1wC+kvOTfgDNGUaltEL4GCUSZR69DaxdwgzL6B2XLoYx+BnFybaftaIP2nv4NwG+lvH0p+ZR6DXDMotvX7i7cK+5MrFPoTUwHWAIcVfScnnKYmJhYBRyb8vTTwAmj6BC1QbgYcZhFx3BxpZ4GfgJcVqFIdyJe7Gl6veQK+IA7j+7mUcSy6MdPgBXK6BMX3b72aSjpuEsZvY30hmWvL2NOT7HYIDwS+PNZXnKyMvqnVcmTk5XAEXRGlEXKZBF/RGWyK6N3A+cjvojuVbuBrMpvSXjfNuCDswzdQm5QhyujTfyJMs+x70i5fniJc3oKwAbhPsANpDtDLx+19rERLkjjTGAhvWGdU8B/Al+tWi5l9P3A3zsZIpkiL/lC4P1Jq7Yy+jrgtoQh7wNep4xepYze3v1kmYqdtn/5pRLn9BTD55HMpyS+B5xboSx5ORuJxGvSuVq3ELN2VY0+gUuRLWp38EoDCTA5JeV9pyHh2iAWx7nAkcro76RNVJpiK6N/hhxxdXNQWXN6hscG4WlI2GcS24HjldG7KhQpMzYIXwmEtCPMoK08u4AvKKMfqkk8lNGPIdbCLto3G2g70k63Qdiz8Lltw1JgGXCwMvov43HhSZRdaCEpEsrngI8oNggPBT4zy0vOUEY/XJU8ebBBuBA5L47HrUf72WngYQpKvBiSaxFnV7cjrYGkkp7lovw6UEZPKaPvd172vtRRQeU5Nczp6YNLlPhrJEkhia8oo79coUh5eTfwCnq/05HD7GpnRdaKk+FqJLAortQgQTTvQFbnoShNsW0QNklOW6wsTtiTnWazeRLpceCPUHxgUWHYIDwYOIvORSPax04B6xDH1aiwBniQ3r02yJHwh1xwysCUuWKneb+fKnFOzwC41fqClKcnkX31tgpFysu5SEhzPFwzOrP+OXCRMnpHfeJ14nwUH0csiUi5ob3XPgZJhx2YMhU7LWHgkZTrnppoNptvQNJsk/gzZfR3q5QnDy7C7DjEjI0UJFKW3cAXZ4vFrgtl9L8C36RdYy1elGQRcN4w45ei2DYI9yU9a+XeMub0DEWQcv1x4JIqBcmDDcJ5wEVIzHX8zDoywR8ErqpHukx8EthCr0neBF5tgzDt79KXwhXbBTesRUyjJG4tek7P0CxLuX79KMaBxzgZ2fLFTfBo9dsGXFpWbbEiUEavR8qITdF7/DUBfMLlk+emMMW2QXiYDcKzkeyS5Skvu0sZ7U3x0eOQlOv/VKUQebBB+DzEKoyKEsbDR6eRNNNv1CNdLi5D4u67U16byOL4vkEGTTtT3sflE2dlr1nGivMnOcbMShZZp5B84S8ro2c7py2bUZV1r5TrP6xo/kE4Ddif5Hjwp4BPj2ogTRxl9BM2CK9DYsKjwJroM80D3mOD8AZl9IY8486mjEVX2LxIGV3W/jqLrAcg+5bNyujrS5IjC6Moa1Ixwkll9P9VMPegHE1bAeLKPQWsbrVaAxf0qIGrkGi/F9Kbs30gUl7s0jwDVhWgsobRKYv0jroFyEFVsibVzxqZ46EUor1nd8HA7a1W68rFd9zYr5rLyOCO4i6kvdeOWyAN4DfzjlmVYttWq1VojXHPHk8UudXNvGaz+dqqhSmAbyA30+7Chw3St0qpVKXYpzabzTL214NwU90C5GAuyVo162gfE8X/XQCc547C5hLLkfTNpPLIuX0Fs334LTnG2Zv+N4kLbRDeWlKwQD9Z4w6pOvfXMLdk7YsNwrcAHwFeRvY8gC3K6GHTd/8GeBtSVrg773op8FZGK4w0FZeHfS6iQy16Pfyp6ZlppCn2FmX0vinPpQl3CFKw/iMkRzFNAJ+iuBK2EbllrZG5JGtfbBC+CvgH6kkmegT4OlK9M4o6ixR8AZICeWfWbKiaORN4Acke/i0MUBiiyGKGP1ZGXwv8KtKfKYkVNgh/sag5PbVzLDX1WHe11r6InAEnpUC+GHhXHbLlweVfn0D75hTfWuxGbpy5Yz/KKGY4iTQdS+pw0KD4Fduzh9JqtR4F/o7OPTbu5wXAScP2yKqA85GMLuhcracRHbpskIKRZRUztEhCeRKvK2NOTy3cRmfyQqW4I60vAP9Lsof8RUhFlZHEtfU9is72QHGH2SeV0XkCxZ6lTDPq9pTrh5Y4p6dCXNbX24FvIdFeWxIeZcvwJLIHjRcuiJR8HnD8KK7arsHGxxBPeES8RPK/K6MHDokt80jgwZTrI/dL9gyOMvpm0n0qVfUJ10gwz6H0Nsd7IfCHwMUly5AZV4Tk/UiMfpLDbCuzl37uS5nFDLcixe+6GShbxeNJQxm9CVm1d9J7BtxEVu1Rqo77y8DvkZxDPgXcpIxOWxgzUbZHM6mSom/x4ymDmxDvcdyBBrIi7guckVQksGpc4MwHkUaW3VFmLeCnwGeHnadsxV6YcG3kM248cw93Xr0aWbW7yw01gDcDL6lHug7e6B5xEzzKIZ8Eriwi+abMYoYHkbw6D+Tl83gyYJAm9klFAhcjgSC1YYNwb2S1jm9H42fX31NGF9I7vMwV+00p1x8tcU7PYFRtRZWSeeV6ZH0KSaboPtduAEfbIDysjLkzciLi4Euq+LIFKZVUCKUotmu/mtZM7D/KmNMzFElOziU2CIuoAZ9URre0YzB3BPct0jtbripr7tlwIdd/QHrRxduU0YUVtihcsbcuf2djYmLiYtIDUUaymdseTtKeroEUMxgYG4TLEGXq5slhxs3A5ciRUbQaxpX7FTYIjyh5/g5cjfDIYQadN5xppJ/XlUXOWWTNs4U2CIOJiYm7gA+lvOxeZfQDRc3pKYy0aiNXOOXMjQ3Cw5EMrCTuG2TMrLj+XN+k1+RvIOGbv17m/Am8keQIMxCH2bUu0KYw0gJU9rZBmOeXvxjpotkv4KWMKip5Zc3CJPAQ0kf5ngLHHVVZ70B6SnfzMuA+G4QbgKztcZpIEFJalVqYJaClQK5CCu93x01MIJ+rElzV3vfRtlzi3voW0lPsxqLnTVPEJuklaQfl+u7m3AVRhqwARwKhDcJlwwYLxBhVWf8WcTo9N+X5g5ldUfPwMMn9novmUeAJekOYZyjus2Th7Ugac7w+W7Q92IbUAizceVlVyt3d1HzUMCDPYQ6k/jkGltW176ni7zMDnKmMLj1xxGVEbSL56CspvqJwXE+x99LZ1jceYWbK6rJSRfmYrwCnj1LvpJwkOX9GlYFlVUbfaIPwTMrrnNFClPrOksZPIjr26o7w6vneu7iLw5Fwz+cjyr8L6a+13Y21M/bvM4iDbguy8u6I96x2EWbnIFZQd+XRGUpwmMUpU7HvB853SQJzmSq/iMMylKzK6KttED6OpEKmmeWD8CPk5n5XgWNmIR47HhHV6wbABuEBiH/hWOB5iEJHCSTx9yTRMa4NwvhcDcSK6h5nBvGLfFUZvTHn58lMkYq9EcnoWgfcrIxeV+DYdfAYcIky+u66BclAYbIqo2+xQfgSxIRcARyBKHme78pOYAPwbaSlk46vZhUSrdjdit10ZvJKZA+8P+JU617ZI9KuZ6F77hYS0164wyzOMAJ7PCONDcKPIllUcV/SDHLjsUhySJNOPYgfRQ1L92rdcvOeX9aWZGZGRJ9rJVo9njx0r9iRib0QyWOIVzft7tZZBN2r9W7gX6rwM3jF9owzu+hVLkhW3CRPfZqC57V0W4hS/w9QST82r9ieccaSrIRpjrG4uRx15ZhAzPXoEa360f+hM6Ksm0kko3E98Lm8zfUGxSu2Z5x5Glkp59OrdElm8n8jOd3fUUYnJcYk4o62FrrHXu6hEP2yyNHYJhcvUAlesT3jzEbkDHoJnUdYcZN8J3IcN3BIrvP4b3OPTcMIXBResT3jzONIqaGFtE3qiB3AD5AAqnUul3ts8IrtGWc2IDHw5yAtdOYjoZzfRRoN3FdGnPYo4M+xPWONK2C4H+1FbArYXFPATOlE59j/D6WId7YitGZUAAAAAElFTkSuQmCC"/> | ||
| 288 | +</a> | ||
| 289 | + | ||
| 290 | +</div> | ||
| 291 | +<nav class="collapse navbar-collapse bs-navbar-collapse" role="navigation"> | ||
| 292 | +<ul class="nav navbar-nav"> | ||
| 293 | +<li> | ||
| 294 | +<a href="/qps"> | ||
| 295 | +Requests statistics | ||
| 296 | +</a> | ||
| 297 | +</li> | ||
| 298 | +<li> | ||
| 299 | + | ||
| 300 | +<li class="dropdown"> | ||
| 301 | +<a href="#" class="dropdown-toggle disabled" data-toggle="dropdown">Performance profiling<span class="caret"></span></a> | ||
| 302 | +<ul class="dropdown-menu" role="menu"> | ||
| 303 | + | ||
| 304 | +<li><a href="/prof?command=lookup goroutine">lookup goroutine</a></li> | ||
| 305 | +<li><a href="/prof?command=lookup heap">lookup heap</a></li> | ||
| 306 | +<li><a href="/prof?command=lookup threadcreate">lookup threadcreate</a></li> | ||
| 307 | +<li><a href="/prof?command=lookup block">lookup block</a></li> | ||
| 308 | +<li><a href="/prof?command=get cpuprof">get cpuprof</a></li> | ||
| 309 | +<li><a href="/prof?command=get memprof">get memprof</a></li> | ||
| 310 | +<li><a href="/prof?command=gc summary">gc summary</a></li> | ||
| 311 | + | ||
| 312 | +</ul> | ||
| 313 | +</li> | ||
| 314 | + | ||
| 315 | +<li> | ||
| 316 | +<a href="/healthcheck"> | ||
| 317 | +Healthcheck | ||
| 318 | +</a> | ||
| 319 | +</li> | ||
| 320 | + | ||
| 321 | +<li> | ||
| 322 | +<a href="/task" class="dropdown-toggle disabled" data-toggle="dropdown">Tasks</a> | ||
| 323 | +</li> | ||
| 324 | + | ||
| 325 | +<li class="dropdown"> | ||
| 326 | +<a href="#" class="dropdown-toggle disabled" data-toggle="dropdown">Config Status<span class="caret"></span></a> | ||
| 327 | +<ul class="dropdown-menu" role="menu"> | ||
| 328 | +<li><a href="/listconf?command=conf">Configs</a></li> | ||
| 329 | +<li><a href="/listconf?command=router">Routers</a></li> | ||
| 330 | +<li><a href="/listconf?command=filter">Filters</a></li> | ||
| 331 | +</ul> | ||
| 332 | +</li> | ||
| 333 | +</ul> | ||
| 334 | +</nav> | ||
| 335 | +</div> | ||
| 336 | +</header> | ||
| 337 | + | ||
| 338 | +<div class="container"> | ||
| 339 | +{{template "content" .}} | ||
| 340 | +</div> | ||
| 341 | + | ||
| 342 | +<script src="//code.jquery.com/jquery-1.11.1.min.js"></script> | ||
| 343 | +<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> | ||
| 344 | +<script src="//cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js"></script> | ||
| 345 | +<script src="//cdn.datatables.net/plug-ins/725b2a2115b/integration/bootstrap/3/dataTables.bootstrap.js | ||
| 346 | +"></script> | ||
| 347 | + | ||
| 348 | +<script type="text/javascript"> | ||
| 349 | +$(document).ready(function() { | ||
| 350 | + $('.table').dataTable(); | ||
| 351 | +}); | ||
| 352 | +</script> | ||
| 353 | +{{template "scripts" .}} | ||
| 354 | +</body> | ||
| 355 | +</html> | ||
| 356 | +` |
vendor/github.com/astaxie/beego/app.go
0 → 100644
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +package beego | ||
| 16 | + | ||
| 17 | +import ( | ||
| 18 | + "crypto/tls" | ||
| 19 | + "crypto/x509" | ||
| 20 | + "fmt" | ||
| 21 | + "io/ioutil" | ||
| 22 | + "net" | ||
| 23 | + "net/http" | ||
| 24 | + "net/http/fcgi" | ||
| 25 | + "os" | ||
| 26 | + "path" | ||
| 27 | + "strings" | ||
| 28 | + "time" | ||
| 29 | + | ||
| 30 | + "github.com/astaxie/beego/grace" | ||
| 31 | + "github.com/astaxie/beego/logs" | ||
| 32 | + "github.com/astaxie/beego/utils" | ||
| 33 | + "golang.org/x/crypto/acme/autocert" | ||
| 34 | +) | ||
| 35 | + | ||
| 36 | +var ( | ||
| 37 | + // BeeApp is an application instance | ||
| 38 | + BeeApp *App | ||
| 39 | +) | ||
| 40 | + | ||
| 41 | +func init() { | ||
| 42 | + // create beego application | ||
| 43 | + BeeApp = NewApp() | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +// App defines beego application with a new PatternServeMux. | ||
| 47 | +type App struct { | ||
| 48 | + Handlers *ControllerRegister | ||
| 49 | + Server *http.Server | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +// NewApp returns a new beego application. | ||
| 53 | +func NewApp() *App { | ||
| 54 | + cr := NewControllerRegister() | ||
| 55 | + app := &App{Handlers: cr, Server: &http.Server{}} | ||
| 56 | + return app | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +// MiddleWare function for http.Handler | ||
| 60 | +type MiddleWare func(http.Handler) http.Handler | ||
| 61 | + | ||
| 62 | +// Run beego application. | ||
| 63 | +func (app *App) Run(mws ...MiddleWare) { | ||
| 64 | + addr := BConfig.Listen.HTTPAddr | ||
| 65 | + | ||
| 66 | + if BConfig.Listen.HTTPPort != 0 { | ||
| 67 | + addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPAddr, BConfig.Listen.HTTPPort) | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + var ( | ||
| 71 | + err error | ||
| 72 | + l net.Listener | ||
| 73 | + endRunning = make(chan bool, 1) | ||
| 74 | + ) | ||
| 75 | + | ||
| 76 | + // run cgi server | ||
| 77 | + if BConfig.Listen.EnableFcgi { | ||
| 78 | + if BConfig.Listen.EnableStdIo { | ||
| 79 | + if err = fcgi.Serve(nil, app.Handlers); err == nil { // standard I/O | ||
| 80 | + logs.Info("Use FCGI via standard I/O") | ||
| 81 | + } else { | ||
| 82 | + logs.Critical("Cannot use FCGI via standard I/O", err) | ||
| 83 | + } | ||
| 84 | + return | ||
| 85 | + } | ||
| 86 | + if BConfig.Listen.HTTPPort == 0 { | ||
| 87 | + // remove the Socket file before start | ||
| 88 | + if utils.FileExists(addr) { | ||
| 89 | + os.Remove(addr) | ||
| 90 | + } | ||
| 91 | + l, err = net.Listen("unix", addr) | ||
| 92 | + } else { | ||
| 93 | + l, err = net.Listen("tcp", addr) | ||
| 94 | + } | ||
| 95 | + if err != nil { | ||
| 96 | + logs.Critical("Listen: ", err) | ||
| 97 | + } | ||
| 98 | + if err = fcgi.Serve(l, app.Handlers); err != nil { | ||
| 99 | + logs.Critical("fcgi.Serve: ", err) | ||
| 100 | + } | ||
| 101 | + return | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + app.Server.Handler = app.Handlers | ||
| 105 | + for i := len(mws) - 1; i >= 0; i-- { | ||
| 106 | + if mws[i] == nil { | ||
| 107 | + continue | ||
| 108 | + } | ||
| 109 | + app.Server.Handler = mws[i](app.Server.Handler) | ||
| 110 | + } | ||
| 111 | + app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second | ||
| 112 | + app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second | ||
| 113 | + app.Server.ErrorLog = logs.GetLogger("HTTP") | ||
| 114 | + | ||
| 115 | + // run graceful mode | ||
| 116 | + if BConfig.Listen.Graceful { | ||
| 117 | + httpsAddr := BConfig.Listen.HTTPSAddr | ||
| 118 | + app.Server.Addr = httpsAddr | ||
| 119 | + if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS { | ||
| 120 | + go func() { | ||
| 121 | + time.Sleep(1000 * time.Microsecond) | ||
| 122 | + if BConfig.Listen.HTTPSPort != 0 { | ||
| 123 | + httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort) | ||
| 124 | + app.Server.Addr = httpsAddr | ||
| 125 | + } | ||
| 126 | + server := grace.NewServer(httpsAddr, app.Handlers) | ||
| 127 | + server.Server.ReadTimeout = app.Server.ReadTimeout | ||
| 128 | + server.Server.WriteTimeout = app.Server.WriteTimeout | ||
| 129 | + if BConfig.Listen.EnableMutualHTTPS { | ||
| 130 | + if err := server.ListenAndServeMutualTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile, BConfig.Listen.TrustCaFile); err != nil { | ||
| 131 | + logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) | ||
| 132 | + time.Sleep(100 * time.Microsecond) | ||
| 133 | + endRunning <- true | ||
| 134 | + } | ||
| 135 | + } else { | ||
| 136 | + if BConfig.Listen.AutoTLS { | ||
| 137 | + m := autocert.Manager{ | ||
| 138 | + Prompt: autocert.AcceptTOS, | ||
| 139 | + HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...), | ||
| 140 | + Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir), | ||
| 141 | + } | ||
| 142 | + app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} | ||
| 143 | + BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", "" | ||
| 144 | + } | ||
| 145 | + if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil { | ||
| 146 | + logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) | ||
| 147 | + time.Sleep(100 * time.Microsecond) | ||
| 148 | + endRunning <- true | ||
| 149 | + } | ||
| 150 | + } | ||
| 151 | + }() | ||
| 152 | + } | ||
| 153 | + if BConfig.Listen.EnableHTTP { | ||
| 154 | + go func() { | ||
| 155 | + server := grace.NewServer(addr, app.Handlers) | ||
| 156 | + server.Server.ReadTimeout = app.Server.ReadTimeout | ||
| 157 | + server.Server.WriteTimeout = app.Server.WriteTimeout | ||
| 158 | + if BConfig.Listen.ListenTCP4 { | ||
| 159 | + server.Network = "tcp4" | ||
| 160 | + } | ||
| 161 | + if err := server.ListenAndServe(); err != nil { | ||
| 162 | + logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid())) | ||
| 163 | + time.Sleep(100 * time.Microsecond) | ||
| 164 | + endRunning <- true | ||
| 165 | + } | ||
| 166 | + }() | ||
| 167 | + } | ||
| 168 | + <-endRunning | ||
| 169 | + return | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + // run normal mode | ||
| 173 | + if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS { | ||
| 174 | + go func() { | ||
| 175 | + time.Sleep(1000 * time.Microsecond) | ||
| 176 | + if BConfig.Listen.HTTPSPort != 0 { | ||
| 177 | + app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort) | ||
| 178 | + } else if BConfig.Listen.EnableHTTP { | ||
| 179 | + BeeLogger.Info("Start https server error, conflict with http. Please reset https port") | ||
| 180 | + return | ||
| 181 | + } | ||
| 182 | + logs.Info("https server Running on https://%s", app.Server.Addr) | ||
| 183 | + if BConfig.Listen.AutoTLS { | ||
| 184 | + m := autocert.Manager{ | ||
| 185 | + Prompt: autocert.AcceptTOS, | ||
| 186 | + HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...), | ||
| 187 | + Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir), | ||
| 188 | + } | ||
| 189 | + app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} | ||
| 190 | + BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", "" | ||
| 191 | + } else if BConfig.Listen.EnableMutualHTTPS { | ||
| 192 | + pool := x509.NewCertPool() | ||
| 193 | + data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile) | ||
| 194 | + if err != nil { | ||
| 195 | + BeeLogger.Info("MutualHTTPS should provide TrustCaFile") | ||
| 196 | + return | ||
| 197 | + } | ||
| 198 | + pool.AppendCertsFromPEM(data) | ||
| 199 | + app.Server.TLSConfig = &tls.Config{ | ||
| 200 | + ClientCAs: pool, | ||
| 201 | + ClientAuth: tls.RequireAndVerifyClientCert, | ||
| 202 | + } | ||
| 203 | + } | ||
| 204 | + if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil { | ||
| 205 | + logs.Critical("ListenAndServeTLS: ", err) | ||
| 206 | + time.Sleep(100 * time.Microsecond) | ||
| 207 | + endRunning <- true | ||
| 208 | + } | ||
| 209 | + }() | ||
| 210 | + | ||
| 211 | + } | ||
| 212 | + if BConfig.Listen.EnableHTTP { | ||
| 213 | + go func() { | ||
| 214 | + app.Server.Addr = addr | ||
| 215 | + logs.Info("http server Running on http://%s", app.Server.Addr) | ||
| 216 | + if BConfig.Listen.ListenTCP4 { | ||
| 217 | + ln, err := net.Listen("tcp4", app.Server.Addr) | ||
| 218 | + if err != nil { | ||
| 219 | + logs.Critical("ListenAndServe: ", err) | ||
| 220 | + time.Sleep(100 * time.Microsecond) | ||
| 221 | + endRunning <- true | ||
| 222 | + return | ||
| 223 | + } | ||
| 224 | + if err = app.Server.Serve(ln); err != nil { | ||
| 225 | + logs.Critical("ListenAndServe: ", err) | ||
| 226 | + time.Sleep(100 * time.Microsecond) | ||
| 227 | + endRunning <- true | ||
| 228 | + return | ||
| 229 | + } | ||
| 230 | + } else { | ||
| 231 | + if err := app.Server.ListenAndServe(); err != nil { | ||
| 232 | + logs.Critical("ListenAndServe: ", err) | ||
| 233 | + time.Sleep(100 * time.Microsecond) | ||
| 234 | + endRunning <- true | ||
| 235 | + } | ||
| 236 | + } | ||
| 237 | + }() | ||
| 238 | + } | ||
| 239 | + <-endRunning | ||
| 240 | +} | ||
| 241 | + | ||
| 242 | +// Router adds a patterned controller handler to BeeApp. | ||
| 243 | +// it's an alias method of App.Router. | ||
| 244 | +// usage: | ||
| 245 | +// simple router | ||
| 246 | +// beego.Router("/admin", &admin.UserController{}) | ||
| 247 | +// beego.Router("/admin/index", &admin.ArticleController{}) | ||
| 248 | +// | ||
| 249 | +// regex router | ||
| 250 | +// | ||
| 251 | +// beego.Router("/api/:id([0-9]+)", &controllers.RController{}) | ||
| 252 | +// | ||
| 253 | +// custom rules | ||
| 254 | +// beego.Router("/api/list",&RestController{},"*:ListFood") | ||
| 255 | +// beego.Router("/api/create",&RestController{},"post:CreateFood") | ||
| 256 | +// beego.Router("/api/update",&RestController{},"put:UpdateFood") | ||
| 257 | +// beego.Router("/api/delete",&RestController{},"delete:DeleteFood") | ||
| 258 | +func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App { | ||
| 259 | + BeeApp.Handlers.Add(rootpath, c, mappingMethods...) | ||
| 260 | + return BeeApp | ||
| 261 | +} | ||
| 262 | + | ||
| 263 | +// UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful | ||
| 264 | +// in web applications that inherit most routes from a base webapp via the underscore | ||
| 265 | +// import, and aim to overwrite only certain paths. | ||
| 266 | +// The method parameter can be empty or "*" for all HTTP methods, or a particular | ||
| 267 | +// method type (e.g. "GET" or "POST") for selective removal. | ||
| 268 | +// | ||
| 269 | +// Usage (replace "GET" with "*" for all methods): | ||
| 270 | +// beego.UnregisterFixedRoute("/yourpreviouspath", "GET") | ||
| 271 | +// beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage") | ||
| 272 | +func UnregisterFixedRoute(fixedRoute string, method string) *App { | ||
| 273 | + subPaths := splitPath(fixedRoute) | ||
| 274 | + if method == "" || method == "*" { | ||
| 275 | + for m := range HTTPMETHOD { | ||
| 276 | + if _, ok := BeeApp.Handlers.routers[m]; !ok { | ||
| 277 | + continue | ||
| 278 | + } | ||
| 279 | + if BeeApp.Handlers.routers[m].prefix == strings.Trim(fixedRoute, "/ ") { | ||
| 280 | + findAndRemoveSingleTree(BeeApp.Handlers.routers[m]) | ||
| 281 | + continue | ||
| 282 | + } | ||
| 283 | + findAndRemoveTree(subPaths, BeeApp.Handlers.routers[m], m) | ||
| 284 | + } | ||
| 285 | + return BeeApp | ||
| 286 | + } | ||
| 287 | + // Single HTTP method | ||
| 288 | + um := strings.ToUpper(method) | ||
| 289 | + if _, ok := BeeApp.Handlers.routers[um]; ok { | ||
| 290 | + if BeeApp.Handlers.routers[um].prefix == strings.Trim(fixedRoute, "/ ") { | ||
| 291 | + findAndRemoveSingleTree(BeeApp.Handlers.routers[um]) | ||
| 292 | + return BeeApp | ||
| 293 | + } | ||
| 294 | + findAndRemoveTree(subPaths, BeeApp.Handlers.routers[um], um) | ||
| 295 | + } | ||
| 296 | + return BeeApp | ||
| 297 | +} | ||
| 298 | + | ||
| 299 | +func findAndRemoveTree(paths []string, entryPointTree *Tree, method string) { | ||
| 300 | + for i := range entryPointTree.fixrouters { | ||
| 301 | + if entryPointTree.fixrouters[i].prefix == paths[0] { | ||
| 302 | + if len(paths) == 1 { | ||
| 303 | + if len(entryPointTree.fixrouters[i].fixrouters) > 0 { | ||
| 304 | + // If the route had children subtrees, remove just the functional leaf, | ||
| 305 | + // to allow children to function as before | ||
| 306 | + if len(entryPointTree.fixrouters[i].leaves) > 0 { | ||
| 307 | + entryPointTree.fixrouters[i].leaves[0] = nil | ||
| 308 | + entryPointTree.fixrouters[i].leaves = entryPointTree.fixrouters[i].leaves[1:] | ||
| 309 | + } | ||
| 310 | + } else { | ||
| 311 | + // Remove the *Tree from the fixrouters slice | ||
| 312 | + entryPointTree.fixrouters[i] = nil | ||
| 313 | + | ||
| 314 | + if i == len(entryPointTree.fixrouters)-1 { | ||
| 315 | + entryPointTree.fixrouters = entryPointTree.fixrouters[:i] | ||
| 316 | + } else { | ||
| 317 | + entryPointTree.fixrouters = append(entryPointTree.fixrouters[:i], entryPointTree.fixrouters[i+1:len(entryPointTree.fixrouters)]...) | ||
| 318 | + } | ||
| 319 | + } | ||
| 320 | + return | ||
| 321 | + } | ||
| 322 | + findAndRemoveTree(paths[1:], entryPointTree.fixrouters[i], method) | ||
| 323 | + } | ||
| 324 | + } | ||
| 325 | +} | ||
| 326 | + | ||
| 327 | +func findAndRemoveSingleTree(entryPointTree *Tree) { | ||
| 328 | + if entryPointTree == nil { | ||
| 329 | + return | ||
| 330 | + } | ||
| 331 | + if len(entryPointTree.fixrouters) > 0 { | ||
| 332 | + // If the route had children subtrees, remove just the functional leaf, | ||
| 333 | + // to allow children to function as before | ||
| 334 | + if len(entryPointTree.leaves) > 0 { | ||
| 335 | + entryPointTree.leaves[0] = nil | ||
| 336 | + entryPointTree.leaves = entryPointTree.leaves[1:] | ||
| 337 | + } | ||
| 338 | + } | ||
| 339 | +} | ||
| 340 | + | ||
| 341 | +// Include will generate router file in the router/xxx.go from the controller's comments | ||
| 342 | +// usage: | ||
| 343 | +// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) | ||
| 344 | +// type BankAccount struct{ | ||
| 345 | +// beego.Controller | ||
| 346 | +// } | ||
| 347 | +// | ||
| 348 | +// register the function | ||
| 349 | +// func (b *BankAccount)Mapping(){ | ||
| 350 | +// b.Mapping("ShowAccount" , b.ShowAccount) | ||
| 351 | +// b.Mapping("ModifyAccount", b.ModifyAccount) | ||
| 352 | +//} | ||
| 353 | +// | ||
| 354 | +// //@router /account/:id [get] | ||
| 355 | +// func (b *BankAccount) ShowAccount(){ | ||
| 356 | +// //logic | ||
| 357 | +// } | ||
| 358 | +// | ||
| 359 | +// | ||
| 360 | +// //@router /account/:id [post] | ||
| 361 | +// func (b *BankAccount) ModifyAccount(){ | ||
| 362 | +// //logic | ||
| 363 | +// } | ||
| 364 | +// | ||
| 365 | +// the comments @router url methodlist | ||
| 366 | +// url support all the function Router's pattern | ||
| 367 | +// methodlist [get post head put delete options *] | ||
| 368 | +func Include(cList ...ControllerInterface) *App { | ||
| 369 | + BeeApp.Handlers.Include(cList...) | ||
| 370 | + return BeeApp | ||
| 371 | +} | ||
| 372 | + | ||
| 373 | +// RESTRouter adds a restful controller handler to BeeApp. | ||
| 374 | +// its' controller implements beego.ControllerInterface and | ||
| 375 | +// defines a param "pattern/:objectId" to visit each resource. | ||
| 376 | +func RESTRouter(rootpath string, c ControllerInterface) *App { | ||
| 377 | + Router(rootpath, c) | ||
| 378 | + Router(path.Join(rootpath, ":objectId"), c) | ||
| 379 | + return BeeApp | ||
| 380 | +} | ||
| 381 | + | ||
| 382 | +// AutoRouter adds defined controller handler to BeeApp. | ||
| 383 | +// it's same to App.AutoRouter. | ||
| 384 | +// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page, | ||
| 385 | +// visit the url /main/list to exec List function or /main/page to exec Page function. | ||
| 386 | +func AutoRouter(c ControllerInterface) *App { | ||
| 387 | + BeeApp.Handlers.AddAuto(c) | ||
| 388 | + return BeeApp | ||
| 389 | +} | ||
| 390 | + | ||
| 391 | +// AutoPrefix adds controller handler to BeeApp with prefix. | ||
| 392 | +// it's same to App.AutoRouterWithPrefix. | ||
| 393 | +// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page, | ||
| 394 | +// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function. | ||
| 395 | +func AutoPrefix(prefix string, c ControllerInterface) *App { | ||
| 396 | + BeeApp.Handlers.AddAutoPrefix(prefix, c) | ||
| 397 | + return BeeApp | ||
| 398 | +} | ||
| 399 | + | ||
| 400 | +// Get used to register router for Get method | ||
| 401 | +// usage: | ||
| 402 | +// beego.Get("/", func(ctx *context.Context){ | ||
| 403 | +// ctx.Output.Body("hello world") | ||
| 404 | +// }) | ||
| 405 | +func Get(rootpath string, f FilterFunc) *App { | ||
| 406 | + BeeApp.Handlers.Get(rootpath, f) | ||
| 407 | + return BeeApp | ||
| 408 | +} | ||
| 409 | + | ||
| 410 | +// Post used to register router for Post method | ||
| 411 | +// usage: | ||
| 412 | +// beego.Post("/api", func(ctx *context.Context){ | ||
| 413 | +// ctx.Output.Body("hello world") | ||
| 414 | +// }) | ||
| 415 | +func Post(rootpath string, f FilterFunc) *App { | ||
| 416 | + BeeApp.Handlers.Post(rootpath, f) | ||
| 417 | + return BeeApp | ||
| 418 | +} | ||
| 419 | + | ||
| 420 | +// Delete used to register router for Delete method | ||
| 421 | +// usage: | ||
| 422 | +// beego.Delete("/api", func(ctx *context.Context){ | ||
| 423 | +// ctx.Output.Body("hello world") | ||
| 424 | +// }) | ||
| 425 | +func Delete(rootpath string, f FilterFunc) *App { | ||
| 426 | + BeeApp.Handlers.Delete(rootpath, f) | ||
| 427 | + return BeeApp | ||
| 428 | +} | ||
| 429 | + | ||
| 430 | +// Put used to register router for Put method | ||
| 431 | +// usage: | ||
| 432 | +// beego.Put("/api", func(ctx *context.Context){ | ||
| 433 | +// ctx.Output.Body("hello world") | ||
| 434 | +// }) | ||
| 435 | +func Put(rootpath string, f FilterFunc) *App { | ||
| 436 | + BeeApp.Handlers.Put(rootpath, f) | ||
| 437 | + return BeeApp | ||
| 438 | +} | ||
| 439 | + | ||
| 440 | +// Head used to register router for Head method | ||
| 441 | +// usage: | ||
| 442 | +// beego.Head("/api", func(ctx *context.Context){ | ||
| 443 | +// ctx.Output.Body("hello world") | ||
| 444 | +// }) | ||
| 445 | +func Head(rootpath string, f FilterFunc) *App { | ||
| 446 | + BeeApp.Handlers.Head(rootpath, f) | ||
| 447 | + return BeeApp | ||
| 448 | +} | ||
| 449 | + | ||
| 450 | +// Options used to register router for Options method | ||
| 451 | +// usage: | ||
| 452 | +// beego.Options("/api", func(ctx *context.Context){ | ||
| 453 | +// ctx.Output.Body("hello world") | ||
| 454 | +// }) | ||
| 455 | +func Options(rootpath string, f FilterFunc) *App { | ||
| 456 | + BeeApp.Handlers.Options(rootpath, f) | ||
| 457 | + return BeeApp | ||
| 458 | +} | ||
| 459 | + | ||
| 460 | +// Patch used to register router for Patch method | ||
| 461 | +// usage: | ||
| 462 | +// beego.Patch("/api", func(ctx *context.Context){ | ||
| 463 | +// ctx.Output.Body("hello world") | ||
| 464 | +// }) | ||
| 465 | +func Patch(rootpath string, f FilterFunc) *App { | ||
| 466 | + BeeApp.Handlers.Patch(rootpath, f) | ||
| 467 | + return BeeApp | ||
| 468 | +} | ||
| 469 | + | ||
| 470 | +// Any used to register router for all methods | ||
| 471 | +// usage: | ||
| 472 | +// beego.Any("/api", func(ctx *context.Context){ | ||
| 473 | +// ctx.Output.Body("hello world") | ||
| 474 | +// }) | ||
| 475 | +func Any(rootpath string, f FilterFunc) *App { | ||
| 476 | + BeeApp.Handlers.Any(rootpath, f) | ||
| 477 | + return BeeApp | ||
| 478 | +} | ||
| 479 | + | ||
| 480 | +// Handler used to register a Handler router | ||
| 481 | +// usage: | ||
| 482 | +// beego.Handler("/api", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { | ||
| 483 | +// fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) | ||
| 484 | +// })) | ||
| 485 | +func Handler(rootpath string, h http.Handler, options ...interface{}) *App { | ||
| 486 | + BeeApp.Handlers.Handler(rootpath, h, options...) | ||
| 487 | + return BeeApp | ||
| 488 | +} | ||
| 489 | + | ||
| 490 | +// InsertFilter adds a FilterFunc with pattern condition and action constant. | ||
| 491 | +// The pos means action constant including | ||
| 492 | +// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter. | ||
| 493 | +// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute) | ||
| 494 | +func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App { | ||
| 495 | + BeeApp.Handlers.InsertFilter(pattern, pos, filter, params...) | ||
| 496 | + return BeeApp | ||
| 497 | +} |
vendor/github.com/astaxie/beego/beego.go
0 → 100644
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +package beego | ||
| 16 | + | ||
| 17 | +import ( | ||
| 18 | + "os" | ||
| 19 | + "path/filepath" | ||
| 20 | + "strconv" | ||
| 21 | + "strings" | ||
| 22 | +) | ||
| 23 | + | ||
| 24 | +const ( | ||
| 25 | + // VERSION represent beego web framework version. | ||
| 26 | + VERSION = "1.10.0" | ||
| 27 | + | ||
| 28 | + // DEV is for develop | ||
| 29 | + DEV = "dev" | ||
| 30 | + // PROD is for production | ||
| 31 | + PROD = "prod" | ||
| 32 | +) | ||
| 33 | + | ||
| 34 | +//hook function to run | ||
| 35 | +type hookfunc func() error | ||
| 36 | + | ||
| 37 | +var ( | ||
| 38 | + hooks = make([]hookfunc, 0) //hook function slice to store the hookfunc | ||
| 39 | +) | ||
| 40 | + | ||
| 41 | +// AddAPPStartHook is used to register the hookfunc | ||
| 42 | +// The hookfuncs will run in beego.Run() | ||
| 43 | +// such as initiating session , starting middleware , building template, starting admin control and so on. | ||
| 44 | +func AddAPPStartHook(hf ...hookfunc) { | ||
| 45 | + hooks = append(hooks, hf...) | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +// Run beego application. | ||
| 49 | +// beego.Run() default run on HttpPort | ||
| 50 | +// beego.Run("localhost") | ||
| 51 | +// beego.Run(":8089") | ||
| 52 | +// beego.Run("127.0.0.1:8089") | ||
| 53 | +func Run(params ...string) { | ||
| 54 | + | ||
| 55 | + initBeforeHTTPRun() | ||
| 56 | + | ||
| 57 | + if len(params) > 0 && params[0] != "" { | ||
| 58 | + strs := strings.Split(params[0], ":") | ||
| 59 | + if len(strs) > 0 && strs[0] != "" { | ||
| 60 | + BConfig.Listen.HTTPAddr = strs[0] | ||
| 61 | + } | ||
| 62 | + if len(strs) > 1 && strs[1] != "" { | ||
| 63 | + BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + BConfig.Listen.Domains = params | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + BeeApp.Run() | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +// RunWithMiddleWares Run beego application with middlewares. | ||
| 73 | +func RunWithMiddleWares(addr string, mws ...MiddleWare) { | ||
| 74 | + initBeforeHTTPRun() | ||
| 75 | + | ||
| 76 | + strs := strings.Split(addr, ":") | ||
| 77 | + if len(strs) > 0 && strs[0] != "" { | ||
| 78 | + BConfig.Listen.HTTPAddr = strs[0] | ||
| 79 | + BConfig.Listen.Domains = []string{strs[0]} | ||
| 80 | + } | ||
| 81 | + if len(strs) > 1 && strs[1] != "" { | ||
| 82 | + BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + BeeApp.Run(mws...) | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +func initBeforeHTTPRun() { | ||
| 89 | + //init hooks | ||
| 90 | + AddAPPStartHook( | ||
| 91 | + registerMime, | ||
| 92 | + registerDefaultErrorHandler, | ||
| 93 | + registerSession, | ||
| 94 | + registerTemplate, | ||
| 95 | + registerAdmin, | ||
| 96 | + registerGzip, | ||
| 97 | + ) | ||
| 98 | + | ||
| 99 | + for _, hk := range hooks { | ||
| 100 | + if err := hk(); err != nil { | ||
| 101 | + panic(err) | ||
| 102 | + } | ||
| 103 | + } | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +// TestBeegoInit is for test package init | ||
| 107 | +func TestBeegoInit(ap string) { | ||
| 108 | + path := filepath.Join(ap, "conf", "app.conf") | ||
| 109 | + os.Chdir(ap) | ||
| 110 | + InitBeegoBeforeTest(path) | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +// InitBeegoBeforeTest is for test package init | ||
| 114 | +func InitBeegoBeforeTest(appConfigPath string) { | ||
| 115 | + if err := LoadAppConfig(appConfigProvider, appConfigPath); err != nil { | ||
| 116 | + panic(err) | ||
| 117 | + } | ||
| 118 | + BConfig.RunMode = "test" | ||
| 119 | + initBeforeHTTPRun() | ||
| 120 | +} |
vendor/github.com/astaxie/beego/config.go
0 → 100644
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +package beego | ||
| 16 | + | ||
| 17 | +import ( | ||
| 18 | + "fmt" | ||
| 19 | + "os" | ||
| 20 | + "path/filepath" | ||
| 21 | + "reflect" | ||
| 22 | + "runtime" | ||
| 23 | + "strings" | ||
| 24 | + | ||
| 25 | + "github.com/astaxie/beego/config" | ||
| 26 | + "github.com/astaxie/beego/context" | ||
| 27 | + "github.com/astaxie/beego/logs" | ||
| 28 | + "github.com/astaxie/beego/session" | ||
| 29 | + "github.com/astaxie/beego/utils" | ||
| 30 | +) | ||
| 31 | + | ||
| 32 | +// Config is the main struct for BConfig | ||
| 33 | +type Config struct { | ||
| 34 | + AppName string //Application name | ||
| 35 | + RunMode string //Running Mode: dev | prod | ||
| 36 | + RouterCaseSensitive bool | ||
| 37 | + ServerName string | ||
| 38 | + RecoverPanic bool | ||
| 39 | + RecoverFunc func(*context.Context) | ||
| 40 | + CopyRequestBody bool | ||
| 41 | + EnableGzip bool | ||
| 42 | + MaxMemory int64 | ||
| 43 | + EnableErrorsShow bool | ||
| 44 | + EnableErrorsRender bool | ||
| 45 | + Listen Listen | ||
| 46 | + WebConfig WebConfig | ||
| 47 | + Log LogConfig | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +// Listen holds for http and https related config | ||
| 51 | +type Listen struct { | ||
| 52 | + Graceful bool // Graceful means use graceful module to start the server | ||
| 53 | + ServerTimeOut int64 | ||
| 54 | + ListenTCP4 bool | ||
| 55 | + EnableHTTP bool | ||
| 56 | + HTTPAddr string | ||
| 57 | + HTTPPort int | ||
| 58 | + AutoTLS bool | ||
| 59 | + Domains []string | ||
| 60 | + TLSCacheDir string | ||
| 61 | + EnableHTTPS bool | ||
| 62 | + EnableMutualHTTPS bool | ||
| 63 | + HTTPSAddr string | ||
| 64 | + HTTPSPort int | ||
| 65 | + HTTPSCertFile string | ||
| 66 | + HTTPSKeyFile string | ||
| 67 | + TrustCaFile string | ||
| 68 | + EnableAdmin bool | ||
| 69 | + AdminAddr string | ||
| 70 | + AdminPort int | ||
| 71 | + EnableFcgi bool | ||
| 72 | + EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +// WebConfig holds web related config | ||
| 76 | +type WebConfig struct { | ||
| 77 | + AutoRender bool | ||
| 78 | + EnableDocs bool | ||
| 79 | + FlashName string | ||
| 80 | + FlashSeparator string | ||
| 81 | + DirectoryIndex bool | ||
| 82 | + StaticDir map[string]string | ||
| 83 | + StaticExtensionsToGzip []string | ||
| 84 | + TemplateLeft string | ||
| 85 | + TemplateRight string | ||
| 86 | + ViewsPath string | ||
| 87 | + EnableXSRF bool | ||
| 88 | + XSRFKey string | ||
| 89 | + XSRFExpire int | ||
| 90 | + Session SessionConfig | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +// SessionConfig holds session related config | ||
| 94 | +type SessionConfig struct { | ||
| 95 | + SessionOn bool | ||
| 96 | + SessionProvider string | ||
| 97 | + SessionName string | ||
| 98 | + SessionGCMaxLifetime int64 | ||
| 99 | + SessionProviderConfig string | ||
| 100 | + SessionCookieLifeTime int | ||
| 101 | + SessionAutoSetCookie bool | ||
| 102 | + SessionDomain string | ||
| 103 | + SessionDisableHTTPOnly bool // used to allow for cross domain cookies/javascript cookies. | ||
| 104 | + SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers | ||
| 105 | + SessionNameInHTTPHeader string | ||
| 106 | + SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params | ||
| 107 | +} | ||
| 108 | + | ||
| 109 | +// LogConfig holds Log related config | ||
| 110 | +type LogConfig struct { | ||
| 111 | + AccessLogs bool | ||
| 112 | + EnableStaticLogs bool //log static files requests default: false | ||
| 113 | + AccessLogsFormat string //access log format: JSON_FORMAT, APACHE_FORMAT or empty string | ||
| 114 | + FileLineNum bool | ||
| 115 | + Outputs map[string]string // Store Adaptor : config | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +var ( | ||
| 119 | + // BConfig is the default config for Application | ||
| 120 | + BConfig *Config | ||
| 121 | + // AppConfig is the instance of Config, store the config information from file | ||
| 122 | + AppConfig *beegoAppConfig | ||
| 123 | + // AppPath is the absolute path to the app | ||
| 124 | + AppPath string | ||
| 125 | + // GlobalSessions is the instance for the session manager | ||
| 126 | + GlobalSessions *session.Manager | ||
| 127 | + | ||
| 128 | + // appConfigPath is the path to the config files | ||
| 129 | + appConfigPath string | ||
| 130 | + // appConfigProvider is the provider for the config, default is ini | ||
| 131 | + appConfigProvider = "ini" | ||
| 132 | +) | ||
| 133 | + | ||
| 134 | +func init() { | ||
| 135 | + BConfig = newBConfig() | ||
| 136 | + var err error | ||
| 137 | + if AppPath, err = filepath.Abs(filepath.Dir(os.Args[0])); err != nil { | ||
| 138 | + panic(err) | ||
| 139 | + } | ||
| 140 | + workPath, err := os.Getwd() | ||
| 141 | + if err != nil { | ||
| 142 | + panic(err) | ||
| 143 | + } | ||
| 144 | + var filename = "app.conf" | ||
| 145 | + if os.Getenv("BEEGO_RUNMODE") != "" { | ||
| 146 | + filename = os.Getenv("BEEGO_RUNMODE") + ".app.conf" | ||
| 147 | + } | ||
| 148 | + appConfigPath = filepath.Join(workPath, "conf", filename) | ||
| 149 | + if !utils.FileExists(appConfigPath) { | ||
| 150 | + appConfigPath = filepath.Join(AppPath, "conf", filename) | ||
| 151 | + if !utils.FileExists(appConfigPath) { | ||
| 152 | + AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()} | ||
| 153 | + return | ||
| 154 | + } | ||
| 155 | + } | ||
| 156 | + if err = parseConfig(appConfigPath); err != nil { | ||
| 157 | + panic(err) | ||
| 158 | + } | ||
| 159 | +} | ||
| 160 | + | ||
| 161 | +func recoverPanic(ctx *context.Context) { | ||
| 162 | + if err := recover(); err != nil { | ||
| 163 | + if err == ErrAbort { | ||
| 164 | + return | ||
| 165 | + } | ||
| 166 | + if !BConfig.RecoverPanic { | ||
| 167 | + panic(err) | ||
| 168 | + } | ||
| 169 | + if BConfig.EnableErrorsShow { | ||
| 170 | + if _, ok := ErrorMaps[fmt.Sprint(err)]; ok { | ||
| 171 | + exception(fmt.Sprint(err), ctx) | ||
| 172 | + return | ||
| 173 | + } | ||
| 174 | + } | ||
| 175 | + var stack string | ||
| 176 | + logs.Critical("the request url is ", ctx.Input.URL()) | ||
| 177 | + logs.Critical("Handler crashed with error", err) | ||
| 178 | + for i := 1; ; i++ { | ||
| 179 | + _, file, line, ok := runtime.Caller(i) | ||
| 180 | + if !ok { | ||
| 181 | + break | ||
| 182 | + } | ||
| 183 | + logs.Critical(fmt.Sprintf("%s:%d", file, line)) | ||
| 184 | + stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line)) | ||
| 185 | + } | ||
| 186 | + if BConfig.RunMode == DEV && BConfig.EnableErrorsRender { | ||
| 187 | + showErr(err, ctx, stack) | ||
| 188 | + } | ||
| 189 | + if ctx.Output.Status != 0 { | ||
| 190 | + ctx.ResponseWriter.WriteHeader(ctx.Output.Status) | ||
| 191 | + } else { | ||
| 192 | + ctx.ResponseWriter.WriteHeader(500) | ||
| 193 | + } | ||
| 194 | + } | ||
| 195 | +} | ||
| 196 | + | ||
| 197 | +func newBConfig() *Config { | ||
| 198 | + return &Config{ | ||
| 199 | + AppName: "beego", | ||
| 200 | + RunMode: PROD, | ||
| 201 | + RouterCaseSensitive: true, | ||
| 202 | + ServerName: "beegoServer:" + VERSION, | ||
| 203 | + RecoverPanic: true, | ||
| 204 | + RecoverFunc: recoverPanic, | ||
| 205 | + CopyRequestBody: false, | ||
| 206 | + EnableGzip: false, | ||
| 207 | + MaxMemory: 1 << 26, //64MB | ||
| 208 | + EnableErrorsShow: true, | ||
| 209 | + EnableErrorsRender: true, | ||
| 210 | + Listen: Listen{ | ||
| 211 | + Graceful: false, | ||
| 212 | + ServerTimeOut: 0, | ||
| 213 | + ListenTCP4: false, | ||
| 214 | + EnableHTTP: true, | ||
| 215 | + AutoTLS: false, | ||
| 216 | + Domains: []string{}, | ||
| 217 | + TLSCacheDir: ".", | ||
| 218 | + HTTPAddr: "", | ||
| 219 | + HTTPPort: 8080, | ||
| 220 | + EnableHTTPS: false, | ||
| 221 | + HTTPSAddr: "", | ||
| 222 | + HTTPSPort: 10443, | ||
| 223 | + HTTPSCertFile: "", | ||
| 224 | + HTTPSKeyFile: "", | ||
| 225 | + EnableAdmin: false, | ||
| 226 | + AdminAddr: "", | ||
| 227 | + AdminPort: 8088, | ||
| 228 | + EnableFcgi: false, | ||
| 229 | + EnableStdIo: false, | ||
| 230 | + }, | ||
| 231 | + WebConfig: WebConfig{ | ||
| 232 | + AutoRender: true, | ||
| 233 | + EnableDocs: false, | ||
| 234 | + FlashName: "BEEGO_FLASH", | ||
| 235 | + FlashSeparator: "BEEGOFLASH", | ||
| 236 | + DirectoryIndex: false, | ||
| 237 | + StaticDir: map[string]string{"/static": "static"}, | ||
| 238 | + StaticExtensionsToGzip: []string{".css", ".js"}, | ||
| 239 | + TemplateLeft: "{{", | ||
| 240 | + TemplateRight: "}}", | ||
| 241 | + ViewsPath: "views", | ||
| 242 | + EnableXSRF: false, | ||
| 243 | + XSRFKey: "beegoxsrf", | ||
| 244 | + XSRFExpire: 0, | ||
| 245 | + Session: SessionConfig{ | ||
| 246 | + SessionOn: false, | ||
| 247 | + SessionProvider: "memory", | ||
| 248 | + SessionName: "beegosessionID", | ||
| 249 | + SessionGCMaxLifetime: 3600, | ||
| 250 | + SessionProviderConfig: "", | ||
| 251 | + SessionDisableHTTPOnly: false, | ||
| 252 | + SessionCookieLifeTime: 0, //set cookie default is the browser life | ||
| 253 | + SessionAutoSetCookie: true, | ||
| 254 | + SessionDomain: "", | ||
| 255 | + SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers | ||
| 256 | + SessionNameInHTTPHeader: "Beegosessionid", | ||
| 257 | + SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params | ||
| 258 | + }, | ||
| 259 | + }, | ||
| 260 | + Log: LogConfig{ | ||
| 261 | + AccessLogs: false, | ||
| 262 | + EnableStaticLogs: false, | ||
| 263 | + AccessLogsFormat: "APACHE_FORMAT", | ||
| 264 | + FileLineNum: true, | ||
| 265 | + Outputs: map[string]string{"console": ""}, | ||
| 266 | + }, | ||
| 267 | + } | ||
| 268 | +} | ||
| 269 | + | ||
| 270 | +// now only support ini, next will support json. | ||
| 271 | +func parseConfig(appConfigPath string) (err error) { | ||
| 272 | + AppConfig, err = newAppConfig(appConfigProvider, appConfigPath) | ||
| 273 | + if err != nil { | ||
| 274 | + return err | ||
| 275 | + } | ||
| 276 | + return assignConfig(AppConfig) | ||
| 277 | +} | ||
| 278 | + | ||
| 279 | +func assignConfig(ac config.Configer) error { | ||
| 280 | + for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} { | ||
| 281 | + assignSingleConfig(i, ac) | ||
| 282 | + } | ||
| 283 | + // set the run mode first | ||
| 284 | + if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" { | ||
| 285 | + BConfig.RunMode = envRunMode | ||
| 286 | + } else if runMode := ac.String("RunMode"); runMode != "" { | ||
| 287 | + BConfig.RunMode = runMode | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + if sd := ac.String("StaticDir"); sd != "" { | ||
| 291 | + BConfig.WebConfig.StaticDir = map[string]string{} | ||
| 292 | + sds := strings.Fields(sd) | ||
| 293 | + for _, v := range sds { | ||
| 294 | + if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 { | ||
| 295 | + BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[1] | ||
| 296 | + } else { | ||
| 297 | + BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[0] | ||
| 298 | + } | ||
| 299 | + } | ||
| 300 | + } | ||
| 301 | + | ||
| 302 | + if sgz := ac.String("StaticExtensionsToGzip"); sgz != "" { | ||
| 303 | + extensions := strings.Split(sgz, ",") | ||
| 304 | + fileExts := []string{} | ||
| 305 | + for _, ext := range extensions { | ||
| 306 | + ext = strings.TrimSpace(ext) | ||
| 307 | + if ext == "" { | ||
| 308 | + continue | ||
| 309 | + } | ||
| 310 | + if !strings.HasPrefix(ext, ".") { | ||
| 311 | + ext = "." + ext | ||
| 312 | + } | ||
| 313 | + fileExts = append(fileExts, ext) | ||
| 314 | + } | ||
| 315 | + if len(fileExts) > 0 { | ||
| 316 | + BConfig.WebConfig.StaticExtensionsToGzip = fileExts | ||
| 317 | + } | ||
| 318 | + } | ||
| 319 | + | ||
| 320 | + if lo := ac.String("LogOutputs"); lo != "" { | ||
| 321 | + // if lo is not nil or empty | ||
| 322 | + // means user has set his own LogOutputs | ||
| 323 | + // clear the default setting to BConfig.Log.Outputs | ||
| 324 | + BConfig.Log.Outputs = make(map[string]string) | ||
| 325 | + los := strings.Split(lo, ";") | ||
| 326 | + for _, v := range los { | ||
| 327 | + if logType2Config := strings.SplitN(v, ",", 2); len(logType2Config) == 2 { | ||
| 328 | + BConfig.Log.Outputs[logType2Config[0]] = logType2Config[1] | ||
| 329 | + } else { | ||
| 330 | + continue | ||
| 331 | + } | ||
| 332 | + } | ||
| 333 | + } | ||
| 334 | + | ||
| 335 | + //init log | ||
| 336 | + logs.Reset() | ||
| 337 | + for adaptor, config := range BConfig.Log.Outputs { | ||
| 338 | + err := logs.SetLogger(adaptor, config) | ||
| 339 | + if err != nil { | ||
| 340 | + fmt.Fprintln(os.Stderr, fmt.Sprintf("%s with the config %q got err:%s", adaptor, config, err.Error())) | ||
| 341 | + } | ||
| 342 | + } | ||
| 343 | + logs.SetLogFuncCall(BConfig.Log.FileLineNum) | ||
| 344 | + | ||
| 345 | + return nil | ||
| 346 | +} | ||
| 347 | + | ||
| 348 | +func assignSingleConfig(p interface{}, ac config.Configer) { | ||
| 349 | + pt := reflect.TypeOf(p) | ||
| 350 | + if pt.Kind() != reflect.Ptr { | ||
| 351 | + return | ||
| 352 | + } | ||
| 353 | + pt = pt.Elem() | ||
| 354 | + if pt.Kind() != reflect.Struct { | ||
| 355 | + return | ||
| 356 | + } | ||
| 357 | + pv := reflect.ValueOf(p).Elem() | ||
| 358 | + | ||
| 359 | + for i := 0; i < pt.NumField(); i++ { | ||
| 360 | + pf := pv.Field(i) | ||
| 361 | + if !pf.CanSet() { | ||
| 362 | + continue | ||
| 363 | + } | ||
| 364 | + name := pt.Field(i).Name | ||
| 365 | + switch pf.Kind() { | ||
| 366 | + case reflect.String: | ||
| 367 | + pf.SetString(ac.DefaultString(name, pf.String())) | ||
| 368 | + case reflect.Int, reflect.Int64: | ||
| 369 | + pf.SetInt(ac.DefaultInt64(name, pf.Int())) | ||
| 370 | + case reflect.Bool: | ||
| 371 | + pf.SetBool(ac.DefaultBool(name, pf.Bool())) | ||
| 372 | + case reflect.Struct: | ||
| 373 | + default: | ||
| 374 | + //do nothing here | ||
| 375 | + } | ||
| 376 | + } | ||
| 377 | + | ||
| 378 | +} | ||
| 379 | + | ||
| 380 | +// LoadAppConfig allow developer to apply a config file | ||
| 381 | +func LoadAppConfig(adapterName, configPath string) error { | ||
| 382 | + absConfigPath, err := filepath.Abs(configPath) | ||
| 383 | + if err != nil { | ||
| 384 | + return err | ||
| 385 | + } | ||
| 386 | + | ||
| 387 | + if !utils.FileExists(absConfigPath) { | ||
| 388 | + return fmt.Errorf("the target config file: %s don't exist", configPath) | ||
| 389 | + } | ||
| 390 | + | ||
| 391 | + appConfigPath = absConfigPath | ||
| 392 | + appConfigProvider = adapterName | ||
| 393 | + | ||
| 394 | + return parseConfig(appConfigPath) | ||
| 395 | +} | ||
| 396 | + | ||
| 397 | +type beegoAppConfig struct { | ||
| 398 | + innerConfig config.Configer | ||
| 399 | +} | ||
| 400 | + | ||
| 401 | +func newAppConfig(appConfigProvider, appConfigPath string) (*beegoAppConfig, error) { | ||
| 402 | + ac, err := config.NewConfig(appConfigProvider, appConfigPath) | ||
| 403 | + if err != nil { | ||
| 404 | + return nil, err | ||
| 405 | + } | ||
| 406 | + return &beegoAppConfig{ac}, nil | ||
| 407 | +} | ||
| 408 | + | ||
| 409 | +func (b *beegoAppConfig) Set(key, val string) error { | ||
| 410 | + if err := b.innerConfig.Set(BConfig.RunMode+"::"+key, val); err != nil { | ||
| 411 | + return err | ||
| 412 | + } | ||
| 413 | + return b.innerConfig.Set(key, val) | ||
| 414 | +} | ||
| 415 | + | ||
| 416 | +func (b *beegoAppConfig) String(key string) string { | ||
| 417 | + if v := b.innerConfig.String(BConfig.RunMode + "::" + key); v != "" { | ||
| 418 | + return v | ||
| 419 | + } | ||
| 420 | + return b.innerConfig.String(key) | ||
| 421 | +} | ||
| 422 | + | ||
| 423 | +func (b *beegoAppConfig) Strings(key string) []string { | ||
| 424 | + if v := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 { | ||
| 425 | + return v | ||
| 426 | + } | ||
| 427 | + return b.innerConfig.Strings(key) | ||
| 428 | +} | ||
| 429 | + | ||
| 430 | +func (b *beegoAppConfig) Int(key string) (int, error) { | ||
| 431 | + if v, err := b.innerConfig.Int(BConfig.RunMode + "::" + key); err == nil { | ||
| 432 | + return v, nil | ||
| 433 | + } | ||
| 434 | + return b.innerConfig.Int(key) | ||
| 435 | +} | ||
| 436 | + | ||
| 437 | +func (b *beegoAppConfig) Int64(key string) (int64, error) { | ||
| 438 | + if v, err := b.innerConfig.Int64(BConfig.RunMode + "::" + key); err == nil { | ||
| 439 | + return v, nil | ||
| 440 | + } | ||
| 441 | + return b.innerConfig.Int64(key) | ||
| 442 | +} | ||
| 443 | + | ||
| 444 | +func (b *beegoAppConfig) Bool(key string) (bool, error) { | ||
| 445 | + if v, err := b.innerConfig.Bool(BConfig.RunMode + "::" + key); err == nil { | ||
| 446 | + return v, nil | ||
| 447 | + } | ||
| 448 | + return b.innerConfig.Bool(key) | ||
| 449 | +} | ||
| 450 | + | ||
| 451 | +func (b *beegoAppConfig) Float(key string) (float64, error) { | ||
| 452 | + if v, err := b.innerConfig.Float(BConfig.RunMode + "::" + key); err == nil { | ||
| 453 | + return v, nil | ||
| 454 | + } | ||
| 455 | + return b.innerConfig.Float(key) | ||
| 456 | +} | ||
| 457 | + | ||
| 458 | +func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string { | ||
| 459 | + if v := b.String(key); v != "" { | ||
| 460 | + return v | ||
| 461 | + } | ||
| 462 | + return defaultVal | ||
| 463 | +} | ||
| 464 | + | ||
| 465 | +func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string { | ||
| 466 | + if v := b.Strings(key); len(v) != 0 { | ||
| 467 | + return v | ||
| 468 | + } | ||
| 469 | + return defaultVal | ||
| 470 | +} | ||
| 471 | + | ||
| 472 | +func (b *beegoAppConfig) DefaultInt(key string, defaultVal int) int { | ||
| 473 | + if v, err := b.Int(key); err == nil { | ||
| 474 | + return v | ||
| 475 | + } | ||
| 476 | + return defaultVal | ||
| 477 | +} | ||
| 478 | + | ||
| 479 | +func (b *beegoAppConfig) DefaultInt64(key string, defaultVal int64) int64 { | ||
| 480 | + if v, err := b.Int64(key); err == nil { | ||
| 481 | + return v | ||
| 482 | + } | ||
| 483 | + return defaultVal | ||
| 484 | +} | ||
| 485 | + | ||
| 486 | +func (b *beegoAppConfig) DefaultBool(key string, defaultVal bool) bool { | ||
| 487 | + if v, err := b.Bool(key); err == nil { | ||
| 488 | + return v | ||
| 489 | + } | ||
| 490 | + return defaultVal | ||
| 491 | +} | ||
| 492 | + | ||
| 493 | +func (b *beegoAppConfig) DefaultFloat(key string, defaultVal float64) float64 { | ||
| 494 | + if v, err := b.Float(key); err == nil { | ||
| 495 | + return v | ||
| 496 | + } | ||
| 497 | + return defaultVal | ||
| 498 | +} | ||
| 499 | + | ||
| 500 | +func (b *beegoAppConfig) DIY(key string) (interface{}, error) { | ||
| 501 | + return b.innerConfig.DIY(key) | ||
| 502 | +} | ||
| 503 | + | ||
| 504 | +func (b *beegoAppConfig) GetSection(section string) (map[string]string, error) { | ||
| 505 | + return b.innerConfig.GetSection(section) | ||
| 506 | +} | ||
| 507 | + | ||
| 508 | +func (b *beegoAppConfig) SaveConfigFile(filename string) error { | ||
| 509 | + return b.innerConfig.SaveConfigFile(filename) | ||
| 510 | +} |
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +// Package config is used to parse config. | ||
| 16 | +// Usage: | ||
| 17 | +// import "github.com/astaxie/beego/config" | ||
| 18 | +//Examples. | ||
| 19 | +// | ||
| 20 | +// cnf, err := config.NewConfig("ini", "config.conf") | ||
| 21 | +// | ||
| 22 | +// cnf APIS: | ||
| 23 | +// | ||
| 24 | +// cnf.Set(key, val string) error | ||
| 25 | +// cnf.String(key string) string | ||
| 26 | +// cnf.Strings(key string) []string | ||
| 27 | +// cnf.Int(key string) (int, error) | ||
| 28 | +// cnf.Int64(key string) (int64, error) | ||
| 29 | +// cnf.Bool(key string) (bool, error) | ||
| 30 | +// cnf.Float(key string) (float64, error) | ||
| 31 | +// cnf.DefaultString(key string, defaultVal string) string | ||
| 32 | +// cnf.DefaultStrings(key string, defaultVal []string) []string | ||
| 33 | +// cnf.DefaultInt(key string, defaultVal int) int | ||
| 34 | +// cnf.DefaultInt64(key string, defaultVal int64) int64 | ||
| 35 | +// cnf.DefaultBool(key string, defaultVal bool) bool | ||
| 36 | +// cnf.DefaultFloat(key string, defaultVal float64) float64 | ||
| 37 | +// cnf.DIY(key string) (interface{}, error) | ||
| 38 | +// cnf.GetSection(section string) (map[string]string, error) | ||
| 39 | +// cnf.SaveConfigFile(filename string) error | ||
| 40 | +//More docs http://beego.me/docs/module/config.md | ||
| 41 | +package config | ||
| 42 | + | ||
| 43 | +import ( | ||
| 44 | + "fmt" | ||
| 45 | + "os" | ||
| 46 | + "reflect" | ||
| 47 | + "time" | ||
| 48 | +) | ||
| 49 | + | ||
| 50 | +// Configer defines how to get and set value from configuration raw data. | ||
| 51 | +type Configer interface { | ||
| 52 | + Set(key, val string) error //support section::key type in given key when using ini type. | ||
| 53 | + String(key string) string //support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. | ||
| 54 | + Strings(key string) []string //get string slice | ||
| 55 | + Int(key string) (int, error) | ||
| 56 | + Int64(key string) (int64, error) | ||
| 57 | + Bool(key string) (bool, error) | ||
| 58 | + Float(key string) (float64, error) | ||
| 59 | + DefaultString(key string, defaultVal string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. | ||
| 60 | + DefaultStrings(key string, defaultVal []string) []string //get string slice | ||
| 61 | + DefaultInt(key string, defaultVal int) int | ||
| 62 | + DefaultInt64(key string, defaultVal int64) int64 | ||
| 63 | + DefaultBool(key string, defaultVal bool) bool | ||
| 64 | + DefaultFloat(key string, defaultVal float64) float64 | ||
| 65 | + DIY(key string) (interface{}, error) | ||
| 66 | + GetSection(section string) (map[string]string, error) | ||
| 67 | + SaveConfigFile(filename string) error | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +// Config is the adapter interface for parsing config file to get raw data to Configer. | ||
| 71 | +type Config interface { | ||
| 72 | + Parse(key string) (Configer, error) | ||
| 73 | + ParseData(data []byte) (Configer, error) | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +var adapters = make(map[string]Config) | ||
| 77 | + | ||
| 78 | +// Register makes a config adapter available by the adapter name. | ||
| 79 | +// If Register is called twice with the same name or if driver is nil, | ||
| 80 | +// it panics. | ||
| 81 | +func Register(name string, adapter Config) { | ||
| 82 | + if adapter == nil { | ||
| 83 | + panic("config: Register adapter is nil") | ||
| 84 | + } | ||
| 85 | + if _, ok := adapters[name]; ok { | ||
| 86 | + panic("config: Register called twice for adapter " + name) | ||
| 87 | + } | ||
| 88 | + adapters[name] = adapter | ||
| 89 | +} | ||
| 90 | + | ||
| 91 | +// NewConfig adapterName is ini/json/xml/yaml. | ||
| 92 | +// filename is the config file path. | ||
| 93 | +func NewConfig(adapterName, filename string) (Configer, error) { | ||
| 94 | + adapter, ok := adapters[adapterName] | ||
| 95 | + if !ok { | ||
| 96 | + return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName) | ||
| 97 | + } | ||
| 98 | + return adapter.Parse(filename) | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +// NewConfigData adapterName is ini/json/xml/yaml. | ||
| 102 | +// data is the config data. | ||
| 103 | +func NewConfigData(adapterName string, data []byte) (Configer, error) { | ||
| 104 | + adapter, ok := adapters[adapterName] | ||
| 105 | + if !ok { | ||
| 106 | + return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName) | ||
| 107 | + } | ||
| 108 | + return adapter.ParseData(data) | ||
| 109 | +} | ||
| 110 | + | ||
| 111 | +// ExpandValueEnvForMap convert all string value with environment variable. | ||
| 112 | +func ExpandValueEnvForMap(m map[string]interface{}) map[string]interface{} { | ||
| 113 | + for k, v := range m { | ||
| 114 | + switch value := v.(type) { | ||
| 115 | + case string: | ||
| 116 | + m[k] = ExpandValueEnv(value) | ||
| 117 | + case map[string]interface{}: | ||
| 118 | + m[k] = ExpandValueEnvForMap(value) | ||
| 119 | + case map[string]string: | ||
| 120 | + for k2, v2 := range value { | ||
| 121 | + value[k2] = ExpandValueEnv(v2) | ||
| 122 | + } | ||
| 123 | + m[k] = value | ||
| 124 | + } | ||
| 125 | + } | ||
| 126 | + return m | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +// ExpandValueEnv returns value of convert with environment variable. | ||
| 130 | +// | ||
| 131 | +// Return environment variable if value start with "${" and end with "}". | ||
| 132 | +// Return default value if environment variable is empty or not exist. | ||
| 133 | +// | ||
| 134 | +// It accept value formats "${env}" , "${env||}}" , "${env||defaultValue}" , "defaultvalue". | ||
| 135 | +// Examples: | ||
| 136 | +// v1 := config.ExpandValueEnv("${GOPATH}") // return the GOPATH environment variable. | ||
| 137 | +// v2 := config.ExpandValueEnv("${GOAsta||/usr/local/go}") // return the default value "/usr/local/go/". | ||
| 138 | +// v3 := config.ExpandValueEnv("Astaxie") // return the value "Astaxie". | ||
| 139 | +func ExpandValueEnv(value string) (realValue string) { | ||
| 140 | + realValue = value | ||
| 141 | + | ||
| 142 | + vLen := len(value) | ||
| 143 | + // 3 = ${} | ||
| 144 | + if vLen < 3 { | ||
| 145 | + return | ||
| 146 | + } | ||
| 147 | + // Need start with "${" and end with "}", then return. | ||
| 148 | + if value[0] != '$' || value[1] != '{' || value[vLen-1] != '}' { | ||
| 149 | + return | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + key := "" | ||
| 153 | + defaultV := "" | ||
| 154 | + // value start with "${" | ||
| 155 | + for i := 2; i < vLen; i++ { | ||
| 156 | + if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') { | ||
| 157 | + key = value[2:i] | ||
| 158 | + defaultV = value[i+2 : vLen-1] // other string is default value. | ||
| 159 | + break | ||
| 160 | + } else if value[i] == '}' { | ||
| 161 | + key = value[2:i] | ||
| 162 | + break | ||
| 163 | + } | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + realValue = os.Getenv(key) | ||
| 167 | + if realValue == "" { | ||
| 168 | + realValue = defaultV | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + return | ||
| 172 | +} | ||
| 173 | + | ||
| 174 | +// ParseBool returns the boolean value represented by the string. | ||
| 175 | +// | ||
| 176 | +// It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On, | ||
| 177 | +// 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off. | ||
| 178 | +// Any other value returns an error. | ||
| 179 | +func ParseBool(val interface{}) (value bool, err error) { | ||
| 180 | + if val != nil { | ||
| 181 | + switch v := val.(type) { | ||
| 182 | + case bool: | ||
| 183 | + return v, nil | ||
| 184 | + case string: | ||
| 185 | + switch v { | ||
| 186 | + case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "Y", "y", "ON", "on", "On": | ||
| 187 | + return true, nil | ||
| 188 | + case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "N", "n", "OFF", "off", "Off": | ||
| 189 | + return false, nil | ||
| 190 | + } | ||
| 191 | + case int8, int32, int64: | ||
| 192 | + strV := fmt.Sprintf("%d", v) | ||
| 193 | + if strV == "1" { | ||
| 194 | + return true, nil | ||
| 195 | + } else if strV == "0" { | ||
| 196 | + return false, nil | ||
| 197 | + } | ||
| 198 | + case float64: | ||
| 199 | + if v == 1.0 { | ||
| 200 | + return true, nil | ||
| 201 | + } else if v == 0.0 { | ||
| 202 | + return false, nil | ||
| 203 | + } | ||
| 204 | + } | ||
| 205 | + return false, fmt.Errorf("parsing %q: invalid syntax", val) | ||
| 206 | + } | ||
| 207 | + return false, fmt.Errorf("parsing <nil>: invalid syntax") | ||
| 208 | +} | ||
| 209 | + | ||
| 210 | +// ToString converts values of any type to string. | ||
| 211 | +func ToString(x interface{}) string { | ||
| 212 | + switch y := x.(type) { | ||
| 213 | + | ||
| 214 | + // Handle dates with special logic | ||
| 215 | + // This needs to come above the fmt.Stringer | ||
| 216 | + // test since time.Time's have a .String() | ||
| 217 | + // method | ||
| 218 | + case time.Time: | ||
| 219 | + return y.Format("A Monday") | ||
| 220 | + | ||
| 221 | + // Handle type string | ||
| 222 | + case string: | ||
| 223 | + return y | ||
| 224 | + | ||
| 225 | + // Handle type with .String() method | ||
| 226 | + case fmt.Stringer: | ||
| 227 | + return y.String() | ||
| 228 | + | ||
| 229 | + // Handle type with .Error() method | ||
| 230 | + case error: | ||
| 231 | + return y.Error() | ||
| 232 | + | ||
| 233 | + } | ||
| 234 | + | ||
| 235 | + // Handle named string type | ||
| 236 | + if v := reflect.ValueOf(x); v.Kind() == reflect.String { | ||
| 237 | + return v.String() | ||
| 238 | + } | ||
| 239 | + | ||
| 240 | + // Fallback to fmt package for anything else like numeric types | ||
| 241 | + return fmt.Sprint(x) | ||
| 242 | +} |
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +package config | ||
| 16 | + | ||
| 17 | +import ( | ||
| 18 | + "errors" | ||
| 19 | + "strconv" | ||
| 20 | + "strings" | ||
| 21 | +) | ||
| 22 | + | ||
| 23 | +type fakeConfigContainer struct { | ||
| 24 | + data map[string]string | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +func (c *fakeConfigContainer) getData(key string) string { | ||
| 28 | + return c.data[strings.ToLower(key)] | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +func (c *fakeConfigContainer) Set(key, val string) error { | ||
| 32 | + c.data[strings.ToLower(key)] = val | ||
| 33 | + return nil | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +func (c *fakeConfigContainer) String(key string) string { | ||
| 37 | + return c.getData(key) | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string { | ||
| 41 | + v := c.String(key) | ||
| 42 | + if v == "" { | ||
| 43 | + return defaultval | ||
| 44 | + } | ||
| 45 | + return v | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +func (c *fakeConfigContainer) Strings(key string) []string { | ||
| 49 | + v := c.String(key) | ||
| 50 | + if v == "" { | ||
| 51 | + return nil | ||
| 52 | + } | ||
| 53 | + return strings.Split(v, ";") | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string { | ||
| 57 | + v := c.Strings(key) | ||
| 58 | + if v == nil { | ||
| 59 | + return defaultval | ||
| 60 | + } | ||
| 61 | + return v | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +func (c *fakeConfigContainer) Int(key string) (int, error) { | ||
| 65 | + return strconv.Atoi(c.getData(key)) | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +func (c *fakeConfigContainer) DefaultInt(key string, defaultval int) int { | ||
| 69 | + v, err := c.Int(key) | ||
| 70 | + if err != nil { | ||
| 71 | + return defaultval | ||
| 72 | + } | ||
| 73 | + return v | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +func (c *fakeConfigContainer) Int64(key string) (int64, error) { | ||
| 77 | + return strconv.ParseInt(c.getData(key), 10, 64) | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 { | ||
| 81 | + v, err := c.Int64(key) | ||
| 82 | + if err != nil { | ||
| 83 | + return defaultval | ||
| 84 | + } | ||
| 85 | + return v | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +func (c *fakeConfigContainer) Bool(key string) (bool, error) { | ||
| 89 | + return ParseBool(c.getData(key)) | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool { | ||
| 93 | + v, err := c.Bool(key) | ||
| 94 | + if err != nil { | ||
| 95 | + return defaultval | ||
| 96 | + } | ||
| 97 | + return v | ||
| 98 | +} | ||
| 99 | + | ||
| 100 | +func (c *fakeConfigContainer) Float(key string) (float64, error) { | ||
| 101 | + return strconv.ParseFloat(c.getData(key), 64) | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +func (c *fakeConfigContainer) DefaultFloat(key string, defaultval float64) float64 { | ||
| 105 | + v, err := c.Float(key) | ||
| 106 | + if err != nil { | ||
| 107 | + return defaultval | ||
| 108 | + } | ||
| 109 | + return v | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +func (c *fakeConfigContainer) DIY(key string) (interface{}, error) { | ||
| 113 | + if v, ok := c.data[strings.ToLower(key)]; ok { | ||
| 114 | + return v, nil | ||
| 115 | + } | ||
| 116 | + return nil, errors.New("key not find") | ||
| 117 | +} | ||
| 118 | + | ||
| 119 | +func (c *fakeConfigContainer) GetSection(section string) (map[string]string, error) { | ||
| 120 | + return nil, errors.New("not implement in the fakeConfigContainer") | ||
| 121 | +} | ||
| 122 | + | ||
| 123 | +func (c *fakeConfigContainer) SaveConfigFile(filename string) error { | ||
| 124 | + return errors.New("not implement in the fakeConfigContainer") | ||
| 125 | +} | ||
| 126 | + | ||
| 127 | +var _ Configer = new(fakeConfigContainer) | ||
| 128 | + | ||
| 129 | +// NewFakeConfig return a fake Configer | ||
| 130 | +func NewFakeConfig() Configer { | ||
| 131 | + return &fakeConfigContainer{ | ||
| 132 | + data: make(map[string]string), | ||
| 133 | + } | ||
| 134 | +} |
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +package config | ||
| 16 | + | ||
| 17 | +import ( | ||
| 18 | + "bufio" | ||
| 19 | + "bytes" | ||
| 20 | + "errors" | ||
| 21 | + "io" | ||
| 22 | + "io/ioutil" | ||
| 23 | + "os" | ||
| 24 | + "os/user" | ||
| 25 | + "path/filepath" | ||
| 26 | + "strconv" | ||
| 27 | + "strings" | ||
| 28 | + "sync" | ||
| 29 | +) | ||
| 30 | + | ||
| 31 | +var ( | ||
| 32 | + defaultSection = "default" // default section means if some ini items not in a section, make them in default section, | ||
| 33 | + bNumComment = []byte{'#'} // number signal | ||
| 34 | + bSemComment = []byte{';'} // semicolon signal | ||
| 35 | + bEmpty = []byte{} | ||
| 36 | + bEqual = []byte{'='} // equal signal | ||
| 37 | + bDQuote = []byte{'"'} // quote signal | ||
| 38 | + sectionStart = []byte{'['} // section start signal | ||
| 39 | + sectionEnd = []byte{']'} // section end signal | ||
| 40 | + lineBreak = "\n" | ||
| 41 | +) | ||
| 42 | + | ||
| 43 | +// IniConfig implements Config to parse ini file. | ||
| 44 | +type IniConfig struct { | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +// Parse creates a new Config and parses the file configuration from the named file. | ||
| 48 | +func (ini *IniConfig) Parse(name string) (Configer, error) { | ||
| 49 | + return ini.parseFile(name) | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) { | ||
| 53 | + data, err := ioutil.ReadFile(name) | ||
| 54 | + if err != nil { | ||
| 55 | + return nil, err | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + return ini.parseData(filepath.Dir(name), data) | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) { | ||
| 62 | + cfg := &IniConfigContainer{ | ||
| 63 | + data: make(map[string]map[string]string), | ||
| 64 | + sectionComment: make(map[string]string), | ||
| 65 | + keyComment: make(map[string]string), | ||
| 66 | + RWMutex: sync.RWMutex{}, | ||
| 67 | + } | ||
| 68 | + cfg.Lock() | ||
| 69 | + defer cfg.Unlock() | ||
| 70 | + | ||
| 71 | + var comment bytes.Buffer | ||
| 72 | + buf := bufio.NewReader(bytes.NewBuffer(data)) | ||
| 73 | + // check the BOM | ||
| 74 | + head, err := buf.Peek(3) | ||
| 75 | + if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 { | ||
| 76 | + for i := 1; i <= 3; i++ { | ||
| 77 | + buf.ReadByte() | ||
| 78 | + } | ||
| 79 | + } | ||
| 80 | + section := defaultSection | ||
| 81 | + for { | ||
| 82 | + line, _, err := buf.ReadLine() | ||
| 83 | + if err == io.EOF { | ||
| 84 | + break | ||
| 85 | + } | ||
| 86 | + //It might be a good idea to throw a error on all unknonw errors? | ||
| 87 | + if _, ok := err.(*os.PathError); ok { | ||
| 88 | + return nil, err | ||
| 89 | + } | ||
| 90 | + line = bytes.TrimSpace(line) | ||
| 91 | + if bytes.Equal(line, bEmpty) { | ||
| 92 | + continue | ||
| 93 | + } | ||
| 94 | + var bComment []byte | ||
| 95 | + switch { | ||
| 96 | + case bytes.HasPrefix(line, bNumComment): | ||
| 97 | + bComment = bNumComment | ||
| 98 | + case bytes.HasPrefix(line, bSemComment): | ||
| 99 | + bComment = bSemComment | ||
| 100 | + } | ||
| 101 | + if bComment != nil { | ||
| 102 | + line = bytes.TrimLeft(line, string(bComment)) | ||
| 103 | + // Need append to a new line if multi-line comments. | ||
| 104 | + if comment.Len() > 0 { | ||
| 105 | + comment.WriteByte('\n') | ||
| 106 | + } | ||
| 107 | + comment.Write(line) | ||
| 108 | + continue | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) { | ||
| 112 | + section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive | ||
| 113 | + if comment.Len() > 0 { | ||
| 114 | + cfg.sectionComment[section] = comment.String() | ||
| 115 | + comment.Reset() | ||
| 116 | + } | ||
| 117 | + if _, ok := cfg.data[section]; !ok { | ||
| 118 | + cfg.data[section] = make(map[string]string) | ||
| 119 | + } | ||
| 120 | + continue | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + if _, ok := cfg.data[section]; !ok { | ||
| 124 | + cfg.data[section] = make(map[string]string) | ||
| 125 | + } | ||
| 126 | + keyValue := bytes.SplitN(line, bEqual, 2) | ||
| 127 | + | ||
| 128 | + key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive | ||
| 129 | + key = strings.ToLower(key) | ||
| 130 | + | ||
| 131 | + // handle include "other.conf" | ||
| 132 | + if len(keyValue) == 1 && strings.HasPrefix(key, "include") { | ||
| 133 | + | ||
| 134 | + includefiles := strings.Fields(key) | ||
| 135 | + if includefiles[0] == "include" && len(includefiles) == 2 { | ||
| 136 | + | ||
| 137 | + otherfile := strings.Trim(includefiles[1], "\"") | ||
| 138 | + if !filepath.IsAbs(otherfile) { | ||
| 139 | + otherfile = filepath.Join(dir, otherfile) | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + i, err := ini.parseFile(otherfile) | ||
| 143 | + if err != nil { | ||
| 144 | + return nil, err | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + for sec, dt := range i.data { | ||
| 148 | + if _, ok := cfg.data[sec]; !ok { | ||
| 149 | + cfg.data[sec] = make(map[string]string) | ||
| 150 | + } | ||
| 151 | + for k, v := range dt { | ||
| 152 | + cfg.data[sec][k] = v | ||
| 153 | + } | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + for sec, comm := range i.sectionComment { | ||
| 157 | + cfg.sectionComment[sec] = comm | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + for k, comm := range i.keyComment { | ||
| 161 | + cfg.keyComment[k] = comm | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + continue | ||
| 165 | + } | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + if len(keyValue) != 2 { | ||
| 169 | + return nil, errors.New("read the content error: \"" + string(line) + "\", should key = val") | ||
| 170 | + } | ||
| 171 | + val := bytes.TrimSpace(keyValue[1]) | ||
| 172 | + if bytes.HasPrefix(val, bDQuote) { | ||
| 173 | + val = bytes.Trim(val, `"`) | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + cfg.data[section][key] = ExpandValueEnv(string(val)) | ||
| 177 | + if comment.Len() > 0 { | ||
| 178 | + cfg.keyComment[section+"."+key] = comment.String() | ||
| 179 | + comment.Reset() | ||
| 180 | + } | ||
| 181 | + | ||
| 182 | + } | ||
| 183 | + return cfg, nil | ||
| 184 | +} | ||
| 185 | + | ||
| 186 | +// ParseData parse ini the data | ||
| 187 | +// When include other.conf,other.conf is either absolute directory | ||
| 188 | +// or under beego in default temporary directory(/tmp/beego[-username]). | ||
| 189 | +func (ini *IniConfig) ParseData(data []byte) (Configer, error) { | ||
| 190 | + dir := "beego" | ||
| 191 | + currentUser, err := user.Current() | ||
| 192 | + if err == nil { | ||
| 193 | + dir = "beego-" + currentUser.Username | ||
| 194 | + } | ||
| 195 | + dir = filepath.Join(os.TempDir(), dir) | ||
| 196 | + if err = os.MkdirAll(dir, os.ModePerm); err != nil { | ||
| 197 | + return nil, err | ||
| 198 | + } | ||
| 199 | + | ||
| 200 | + return ini.parseData(dir, data) | ||
| 201 | +} | ||
| 202 | + | ||
| 203 | +// IniConfigContainer A Config represents the ini configuration. | ||
| 204 | +// When set and get value, support key as section:name type. | ||
| 205 | +type IniConfigContainer struct { | ||
| 206 | + data map[string]map[string]string // section=> key:val | ||
| 207 | + sectionComment map[string]string // section : comment | ||
| 208 | + keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment. | ||
| 209 | + sync.RWMutex | ||
| 210 | +} | ||
| 211 | + | ||
| 212 | +// Bool returns the boolean value for a given key. | ||
| 213 | +func (c *IniConfigContainer) Bool(key string) (bool, error) { | ||
| 214 | + return ParseBool(c.getdata(key)) | ||
| 215 | +} | ||
| 216 | + | ||
| 217 | +// DefaultBool returns the boolean value for a given key. | ||
| 218 | +// if err != nil return defaultval | ||
| 219 | +func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool { | ||
| 220 | + v, err := c.Bool(key) | ||
| 221 | + if err != nil { | ||
| 222 | + return defaultval | ||
| 223 | + } | ||
| 224 | + return v | ||
| 225 | +} | ||
| 226 | + | ||
| 227 | +// Int returns the integer value for a given key. | ||
| 228 | +func (c *IniConfigContainer) Int(key string) (int, error) { | ||
| 229 | + return strconv.Atoi(c.getdata(key)) | ||
| 230 | +} | ||
| 231 | + | ||
| 232 | +// DefaultInt returns the integer value for a given key. | ||
| 233 | +// if err != nil return defaultval | ||
| 234 | +func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int { | ||
| 235 | + v, err := c.Int(key) | ||
| 236 | + if err != nil { | ||
| 237 | + return defaultval | ||
| 238 | + } | ||
| 239 | + return v | ||
| 240 | +} | ||
| 241 | + | ||
| 242 | +// Int64 returns the int64 value for a given key. | ||
| 243 | +func (c *IniConfigContainer) Int64(key string) (int64, error) { | ||
| 244 | + return strconv.ParseInt(c.getdata(key), 10, 64) | ||
| 245 | +} | ||
| 246 | + | ||
| 247 | +// DefaultInt64 returns the int64 value for a given key. | ||
| 248 | +// if err != nil return defaultval | ||
| 249 | +func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 { | ||
| 250 | + v, err := c.Int64(key) | ||
| 251 | + if err != nil { | ||
| 252 | + return defaultval | ||
| 253 | + } | ||
| 254 | + return v | ||
| 255 | +} | ||
| 256 | + | ||
| 257 | +// Float returns the float value for a given key. | ||
| 258 | +func (c *IniConfigContainer) Float(key string) (float64, error) { | ||
| 259 | + return strconv.ParseFloat(c.getdata(key), 64) | ||
| 260 | +} | ||
| 261 | + | ||
| 262 | +// DefaultFloat returns the float64 value for a given key. | ||
| 263 | +// if err != nil return defaultval | ||
| 264 | +func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 { | ||
| 265 | + v, err := c.Float(key) | ||
| 266 | + if err != nil { | ||
| 267 | + return defaultval | ||
| 268 | + } | ||
| 269 | + return v | ||
| 270 | +} | ||
| 271 | + | ||
| 272 | +// String returns the string value for a given key. | ||
| 273 | +func (c *IniConfigContainer) String(key string) string { | ||
| 274 | + return c.getdata(key) | ||
| 275 | +} | ||
| 276 | + | ||
| 277 | +// DefaultString returns the string value for a given key. | ||
| 278 | +// if err != nil return defaultval | ||
| 279 | +func (c *IniConfigContainer) DefaultString(key string, defaultval string) string { | ||
| 280 | + v := c.String(key) | ||
| 281 | + if v == "" { | ||
| 282 | + return defaultval | ||
| 283 | + } | ||
| 284 | + return v | ||
| 285 | +} | ||
| 286 | + | ||
| 287 | +// Strings returns the []string value for a given key. | ||
| 288 | +// Return nil if config value does not exist or is empty. | ||
| 289 | +func (c *IniConfigContainer) Strings(key string) []string { | ||
| 290 | + v := c.String(key) | ||
| 291 | + if v == "" { | ||
| 292 | + return nil | ||
| 293 | + } | ||
| 294 | + return strings.Split(v, ";") | ||
| 295 | +} | ||
| 296 | + | ||
| 297 | +// DefaultStrings returns the []string value for a given key. | ||
| 298 | +// if err != nil return defaultval | ||
| 299 | +func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string { | ||
| 300 | + v := c.Strings(key) | ||
| 301 | + if v == nil { | ||
| 302 | + return defaultval | ||
| 303 | + } | ||
| 304 | + return v | ||
| 305 | +} | ||
| 306 | + | ||
| 307 | +// GetSection returns map for the given section | ||
| 308 | +func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) { | ||
| 309 | + if v, ok := c.data[section]; ok { | ||
| 310 | + return v, nil | ||
| 311 | + } | ||
| 312 | + return nil, errors.New("not exist section") | ||
| 313 | +} | ||
| 314 | + | ||
| 315 | +// SaveConfigFile save the config into file. | ||
| 316 | +// | ||
| 317 | +// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Function. | ||
| 318 | +func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) { | ||
| 319 | + // Write configuration file by filename. | ||
| 320 | + f, err := os.Create(filename) | ||
| 321 | + if err != nil { | ||
| 322 | + return err | ||
| 323 | + } | ||
| 324 | + defer f.Close() | ||
| 325 | + | ||
| 326 | + // Get section or key comments. Fixed #1607 | ||
| 327 | + getCommentStr := func(section, key string) string { | ||
| 328 | + var ( | ||
| 329 | + comment string | ||
| 330 | + ok bool | ||
| 331 | + ) | ||
| 332 | + if len(key) == 0 { | ||
| 333 | + comment, ok = c.sectionComment[section] | ||
| 334 | + } else { | ||
| 335 | + comment, ok = c.keyComment[section+"."+key] | ||
| 336 | + } | ||
| 337 | + | ||
| 338 | + if ok { | ||
| 339 | + // Empty comment | ||
| 340 | + if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 { | ||
| 341 | + return string(bNumComment) | ||
| 342 | + } | ||
| 343 | + prefix := string(bNumComment) | ||
| 344 | + // Add the line head character "#" | ||
| 345 | + return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1) | ||
| 346 | + } | ||
| 347 | + return "" | ||
| 348 | + } | ||
| 349 | + | ||
| 350 | + buf := bytes.NewBuffer(nil) | ||
| 351 | + // Save default section at first place | ||
| 352 | + if dt, ok := c.data[defaultSection]; ok { | ||
| 353 | + for key, val := range dt { | ||
| 354 | + if key != " " { | ||
| 355 | + // Write key comments. | ||
| 356 | + if v := getCommentStr(defaultSection, key); len(v) > 0 { | ||
| 357 | + if _, err = buf.WriteString(v + lineBreak); err != nil { | ||
| 358 | + return err | ||
| 359 | + } | ||
| 360 | + } | ||
| 361 | + | ||
| 362 | + // Write key and value. | ||
| 363 | + if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil { | ||
| 364 | + return err | ||
| 365 | + } | ||
| 366 | + } | ||
| 367 | + } | ||
| 368 | + | ||
| 369 | + // Put a line between sections. | ||
| 370 | + if _, err = buf.WriteString(lineBreak); err != nil { | ||
| 371 | + return err | ||
| 372 | + } | ||
| 373 | + } | ||
| 374 | + // Save named sections | ||
| 375 | + for section, dt := range c.data { | ||
| 376 | + if section != defaultSection { | ||
| 377 | + // Write section comments. | ||
| 378 | + if v := getCommentStr(section, ""); len(v) > 0 { | ||
| 379 | + if _, err = buf.WriteString(v + lineBreak); err != nil { | ||
| 380 | + return err | ||
| 381 | + } | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + // Write section name. | ||
| 385 | + if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil { | ||
| 386 | + return err | ||
| 387 | + } | ||
| 388 | + | ||
| 389 | + for key, val := range dt { | ||
| 390 | + if key != " " { | ||
| 391 | + // Write key comments. | ||
| 392 | + if v := getCommentStr(section, key); len(v) > 0 { | ||
| 393 | + if _, err = buf.WriteString(v + lineBreak); err != nil { | ||
| 394 | + return err | ||
| 395 | + } | ||
| 396 | + } | ||
| 397 | + | ||
| 398 | + // Write key and value. | ||
| 399 | + if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil { | ||
| 400 | + return err | ||
| 401 | + } | ||
| 402 | + } | ||
| 403 | + } | ||
| 404 | + | ||
| 405 | + // Put a line between sections. | ||
| 406 | + if _, err = buf.WriteString(lineBreak); err != nil { | ||
| 407 | + return err | ||
| 408 | + } | ||
| 409 | + } | ||
| 410 | + } | ||
| 411 | + _, err = buf.WriteTo(f) | ||
| 412 | + return err | ||
| 413 | +} | ||
| 414 | + | ||
| 415 | +// Set writes a new value for key. | ||
| 416 | +// if write to one section, the key need be "section::key". | ||
| 417 | +// if the section is not existed, it panics. | ||
| 418 | +func (c *IniConfigContainer) Set(key, value string) error { | ||
| 419 | + c.Lock() | ||
| 420 | + defer c.Unlock() | ||
| 421 | + if len(key) == 0 { | ||
| 422 | + return errors.New("key is empty") | ||
| 423 | + } | ||
| 424 | + | ||
| 425 | + var ( | ||
| 426 | + section, k string | ||
| 427 | + sectionKey = strings.Split(strings.ToLower(key), "::") | ||
| 428 | + ) | ||
| 429 | + | ||
| 430 | + if len(sectionKey) >= 2 { | ||
| 431 | + section = sectionKey[0] | ||
| 432 | + k = sectionKey[1] | ||
| 433 | + } else { | ||
| 434 | + section = defaultSection | ||
| 435 | + k = sectionKey[0] | ||
| 436 | + } | ||
| 437 | + | ||
| 438 | + if _, ok := c.data[section]; !ok { | ||
| 439 | + c.data[section] = make(map[string]string) | ||
| 440 | + } | ||
| 441 | + c.data[section][k] = value | ||
| 442 | + return nil | ||
| 443 | +} | ||
| 444 | + | ||
| 445 | +// DIY returns the raw value by a given key. | ||
| 446 | +func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) { | ||
| 447 | + if v, ok := c.data[strings.ToLower(key)]; ok { | ||
| 448 | + return v, nil | ||
| 449 | + } | ||
| 450 | + return v, errors.New("key not find") | ||
| 451 | +} | ||
| 452 | + | ||
| 453 | +// section.key or key | ||
| 454 | +func (c *IniConfigContainer) getdata(key string) string { | ||
| 455 | + if len(key) == 0 { | ||
| 456 | + return "" | ||
| 457 | + } | ||
| 458 | + c.RLock() | ||
| 459 | + defer c.RUnlock() | ||
| 460 | + | ||
| 461 | + var ( | ||
| 462 | + section, k string | ||
| 463 | + sectionKey = strings.Split(strings.ToLower(key), "::") | ||
| 464 | + ) | ||
| 465 | + if len(sectionKey) >= 2 { | ||
| 466 | + section = sectionKey[0] | ||
| 467 | + k = sectionKey[1] | ||
| 468 | + } else { | ||
| 469 | + section = defaultSection | ||
| 470 | + k = sectionKey[0] | ||
| 471 | + } | ||
| 472 | + if v, ok := c.data[section]; ok { | ||
| 473 | + if vv, ok := v[k]; ok { | ||
| 474 | + return vv | ||
| 475 | + } | ||
| 476 | + } | ||
| 477 | + return "" | ||
| 478 | +} | ||
| 479 | + | ||
| 480 | +func init() { | ||
| 481 | + Register("ini", &IniConfig{}) | ||
| 482 | +} |
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +package config | ||
| 16 | + | ||
| 17 | +import ( | ||
| 18 | + "encoding/json" | ||
| 19 | + "errors" | ||
| 20 | + "fmt" | ||
| 21 | + "io/ioutil" | ||
| 22 | + "os" | ||
| 23 | + "strings" | ||
| 24 | + "sync" | ||
| 25 | +) | ||
| 26 | + | ||
| 27 | +// JSONConfig is a json config parser and implements Config interface. | ||
| 28 | +type JSONConfig struct { | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +// Parse returns a ConfigContainer with parsed json config map. | ||
| 32 | +func (js *JSONConfig) Parse(filename string) (Configer, error) { | ||
| 33 | + file, err := os.Open(filename) | ||
| 34 | + if err != nil { | ||
| 35 | + return nil, err | ||
| 36 | + } | ||
| 37 | + defer file.Close() | ||
| 38 | + content, err := ioutil.ReadAll(file) | ||
| 39 | + if err != nil { | ||
| 40 | + return nil, err | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + return js.ParseData(content) | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +// ParseData returns a ConfigContainer with json string | ||
| 47 | +func (js *JSONConfig) ParseData(data []byte) (Configer, error) { | ||
| 48 | + x := &JSONConfigContainer{ | ||
| 49 | + data: make(map[string]interface{}), | ||
| 50 | + } | ||
| 51 | + err := json.Unmarshal(data, &x.data) | ||
| 52 | + if err != nil { | ||
| 53 | + var wrappingArray []interface{} | ||
| 54 | + err2 := json.Unmarshal(data, &wrappingArray) | ||
| 55 | + if err2 != nil { | ||
| 56 | + return nil, err | ||
| 57 | + } | ||
| 58 | + x.data["rootArray"] = wrappingArray | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + x.data = ExpandValueEnvForMap(x.data) | ||
| 62 | + | ||
| 63 | + return x, nil | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +// JSONConfigContainer A Config represents the json configuration. | ||
| 67 | +// Only when get value, support key as section:name type. | ||
| 68 | +type JSONConfigContainer struct { | ||
| 69 | + data map[string]interface{} | ||
| 70 | + sync.RWMutex | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +// Bool returns the boolean value for a given key. | ||
| 74 | +func (c *JSONConfigContainer) Bool(key string) (bool, error) { | ||
| 75 | + val := c.getData(key) | ||
| 76 | + if val != nil { | ||
| 77 | + return ParseBool(val) | ||
| 78 | + } | ||
| 79 | + return false, fmt.Errorf("not exist key: %q", key) | ||
| 80 | +} | ||
| 81 | + | ||
| 82 | +// DefaultBool return the bool value if has no error | ||
| 83 | +// otherwise return the defaultval | ||
| 84 | +func (c *JSONConfigContainer) DefaultBool(key string, defaultval bool) bool { | ||
| 85 | + if v, err := c.Bool(key); err == nil { | ||
| 86 | + return v | ||
| 87 | + } | ||
| 88 | + return defaultval | ||
| 89 | +} | ||
| 90 | + | ||
| 91 | +// Int returns the integer value for a given key. | ||
| 92 | +func (c *JSONConfigContainer) Int(key string) (int, error) { | ||
| 93 | + val := c.getData(key) | ||
| 94 | + if val != nil { | ||
| 95 | + if v, ok := val.(float64); ok { | ||
| 96 | + return int(v), nil | ||
| 97 | + } | ||
| 98 | + return 0, errors.New("not int value") | ||
| 99 | + } | ||
| 100 | + return 0, errors.New("not exist key:" + key) | ||
| 101 | +} | ||
| 102 | + | ||
| 103 | +// DefaultInt returns the integer value for a given key. | ||
| 104 | +// if err != nil return defaultval | ||
| 105 | +func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int { | ||
| 106 | + if v, err := c.Int(key); err == nil { | ||
| 107 | + return v | ||
| 108 | + } | ||
| 109 | + return defaultval | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +// Int64 returns the int64 value for a given key. | ||
| 113 | +func (c *JSONConfigContainer) Int64(key string) (int64, error) { | ||
| 114 | + val := c.getData(key) | ||
| 115 | + if val != nil { | ||
| 116 | + if v, ok := val.(float64); ok { | ||
| 117 | + return int64(v), nil | ||
| 118 | + } | ||
| 119 | + return 0, errors.New("not int64 value") | ||
| 120 | + } | ||
| 121 | + return 0, errors.New("not exist key:" + key) | ||
| 122 | +} | ||
| 123 | + | ||
| 124 | +// DefaultInt64 returns the int64 value for a given key. | ||
| 125 | +// if err != nil return defaultval | ||
| 126 | +func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 { | ||
| 127 | + if v, err := c.Int64(key); err == nil { | ||
| 128 | + return v | ||
| 129 | + } | ||
| 130 | + return defaultval | ||
| 131 | +} | ||
| 132 | + | ||
| 133 | +// Float returns the float value for a given key. | ||
| 134 | +func (c *JSONConfigContainer) Float(key string) (float64, error) { | ||
| 135 | + val := c.getData(key) | ||
| 136 | + if val != nil { | ||
| 137 | + if v, ok := val.(float64); ok { | ||
| 138 | + return v, nil | ||
| 139 | + } | ||
| 140 | + return 0.0, errors.New("not float64 value") | ||
| 141 | + } | ||
| 142 | + return 0.0, errors.New("not exist key:" + key) | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +// DefaultFloat returns the float64 value for a given key. | ||
| 146 | +// if err != nil return defaultval | ||
| 147 | +func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 { | ||
| 148 | + if v, err := c.Float(key); err == nil { | ||
| 149 | + return v | ||
| 150 | + } | ||
| 151 | + return defaultval | ||
| 152 | +} | ||
| 153 | + | ||
| 154 | +// String returns the string value for a given key. | ||
| 155 | +func (c *JSONConfigContainer) String(key string) string { | ||
| 156 | + val := c.getData(key) | ||
| 157 | + if val != nil { | ||
| 158 | + if v, ok := val.(string); ok { | ||
| 159 | + return v | ||
| 160 | + } | ||
| 161 | + } | ||
| 162 | + return "" | ||
| 163 | +} | ||
| 164 | + | ||
| 165 | +// DefaultString returns the string value for a given key. | ||
| 166 | +// if err != nil return defaultval | ||
| 167 | +func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string { | ||
| 168 | + // TODO FIXME should not use "" to replace non existence | ||
| 169 | + if v := c.String(key); v != "" { | ||
| 170 | + return v | ||
| 171 | + } | ||
| 172 | + return defaultval | ||
| 173 | +} | ||
| 174 | + | ||
| 175 | +// Strings returns the []string value for a given key. | ||
| 176 | +func (c *JSONConfigContainer) Strings(key string) []string { | ||
| 177 | + stringVal := c.String(key) | ||
| 178 | + if stringVal == "" { | ||
| 179 | + return nil | ||
| 180 | + } | ||
| 181 | + return strings.Split(c.String(key), ";") | ||
| 182 | +} | ||
| 183 | + | ||
| 184 | +// DefaultStrings returns the []string value for a given key. | ||
| 185 | +// if err != nil return defaultval | ||
| 186 | +func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string { | ||
| 187 | + if v := c.Strings(key); v != nil { | ||
| 188 | + return v | ||
| 189 | + } | ||
| 190 | + return defaultval | ||
| 191 | +} | ||
| 192 | + | ||
| 193 | +// GetSection returns map for the given section | ||
| 194 | +func (c *JSONConfigContainer) GetSection(section string) (map[string]string, error) { | ||
| 195 | + if v, ok := c.data[section]; ok { | ||
| 196 | + return v.(map[string]string), nil | ||
| 197 | + } | ||
| 198 | + return nil, errors.New("nonexist section " + section) | ||
| 199 | +} | ||
| 200 | + | ||
| 201 | +// SaveConfigFile save the config into file | ||
| 202 | +func (c *JSONConfigContainer) SaveConfigFile(filename string) (err error) { | ||
| 203 | + // Write configuration file by filename. | ||
| 204 | + f, err := os.Create(filename) | ||
| 205 | + if err != nil { | ||
| 206 | + return err | ||
| 207 | + } | ||
| 208 | + defer f.Close() | ||
| 209 | + b, err := json.MarshalIndent(c.data, "", " ") | ||
| 210 | + if err != nil { | ||
| 211 | + return err | ||
| 212 | + } | ||
| 213 | + _, err = f.Write(b) | ||
| 214 | + return err | ||
| 215 | +} | ||
| 216 | + | ||
| 217 | +// Set writes a new value for key. | ||
| 218 | +func (c *JSONConfigContainer) Set(key, val string) error { | ||
| 219 | + c.Lock() | ||
| 220 | + defer c.Unlock() | ||
| 221 | + c.data[key] = val | ||
| 222 | + return nil | ||
| 223 | +} | ||
| 224 | + | ||
| 225 | +// DIY returns the raw value by a given key. | ||
| 226 | +func (c *JSONConfigContainer) DIY(key string) (v interface{}, err error) { | ||
| 227 | + val := c.getData(key) | ||
| 228 | + if val != nil { | ||
| 229 | + return val, nil | ||
| 230 | + } | ||
| 231 | + return nil, errors.New("not exist key") | ||
| 232 | +} | ||
| 233 | + | ||
| 234 | +// section.key or key | ||
| 235 | +func (c *JSONConfigContainer) getData(key string) interface{} { | ||
| 236 | + if len(key) == 0 { | ||
| 237 | + return nil | ||
| 238 | + } | ||
| 239 | + | ||
| 240 | + c.RLock() | ||
| 241 | + defer c.RUnlock() | ||
| 242 | + | ||
| 243 | + sectionKeys := strings.Split(key, "::") | ||
| 244 | + if len(sectionKeys) >= 2 { | ||
| 245 | + curValue, ok := c.data[sectionKeys[0]] | ||
| 246 | + if !ok { | ||
| 247 | + return nil | ||
| 248 | + } | ||
| 249 | + for _, key := range sectionKeys[1:] { | ||
| 250 | + if v, ok := curValue.(map[string]interface{}); ok { | ||
| 251 | + if curValue, ok = v[key]; !ok { | ||
| 252 | + return nil | ||
| 253 | + } | ||
| 254 | + } | ||
| 255 | + } | ||
| 256 | + return curValue | ||
| 257 | + } | ||
| 258 | + if v, ok := c.data[key]; ok { | ||
| 259 | + return v | ||
| 260 | + } | ||
| 261 | + return nil | ||
| 262 | +} | ||
| 263 | + | ||
| 264 | +func init() { | ||
| 265 | + Register("json", &JSONConfig{}) | ||
| 266 | +} |
| 1 | +// Copyright 2015 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +package context | ||
| 16 | + | ||
| 17 | +import ( | ||
| 18 | + "bytes" | ||
| 19 | + "compress/flate" | ||
| 20 | + "compress/gzip" | ||
| 21 | + "compress/zlib" | ||
| 22 | + "io" | ||
| 23 | + "net/http" | ||
| 24 | + "os" | ||
| 25 | + "strconv" | ||
| 26 | + "strings" | ||
| 27 | + "sync" | ||
| 28 | +) | ||
| 29 | + | ||
| 30 | +var ( | ||
| 31 | + //Default size==20B same as nginx | ||
| 32 | + defaultGzipMinLength = 20 | ||
| 33 | + //Content will only be compressed if content length is either unknown or greater than gzipMinLength. | ||
| 34 | + gzipMinLength = defaultGzipMinLength | ||
| 35 | + //The compression level used for deflate compression. (0-9). | ||
| 36 | + gzipCompressLevel int | ||
| 37 | + //List of HTTP methods to compress. If not set, only GET requests are compressed. | ||
| 38 | + includedMethods map[string]bool | ||
| 39 | + getMethodOnly bool | ||
| 40 | +) | ||
| 41 | + | ||
| 42 | +// InitGzip init the gzipcompress | ||
| 43 | +func InitGzip(minLength, compressLevel int, methods []string) { | ||
| 44 | + if minLength >= 0 { | ||
| 45 | + gzipMinLength = minLength | ||
| 46 | + } | ||
| 47 | + gzipCompressLevel = compressLevel | ||
| 48 | + if gzipCompressLevel < flate.NoCompression || gzipCompressLevel > flate.BestCompression { | ||
| 49 | + gzipCompressLevel = flate.BestSpeed | ||
| 50 | + } | ||
| 51 | + getMethodOnly = (len(methods) == 0) || (len(methods) == 1 && strings.ToUpper(methods[0]) == "GET") | ||
| 52 | + includedMethods = make(map[string]bool, len(methods)) | ||
| 53 | + for _, v := range methods { | ||
| 54 | + includedMethods[strings.ToUpper(v)] = true | ||
| 55 | + } | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +type resetWriter interface { | ||
| 59 | + io.Writer | ||
| 60 | + Reset(w io.Writer) | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +type nopResetWriter struct { | ||
| 64 | + io.Writer | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +func (n nopResetWriter) Reset(w io.Writer) { | ||
| 68 | + //do nothing | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +type acceptEncoder struct { | ||
| 72 | + name string | ||
| 73 | + levelEncode func(int) resetWriter | ||
| 74 | + customCompressLevelPool *sync.Pool | ||
| 75 | + bestCompressionPool *sync.Pool | ||
| 76 | +} | ||
| 77 | + | ||
| 78 | +func (ac acceptEncoder) encode(wr io.Writer, level int) resetWriter { | ||
| 79 | + if ac.customCompressLevelPool == nil || ac.bestCompressionPool == nil { | ||
| 80 | + return nopResetWriter{wr} | ||
| 81 | + } | ||
| 82 | + var rwr resetWriter | ||
| 83 | + switch level { | ||
| 84 | + case flate.BestSpeed: | ||
| 85 | + rwr = ac.customCompressLevelPool.Get().(resetWriter) | ||
| 86 | + case flate.BestCompression: | ||
| 87 | + rwr = ac.bestCompressionPool.Get().(resetWriter) | ||
| 88 | + default: | ||
| 89 | + rwr = ac.levelEncode(level) | ||
| 90 | + } | ||
| 91 | + rwr.Reset(wr) | ||
| 92 | + return rwr | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +func (ac acceptEncoder) put(wr resetWriter, level int) { | ||
| 96 | + if ac.customCompressLevelPool == nil || ac.bestCompressionPool == nil { | ||
| 97 | + return | ||
| 98 | + } | ||
| 99 | + wr.Reset(nil) | ||
| 100 | + | ||
| 101 | + //notice | ||
| 102 | + //compressionLevel==BestCompression DOES NOT MATTER | ||
| 103 | + //sync.Pool will not memory leak | ||
| 104 | + | ||
| 105 | + switch level { | ||
| 106 | + case gzipCompressLevel: | ||
| 107 | + ac.customCompressLevelPool.Put(wr) | ||
| 108 | + case flate.BestCompression: | ||
| 109 | + ac.bestCompressionPool.Put(wr) | ||
| 110 | + } | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +var ( | ||
| 114 | + noneCompressEncoder = acceptEncoder{"", nil, nil, nil} | ||
| 115 | + gzipCompressEncoder = acceptEncoder{ | ||
| 116 | + name: "gzip", | ||
| 117 | + levelEncode: func(level int) resetWriter { wr, _ := gzip.NewWriterLevel(nil, level); return wr }, | ||
| 118 | + customCompressLevelPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, gzipCompressLevel); return wr }}, | ||
| 119 | + bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, flate.BestCompression); return wr }}, | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + //according to the sec :http://tools.ietf.org/html/rfc2616#section-3.5 ,the deflate compress in http is zlib indeed | ||
| 123 | + //deflate | ||
| 124 | + //The "zlib" format defined in RFC 1950 [31] in combination with | ||
| 125 | + //the "deflate" compression mechanism described in RFC 1951 [29]. | ||
| 126 | + deflateCompressEncoder = acceptEncoder{ | ||
| 127 | + name: "deflate", | ||
| 128 | + levelEncode: func(level int) resetWriter { wr, _ := zlib.NewWriterLevel(nil, level); return wr }, | ||
| 129 | + customCompressLevelPool: &sync.Pool{New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, gzipCompressLevel); return wr }}, | ||
| 130 | + bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, flate.BestCompression); return wr }}, | ||
| 131 | + } | ||
| 132 | +) | ||
| 133 | + | ||
| 134 | +var ( | ||
| 135 | + encoderMap = map[string]acceptEncoder{ // all the other compress methods will ignore | ||
| 136 | + "gzip": gzipCompressEncoder, | ||
| 137 | + "deflate": deflateCompressEncoder, | ||
| 138 | + "*": gzipCompressEncoder, // * means any compress will accept,we prefer gzip | ||
| 139 | + "identity": noneCompressEncoder, // identity means none-compress | ||
| 140 | + } | ||
| 141 | +) | ||
| 142 | + | ||
| 143 | +// WriteFile reads from file and writes to writer by the specific encoding(gzip/deflate) | ||
| 144 | +func WriteFile(encoding string, writer io.Writer, file *os.File) (bool, string, error) { | ||
| 145 | + return writeLevel(encoding, writer, file, flate.BestCompression) | ||
| 146 | +} | ||
| 147 | + | ||
| 148 | +// WriteBody reads writes content to writer by the specific encoding(gzip/deflate) | ||
| 149 | +func WriteBody(encoding string, writer io.Writer, content []byte) (bool, string, error) { | ||
| 150 | + if encoding == "" || len(content) < gzipMinLength { | ||
| 151 | + _, err := writer.Write(content) | ||
| 152 | + return false, "", err | ||
| 153 | + } | ||
| 154 | + return writeLevel(encoding, writer, bytes.NewReader(content), gzipCompressLevel) | ||
| 155 | +} | ||
| 156 | + | ||
| 157 | +// writeLevel reads from reader,writes to writer by specific encoding and compress level | ||
| 158 | +// the compress level is defined by deflate package | ||
| 159 | +func writeLevel(encoding string, writer io.Writer, reader io.Reader, level int) (bool, string, error) { | ||
| 160 | + var outputWriter resetWriter | ||
| 161 | + var err error | ||
| 162 | + var ce = noneCompressEncoder | ||
| 163 | + | ||
| 164 | + if cf, ok := encoderMap[encoding]; ok { | ||
| 165 | + ce = cf | ||
| 166 | + } | ||
| 167 | + encoding = ce.name | ||
| 168 | + outputWriter = ce.encode(writer, level) | ||
| 169 | + defer ce.put(outputWriter, level) | ||
| 170 | + | ||
| 171 | + _, err = io.Copy(outputWriter, reader) | ||
| 172 | + if err != nil { | ||
| 173 | + return false, "", err | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + switch outputWriter.(type) { | ||
| 177 | + case io.WriteCloser: | ||
| 178 | + outputWriter.(io.WriteCloser).Close() | ||
| 179 | + } | ||
| 180 | + return encoding != "", encoding, nil | ||
| 181 | +} | ||
| 182 | + | ||
| 183 | +// ParseEncoding will extract the right encoding for response | ||
| 184 | +// the Accept-Encoding's sec is here: | ||
| 185 | +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 | ||
| 186 | +func ParseEncoding(r *http.Request) string { | ||
| 187 | + if r == nil { | ||
| 188 | + return "" | ||
| 189 | + } | ||
| 190 | + if (getMethodOnly && r.Method == "GET") || includedMethods[r.Method] { | ||
| 191 | + return parseEncoding(r) | ||
| 192 | + } | ||
| 193 | + return "" | ||
| 194 | +} | ||
| 195 | + | ||
| 196 | +type q struct { | ||
| 197 | + name string | ||
| 198 | + value float64 | ||
| 199 | +} | ||
| 200 | + | ||
| 201 | +func parseEncoding(r *http.Request) string { | ||
| 202 | + acceptEncoding := r.Header.Get("Accept-Encoding") | ||
| 203 | + if acceptEncoding == "" { | ||
| 204 | + return "" | ||
| 205 | + } | ||
| 206 | + var lastQ q | ||
| 207 | + for _, v := range strings.Split(acceptEncoding, ",") { | ||
| 208 | + v = strings.TrimSpace(v) | ||
| 209 | + if v == "" { | ||
| 210 | + continue | ||
| 211 | + } | ||
| 212 | + vs := strings.Split(v, ";") | ||
| 213 | + var cf acceptEncoder | ||
| 214 | + var ok bool | ||
| 215 | + if cf, ok = encoderMap[vs[0]]; !ok { | ||
| 216 | + continue | ||
| 217 | + } | ||
| 218 | + if len(vs) == 1 { | ||
| 219 | + return cf.name | ||
| 220 | + } | ||
| 221 | + if len(vs) == 2 { | ||
| 222 | + f, _ := strconv.ParseFloat(strings.Replace(vs[1], "q=", "", -1), 64) | ||
| 223 | + if f == 0 { | ||
| 224 | + continue | ||
| 225 | + } | ||
| 226 | + if f > lastQ.value { | ||
| 227 | + lastQ = q{cf.name, f} | ||
| 228 | + } | ||
| 229 | + } | ||
| 230 | + } | ||
| 231 | + return lastQ.name | ||
| 232 | +} |
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +// Package context provide the context utils | ||
| 16 | +// Usage: | ||
| 17 | +// | ||
| 18 | +// import "github.com/astaxie/beego/context" | ||
| 19 | +// | ||
| 20 | +// ctx := context.Context{Request:req,ResponseWriter:rw} | ||
| 21 | +// | ||
| 22 | +// more docs http://beego.me/docs/module/context.md | ||
| 23 | +package context | ||
| 24 | + | ||
| 25 | +import ( | ||
| 26 | + "bufio" | ||
| 27 | + "crypto/hmac" | ||
| 28 | + "crypto/sha1" | ||
| 29 | + "encoding/base64" | ||
| 30 | + "errors" | ||
| 31 | + "fmt" | ||
| 32 | + "net" | ||
| 33 | + "net/http" | ||
| 34 | + "strconv" | ||
| 35 | + "strings" | ||
| 36 | + "time" | ||
| 37 | + | ||
| 38 | + "github.com/astaxie/beego/utils" | ||
| 39 | +) | ||
| 40 | + | ||
| 41 | +// NewContext return the Context with Input and Output | ||
| 42 | +func NewContext() *Context { | ||
| 43 | + return &Context{ | ||
| 44 | + Input: NewInput(), | ||
| 45 | + Output: NewOutput(), | ||
| 46 | + } | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +// Context Http request context struct including BeegoInput, BeegoOutput, http.Request and http.ResponseWriter. | ||
| 50 | +// BeegoInput and BeegoOutput provides some api to operate request and response more easily. | ||
| 51 | +type Context struct { | ||
| 52 | + Input *BeegoInput | ||
| 53 | + Output *BeegoOutput | ||
| 54 | + Request *http.Request | ||
| 55 | + ResponseWriter *Response | ||
| 56 | + _xsrfToken string | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +// Reset init Context, BeegoInput and BeegoOutput | ||
| 60 | +func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { | ||
| 61 | + ctx.Request = r | ||
| 62 | + if ctx.ResponseWriter == nil { | ||
| 63 | + ctx.ResponseWriter = &Response{} | ||
| 64 | + } | ||
| 65 | + ctx.ResponseWriter.reset(rw) | ||
| 66 | + ctx.Input.Reset(ctx) | ||
| 67 | + ctx.Output.Reset(ctx) | ||
| 68 | + ctx._xsrfToken = "" | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +// Redirect does redirection to localurl with http header status code. | ||
| 72 | +func (ctx *Context) Redirect(status int, localurl string) { | ||
| 73 | + http.Redirect(ctx.ResponseWriter, ctx.Request, localurl, status) | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +// Abort stops this request. | ||
| 77 | +// if beego.ErrorMaps exists, panic body. | ||
| 78 | +func (ctx *Context) Abort(status int, body string) { | ||
| 79 | + ctx.Output.SetStatus(status) | ||
| 80 | + panic(body) | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +// WriteString Write string to response body. | ||
| 84 | +// it sends response body. | ||
| 85 | +func (ctx *Context) WriteString(content string) { | ||
| 86 | + ctx.ResponseWriter.Write([]byte(content)) | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +// GetCookie Get cookie from request by a given key. | ||
| 90 | +// It's alias of BeegoInput.Cookie. | ||
| 91 | +func (ctx *Context) GetCookie(key string) string { | ||
| 92 | + return ctx.Input.Cookie(key) | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +// SetCookie Set cookie for response. | ||
| 96 | +// It's alias of BeegoOutput.Cookie. | ||
| 97 | +func (ctx *Context) SetCookie(name string, value string, others ...interface{}) { | ||
| 98 | + ctx.Output.Cookie(name, value, others...) | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +// GetSecureCookie Get secure cookie from request by a given key. | ||
| 102 | +func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) { | ||
| 103 | + val := ctx.Input.Cookie(key) | ||
| 104 | + if val == "" { | ||
| 105 | + return "", false | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + parts := strings.SplitN(val, "|", 3) | ||
| 109 | + | ||
| 110 | + if len(parts) != 3 { | ||
| 111 | + return "", false | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + vs := parts[0] | ||
| 115 | + timestamp := parts[1] | ||
| 116 | + sig := parts[2] | ||
| 117 | + | ||
| 118 | + h := hmac.New(sha1.New, []byte(Secret)) | ||
| 119 | + fmt.Fprintf(h, "%s%s", vs, timestamp) | ||
| 120 | + | ||
| 121 | + if fmt.Sprintf("%02x", h.Sum(nil)) != sig { | ||
| 122 | + return "", false | ||
| 123 | + } | ||
| 124 | + res, _ := base64.URLEncoding.DecodeString(vs) | ||
| 125 | + return string(res), true | ||
| 126 | +} | ||
| 127 | + | ||
| 128 | +// SetSecureCookie Set Secure cookie for response. | ||
| 129 | +func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) { | ||
| 130 | + vs := base64.URLEncoding.EncodeToString([]byte(value)) | ||
| 131 | + timestamp := strconv.FormatInt(time.Now().UnixNano(), 10) | ||
| 132 | + h := hmac.New(sha1.New, []byte(Secret)) | ||
| 133 | + fmt.Fprintf(h, "%s%s", vs, timestamp) | ||
| 134 | + sig := fmt.Sprintf("%02x", h.Sum(nil)) | ||
| 135 | + cookie := strings.Join([]string{vs, timestamp, sig}, "|") | ||
| 136 | + ctx.Output.Cookie(name, cookie, others...) | ||
| 137 | +} | ||
| 138 | + | ||
| 139 | +// XSRFToken creates a xsrf token string and returns. | ||
| 140 | +func (ctx *Context) XSRFToken(key string, expire int64) string { | ||
| 141 | + if ctx._xsrfToken == "" { | ||
| 142 | + token, ok := ctx.GetSecureCookie(key, "_xsrf") | ||
| 143 | + if !ok { | ||
| 144 | + token = string(utils.RandomCreateBytes(32)) | ||
| 145 | + ctx.SetSecureCookie(key, "_xsrf", token, expire) | ||
| 146 | + } | ||
| 147 | + ctx._xsrfToken = token | ||
| 148 | + } | ||
| 149 | + return ctx._xsrfToken | ||
| 150 | +} | ||
| 151 | + | ||
| 152 | +// CheckXSRFCookie checks xsrf token in this request is valid or not. | ||
| 153 | +// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken" | ||
| 154 | +// or in form field value named as "_xsrf". | ||
| 155 | +func (ctx *Context) CheckXSRFCookie() bool { | ||
| 156 | + token := ctx.Input.Query("_xsrf") | ||
| 157 | + if token == "" { | ||
| 158 | + token = ctx.Request.Header.Get("X-Xsrftoken") | ||
| 159 | + } | ||
| 160 | + if token == "" { | ||
| 161 | + token = ctx.Request.Header.Get("X-Csrftoken") | ||
| 162 | + } | ||
| 163 | + if token == "" { | ||
| 164 | + ctx.Abort(403, "'_xsrf' argument missing from POST") | ||
| 165 | + return false | ||
| 166 | + } | ||
| 167 | + if ctx._xsrfToken != token { | ||
| 168 | + ctx.Abort(403, "XSRF cookie does not match POST argument") | ||
| 169 | + return false | ||
| 170 | + } | ||
| 171 | + return true | ||
| 172 | +} | ||
| 173 | + | ||
| 174 | +// RenderMethodResult renders the return value of a controller method to the output | ||
| 175 | +func (ctx *Context) RenderMethodResult(result interface{}) { | ||
| 176 | + if result != nil { | ||
| 177 | + renderer, ok := result.(Renderer) | ||
| 178 | + if !ok { | ||
| 179 | + err, ok := result.(error) | ||
| 180 | + if ok { | ||
| 181 | + renderer = errorRenderer(err) | ||
| 182 | + } else { | ||
| 183 | + renderer = jsonRenderer(result) | ||
| 184 | + } | ||
| 185 | + } | ||
| 186 | + renderer.Render(ctx) | ||
| 187 | + } | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +//Response is a wrapper for the http.ResponseWriter | ||
| 191 | +//started set to true if response was written to then don't execute other handler | ||
| 192 | +type Response struct { | ||
| 193 | + http.ResponseWriter | ||
| 194 | + Started bool | ||
| 195 | + Status int | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +func (r *Response) reset(rw http.ResponseWriter) { | ||
| 199 | + r.ResponseWriter = rw | ||
| 200 | + r.Status = 0 | ||
| 201 | + r.Started = false | ||
| 202 | +} | ||
| 203 | + | ||
| 204 | +// Write writes the data to the connection as part of an HTTP reply, | ||
| 205 | +// and sets `started` to true. | ||
| 206 | +// started means the response has sent out. | ||
| 207 | +func (r *Response) Write(p []byte) (int, error) { | ||
| 208 | + r.Started = true | ||
| 209 | + return r.ResponseWriter.Write(p) | ||
| 210 | +} | ||
| 211 | + | ||
| 212 | +// WriteHeader sends an HTTP response header with status code, | ||
| 213 | +// and sets `started` to true. | ||
| 214 | +func (r *Response) WriteHeader(code int) { | ||
| 215 | + if r.Status > 0 { | ||
| 216 | + //prevent multiple response.WriteHeader calls | ||
| 217 | + return | ||
| 218 | + } | ||
| 219 | + r.Status = code | ||
| 220 | + r.Started = true | ||
| 221 | + r.ResponseWriter.WriteHeader(code) | ||
| 222 | +} | ||
| 223 | + | ||
| 224 | +// Hijack hijacker for http | ||
| 225 | +func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||
| 226 | + hj, ok := r.ResponseWriter.(http.Hijacker) | ||
| 227 | + if !ok { | ||
| 228 | + return nil, nil, errors.New("webserver doesn't support hijacking") | ||
| 229 | + } | ||
| 230 | + return hj.Hijack() | ||
| 231 | +} | ||
| 232 | + | ||
| 233 | +// Flush http.Flusher | ||
| 234 | +func (r *Response) Flush() { | ||
| 235 | + if f, ok := r.ResponseWriter.(http.Flusher); ok { | ||
| 236 | + f.Flush() | ||
| 237 | + } | ||
| 238 | +} | ||
| 239 | + | ||
| 240 | +// CloseNotify http.CloseNotifier | ||
| 241 | +func (r *Response) CloseNotify() <-chan bool { | ||
| 242 | + if cn, ok := r.ResponseWriter.(http.CloseNotifier); ok { | ||
| 243 | + return cn.CloseNotify() | ||
| 244 | + } | ||
| 245 | + return nil | ||
| 246 | +} |
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +package context | ||
| 16 | + | ||
| 17 | +import ( | ||
| 18 | + "bytes" | ||
| 19 | + "compress/gzip" | ||
| 20 | + "errors" | ||
| 21 | + "io" | ||
| 22 | + "io/ioutil" | ||
| 23 | + "net" | ||
| 24 | + "net/http" | ||
| 25 | + "net/url" | ||
| 26 | + "reflect" | ||
| 27 | + "regexp" | ||
| 28 | + "strconv" | ||
| 29 | + "strings" | ||
| 30 | + | ||
| 31 | + "github.com/astaxie/beego/session" | ||
| 32 | +) | ||
| 33 | + | ||
| 34 | +// Regexes for checking the accept headers | ||
| 35 | +// TODO make sure these are correct | ||
| 36 | +var ( | ||
| 37 | + acceptsHTMLRegex = regexp.MustCompile(`(text/html|application/xhtml\+xml)(?:,|$)`) | ||
| 38 | + acceptsXMLRegex = regexp.MustCompile(`(application/xml|text/xml)(?:,|$)`) | ||
| 39 | + acceptsJSONRegex = regexp.MustCompile(`(application/json)(?:,|$)`) | ||
| 40 | + acceptsYAMLRegex = regexp.MustCompile(`(application/x-yaml)(?:,|$)`) | ||
| 41 | + maxParam = 50 | ||
| 42 | +) | ||
| 43 | + | ||
| 44 | +// BeegoInput operates the http request header, data, cookie and body. | ||
| 45 | +// it also contains router params and current session. | ||
| 46 | +type BeegoInput struct { | ||
| 47 | + Context *Context | ||
| 48 | + CruSession session.Store | ||
| 49 | + pnames []string | ||
| 50 | + pvalues []string | ||
| 51 | + data map[interface{}]interface{} // store some values in this context when calling context in filter or controller. | ||
| 52 | + RequestBody []byte | ||
| 53 | + RunMethod string | ||
| 54 | + RunController reflect.Type | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +// NewInput return BeegoInput generated by Context. | ||
| 58 | +func NewInput() *BeegoInput { | ||
| 59 | + return &BeegoInput{ | ||
| 60 | + pnames: make([]string, 0, maxParam), | ||
| 61 | + pvalues: make([]string, 0, maxParam), | ||
| 62 | + data: make(map[interface{}]interface{}), | ||
| 63 | + } | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +// Reset init the BeegoInput | ||
| 67 | +func (input *BeegoInput) Reset(ctx *Context) { | ||
| 68 | + input.Context = ctx | ||
| 69 | + input.CruSession = nil | ||
| 70 | + input.pnames = input.pnames[:0] | ||
| 71 | + input.pvalues = input.pvalues[:0] | ||
| 72 | + input.data = nil | ||
| 73 | + input.RequestBody = []byte{} | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +// Protocol returns request protocol name, such as HTTP/1.1 . | ||
| 77 | +func (input *BeegoInput) Protocol() string { | ||
| 78 | + return input.Context.Request.Proto | ||
| 79 | +} | ||
| 80 | + | ||
| 81 | +// URI returns full request url with query string, fragment. | ||
| 82 | +func (input *BeegoInput) URI() string { | ||
| 83 | + return input.Context.Request.RequestURI | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +// URL returns request url path (without query string, fragment). | ||
| 87 | +func (input *BeegoInput) URL() string { | ||
| 88 | + return input.Context.Request.URL.Path | ||
| 89 | +} | ||
| 90 | + | ||
| 91 | +// Site returns base site url as scheme://domain type. | ||
| 92 | +func (input *BeegoInput) Site() string { | ||
| 93 | + return input.Scheme() + "://" + input.Domain() | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +// Scheme returns request scheme as "http" or "https". | ||
| 97 | +func (input *BeegoInput) Scheme() string { | ||
| 98 | + if scheme := input.Header("X-Forwarded-Proto"); scheme != "" { | ||
| 99 | + return scheme | ||
| 100 | + } | ||
| 101 | + if input.Context.Request.URL.Scheme != "" { | ||
| 102 | + return input.Context.Request.URL.Scheme | ||
| 103 | + } | ||
| 104 | + if input.Context.Request.TLS == nil { | ||
| 105 | + return "http" | ||
| 106 | + } | ||
| 107 | + return "https" | ||
| 108 | +} | ||
| 109 | + | ||
| 110 | +// Domain returns host name. | ||
| 111 | +// Alias of Host method. | ||
| 112 | +func (input *BeegoInput) Domain() string { | ||
| 113 | + return input.Host() | ||
| 114 | +} | ||
| 115 | + | ||
| 116 | +// Host returns host name. | ||
| 117 | +// if no host info in request, return localhost. | ||
| 118 | +func (input *BeegoInput) Host() string { | ||
| 119 | + if input.Context.Request.Host != "" { | ||
| 120 | + if hostPart, _, err := net.SplitHostPort(input.Context.Request.Host); err == nil { | ||
| 121 | + return hostPart | ||
| 122 | + } | ||
| 123 | + return input.Context.Request.Host | ||
| 124 | + } | ||
| 125 | + return "localhost" | ||
| 126 | +} | ||
| 127 | + | ||
| 128 | +// Method returns http request method. | ||
| 129 | +func (input *BeegoInput) Method() string { | ||
| 130 | + return input.Context.Request.Method | ||
| 131 | +} | ||
| 132 | + | ||
| 133 | +// Is returns boolean of this request is on given method, such as Is("POST"). | ||
| 134 | +func (input *BeegoInput) Is(method string) bool { | ||
| 135 | + return input.Method() == method | ||
| 136 | +} | ||
| 137 | + | ||
| 138 | +// IsGet Is this a GET method request? | ||
| 139 | +func (input *BeegoInput) IsGet() bool { | ||
| 140 | + return input.Is("GET") | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +// IsPost Is this a POST method request? | ||
| 144 | +func (input *BeegoInput) IsPost() bool { | ||
| 145 | + return input.Is("POST") | ||
| 146 | +} | ||
| 147 | + | ||
| 148 | +// IsHead Is this a Head method request? | ||
| 149 | +func (input *BeegoInput) IsHead() bool { | ||
| 150 | + return input.Is("HEAD") | ||
| 151 | +} | ||
| 152 | + | ||
| 153 | +// IsOptions Is this a OPTIONS method request? | ||
| 154 | +func (input *BeegoInput) IsOptions() bool { | ||
| 155 | + return input.Is("OPTIONS") | ||
| 156 | +} | ||
| 157 | + | ||
| 158 | +// IsPut Is this a PUT method request? | ||
| 159 | +func (input *BeegoInput) IsPut() bool { | ||
| 160 | + return input.Is("PUT") | ||
| 161 | +} | ||
| 162 | + | ||
| 163 | +// IsDelete Is this a DELETE method request? | ||
| 164 | +func (input *BeegoInput) IsDelete() bool { | ||
| 165 | + return input.Is("DELETE") | ||
| 166 | +} | ||
| 167 | + | ||
| 168 | +// IsPatch Is this a PATCH method request? | ||
| 169 | +func (input *BeegoInput) IsPatch() bool { | ||
| 170 | + return input.Is("PATCH") | ||
| 171 | +} | ||
| 172 | + | ||
| 173 | +// IsAjax returns boolean of this request is generated by ajax. | ||
| 174 | +func (input *BeegoInput) IsAjax() bool { | ||
| 175 | + return input.Header("X-Requested-With") == "XMLHttpRequest" | ||
| 176 | +} | ||
| 177 | + | ||
| 178 | +// IsSecure returns boolean of this request is in https. | ||
| 179 | +func (input *BeegoInput) IsSecure() bool { | ||
| 180 | + return input.Scheme() == "https" | ||
| 181 | +} | ||
| 182 | + | ||
| 183 | +// IsWebsocket returns boolean of this request is in webSocket. | ||
| 184 | +func (input *BeegoInput) IsWebsocket() bool { | ||
| 185 | + return input.Header("Upgrade") == "websocket" | ||
| 186 | +} | ||
| 187 | + | ||
| 188 | +// IsUpload returns boolean of whether file uploads in this request or not.. | ||
| 189 | +func (input *BeegoInput) IsUpload() bool { | ||
| 190 | + return strings.Contains(input.Header("Content-Type"), "multipart/form-data") | ||
| 191 | +} | ||
| 192 | + | ||
| 193 | +// AcceptsHTML Checks if request accepts html response | ||
| 194 | +func (input *BeegoInput) AcceptsHTML() bool { | ||
| 195 | + return acceptsHTMLRegex.MatchString(input.Header("Accept")) | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +// AcceptsXML Checks if request accepts xml response | ||
| 199 | +func (input *BeegoInput) AcceptsXML() bool { | ||
| 200 | + return acceptsXMLRegex.MatchString(input.Header("Accept")) | ||
| 201 | +} | ||
| 202 | + | ||
| 203 | +// AcceptsJSON Checks if request accepts json response | ||
| 204 | +func (input *BeegoInput) AcceptsJSON() bool { | ||
| 205 | + return acceptsJSONRegex.MatchString(input.Header("Accept")) | ||
| 206 | +} | ||
| 207 | +// AcceptsYAML Checks if request accepts json response | ||
| 208 | +func (input *BeegoInput) AcceptsYAML() bool { | ||
| 209 | + return acceptsYAMLRegex.MatchString(input.Header("Accept")) | ||
| 210 | +} | ||
| 211 | + | ||
| 212 | +// IP returns request client ip. | ||
| 213 | +// if in proxy, return first proxy id. | ||
| 214 | +// if error, return RemoteAddr. | ||
| 215 | +func (input *BeegoInput) IP() string { | ||
| 216 | + ips := input.Proxy() | ||
| 217 | + if len(ips) > 0 && ips[0] != "" { | ||
| 218 | + rip, _, err := net.SplitHostPort(ips[0]) | ||
| 219 | + if err != nil { | ||
| 220 | + rip = ips[0] | ||
| 221 | + } | ||
| 222 | + return rip | ||
| 223 | + } | ||
| 224 | + if ip, _, err := net.SplitHostPort(input.Context.Request.RemoteAddr); err == nil { | ||
| 225 | + return ip | ||
| 226 | + } | ||
| 227 | + return input.Context.Request.RemoteAddr | ||
| 228 | +} | ||
| 229 | + | ||
| 230 | +// Proxy returns proxy client ips slice. | ||
| 231 | +func (input *BeegoInput) Proxy() []string { | ||
| 232 | + if ips := input.Header("X-Forwarded-For"); ips != "" { | ||
| 233 | + return strings.Split(ips, ",") | ||
| 234 | + } | ||
| 235 | + return []string{} | ||
| 236 | +} | ||
| 237 | + | ||
| 238 | +// Referer returns http referer header. | ||
| 239 | +func (input *BeegoInput) Referer() string { | ||
| 240 | + return input.Header("Referer") | ||
| 241 | +} | ||
| 242 | + | ||
| 243 | +// Refer returns http referer header. | ||
| 244 | +func (input *BeegoInput) Refer() string { | ||
| 245 | + return input.Referer() | ||
| 246 | +} | ||
| 247 | + | ||
| 248 | +// SubDomains returns sub domain string. | ||
| 249 | +// if aa.bb.domain.com, returns aa.bb . | ||
| 250 | +func (input *BeegoInput) SubDomains() string { | ||
| 251 | + parts := strings.Split(input.Host(), ".") | ||
| 252 | + if len(parts) >= 3 { | ||
| 253 | + return strings.Join(parts[:len(parts)-2], ".") | ||
| 254 | + } | ||
| 255 | + return "" | ||
| 256 | +} | ||
| 257 | + | ||
| 258 | +// Port returns request client port. | ||
| 259 | +// when error or empty, return 80. | ||
| 260 | +func (input *BeegoInput) Port() int { | ||
| 261 | + if _, portPart, err := net.SplitHostPort(input.Context.Request.Host); err == nil { | ||
| 262 | + port, _ := strconv.Atoi(portPart) | ||
| 263 | + return port | ||
| 264 | + } | ||
| 265 | + return 80 | ||
| 266 | +} | ||
| 267 | + | ||
| 268 | +// UserAgent returns request client user agent string. | ||
| 269 | +func (input *BeegoInput) UserAgent() string { | ||
| 270 | + return input.Header("User-Agent") | ||
| 271 | +} | ||
| 272 | + | ||
| 273 | +// ParamsLen return the length of the params | ||
| 274 | +func (input *BeegoInput) ParamsLen() int { | ||
| 275 | + return len(input.pnames) | ||
| 276 | +} | ||
| 277 | + | ||
| 278 | +// Param returns router param by a given key. | ||
| 279 | +func (input *BeegoInput) Param(key string) string { | ||
| 280 | + for i, v := range input.pnames { | ||
| 281 | + if v == key && i <= len(input.pvalues) { | ||
| 282 | + return input.pvalues[i] | ||
| 283 | + } | ||
| 284 | + } | ||
| 285 | + return "" | ||
| 286 | +} | ||
| 287 | + | ||
| 288 | +// Params returns the map[key]value. | ||
| 289 | +func (input *BeegoInput) Params() map[string]string { | ||
| 290 | + m := make(map[string]string) | ||
| 291 | + for i, v := range input.pnames { | ||
| 292 | + if i <= len(input.pvalues) { | ||
| 293 | + m[v] = input.pvalues[i] | ||
| 294 | + } | ||
| 295 | + } | ||
| 296 | + return m | ||
| 297 | +} | ||
| 298 | + | ||
| 299 | +// SetParam will set the param with key and value | ||
| 300 | +func (input *BeegoInput) SetParam(key, val string) { | ||
| 301 | + // check if already exists | ||
| 302 | + for i, v := range input.pnames { | ||
| 303 | + if v == key && i <= len(input.pvalues) { | ||
| 304 | + input.pvalues[i] = val | ||
| 305 | + return | ||
| 306 | + } | ||
| 307 | + } | ||
| 308 | + input.pvalues = append(input.pvalues, val) | ||
| 309 | + input.pnames = append(input.pnames, key) | ||
| 310 | +} | ||
| 311 | + | ||
| 312 | +// ResetParams clears any of the input's Params | ||
| 313 | +// This function is used to clear parameters so they may be reset between filter | ||
| 314 | +// passes. | ||
| 315 | +func (input *BeegoInput) ResetParams() { | ||
| 316 | + input.pnames = input.pnames[:0] | ||
| 317 | + input.pvalues = input.pvalues[:0] | ||
| 318 | +} | ||
| 319 | + | ||
| 320 | +// Query returns input data item string by a given string. | ||
| 321 | +func (input *BeegoInput) Query(key string) string { | ||
| 322 | + if val := input.Param(key); val != "" { | ||
| 323 | + return val | ||
| 324 | + } | ||
| 325 | + if input.Context.Request.Form == nil { | ||
| 326 | + input.Context.Request.ParseForm() | ||
| 327 | + } | ||
| 328 | + return input.Context.Request.Form.Get(key) | ||
| 329 | +} | ||
| 330 | + | ||
| 331 | +// Header returns request header item string by a given string. | ||
| 332 | +// if non-existed, return empty string. | ||
| 333 | +func (input *BeegoInput) Header(key string) string { | ||
| 334 | + return input.Context.Request.Header.Get(key) | ||
| 335 | +} | ||
| 336 | + | ||
| 337 | +// Cookie returns request cookie item string by a given key. | ||
| 338 | +// if non-existed, return empty string. | ||
| 339 | +func (input *BeegoInput) Cookie(key string) string { | ||
| 340 | + ck, err := input.Context.Request.Cookie(key) | ||
| 341 | + if err != nil { | ||
| 342 | + return "" | ||
| 343 | + } | ||
| 344 | + return ck.Value | ||
| 345 | +} | ||
| 346 | + | ||
| 347 | +// Session returns current session item value by a given key. | ||
| 348 | +// if non-existed, return nil. | ||
| 349 | +func (input *BeegoInput) Session(key interface{}) interface{} { | ||
| 350 | + return input.CruSession.Get(key) | ||
| 351 | +} | ||
| 352 | + | ||
| 353 | +// CopyBody returns the raw request body data as bytes. | ||
| 354 | +func (input *BeegoInput) CopyBody(MaxMemory int64) []byte { | ||
| 355 | + if input.Context.Request.Body == nil { | ||
| 356 | + return []byte{} | ||
| 357 | + } | ||
| 358 | + | ||
| 359 | + var requestbody []byte | ||
| 360 | + safe := &io.LimitedReader{R: input.Context.Request.Body, N: MaxMemory} | ||
| 361 | + if input.Header("Content-Encoding") == "gzip" { | ||
| 362 | + reader, err := gzip.NewReader(safe) | ||
| 363 | + if err != nil { | ||
| 364 | + return nil | ||
| 365 | + } | ||
| 366 | + requestbody, _ = ioutil.ReadAll(reader) | ||
| 367 | + } else { | ||
| 368 | + requestbody, _ = ioutil.ReadAll(safe) | ||
| 369 | + } | ||
| 370 | + | ||
| 371 | + input.Context.Request.Body.Close() | ||
| 372 | + bf := bytes.NewBuffer(requestbody) | ||
| 373 | + input.Context.Request.Body = http.MaxBytesReader(input.Context.ResponseWriter, ioutil.NopCloser(bf), MaxMemory) | ||
| 374 | + input.RequestBody = requestbody | ||
| 375 | + return requestbody | ||
| 376 | +} | ||
| 377 | + | ||
| 378 | +// Data return the implicit data in the input | ||
| 379 | +func (input *BeegoInput) Data() map[interface{}]interface{} { | ||
| 380 | + if input.data == nil { | ||
| 381 | + input.data = make(map[interface{}]interface{}) | ||
| 382 | + } | ||
| 383 | + return input.data | ||
| 384 | +} | ||
| 385 | + | ||
| 386 | +// GetData returns the stored data in this context. | ||
| 387 | +func (input *BeegoInput) GetData(key interface{}) interface{} { | ||
| 388 | + if v, ok := input.data[key]; ok { | ||
| 389 | + return v | ||
| 390 | + } | ||
| 391 | + return nil | ||
| 392 | +} | ||
| 393 | + | ||
| 394 | +// SetData stores data with given key in this context. | ||
| 395 | +// This data are only available in this context. | ||
| 396 | +func (input *BeegoInput) SetData(key, val interface{}) { | ||
| 397 | + if input.data == nil { | ||
| 398 | + input.data = make(map[interface{}]interface{}) | ||
| 399 | + } | ||
| 400 | + input.data[key] = val | ||
| 401 | +} | ||
| 402 | + | ||
| 403 | +// ParseFormOrMulitForm parseForm or parseMultiForm based on Content-type | ||
| 404 | +func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error { | ||
| 405 | + // Parse the body depending on the content type. | ||
| 406 | + if strings.Contains(input.Header("Content-Type"), "multipart/form-data") { | ||
| 407 | + if err := input.Context.Request.ParseMultipartForm(maxMemory); err != nil { | ||
| 408 | + return errors.New("Error parsing request body:" + err.Error()) | ||
| 409 | + } | ||
| 410 | + } else if err := input.Context.Request.ParseForm(); err != nil { | ||
| 411 | + return errors.New("Error parsing request body:" + err.Error()) | ||
| 412 | + } | ||
| 413 | + return nil | ||
| 414 | +} | ||
| 415 | + | ||
| 416 | +// Bind data from request.Form[key] to dest | ||
| 417 | +// like /?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie | ||
| 418 | +// var id int beegoInput.Bind(&id, "id") id ==123 | ||
| 419 | +// var isok bool beegoInput.Bind(&isok, "isok") isok ==true | ||
| 420 | +// var ft float64 beegoInput.Bind(&ft, "ft") ft ==1.2 | ||
| 421 | +// ol := make([]int, 0, 2) beegoInput.Bind(&ol, "ol") ol ==[1 2] | ||
| 422 | +// ul := make([]string, 0, 2) beegoInput.Bind(&ul, "ul") ul ==[str array] | ||
| 423 | +// user struct{Name} beegoInput.Bind(&user, "user") user == {Name:"astaxie"} | ||
| 424 | +func (input *BeegoInput) Bind(dest interface{}, key string) error { | ||
| 425 | + value := reflect.ValueOf(dest) | ||
| 426 | + if value.Kind() != reflect.Ptr { | ||
| 427 | + return errors.New("beego: non-pointer passed to Bind: " + key) | ||
| 428 | + } | ||
| 429 | + value = value.Elem() | ||
| 430 | + if !value.CanSet() { | ||
| 431 | + return errors.New("beego: non-settable variable passed to Bind: " + key) | ||
| 432 | + } | ||
| 433 | + typ := value.Type() | ||
| 434 | + // Get real type if dest define with interface{}. | ||
| 435 | + // e.g var dest interface{} dest=1.0 | ||
| 436 | + if value.Kind() == reflect.Interface { | ||
| 437 | + typ = value.Elem().Type() | ||
| 438 | + } | ||
| 439 | + rv := input.bind(key, typ) | ||
| 440 | + if !rv.IsValid() { | ||
| 441 | + return errors.New("beego: reflect value is empty") | ||
| 442 | + } | ||
| 443 | + value.Set(rv) | ||
| 444 | + return nil | ||
| 445 | +} | ||
| 446 | + | ||
| 447 | +func (input *BeegoInput) bind(key string, typ reflect.Type) reflect.Value { | ||
| 448 | + if input.Context.Request.Form == nil { | ||
| 449 | + input.Context.Request.ParseForm() | ||
| 450 | + } | ||
| 451 | + rv := reflect.Zero(typ) | ||
| 452 | + switch typ.Kind() { | ||
| 453 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
| 454 | + val := input.Query(key) | ||
| 455 | + if len(val) == 0 { | ||
| 456 | + return rv | ||
| 457 | + } | ||
| 458 | + rv = input.bindInt(val, typ) | ||
| 459 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | ||
| 460 | + val := input.Query(key) | ||
| 461 | + if len(val) == 0 { | ||
| 462 | + return rv | ||
| 463 | + } | ||
| 464 | + rv = input.bindUint(val, typ) | ||
| 465 | + case reflect.Float32, reflect.Float64: | ||
| 466 | + val := input.Query(key) | ||
| 467 | + if len(val) == 0 { | ||
| 468 | + return rv | ||
| 469 | + } | ||
| 470 | + rv = input.bindFloat(val, typ) | ||
| 471 | + case reflect.String: | ||
| 472 | + val := input.Query(key) | ||
| 473 | + if len(val) == 0 { | ||
| 474 | + return rv | ||
| 475 | + } | ||
| 476 | + rv = input.bindString(val, typ) | ||
| 477 | + case reflect.Bool: | ||
| 478 | + val := input.Query(key) | ||
| 479 | + if len(val) == 0 { | ||
| 480 | + return rv | ||
| 481 | + } | ||
| 482 | + rv = input.bindBool(val, typ) | ||
| 483 | + case reflect.Slice: | ||
| 484 | + rv = input.bindSlice(&input.Context.Request.Form, key, typ) | ||
| 485 | + case reflect.Struct: | ||
| 486 | + rv = input.bindStruct(&input.Context.Request.Form, key, typ) | ||
| 487 | + case reflect.Ptr: | ||
| 488 | + rv = input.bindPoint(key, typ) | ||
| 489 | + case reflect.Map: | ||
| 490 | + rv = input.bindMap(&input.Context.Request.Form, key, typ) | ||
| 491 | + } | ||
| 492 | + return rv | ||
| 493 | +} | ||
| 494 | + | ||
| 495 | +func (input *BeegoInput) bindValue(val string, typ reflect.Type) reflect.Value { | ||
| 496 | + rv := reflect.Zero(typ) | ||
| 497 | + switch typ.Kind() { | ||
| 498 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
| 499 | + rv = input.bindInt(val, typ) | ||
| 500 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | ||
| 501 | + rv = input.bindUint(val, typ) | ||
| 502 | + case reflect.Float32, reflect.Float64: | ||
| 503 | + rv = input.bindFloat(val, typ) | ||
| 504 | + case reflect.String: | ||
| 505 | + rv = input.bindString(val, typ) | ||
| 506 | + case reflect.Bool: | ||
| 507 | + rv = input.bindBool(val, typ) | ||
| 508 | + case reflect.Slice: | ||
| 509 | + rv = input.bindSlice(&url.Values{"": {val}}, "", typ) | ||
| 510 | + case reflect.Struct: | ||
| 511 | + rv = input.bindStruct(&url.Values{"": {val}}, "", typ) | ||
| 512 | + case reflect.Ptr: | ||
| 513 | + rv = input.bindPoint(val, typ) | ||
| 514 | + case reflect.Map: | ||
| 515 | + rv = input.bindMap(&url.Values{"": {val}}, "", typ) | ||
| 516 | + } | ||
| 517 | + return rv | ||
| 518 | +} | ||
| 519 | + | ||
| 520 | +func (input *BeegoInput) bindInt(val string, typ reflect.Type) reflect.Value { | ||
| 521 | + intValue, err := strconv.ParseInt(val, 10, 64) | ||
| 522 | + if err != nil { | ||
| 523 | + return reflect.Zero(typ) | ||
| 524 | + } | ||
| 525 | + pValue := reflect.New(typ) | ||
| 526 | + pValue.Elem().SetInt(intValue) | ||
| 527 | + return pValue.Elem() | ||
| 528 | +} | ||
| 529 | + | ||
| 530 | +func (input *BeegoInput) bindUint(val string, typ reflect.Type) reflect.Value { | ||
| 531 | + uintValue, err := strconv.ParseUint(val, 10, 64) | ||
| 532 | + if err != nil { | ||
| 533 | + return reflect.Zero(typ) | ||
| 534 | + } | ||
| 535 | + pValue := reflect.New(typ) | ||
| 536 | + pValue.Elem().SetUint(uintValue) | ||
| 537 | + return pValue.Elem() | ||
| 538 | +} | ||
| 539 | + | ||
| 540 | +func (input *BeegoInput) bindFloat(val string, typ reflect.Type) reflect.Value { | ||
| 541 | + floatValue, err := strconv.ParseFloat(val, 64) | ||
| 542 | + if err != nil { | ||
| 543 | + return reflect.Zero(typ) | ||
| 544 | + } | ||
| 545 | + pValue := reflect.New(typ) | ||
| 546 | + pValue.Elem().SetFloat(floatValue) | ||
| 547 | + return pValue.Elem() | ||
| 548 | +} | ||
| 549 | + | ||
| 550 | +func (input *BeegoInput) bindString(val string, typ reflect.Type) reflect.Value { | ||
| 551 | + return reflect.ValueOf(val) | ||
| 552 | +} | ||
| 553 | + | ||
| 554 | +func (input *BeegoInput) bindBool(val string, typ reflect.Type) reflect.Value { | ||
| 555 | + val = strings.TrimSpace(strings.ToLower(val)) | ||
| 556 | + switch val { | ||
| 557 | + case "true", "on", "1": | ||
| 558 | + return reflect.ValueOf(true) | ||
| 559 | + } | ||
| 560 | + return reflect.ValueOf(false) | ||
| 561 | +} | ||
| 562 | + | ||
| 563 | +type sliceValue struct { | ||
| 564 | + index int // Index extracted from brackets. If -1, no index was provided. | ||
| 565 | + value reflect.Value // the bound value for this slice element. | ||
| 566 | +} | ||
| 567 | + | ||
| 568 | +func (input *BeegoInput) bindSlice(params *url.Values, key string, typ reflect.Type) reflect.Value { | ||
| 569 | + maxIndex := -1 | ||
| 570 | + numNoIndex := 0 | ||
| 571 | + sliceValues := []sliceValue{} | ||
| 572 | + for reqKey, vals := range *params { | ||
| 573 | + if !strings.HasPrefix(reqKey, key+"[") { | ||
| 574 | + continue | ||
| 575 | + } | ||
| 576 | + // Extract the index, and the index where a sub-key starts. (e.g. field[0].subkey) | ||
| 577 | + index := -1 | ||
| 578 | + leftBracket, rightBracket := len(key), strings.Index(reqKey[len(key):], "]")+len(key) | ||
| 579 | + if rightBracket > leftBracket+1 { | ||
| 580 | + index, _ = strconv.Atoi(reqKey[leftBracket+1 : rightBracket]) | ||
| 581 | + } | ||
| 582 | + subKeyIndex := rightBracket + 1 | ||
| 583 | + | ||
| 584 | + // Handle the indexed case. | ||
| 585 | + if index > -1 { | ||
| 586 | + if index > maxIndex { | ||
| 587 | + maxIndex = index | ||
| 588 | + } | ||
| 589 | + sliceValues = append(sliceValues, sliceValue{ | ||
| 590 | + index: index, | ||
| 591 | + value: input.bind(reqKey[:subKeyIndex], typ.Elem()), | ||
| 592 | + }) | ||
| 593 | + continue | ||
| 594 | + } | ||
| 595 | + | ||
| 596 | + // It's an un-indexed element. (e.g. element[]) | ||
| 597 | + numNoIndex += len(vals) | ||
| 598 | + for _, val := range vals { | ||
| 599 | + // Unindexed values can only be direct-bound. | ||
| 600 | + sliceValues = append(sliceValues, sliceValue{ | ||
| 601 | + index: -1, | ||
| 602 | + value: input.bindValue(val, typ.Elem()), | ||
| 603 | + }) | ||
| 604 | + } | ||
| 605 | + } | ||
| 606 | + resultArray := reflect.MakeSlice(typ, maxIndex+1, maxIndex+1+numNoIndex) | ||
| 607 | + for _, sv := range sliceValues { | ||
| 608 | + if sv.index != -1 { | ||
| 609 | + resultArray.Index(sv.index).Set(sv.value) | ||
| 610 | + } else { | ||
| 611 | + resultArray = reflect.Append(resultArray, sv.value) | ||
| 612 | + } | ||
| 613 | + } | ||
| 614 | + return resultArray | ||
| 615 | +} | ||
| 616 | + | ||
| 617 | +func (input *BeegoInput) bindStruct(params *url.Values, key string, typ reflect.Type) reflect.Value { | ||
| 618 | + result := reflect.New(typ).Elem() | ||
| 619 | + fieldValues := make(map[string]reflect.Value) | ||
| 620 | + for reqKey, val := range *params { | ||
| 621 | + var fieldName string | ||
| 622 | + if strings.HasPrefix(reqKey, key+".") { | ||
| 623 | + fieldName = reqKey[len(key)+1:] | ||
| 624 | + } else if strings.HasPrefix(reqKey, key+"[") && reqKey[len(reqKey)-1] == ']' { | ||
| 625 | + fieldName = reqKey[len(key)+1 : len(reqKey)-1] | ||
| 626 | + } else { | ||
| 627 | + continue | ||
| 628 | + } | ||
| 629 | + | ||
| 630 | + if _, ok := fieldValues[fieldName]; !ok { | ||
| 631 | + // Time to bind this field. Get it and make sure we can set it. | ||
| 632 | + fieldValue := result.FieldByName(fieldName) | ||
| 633 | + if !fieldValue.IsValid() { | ||
| 634 | + continue | ||
| 635 | + } | ||
| 636 | + if !fieldValue.CanSet() { | ||
| 637 | + continue | ||
| 638 | + } | ||
| 639 | + boundVal := input.bindValue(val[0], fieldValue.Type()) | ||
| 640 | + fieldValue.Set(boundVal) | ||
| 641 | + fieldValues[fieldName] = boundVal | ||
| 642 | + } | ||
| 643 | + } | ||
| 644 | + | ||
| 645 | + return result | ||
| 646 | +} | ||
| 647 | + | ||
| 648 | +func (input *BeegoInput) bindPoint(key string, typ reflect.Type) reflect.Value { | ||
| 649 | + return input.bind(key, typ.Elem()).Addr() | ||
| 650 | +} | ||
| 651 | + | ||
| 652 | +func (input *BeegoInput) bindMap(params *url.Values, key string, typ reflect.Type) reflect.Value { | ||
| 653 | + var ( | ||
| 654 | + result = reflect.MakeMap(typ) | ||
| 655 | + keyType = typ.Key() | ||
| 656 | + valueType = typ.Elem() | ||
| 657 | + ) | ||
| 658 | + for paramName, values := range *params { | ||
| 659 | + if !strings.HasPrefix(paramName, key+"[") || paramName[len(paramName)-1] != ']' { | ||
| 660 | + continue | ||
| 661 | + } | ||
| 662 | + | ||
| 663 | + key := paramName[len(key)+1 : len(paramName)-1] | ||
| 664 | + result.SetMapIndex(input.bindValue(key, keyType), input.bindValue(values[0], valueType)) | ||
| 665 | + } | ||
| 666 | + return result | ||
| 667 | +} |
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
| 2 | +// | ||
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +// you may not use this file except in compliance with the License. | ||
| 5 | +// You may obtain a copy of the License at | ||
| 6 | +// | ||
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +// | ||
| 9 | +// Unless required by applicable law or agreed to in writing, software | ||
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +// See the License for the specific language governing permissions and | ||
| 13 | +// limitations under the License. | ||
| 14 | + | ||
| 15 | +package context | ||
| 16 | + | ||
| 17 | +import ( | ||
| 18 | + "bytes" | ||
| 19 | + "encoding/json" | ||
| 20 | + "encoding/xml" | ||
| 21 | + "errors" | ||
| 22 | + "fmt" | ||
| 23 | + "html/template" | ||
| 24 | + "io" | ||
| 25 | + "mime" | ||
| 26 | + "net/http" | ||
| 27 | + "net/url" | ||
| 28 | + "os" | ||
| 29 | + "path/filepath" | ||
| 30 | + "strconv" | ||
| 31 | + "strings" | ||
| 32 | + "time" | ||
| 33 | + "gopkg.in/yaml.v2" | ||
| 34 | +) | ||
| 35 | + | ||
| 36 | +// BeegoOutput does work for sending response header. | ||
| 37 | +type BeegoOutput struct { | ||
| 38 | + Context *Context | ||
| 39 | + Status int | ||
| 40 | + EnableGzip bool | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +// NewOutput returns new BeegoOutput. | ||
| 44 | +// it contains nothing now. | ||
| 45 | +func NewOutput() *BeegoOutput { | ||
| 46 | + return &BeegoOutput{} | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +// Reset init BeegoOutput | ||
| 50 | +func (output *BeegoOutput) Reset(ctx *Context) { | ||
| 51 | + output.Context = ctx | ||
| 52 | + output.Status = 0 | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +// Header sets response header item string via given key. | ||
| 56 | +func (output *BeegoOutput) Header(key, val string) { | ||
| 57 | + output.Context.ResponseWriter.Header().Set(key, val) | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +// Body sets response body content. | ||
| 61 | +// if EnableGzip, compress content string. | ||
| 62 | +// it sends out response body directly. | ||
| 63 | +func (output *BeegoOutput) Body(content []byte) error { | ||
| 64 | + var encoding string | ||
| 65 | + var buf = &bytes.Buffer{} | ||
| 66 | + if output.EnableGzip { | ||
| 67 | + encoding = ParseEncoding(output.Context.Request) | ||
| 68 | + } | ||
| 69 | + if b, n, _ := WriteBody(encoding, buf, content); b { | ||
| 70 | + output.Header("Content-Encoding", n) | ||
| 71 | + output.Header("Content-Length", strconv.Itoa(buf.Len())) | ||
| 72 | + } else { | ||
| 73 | + output.Header("Content-Length", strconv.Itoa(len(content))) | ||
| 74 | + } | ||
| 75 | + // Write status code if it has been set manually | ||
| 76 | + // Set it to 0 afterwards to prevent "multiple response.WriteHeader calls" | ||
| 77 | + if output.Status != 0 { | ||
| 78 | + output.Context.ResponseWriter.WriteHeader(output.Status) | ||
| 79 | + output.Status = 0 | ||
| 80 | + } else { | ||
| 81 | + output.Context.ResponseWriter.Started = true | ||
| 82 | + } | ||
| 83 | + io.Copy(output.Context.ResponseWriter, buf) | ||
| 84 | + return nil | ||
| 85 | +} | ||
| 86 | + | ||
| 87 | +// Cookie sets cookie value via given key. | ||
| 88 | +// others are ordered as cookie's max age time, path,domain, secure and httponly. | ||
| 89 | +func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) { | ||
| 90 | + var b bytes.Buffer | ||
| 91 | + fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value)) | ||
| 92 | + | ||
| 93 | + //fix cookie not work in IE | ||
| 94 | + if len(others) > 0 { | ||
| 95 | + var maxAge int64 | ||
| 96 | + | ||
| 97 | + switch v := others[0].(type) { | ||
| 98 | + case int: | ||
| 99 | + maxAge = int64(v) | ||
| 100 | + case int32: | ||
| 101 | + maxAge = int64(v) | ||
| 102 | + case int64: | ||
| 103 | + maxAge = v | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + switch { | ||
| 107 | + case maxAge > 0: | ||
| 108 | + fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(maxAge)*time.Second).UTC().Format(time.RFC1123), maxAge) | ||
| 109 | + case maxAge < 0: | ||
| 110 | + fmt.Fprintf(&b, "; Max-Age=0") | ||
| 111 | + } | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + // the settings below | ||
| 115 | + // Path, Domain, Secure, HttpOnly | ||
| 116 | + // can use nil skip set | ||
| 117 | + | ||
| 118 | + // default "/" | ||
| 119 | + if len(others) > 1 { | ||
| 120 | + if v, ok := others[1].(string); ok && len(v) > 0 { | ||
| 121 | + fmt.Fprintf(&b, "; Path=%s", sanitizeValue(v)) | ||
| 122 | + } | ||
| 123 | + } else { | ||
| 124 | + fmt.Fprintf(&b, "; Path=%s", "/") | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + // default empty | ||
| 128 | + if len(others) > 2 { | ||
| 129 | + if v, ok := others[2].(string); ok && len(v) > 0 { | ||
| 130 | + fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(v)) | ||
| 131 | + } | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + // default empty | ||
| 135 | + if len(others) > 3 { | ||
| 136 | + var secure bool | ||
| 137 | + switch v := others[3].(type) { | ||
| 138 | + case bool: | ||
| 139 | + secure = v | ||
| 140 | + default: | ||
| 141 | + if others[3] != nil { | ||
| 142 | + secure = true | ||
| 143 | + } | ||
| 144 | + } | ||
| 145 | + if secure { | ||
| 146 | + fmt.Fprintf(&b, "; Secure") | ||
| 147 | + } | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + // default false. for session cookie default true | ||
| 151 | + if len(others) > 4 { | ||
| 152 | + if v, ok := others[4].(bool); ok && v { | ||
| 153 | + fmt.Fprintf(&b, "; HttpOnly") | ||
| 154 | + } | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + output.Context.ResponseWriter.Header().Add("Set-Cookie", b.String()) | ||
| 158 | +} | ||
| 159 | + | ||
| 160 | +var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") | ||
| 161 | + | ||
| 162 | +func sanitizeName(n string) string { | ||
| 163 | + return cookieNameSanitizer.Replace(n) | ||
| 164 | +} | ||
| 165 | + | ||
| 166 | +var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ") | ||
| 167 | + | ||
| 168 | +func sanitizeValue(v string) string { | ||
| 169 | + return cookieValueSanitizer.Replace(v) | ||
| 170 | +} | ||
| 171 | + | ||
| 172 | +func jsonRenderer(value interface{}) Renderer { | ||
| 173 | + return rendererFunc(func(ctx *Context) { | ||
| 174 | + ctx.Output.JSON(value, false, false) | ||
| 175 | + }) | ||
| 176 | +} | ||
| 177 | + | ||
| 178 | +func errorRenderer(err error) Renderer { | ||
| 179 | + return rendererFunc(func(ctx *Context) { | ||
| 180 | + ctx.Output.SetStatus(500) | ||
| 181 | + ctx.Output.Body([]byte(err.Error())) | ||
| 182 | + }) | ||
| 183 | +} | ||
| 184 | + | ||
| 185 | +// JSON writes json to response body. | ||
| 186 | +// if encoding is true, it converts utf-8 to \u0000 type. | ||
| 187 | +func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error { | ||
| 188 | + output.Header("Content-Type", "application/json; charset=utf-8") | ||
| 189 | + var content []byte | ||
| 190 | + var err error | ||
| 191 | + if hasIndent { | ||
| 192 | + content, err = json.MarshalIndent(data, "", " ") | ||
| 193 | + } else { | ||
| 194 | + content, err = json.Marshal(data) | ||
| 195 | + } | ||
| 196 | + if err != nil { | ||
| 197 | + http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError) | ||
| 198 | + return err | ||
| 199 | + } | ||
| 200 | + if encoding { | ||
| 201 | + content = []byte(stringsToJSON(string(content))) | ||
| 202 | + } | ||
| 203 | + return output.Body(content) | ||
| 204 | +} | ||
| 205 | + | ||
| 206 | + | ||
| 207 | +// YAML writes yaml to response body. | ||
| 208 | +func (output *BeegoOutput) YAML(data interface{}) error { | ||
| 209 | + output.Header("Content-Type", "application/application/x-yaml; charset=utf-8") | ||
| 210 | + var content []byte | ||
| 211 | + var err error | ||
| 212 | + content, err = yaml.Marshal(data) | ||
| 213 | + if err != nil { | ||
| 214 | + http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError) | ||
| 215 | + return err | ||
| 216 | + } | ||
| 217 | + return output.Body(content) | ||
| 218 | +} | ||
| 219 | + | ||
| 220 | +// JSONP writes jsonp to response body. | ||
| 221 | +func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { | ||
| 222 | + output.Header("Content-Type", "application/javascript; charset=utf-8") | ||
| 223 | + var content []byte | ||
| 224 | + var err error | ||
| 225 | + if hasIndent { | ||
| 226 | + content, err = json.MarshalIndent(data, "", " ") | ||
| 227 | + } else { | ||
| 228 | + content, err = json.Marshal(data) | ||
| 229 | + } | ||
| 230 | + if err != nil { | ||
| 231 | + http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError) | ||
| 232 | + return err | ||
| 233 | + } | ||
| 234 | + callback := output.Context.Input.Query("callback") | ||
| 235 | + if callback == "" { | ||
| 236 | + return errors.New(`"callback" parameter required`) | ||
| 237 | + } | ||
| 238 | + callback = template.JSEscapeString(callback) | ||
| 239 | + callbackContent := bytes.NewBufferString(" if(window." + callback + ")" + callback) | ||
| 240 | + callbackContent.WriteString("(") | ||
| 241 | + callbackContent.Write(content) | ||
| 242 | + callbackContent.WriteString(");\r\n") | ||
| 243 | + return output.Body(callbackContent.Bytes()) | ||
| 244 | +} | ||
| 245 | + | ||
| 246 | +// XML writes xml string to response body. | ||
| 247 | +func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error { | ||
| 248 | + output.Header("Content-Type", "application/xml; charset=utf-8") | ||
| 249 | + var content []byte | ||
| 250 | + var err error | ||
| 251 | + if hasIndent { | ||
| 252 | + content, err = xml.MarshalIndent(data, "", " ") | ||
| 253 | + } else { | ||
| 254 | + content, err = xml.Marshal(data) | ||
| 255 | + } | ||
| 256 | + if err != nil { | ||
| 257 | + http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError) | ||
| 258 | + return err | ||
| 259 | + } | ||
| 260 | + return output.Body(content) | ||
| 261 | +} | ||
| 262 | + | ||
| 263 | +// Download forces response for download file. | ||
| 264 | +// it prepares the download response header automatically. | ||
| 265 | +func (output *BeegoOutput) Download(file string, filename ...string) { | ||
| 266 | + // check get file error, file not found or other error. | ||
| 267 | + if _, err := os.Stat(file); err != nil { | ||
| 268 | + http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file) | ||
| 269 | + return | ||
| 270 | + } | ||
| 271 | + | ||
| 272 | + var fName string | ||
| 273 | + if len(filename) > 0 && filename[0] != "" { | ||
| 274 | + fName = filename[0] | ||
| 275 | + } else { | ||
| 276 | + fName = filepath.Base(file) | ||
| 277 | + } | ||
| 278 | + output.Header("Content-Disposition", "attachment; filename="+url.PathEscape(fName)) | ||
| 279 | + output.Header("Content-Description", "File Transfer") | ||
| 280 | + output.Header("Content-Type", "application/octet-stream") | ||
| 281 | + output.Header("Content-Transfer-Encoding", "binary") | ||
| 282 | + output.Header("Expires", "0") | ||
| 283 | + output.Header("Cache-Control", "must-revalidate") | ||
| 284 | + output.Header("Pragma", "public") | ||
| 285 | + http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file) | ||
| 286 | +} | ||
| 287 | + | ||
| 288 | +// ContentType sets the content type from ext string. | ||
| 289 | +// MIME type is given in mime package. | ||
| 290 | +func (output *BeegoOutput) ContentType(ext string) { | ||
| 291 | + if !strings.HasPrefix(ext, ".") { | ||
| 292 | + ext = "." + ext | ||
| 293 | + } | ||
| 294 | + ctype := mime.TypeByExtension(ext) | ||
| 295 | + if ctype != "" { | ||
| 296 | + output.Header("Content-Type", ctype) | ||
| 297 | + } | ||
| 298 | +} | ||
| 299 | + | ||
| 300 | +// SetStatus sets response status code. | ||
| 301 | +// It writes response header directly. | ||
| 302 | +func (output *BeegoOutput) SetStatus(status int) { | ||
| 303 | + output.Status = status | ||
| 304 | +} | ||
| 305 | + | ||
| 306 | +// IsCachable returns boolean of this request is cached. | ||
| 307 | +// HTTP 304 means cached. | ||
| 308 | +func (output *BeegoOutput) IsCachable() bool { | ||
| 309 | + return output.Status >= 200 && output.Status < 300 || output.Status == 304 | ||
| 310 | +} | ||
| 311 | + | ||
| 312 | +// IsEmpty returns boolean of this request is empty. | ||
| 313 | +// HTTP 201,204 and 304 means empty. | ||
| 314 | +func (output *BeegoOutput) IsEmpty() bool { | ||
| 315 | + return output.Status == 201 || output.Status == 204 || output.Status == 304 | ||
| 316 | +} | ||
| 317 | + | ||
| 318 | +// IsOk returns boolean of this request runs well. | ||
| 319 | +// HTTP 200 means ok. | ||
| 320 | +func (output *BeegoOutput) IsOk() bool { | ||
| 321 | + return output.Status == 200 | ||
| 322 | +} | ||
| 323 | + | ||
| 324 | +// IsSuccessful returns boolean of this request runs successfully. | ||
| 325 | +// HTTP 2xx means ok. | ||
| 326 | +func (output *BeegoOutput) IsSuccessful() bool { | ||
| 327 | + return output.Status >= 200 && output.Status < 300 | ||
| 328 | +} | ||
| 329 | + | ||
| 330 | +// IsRedirect returns boolean of this request is redirection header. | ||
| 331 | +// HTTP 301,302,307 means redirection. | ||
| 332 | +func (output *BeegoOutput) IsRedirect() bool { | ||
| 333 | + return output.Status == 301 || output.Status == 302 || output.Status == 303 || output.Status == 307 | ||
| 334 | +} | ||
| 335 | + | ||
| 336 | +// IsForbidden returns boolean of this request is forbidden. | ||
| 337 | +// HTTP 403 means forbidden. | ||
| 338 | +func (output *BeegoOutput) IsForbidden() bool { | ||
| 339 | + return output.Status == 403 | ||
| 340 | +} | ||
| 341 | + | ||
| 342 | +// IsNotFound returns boolean of this request is not found. | ||
| 343 | +// HTTP 404 means not found. | ||
| 344 | +func (output *BeegoOutput) IsNotFound() bool { | ||
| 345 | + return output.Status == 404 | ||
| 346 | +} | ||
| 347 | + | ||
| 348 | +// IsClientError returns boolean of this request client sends error data. | ||
| 349 | +// HTTP 4xx means client error. | ||
| 350 | +func (output *BeegoOutput) IsClientError() bool { | ||
| 351 | + return output.Status >= 400 && output.Status < 500 | ||
| 352 | +} | ||
| 353 | + | ||
| 354 | +// IsServerError returns boolean of this server handler errors. | ||
| 355 | +// HTTP 5xx means server internal error. | ||
| 356 | +func (output *BeegoOutput) IsServerError() bool { | ||
| 357 | + return output.Status >= 500 && output.Status < 600 | ||
| 358 | +} | ||
| 359 | + | ||
| 360 | +func stringsToJSON(str string) string { | ||
| 361 | + var jsons bytes.Buffer | ||
| 362 | + for _, r := range str { | ||
| 363 | + rint := int(r) | ||
| 364 | + if rint < 128 { | ||
| 365 | + jsons.WriteRune(r) | ||
| 366 | + } else { | ||
| 367 | + jsons.WriteString("\\u") | ||
| 368 | + if rint < 0x100 { | ||
| 369 | + jsons.WriteString("00") | ||
| 370 | + } else if rint < 0x1000 { | ||
| 371 | + jsons.WriteString("0") | ||
| 372 | + } | ||
| 373 | + jsons.WriteString(strconv.FormatInt(int64(rint), 16)) | ||
| 374 | + } | ||
| 375 | + } | ||
| 376 | + return jsons.String() | ||
| 377 | +} | ||
| 378 | + | ||
| 379 | +// Session sets session item value with given key. | ||
| 380 | +func (output *BeegoOutput) Session(name interface{}, value interface{}) { | ||
| 381 | + output.Context.Input.CruSession.Set(name, value) | ||
| 382 | +} |
| 1 | +package param | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "fmt" | ||
| 5 | + "reflect" | ||
| 6 | + | ||
| 7 | + beecontext "github.com/astaxie/beego/context" | ||
| 8 | + "github.com/astaxie/beego/logs" | ||
| 9 | +) | ||
| 10 | + | ||
| 11 | +// ConvertParams converts http method params to values that will be passed to the method controller as arguments | ||
| 12 | +func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) { | ||
| 13 | + result = make([]reflect.Value, 0, len(methodParams)) | ||
| 14 | + for i := 0; i < len(methodParams); i++ { | ||
| 15 | + reflectValue := convertParam(methodParams[i], methodType.In(i), ctx) | ||
| 16 | + result = append(result, reflectValue) | ||
| 17 | + } | ||
| 18 | + return | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Context) (result reflect.Value) { | ||
| 22 | + paramValue := getParamValue(param, ctx) | ||
| 23 | + if paramValue == "" { | ||
| 24 | + if param.required { | ||
| 25 | + ctx.Abort(400, fmt.Sprintf("Missing parameter %s", param.name)) | ||
| 26 | + } else { | ||
| 27 | + paramValue = param.defaultValue | ||
| 28 | + } | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + reflectValue, err := parseValue(param, paramValue, paramType) | ||
| 32 | + if err != nil { | ||
| 33 | + logs.Debug(fmt.Sprintf("Error converting param %s to type %s. Value: %v, Error: %s", param.name, paramType, paramValue, err)) | ||
| 34 | + ctx.Abort(400, fmt.Sprintf("Invalid parameter %s. Can not convert %v to type %s", param.name, paramValue, paramType)) | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + return reflectValue | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +func getParamValue(param *MethodParam, ctx *beecontext.Context) string { | ||
| 41 | + switch param.in { | ||
| 42 | + case body: | ||
| 43 | + return string(ctx.Input.RequestBody) | ||
| 44 | + case header: | ||
| 45 | + return ctx.Input.Header(param.name) | ||
| 46 | + case path: | ||
| 47 | + return ctx.Input.Query(":" + param.name) | ||
| 48 | + default: | ||
| 49 | + return ctx.Input.Query(param.name) | ||
| 50 | + } | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +func parseValue(param *MethodParam, paramValue string, paramType reflect.Type) (result reflect.Value, err error) { | ||
| 54 | + if paramValue == "" { | ||
| 55 | + return reflect.Zero(paramType), nil | ||
| 56 | + } | ||
| 57 | + parser := getParser(param, paramType) | ||
| 58 | + value, err := parser.parse(paramValue, paramType) | ||
| 59 | + if err != nil { | ||
| 60 | + return result, err | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + return safeConvert(reflect.ValueOf(value), paramType) | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +func safeConvert(value reflect.Value, t reflect.Type) (result reflect.Value, err error) { | ||
| 67 | + defer func() { | ||
| 68 | + if r := recover(); r != nil { | ||
| 69 | + var ok bool | ||
| 70 | + err, ok = r.(error) | ||
| 71 | + if !ok { | ||
| 72 | + err = fmt.Errorf("%v", r) | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | + }() | ||
| 76 | + result = value.Convert(t) | ||
| 77 | + return | ||
| 78 | +} |
| 1 | +package param | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "fmt" | ||
| 5 | + "strings" | ||
| 6 | +) | ||
| 7 | + | ||
| 8 | +//MethodParam keeps param information to be auto passed to controller methods | ||
| 9 | +type MethodParam struct { | ||
| 10 | + name string | ||
| 11 | + in paramType | ||
| 12 | + required bool | ||
| 13 | + defaultValue string | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | +type paramType byte | ||
| 17 | + | ||
| 18 | +const ( | ||
| 19 | + param paramType = iota | ||
| 20 | + path | ||
| 21 | + body | ||
| 22 | + header | ||
| 23 | +) | ||
| 24 | + | ||
| 25 | +//New creates a new MethodParam with name and specific options | ||
| 26 | +func New(name string, opts ...MethodParamOption) *MethodParam { | ||
| 27 | + return newParam(name, nil, opts) | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +func newParam(name string, parser paramParser, opts []MethodParamOption) (param *MethodParam) { | ||
| 31 | + param = &MethodParam{name: name} | ||
| 32 | + for _, option := range opts { | ||
| 33 | + option(param) | ||
| 34 | + } | ||
| 35 | + return | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +//Make creates an array of MethodParmas or an empty array | ||
| 39 | +func Make(list ...*MethodParam) []*MethodParam { | ||
| 40 | + if len(list) > 0 { | ||
| 41 | + return list | ||
| 42 | + } | ||
| 43 | + return nil | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +func (mp *MethodParam) String() string { | ||
| 47 | + options := []string{} | ||
| 48 | + result := "param.New(\"" + mp.name + "\"" | ||
| 49 | + if mp.required { | ||
| 50 | + options = append(options, "param.IsRequired") | ||
| 51 | + } | ||
| 52 | + switch mp.in { | ||
| 53 | + case path: | ||
| 54 | + options = append(options, "param.InPath") | ||
| 55 | + case body: | ||
| 56 | + options = append(options, "param.InBody") | ||
| 57 | + case header: | ||
| 58 | + options = append(options, "param.InHeader") | ||
| 59 | + } | ||
| 60 | + if mp.defaultValue != "" { | ||
| 61 | + options = append(options, fmt.Sprintf(`param.Default("%s")`, mp.defaultValue)) | ||
| 62 | + } | ||
| 63 | + if len(options) > 0 { | ||
| 64 | + result += ", " | ||
| 65 | + } | ||
| 66 | + result += strings.Join(options, ", ") | ||
| 67 | + result += ")" | ||
| 68 | + return result | ||
| 69 | +} |
-
请 注册 或 登录 后发表评论