├── .gitignore ├── .travis.yml ├── Dockerfile ├── Godeps ├── Godeps.json ├── Readme └── _workspace │ ├── .gitignore │ └── src │ └── github.com │ ├── Sirupsen │ └── logrus │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── entry.go │ │ ├── entry_test.go │ │ ├── examples │ │ ├── basic │ │ │ └── basic.go │ │ └── hook │ │ │ └── hook.go │ │ ├── exported.go │ │ ├── formatter.go │ │ ├── formatter_bench_test.go │ │ ├── hook_test.go │ │ ├── hooks.go │ │ ├── hooks │ │ ├── airbrake │ │ │ └── airbrake.go │ │ ├── papertrail │ │ │ ├── README.md │ │ │ ├── papertrail.go │ │ │ └── papertrail_test.go │ │ ├── sentry │ │ │ ├── README.md │ │ │ ├── sentry.go │ │ │ └── sentry_test.go │ │ └── syslog │ │ │ ├── README.md │ │ │ ├── syslog.go │ │ │ └── syslog_test.go │ │ ├── json_formatter.go │ │ ├── logger.go │ │ ├── logrus.go │ │ ├── logrus_test.go │ │ ├── terminal_darwin.go │ │ ├── terminal_freebsd.go │ │ ├── terminal_linux.go │ │ ├── terminal_notwindows.go │ │ ├── terminal_windows.go │ │ └── text_formatter.go │ ├── codegangsta │ └── negroni │ │ ├── LICENSE │ │ ├── README.md │ │ ├── doc.go │ │ ├── logger.go │ │ ├── logger_test.go │ │ ├── negroni.go │ │ ├── negroni_test.go │ │ ├── recovery.go │ │ ├── recovery_test.go │ │ ├── response_writer.go │ │ ├── response_writer_test.go │ │ ├── static.go │ │ └── static_test.go │ ├── julienschmidt │ └── httprouter │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── path.go │ │ ├── path_test.go │ │ ├── router.go │ │ ├── router_test.go │ │ ├── tree.go │ │ └── tree_test.go │ └── meatballhat │ └── negroni-logrus │ ├── .gitignore │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ ├── example │ └── example.go │ └── middleware.go ├── LICENSE ├── README.md ├── db.go ├── db.json ├── db_test.go ├── handlers.go ├── handlers_test.go ├── qrest.iml ├── server.go └── server_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | 3 | ### Go template 4 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 5 | *.o 6 | *.a 7 | *.so 8 | 9 | # Folders 10 | _obj 11 | _test 12 | 13 | # Architecture specific extensions/prefixes 14 | *.[568vq] 15 | [568vq].out 16 | 17 | *.cgo1.go 18 | *.cgo2.c 19 | _cgo_defun.c 20 | _cgo_gotypes.go 21 | _cgo_export.* 22 | 23 | _testmain.go 24 | 25 | *.exe 26 | *.test 27 | *.prof 28 | 29 | 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang 2 | 3 | ADD . /go/src/github.com/landaire/qrest 4 | 5 | WORKDIR src/github.com/landaire/qrest 6 | 7 | RUN go get github.com/tools/godep && /go/bin/godep go install github.com/landaire/qrest 8 | 9 | ENTRYPOINT ["/go/bin/qrest"] 10 | 11 | EXPOSE 3000 12 | 13 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "json-server", 3 | "GoVersion": "go1.4.2", 4 | "Deps": [ 5 | { 6 | "ImportPath": "github.com/Sirupsen/logrus", 7 | "Comment": "v0.6.1-10-g6c28e16", 8 | "Rev": "6c28e16200e55c2549705ac24ea58976c07f18ac" 9 | }, 10 | { 11 | "ImportPath": "github.com/codegangsta/negroni", 12 | "Comment": "v0.1-43-g0942b5e", 13 | "Rev": "0942b5e3ae397dd19c85234d7e936cb3c1327a63" 14 | }, 15 | { 16 | "ImportPath": "github.com/julienschmidt/httprouter", 17 | "Rev": "8c199fb6259ffc1af525cc3ad52ee60ba8359669" 18 | }, 19 | { 20 | "ImportPath": "github.com/meatballhat/negroni-logrus", 21 | "Rev": "d5e114d7a445f7a3652cf807d3f83d5dceb8de26" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /Godeps/_workspace/.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /bin 3 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/.gitignore: -------------------------------------------------------------------------------- 1 | logrus 2 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.2 4 | - 1.3 5 | - tip 6 | install: 7 | - go get github.com/stretchr/testify 8 | - go get github.com/stvp/go-udp-testing 9 | - go get github.com/tobi/airbrake-go 10 | - go get github.com/getsentry/raven-go 11 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Simon Eskildsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md: -------------------------------------------------------------------------------- 1 | # Logrus :walrus: [![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) 2 | 3 | Logrus is a structured logger for Go (golang), completely API compatible with 4 | the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not 5 | yet stable (pre 1.0), the core API is unlikely change much but please version 6 | control your Logrus to make sure you aren't fetching latest `master` on every 7 | build.** 8 | 9 | Nicely color-coded in development (when a TTY is attached, otherwise just 10 | plain text): 11 | 12 | ![Colored](http://i.imgur.com/PY7qMwd.png) 13 | 14 | With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash 15 | or Splunk: 16 | 17 | ```json 18 | {"animal":"walrus","level":"info","msg":"A group of walrus emerges from the 19 | ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} 20 | 21 | {"level":"warning","msg":"The group's number increased tremendously!", 22 | "number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"} 23 | 24 | {"animal":"walrus","level":"info","msg":"A giant walrus appears!", 25 | "size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"} 26 | 27 | {"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.", 28 | "size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"} 29 | 30 | {"level":"fatal","msg":"The ice breaks!","number":100,"omg":true, 31 | "time":"2014-03-10 19:57:38.562543128 -0400 EDT"} 32 | ``` 33 | 34 | With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not 35 | attached, the output is compatible with the 36 | [l2met](http://r.32k.io/l2met-introduction) format: 37 | 38 | ```text 39 | time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10 40 | time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122 41 | time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10 42 | time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9 43 | time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100 44 | ``` 45 | 46 | #### Example 47 | 48 | The simplest way to use Logrus is simply the package-level exported logger: 49 | 50 | ```go 51 | package main 52 | 53 | import ( 54 | log "github.com/Sirupsen/logrus" 55 | ) 56 | 57 | func main() { 58 | log.WithFields(log.Fields{ 59 | "animal": "walrus", 60 | }).Info("A walrus appears") 61 | } 62 | ``` 63 | 64 | Note that it's completely api-compatible with the stdlib logger, so you can 65 | replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"` 66 | and you'll now have the flexibility of Logrus. You can customize it all you 67 | want: 68 | 69 | ```go 70 | package main 71 | 72 | import ( 73 | "os" 74 | log "github.com/Sirupsen/logrus" 75 | "github.com/Sirupsen/logrus/hooks/airbrake" 76 | ) 77 | 78 | func init() { 79 | // Log as JSON instead of the default ASCII formatter. 80 | log.SetFormatter(&log.JSONFormatter{}) 81 | 82 | // Use the Airbrake hook to report errors that have Error severity or above to 83 | // an exception tracker. You can create custom hooks, see the Hooks section. 84 | log.AddHook(&logrus_airbrake.AirbrakeHook{}) 85 | 86 | // Output to stderr instead of stdout, could also be a file. 87 | log.SetOutput(os.Stderr) 88 | 89 | // Only log the warning severity or above. 90 | log.SetLevel(log.WarnLevel) 91 | } 92 | 93 | func main() { 94 | log.WithFields(log.Fields{ 95 | "animal": "walrus", 96 | "size": 10, 97 | }).Info("A group of walrus emerges from the ocean") 98 | 99 | log.WithFields(log.Fields{ 100 | "omg": true, 101 | "number": 122, 102 | }).Warn("The group's number increased tremendously!") 103 | 104 | log.WithFields(log.Fields{ 105 | "omg": true, 106 | "number": 100, 107 | }).Fatal("The ice breaks!") 108 | } 109 | ``` 110 | 111 | For more advanced usage such as logging to multiple locations from the same 112 | application, you can also create an instance of the `logrus` Logger: 113 | 114 | ```go 115 | package main 116 | 117 | import ( 118 | "github.com/Sirupsen/logrus" 119 | ) 120 | 121 | // Create a new instance of the logger. You can have any number of instances. 122 | var log = logrus.New() 123 | 124 | func main() { 125 | // The API for setting attributes is a little different than the package level 126 | // exported logger. See Godoc. 127 | log.Out = os.Stderr 128 | 129 | log.WithFields(logrus.Fields{ 130 | "animal": "walrus", 131 | "size": 10, 132 | }).Info("A group of walrus emerges from the ocean") 133 | } 134 | ``` 135 | 136 | #### Fields 137 | 138 | Logrus encourages careful, structured logging though logging fields instead of 139 | long, unparseable error messages. For example, instead of: `log.Fatalf("Failed 140 | to send event %s to topic %s with key %d")`, you should log the much more 141 | discoverable: 142 | 143 | ```go 144 | log.WithFields(log.Fields{ 145 | "event": event, 146 | "topic": topic, 147 | "key": key, 148 | }).Fatal("Failed to send event") 149 | ``` 150 | 151 | We've found this API forces you to think about logging in a way that produces 152 | much more useful logging messages. We've been in countless situations where just 153 | a single added field to a log statement that was already there would've saved us 154 | hours. The `WithFields` call is optional. 155 | 156 | In general, with Logrus using any of the `printf`-family functions should be 157 | seen as a hint you should add a field, however, you can still use the 158 | `printf`-family functions with Logrus. 159 | 160 | #### Hooks 161 | 162 | You can add hooks for logging levels. For example to send errors to an exception 163 | tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to 164 | multiple places simultaneously, e.g. syslog. 165 | 166 | ```go 167 | // Not the real implementation of the Airbrake hook. Just a simple sample. 168 | import ( 169 | log "github.com/Sirupsen/logrus" 170 | ) 171 | 172 | func init() { 173 | log.AddHook(new(AirbrakeHook)) 174 | } 175 | 176 | type AirbrakeHook struct{} 177 | 178 | // `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains 179 | // the fields for the entry. See the Fields section of the README. 180 | func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { 181 | err := airbrake.Notify(entry.Data["error"].(error)) 182 | if err != nil { 183 | log.WithFields(log.Fields{ 184 | "source": "airbrake", 185 | "endpoint": airbrake.Endpoint, 186 | }).Info("Failed to send error to Airbrake") 187 | } 188 | 189 | return nil 190 | } 191 | 192 | // `Levels()` returns a slice of `Levels` the hook is fired for. 193 | func (hook *AirbrakeHook) Levels() []log.Level { 194 | return []log.Level{ 195 | log.ErrorLevel, 196 | log.FatalLevel, 197 | log.PanicLevel, 198 | } 199 | } 200 | ``` 201 | 202 | Logrus comes with built-in hooks. Add those, or your custom hook, in `init`: 203 | 204 | ```go 205 | import ( 206 | log "github.com/Sirupsen/logrus" 207 | "github.com/Sirupsen/logrus/hooks/airbrake" 208 | "github.com/Sirupsen/logrus/hooks/syslog" 209 | "log/syslog" 210 | ) 211 | 212 | func init() { 213 | log.AddHook(new(logrus_airbrake.AirbrakeHook)) 214 | 215 | hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") 216 | if err != nil { 217 | log.Error("Unable to connect to local syslog daemon") 218 | } else { 219 | log.AddHook(hook) 220 | } 221 | } 222 | ``` 223 | 224 | * [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) 225 | Send errors to an exception tracking service compatible with the Airbrake API. 226 | Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. 227 | 228 | * [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) 229 | Send errors to the Papertrail hosted logging service via UDP. 230 | 231 | * [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) 232 | Send errors to remote syslog server. 233 | Uses standard library `log/syslog` behind the scenes. 234 | 235 | * [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus) 236 | Send errors to a channel in hipchat. 237 | 238 | #### Level logging 239 | 240 | Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic. 241 | 242 | ```go 243 | log.Debug("Useful debugging information.") 244 | log.Info("Something noteworthy happened!") 245 | log.Warn("You should probably take a look at this.") 246 | log.Error("Something failed but I'm not quitting.") 247 | // Calls os.Exit(1) after logging 248 | log.Fatal("Bye.") 249 | // Calls panic() after logging 250 | log.Panic("I'm bailing.") 251 | ``` 252 | 253 | You can set the logging level on a `Logger`, then it will only log entries with 254 | that severity or anything above it: 255 | 256 | ```go 257 | // Will log anything that is info or above (warn, error, fatal, panic). Default. 258 | log.SetLevel(log.InfoLevel) 259 | ``` 260 | 261 | It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose 262 | environment if your application has that. 263 | 264 | #### Entries 265 | 266 | Besides the fields added with `WithField` or `WithFields` some fields are 267 | automatically added to all logging events: 268 | 269 | 1. `time`. The timestamp when the entry was created. 270 | 2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after 271 | the `AddFields` call. E.g. `Failed to send event.` 272 | 3. `level`. The logging level. E.g. `info`. 273 | 274 | #### Environments 275 | 276 | Logrus has no notion of environment. 277 | 278 | If you wish for hooks and formatters to only be used in specific environments, 279 | you should handle that yourself. For example, if your application has a global 280 | variable `Environment`, which is a string representation of the environment you 281 | could do: 282 | 283 | ```go 284 | import ( 285 | log "github.com/Sirupsen/logrus" 286 | ) 287 | 288 | init() { 289 | // do something here to set environment depending on an environment variable 290 | // or command-line flag 291 | if Environment == "production" { 292 | log.SetFormatter(logrus.JSONFormatter) 293 | } else { 294 | // The TextFormatter is default, you don't actually have to do this. 295 | log.SetFormatter(logrus.TextFormatter) 296 | } 297 | } 298 | ``` 299 | 300 | This configuration is how `logrus` was intended to be used, but JSON in 301 | production is mostly only useful if you do log aggregation with tools like 302 | Splunk or Logstash. 303 | 304 | #### Formatters 305 | 306 | The built-in logging formatters are: 307 | 308 | * `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise 309 | without colors. 310 | * *Note:* to force colored output when there is no TTY, set the `ForceColors` 311 | field to `true`. To force no colored output even if there is a TTY set the 312 | `DisableColors` field to `true` 313 | * `logrus.JSONFormatter`. Logs fields as JSON. 314 | 315 | Third party logging formatters: 316 | 317 | * [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦. 318 | 319 | You can define your formatter by implementing the `Formatter` interface, 320 | requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a 321 | `Fields` type (`map[string]interface{}`) with all your fields as well as the 322 | default ones (see Entries section above): 323 | 324 | ```go 325 | type MyJSONFormatter struct { 326 | } 327 | 328 | log.SetFormatter(new(MyJSONFormatter)) 329 | 330 | func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { 331 | // Note this doesn't include Time, Level and Message which are available on 332 | // the Entry. Consult `godoc` on information about those fields or read the 333 | // source of the official loggers. 334 | serialized, err := json.Marshal(entry.Data) 335 | if err != nil { 336 | return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) 337 | } 338 | return append(serialized, '\n'), nil 339 | } 340 | ``` 341 | 342 | #### Rotation 343 | 344 | Log rotation is not provided with Logrus. Log rotation should be done by an 345 | external program (like `logrotated(8)`) that can compress and delete old log 346 | entries. It should not be a feature of the application-level logger. 347 | 348 | 349 | [godoc]: https://godoc.org/github.com/Sirupsen/logrus 350 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "time" 9 | ) 10 | 11 | // An entry is the final or intermediate Logrus logging entry. It contains all 12 | // the fields passed with WithField{,s}. It's finally logged when Debug, Info, 13 | // Warn, Error, Fatal or Panic is called on it. These objects can be reused and 14 | // passed around as much as you wish to avoid field duplication. 15 | type Entry struct { 16 | Logger *Logger 17 | 18 | // Contains all the fields set by the user. 19 | Data Fields 20 | 21 | // Time at which the log entry was created 22 | Time time.Time 23 | 24 | // Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic 25 | Level Level 26 | 27 | // Message passed to Debug, Info, Warn, Error, Fatal or Panic 28 | Message string 29 | } 30 | 31 | func NewEntry(logger *Logger) *Entry { 32 | return &Entry{ 33 | Logger: logger, 34 | // Default is three fields, give a little extra room 35 | Data: make(Fields, 5), 36 | } 37 | } 38 | 39 | // Returns a reader for the entry, which is a proxy to the formatter. 40 | func (entry *Entry) Reader() (*bytes.Buffer, error) { 41 | serialized, err := entry.Logger.Formatter.Format(entry) 42 | return bytes.NewBuffer(serialized), err 43 | } 44 | 45 | // Returns the string representation from the reader and ultimately the 46 | // formatter. 47 | func (entry *Entry) String() (string, error) { 48 | reader, err := entry.Reader() 49 | if err != nil { 50 | return "", err 51 | } 52 | 53 | return reader.String(), err 54 | } 55 | 56 | // Add a single field to the Entry. 57 | func (entry *Entry) WithField(key string, value interface{}) *Entry { 58 | return entry.WithFields(Fields{key: value}) 59 | } 60 | 61 | // Add a map of fields to the Entry. 62 | func (entry *Entry) WithFields(fields Fields) *Entry { 63 | data := Fields{} 64 | for k, v := range entry.Data { 65 | data[k] = v 66 | } 67 | for k, v := range fields { 68 | data[k] = v 69 | } 70 | return &Entry{Logger: entry.Logger, Data: data} 71 | } 72 | 73 | func (entry *Entry) log(level Level, msg string) { 74 | entry.Time = time.Now() 75 | entry.Level = level 76 | entry.Message = msg 77 | 78 | if err := entry.Logger.Hooks.Fire(level, entry); err != nil { 79 | entry.Logger.mu.Lock() 80 | fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) 81 | entry.Logger.mu.Unlock() 82 | } 83 | 84 | reader, err := entry.Reader() 85 | if err != nil { 86 | entry.Logger.mu.Lock() 87 | fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) 88 | entry.Logger.mu.Unlock() 89 | } 90 | 91 | entry.Logger.mu.Lock() 92 | defer entry.Logger.mu.Unlock() 93 | 94 | _, err = io.Copy(entry.Logger.Out, reader) 95 | if err != nil { 96 | fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) 97 | } 98 | 99 | // To avoid Entry#log() returning a value that only would make sense for 100 | // panic() to use in Entry#Panic(), we avoid the allocation by checking 101 | // directly here. 102 | if level <= PanicLevel { 103 | panic(entry) 104 | } 105 | } 106 | 107 | func (entry *Entry) Debug(args ...interface{}) { 108 | if entry.Logger.Level >= DebugLevel { 109 | entry.log(DebugLevel, fmt.Sprint(args...)) 110 | } 111 | } 112 | 113 | func (entry *Entry) Print(args ...interface{}) { 114 | entry.Info(args...) 115 | } 116 | 117 | func (entry *Entry) Info(args ...interface{}) { 118 | if entry.Logger.Level >= InfoLevel { 119 | entry.log(InfoLevel, fmt.Sprint(args...)) 120 | } 121 | } 122 | 123 | func (entry *Entry) Warn(args ...interface{}) { 124 | if entry.Logger.Level >= WarnLevel { 125 | entry.log(WarnLevel, fmt.Sprint(args...)) 126 | } 127 | } 128 | 129 | func (entry *Entry) Error(args ...interface{}) { 130 | if entry.Logger.Level >= ErrorLevel { 131 | entry.log(ErrorLevel, fmt.Sprint(args...)) 132 | } 133 | } 134 | 135 | func (entry *Entry) Fatal(args ...interface{}) { 136 | if entry.Logger.Level >= FatalLevel { 137 | entry.log(FatalLevel, fmt.Sprint(args...)) 138 | } 139 | os.Exit(1) 140 | } 141 | 142 | func (entry *Entry) Panic(args ...interface{}) { 143 | if entry.Logger.Level >= PanicLevel { 144 | entry.log(PanicLevel, fmt.Sprint(args...)) 145 | } 146 | panic(fmt.Sprint(args...)) 147 | } 148 | 149 | // Entry Printf family functions 150 | 151 | func (entry *Entry) Debugf(format string, args ...interface{}) { 152 | if entry.Logger.Level >= DebugLevel { 153 | entry.Debug(fmt.Sprintf(format, args...)) 154 | } 155 | } 156 | 157 | func (entry *Entry) Infof(format string, args ...interface{}) { 158 | if entry.Logger.Level >= InfoLevel { 159 | entry.Info(fmt.Sprintf(format, args...)) 160 | } 161 | } 162 | 163 | func (entry *Entry) Printf(format string, args ...interface{}) { 164 | entry.Infof(format, args...) 165 | } 166 | 167 | func (entry *Entry) Warnf(format string, args ...interface{}) { 168 | if entry.Logger.Level >= WarnLevel { 169 | entry.Warn(fmt.Sprintf(format, args...)) 170 | } 171 | } 172 | 173 | func (entry *Entry) Warningf(format string, args ...interface{}) { 174 | entry.Warnf(format, args...) 175 | } 176 | 177 | func (entry *Entry) Errorf(format string, args ...interface{}) { 178 | if entry.Logger.Level >= ErrorLevel { 179 | entry.Error(fmt.Sprintf(format, args...)) 180 | } 181 | } 182 | 183 | func (entry *Entry) Fatalf(format string, args ...interface{}) { 184 | if entry.Logger.Level >= FatalLevel { 185 | entry.Fatal(fmt.Sprintf(format, args...)) 186 | } 187 | } 188 | 189 | func (entry *Entry) Panicf(format string, args ...interface{}) { 190 | if entry.Logger.Level >= PanicLevel { 191 | entry.Panic(fmt.Sprintf(format, args...)) 192 | } 193 | } 194 | 195 | // Entry Println family functions 196 | 197 | func (entry *Entry) Debugln(args ...interface{}) { 198 | if entry.Logger.Level >= DebugLevel { 199 | entry.Debug(entry.sprintlnn(args...)) 200 | } 201 | } 202 | 203 | func (entry *Entry) Infoln(args ...interface{}) { 204 | if entry.Logger.Level >= InfoLevel { 205 | entry.Info(entry.sprintlnn(args...)) 206 | } 207 | } 208 | 209 | func (entry *Entry) Println(args ...interface{}) { 210 | entry.Infoln(args...) 211 | } 212 | 213 | func (entry *Entry) Warnln(args ...interface{}) { 214 | if entry.Logger.Level >= WarnLevel { 215 | entry.Warn(entry.sprintlnn(args...)) 216 | } 217 | } 218 | 219 | func (entry *Entry) Warningln(args ...interface{}) { 220 | entry.Warnln(args...) 221 | } 222 | 223 | func (entry *Entry) Errorln(args ...interface{}) { 224 | if entry.Logger.Level >= ErrorLevel { 225 | entry.Error(entry.sprintlnn(args...)) 226 | } 227 | } 228 | 229 | func (entry *Entry) Fatalln(args ...interface{}) { 230 | if entry.Logger.Level >= FatalLevel { 231 | entry.Fatal(entry.sprintlnn(args...)) 232 | } 233 | } 234 | 235 | func (entry *Entry) Panicln(args ...interface{}) { 236 | if entry.Logger.Level >= PanicLevel { 237 | entry.Panic(entry.sprintlnn(args...)) 238 | } 239 | } 240 | 241 | // Sprintlnn => Sprint no newline. This is to get the behavior of how 242 | // fmt.Sprintln where spaces are always added between operands, regardless of 243 | // their type. Instead of vendoring the Sprintln implementation to spare a 244 | // string allocation, we do the simplest thing. 245 | func (entry *Entry) sprintlnn(args ...interface{}) string { 246 | msg := fmt.Sprintln(args...) 247 | return msg[:len(msg)-1] 248 | } 249 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/entry_test.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestEntryPanicln(t *testing.T) { 12 | errBoom := fmt.Errorf("boom time") 13 | 14 | defer func() { 15 | p := recover() 16 | assert.NotNil(t, p) 17 | 18 | switch pVal := p.(type) { 19 | case *Entry: 20 | assert.Equal(t, "kaboom", pVal.Message) 21 | assert.Equal(t, errBoom, pVal.Data["err"]) 22 | default: 23 | t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal) 24 | } 25 | }() 26 | 27 | logger := New() 28 | logger.Out = &bytes.Buffer{} 29 | entry := NewEntry(logger) 30 | entry.WithField("err", errBoom).Panicln("kaboom") 31 | } 32 | 33 | func TestEntryPanicf(t *testing.T) { 34 | errBoom := fmt.Errorf("boom again") 35 | 36 | defer func() { 37 | p := recover() 38 | assert.NotNil(t, p) 39 | 40 | switch pVal := p.(type) { 41 | case *Entry: 42 | assert.Equal(t, "kaboom true", pVal.Message) 43 | assert.Equal(t, errBoom, pVal.Data["err"]) 44 | default: 45 | t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal) 46 | } 47 | }() 48 | 49 | logger := New() 50 | logger.Out = &bytes.Buffer{} 51 | entry := NewEntry(logger) 52 | entry.WithField("err", errBoom).Panicf("kaboom %v", true) 53 | } 54 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Sirupsen/logrus" 5 | ) 6 | 7 | var log = logrus.New() 8 | 9 | func init() { 10 | log.Formatter = new(logrus.JSONFormatter) 11 | log.Formatter = new(logrus.TextFormatter) // default 12 | } 13 | 14 | func main() { 15 | defer func() { 16 | err := recover() 17 | if err != nil { 18 | log.WithFields(logrus.Fields{ 19 | "omg": true, 20 | "err": err, 21 | "number": 100, 22 | }).Fatal("The ice breaks!") 23 | } 24 | }() 25 | 26 | log.WithFields(logrus.Fields{ 27 | "animal": "walrus", 28 | "size": 10, 29 | }).Info("A group of walrus emerges from the ocean") 30 | 31 | log.WithFields(logrus.Fields{ 32 | "omg": true, 33 | "number": 122, 34 | }).Warn("The group's number increased tremendously!") 35 | 36 | log.WithFields(logrus.Fields{ 37 | "animal": "orca", 38 | "size": 9009, 39 | }).Panic("It's over 9000!") 40 | } 41 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Sirupsen/logrus" 5 | "github.com/Sirupsen/logrus/hooks/airbrake" 6 | "github.com/tobi/airbrake-go" 7 | ) 8 | 9 | var log = logrus.New() 10 | 11 | func init() { 12 | log.Formatter = new(logrus.TextFormatter) // default 13 | log.Hooks.Add(new(logrus_airbrake.AirbrakeHook)) 14 | } 15 | 16 | func main() { 17 | airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml" 18 | airbrake.ApiKey = "whatever" 19 | airbrake.Environment = "production" 20 | 21 | log.WithFields(logrus.Fields{ 22 | "animal": "walrus", 23 | "size": 10, 24 | }).Info("A group of walrus emerges from the ocean") 25 | 26 | log.WithFields(logrus.Fields{ 27 | "omg": true, 28 | "number": 122, 29 | }).Warn("The group's number increased tremendously!") 30 | 31 | log.WithFields(logrus.Fields{ 32 | "omg": true, 33 | "number": 100, 34 | }).Fatal("The ice breaks!") 35 | } 36 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | var ( 8 | // std is the name of the standard logger in stdlib `log` 9 | std = New() 10 | ) 11 | 12 | // SetOutput sets the standard logger output. 13 | func SetOutput(out io.Writer) { 14 | std.mu.Lock() 15 | defer std.mu.Unlock() 16 | std.Out = out 17 | } 18 | 19 | // SetFormatter sets the standard logger formatter. 20 | func SetFormatter(formatter Formatter) { 21 | std.mu.Lock() 22 | defer std.mu.Unlock() 23 | std.Formatter = formatter 24 | } 25 | 26 | // SetLevel sets the standard logger level. 27 | func SetLevel(level Level) { 28 | std.mu.Lock() 29 | defer std.mu.Unlock() 30 | std.Level = level 31 | } 32 | 33 | // GetLevel returns the standard logger level. 34 | func GetLevel() Level { 35 | return std.Level 36 | } 37 | 38 | // AddHook adds a hook to the standard logger hooks. 39 | func AddHook(hook Hook) { 40 | std.mu.Lock() 41 | defer std.mu.Unlock() 42 | std.Hooks.Add(hook) 43 | } 44 | 45 | // WithField creates an entry from the standard logger and adds a field to 46 | // it. If you want multiple fields, use `WithFields`. 47 | // 48 | // Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal 49 | // or Panic on the Entry it returns. 50 | func WithField(key string, value interface{}) *Entry { 51 | return std.WithField(key, value) 52 | } 53 | 54 | // WithFields creates an entry from the standard logger and adds multiple 55 | // fields to it. This is simply a helper for `WithField`, invoking it 56 | // once for each field. 57 | // 58 | // Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal 59 | // or Panic on the Entry it returns. 60 | func WithFields(fields Fields) *Entry { 61 | return std.WithFields(fields) 62 | } 63 | 64 | // Debug logs a message at level Debug on the standard logger. 65 | func Debug(args ...interface{}) { 66 | std.Debug(args...) 67 | } 68 | 69 | // Print logs a message at level Info on the standard logger. 70 | func Print(args ...interface{}) { 71 | std.Print(args...) 72 | } 73 | 74 | // Info logs a message at level Info on the standard logger. 75 | func Info(args ...interface{}) { 76 | std.Info(args...) 77 | } 78 | 79 | // Warn logs a message at level Warn on the standard logger. 80 | func Warn(args ...interface{}) { 81 | std.Warn(args...) 82 | } 83 | 84 | // Warning logs a message at level Warn on the standard logger. 85 | func Warning(args ...interface{}) { 86 | std.Warning(args...) 87 | } 88 | 89 | // Error logs a message at level Error on the standard logger. 90 | func Error(args ...interface{}) { 91 | std.Error(args...) 92 | } 93 | 94 | // Panic logs a message at level Panic on the standard logger. 95 | func Panic(args ...interface{}) { 96 | std.Panic(args...) 97 | } 98 | 99 | // Fatal logs a message at level Fatal on the standard logger. 100 | func Fatal(args ...interface{}) { 101 | std.Fatal(args...) 102 | } 103 | 104 | // Debugf logs a message at level Debug on the standard logger. 105 | func Debugf(format string, args ...interface{}) { 106 | std.Debugf(format, args...) 107 | } 108 | 109 | // Printf logs a message at level Info on the standard logger. 110 | func Printf(format string, args ...interface{}) { 111 | std.Printf(format, args...) 112 | } 113 | 114 | // Infof logs a message at level Info on the standard logger. 115 | func Infof(format string, args ...interface{}) { 116 | std.Infof(format, args...) 117 | } 118 | 119 | // Warnf logs a message at level Warn on the standard logger. 120 | func Warnf(format string, args ...interface{}) { 121 | std.Warnf(format, args...) 122 | } 123 | 124 | // Warningf logs a message at level Warn on the standard logger. 125 | func Warningf(format string, args ...interface{}) { 126 | std.Warningf(format, args...) 127 | } 128 | 129 | // Errorf logs a message at level Error on the standard logger. 130 | func Errorf(format string, args ...interface{}) { 131 | std.Errorf(format, args...) 132 | } 133 | 134 | // Panicf logs a message at level Panic on the standard logger. 135 | func Panicf(format string, args ...interface{}) { 136 | std.Panicf(format, args...) 137 | } 138 | 139 | // Fatalf logs a message at level Fatal on the standard logger. 140 | func Fatalf(format string, args ...interface{}) { 141 | std.Fatalf(format, args...) 142 | } 143 | 144 | // Debugln logs a message at level Debug on the standard logger. 145 | func Debugln(args ...interface{}) { 146 | std.Debugln(args...) 147 | } 148 | 149 | // Println logs a message at level Info on the standard logger. 150 | func Println(args ...interface{}) { 151 | std.Println(args...) 152 | } 153 | 154 | // Infoln logs a message at level Info on the standard logger. 155 | func Infoln(args ...interface{}) { 156 | std.Infoln(args...) 157 | } 158 | 159 | // Warnln logs a message at level Warn on the standard logger. 160 | func Warnln(args ...interface{}) { 161 | std.Warnln(args...) 162 | } 163 | 164 | // Warningln logs a message at level Warn on the standard logger. 165 | func Warningln(args ...interface{}) { 166 | std.Warningln(args...) 167 | } 168 | 169 | // Errorln logs a message at level Error on the standard logger. 170 | func Errorln(args ...interface{}) { 171 | std.Errorln(args...) 172 | } 173 | 174 | // Panicln logs a message at level Panic on the standard logger. 175 | func Panicln(args ...interface{}) { 176 | std.Panicln(args...) 177 | } 178 | 179 | // Fatalln logs a message at level Fatal on the standard logger. 180 | func Fatalln(args ...interface{}) { 181 | std.Fatalln(args...) 182 | } 183 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | // The Formatter interface is used to implement a custom Formatter. It takes an 4 | // `Entry`. It exposes all the fields, including the default ones: 5 | // 6 | // * `entry.Data["msg"]`. The message passed from Info, Warn, Error .. 7 | // * `entry.Data["time"]`. The timestamp. 8 | // * `entry.Data["level"]. The level the entry was logged at. 9 | // 10 | // Any additional fields added with `WithField` or `WithFields` are also in 11 | // `entry.Data`. Format is expected to return an array of bytes which are then 12 | // logged to `logger.Out`. 13 | type Formatter interface { 14 | Format(*Entry) ([]byte, error) 15 | } 16 | 17 | // This is to not silently overwrite `time`, `msg` and `level` fields when 18 | // dumping it. If this code wasn't there doing: 19 | // 20 | // logrus.WithField("level", 1).Info("hello") 21 | // 22 | // Would just silently drop the user provided level. Instead with this code 23 | // it'll logged as: 24 | // 25 | // {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} 26 | // 27 | // It's not exported because it's still using Data in an opinionated way. It's to 28 | // avoid code duplication between the two default formatters. 29 | func prefixFieldClashes(entry *Entry) { 30 | _, ok := entry.Data["time"] 31 | if ok { 32 | entry.Data["fields.time"] = entry.Data["time"] 33 | } 34 | 35 | _, ok = entry.Data["msg"] 36 | if ok { 37 | entry.Data["fields.msg"] = entry.Data["msg"] 38 | } 39 | 40 | _, ok = entry.Data["level"] 41 | if ok { 42 | entry.Data["fields.level"] = entry.Data["level"] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter_bench_test.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | // smallFields is a small size data set for benchmarking 9 | var smallFields = Fields{ 10 | "foo": "bar", 11 | "baz": "qux", 12 | "one": "two", 13 | "three": "four", 14 | } 15 | 16 | // largeFields is a large size data set for benchmarking 17 | var largeFields = Fields{ 18 | "foo": "bar", 19 | "baz": "qux", 20 | "one": "two", 21 | "three": "four", 22 | "five": "six", 23 | "seven": "eight", 24 | "nine": "ten", 25 | "eleven": "twelve", 26 | "thirteen": "fourteen", 27 | "fifteen": "sixteen", 28 | "seventeen": "eighteen", 29 | "nineteen": "twenty", 30 | "a": "b", 31 | "c": "d", 32 | "e": "f", 33 | "g": "h", 34 | "i": "j", 35 | "k": "l", 36 | "m": "n", 37 | "o": "p", 38 | "q": "r", 39 | "s": "t", 40 | "u": "v", 41 | "w": "x", 42 | "y": "z", 43 | "this": "will", 44 | "make": "thirty", 45 | "entries": "yeah", 46 | } 47 | 48 | func BenchmarkSmallTextFormatter(b *testing.B) { 49 | doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields) 50 | } 51 | 52 | func BenchmarkLargeTextFormatter(b *testing.B) { 53 | doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields) 54 | } 55 | 56 | func BenchmarkSmallColoredTextFormatter(b *testing.B) { 57 | doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields) 58 | } 59 | 60 | func BenchmarkLargeColoredTextFormatter(b *testing.B) { 61 | doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields) 62 | } 63 | 64 | func BenchmarkSmallJSONFormatter(b *testing.B) { 65 | doBenchmark(b, &JSONFormatter{}, smallFields) 66 | } 67 | 68 | func BenchmarkLargeJSONFormatter(b *testing.B) { 69 | doBenchmark(b, &JSONFormatter{}, largeFields) 70 | } 71 | 72 | func doBenchmark(b *testing.B, formatter Formatter, fields Fields) { 73 | entry := &Entry{ 74 | Time: time.Time{}, 75 | Level: InfoLevel, 76 | Message: "message", 77 | Data: fields, 78 | } 79 | var d []byte 80 | var err error 81 | for i := 0; i < b.N; i++ { 82 | d, err = formatter.Format(entry) 83 | if err != nil { 84 | b.Fatal(err) 85 | } 86 | b.SetBytes(int64(len(d))) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hook_test.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type TestHook struct { 10 | Fired bool 11 | } 12 | 13 | func (hook *TestHook) Fire(entry *Entry) error { 14 | hook.Fired = true 15 | return nil 16 | } 17 | 18 | func (hook *TestHook) Levels() []Level { 19 | return []Level{ 20 | DebugLevel, 21 | InfoLevel, 22 | WarnLevel, 23 | ErrorLevel, 24 | FatalLevel, 25 | PanicLevel, 26 | } 27 | } 28 | 29 | func TestHookFires(t *testing.T) { 30 | hook := new(TestHook) 31 | 32 | LogAndAssertJSON(t, func(log *Logger) { 33 | log.Hooks.Add(hook) 34 | assert.Equal(t, hook.Fired, false) 35 | 36 | log.Print("test") 37 | }, func(fields Fields) { 38 | assert.Equal(t, hook.Fired, true) 39 | }) 40 | } 41 | 42 | type ModifyHook struct { 43 | } 44 | 45 | func (hook *ModifyHook) Fire(entry *Entry) error { 46 | entry.Data["wow"] = "whale" 47 | return nil 48 | } 49 | 50 | func (hook *ModifyHook) Levels() []Level { 51 | return []Level{ 52 | DebugLevel, 53 | InfoLevel, 54 | WarnLevel, 55 | ErrorLevel, 56 | FatalLevel, 57 | PanicLevel, 58 | } 59 | } 60 | 61 | func TestHookCanModifyEntry(t *testing.T) { 62 | hook := new(ModifyHook) 63 | 64 | LogAndAssertJSON(t, func(log *Logger) { 65 | log.Hooks.Add(hook) 66 | log.WithField("wow", "elephant").Print("test") 67 | }, func(fields Fields) { 68 | assert.Equal(t, fields["wow"], "whale") 69 | }) 70 | } 71 | 72 | func TestCanFireMultipleHooks(t *testing.T) { 73 | hook1 := new(ModifyHook) 74 | hook2 := new(TestHook) 75 | 76 | LogAndAssertJSON(t, func(log *Logger) { 77 | log.Hooks.Add(hook1) 78 | log.Hooks.Add(hook2) 79 | 80 | log.WithField("wow", "elephant").Print("test") 81 | }, func(fields Fields) { 82 | assert.Equal(t, fields["wow"], "whale") 83 | assert.Equal(t, hook2.Fired, true) 84 | }) 85 | } 86 | 87 | type ErrorHook struct { 88 | Fired bool 89 | } 90 | 91 | func (hook *ErrorHook) Fire(entry *Entry) error { 92 | hook.Fired = true 93 | return nil 94 | } 95 | 96 | func (hook *ErrorHook) Levels() []Level { 97 | return []Level{ 98 | ErrorLevel, 99 | } 100 | } 101 | 102 | func TestErrorHookShouldntFireOnInfo(t *testing.T) { 103 | hook := new(ErrorHook) 104 | 105 | LogAndAssertJSON(t, func(log *Logger) { 106 | log.Hooks.Add(hook) 107 | log.Info("test") 108 | }, func(fields Fields) { 109 | assert.Equal(t, hook.Fired, false) 110 | }) 111 | } 112 | 113 | func TestErrorHookShouldFireOnError(t *testing.T) { 114 | hook := new(ErrorHook) 115 | 116 | LogAndAssertJSON(t, func(log *Logger) { 117 | log.Hooks.Add(hook) 118 | log.Error("test") 119 | }, func(fields Fields) { 120 | assert.Equal(t, hook.Fired, true) 121 | }) 122 | } 123 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | // A hook to be fired when logging on the logging levels returned from 4 | // `Levels()` on your implementation of the interface. Note that this is not 5 | // fired in a goroutine or a channel with workers, you should handle such 6 | // functionality yourself if your call is non-blocking and you don't wish for 7 | // the logging calls for levels returned from `Levels()` to block. 8 | type Hook interface { 9 | Levels() []Level 10 | Fire(*Entry) error 11 | } 12 | 13 | // Internal type for storing the hooks on a logger instance. 14 | type levelHooks map[Level][]Hook 15 | 16 | // Add a hook to an instance of logger. This is called with 17 | // `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface. 18 | func (hooks levelHooks) Add(hook Hook) { 19 | for _, level := range hook.Levels() { 20 | hooks[level] = append(hooks[level], hook) 21 | } 22 | } 23 | 24 | // Fire all the hooks for the passed level. Used by `entry.log` to fire 25 | // appropriate hooks for a log entry. 26 | func (hooks levelHooks) Fire(level Level, entry *Entry) error { 27 | for _, hook := range hooks[level] { 28 | if err := hook.Fire(entry); err != nil { 29 | return err 30 | } 31 | } 32 | 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go: -------------------------------------------------------------------------------- 1 | package logrus_airbrake 2 | 3 | import ( 4 | "github.com/Sirupsen/logrus" 5 | "github.com/tobi/airbrake-go" 6 | ) 7 | 8 | // AirbrakeHook to send exceptions to an exception-tracking service compatible 9 | // with the Airbrake API. You must set: 10 | // * airbrake.Endpoint 11 | // * airbrake.ApiKey 12 | // * airbrake.Environment (only sends exceptions when set to "production") 13 | // 14 | // Before using this hook, to send an error. Entries that trigger an Error, 15 | // Fatal or Panic should now include an "error" field to send to Airbrake. 16 | type AirbrakeHook struct{} 17 | 18 | func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { 19 | if entry.Data["error"] == nil { 20 | entry.Logger.WithFields(logrus.Fields{ 21 | "source": "airbrake", 22 | "endpoint": airbrake.Endpoint, 23 | }).Warn("Exceptions sent to Airbrake must have an 'error' key with the error") 24 | return nil 25 | } 26 | 27 | err, ok := entry.Data["error"].(error) 28 | if !ok { 29 | entry.Logger.WithFields(logrus.Fields{ 30 | "source": "airbrake", 31 | "endpoint": airbrake.Endpoint, 32 | }).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`") 33 | return nil 34 | } 35 | 36 | airErr := airbrake.Notify(err) 37 | if airErr != nil { 38 | entry.Logger.WithFields(logrus.Fields{ 39 | "source": "airbrake", 40 | "endpoint": airbrake.Endpoint, 41 | "error": airErr, 42 | }).Warn("Failed to send error to Airbrake") 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func (hook *AirbrakeHook) Levels() []logrus.Level { 49 | return []logrus.Level{ 50 | logrus.ErrorLevel, 51 | logrus.FatalLevel, 52 | logrus.PanicLevel, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md: -------------------------------------------------------------------------------- 1 | # Papertrail Hook for Logrus :walrus: 2 | 3 | [Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts). 4 | 5 | In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible. 6 | 7 | ## Usage 8 | 9 | You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`. 10 | 11 | For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs. 12 | 13 | ```go 14 | import ( 15 | "log/syslog" 16 | "github.com/Sirupsen/logrus" 17 | "github.com/Sirupsen/logrus/hooks/papertrail" 18 | ) 19 | 20 | func main() { 21 | log := logrus.New() 22 | hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME) 23 | 24 | if err == nil { 25 | log.Hooks.Add(hook) 26 | } 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go: -------------------------------------------------------------------------------- 1 | package logrus_papertrail 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "time" 8 | 9 | "github.com/Sirupsen/logrus" 10 | ) 11 | 12 | const ( 13 | format = "Jan 2 15:04:05" 14 | ) 15 | 16 | // PapertrailHook to send logs to a logging service compatible with the Papertrail API. 17 | type PapertrailHook struct { 18 | Host string 19 | Port int 20 | AppName string 21 | UDPConn net.Conn 22 | } 23 | 24 | // NewPapertrailHook creates a hook to be added to an instance of logger. 25 | func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) { 26 | conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port)) 27 | return &PapertrailHook{host, port, appName, conn}, err 28 | } 29 | 30 | // Fire is called when a log event is fired. 31 | func (hook *PapertrailHook) Fire(entry *logrus.Entry) error { 32 | date := time.Now().Format(format) 33 | payload := fmt.Sprintf("<22> %s %s: [%s] %s", date, hook.AppName, entry.Level, entry.Message) 34 | 35 | bytesWritten, err := hook.UDPConn.Write([]byte(payload)) 36 | if err != nil { 37 | fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err) 38 | return err 39 | } 40 | 41 | return nil 42 | } 43 | 44 | // Levels returns the available logging levels. 45 | func (hook *PapertrailHook) Levels() []logrus.Level { 46 | return []logrus.Level{ 47 | logrus.PanicLevel, 48 | logrus.FatalLevel, 49 | logrus.ErrorLevel, 50 | logrus.WarnLevel, 51 | logrus.InfoLevel, 52 | logrus.DebugLevel, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go: -------------------------------------------------------------------------------- 1 | package logrus_papertrail 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/Sirupsen/logrus" 8 | "github.com/stvp/go-udp-testing" 9 | ) 10 | 11 | func TestWritingToUDP(t *testing.T) { 12 | port := 16661 13 | udp.SetAddr(fmt.Sprintf(":%d", port)) 14 | 15 | hook, err := NewPapertrailHook("localhost", port, "test") 16 | if err != nil { 17 | t.Errorf("Unable to connect to local UDP server.") 18 | } 19 | 20 | log := logrus.New() 21 | log.Hooks.Add(hook) 22 | 23 | udp.ShouldReceive(t, "foo", func() { 24 | log.Info("foo") 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md: -------------------------------------------------------------------------------- 1 | # Sentry Hook for Logrus :walrus: 2 | 3 | [Sentry](https://getsentry.com) provides both self-hosted and hosted 4 | solutions for exception tracking. 5 | Both client and server are 6 | [open source](https://github.com/getsentry/sentry). 7 | 8 | ## Usage 9 | 10 | Every sentry application defined on the server gets a different 11 | [DSN](https://www.getsentry.com/docs/). In the example below replace 12 | `YOUR_DSN` with the one created for your application. 13 | 14 | ```go 15 | import ( 16 | "github.com/Sirupsen/logrus" 17 | "github.com/Sirupsen/logrus/hooks/sentry" 18 | ) 19 | 20 | func main() { 21 | log := logrus.New() 22 | hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{ 23 | logrus.PanicLevel, 24 | logrus.FatalLevel, 25 | logrus.ErrorLevel, 26 | }) 27 | 28 | if err == nil { 29 | log.Hooks.Add(hook) 30 | } 31 | } 32 | ``` 33 | 34 | ## Special fields 35 | 36 | Some logrus fields have a special meaning in this hook, 37 | these are server_name and logger. 38 | When logs are sent to sentry these fields are treated differently. 39 | - server_name (also known as hostname) is the name of the server which 40 | is logging the event (hostname.example.com) 41 | - logger is the part of the application which is logging the event. 42 | In go this usually means setting it to the name of the package. 43 | 44 | ## Timeout 45 | 46 | `Timeout` is the time the sentry hook will wait for a response 47 | from the sentry server. 48 | 49 | If this time elapses with no response from 50 | the server an error will be returned. 51 | 52 | If `Timeout` is set to 0 the SentryHook will not wait for a reply 53 | and will assume a correct delivery. 54 | 55 | The SentryHook has a default timeout of `100 milliseconds` when created 56 | with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field: 57 | 58 | ```go 59 | hook, _ := logrus_sentry.NewSentryHook(...) 60 | hook.Timeout = 20*time.Seconds 61 | ``` 62 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go: -------------------------------------------------------------------------------- 1 | package logrus_sentry 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/Sirupsen/logrus" 8 | "github.com/getsentry/raven-go" 9 | ) 10 | 11 | var ( 12 | severityMap = map[logrus.Level]raven.Severity{ 13 | logrus.DebugLevel: raven.DEBUG, 14 | logrus.InfoLevel: raven.INFO, 15 | logrus.WarnLevel: raven.WARNING, 16 | logrus.ErrorLevel: raven.ERROR, 17 | logrus.FatalLevel: raven.FATAL, 18 | logrus.PanicLevel: raven.FATAL, 19 | } 20 | ) 21 | 22 | func getAndDel(d logrus.Fields, key string) (string, bool) { 23 | var ( 24 | ok bool 25 | v interface{} 26 | val string 27 | ) 28 | if v, ok = d[key]; !ok { 29 | return "", false 30 | } 31 | 32 | if val, ok = v.(string); !ok { 33 | return "", false 34 | } 35 | delete(d, key) 36 | return val, true 37 | } 38 | 39 | // SentryHook delivers logs to a sentry server. 40 | type SentryHook struct { 41 | // Timeout sets the time to wait for a delivery error from the sentry server. 42 | // If this is set to zero the server will not wait for any response and will 43 | // consider the message correctly sent 44 | Timeout time.Duration 45 | 46 | client *raven.Client 47 | levels []logrus.Level 48 | } 49 | 50 | // NewSentryHook creates a hook to be added to an instance of logger 51 | // and initializes the raven client. 52 | // This method sets the timeout to 100 milliseconds. 53 | func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) { 54 | client, err := raven.NewClient(DSN, nil) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return &SentryHook{100 * time.Millisecond, client, levels}, nil 59 | } 60 | 61 | // Called when an event should be sent to sentry 62 | // Special fields that sentry uses to give more information to the server 63 | // are extracted from entry.Data (if they are found) 64 | // These fields are: logger and server_name 65 | func (hook *SentryHook) Fire(entry *logrus.Entry) error { 66 | packet := &raven.Packet{ 67 | Message: entry.Message, 68 | Timestamp: raven.Timestamp(entry.Time), 69 | Level: severityMap[entry.Level], 70 | Platform: "go", 71 | } 72 | 73 | d := entry.Data 74 | 75 | if logger, ok := getAndDel(d, "logger"); ok { 76 | packet.Logger = logger 77 | } 78 | if serverName, ok := getAndDel(d, "server_name"); ok { 79 | packet.ServerName = serverName 80 | } 81 | packet.Extra = map[string]interface{}(d) 82 | 83 | _, errCh := hook.client.Capture(packet, nil) 84 | timeout := hook.Timeout 85 | if timeout != 0 { 86 | timeoutCh := time.After(timeout) 87 | select { 88 | case err := <-errCh: 89 | return err 90 | case <-timeoutCh: 91 | return fmt.Errorf("no response from sentry server in %s", timeout) 92 | } 93 | } 94 | return nil 95 | } 96 | 97 | // Levels returns the available logging levels. 98 | func (hook *SentryHook) Levels() []logrus.Level { 99 | return hook.levels 100 | } 101 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go: -------------------------------------------------------------------------------- 1 | package logrus_sentry 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/Sirupsen/logrus" 13 | "github.com/getsentry/raven-go" 14 | ) 15 | 16 | const ( 17 | message = "error message" 18 | server_name = "testserver.internal" 19 | logger_name = "test.logger" 20 | ) 21 | 22 | func getTestLogger() *logrus.Logger { 23 | l := logrus.New() 24 | l.Out = ioutil.Discard 25 | return l 26 | } 27 | 28 | func WithTestDSN(t *testing.T, tf func(string, <-chan *raven.Packet)) { 29 | pch := make(chan *raven.Packet, 1) 30 | s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 31 | defer req.Body.Close() 32 | d := json.NewDecoder(req.Body) 33 | p := &raven.Packet{} 34 | err := d.Decode(p) 35 | if err != nil { 36 | t.Fatal(err.Error()) 37 | } 38 | 39 | pch <- p 40 | })) 41 | defer s.Close() 42 | 43 | fragments := strings.SplitN(s.URL, "://", 2) 44 | dsn := fmt.Sprintf( 45 | "%s://public:secret@%s/sentry/project-id", 46 | fragments[0], 47 | fragments[1], 48 | ) 49 | tf(dsn, pch) 50 | } 51 | 52 | func TestSpecialFields(t *testing.T) { 53 | WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) { 54 | logger := getTestLogger() 55 | 56 | hook, err := NewSentryHook(dsn, []logrus.Level{ 57 | logrus.ErrorLevel, 58 | }) 59 | 60 | if err != nil { 61 | t.Fatal(err.Error()) 62 | } 63 | logger.Hooks.Add(hook) 64 | logger.WithFields(logrus.Fields{ 65 | "server_name": server_name, 66 | "logger": logger_name, 67 | }).Error(message) 68 | 69 | packet := <-pch 70 | if packet.Logger != logger_name { 71 | t.Errorf("logger should have been %s, was %s", logger_name, packet.Logger) 72 | } 73 | 74 | if packet.ServerName != server_name { 75 | t.Errorf("server_name should have been %s, was %s", server_name, packet.ServerName) 76 | } 77 | }) 78 | } 79 | 80 | func TestSentryHandler(t *testing.T) { 81 | WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) { 82 | logger := getTestLogger() 83 | hook, err := NewSentryHook(dsn, []logrus.Level{ 84 | logrus.ErrorLevel, 85 | }) 86 | if err != nil { 87 | t.Fatal(err.Error()) 88 | } 89 | logger.Hooks.Add(hook) 90 | 91 | logger.Error(message) 92 | packet := <-pch 93 | if packet.Message != message { 94 | t.Errorf("message should have been %s, was %s", message, packet.Message) 95 | } 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/README.md: -------------------------------------------------------------------------------- 1 | # Syslog Hooks for Logrus :walrus: 2 | 3 | ## Usage 4 | 5 | ```go 6 | import ( 7 | "log/syslog" 8 | "github.com/Sirupsen/logrus" 9 | "github.com/Sirupsen/logrus/hooks/syslog" 10 | ) 11 | 12 | func main() { 13 | log := logrus.New() 14 | hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") 15 | 16 | if err == nil { 17 | log.Hooks.Add(hook) 18 | } 19 | } 20 | ``` -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go: -------------------------------------------------------------------------------- 1 | package logrus_syslog 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Sirupsen/logrus" 6 | "log/syslog" 7 | "os" 8 | ) 9 | 10 | // SyslogHook to send logs via syslog. 11 | type SyslogHook struct { 12 | Writer *syslog.Writer 13 | SyslogNetwork string 14 | SyslogRaddr string 15 | } 16 | 17 | // Creates a hook to be added to an instance of logger. This is called with 18 | // `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")` 19 | // `if err == nil { log.Hooks.Add(hook) }` 20 | func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) { 21 | w, err := syslog.Dial(network, raddr, priority, tag) 22 | return &SyslogHook{w, network, raddr}, err 23 | } 24 | 25 | func (hook *SyslogHook) Fire(entry *logrus.Entry) error { 26 | line, err := entry.String() 27 | if err != nil { 28 | fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err) 29 | return err 30 | } 31 | 32 | switch entry.Level { 33 | case logrus.PanicLevel: 34 | return hook.Writer.Crit(line) 35 | case logrus.FatalLevel: 36 | return hook.Writer.Crit(line) 37 | case logrus.ErrorLevel: 38 | return hook.Writer.Err(line) 39 | case logrus.WarnLevel: 40 | return hook.Writer.Warning(line) 41 | case logrus.InfoLevel: 42 | return hook.Writer.Info(line) 43 | case logrus.DebugLevel: 44 | return hook.Writer.Debug(line) 45 | default: 46 | return nil 47 | } 48 | } 49 | 50 | func (hook *SyslogHook) Levels() []logrus.Level { 51 | return []logrus.Level{ 52 | logrus.PanicLevel, 53 | logrus.FatalLevel, 54 | logrus.ErrorLevel, 55 | logrus.WarnLevel, 56 | logrus.InfoLevel, 57 | logrus.DebugLevel, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go: -------------------------------------------------------------------------------- 1 | package logrus_syslog 2 | 3 | import ( 4 | "github.com/Sirupsen/logrus" 5 | "log/syslog" 6 | "testing" 7 | ) 8 | 9 | func TestLocalhostAddAndPrint(t *testing.T) { 10 | log := logrus.New() 11 | hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") 12 | 13 | if err != nil { 14 | t.Errorf("Unable to connect to local syslog.") 15 | } 16 | 17 | log.Hooks.Add(hook) 18 | 19 | for _, level := range hook.Levels() { 20 | if len(log.Hooks[level]) != 1 { 21 | t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level])) 22 | } 23 | } 24 | 25 | log.Info("Congratulations!") 26 | } 27 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | type JSONFormatter struct{} 10 | 11 | func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { 12 | prefixFieldClashes(entry) 13 | entry.Data["time"] = entry.Time.Format(time.RFC3339) 14 | entry.Data["msg"] = entry.Message 15 | entry.Data["level"] = entry.Level.String() 16 | 17 | serialized, err := json.Marshal(entry.Data) 18 | if err != nil { 19 | return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) 20 | } 21 | return append(serialized, '\n'), nil 22 | } 23 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "sync" 7 | ) 8 | 9 | type Logger struct { 10 | // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a 11 | // file, or leave it default which is `os.Stdout`. You can also set this to 12 | // something more adventorous, such as logging to Kafka. 13 | Out io.Writer 14 | // Hooks for the logger instance. These allow firing events based on logging 15 | // levels and log entries. For example, to send errors to an error tracking 16 | // service, log to StatsD or dump the core on fatal errors. 17 | Hooks levelHooks 18 | // All log entries pass through the formatter before logged to Out. The 19 | // included formatters are `TextFormatter` and `JSONFormatter` for which 20 | // TextFormatter is the default. In development (when a TTY is attached) it 21 | // logs with colors, but to a file it wouldn't. You can easily implement your 22 | // own that implements the `Formatter` interface, see the `README` or included 23 | // formatters for examples. 24 | Formatter Formatter 25 | // The logging level the logger should log at. This is typically (and defaults 26 | // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be 27 | // logged. `logrus.Debug` is useful in 28 | Level Level 29 | // Used to sync writing to the log. 30 | mu sync.Mutex 31 | } 32 | 33 | // Creates a new logger. Configuration should be set by changing `Formatter`, 34 | // `Out` and `Hooks` directly on the default logger instance. You can also just 35 | // instantiate your own: 36 | // 37 | // var log = &Logger{ 38 | // Out: os.Stderr, 39 | // Formatter: new(JSONFormatter), 40 | // Hooks: make(levelHooks), 41 | // Level: logrus.DebugLevel, 42 | // } 43 | // 44 | // It's recommended to make this a global instance called `log`. 45 | func New() *Logger { 46 | return &Logger{ 47 | Out: os.Stdout, 48 | Formatter: new(TextFormatter), 49 | Hooks: make(levelHooks), 50 | Level: InfoLevel, 51 | } 52 | } 53 | 54 | // Adds a field to the log entry, note that you it doesn't log until you call 55 | // Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry. 56 | // Ff you want multiple fields, use `WithFields`. 57 | func (logger *Logger) WithField(key string, value interface{}) *Entry { 58 | return NewEntry(logger).WithField(key, value) 59 | } 60 | 61 | // Adds a struct of fields to the log entry. All it does is call `WithField` for 62 | // each `Field`. 63 | func (logger *Logger) WithFields(fields Fields) *Entry { 64 | return NewEntry(logger).WithFields(fields) 65 | } 66 | 67 | func (logger *Logger) Debugf(format string, args ...interface{}) { 68 | NewEntry(logger).Debugf(format, args...) 69 | } 70 | 71 | func (logger *Logger) Infof(format string, args ...interface{}) { 72 | NewEntry(logger).Infof(format, args...) 73 | } 74 | 75 | func (logger *Logger) Printf(format string, args ...interface{}) { 76 | NewEntry(logger).Printf(format, args...) 77 | } 78 | 79 | func (logger *Logger) Warnf(format string, args ...interface{}) { 80 | NewEntry(logger).Warnf(format, args...) 81 | } 82 | 83 | func (logger *Logger) Warningf(format string, args ...interface{}) { 84 | NewEntry(logger).Warnf(format, args...) 85 | } 86 | 87 | func (logger *Logger) Errorf(format string, args ...interface{}) { 88 | NewEntry(logger).Errorf(format, args...) 89 | } 90 | 91 | func (logger *Logger) Fatalf(format string, args ...interface{}) { 92 | NewEntry(logger).Fatalf(format, args...) 93 | } 94 | 95 | func (logger *Logger) Panicf(format string, args ...interface{}) { 96 | NewEntry(logger).Panicf(format, args...) 97 | } 98 | 99 | func (logger *Logger) Debug(args ...interface{}) { 100 | NewEntry(logger).Debug(args...) 101 | } 102 | 103 | func (logger *Logger) Info(args ...interface{}) { 104 | NewEntry(logger).Info(args...) 105 | } 106 | 107 | func (logger *Logger) Print(args ...interface{}) { 108 | NewEntry(logger).Info(args...) 109 | } 110 | 111 | func (logger *Logger) Warn(args ...interface{}) { 112 | NewEntry(logger).Warn(args...) 113 | } 114 | 115 | func (logger *Logger) Warning(args ...interface{}) { 116 | NewEntry(logger).Warn(args...) 117 | } 118 | 119 | func (logger *Logger) Error(args ...interface{}) { 120 | NewEntry(logger).Error(args...) 121 | } 122 | 123 | func (logger *Logger) Fatal(args ...interface{}) { 124 | NewEntry(logger).Fatal(args...) 125 | } 126 | 127 | func (logger *Logger) Panic(args ...interface{}) { 128 | NewEntry(logger).Panic(args...) 129 | } 130 | 131 | func (logger *Logger) Debugln(args ...interface{}) { 132 | NewEntry(logger).Debugln(args...) 133 | } 134 | 135 | func (logger *Logger) Infoln(args ...interface{}) { 136 | NewEntry(logger).Infoln(args...) 137 | } 138 | 139 | func (logger *Logger) Println(args ...interface{}) { 140 | NewEntry(logger).Println(args...) 141 | } 142 | 143 | func (logger *Logger) Warnln(args ...interface{}) { 144 | NewEntry(logger).Warnln(args...) 145 | } 146 | 147 | func (logger *Logger) Warningln(args ...interface{}) { 148 | NewEntry(logger).Warnln(args...) 149 | } 150 | 151 | func (logger *Logger) Errorln(args ...interface{}) { 152 | NewEntry(logger).Errorln(args...) 153 | } 154 | 155 | func (logger *Logger) Fatalln(args ...interface{}) { 156 | NewEntry(logger).Fatalln(args...) 157 | } 158 | 159 | func (logger *Logger) Panicln(args ...interface{}) { 160 | NewEntry(logger).Panicln(args...) 161 | } 162 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | // Fields type, used to pass to `WithFields`. 9 | type Fields map[string]interface{} 10 | 11 | // Level type 12 | type Level uint8 13 | 14 | // Convert the Level to a string. E.g. PanicLevel becomes "panic". 15 | func (level Level) String() string { 16 | switch level { 17 | case DebugLevel: 18 | return "debug" 19 | case InfoLevel: 20 | return "info" 21 | case WarnLevel: 22 | return "warning" 23 | case ErrorLevel: 24 | return "error" 25 | case FatalLevel: 26 | return "fatal" 27 | case PanicLevel: 28 | return "panic" 29 | } 30 | 31 | return "unknown" 32 | } 33 | 34 | // ParseLevel takes a string level and returns the Logrus log level constant. 35 | func ParseLevel(lvl string) (Level, error) { 36 | switch lvl { 37 | case "panic": 38 | return PanicLevel, nil 39 | case "fatal": 40 | return FatalLevel, nil 41 | case "error": 42 | return ErrorLevel, nil 43 | case "warn", "warning": 44 | return WarnLevel, nil 45 | case "info": 46 | return InfoLevel, nil 47 | case "debug": 48 | return DebugLevel, nil 49 | } 50 | 51 | var l Level 52 | return l, fmt.Errorf("not a valid logrus Level: %q", lvl) 53 | } 54 | 55 | // These are the different logging levels. You can set the logging level to log 56 | // on your instance of logger, obtained with `logrus.New()`. 57 | const ( 58 | // PanicLevel level, highest level of severity. Logs and then calls panic with the 59 | // message passed to Debug, Info, ... 60 | PanicLevel Level = iota 61 | // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the 62 | // logging level is set to Panic. 63 | FatalLevel 64 | // ErrorLevel level. Logs. Used for errors that should definitely be noted. 65 | // Commonly used for hooks to send errors to an error tracking service. 66 | ErrorLevel 67 | // WarnLevel level. Non-critical entries that deserve eyes. 68 | WarnLevel 69 | // InfoLevel level. General operational entries about what's going on inside the 70 | // application. 71 | InfoLevel 72 | // DebugLevel level. Usually only enabled when debugging. Very verbose logging. 73 | DebugLevel 74 | ) 75 | 76 | // Won't compile if StdLogger can't be realized by a log.Logger 77 | var _ StdLogger = &log.Logger{} 78 | 79 | // StdLogger is what your logrus-enabled library should take, that way 80 | // it'll accept a stdlib logger and a logrus logger. There's no standard 81 | // interface, this is the closest we get, unfortunately. 82 | type StdLogger interface { 83 | Print(...interface{}) 84 | Printf(string, ...interface{}) 85 | Println(...interface{}) 86 | 87 | Fatal(...interface{}) 88 | Fatalf(string, ...interface{}) 89 | Fatalln(...interface{}) 90 | 91 | Panic(...interface{}) 92 | Panicf(string, ...interface{}) 93 | Panicln(...interface{}) 94 | } 95 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "strconv" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) { 14 | var buffer bytes.Buffer 15 | var fields Fields 16 | 17 | logger := New() 18 | logger.Out = &buffer 19 | logger.Formatter = new(JSONFormatter) 20 | 21 | log(logger) 22 | 23 | err := json.Unmarshal(buffer.Bytes(), &fields) 24 | assert.Nil(t, err) 25 | 26 | assertions(fields) 27 | } 28 | 29 | func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) { 30 | var buffer bytes.Buffer 31 | 32 | logger := New() 33 | logger.Out = &buffer 34 | logger.Formatter = &TextFormatter{ 35 | DisableColors: true, 36 | } 37 | 38 | log(logger) 39 | 40 | fields := make(map[string]string) 41 | for _, kv := range strings.Split(buffer.String(), " ") { 42 | if !strings.Contains(kv, "=") { 43 | continue 44 | } 45 | kvArr := strings.Split(kv, "=") 46 | key := strings.TrimSpace(kvArr[0]) 47 | val, err := strconv.Unquote(kvArr[1]) 48 | assert.NoError(t, err) 49 | fields[key] = val 50 | } 51 | assertions(fields) 52 | } 53 | 54 | func TestPrint(t *testing.T) { 55 | LogAndAssertJSON(t, func(log *Logger) { 56 | log.Print("test") 57 | }, func(fields Fields) { 58 | assert.Equal(t, fields["msg"], "test") 59 | assert.Equal(t, fields["level"], "info") 60 | }) 61 | } 62 | 63 | func TestInfo(t *testing.T) { 64 | LogAndAssertJSON(t, func(log *Logger) { 65 | log.Info("test") 66 | }, func(fields Fields) { 67 | assert.Equal(t, fields["msg"], "test") 68 | assert.Equal(t, fields["level"], "info") 69 | }) 70 | } 71 | 72 | func TestWarn(t *testing.T) { 73 | LogAndAssertJSON(t, func(log *Logger) { 74 | log.Warn("test") 75 | }, func(fields Fields) { 76 | assert.Equal(t, fields["msg"], "test") 77 | assert.Equal(t, fields["level"], "warning") 78 | }) 79 | } 80 | 81 | func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) { 82 | LogAndAssertJSON(t, func(log *Logger) { 83 | log.Infoln("test", "test") 84 | }, func(fields Fields) { 85 | assert.Equal(t, fields["msg"], "test test") 86 | }) 87 | } 88 | 89 | func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) { 90 | LogAndAssertJSON(t, func(log *Logger) { 91 | log.Infoln("test", 10) 92 | }, func(fields Fields) { 93 | assert.Equal(t, fields["msg"], "test 10") 94 | }) 95 | } 96 | 97 | func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) { 98 | LogAndAssertJSON(t, func(log *Logger) { 99 | log.Infoln(10, 10) 100 | }, func(fields Fields) { 101 | assert.Equal(t, fields["msg"], "10 10") 102 | }) 103 | } 104 | 105 | func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) { 106 | LogAndAssertJSON(t, func(log *Logger) { 107 | log.Infoln(10, 10) 108 | }, func(fields Fields) { 109 | assert.Equal(t, fields["msg"], "10 10") 110 | }) 111 | } 112 | 113 | func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) { 114 | LogAndAssertJSON(t, func(log *Logger) { 115 | log.Info("test", 10) 116 | }, func(fields Fields) { 117 | assert.Equal(t, fields["msg"], "test10") 118 | }) 119 | } 120 | 121 | func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) { 122 | LogAndAssertJSON(t, func(log *Logger) { 123 | log.Info("test", "test") 124 | }, func(fields Fields) { 125 | assert.Equal(t, fields["msg"], "testtest") 126 | }) 127 | } 128 | 129 | func TestWithFieldsShouldAllowAssignments(t *testing.T) { 130 | var buffer bytes.Buffer 131 | var fields Fields 132 | 133 | logger := New() 134 | logger.Out = &buffer 135 | logger.Formatter = new(JSONFormatter) 136 | 137 | localLog := logger.WithFields(Fields{ 138 | "key1": "value1", 139 | }) 140 | 141 | localLog.WithField("key2", "value2").Info("test") 142 | err := json.Unmarshal(buffer.Bytes(), &fields) 143 | assert.Nil(t, err) 144 | 145 | assert.Equal(t, "value2", fields["key2"]) 146 | assert.Equal(t, "value1", fields["key1"]) 147 | 148 | buffer = bytes.Buffer{} 149 | fields = Fields{} 150 | localLog.Info("test") 151 | err = json.Unmarshal(buffer.Bytes(), &fields) 152 | assert.Nil(t, err) 153 | 154 | _, ok := fields["key2"] 155 | assert.Equal(t, false, ok) 156 | assert.Equal(t, "value1", fields["key1"]) 157 | } 158 | 159 | func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) { 160 | LogAndAssertJSON(t, func(log *Logger) { 161 | log.WithField("msg", "hello").Info("test") 162 | }, func(fields Fields) { 163 | assert.Equal(t, fields["msg"], "test") 164 | }) 165 | } 166 | 167 | func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) { 168 | LogAndAssertJSON(t, func(log *Logger) { 169 | log.WithField("msg", "hello").Info("test") 170 | }, func(fields Fields) { 171 | assert.Equal(t, fields["msg"], "test") 172 | assert.Equal(t, fields["fields.msg"], "hello") 173 | }) 174 | } 175 | 176 | func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) { 177 | LogAndAssertJSON(t, func(log *Logger) { 178 | log.WithField("time", "hello").Info("test") 179 | }, func(fields Fields) { 180 | assert.Equal(t, fields["fields.time"], "hello") 181 | }) 182 | } 183 | 184 | func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) { 185 | LogAndAssertJSON(t, func(log *Logger) { 186 | log.WithField("level", 1).Info("test") 187 | }, func(fields Fields) { 188 | assert.Equal(t, fields["level"], "info") 189 | assert.Equal(t, fields["fields.level"], 1) 190 | }) 191 | } 192 | 193 | func TestDefaultFieldsAreNotPrefixed(t *testing.T) { 194 | LogAndAssertText(t, func(log *Logger) { 195 | ll := log.WithField("herp", "derp") 196 | ll.Info("hello") 197 | ll.Info("bye") 198 | }, func(fields map[string]string) { 199 | for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} { 200 | if _, ok := fields[fieldName]; ok { 201 | t.Fatalf("should not have prefixed %q: %v", fieldName, fields) 202 | } 203 | } 204 | }) 205 | } 206 | 207 | func TestConvertLevelToString(t *testing.T) { 208 | assert.Equal(t, "debug", DebugLevel.String()) 209 | assert.Equal(t, "info", InfoLevel.String()) 210 | assert.Equal(t, "warning", WarnLevel.String()) 211 | assert.Equal(t, "error", ErrorLevel.String()) 212 | assert.Equal(t, "fatal", FatalLevel.String()) 213 | assert.Equal(t, "panic", PanicLevel.String()) 214 | } 215 | 216 | func TestParseLevel(t *testing.T) { 217 | l, err := ParseLevel("panic") 218 | assert.Nil(t, err) 219 | assert.Equal(t, PanicLevel, l) 220 | 221 | l, err = ParseLevel("fatal") 222 | assert.Nil(t, err) 223 | assert.Equal(t, FatalLevel, l) 224 | 225 | l, err = ParseLevel("error") 226 | assert.Nil(t, err) 227 | assert.Equal(t, ErrorLevel, l) 228 | 229 | l, err = ParseLevel("warn") 230 | assert.Nil(t, err) 231 | assert.Equal(t, WarnLevel, l) 232 | 233 | l, err = ParseLevel("warning") 234 | assert.Nil(t, err) 235 | assert.Equal(t, WarnLevel, l) 236 | 237 | l, err = ParseLevel("info") 238 | assert.Nil(t, err) 239 | assert.Equal(t, InfoLevel, l) 240 | 241 | l, err = ParseLevel("debug") 242 | assert.Nil(t, err) 243 | assert.Equal(t, DebugLevel, l) 244 | 245 | l, err = ParseLevel("invalid") 246 | assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error()) 247 | } 248 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_darwin.go: -------------------------------------------------------------------------------- 1 | // Based on ssh/terminal: 2 | // Copyright 2013 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package logrus 7 | 8 | import "syscall" 9 | 10 | const ioctlReadTermios = syscall.TIOCGETA 11 | 12 | type Termios syscall.Termios 13 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_freebsd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin. 3 | */ 4 | package logrus 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | const ioctlReadTermios = syscall.TIOCGETA 11 | 12 | type Termios struct { 13 | Iflag uint32 14 | Oflag uint32 15 | Cflag uint32 16 | Lflag uint32 17 | Cc [20]uint8 18 | Ispeed uint32 19 | Ospeed uint32 20 | } 21 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_linux.go: -------------------------------------------------------------------------------- 1 | // Based on ssh/terminal: 2 | // Copyright 2013 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package logrus 7 | 8 | import "syscall" 9 | 10 | const ioctlReadTermios = syscall.TCGETS 11 | 12 | type Termios syscall.Termios 13 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go: -------------------------------------------------------------------------------- 1 | // Based on ssh/terminal: 2 | // Copyright 2011 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // +build linux,!appengine darwin freebsd 7 | 8 | package logrus 9 | 10 | import ( 11 | "syscall" 12 | "unsafe" 13 | ) 14 | 15 | // IsTerminal returns true if the given file descriptor is a terminal. 16 | func IsTerminal() bool { 17 | fd := syscall.Stdout 18 | var termios Termios 19 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) 20 | return err == 0 21 | } 22 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_windows.go: -------------------------------------------------------------------------------- 1 | // Based on ssh/terminal: 2 | // Copyright 2011 The Go Authors. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // +build windows 7 | 8 | package logrus 9 | 10 | import ( 11 | "syscall" 12 | "unsafe" 13 | ) 14 | 15 | var kernel32 = syscall.NewLazyDLL("kernel32.dll") 16 | 17 | var ( 18 | procGetConsoleMode = kernel32.NewProc("GetConsoleMode") 19 | ) 20 | 21 | // IsTerminal returns true if the given file descriptor is a terminal. 22 | func IsTerminal() bool { 23 | fd := syscall.Stdout 24 | var st uint32 25 | r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) 26 | return r != 0 && e == 0 27 | } 28 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go: -------------------------------------------------------------------------------- 1 | package logrus 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sort" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | const ( 12 | nocolor = 0 13 | red = 31 14 | green = 32 15 | yellow = 33 16 | blue = 34 17 | ) 18 | 19 | var ( 20 | baseTimestamp time.Time 21 | isTerminal bool 22 | ) 23 | 24 | func init() { 25 | baseTimestamp = time.Now() 26 | isTerminal = IsTerminal() 27 | } 28 | 29 | func miniTS() int { 30 | return int(time.Since(baseTimestamp) / time.Second) 31 | } 32 | 33 | type TextFormatter struct { 34 | // Set to true to bypass checking for a TTY before outputting colors. 35 | ForceColors bool 36 | DisableColors bool 37 | } 38 | 39 | func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { 40 | 41 | var keys []string 42 | for k := range entry.Data { 43 | keys = append(keys, k) 44 | } 45 | sort.Strings(keys) 46 | 47 | b := &bytes.Buffer{} 48 | 49 | prefixFieldClashes(entry) 50 | 51 | isColored := (f.ForceColors || isTerminal) && !f.DisableColors 52 | 53 | if isColored { 54 | printColored(b, entry, keys) 55 | } else { 56 | f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339)) 57 | f.appendKeyValue(b, "level", entry.Level.String()) 58 | f.appendKeyValue(b, "msg", entry.Message) 59 | for _, key := range keys { 60 | f.appendKeyValue(b, key, entry.Data[key]) 61 | } 62 | } 63 | 64 | b.WriteByte('\n') 65 | return b.Bytes(), nil 66 | } 67 | 68 | func printColored(b *bytes.Buffer, entry *Entry, keys []string) { 69 | var levelColor int 70 | switch entry.Level { 71 | case WarnLevel: 72 | levelColor = yellow 73 | case ErrorLevel, FatalLevel, PanicLevel: 74 | levelColor = red 75 | default: 76 | levelColor = blue 77 | } 78 | 79 | levelText := strings.ToUpper(entry.Level.String())[0:4] 80 | 81 | fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) 82 | for _, k := range keys { 83 | v := entry.Data[k] 84 | fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v) 85 | } 86 | } 87 | 88 | func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) { 89 | switch value.(type) { 90 | case string, error: 91 | fmt.Fprintf(b, "%v=%q ", key, value) 92 | default: 93 | fmt.Fprintf(b, "%v=%v ", key, value) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jeremy Saenz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/README.md: -------------------------------------------------------------------------------- 1 | # Negroni [![GoDoc](https://godoc.org/github.com/codegangsta/negroni?status.svg)](http://godoc.org/github.com/codegangsta/negroni) [![wercker status](https://app.wercker.com/status/13688a4a94b82d84a0b8d038c4965b61/s "wercker status")](https://app.wercker.com/project/bykey/13688a4a94b82d84a0b8d038c4965b61) 2 | 3 | Negroni is an idiomatic approach to web middleware in Go. It is tiny, non-intrusive, and encourages use of `net/http` Handlers. 4 | 5 | If you like the idea of [Martini](http://github.com/go-martini/martini), but you think it contains too much magic, then Negroni is a great fit. 6 | 7 | ## Getting Started 8 | 9 | After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`. 10 | 11 | ~~~ go 12 | package main 13 | 14 | import ( 15 | "github.com/codegangsta/negroni" 16 | "net/http" 17 | "fmt" 18 | ) 19 | 20 | func main() { 21 | mux := http.NewServeMux() 22 | mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 23 | fmt.Fprintf(w, "Welcome to the home page!") 24 | }) 25 | 26 | n := negroni.Classic() 27 | n.UseHandler(mux) 28 | n.Run(":3000") 29 | } 30 | ~~~ 31 | 32 | Then install the Negroni package (**go 1.1** and greater is required): 33 | ~~~ 34 | go get github.com/codegangsta/negroni 35 | ~~~ 36 | 37 | Then run your server: 38 | ~~~ 39 | go run server.go 40 | ~~~ 41 | 42 | You will now have a Go net/http webserver running on `localhost:3000`. 43 | 44 | ## Need Help? 45 | If you have a question or feature request, [go ask the mailing list](https://groups.google.com/forum/#!forum/negroni-users). The GitHub issues for Negroni will be used exclusively for bug reports and pull requests. 46 | 47 | ## Is Negroni a Framework? 48 | Negroni is **not** a framework. It is a library that is designed to work directly with net/http. 49 | 50 | ## Routing? 51 | Negroni is BYOR (Bring your own Router). The Go community already has a number of great http routers available, Negroni tries to play well with all of them by fully supporting `net/http`. For instance, integrating with [Gorilla Mux](http://github.com/gorilla/mux) looks like so: 52 | 53 | ~~~ go 54 | router := mux.NewRouter() 55 | router.HandleFunc("/", HomeHandler) 56 | 57 | n := negroni.New(Middleware1, Middleware2) 58 | // Or use a middleware with the Use() function 59 | n.Use(Middleware3) 60 | // router goes last 61 | n.UseHandler(router) 62 | 63 | n.Run(":3000") 64 | ~~~ 65 | 66 | ## `negroni.Classic()` 67 | `negroni.Classic()` provides some default middleware that is useful for most applications: 68 | 69 | * `negroni.Recovery` - Panic Recovery Middleware. 70 | * `negroni.Logging` - Request/Response Logging Middleware. 71 | * `negroni.Static` - Static File serving under the "public" directory. 72 | 73 | This makes it really easy to get started with some useful features from Negroni. 74 | 75 | ## Handlers 76 | Negroni provides a bidirectional middleware flow. This is done through the `negroni.Handler` interface: 77 | 78 | ~~~ go 79 | type Handler interface { 80 | ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) 81 | } 82 | ~~~ 83 | 84 | If a middleware hasn't already written to the ResponseWriter, it should call the next `http.HandlerFunc` in the chain to yield to the next middleware handler. This can be used for great good: 85 | 86 | ~~~ go 87 | func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 88 | // do some stuff before 89 | next(rw, r) 90 | // do some stuff after 91 | } 92 | ~~~ 93 | 94 | And you can map it to the handler chain with the `Use` function: 95 | 96 | ~~~ go 97 | n := negroni.New() 98 | n.Use(negroni.HandlerFunc(MyMiddleware)) 99 | ~~~ 100 | 101 | You can also map plain old `http.Handler`s: 102 | 103 | ~~~ go 104 | n := negroni.New() 105 | 106 | mux := http.NewServeMux() 107 | // map your routes 108 | 109 | n.UseHandler(mux) 110 | 111 | n.Run(":3000") 112 | ~~~ 113 | 114 | ## `Run()` 115 | Negroni has a convenience function called `Run`. `Run` takes an addr string identical to [http.ListenAndServe](http://golang.org/pkg/net/http#ListenAndServe). 116 | 117 | ~~~ go 118 | n := negroni.Classic() 119 | // ... 120 | log.Fatal(http.ListenAndServe(":8080", n)) 121 | ~~~ 122 | 123 | ## Route Specific Middleware 124 | If you have a route group of routes that need specific middleware to be executed, you can simply create a new Negroni instance and use it as your route handler. 125 | 126 | ~~~ go 127 | router := mux.NewRouter() 128 | adminRoutes := mux.NewRouter() 129 | // add admin routes here 130 | 131 | // Create a new negroni for the admin middleware 132 | router.Handle("/admin", negroni.New( 133 | Middleware1, 134 | Middleware2, 135 | negroni.Wrap(adminRoutes), 136 | )) 137 | ~~~ 138 | 139 | ## Third Party Middleware 140 | 141 | Here is a current list of Negroni compatible middlware. Feel free to put up a PR linking your middleware if you have built one: 142 | 143 | 144 | | Middleware | Author | Description | 145 | | -----------|--------|-------------| 146 | | [RestGate](https://github.com/pjebs/restgate) | [Prasanga Siripala](https://github.com/pjebs) | Secure authentication for REST API endpoints | 147 | | [Graceful](https://github.com/stretchr/graceful) | [Tyler Bunnell](https://github.com/tylerb) | Graceful HTTP Shutdown | 148 | | [secure](https://github.com/unrolled/secure) | [Cory Jacobsen](https://github.com/unrolled) | Middleware that implements a few quick security wins | 149 | | [binding](https://github.com/mholt/binding) | [Matt Holt](https://github.com/mholt) | Data binding from HTTP requests into structs | 150 | | [logrus](https://github.com/meatballhat/negroni-logrus) | [Dan Buch](https://github.com/meatballhat) | Logrus-based logger | 151 | | [render](https://github.com/unrolled/render) | [Cory Jacobsen](https://github.com/unrolled) | Render JSON, XML and HTML templates | 152 | | [gorelic](https://github.com/jingweno/negroni-gorelic) | [Jingwen Owen Ou](https://github.com/jingweno) | New Relic agent for Go runtime | 153 | | [gzip](https://github.com/phyber/negroni-gzip) | [phyber](https://github.com/phyber) | GZIP response compression | 154 | | [oauth2](https://github.com/goincremental/negroni-oauth2) | [David Bochenski](https://github.com/bochenski) | oAuth2 middleware | 155 | | [sessions](https://github.com/goincremental/negroni-sessions) | [David Bochenski](https://github.com/bochenski) | Session Management | 156 | | [permissions2](https://github.com/xyproto/permissions2) | [Alexander Rødseth](https://github.com/xyproto) | Cookies, users and permissions | 157 | | [onthefly](https://github.com/xyproto/onthefly) | [Alexander Rødseth](https://github.com/xyproto) | Generate TinySVG, HTML and CSS on the fly | 158 | | [cors](https://github.com/rs/cors) | [Olivier Poitrey](https://github.com/rs) | [Cross Origin Resource Sharing](http://www.w3.org/TR/cors/) (CORS) support | 159 | 160 | ## Examples 161 | [Alexander Rødseth](https://github.com/xyproto) created [mooseware](https://github.com/xyproto/mooseware), a skeleton for writing a Negroni middleware handler. 162 | 163 | ## Live code reload? 164 | [gin](https://github.com/codegangsta/gin) and [fresh](https://github.com/pilu/fresh) both live reload negroni apps. 165 | 166 | ## About 167 | 168 | Negroni is obsessively designed by none other than the [Code Gangsta](http://codegangsta.io/) 169 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/doc.go: -------------------------------------------------------------------------------- 1 | // Package negroni is an idiomatic approach to web middleware in Go. It is tiny, non-intrusive, and encourages use of net/http Handlers. 2 | // 3 | // If you like the idea of Martini, but you think it contains too much magic, then Negroni is a great fit. 4 | // 5 | // For a full guide visit http://github.com/codegangsta/negroni 6 | // 7 | // package main 8 | // 9 | // import ( 10 | // "github.com/codegangsta/negroni" 11 | // "net/http" 12 | // "fmt" 13 | // ) 14 | // 15 | // func main() { 16 | // mux := http.NewServeMux() 17 | // mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 18 | // fmt.Fprintf(w, "Welcome to the home page!") 19 | // }) 20 | // 21 | // n := negroni.Classic() 22 | // n.UseHandler(mux) 23 | // n.Run(":3000") 24 | // } 25 | package negroni 26 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/logger.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "time" 8 | ) 9 | 10 | // Logger is a middleware handler that logs the request as it goes in and the response as it goes out. 11 | type Logger struct { 12 | // Logger inherits from log.Logger used to log messages with the Logger middleware 13 | *log.Logger 14 | } 15 | 16 | // NewLogger returns a new Logger instance 17 | func NewLogger() *Logger { 18 | return &Logger{log.New(os.Stdout, "[negroni] ", 0)} 19 | } 20 | 21 | func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 22 | start := time.Now() 23 | l.Printf("Started %s %s", r.Method, r.URL.Path) 24 | 25 | next(rw, r) 26 | 27 | res := rw.(ResponseWriter) 28 | l.Printf("Completed %v %s in %v", res.Status(), http.StatusText(res.Status()), time.Since(start)) 29 | } 30 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/logger_test.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func Test_Logger(t *testing.T) { 12 | buff := bytes.NewBufferString("") 13 | recorder := httptest.NewRecorder() 14 | 15 | l := NewLogger() 16 | l.Logger = log.New(buff, "[negroni] ", 0) 17 | 18 | n := New() 19 | // replace log for testing 20 | n.Use(l) 21 | n.UseHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 22 | rw.WriteHeader(http.StatusNotFound) 23 | })) 24 | 25 | req, err := http.NewRequest("GET", "http://localhost:3000/foobar", nil) 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | 30 | n.ServeHTTP(recorder, req) 31 | expect(t, recorder.Code, http.StatusNotFound) 32 | refute(t, len(buff.String()), 0) 33 | } 34 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/negroni.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | ) 8 | 9 | // Handler handler is an interface that objects can implement to be registered to serve as middleware 10 | // in the Negroni middleware stack. 11 | // ServeHTTP should yield to the next middleware in the chain by invoking the next http.HandlerFunc 12 | // passed in. 13 | // 14 | // If the Handler writes to the ResponseWriter, the next http.HandlerFunc should not be invoked. 15 | type Handler interface { 16 | ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) 17 | } 18 | 19 | // HandlerFunc is an adapter to allow the use of ordinary functions as Negroni handlers. 20 | // If f is a function with the appropriate signature, HandlerFunc(f) is a Handler object that calls f. 21 | type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) 22 | 23 | func (h HandlerFunc) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 24 | h(rw, r, next) 25 | } 26 | 27 | type middleware struct { 28 | handler Handler 29 | next *middleware 30 | } 31 | 32 | func (m middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 33 | m.handler.ServeHTTP(rw, r, m.next.ServeHTTP) 34 | } 35 | 36 | // Wrap converts a http.Handler into a negroni.Handler so it can be used as a Negroni 37 | // middleware. The next http.HandlerFunc is automatically called after the Handler 38 | // is executed. 39 | func Wrap(handler http.Handler) Handler { 40 | return HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 41 | handler.ServeHTTP(rw, r) 42 | next(rw, r) 43 | }) 44 | } 45 | 46 | // Negroni is a stack of Middleware Handlers that can be invoked as an http.Handler. 47 | // Negroni middleware is evaluated in the order that they are added to the stack using 48 | // the Use and UseHandler methods. 49 | type Negroni struct { 50 | middleware middleware 51 | handlers []Handler 52 | } 53 | 54 | // New returns a new Negroni instance with no middleware preconfigured. 55 | func New(handlers ...Handler) *Negroni { 56 | return &Negroni{ 57 | handlers: handlers, 58 | middleware: build(handlers), 59 | } 60 | } 61 | 62 | // Classic returns a new Negroni instance with the default middleware already 63 | // in the stack. 64 | // 65 | // Recovery - Panic Recovery Middleware 66 | // Logger - Request/Response Logging 67 | // Static - Static File Serving 68 | func Classic() *Negroni { 69 | return New(NewRecovery(), NewLogger(), NewStatic(http.Dir("public"))) 70 | } 71 | 72 | func (n *Negroni) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 73 | n.middleware.ServeHTTP(NewResponseWriter(rw), r) 74 | } 75 | 76 | // Use adds a Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni. 77 | func (n *Negroni) Use(handler Handler) { 78 | n.handlers = append(n.handlers, handler) 79 | n.middleware = build(n.handlers) 80 | } 81 | 82 | // UseHandler adds a http.Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni. 83 | func (n *Negroni) UseHandler(handler http.Handler) { 84 | n.Use(Wrap(handler)) 85 | } 86 | 87 | // Run is a convenience function that runs the negroni stack as an HTTP 88 | // server. The addr string takes the same format as http.ListenAndServe. 89 | func (n *Negroni) Run(addr string) { 90 | l := log.New(os.Stdout, "[negroni] ", 0) 91 | l.Printf("listening on %s", addr) 92 | l.Fatal(http.ListenAndServe(addr, n)) 93 | } 94 | 95 | // Returns a list of all the handlers in the current Negroni middleware chain. 96 | func (n *Negroni) Handlers() ([]Handler) { 97 | return n.handlers 98 | } 99 | 100 | func build(handlers []Handler) middleware { 101 | var next middleware 102 | 103 | if len(handlers) == 0 { 104 | return voidMiddleware() 105 | } else if len(handlers) > 1 { 106 | next = build(handlers[1:]) 107 | } else { 108 | next = voidMiddleware() 109 | } 110 | 111 | return middleware{handlers[0], &next} 112 | } 113 | 114 | func voidMiddleware() middleware { 115 | return middleware{ 116 | HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {}), 117 | &middleware{}, 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/negroni_test.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | /* Test Helpers */ 11 | func expect(t *testing.T, a interface{}, b interface{}) { 12 | if a != b { 13 | t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) 14 | } 15 | } 16 | 17 | func refute(t *testing.T, a interface{}, b interface{}) { 18 | if a == b { 19 | t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) 20 | } 21 | } 22 | 23 | func TestNegroniRun(t *testing.T) { 24 | // just test that Run doesn't bomb 25 | go New().Run(":3000") 26 | } 27 | 28 | func TestNegroniServeHTTP(t *testing.T) { 29 | result := "" 30 | response := httptest.NewRecorder() 31 | 32 | n := New() 33 | n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 34 | result += "foo" 35 | next(rw, r) 36 | result += "ban" 37 | })) 38 | n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 39 | result += "bar" 40 | next(rw, r) 41 | result += "baz" 42 | })) 43 | n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 44 | result += "bat" 45 | rw.WriteHeader(http.StatusBadRequest) 46 | })) 47 | 48 | n.ServeHTTP(response, (*http.Request)(nil)) 49 | 50 | expect(t, result, "foobarbatbazban") 51 | expect(t, response.Code, http.StatusBadRequest) 52 | } 53 | 54 | // Ensures that a Negroni middleware chain 55 | // can correctly return all of its handlers. 56 | func TestHandlers(t *testing.T) { 57 | response := httptest.NewRecorder() 58 | n := New() 59 | handlers := n.Handlers() 60 | expect(t, 0, len(handlers)) 61 | 62 | n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 63 | rw.WriteHeader(http.StatusOK) 64 | })) 65 | 66 | // Expects the length of handlers to be exactly 1 67 | // after adding exactly one handler to the middleware chain 68 | handlers = n.Handlers() 69 | expect(t, 1, len(handlers)) 70 | 71 | // Ensures that the first handler that is in sequence behaves 72 | // exactly the same as the one that was registered earlier 73 | handlers[0].ServeHTTP(response, (*http.Request)(nil), nil) 74 | expect(t, response.Code, http.StatusOK) 75 | } -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/recovery.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | "runtime" 9 | ) 10 | 11 | // Recovery is a Negroni middleware that recovers from any panics and writes a 500 if there was one. 12 | type Recovery struct { 13 | Logger *log.Logger 14 | PrintStack bool 15 | StackAll bool 16 | StackSize int 17 | } 18 | 19 | // NewRecovery returns a new instance of Recovery 20 | func NewRecovery() *Recovery { 21 | return &Recovery{ 22 | Logger: log.New(os.Stdout, "[negroni] ", 0), 23 | PrintStack: true, 24 | StackAll: false, 25 | StackSize: 1024 * 8, 26 | } 27 | } 28 | 29 | func (rec *Recovery) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 30 | defer func() { 31 | if err := recover(); err != nil { 32 | rw.WriteHeader(http.StatusInternalServerError) 33 | stack := make([]byte, rec.StackSize) 34 | stack = stack[:runtime.Stack(stack, rec.StackAll)] 35 | 36 | f := "PANIC: %s\n%s" 37 | rec.Logger.Printf(f, err, stack) 38 | 39 | if rec.PrintStack { 40 | fmt.Fprintf(rw, f, err, stack) 41 | } 42 | } 43 | }() 44 | 45 | next(rw, r) 46 | } 47 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/recovery_test.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestRecovery(t *testing.T) { 12 | buff := bytes.NewBufferString("") 13 | recorder := httptest.NewRecorder() 14 | 15 | rec := NewRecovery() 16 | rec.Logger = log.New(buff, "[negroni] ", 0) 17 | 18 | n := New() 19 | // replace log for testing 20 | n.Use(rec) 21 | n.UseHandler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 22 | panic("here is a panic!") 23 | })) 24 | n.ServeHTTP(recorder, (*http.Request)(nil)) 25 | expect(t, recorder.Code, http.StatusInternalServerError) 26 | refute(t, recorder.Body.Len(), 0) 27 | refute(t, len(buff.String()), 0) 28 | } 29 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/response_writer.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | ) 9 | 10 | // ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about 11 | // the response. It is recommended that middleware handlers use this construct to wrap a responsewriter 12 | // if the functionality calls for it. 13 | type ResponseWriter interface { 14 | http.ResponseWriter 15 | http.Flusher 16 | // Status returns the status code of the response or 0 if the response has not been written. 17 | Status() int 18 | // Written returns whether or not the ResponseWriter has been written. 19 | Written() bool 20 | // Size returns the size of the response body. 21 | Size() int 22 | // Before allows for a function to be called before the ResponseWriter has been written to. This is 23 | // useful for setting headers or any other operations that must happen before a response has been written. 24 | Before(func(ResponseWriter)) 25 | } 26 | 27 | type beforeFunc func(ResponseWriter) 28 | 29 | // NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter 30 | func NewResponseWriter(rw http.ResponseWriter) ResponseWriter { 31 | return &responseWriter{rw, 0, 0, nil} 32 | } 33 | 34 | type responseWriter struct { 35 | http.ResponseWriter 36 | status int 37 | size int 38 | beforeFuncs []beforeFunc 39 | } 40 | 41 | func (rw *responseWriter) WriteHeader(s int) { 42 | rw.callBefore() 43 | rw.ResponseWriter.WriteHeader(s) 44 | rw.status = s 45 | } 46 | 47 | func (rw *responseWriter) Write(b []byte) (int, error) { 48 | if !rw.Written() { 49 | // The status will be StatusOK if WriteHeader has not been called yet 50 | rw.WriteHeader(http.StatusOK) 51 | } 52 | size, err := rw.ResponseWriter.Write(b) 53 | rw.size += size 54 | return size, err 55 | } 56 | 57 | func (rw *responseWriter) Status() int { 58 | return rw.status 59 | } 60 | 61 | func (rw *responseWriter) Size() int { 62 | return rw.size 63 | } 64 | 65 | func (rw *responseWriter) Written() bool { 66 | return rw.status != 0 67 | } 68 | 69 | func (rw *responseWriter) Before(before func(ResponseWriter)) { 70 | rw.beforeFuncs = append(rw.beforeFuncs, before) 71 | } 72 | 73 | func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 74 | hijacker, ok := rw.ResponseWriter.(http.Hijacker) 75 | if !ok { 76 | return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface") 77 | } 78 | return hijacker.Hijack() 79 | } 80 | 81 | func (rw *responseWriter) CloseNotify() <-chan bool { 82 | return rw.ResponseWriter.(http.CloseNotifier).CloseNotify() 83 | } 84 | 85 | func (rw *responseWriter) callBefore() { 86 | for i := len(rw.beforeFuncs) - 1; i >= 0; i-- { 87 | rw.beforeFuncs[i](rw) 88 | } 89 | } 90 | 91 | func (rw *responseWriter) Flush() { 92 | flusher, ok := rw.ResponseWriter.(http.Flusher) 93 | if ok { 94 | flusher.Flush() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/response_writer_test.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bufio" 5 | "net" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type closeNotifyingRecorder struct { 13 | *httptest.ResponseRecorder 14 | closed chan bool 15 | } 16 | 17 | func newCloseNotifyingRecorder() *closeNotifyingRecorder { 18 | return &closeNotifyingRecorder{ 19 | httptest.NewRecorder(), 20 | make(chan bool, 1), 21 | } 22 | } 23 | 24 | func (c *closeNotifyingRecorder) close() { 25 | c.closed <- true 26 | } 27 | 28 | func (c *closeNotifyingRecorder) CloseNotify() <-chan bool { 29 | return c.closed 30 | } 31 | 32 | type hijackableResponse struct { 33 | Hijacked bool 34 | } 35 | 36 | func newHijackableResponse() *hijackableResponse { 37 | return &hijackableResponse{} 38 | } 39 | 40 | func (h *hijackableResponse) Header() http.Header { return nil } 41 | func (h *hijackableResponse) Write(buf []byte) (int, error) { return 0, nil } 42 | func (h *hijackableResponse) WriteHeader(code int) {} 43 | func (h *hijackableResponse) Flush() {} 44 | func (h *hijackableResponse) Hijack() (net.Conn, *bufio.ReadWriter, error) { 45 | h.Hijacked = true 46 | return nil, nil, nil 47 | } 48 | 49 | func TestResponseWriterWritingString(t *testing.T) { 50 | rec := httptest.NewRecorder() 51 | rw := NewResponseWriter(rec) 52 | 53 | rw.Write([]byte("Hello world")) 54 | 55 | expect(t, rec.Code, rw.Status()) 56 | expect(t, rec.Body.String(), "Hello world") 57 | expect(t, rw.Status(), http.StatusOK) 58 | expect(t, rw.Size(), 11) 59 | expect(t, rw.Written(), true) 60 | } 61 | 62 | func TestResponseWriterWritingStrings(t *testing.T) { 63 | rec := httptest.NewRecorder() 64 | rw := NewResponseWriter(rec) 65 | 66 | rw.Write([]byte("Hello world")) 67 | rw.Write([]byte("foo bar bat baz")) 68 | 69 | expect(t, rec.Code, rw.Status()) 70 | expect(t, rec.Body.String(), "Hello worldfoo bar bat baz") 71 | expect(t, rw.Status(), http.StatusOK) 72 | expect(t, rw.Size(), 26) 73 | } 74 | 75 | func TestResponseWriterWritingHeader(t *testing.T) { 76 | rec := httptest.NewRecorder() 77 | rw := NewResponseWriter(rec) 78 | 79 | rw.WriteHeader(http.StatusNotFound) 80 | 81 | expect(t, rec.Code, rw.Status()) 82 | expect(t, rec.Body.String(), "") 83 | expect(t, rw.Status(), http.StatusNotFound) 84 | expect(t, rw.Size(), 0) 85 | } 86 | 87 | func TestResponseWriterBefore(t *testing.T) { 88 | rec := httptest.NewRecorder() 89 | rw := NewResponseWriter(rec) 90 | result := "" 91 | 92 | rw.Before(func(ResponseWriter) { 93 | result += "foo" 94 | }) 95 | rw.Before(func(ResponseWriter) { 96 | result += "bar" 97 | }) 98 | 99 | rw.WriteHeader(http.StatusNotFound) 100 | 101 | expect(t, rec.Code, rw.Status()) 102 | expect(t, rec.Body.String(), "") 103 | expect(t, rw.Status(), http.StatusNotFound) 104 | expect(t, rw.Size(), 0) 105 | expect(t, result, "barfoo") 106 | } 107 | 108 | func TestResponseWriterHijack(t *testing.T) { 109 | hijackable := newHijackableResponse() 110 | rw := NewResponseWriter(hijackable) 111 | hijacker, ok := rw.(http.Hijacker) 112 | expect(t, ok, true) 113 | _, _, err := hijacker.Hijack() 114 | if err != nil { 115 | t.Error(err) 116 | } 117 | expect(t, hijackable.Hijacked, true) 118 | } 119 | 120 | func TestResponseWriteHijackNotOK(t *testing.T) { 121 | hijackable := new(http.ResponseWriter) 122 | rw := NewResponseWriter(*hijackable) 123 | hijacker, ok := rw.(http.Hijacker) 124 | expect(t, ok, true) 125 | _, _, err := hijacker.Hijack() 126 | 127 | refute(t, err, nil) 128 | } 129 | 130 | func TestResponseWriterCloseNotify(t *testing.T) { 131 | rec := newCloseNotifyingRecorder() 132 | rw := NewResponseWriter(rec) 133 | closed := false 134 | notifier := rw.(http.CloseNotifier).CloseNotify() 135 | rec.close() 136 | select { 137 | case <-notifier: 138 | closed = true 139 | case <-time.After(time.Second): 140 | } 141 | expect(t, closed, true) 142 | } 143 | 144 | func TestResponseWriterFlusher(t *testing.T) { 145 | rec := httptest.NewRecorder() 146 | rw := NewResponseWriter(rec) 147 | 148 | _, ok := rw.(http.Flusher) 149 | expect(t, ok, true) 150 | } 151 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/static.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "net/http" 5 | "path" 6 | "strings" 7 | ) 8 | 9 | // Static is a middleware handler that serves static files in the given directory/filesystem. 10 | type Static struct { 11 | // Dir is the directory to serve static files from 12 | Dir http.FileSystem 13 | // Prefix is the optional prefix used to serve the static directory content 14 | Prefix string 15 | // IndexFile defines which file to serve as index if it exists. 16 | IndexFile string 17 | } 18 | 19 | // NewStatic returns a new instance of Static 20 | func NewStatic(directory http.FileSystem) *Static { 21 | return &Static{ 22 | Dir: directory, 23 | Prefix: "", 24 | IndexFile: "index.html", 25 | } 26 | } 27 | 28 | func (s *Static) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 29 | if r.Method != "GET" && r.Method != "HEAD" { 30 | next(rw, r) 31 | return 32 | } 33 | file := r.URL.Path 34 | // if we have a prefix, filter requests by stripping the prefix 35 | if s.Prefix != "" { 36 | if !strings.HasPrefix(file, s.Prefix) { 37 | next(rw, r) 38 | return 39 | } 40 | file = file[len(s.Prefix):] 41 | if file != "" && file[0] != '/' { 42 | next(rw, r) 43 | return 44 | } 45 | } 46 | f, err := s.Dir.Open(file) 47 | if err != nil { 48 | // discard the error? 49 | next(rw, r) 50 | return 51 | } 52 | defer f.Close() 53 | 54 | fi, err := f.Stat() 55 | if err != nil { 56 | next(rw, r) 57 | return 58 | } 59 | 60 | // try to serve index file 61 | if fi.IsDir() { 62 | // redirect if missing trailing slash 63 | if !strings.HasSuffix(r.URL.Path, "/") { 64 | http.Redirect(rw, r, r.URL.Path+"/", http.StatusFound) 65 | return 66 | } 67 | 68 | file = path.Join(file, s.IndexFile) 69 | f, err = s.Dir.Open(file) 70 | if err != nil { 71 | next(rw, r) 72 | return 73 | } 74 | defer f.Close() 75 | 76 | fi, err = f.Stat() 77 | if err != nil || fi.IsDir() { 78 | next(rw, r) 79 | return 80 | } 81 | } 82 | 83 | http.ServeContent(rw, r, file, fi.ModTime(), f) 84 | } 85 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/negroni/static_test.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestStatic(t *testing.T) { 11 | response := httptest.NewRecorder() 12 | response.Body = new(bytes.Buffer) 13 | 14 | n := New() 15 | n.Use(NewStatic(http.Dir("."))) 16 | 17 | req, err := http.NewRequest("GET", "http://localhost:3000/negroni.go", nil) 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | n.ServeHTTP(response, req) 22 | expect(t, response.Code, http.StatusOK) 23 | expect(t, response.Header().Get("Expires"), "") 24 | if response.Body.Len() == 0 { 25 | t.Errorf("Got empty body for GET request") 26 | } 27 | } 28 | 29 | func TestStaticHead(t *testing.T) { 30 | response := httptest.NewRecorder() 31 | response.Body = new(bytes.Buffer) 32 | 33 | n := New() 34 | n.Use(NewStatic(http.Dir("."))) 35 | n.UseHandler(http.NotFoundHandler()) 36 | 37 | req, err := http.NewRequest("HEAD", "http://localhost:3000/negroni.go", nil) 38 | if err != nil { 39 | t.Error(err) 40 | } 41 | 42 | n.ServeHTTP(response, req) 43 | expect(t, response.Code, http.StatusOK) 44 | if response.Body.Len() != 0 { 45 | t.Errorf("Got non-empty body for HEAD request") 46 | } 47 | } 48 | 49 | func TestStaticAsPost(t *testing.T) { 50 | response := httptest.NewRecorder() 51 | 52 | n := New() 53 | n.Use(NewStatic(http.Dir("."))) 54 | n.UseHandler(http.NotFoundHandler()) 55 | 56 | req, err := http.NewRequest("POST", "http://localhost:3000/negroni.go", nil) 57 | if err != nil { 58 | t.Error(err) 59 | } 60 | 61 | n.ServeHTTP(response, req) 62 | expect(t, response.Code, http.StatusNotFound) 63 | } 64 | 65 | func TestStaticBadDir(t *testing.T) { 66 | response := httptest.NewRecorder() 67 | 68 | n := Classic() 69 | n.UseHandler(http.NotFoundHandler()) 70 | 71 | req, err := http.NewRequest("GET", "http://localhost:3000/negroni.go", nil) 72 | if err != nil { 73 | t.Error(err) 74 | } 75 | 76 | n.ServeHTTP(response, req) 77 | refute(t, response.Code, http.StatusOK) 78 | } 79 | 80 | func TestStaticOptionsServeIndex(t *testing.T) { 81 | response := httptest.NewRecorder() 82 | 83 | n := New() 84 | s := NewStatic(http.Dir(".")) 85 | s.IndexFile = "negroni.go" 86 | n.Use(s) 87 | 88 | req, err := http.NewRequest("GET", "http://localhost:3000/", nil) 89 | if err != nil { 90 | t.Error(err) 91 | } 92 | 93 | n.ServeHTTP(response, req) 94 | expect(t, response.Code, http.StatusOK) 95 | } 96 | 97 | func TestStaticOptionsPrefix(t *testing.T) { 98 | response := httptest.NewRecorder() 99 | 100 | n := New() 101 | s := NewStatic(http.Dir(".")) 102 | s.Prefix = "/public" 103 | n.Use(s) 104 | 105 | // Check file content behaviour 106 | req, err := http.NewRequest("GET", "http://localhost:3000/public/negroni.go", nil) 107 | if err != nil { 108 | t.Error(err) 109 | } 110 | 111 | n.ServeHTTP(response, req) 112 | expect(t, response.Code, http.StatusOK) 113 | } 114 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.1 5 | - 1.2 6 | - 1.3 7 | - 1.4 8 | - tip 9 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/julienschmidt/httprouter/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Julien Schmidt. All rights reserved. 2 | 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * The names of the contributors may not be used to endorse or promote 12 | products derived from this software without specific prior written 13 | permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL JULIEN SCHMIDT BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md: -------------------------------------------------------------------------------- 1 | # HttpRouter [![Build Status](https://travis-ci.org/julienschmidt/httprouter.png?branch=master)](https://travis-ci.org/julienschmidt/httprouter) [![Coverage](http://gocover.io/_badge/github.com/julienschmidt/httprouter?0)](http://gocover.io/github.com/julienschmidt/httprouter) [![GoDoc](http://godoc.org/github.com/julienschmidt/httprouter?status.png)](http://godoc.org/github.com/julienschmidt/httprouter) 2 | 3 | HttpRouter is a lightweight high performance HTTP request router 4 | (also called *multiplexer* or just *mux* for short) for [Go](http://golang.org/). 5 | 6 | In contrast to the [default mux](http://golang.org/pkg/net/http/#ServeMux) of Go's net/http package, this router supports 7 | variables in the routing pattern and matches against the request method. 8 | It also scales better. 9 | 10 | The router is optimized for high performance and a small memory footprint. 11 | It scales well even with very long paths and a large number of routes. 12 | A compressing dynamic trie (radix tree) structure is used for efficient matching. 13 | 14 | ## Features 15 | **Only explicit matches:** With other routers, like [http.ServeMux](http://golang.org/pkg/net/http/#ServeMux), 16 | a requested URL path could match multiple patterns. Therefore they have some 17 | awkward pattern priority rules, like *longest match* or *first registered, 18 | first matched*. By design of this router, a request can only match exactly one 19 | or no route. As a result, there are also no unintended matches, which makes it 20 | great for SEO and improves the user experience. 21 | 22 | **Stop caring about trailing slashes:** Choose the URL style you like, the 23 | router automatically redirects the client if a trailing slash is missing or if 24 | there is one extra. Of course it only does so, if the new path has a handler. 25 | If you don't like it, you can [turn off this behavior](http://godoc.org/github.com/julienschmidt/httprouter#Router.RedirectTrailingSlash). 26 | 27 | **Path auto-correction:** Besides detecting the missing or additional trailing 28 | slash at no extra cost, the router can also fix wrong cases and remove 29 | superfluous path elements (like `../` or `//`). 30 | Is [CAPTAIN CAPS LOCK](http://www.urbandictionary.com/define.php?term=Captain+Caps+Lock) one of your users? 31 | HttpRouter can help him by making a case-insensitive look-up and redirecting him 32 | to the correct URL. 33 | 34 | **Parameters in your routing pattern:** Stop parsing the requested URL path, 35 | just give the path segment a name and the router delivers the dynamic value to 36 | you. Because of the design of the router, path parameters are very cheap. 37 | 38 | **Zero Garbage:** The matching and dispatching process generates zero bytes of 39 | garbage. In fact, the only heap allocations that are made, is by building the 40 | slice of the key-value pairs for path parameters. If the request path contains 41 | no parameters, not a single heap allocation is necessary. 42 | 43 | **Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark). 44 | See below for technical details of the implementation. 45 | 46 | **No more server crashes:** You can set a [Panic handler](http://godoc.org/github.com/julienschmidt/httprouter#Router.PanicHandler) to deal with panics 47 | occurring during handling a HTTP request. The router then recovers and lets the 48 | PanicHandler log what happened and deliver a nice error page. 49 | 50 | Of course you can also set **custom [NotFound](http://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) and [MethodNotAllowed](http://godoc.org/github.com/julienschmidt/httprouter#Router.MethodNotAllowed) handlers** and [**serve static files**](http://godoc.org/github.com/julienschmidt/httprouter#Router.ServeFiles). 51 | 52 | ## Usage 53 | This is just a quick introduction, view the [GoDoc](http://godoc.org/github.com/julienschmidt/httprouter) for details. 54 | 55 | Let's start with a trivial example: 56 | ```go 57 | package main 58 | 59 | import ( 60 | "fmt" 61 | "github.com/julienschmidt/httprouter" 62 | "net/http" 63 | "log" 64 | ) 65 | 66 | func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 67 | fmt.Fprint(w, "Welcome!\n") 68 | } 69 | 70 | func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 71 | fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) 72 | } 73 | 74 | func main() { 75 | router := httprouter.New() 76 | router.GET("/", Index) 77 | router.GET("/hello/:name", Hello) 78 | 79 | log.Fatal(http.ListenAndServe(":8080", router)) 80 | } 81 | ``` 82 | 83 | ### Named parameters 84 | As you can see, `:name` is a *named parameter*. 85 | The values are accessible via `httprouter.Params`, which is just a slice of `httprouter.Param`s. 86 | You can get the value of a parameter either by its index in the slice, or by using the `ByName(name)` method: 87 | `:name` can be retrived by `ByName("name")`. 88 | 89 | Named parameters only match a single path segment: 90 | ``` 91 | Pattern: /user/:user 92 | 93 | /user/gordon match 94 | /user/you match 95 | /user/gordon/profile no match 96 | /user/ no match 97 | ``` 98 | 99 | **Note:** Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns `/user/new` and `/user/:user` for the same request method at the same time. The routing of different request methods is independent from each other. 100 | 101 | ### Catch-All parameters 102 | The second type are *catch-all* parameters and have the form `*name`. 103 | Like the name suggests, they match everything. 104 | Therefore they must always be at the **end** of the pattern: 105 | ``` 106 | Pattern: /src/*filepath 107 | 108 | /src/ match 109 | /src/somefile.go match 110 | /src/subdir/somefile.go match 111 | ``` 112 | 113 | ## How does it work? 114 | The router relies on a tree structure which makes heavy use of *common prefixes*, 115 | it is basically a *compact* [*prefix tree*](http://en.wikipedia.org/wiki/Trie) 116 | (or just [*Radix tree*](http://en.wikipedia.org/wiki/Radix_tree)). 117 | Nodes with a common prefix also share a common parent. Here is a short example 118 | what the routing tree for the `GET` request method could look like: 119 | 120 | ``` 121 | Priority Path Handle 122 | 9 \ *<1> 123 | 3 ├s nil 124 | 2 |├earch\ *<2> 125 | 1 |└upport\ *<3> 126 | 2 ├blog\ *<4> 127 | 1 | └:post nil 128 | 1 | └\ *<5> 129 | 2 ├about-us\ *<6> 130 | 1 | └team\ *<7> 131 | 1 └contact\ *<8> 132 | ``` 133 | Every `*` represents the memory address of a handler function (a pointer). 134 | If you follow a path trough the tree from the root to the leaf, you get the 135 | complete route path, e.g `\blog\:post\`, where `:post` is just a placeholder 136 | ([*parameter*](#named-parameters)) for an actual post name. Unlike hash-maps, a 137 | tree structure also allows us to use dynamic parts like the `:post` parameter, 138 | since we actually match against the routing patterns instead of just comparing 139 | hashes. [As benchmarks show](https://github.com/julienschmidt/go-http-routing-benchmark), 140 | this works very well and efficient. 141 | 142 | Since URL paths have a hierarchical structure and make use only of a limited set 143 | of characters (byte values), it is very likely that there are a lot of common 144 | prefixes. This allows us to easily reduce the routing into ever smaller problems. 145 | Moreover the router manages a separate tree for every request method. 146 | For one thing it is more space efficient than holding a method->handle map in 147 | every single node, for another thing is also allows us to greatly reduce the 148 | routing problem before even starting the look-up in the prefix-tree. 149 | 150 | For even better scalability, the child nodes on each tree level are ordered by 151 | priority, where the priority is just the number of handles registered in sub 152 | nodes (children, grandchildren, and so on..). 153 | This helps in two ways: 154 | 155 | 1. Nodes which are part of the most routing paths are evaluated first. This 156 | helps to make as much routes as possible to be reachable as fast as possible. 157 | 2. It is some sort of cost compensation. The longest reachable path (highest 158 | cost) can always be evaluated first. The following scheme visualizes the tree 159 | structure. Nodes are evaluated from top to bottom and from left to right. 160 | 161 | ``` 162 | ├------------ 163 | ├--------- 164 | ├----- 165 | ├---- 166 | ├-- 167 | ├-- 168 | └- 169 | ``` 170 | 171 | 172 | ## Why doesn't this work with http.Handler? 173 | **It does!** The router itself implements the http.Handler interface. 174 | Moreover the router provides convenient [adapters for http.Handler](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handler)s and [http.HandlerFunc](http://godoc.org/github.com/julienschmidt/httprouter#Router.HandlerFunc)s 175 | which allows them to be used as a [httprouter.Handle](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) when registering a route. 176 | The only disadvantage is, that no parameter values can be retrieved when a 177 | http.Handler or http.HandlerFunc is used, since there is no efficient way to 178 | pass the values with the existing function parameters. 179 | Therefore [httprouter.Handle](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) has a third function parameter. 180 | 181 | Just try it out for yourself, the usage of HttpRouter is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up. 182 | 183 | 184 | ## Where can I find Middleware *X*? 185 | This package just provides a very efficient request router with a few extra 186 | features. The router is just a [http.Handler](http://golang.org/pkg/net/http/#Handler), 187 | you can chain any http.Handler compatible middleware before the router, 188 | for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers). 189 | Or you could [just write your own](http://justinas.org/writing-http-middleware-in-go/), 190 | it's very easy! 191 | 192 | Alternatively, you could try [a web framework based on HttpRouter](#web-frameworks-based-on-httprouter). 193 | 194 | ### Multi-domain / Sub-domains 195 | Here is a quick example: Does your server serve multiple domains / hosts? 196 | You want to use sub-domains? 197 | Define a router per host! 198 | ```go 199 | // We need an object that implements the http.Handler interface. 200 | // Therefore we need a type for which we implement the ServeHTTP method. 201 | // We just use a map here, in which we map host names (with port) to http.Handlers 202 | type HostSwitch map[string]http.Handler 203 | 204 | // Implement the ServerHTTP method on our new type 205 | func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) { 206 | // Check if a http.Handler is registered for the given host. 207 | // If yes, use it to handle the request. 208 | if handler := hs[r.Host]; handler != nil { 209 | handler.ServeHTTP(w, r) 210 | } else { 211 | // Handle host names for wich no handler is registered 212 | http.Error(w, "Forbidden", 403) // Or Redirect? 213 | } 214 | } 215 | 216 | func main() { 217 | // Initialize a router as usual 218 | router := httprouter.New() 219 | router.GET("/", Index) 220 | router.GET("/hello/:name", Hello) 221 | 222 | // Make a new HostSwitch and insert the router (our http handler) 223 | // for example.com and port 12345 224 | hs := make(HostSwitch) 225 | hs["example.com:12345"] = router 226 | 227 | // Use the HostSwitch to listen and serve on port 12345 228 | log.Fatal(http.ListenAndServe(":12345", hs)) 229 | } 230 | ``` 231 | 232 | ### Basic Authentication 233 | Another quick example: Basic Authentification (RFC 2617) for handles: 234 | 235 | ```go 236 | package main 237 | 238 | import ( 239 | "bytes" 240 | "encoding/base64" 241 | "fmt" 242 | "github.com/julienschmidt/httprouter" 243 | "net/http" 244 | "log" 245 | "strings" 246 | ) 247 | 248 | func BasicAuth(h httprouter.Handle, user, pass []byte) httprouter.Handle { 249 | return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 250 | const basicAuthPrefix string = "Basic " 251 | 252 | // Get the Basic Authentication credentials 253 | auth := r.Header.Get("Authorization") 254 | if strings.HasPrefix(auth, basicAuthPrefix) { 255 | // Check credentials 256 | payload, err := base64.StdEncoding.DecodeString(auth[len(basicAuthPrefix):]) 257 | if err == nil { 258 | pair := bytes.SplitN(payload, []byte(":"), 2) 259 | if len(pair) == 2 && 260 | bytes.Equal(pair[0], user) && 261 | bytes.Equal(pair[1], pass) { 262 | 263 | // Delegate request to the given handle 264 | h(w, r, ps) 265 | return 266 | } 267 | } 268 | } 269 | 270 | // Request Basic Authentication otherwise 271 | w.Header().Set("WWW-Authenticate", "Basic realm=Restricted") 272 | http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 273 | } 274 | } 275 | 276 | func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 277 | fmt.Fprint(w, "Not protected!\n") 278 | } 279 | 280 | func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 281 | fmt.Fprint(w, "Protected!\n") 282 | } 283 | 284 | func main() { 285 | user := []byte("gordon") 286 | pass := []byte("secret!") 287 | 288 | router := httprouter.New() 289 | router.GET("/", Index) 290 | router.GET("/protected/", BasicAuth(Protected, user, pass)) 291 | 292 | log.Fatal(http.ListenAndServe(":8080", router)) 293 | } 294 | ``` 295 | 296 | ## Chaining with the NotFound handler 297 | 298 | **NOTE: It might be required to set [Router.HandleMethodNotAllowed](http://godoc.org/github.com/julienschmidt/httprouter#Router.HandleMethodNotAllowed) to `false` to avoid problems.** 299 | 300 | You can use another [http.HandlerFunc](http://golang.org/pkg/net/http/#HandlerFunc), for example another router, to handle requests which could not be matched by this router by using the [Router.NotFound](http://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) handler. This allows chaining. 301 | 302 | ### Static files 303 | The `NotFound` handler can for example be used to serve static files from the root path `/` (like an index.html file along with other assets): 304 | ```go 305 | // Serve static files from the ./public directory 306 | router.NotFound = http.FileServer(http.Dir("public")).ServeHTTP 307 | ``` 308 | 309 | But this approach sidesteps the strict core rules of this router to avoid routing problems. A cleaner approach is to use a distinct sub-path for serving files, like `/static/*filepath` or `/files/*filepath`. 310 | 311 | ## Web Frameworks based on HttpRouter 312 | If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package: 313 | * [Ace](https://github.com/plimble/ace): Blazing fast Go Web Framework 314 | * [api2go](https://github.com/univedo/api2go): A JSON API Implementation for Go 315 | * [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance 316 | * [Goat](https://github.com/bahlo/goat): A minimalistic REST API server in Go 317 | * [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine 318 | * [Hitch](https://github.com/nbio/hitch): Hitch ties httprouter, [httpcontext](https://github.com/nbio/httpcontext), and middleware up in a bow 319 | * [kami](https://github.com/guregu/kami): A tiny web framework using x/net/context 320 | * [Medeina](https://github.com/imdario/medeina): Inspired by Ruby's Roda and Cuba 321 | * [Neko](https://github.com/rocwong/neko): A lightweight web application framework for Golang 322 | * [Roxanna](https://github.com/iamthemuffinman/Roxanna): An amalgamation of httprouter, better logging, and hot reload 323 | * [siesta](https://github.com/VividCortex/siesta): Composable HTTP handlers with contexts 324 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/julienschmidt/httprouter/path.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Julien Schmidt. All rights reserved. 2 | // Based on the path package, Copyright 2009 The Go Authors. 3 | // Use of this source code is governed by a BSD-style license that can be found 4 | // in the LICENSE file. 5 | 6 | package httprouter 7 | 8 | // CleanPath is the URL version of path.Clean, it returns a canonical URL path 9 | // for p, eliminating . and .. elements. 10 | // 11 | // The following rules are applied iteratively until no further processing can 12 | // be done: 13 | // 1. Replace multiple slashes with a single slash. 14 | // 2. Eliminate each . path name element (the current directory). 15 | // 3. Eliminate each inner .. path name element (the parent directory) 16 | // along with the non-.. element that precedes it. 17 | // 4. Eliminate .. elements that begin a rooted path: 18 | // that is, replace "/.." by "/" at the beginning of a path. 19 | // 20 | // If the result of this process is an empty string, "/" is returned 21 | func CleanPath(p string) string { 22 | // Turn empty string into "/" 23 | if p == "" { 24 | return "/" 25 | } 26 | 27 | n := len(p) 28 | var buf []byte 29 | 30 | // Invariants: 31 | // reading from path; r is index of next byte to process. 32 | // writing to buf; w is index of next byte to write. 33 | 34 | // path must start with '/' 35 | r := 1 36 | w := 1 37 | 38 | if p[0] != '/' { 39 | r = 0 40 | buf = make([]byte, n+1) 41 | buf[0] = '/' 42 | } 43 | 44 | trailing := n > 2 && p[n-1] == '/' 45 | 46 | // A bit more clunky without a 'lazybuf' like the path package, but the loop 47 | // gets completely inlined (bufApp). So in contrast to the path package this 48 | // loop has no expensive function calls (except 1x make) 49 | 50 | for r < n { 51 | switch { 52 | case p[r] == '/': 53 | // empty path element, trailing slash is added after the end 54 | r++ 55 | 56 | case p[r] == '.' && r+1 == n: 57 | trailing = true 58 | r++ 59 | 60 | case p[r] == '.' && p[r+1] == '/': 61 | // . element 62 | r++ 63 | 64 | case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): 65 | // .. element: remove to last / 66 | r += 2 67 | 68 | if w > 1 { 69 | // can backtrack 70 | w-- 71 | 72 | if buf == nil { 73 | for w > 1 && p[w] != '/' { 74 | w-- 75 | } 76 | } else { 77 | for w > 1 && buf[w] != '/' { 78 | w-- 79 | } 80 | } 81 | } 82 | 83 | default: 84 | // real path element. 85 | // add slash if needed 86 | if w > 1 { 87 | bufApp(&buf, p, w, '/') 88 | w++ 89 | } 90 | 91 | // copy element 92 | for r < n && p[r] != '/' { 93 | bufApp(&buf, p, w, p[r]) 94 | w++ 95 | r++ 96 | } 97 | } 98 | } 99 | 100 | // re-append trailing slash 101 | if trailing && w > 1 { 102 | bufApp(&buf, p, w, '/') 103 | w++ 104 | } 105 | 106 | if buf == nil { 107 | return p[:w] 108 | } 109 | return string(buf[:w]) 110 | } 111 | 112 | // internal helper to lazily create a buffer if necessary 113 | func bufApp(buf *[]byte, s string, w int, c byte) { 114 | if *buf == nil { 115 | if s[w] == c { 116 | return 117 | } 118 | 119 | *buf = make([]byte, len(s)) 120 | copy(*buf, s[:w]) 121 | } 122 | (*buf)[w] = c 123 | } 124 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/julienschmidt/httprouter/path_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Julien Schmidt. All rights reserved. 2 | // Based on the path package, Copyright 2009 The Go Authors. 3 | // Use of this source code is governed by a BSD-style license that can be found 4 | // in the LICENSE file. 5 | 6 | package httprouter 7 | 8 | import ( 9 | "runtime" 10 | "testing" 11 | ) 12 | 13 | var cleanTests = []struct { 14 | path, result string 15 | }{ 16 | // Already clean 17 | {"/", "/"}, 18 | {"/abc", "/abc"}, 19 | {"/a/b/c", "/a/b/c"}, 20 | {"/abc/", "/abc/"}, 21 | {"/a/b/c/", "/a/b/c/"}, 22 | 23 | // missing root 24 | {"", "/"}, 25 | {"abc", "/abc"}, 26 | {"abc/def", "/abc/def"}, 27 | {"a/b/c", "/a/b/c"}, 28 | 29 | // Remove doubled slash 30 | {"//", "/"}, 31 | {"/abc//", "/abc/"}, 32 | {"/abc/def//", "/abc/def/"}, 33 | {"/a/b/c//", "/a/b/c/"}, 34 | {"/abc//def//ghi", "/abc/def/ghi"}, 35 | {"//abc", "/abc"}, 36 | {"///abc", "/abc"}, 37 | {"//abc//", "/abc/"}, 38 | 39 | // Remove . elements 40 | {".", "/"}, 41 | {"./", "/"}, 42 | {"/abc/./def", "/abc/def"}, 43 | {"/./abc/def", "/abc/def"}, 44 | {"/abc/.", "/abc/"}, 45 | 46 | // Remove .. elements 47 | {"..", "/"}, 48 | {"../", "/"}, 49 | {"../../", "/"}, 50 | {"../..", "/"}, 51 | {"../../abc", "/abc"}, 52 | {"/abc/def/ghi/../jkl", "/abc/def/jkl"}, 53 | {"/abc/def/../ghi/../jkl", "/abc/jkl"}, 54 | {"/abc/def/..", "/abc"}, 55 | {"/abc/def/../..", "/"}, 56 | {"/abc/def/../../..", "/"}, 57 | {"/abc/def/../../..", "/"}, 58 | {"/abc/def/../../../ghi/jkl/../../../mno", "/mno"}, 59 | 60 | // Combinations 61 | {"abc/./../def", "/def"}, 62 | {"abc//./../def", "/def"}, 63 | {"abc/../../././../def", "/def"}, 64 | } 65 | 66 | func TestPathClean(t *testing.T) { 67 | for _, test := range cleanTests { 68 | if s := CleanPath(test.path); s != test.result { 69 | t.Errorf("CleanPath(%q) = %q, want %q", test.path, s, test.result) 70 | } 71 | if s := CleanPath(test.result); s != test.result { 72 | t.Errorf("CleanPath(%q) = %q, want %q", test.result, s, test.result) 73 | } 74 | } 75 | } 76 | 77 | func TestPathCleanMallocs(t *testing.T) { 78 | if testing.Short() { 79 | t.Skip("skipping malloc count in short mode") 80 | } 81 | if runtime.GOMAXPROCS(0) > 1 { 82 | t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1") 83 | return 84 | } 85 | 86 | for _, test := range cleanTests { 87 | allocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) }) 88 | if allocs > 0 { 89 | t.Errorf("CleanPath(%q): %v allocs, want zero", test.result, allocs) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Julien Schmidt. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be found 3 | // in the LICENSE file. 4 | 5 | // Package httprouter is a trie based high performance HTTP request router. 6 | // 7 | // A trivial example is: 8 | // 9 | // package main 10 | // 11 | // import ( 12 | // "fmt" 13 | // "github.com/julienschmidt/httprouter" 14 | // "net/http" 15 | // "log" 16 | // ) 17 | // 18 | // func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 19 | // fmt.Fprint(w, "Welcome!\n") 20 | // } 21 | // 22 | // func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 23 | // fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) 24 | // } 25 | // 26 | // func main() { 27 | // router := httprouter.New() 28 | // router.GET("/", Index) 29 | // router.GET("/hello/:name", Hello) 30 | // 31 | // log.Fatal(http.ListenAndServe(":8080", router)) 32 | // } 33 | // 34 | // The router matches incoming requests by the request method and the path. 35 | // If a handle is registered for this path and method, the router delegates the 36 | // request to that function. 37 | // For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to 38 | // register handles, for all other methods router.Handle can be used. 39 | // 40 | // The registered path, against which the router matches incoming requests, can 41 | // contain two types of parameters: 42 | // Syntax Type 43 | // :name named parameter 44 | // *name catch-all parameter 45 | // 46 | // Named parameters are dynamic path segments. They match anything until the 47 | // next '/' or the path end: 48 | // Path: /blog/:category/:post 49 | // 50 | // Requests: 51 | // /blog/go/request-routers match: category="go", post="request-routers" 52 | // /blog/go/request-routers/ no match, but the router would redirect 53 | // /blog/go/ no match 54 | // /blog/go/request-routers/comments no match 55 | // 56 | // Catch-all parameters match anything until the path end, including the 57 | // directory index (the '/' before the catch-all). Since they match anything 58 | // until the end, catch-all paramerters must always be the final path element. 59 | // Path: /files/*filepath 60 | // 61 | // Requests: 62 | // /files/ match: filepath="/" 63 | // /files/LICENSE match: filepath="/LICENSE" 64 | // /files/templates/article.html match: filepath="/templates/article.html" 65 | // /files no match, but the router would redirect 66 | // 67 | // The value of parameters is saved as a slice of the Param struct, consisting 68 | // each of a key and a value. The slice is passed to the Handle func as a third 69 | // parameter. 70 | // There are two ways to retrieve the value of a parameter: 71 | // // by the name of the parameter 72 | // user := ps.ByName("user") // defined by :user or *user 73 | // 74 | // // by the index of the parameter. This way you can also get the name (key) 75 | // thirdKey := ps[2].Key // the name of the 3rd parameter 76 | // thirdValue := ps[2].Value // the value of the 3rd parameter 77 | package httprouter 78 | 79 | import ( 80 | "net/http" 81 | ) 82 | 83 | // Handle is a function that can be registered to a route to handle HTTP 84 | // requests. Like http.HandlerFunc, but has a third parameter for the values of 85 | // wildcards (variables). 86 | type Handle func(http.ResponseWriter, *http.Request, Params) 87 | 88 | // Param is a single URL parameter, consisting of a key and a value. 89 | type Param struct { 90 | Key string 91 | Value string 92 | } 93 | 94 | // Params is a Param-slice, as returned by the router. 95 | // The slice is ordered, the first URL parameter is also the first slice value. 96 | // It is therefore safe to read values by the index. 97 | type Params []Param 98 | 99 | // ByName returns the value of the first Param which key matches the given name. 100 | // If no matching Param is found, an empty string is returned. 101 | func (ps Params) ByName(name string) string { 102 | for i := range ps { 103 | if ps[i].Key == name { 104 | return ps[i].Value 105 | } 106 | } 107 | return "" 108 | } 109 | 110 | // Router is a http.Handler which can be used to dispatch requests to different 111 | // handler functions via configurable routes 112 | type Router struct { 113 | trees map[string]*node 114 | 115 | // Enables automatic redirection if the current route can't be matched but a 116 | // handler for the path with (without) the trailing slash exists. 117 | // For example if /foo/ is requested but a route only exists for /foo, the 118 | // client is redirected to /foo with http status code 301 for GET requests 119 | // and 307 for all other request methods. 120 | RedirectTrailingSlash bool 121 | 122 | // If enabled, the router tries to fix the current request path, if no 123 | // handle is registered for it. 124 | // First superfluous path elements like ../ or // are removed. 125 | // Afterwards the router does a case-insensitive lookup of the cleaned path. 126 | // If a handle can be found for this route, the router makes a redirection 127 | // to the corrected path with status code 301 for GET requests and 307 for 128 | // all other request methods. 129 | // For example /FOO and /..//Foo could be redirected to /foo. 130 | // RedirectTrailingSlash is independent of this option. 131 | RedirectFixedPath bool 132 | 133 | // If enabled, the router checks if another method is allowed for the 134 | // current route, if the current request can not be routed. 135 | // If this is the case, the request is answered with 'Method Not Allowed' 136 | // and HTTP status code 405. 137 | // If no other Method is allowed, the request is delegated to the NotFound 138 | // handler. 139 | HandleMethodNotAllowed bool 140 | 141 | // Configurable http.HandlerFunc which is called when no matching route is 142 | // found. If it is not set, http.NotFound is used. 143 | NotFound http.HandlerFunc 144 | 145 | // Configurable http.HandlerFunc which is called when a request 146 | // cannot be routed and HandleMethodNotAllowed is true. 147 | // If it is not set, http.Error with http.StatusMethodNotAllowed is used. 148 | MethodNotAllowed http.HandlerFunc 149 | 150 | // Function to handle panics recovered from http handlers. 151 | // It should be used to generate a error page and return the http error code 152 | // 500 (Internal Server Error). 153 | // The handler can be used to keep your server from crashing because of 154 | // unrecovered panics. 155 | PanicHandler func(http.ResponseWriter, *http.Request, interface{}) 156 | } 157 | 158 | // Make sure the Router conforms with the http.Handler interface 159 | var _ http.Handler = New() 160 | 161 | // New returns a new initialized Router. 162 | // Path auto-correction, including trailing slashes, is enabled by default. 163 | func New() *Router { 164 | return &Router{ 165 | RedirectTrailingSlash: true, 166 | RedirectFixedPath: true, 167 | HandleMethodNotAllowed: true, 168 | } 169 | } 170 | 171 | // GET is a shortcut for router.Handle("GET", path, handle) 172 | func (r *Router) GET(path string, handle Handle) { 173 | r.Handle("GET", path, handle) 174 | } 175 | 176 | // HEAD is a shortcut for router.Handle("HEAD", path, handle) 177 | func (r *Router) HEAD(path string, handle Handle) { 178 | r.Handle("HEAD", path, handle) 179 | } 180 | 181 | // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) 182 | func (r *Router) OPTIONS(path string, handle Handle) { 183 | r.Handle("OPTIONS", path, handle) 184 | } 185 | 186 | // POST is a shortcut for router.Handle("POST", path, handle) 187 | func (r *Router) POST(path string, handle Handle) { 188 | r.Handle("POST", path, handle) 189 | } 190 | 191 | // PUT is a shortcut for router.Handle("PUT", path, handle) 192 | func (r *Router) PUT(path string, handle Handle) { 193 | r.Handle("PUT", path, handle) 194 | } 195 | 196 | // PATCH is a shortcut for router.Handle("PATCH", path, handle) 197 | func (r *Router) PATCH(path string, handle Handle) { 198 | r.Handle("PATCH", path, handle) 199 | } 200 | 201 | // DELETE is a shortcut for router.Handle("DELETE", path, handle) 202 | func (r *Router) DELETE(path string, handle Handle) { 203 | r.Handle("DELETE", path, handle) 204 | } 205 | 206 | // Handle registers a new request handle with the given path and method. 207 | // 208 | // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut 209 | // functions can be used. 210 | // 211 | // This function is intended for bulk loading and to allow the usage of less 212 | // frequently used, non-standardized or custom methods (e.g. for internal 213 | // communication with a proxy). 214 | func (r *Router) Handle(method, path string, handle Handle) { 215 | if path[0] != '/' { 216 | panic("path must begin with '/' in path '" + path + "'") 217 | } 218 | 219 | if r.trees == nil { 220 | r.trees = make(map[string]*node) 221 | } 222 | 223 | root := r.trees[method] 224 | if root == nil { 225 | root = new(node) 226 | r.trees[method] = root 227 | } 228 | 229 | root.addRoute(path, handle) 230 | } 231 | 232 | // Handler is an adapter which allows the usage of an http.Handler as a 233 | // request handle. 234 | func (r *Router) Handler(method, path string, handler http.Handler) { 235 | r.Handle(method, path, 236 | func(w http.ResponseWriter, req *http.Request, _ Params) { 237 | handler.ServeHTTP(w, req) 238 | }, 239 | ) 240 | } 241 | 242 | // HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a 243 | // request handle. 244 | func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) { 245 | r.Handler(method, path, handler) 246 | } 247 | 248 | // ServeFiles serves files from the given file system root. 249 | // The path must end with "/*filepath", files are then served from the local 250 | // path /defined/root/dir/*filepath. 251 | // For example if root is "/etc" and *filepath is "passwd", the local file 252 | // "/etc/passwd" would be served. 253 | // Internally a http.FileServer is used, therefore http.NotFound is used instead 254 | // of the Router's NotFound handler. 255 | // To use the operating system's file system implementation, 256 | // use http.Dir: 257 | // router.ServeFiles("/src/*filepath", http.Dir("/var/www")) 258 | func (r *Router) ServeFiles(path string, root http.FileSystem) { 259 | if len(path) < 10 || path[len(path)-10:] != "/*filepath" { 260 | panic("path must end with /*filepath in path '" + path + "'") 261 | } 262 | 263 | fileServer := http.FileServer(root) 264 | 265 | r.GET(path, func(w http.ResponseWriter, req *http.Request, ps Params) { 266 | req.URL.Path = ps.ByName("filepath") 267 | fileServer.ServeHTTP(w, req) 268 | }) 269 | } 270 | 271 | func (r *Router) recv(w http.ResponseWriter, req *http.Request) { 272 | if rcv := recover(); rcv != nil { 273 | r.PanicHandler(w, req, rcv) 274 | } 275 | } 276 | 277 | // Lookup allows the manual lookup of a method + path combo. 278 | // This is e.g. useful to build a framework around this router. 279 | // If the path was found, it returns the handle function and the path parameter 280 | // values. Otherwise the third return value indicates whether a redirection to 281 | // the same path with an extra / without the trailing slash should be performed. 282 | func (r *Router) Lookup(method, path string) (Handle, Params, bool) { 283 | if root := r.trees[method]; root != nil { 284 | return root.getValue(path) 285 | } 286 | return nil, nil, false 287 | } 288 | 289 | // ServeHTTP makes the router implement the http.Handler interface. 290 | func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { 291 | if r.PanicHandler != nil { 292 | defer r.recv(w, req) 293 | } 294 | 295 | if root := r.trees[req.Method]; root != nil { 296 | path := req.URL.Path 297 | 298 | if handle, ps, tsr := root.getValue(path); handle != nil { 299 | handle(w, req, ps) 300 | return 301 | } else if req.Method != "CONNECT" && path != "/" { 302 | code := 301 // Permanent redirect, request with GET method 303 | if req.Method != "GET" { 304 | // Temporary redirect, request with same method 305 | // As of Go 1.3, Go does not support status code 308. 306 | code = 307 307 | } 308 | 309 | if tsr && r.RedirectTrailingSlash { 310 | if len(path) > 1 && path[len(path)-1] == '/' { 311 | req.URL.Path = path[:len(path)-1] 312 | } else { 313 | req.URL.Path = path + "/" 314 | } 315 | http.Redirect(w, req, req.URL.String(), code) 316 | return 317 | } 318 | 319 | // Try to fix the request path 320 | if r.RedirectFixedPath { 321 | fixedPath, found := root.findCaseInsensitivePath( 322 | CleanPath(path), 323 | r.RedirectTrailingSlash, 324 | ) 325 | if found { 326 | req.URL.Path = string(fixedPath) 327 | http.Redirect(w, req, req.URL.String(), code) 328 | return 329 | } 330 | } 331 | } 332 | } 333 | 334 | // Handle 405 335 | if r.HandleMethodNotAllowed { 336 | for method := range r.trees { 337 | // Skip the requested method - we already tried this one 338 | if method == req.Method { 339 | continue 340 | } 341 | 342 | handle, _, _ := r.trees[method].getValue(req.URL.Path) 343 | if handle != nil { 344 | if r.MethodNotAllowed != nil { 345 | r.MethodNotAllowed(w, req) 346 | } else { 347 | http.Error(w, 348 | http.StatusText(http.StatusMethodNotAllowed), 349 | http.StatusMethodNotAllowed, 350 | ) 351 | } 352 | return 353 | } 354 | } 355 | } 356 | 357 | // Handle 404 358 | if r.NotFound != nil { 359 | r.NotFound(w, req) 360 | } else { 361 | http.NotFound(w, req) 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Julien Schmidt. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be found 3 | // in the LICENSE file. 4 | 5 | package httprouter 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "net/http" 11 | "net/http/httptest" 12 | "reflect" 13 | "testing" 14 | ) 15 | 16 | type mockResponseWriter struct{} 17 | 18 | func (m *mockResponseWriter) Header() (h http.Header) { 19 | return http.Header{} 20 | } 21 | 22 | func (m *mockResponseWriter) Write(p []byte) (n int, err error) { 23 | return len(p), nil 24 | } 25 | 26 | func (m *mockResponseWriter) WriteString(s string) (n int, err error) { 27 | return len(s), nil 28 | } 29 | 30 | func (m *mockResponseWriter) WriteHeader(int) {} 31 | 32 | func TestParams(t *testing.T) { 33 | ps := Params{ 34 | Param{"param1", "value1"}, 35 | Param{"param2", "value2"}, 36 | Param{"param3", "value3"}, 37 | } 38 | for i := range ps { 39 | if val := ps.ByName(ps[i].Key); val != ps[i].Value { 40 | t.Errorf("Wrong value for %s: Got %s; Want %s", ps[i].Key, val, ps[i].Value) 41 | } 42 | } 43 | if val := ps.ByName("noKey"); val != "" { 44 | t.Errorf("Expected empty string for not found key; got: %s", val) 45 | } 46 | } 47 | 48 | func TestRouter(t *testing.T) { 49 | router := New() 50 | 51 | routed := false 52 | router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) { 53 | routed = true 54 | want := Params{Param{"name", "gopher"}} 55 | if !reflect.DeepEqual(ps, want) { 56 | t.Fatalf("wrong wildcard values: want %v, got %v", want, ps) 57 | } 58 | }) 59 | 60 | w := new(mockResponseWriter) 61 | 62 | req, _ := http.NewRequest("GET", "/user/gopher", nil) 63 | router.ServeHTTP(w, req) 64 | 65 | if !routed { 66 | t.Fatal("routing failed") 67 | } 68 | } 69 | 70 | type handlerStruct struct { 71 | handeled *bool 72 | } 73 | 74 | func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) { 75 | *h.handeled = true 76 | } 77 | 78 | func TestRouterAPI(t *testing.T) { 79 | var get, head, options, post, put, patch, delete, handler, handlerFunc bool 80 | 81 | httpHandler := handlerStruct{&handler} 82 | 83 | router := New() 84 | router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { 85 | get = true 86 | }) 87 | router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { 88 | head = true 89 | }) 90 | router.OPTIONS("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { 91 | options = true 92 | }) 93 | router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) { 94 | post = true 95 | }) 96 | router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) { 97 | put = true 98 | }) 99 | router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) { 100 | patch = true 101 | }) 102 | router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) { 103 | delete = true 104 | }) 105 | router.Handler("GET", "/Handler", httpHandler) 106 | router.HandlerFunc("GET", "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) { 107 | handlerFunc = true 108 | }) 109 | 110 | w := new(mockResponseWriter) 111 | 112 | r, _ := http.NewRequest("GET", "/GET", nil) 113 | router.ServeHTTP(w, r) 114 | if !get { 115 | t.Error("routing GET failed") 116 | } 117 | 118 | r, _ = http.NewRequest("HEAD", "/GET", nil) 119 | router.ServeHTTP(w, r) 120 | if !head { 121 | t.Error("routing HEAD failed") 122 | } 123 | 124 | r, _ = http.NewRequest("OPTIONS", "/GET", nil) 125 | router.ServeHTTP(w, r) 126 | if !options { 127 | t.Error("routing OPTIONS failed") 128 | } 129 | 130 | r, _ = http.NewRequest("POST", "/POST", nil) 131 | router.ServeHTTP(w, r) 132 | if !post { 133 | t.Error("routing POST failed") 134 | } 135 | 136 | r, _ = http.NewRequest("PUT", "/PUT", nil) 137 | router.ServeHTTP(w, r) 138 | if !put { 139 | t.Error("routing PUT failed") 140 | } 141 | 142 | r, _ = http.NewRequest("PATCH", "/PATCH", nil) 143 | router.ServeHTTP(w, r) 144 | if !patch { 145 | t.Error("routing PATCH failed") 146 | } 147 | 148 | r, _ = http.NewRequest("DELETE", "/DELETE", nil) 149 | router.ServeHTTP(w, r) 150 | if !delete { 151 | t.Error("routing DELETE failed") 152 | } 153 | 154 | r, _ = http.NewRequest("GET", "/Handler", nil) 155 | router.ServeHTTP(w, r) 156 | if !handler { 157 | t.Error("routing Handler failed") 158 | } 159 | 160 | r, _ = http.NewRequest("GET", "/HandlerFunc", nil) 161 | router.ServeHTTP(w, r) 162 | if !handlerFunc { 163 | t.Error("routing HandlerFunc failed") 164 | } 165 | } 166 | 167 | func TestRouterRoot(t *testing.T) { 168 | router := New() 169 | recv := catchPanic(func() { 170 | router.GET("noSlashRoot", nil) 171 | }) 172 | if recv == nil { 173 | t.Fatal("registering path not beginning with '/' did not panic") 174 | } 175 | } 176 | 177 | func TestRouterNotAllowed(t *testing.T) { 178 | handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {} 179 | 180 | router := New() 181 | router.POST("/path", handlerFunc) 182 | 183 | // Test not allowed 184 | r, _ := http.NewRequest("GET", "/path", nil) 185 | w := httptest.NewRecorder() 186 | router.ServeHTTP(w, r) 187 | if !(w.Code == http.StatusMethodNotAllowed) { 188 | t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header()) 189 | } 190 | 191 | w = httptest.NewRecorder() 192 | responseText := "custom method" 193 | router.MethodNotAllowed = func(w http.ResponseWriter, req *http.Request) { 194 | w.WriteHeader(http.StatusTeapot) 195 | w.Write([]byte(responseText)) 196 | } 197 | router.ServeHTTP(w, r) 198 | if got := w.Body.String(); !(got == responseText) { 199 | t.Errorf("unexpected response got %q want %q", got, responseText) 200 | } 201 | if w.Code != http.StatusTeapot { 202 | t.Errorf("unexpected response code %d want %d", w.Code, http.StatusTeapot) 203 | } 204 | } 205 | 206 | func TestRouterNotFound(t *testing.T) { 207 | handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {} 208 | 209 | router := New() 210 | router.GET("/path", handlerFunc) 211 | router.GET("/dir/", handlerFunc) 212 | router.GET("/", handlerFunc) 213 | 214 | testRoutes := []struct { 215 | route string 216 | code int 217 | header string 218 | }{ 219 | {"/path/", 301, "map[Location:[/path]]"}, // TSR -/ 220 | {"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/ 221 | {"", 301, "map[Location:[/]]"}, // TSR +/ 222 | {"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case 223 | {"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case 224 | {"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/ 225 | {"/DIR", 301, "map[Location:[/dir/]]"}, // Fixed Case +/ 226 | {"/../path", 301, "map[Location:[/path]]"}, // CleanPath 227 | {"/nope", 404, ""}, // NotFound 228 | } 229 | for _, tr := range testRoutes { 230 | r, _ := http.NewRequest("GET", tr.route, nil) 231 | w := httptest.NewRecorder() 232 | router.ServeHTTP(w, r) 233 | if !(w.Code == tr.code && (w.Code == 404 || fmt.Sprint(w.Header()) == tr.header)) { 234 | t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header()) 235 | } 236 | } 237 | 238 | // Test custom not found handler 239 | var notFound bool 240 | router.NotFound = func(rw http.ResponseWriter, r *http.Request) { 241 | rw.WriteHeader(404) 242 | notFound = true 243 | } 244 | r, _ := http.NewRequest("GET", "/nope", nil) 245 | w := httptest.NewRecorder() 246 | router.ServeHTTP(w, r) 247 | if !(w.Code == 404 && notFound == true) { 248 | t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header()) 249 | } 250 | 251 | // Test other method than GET (want 307 instead of 301) 252 | router.PATCH("/path", handlerFunc) 253 | r, _ = http.NewRequest("PATCH", "/path/", nil) 254 | w = httptest.NewRecorder() 255 | router.ServeHTTP(w, r) 256 | if !(w.Code == 307 && fmt.Sprint(w.Header()) == "map[Location:[/path]]") { 257 | t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header()) 258 | } 259 | 260 | // Test special case where no node for the prefix "/" exists 261 | router = New() 262 | router.GET("/a", handlerFunc) 263 | r, _ = http.NewRequest("GET", "/", nil) 264 | w = httptest.NewRecorder() 265 | router.ServeHTTP(w, r) 266 | if !(w.Code == 404) { 267 | t.Errorf("NotFound handling route / failed: Code=%d", w.Code) 268 | } 269 | } 270 | 271 | func TestRouterPanicHandler(t *testing.T) { 272 | router := New() 273 | panicHandled := false 274 | 275 | router.PanicHandler = func(rw http.ResponseWriter, r *http.Request, p interface{}) { 276 | panicHandled = true 277 | } 278 | 279 | router.Handle("PUT", "/user/:name", func(_ http.ResponseWriter, _ *http.Request, _ Params) { 280 | panic("oops!") 281 | }) 282 | 283 | w := new(mockResponseWriter) 284 | req, _ := http.NewRequest("PUT", "/user/gopher", nil) 285 | 286 | defer func() { 287 | if rcv := recover(); rcv != nil { 288 | t.Fatal("handling panic failed") 289 | } 290 | }() 291 | 292 | router.ServeHTTP(w, req) 293 | 294 | if !panicHandled { 295 | t.Fatal("simulating failed") 296 | } 297 | } 298 | 299 | func TestRouterLookup(t *testing.T) { 300 | routed := false 301 | wantHandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) { 302 | routed = true 303 | } 304 | wantParams := Params{Param{"name", "gopher"}} 305 | 306 | router := New() 307 | 308 | // try empty router first 309 | handle, _, tsr := router.Lookup("GET", "/nope") 310 | if handle != nil { 311 | t.Fatalf("Got handle for unregistered pattern: %v", handle) 312 | } 313 | if tsr { 314 | t.Error("Got wrong TSR recommendation!") 315 | } 316 | 317 | // insert route and try again 318 | router.GET("/user/:name", wantHandle) 319 | 320 | handle, params, tsr := router.Lookup("GET", "/user/gopher") 321 | if handle == nil { 322 | t.Fatal("Got no handle!") 323 | } else { 324 | handle(nil, nil, nil) 325 | if !routed { 326 | t.Fatal("Routing failed!") 327 | } 328 | } 329 | 330 | if !reflect.DeepEqual(params, wantParams) { 331 | t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params) 332 | } 333 | 334 | handle, _, tsr = router.Lookup("GET", "/user/gopher/") 335 | if handle != nil { 336 | t.Fatalf("Got handle for unregistered pattern: %v", handle) 337 | } 338 | if !tsr { 339 | t.Error("Got no TSR recommendation!") 340 | } 341 | 342 | handle, _, tsr = router.Lookup("GET", "/nope") 343 | if handle != nil { 344 | t.Fatalf("Got handle for unregistered pattern: %v", handle) 345 | } 346 | if tsr { 347 | t.Error("Got wrong TSR recommendation!") 348 | } 349 | } 350 | 351 | type mockFileSystem struct { 352 | opened bool 353 | } 354 | 355 | func (mfs *mockFileSystem) Open(name string) (http.File, error) { 356 | mfs.opened = true 357 | return nil, errors.New("this is just a mock") 358 | } 359 | 360 | func TestRouterServeFiles(t *testing.T) { 361 | router := New() 362 | mfs := &mockFileSystem{} 363 | 364 | recv := catchPanic(func() { 365 | router.ServeFiles("/noFilepath", mfs) 366 | }) 367 | if recv == nil { 368 | t.Fatal("registering path not ending with '*filepath' did not panic") 369 | } 370 | 371 | router.ServeFiles("/*filepath", mfs) 372 | w := new(mockResponseWriter) 373 | r, _ := http.NewRequest("GET", "/favicon.ico", nil) 374 | router.ServeHTTP(w, r) 375 | if !mfs.opened { 376 | t.Error("serving file failed") 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Julien Schmidt. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be found 3 | // in the LICENSE file. 4 | 5 | package httprouter 6 | 7 | import ( 8 | "strings" 9 | "unicode" 10 | ) 11 | 12 | func min(a, b int) int { 13 | if a <= b { 14 | return a 15 | } 16 | return b 17 | } 18 | 19 | func countParams(path string) uint8 { 20 | var n uint 21 | for i := 0; i < len(path); i++ { 22 | if path[i] != ':' && path[i] != '*' { 23 | continue 24 | } 25 | n++ 26 | } 27 | if n >= 255 { 28 | return 255 29 | } 30 | return uint8(n) 31 | } 32 | 33 | type nodeType uint8 34 | 35 | const ( 36 | static nodeType = 0 37 | param nodeType = 1 38 | catchAll nodeType = 2 39 | ) 40 | 41 | type node struct { 42 | path string 43 | wildChild bool 44 | nType nodeType 45 | maxParams uint8 46 | indices string 47 | children []*node 48 | handle Handle 49 | priority uint32 50 | } 51 | 52 | // increments priority of the given child and reorders if necessary 53 | func (n *node) incrementChildPrio(pos int) int { 54 | n.children[pos].priority++ 55 | prio := n.children[pos].priority 56 | 57 | // adjust position (move to front) 58 | newPos := pos 59 | for newPos > 0 && n.children[newPos-1].priority < prio { 60 | // swap node positions 61 | tmpN := n.children[newPos-1] 62 | n.children[newPos-1] = n.children[newPos] 63 | n.children[newPos] = tmpN 64 | 65 | newPos-- 66 | } 67 | 68 | // build new index char string 69 | if newPos != pos { 70 | n.indices = n.indices[:newPos] + // unchanged prefix, might be empty 71 | n.indices[pos:pos+1] + // the index char we move 72 | n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos' 73 | } 74 | 75 | return newPos 76 | } 77 | 78 | // addRoute adds a node with the given handle to the path. 79 | // Not concurrency-safe! 80 | func (n *node) addRoute(path string, handle Handle) { 81 | fullPath := path 82 | n.priority++ 83 | numParams := countParams(path) 84 | 85 | // non-empty tree 86 | if len(n.path) > 0 || len(n.children) > 0 { 87 | walk: 88 | for { 89 | // Update maxParams of the current node 90 | if numParams > n.maxParams { 91 | n.maxParams = numParams 92 | } 93 | 94 | // Find the longest common prefix. 95 | // This also implies that the common prefix contains no ':' or '*' 96 | // since the existing key can't contain those chars. 97 | i := 0 98 | max := min(len(path), len(n.path)) 99 | for i < max && path[i] == n.path[i] { 100 | i++ 101 | } 102 | 103 | // Split edge 104 | if i < len(n.path) { 105 | child := node{ 106 | path: n.path[i:], 107 | wildChild: n.wildChild, 108 | indices: n.indices, 109 | children: n.children, 110 | handle: n.handle, 111 | priority: n.priority - 1, 112 | } 113 | 114 | // Update maxParams (max of all children) 115 | for i := range child.children { 116 | if child.children[i].maxParams > child.maxParams { 117 | child.maxParams = child.children[i].maxParams 118 | } 119 | } 120 | 121 | n.children = []*node{&child} 122 | // []byte for proper unicode char conversion, see #65 123 | n.indices = string([]byte{n.path[i]}) 124 | n.path = path[:i] 125 | n.handle = nil 126 | n.wildChild = false 127 | } 128 | 129 | // Make new node a child of this node 130 | if i < len(path) { 131 | path = path[i:] 132 | 133 | if n.wildChild { 134 | n = n.children[0] 135 | n.priority++ 136 | 137 | // Update maxParams of the child node 138 | if numParams > n.maxParams { 139 | n.maxParams = numParams 140 | } 141 | numParams-- 142 | 143 | // Check if the wildcard matches 144 | if len(path) >= len(n.path) && n.path == path[:len(n.path)] { 145 | // check for longer wildcard, e.g. :name and :names 146 | if len(n.path) >= len(path) || path[len(n.path)] == '/' { 147 | continue walk 148 | } 149 | } 150 | 151 | panic("path segment '" + path + 152 | "' conflicts with existing wildcard '" + n.path + 153 | "' in path '" + fullPath + "'") 154 | } 155 | 156 | c := path[0] 157 | 158 | // slash after param 159 | if n.nType == param && c == '/' && len(n.children) == 1 { 160 | n = n.children[0] 161 | n.priority++ 162 | continue walk 163 | } 164 | 165 | // Check if a child with the next path byte exists 166 | for i := 0; i < len(n.indices); i++ { 167 | if c == n.indices[i] { 168 | i = n.incrementChildPrio(i) 169 | n = n.children[i] 170 | continue walk 171 | } 172 | } 173 | 174 | // Otherwise insert it 175 | if c != ':' && c != '*' { 176 | // []byte for proper unicode char conversion, see #65 177 | n.indices += string([]byte{c}) 178 | child := &node{ 179 | maxParams: numParams, 180 | } 181 | n.children = append(n.children, child) 182 | n.incrementChildPrio(len(n.indices) - 1) 183 | n = child 184 | } 185 | n.insertChild(numParams, path, fullPath, handle) 186 | return 187 | 188 | } else if i == len(path) { // Make node a (in-path) leaf 189 | if n.handle != nil { 190 | panic("a handle is already registered for path ''" + fullPath + "'") 191 | } 192 | n.handle = handle 193 | } 194 | return 195 | } 196 | } else { // Empty tree 197 | n.insertChild(numParams, path, fullPath, handle) 198 | } 199 | } 200 | 201 | func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle) { 202 | var offset int // already handled bytes of the path 203 | 204 | // find prefix until first wildcard (beginning with ':'' or '*'') 205 | for i, max := 0, len(path); numParams > 0; i++ { 206 | c := path[i] 207 | if c != ':' && c != '*' { 208 | continue 209 | } 210 | 211 | // find wildcard end (either '/' or path end) 212 | end := i + 1 213 | for end < max && path[end] != '/' { 214 | switch path[end] { 215 | // the wildcard name must not contain ':' and '*' 216 | case ':', '*': 217 | panic("only one wildcard per path segment is allowed, has: '" + 218 | path[i:] + "' in path '" + fullPath + "'") 219 | default: 220 | end++ 221 | } 222 | } 223 | 224 | // check if this Node existing children which would be 225 | // unreachable if we insert the wildcard here 226 | if len(n.children) > 0 { 227 | panic("wildcard route '" + path[i:end] + 228 | "' conflicts with existing children in path '" + fullPath + "'") 229 | } 230 | 231 | // check if the wildcard has a name 232 | if end-i < 2 { 233 | panic("wildcards must be named with a non-empty name in path '" + fullPath + "'") 234 | } 235 | 236 | if c == ':' { // param 237 | // split path at the beginning of the wildcard 238 | if i > 0 { 239 | n.path = path[offset:i] 240 | offset = i 241 | } 242 | 243 | child := &node{ 244 | nType: param, 245 | maxParams: numParams, 246 | } 247 | n.children = []*node{child} 248 | n.wildChild = true 249 | n = child 250 | n.priority++ 251 | numParams-- 252 | 253 | // if the path doesn't end with the wildcard, then there 254 | // will be another non-wildcard subpath starting with '/' 255 | if end < max { 256 | n.path = path[offset:end] 257 | offset = end 258 | 259 | child := &node{ 260 | maxParams: numParams, 261 | priority: 1, 262 | } 263 | n.children = []*node{child} 264 | n = child 265 | } 266 | 267 | } else { // catchAll 268 | if end != max || numParams > 1 { 269 | panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") 270 | } 271 | 272 | if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { 273 | panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'") 274 | } 275 | 276 | // currently fixed width 1 for '/' 277 | i-- 278 | if path[i] != '/' { 279 | panic("no / before catch-all in path '" + fullPath + "'") 280 | } 281 | 282 | n.path = path[offset:i] 283 | 284 | // first node: catchAll node with empty path 285 | child := &node{ 286 | wildChild: true, 287 | nType: catchAll, 288 | maxParams: 1, 289 | } 290 | n.children = []*node{child} 291 | n.indices = string(path[i]) 292 | n = child 293 | n.priority++ 294 | 295 | // second node: node holding the variable 296 | child = &node{ 297 | path: path[i:], 298 | nType: catchAll, 299 | maxParams: 1, 300 | handle: handle, 301 | priority: 1, 302 | } 303 | n.children = []*node{child} 304 | 305 | return 306 | } 307 | } 308 | 309 | // insert remaining path part and handle to the leaf 310 | n.path = path[offset:] 311 | n.handle = handle 312 | } 313 | 314 | // Returns the handle registered with the given path (key). The values of 315 | // wildcards are saved to a map. 316 | // If no handle can be found, a TSR (trailing slash redirect) recommendation is 317 | // made if a handle exists with an extra (without the) trailing slash for the 318 | // given path. 319 | func (n *node) getValue(path string) (handle Handle, p Params, tsr bool) { 320 | walk: // Outer loop for walking the tree 321 | for { 322 | if len(path) > len(n.path) { 323 | if path[:len(n.path)] == n.path { 324 | path = path[len(n.path):] 325 | // If this node does not have a wildcard (param or catchAll) 326 | // child, we can just look up the next child node and continue 327 | // to walk down the tree 328 | if !n.wildChild { 329 | c := path[0] 330 | for i := 0; i < len(n.indices); i++ { 331 | if c == n.indices[i] { 332 | n = n.children[i] 333 | continue walk 334 | } 335 | } 336 | 337 | // Nothing found. 338 | // We can recommend to redirect to the same URL without a 339 | // trailing slash if a leaf exists for that path. 340 | tsr = (path == "/" && n.handle != nil) 341 | return 342 | 343 | } 344 | 345 | // handle wildcard child 346 | n = n.children[0] 347 | switch n.nType { 348 | case param: 349 | // find param end (either '/' or path end) 350 | end := 0 351 | for end < len(path) && path[end] != '/' { 352 | end++ 353 | } 354 | 355 | // save param value 356 | if p == nil { 357 | // lazy allocation 358 | p = make(Params, 0, n.maxParams) 359 | } 360 | i := len(p) 361 | p = p[:i+1] // expand slice within preallocated capacity 362 | p[i].Key = n.path[1:] 363 | p[i].Value = path[:end] 364 | 365 | // we need to go deeper! 366 | if end < len(path) { 367 | if len(n.children) > 0 { 368 | path = path[end:] 369 | n = n.children[0] 370 | continue walk 371 | } 372 | 373 | // ... but we can't 374 | tsr = (len(path) == end+1) 375 | return 376 | } 377 | 378 | if handle = n.handle; handle != nil { 379 | return 380 | } else if len(n.children) == 1 { 381 | // No handle found. Check if a handle for this path + a 382 | // trailing slash exists for TSR recommendation 383 | n = n.children[0] 384 | tsr = (n.path == "/" && n.handle != nil) 385 | } 386 | 387 | return 388 | 389 | case catchAll: 390 | // save param value 391 | if p == nil { 392 | // lazy allocation 393 | p = make(Params, 0, n.maxParams) 394 | } 395 | i := len(p) 396 | p = p[:i+1] // expand slice within preallocated capacity 397 | p[i].Key = n.path[2:] 398 | p[i].Value = path 399 | 400 | handle = n.handle 401 | return 402 | 403 | default: 404 | panic("invalid node type") 405 | } 406 | } 407 | } else if path == n.path { 408 | // We should have reached the node containing the handle. 409 | // Check if this node has a handle registered. 410 | if handle = n.handle; handle != nil { 411 | return 412 | } 413 | 414 | // No handle found. Check if a handle for this path + a 415 | // trailing slash exists for trailing slash recommendation 416 | for i := 0; i < len(n.indices); i++ { 417 | if n.indices[i] == '/' { 418 | n = n.children[i] 419 | tsr = (len(n.path) == 1 && n.handle != nil) || 420 | (n.nType == catchAll && n.children[0].handle != nil) 421 | return 422 | } 423 | } 424 | 425 | return 426 | } 427 | 428 | // Nothing found. We can recommend to redirect to the same URL with an 429 | // extra trailing slash if a leaf exists for that path 430 | tsr = (path == "/") || 431 | (len(n.path) == len(path)+1 && n.path[len(path)] == '/' && 432 | path == n.path[:len(n.path)-1] && n.handle != nil) 433 | return 434 | } 435 | } 436 | 437 | // Makes a case-insensitive lookup of the given path and tries to find a handler. 438 | // It can optionally also fix trailing slashes. 439 | // It returns the case-corrected path and a bool indicating whether the lookup 440 | // was successful. 441 | func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) { 442 | ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory 443 | 444 | // Outer loop for walking the tree 445 | for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) { 446 | path = path[len(n.path):] 447 | ciPath = append(ciPath, n.path...) 448 | 449 | if len(path) > 0 { 450 | // If this node does not have a wildcard (param or catchAll) child, 451 | // we can just look up the next child node and continue to walk down 452 | // the tree 453 | if !n.wildChild { 454 | r := unicode.ToLower(rune(path[0])) 455 | for i, index := range n.indices { 456 | // must use recursive approach since both index and 457 | // ToLower(index) could exist. We must check both. 458 | if r == unicode.ToLower(index) { 459 | out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash) 460 | if found { 461 | return append(ciPath, out...), true 462 | } 463 | } 464 | } 465 | 466 | // Nothing found. We can recommend to redirect to the same URL 467 | // without a trailing slash if a leaf exists for that path 468 | found = (fixTrailingSlash && path == "/" && n.handle != nil) 469 | return 470 | } 471 | 472 | n = n.children[0] 473 | switch n.nType { 474 | case param: 475 | // find param end (either '/' or path end) 476 | k := 0 477 | for k < len(path) && path[k] != '/' { 478 | k++ 479 | } 480 | 481 | // add param value to case insensitive path 482 | ciPath = append(ciPath, path[:k]...) 483 | 484 | // we need to go deeper! 485 | if k < len(path) { 486 | if len(n.children) > 0 { 487 | path = path[k:] 488 | n = n.children[0] 489 | continue 490 | } 491 | 492 | // ... but we can't 493 | if fixTrailingSlash && len(path) == k+1 { 494 | return ciPath, true 495 | } 496 | return 497 | } 498 | 499 | if n.handle != nil { 500 | return ciPath, true 501 | } else if fixTrailingSlash && len(n.children) == 1 { 502 | // No handle found. Check if a handle for this path + a 503 | // trailing slash exists 504 | n = n.children[0] 505 | if n.path == "/" && n.handle != nil { 506 | return append(ciPath, '/'), true 507 | } 508 | } 509 | return 510 | 511 | case catchAll: 512 | return append(ciPath, path...), true 513 | 514 | default: 515 | panic("invalid node type") 516 | } 517 | } else { 518 | // We should have reached the node containing the handle. 519 | // Check if this node has a handle registered. 520 | if n.handle != nil { 521 | return ciPath, true 522 | } 523 | 524 | // No handle found. 525 | // Try to fix the path by adding a trailing slash 526 | if fixTrailingSlash { 527 | for i := 0; i < len(n.indices); i++ { 528 | if n.indices[i] == '/' { 529 | n = n.children[i] 530 | if (len(n.path) == 1 && n.handle != nil) || 531 | (n.nType == catchAll && n.children[0].handle != nil) { 532 | return append(ciPath, '/'), true 533 | } 534 | return 535 | } 536 | } 537 | } 538 | return 539 | } 540 | } 541 | 542 | // Nothing found. 543 | // Try to fix the path by adding / removing a trailing slash 544 | if fixTrailingSlash { 545 | if path == "/" { 546 | return ciPath, true 547 | } 548 | if len(path)+1 == len(n.path) && n.path[len(path)] == '/' && 549 | strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) && 550 | n.handle != nil { 551 | return append(ciPath, n.path...), true 552 | } 553 | } 554 | return 555 | } 556 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/meatballhat/negroni-logrus/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/meatballhat/negroni-logrus/.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: go 3 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/meatballhat/negroni-logrus/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dan Buch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/meatballhat/negroni-logrus/README.md: -------------------------------------------------------------------------------- 1 | negroni-logrus 2 | ============== 3 | 4 | logrus middleware for negroni 5 | 6 | ## Usage 7 | 8 | Take a peek at the [example](./example/example.go). 9 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/meatballhat/negroni-logrus/example/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/codegangsta/negroni" 8 | "github.com/meatballhat/negroni-logrus" 9 | ) 10 | 11 | func main() { 12 | r := http.NewServeMux() 13 | r.HandleFunc(`/`, func(w http.ResponseWriter, r *http.Request) { 14 | w.WriteHeader(http.StatusOK) 15 | fmt.Fprintf(w, "success!\n") 16 | }) 17 | 18 | n := negroni.New() 19 | n.Use(negronilogrus.NewMiddleware()) 20 | n.UseHandler(r) 21 | 22 | n.Run(":9999") 23 | } 24 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/meatballhat/negroni-logrus/middleware.go: -------------------------------------------------------------------------------- 1 | package negronilogrus 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/Sirupsen/logrus" 9 | "github.com/codegangsta/negroni" 10 | ) 11 | 12 | // Middleware is a middleware handler that logs the request as it goes in and the response as it goes out. 13 | type Middleware struct { 14 | // Logger is the log.Logger instance used to log messages with the Logger middleware 15 | Logger *logrus.Logger 16 | // Name is the name of the application as recorded in latency metrics 17 | Name string 18 | } 19 | 20 | // NewMiddleware returns a new *Middleware, yay! 21 | func NewMiddleware() *Middleware { 22 | return NewCustomMiddleware(logrus.InfoLevel, &logrus.TextFormatter{}, "web") 23 | } 24 | 25 | // NewCustomMiddleware builds a *Middleware with the given level and formatter 26 | func NewCustomMiddleware(level logrus.Level, formatter logrus.Formatter, name string) *Middleware { 27 | log := logrus.New() 28 | log.Level = level 29 | log.Formatter = formatter 30 | 31 | return &Middleware{Logger: log, Name: name} 32 | } 33 | 34 | func (l *Middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 35 | start := time.Now() 36 | l.Logger.WithFields(logrus.Fields{ 37 | "method": r.Method, 38 | "request": r.RequestURI, 39 | "remote": r.RemoteAddr, 40 | }).Info("started handling request") 41 | 42 | next(rw, r) 43 | 44 | latency := time.Since(start) 45 | res := rw.(negroni.ResponseWriter) 46 | l.Logger.WithFields(logrus.Fields{ 47 | "status": res.Status(), 48 | "method": r.Method, 49 | "request": r.RequestURI, 50 | "remote": r.RemoteAddr, 51 | "text_status": http.StatusText(res.Status()), 52 | "took": latency, 53 | fmt.Sprintf("measure#%s.latency", l.Name): latency.Nanoseconds(), 54 | }).Info("completed handling request") 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Lander Brandt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | qrest is a quick RESTful JSON server 2 | 3 | [![Build Status](https://travis-ci.org/landaire/qrest.svg?branch=travis-ci)](https://travis-ci.org/landaire/qrest) 4 | 5 | # Usage 6 | 7 | Create a JSON file containing the data you'd like to be part of your server. An example file might look like: 8 | 9 | { 10 | "posts": [ { "id": 1, "title": "Foo" } ] 11 | } 12 | 13 | Start qrest with this file as an argument: 14 | 15 | qrest db.json 16 | 17 | Or in a docker container: 18 | 19 | $ docker build -t qrest . 20 | $ docker run --rm -p 3000:3000 qrest "db.json" # assuming db.json is in this source directory 21 | 22 | This will create the following routes for you to use: 23 | 24 | POST /posts (creates a new post record) 25 | GET /posts (returns all post records) 26 | GET /posts/:id (returns a specific record) 27 | PUT /posts/:id (creates or updates a record with the specified ID) 28 | PATCH /posts/:id (updates a record with the specified ID) 29 | DELETE /posts/:id (deletes the specified record) 30 | 31 | # License 32 | 33 | This project is released under the MIT license. 34 | 35 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "os/signal" 10 | "strconv" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | var ( 16 | serverData = make(BackingData) 17 | dataMutex sync.RWMutex 18 | dirty = false 19 | maxIds = make(map[string]int64) 20 | ErrorNotFound = errors.New("Item not present in data set") 21 | JsonFilePath string 22 | ) 23 | 24 | func init() { 25 | if len(os.Args) > 1 { 26 | JsonFilePath = os.Args[1] 27 | } 28 | } 29 | 30 | type BackingData map[string]interface{} 31 | 32 | // recordIndex returns the index of a record within the `BackingData[itemType]` array 33 | // 34 | func (b BackingData) recordIndex(itemType string, id int64) (int, error) { 35 | rows, err := b.ItemType(itemType) 36 | 37 | if err != nil { 38 | return -1, err 39 | } 40 | 41 | for i, row := range rows { 42 | rowMap, _ := row.(map[string]interface{}) 43 | 44 | currentId, ok := rowMap["id"].(int64) 45 | 46 | if !ok { 47 | logger.Errorf("ID either not present for record at index %i or it's unknown type\n", i) 48 | continue 49 | } 50 | 51 | if err != nil { 52 | logger.Errorln(err) 53 | continue 54 | } 55 | 56 | // Found the item 57 | if currentId == id { 58 | return i, nil 59 | } 60 | } 61 | 62 | return -1, ErrorNotFound 63 | } 64 | 65 | // RecordWithId will return a record with the provided ID. If no such record exists, err 66 | // will be set to ErrorNotFound 67 | // 68 | func (b BackingData) RecordWithId(itemType string, id int64) (map[string]interface{}, error) { 69 | rows, err := b.ItemType(itemType) 70 | 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | index, err := b.recordIndex(itemType, id) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | rowMap := rows[index].(map[string]interface{}) 81 | 82 | return rowMap, nil 83 | } 84 | 85 | func (b BackingData) DeleteRecord(itemType string, id int64) error { 86 | records, err := b.ItemType(itemType) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | index, err := b.recordIndex(itemType, id) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | b[itemType] = append(records[:index], records[index+1:]...) 97 | 98 | return nil 99 | } 100 | 101 | func (b BackingData) ItemType(itemType string) ([]interface{}, error) { 102 | value, ok := b[itemType] 103 | 104 | if !ok { 105 | return nil, ErrorNotFound 106 | } 107 | 108 | return value.([]interface{}), nil 109 | } 110 | 111 | func (b BackingData) ItemTypes() []string { 112 | itemTypes := make([]string, 0, len(serverData)) 113 | 114 | for key, _ := range b { 115 | itemTypes = append(itemTypes, key) 116 | } 117 | 118 | return itemTypes 119 | } 120 | 121 | func (b BackingData) AddRecord(itemType string, record map[string]interface{}) { 122 | items, _ := b.ItemType(itemType) 123 | b[itemType] = append(items, record) 124 | } 125 | 126 | func (b BackingData) Copy() BackingData { 127 | data := make(BackingData) 128 | 129 | for key, value := range b { 130 | data[key] = copyInterfaceType(value) 131 | } 132 | 133 | return data 134 | } 135 | 136 | func copyInterfaceType(value interface{}) interface{} { 137 | switch value.(type) { 138 | case map[string]interface{}: 139 | mapValue := value.(map[string]interface{}) 140 | 141 | mapValueCopy := make(map[string]interface{}) 142 | 143 | for key, value := range mapValue { 144 | mapValueCopy[key] = copyInterfaceType(value) 145 | } 146 | 147 | return mapValueCopy 148 | case []interface{}: 149 | valueArray := value.([]interface{}) 150 | valueArrayCopy := make([]interface{}, len(valueArray)) 151 | copy(valueArrayCopy, valueArray) 152 | for i, value := range valueArrayCopy { 153 | valueArray[i] = copyInterfaceType(value) 154 | } 155 | 156 | return valueArrayCopy 157 | case json.Number: 158 | jsonNumber := value.(json.Number) 159 | number, _ := jsonNumber.Int64() 160 | 161 | return json.Number(strconv.FormatInt(number, 10)) 162 | default: 163 | return value 164 | } 165 | } 166 | 167 | // Parses the JSON file provided in the command arguments 168 | // 169 | func parseJsonFile(fname string) { 170 | file, err := os.Open(fname) 171 | if err != nil { 172 | logger.Fatalln(err) 173 | } 174 | 175 | defer file.Close() 176 | 177 | err = decodeJson(file, &serverData) 178 | if err != nil { 179 | logger.Fatalln(err) 180 | } 181 | 182 | // Get the highest IDs 183 | for _, itemType := range serverData.ItemTypes() { 184 | rows, _ := serverData.ItemType(itemType) 185 | for _, record := range rows { 186 | record := record.(map[string]interface{}) 187 | id, ok := record["id"].(int64) 188 | 189 | if !ok { 190 | continue 191 | } 192 | 193 | if err != nil { 194 | continue 195 | } 196 | 197 | if max, ok := maxIds[itemType]; id > max || !ok { 198 | maxIds[itemType] = id 199 | } 200 | } 201 | } 202 | } 203 | 204 | // Flushes the in-memory data to the JSON file 205 | func flushJson(filename string) { 206 | c := make(chan os.Signal, 1) 207 | signal.Notify(c, os.Interrupt) 208 | 209 | write := func() { 210 | if !dirty { 211 | return 212 | } 213 | 214 | dataMutex.RLock() 215 | dirty = false 216 | 217 | jsonData, err := json.Marshal(serverData) 218 | dataMutex.RUnlock() 219 | if err != nil { 220 | logger.Error(err) 221 | return 222 | } 223 | 224 | ioutil.WriteFile(filename, jsonData, 0755) 225 | } 226 | 227 | // Flush loop 228 | for { 229 | select { 230 | case <-c: 231 | write() 232 | return 233 | // Flush every 30 seconds 234 | case <-time.After(30 * time.Second): 235 | write() 236 | } 237 | } 238 | } 239 | 240 | // Makes decoding JSON less repetitive (no need to create the decoder, call UseNumber(), etc.) 241 | // 242 | func decodeJson(r io.Reader, data interface{}) error { 243 | decoder := json.NewDecoder(r) 244 | decoder.UseNumber() 245 | 246 | err := decoder.Decode(data) 247 | 248 | if err != nil { 249 | return err 250 | } 251 | 252 | // Convert all json.Number to int64 253 | if dataMap, ok := data.(*map[string]interface{}); ok { 254 | convertMapNumbers(*dataMap) 255 | } 256 | 257 | if backingData, ok := data.(*BackingData); ok { 258 | backingDataAsMap := (*map[string]interface{})(backingData) 259 | convertMapNumbers(*backingDataAsMap) 260 | } 261 | 262 | return nil 263 | } 264 | 265 | func convertMapNumbers(data map[string]interface{}) { 266 | for key, value := range data { 267 | data[key] = convertMapType(value) 268 | } 269 | } 270 | 271 | func convertMapType(value interface{}) interface{} { 272 | switch value.(type) { 273 | case map[string]interface{}: 274 | dataMap := value.(map[string]interface{}) 275 | convertMapNumbers(dataMap) 276 | 277 | return dataMap 278 | case []interface{}: 279 | valueArray := value.([]interface{}) 280 | for i, value := range valueArray { 281 | newValue := convertMapType(value) 282 | valueArray[i] = newValue 283 | } 284 | 285 | return valueArray 286 | case json.Number: 287 | number := value.(json.Number) 288 | numberAsInt, _ := number.Int64() 289 | 290 | return numberAsInt 291 | default: 292 | return value 293 | } 294 | 295 | } 296 | -------------------------------------------------------------------------------- /db.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": [ 3 | { 4 | "id": 1, 5 | "title": "Testing", 6 | "author": "Foo" 7 | } 8 | ], 9 | "comments": [ 10 | { 11 | "id": 1, 12 | "body": "Testing", 13 | "postId": 1 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /db_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | const jsonTestData = ` 11 | { 12 | "posts": [ 13 | { 14 | "id": 1, 15 | "title": "Testing", 16 | "author": "Foo" 17 | }, 18 | { 19 | "id": 2, 20 | "title": "Testing Post ID 2", 21 | "author": "Bar" 22 | } 23 | ], 24 | "comments": [ 25 | { 26 | "id": 1, 27 | "body": "Testing", 28 | "postId": 1 29 | }, 30 | { 31 | "id": 2, 32 | "body": "Testing Comment ID 2", 33 | "postId": 2 34 | } 35 | ] 36 | } 37 | ` 38 | 39 | func TestParseJsonFile(t *testing.T) { 40 | // `TestMain` will have already called parseJsonFile for the initial setup, 41 | // so this is just a quick check to make sure that actually succeeded 42 | if maxIds["posts"] != 2 || maxIds["comments"] != 2 { 43 | t.Fatal("Failing TestParseJsonFile fails all tests") 44 | } 45 | } 46 | 47 | func TestItemTypes(t *testing.T) { 48 | itemTypes := serverData.ItemTypes() 49 | itemTypesFound := make(map[string]bool) 50 | expectedTypes := []string{"posts", "comments"} 51 | 52 | if len(itemTypes) != len(expectedTypes) { 53 | t.Errorf("Expected %d item types, got %d\n", len(expectedTypes), len(itemTypes)) 54 | } 55 | 56 | for _, itemType := range expectedTypes { 57 | itemTypesFound[itemType] = false 58 | } 59 | 60 | for _, itemType := range itemTypes { 61 | itemTypesFound[itemType] = true 62 | } 63 | 64 | for itemType, found := range itemTypesFound { 65 | if !found { 66 | t.Errorf("Item type %s not found\n", itemType) 67 | } 68 | } 69 | } 70 | 71 | func TestFetchingRecord(t *testing.T) { 72 | type Record struct { 73 | Type string 74 | Id int64 75 | Expected map[string]interface{} 76 | } 77 | 78 | recordsToFetch := []Record{ 79 | Record{ 80 | Type: "posts", 81 | Id: 1, 82 | Expected: map[string]interface{}{ 83 | "id": int64(1), 84 | "title": "Testing", 85 | "author": "Foo", 86 | }, 87 | }, 88 | Record{ 89 | Type: "comments", 90 | Id: 2, 91 | Expected: map[string]interface{}{ 92 | "id": int64(2), 93 | "body": "Testing Comment ID 2", 94 | "postId": int64(2), 95 | }, 96 | }, 97 | } 98 | 99 | for _, expectedRecord := range recordsToFetch { 100 | actualRecord, err := serverData.RecordWithId(expectedRecord.Type, expectedRecord.Id) 101 | 102 | if err != nil { 103 | t.Error("No error was expected:", err) 104 | continue 105 | } 106 | 107 | if len(actualRecord) != len(expectedRecord.Expected) { 108 | t.Errorf("Invalid number of columns in returned record. Expected %d, got %d", len(expectedRecord.Expected), len(actualRecord)) 109 | } 110 | 111 | for key, expectedValue := range expectedRecord.Expected { 112 | actualValue, ok := actualRecord[key] 113 | 114 | if !ok { 115 | t.Error("invalid key", key) 116 | continue 117 | } 118 | 119 | if expectedValue != actualValue { 120 | expectedType := reflect.TypeOf(expectedValue) 121 | actualType := reflect.TypeOf(actualValue) 122 | 123 | // Special handling for json.Number 124 | if jsonNumber, ok := actualValue.(json.Number); ok { 125 | int64Number, err := jsonNumber.Int64() 126 | if err == nil && int64Number == expectedValue { 127 | continue 128 | } 129 | } 130 | t.Errorf("expected %#v of type %s, got %#v of type %s", expectedValue, expectedType, actualValue, actualType) 131 | continue 132 | } 133 | } 134 | } 135 | 136 | // Test getting a bad record 137 | errorMessage := "Should have gotten an error, none given" 138 | if _, err := serverData.RecordWithId("posts", -1); err == nil { 139 | t.Error(errorMessage) 140 | } 141 | 142 | if _, err := serverData.RecordWithId("non-existant", -1); err == nil { 143 | t.Error(errorMessage) 144 | } 145 | } 146 | 147 | func TestJsonDecode(t *testing.T) { 148 | reader := strings.NewReader(`{"number": 2}`) 149 | 150 | data := make(map[string]interface{}) 151 | 152 | err := decodeJson(reader, &data) 153 | 154 | if err != nil { 155 | t.Fail() 156 | } 157 | 158 | // kind of quicker than reflect.TypeOf? 159 | switch data["number"].(type) { 160 | case int64: 161 | break 162 | default: 163 | t.Fail() 164 | } 165 | } 166 | 167 | // TODO: Need to add tests for db. Most of the functionality will also be covered by the handlers, but there 168 | // should also be isolated tests 169 | // 170 | -------------------------------------------------------------------------------- /handlers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "fmt" 8 | "strconv" 9 | 10 | "github.com/julienschmidt/httprouter" 11 | ) 12 | 13 | // addDynamicRoutes will dynamically add RESTful routes to the router. Routes are added based off of the keys that 14 | // are present in the parsed JSON file. For instance, if a JSON file is set up like: 15 | // 16 | // { 17 | // "posts": [ { "id": 1, "title": "Foo" } ] 18 | // } 19 | // 20 | // The following routes will be created: 21 | // 22 | // POST /posts (creates a new post record) 23 | // GET /posts (returns all post records) 24 | // GET /posts/:id (returns a specific record) 25 | // PUT /posts/:id (creates or updates a record with the specified ID) 26 | // PATCH /posts/:id (updates a record with the specified ID) 27 | // DELETE /posts/:id (deletes the specified record) 28 | // 29 | // 30 | func addDynamicRoutes(router *httprouter.Router) { 31 | // set up our routes 32 | for _, itemType := range serverData.ItemTypes() { 33 | // Shadow these variables. If this isn't done, then the closures below will see 34 | // `value` and `key` as whatever they were in the last(?) iteration of the above for loop 35 | itemType := itemType 36 | 37 | // POST /type 38 | router.POST(fmt.Sprintf("/%s", itemType), func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 39 | data, err := readRequestData(r) 40 | if err != nil { 41 | w.WriteHeader(http.StatusBadRequest) 42 | return 43 | } 44 | 45 | dataMutex.Lock() 46 | 47 | // The idea with grabbing the record with ID 1 is to see if any records even exist. If none exist, the loop 48 | // should not execute at all, giving the first record id 1 49 | id := int64(1) 50 | _, err = serverData.RecordWithId(itemType, id) 51 | for id = maxIds[itemType]; err != ErrorNotFound; _, err = serverData.RecordWithId(itemType, id) { 52 | id++ 53 | } 54 | 55 | data["id"] = id 56 | 57 | dirty = true 58 | serverData.AddRecord(itemType, data) 59 | 60 | maxIds[itemType] = id 61 | 62 | dataMutex.Unlock() 63 | 64 | w.WriteHeader(http.StatusCreated) 65 | }) 66 | 67 | // GET /type 68 | router.GET(fmt.Sprintf("/%s", itemType), func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 69 | items, _ := serverData.ItemType(itemType) 70 | genericJsonResponse(w, r, items) 71 | }) 72 | 73 | // (GET,PATCH,PUT,DELETE) /type/id 74 | for _, method := range []string{"GET", "PATCH", "PUT", "DELETE"} { 75 | method := method 76 | router.Handle(method, fmt.Sprintf("/%s/:id", itemType), func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 77 | idParam, _ := strconv.ParseInt(ps.ByName("id"), 10, 64) 78 | 79 | record, err := serverData.RecordWithId(itemType, idParam) 80 | 81 | if err != nil { 82 | if err == ErrorNotFound { 83 | // If it's not found, then this request acts as a POST 84 | if method == "PUT" { 85 | newData, err := readRequestData(r) 86 | if err != nil { 87 | w.WriteHeader(http.StatusBadRequest) 88 | return 89 | } 90 | 91 | if _, hasId := newData["id"]; !hasId { 92 | newData["id"] = idParam 93 | } 94 | 95 | dataMutex.Lock() 96 | dirty = true 97 | 98 | serverData.AddRecord(itemType, newData) 99 | 100 | dataMutex.Unlock() 101 | 102 | w.WriteHeader(http.StatusCreated) 103 | } else { 104 | w.WriteHeader(http.StatusNotFound) 105 | } 106 | } else { 107 | w.WriteHeader(http.StatusInternalServerError) 108 | } 109 | 110 | return 111 | } 112 | 113 | // The method type determines how we respond 114 | switch method { 115 | case "GET": 116 | genericJsonResponse(w, r, record) 117 | return 118 | case "PATCH": 119 | updatedData, err := readRequestData(r) 120 | if err != nil { 121 | w.WriteHeader(http.StatusBadRequest) 122 | return 123 | } 124 | 125 | dataMutex.Lock() 126 | for key, value := range updatedData { 127 | record[key] = value 128 | } 129 | 130 | dirty = true 131 | 132 | dataMutex.Unlock() 133 | 134 | return 135 | case "PUT": 136 | updatedData, err := readRequestData(r) 137 | if err != nil { 138 | w.WriteHeader(http.StatusBadRequest) 139 | return 140 | } 141 | 142 | dataMutex.Lock() 143 | 144 | for key, _ := range record { 145 | record[key] = nil 146 | } 147 | 148 | for key, value := range updatedData { 149 | record[key] = value 150 | } 151 | 152 | dirty = true 153 | 154 | dataMutex.Unlock() 155 | 156 | w.WriteHeader(http.StatusOK) 157 | return 158 | case "DELETE": 159 | dataMutex.Lock() 160 | 161 | dirty = true 162 | serverData.DeleteRecord(itemType, idParam) 163 | 164 | dataMutex.Unlock() 165 | 166 | w.WriteHeader(http.StatusOK) 167 | return 168 | } 169 | }) 170 | } 171 | } 172 | } 173 | 174 | // addStaticRoutes adds all routes which are present regardless of the JSON file's data. These include 175 | // 176 | // GET /db (returns the entire DB as a JSON structure) 177 | // 178 | // 179 | func addStaticRoutes(router *httprouter.Router) { 180 | router.GET("/db", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 181 | genericJsonResponse(w, r, serverData) 182 | }) 183 | } 184 | 185 | // genericJsonResponse writes a generic JSON response and handles any errors which may occur 186 | // when marshalling the data 187 | // 188 | func genericJsonResponse(w http.ResponseWriter, r *http.Request, data interface{}) { 189 | jsonData, err := json.Marshal(data) 190 | if err != nil { 191 | w.WriteHeader(http.StatusInternalServerError) 192 | return 193 | } 194 | 195 | w.Header().Set("Content-Type", "application/json") 196 | w.Write(jsonData) 197 | } 198 | 199 | // readRequestData parses the JSON body of a request 200 | // 201 | func readRequestData(r *http.Request) (returnData map[string]interface{}, err error) { 202 | returnData = make(map[string]interface{}) 203 | 204 | err = decodeJson(r.Body, &returnData) 205 | 206 | return 207 | } 208 | -------------------------------------------------------------------------------- /handlers_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "reflect" 7 | "strings" 8 | "testing" 9 | "io" 10 | "encoding/json" 11 | "bytes" 12 | "math/rand" 13 | ) 14 | 15 | func TestGetAllRecordsOfType(t *testing.T) { 16 | types := map[string]string{ 17 | "posts": `[ 18 | { 19 | "id": 1, 20 | "title": "Testing", 21 | "author": "Foo" 22 | }, 23 | { 24 | "id": 2, 25 | "title": "Testing Post ID 2", 26 | "author": "Bar" 27 | } 28 | ]`, 29 | "comments": `[ 30 | { 31 | "id": 1, 32 | "body": "Testing", 33 | "postId": 1 34 | }, 35 | { 36 | "id": 2, 37 | "body": "Testing Comment ID 2", 38 | "postId": 2 39 | } 40 | ]`, 41 | } 42 | 43 | for recordType, expectedJson := range types { 44 | err := testGetRequest("/"+recordType, expectedJson, http.StatusOK, true, true) 45 | if err != nil { 46 | t.Error(err) 47 | } 48 | } 49 | 50 | err := testGetRequest("/invalid", "", http.StatusNotFound, true, false) 51 | if err != nil { 52 | t.Error(err) 53 | } 54 | } 55 | 56 | func TestGetRecord(t *testing.T) { 57 | paths := map[string]string{ 58 | "/posts/2": `{ 59 | "id": 2, 60 | "title": "Testing Post ID 2", 61 | "author": "Bar" 62 | }`, 63 | "/comments/1": `{ 64 | "id": 1, 65 | "body": "Testing", 66 | "postId": 1 67 | }`, 68 | } 69 | 70 | for path, expectedJson := range paths { 71 | err := testGetRequest(path, expectedJson, http.StatusOK, true, true) 72 | if err != nil { 73 | t.Error(err) 74 | } 75 | } 76 | 77 | 78 | invalidPaths := []string{ 79 | "/posts/-1", 80 | "/posts/9000", 81 | "/comments/-1", 82 | "/comments/9000", 83 | } 84 | 85 | for _, path := range invalidPaths { 86 | err := testGetRequest(path, "", http.StatusNotFound, true, false) 87 | if err != nil { 88 | t.Error(err) 89 | } 90 | } 91 | } 92 | 93 | func TestPostRecord(t *testing.T) { 94 | databaseBeforeModification := serverData.Copy() 95 | defer func() { 96 | serverData = databaseBeforeModification.Copy() 97 | }() 98 | 99 | arbitraryStringWithRandomNumber := func(text string) string { 100 | return fmt.Sprintf("%s %d", text, rand.Int()) 101 | } 102 | 103 | titleString := func() string { 104 | return arbitraryStringWithRandomNumber("Title") 105 | } 106 | 107 | authorString := func() string { 108 | return arbitraryStringWithRandomNumber("Author") 109 | } 110 | 111 | bodyString := func() string { 112 | return arbitraryStringWithRandomNumber("Body") 113 | } 114 | 115 | // welcome to map[string]interface{} hell 116 | types := map[string][]map[string]interface{} { 117 | "posts": []map[string]interface{} { 118 | map[string]interface{} { 119 | "id": 2, // ID should be ignored 120 | "title": titleString(), 121 | "author": authorString(), 122 | }, 123 | map[string]interface{} { 124 | "id": -1, // ID should be ignored 125 | "title": titleString(), 126 | "author": authorString(), 127 | }, 128 | map[string]interface{} { 129 | "title": titleString(), 130 | "author": authorString(), 131 | }, 132 | map[string]interface{} { 133 | "title": titleString(), 134 | "author": authorString(), 135 | }, 136 | }, 137 | "comments": []map[string]interface{} { 138 | map[string]interface{} { 139 | "id": 2, // ID should be ignored 140 | "body": bodyString(), 141 | "postId": rand.Int63(), 142 | }, 143 | map[string]interface{} { 144 | "id": -1, 145 | "body": bodyString(), 146 | "postId": rand.Int63(), 147 | }, 148 | map[string]interface{} { 149 | "body": bodyString(), 150 | "postId": rand.Int63(), 151 | }, 152 | map[string]interface{} { 153 | "body": bodyString(), 154 | "postId": rand.Int63(), 155 | }, 156 | }, 157 | } 158 | 159 | for recordType, tests := range types { 160 | for _, test := range tests { 161 | testAsJson, err := json.Marshal(test) 162 | if err != nil { 163 | t.Error(err) 164 | continue 165 | } 166 | 167 | err = makeRequest("POST", "/" + recordType, bytes.NewBuffer(testAsJson), []int{http.StatusCreated}) 168 | if err != nil { 169 | t.Error(err) 170 | return 171 | } 172 | 173 | test["id"] = maxIds[recordType] 174 | 175 | testAsJson, err = json.Marshal(test) 176 | if err != nil { 177 | t.Error(err) 178 | continue 179 | } 180 | 181 | err = testGetRequest(fmt.Sprintf("/%s/%d", recordType, maxIds[recordType]), string(testAsJson), http.StatusOK, false, true) 182 | if err != nil { 183 | t.Error(err) 184 | } 185 | } 186 | } 187 | } 188 | 189 | func TestPutRecord(t *testing.T) { 190 | databaseBeforeModification := serverData.Copy() 191 | defer func() { 192 | serverData = databaseBeforeModification 193 | }() 194 | 195 | 196 | arbitraryStringWithRandomNumber := func(text string) string { 197 | return fmt.Sprintf("%s %d", text, rand.Int()) 198 | } 199 | 200 | titleString := func() string { 201 | return arbitraryStringWithRandomNumber("Title") 202 | } 203 | 204 | authorString := func() string { 205 | return arbitraryStringWithRandomNumber("Author") 206 | } 207 | 208 | bodyString := func() string { 209 | return arbitraryStringWithRandomNumber("Body") 210 | } 211 | 212 | // welcome to map[string]interface{} hell 213 | types := map[string][]map[string]interface{} { 214 | "posts": []map[string]interface{} { 215 | map[string]interface{} { 216 | "id": 2, 217 | "title": titleString(), 218 | "author": authorString(), 219 | }, 220 | map[string]interface{} { 221 | "id": 1000, 222 | "title": titleString(), 223 | "author": authorString(), 224 | }, 225 | }, 226 | "comments": []map[string]interface{} { 227 | map[string]interface{} { 228 | "id": 2, // ID should be ignored 229 | "body": bodyString(), 230 | "postId": rand.Int63(), 231 | }, 232 | map[string]interface{} { 233 | "id": 1000, 234 | "body": bodyString(), 235 | "postId": rand.Int63(), 236 | }, 237 | }, 238 | } 239 | 240 | for recordType, tests := range types { 241 | for _, test := range tests { 242 | testAsJson, err := json.Marshal(test) 243 | if err != nil { 244 | t.Error(err) 245 | continue 246 | } 247 | 248 | acceptableStatuses := []int{http.StatusCreated, http.StatusOK} 249 | err = makeRequest("PUT", fmt.Sprintf("/%s/%d", recordType, test["id"]), bytes.NewBuffer(testAsJson), acceptableStatuses) 250 | if err != nil { 251 | t.Error(err) 252 | return 253 | } 254 | 255 | testAsJson, err = json.Marshal(test) 256 | if err != nil { 257 | t.Error(err) 258 | continue 259 | } 260 | 261 | err = testGetRequest(fmt.Sprintf("/%s/%d", recordType, int64(test["id"].(int))), string(testAsJson), http.StatusOK, false, true) 262 | if err != nil { 263 | t.Error(err) 264 | } 265 | } 266 | } 267 | } 268 | 269 | func TestDeleteRecord(t *testing.T) { 270 | databaseBeforeModification := serverData.Copy() 271 | defer func() { 272 | serverData = databaseBeforeModification 273 | }() 274 | 275 | err := makeRequest("DELETE", "/posts/1", strings.NewReader(""), []int{http.StatusOK}) 276 | 277 | if err != nil { 278 | t.Error(err) 279 | return 280 | } 281 | 282 | err = testGetRequest("/posts/1", "", http.StatusNotFound, false, false) 283 | if err != nil { 284 | t.Error(err) 285 | return 286 | } 287 | } 288 | 289 | func TestGetAll(t *testing.T) { 290 | err := testGetRequest("/db", jsonTestData, http.StatusOK, false, true) 291 | if err != nil { 292 | t.Error(err) 293 | return 294 | } 295 | } 296 | 297 | func testGetRequest(path string, expectedJson string, expectedStatus int, useArray bool, compareBody bool) error { 298 | resp, err := http.Get("http://" + TestServerAddr + path) 299 | 300 | if err != nil { 301 | return err 302 | } 303 | 304 | defer resp.Body.Close() 305 | 306 | if resp.StatusCode != expectedStatus { 307 | return fmt.Errorf("Unexpected status code. Expected %d, got %d\n", expectedStatus, resp.StatusCode) 308 | } 309 | 310 | // Seems a little weird to have this as a parameter, but it prevents duplication of the above code 311 | if !compareBody { 312 | return nil 313 | } 314 | 315 | match, err, expected, actual := jsonResponseMatchesActual(resp, expectedJson, useArray) 316 | 317 | if err != nil { 318 | return err 319 | } 320 | 321 | if !match { 322 | // TODO: make this print out the data mismatch? should probably just print the raw maps 323 | return fmt.Errorf("Data mismatch for path %s.\n Expected:\n%#v\n\ngot\n%#v", path, expected, actual) 324 | } 325 | 326 | 327 | return nil 328 | } 329 | 330 | // Makes a request at `path` with the given method and body. `acceptableStatuses` are any status that is acceptable 331 | // 332 | func makeRequest(method string, path string, body io.Reader, acceptableStatuses []int) error { 333 | // TODO: make the body type a parameter to support testing that body type is application/json? currently no 334 | // such check exists 335 | req, err := http.NewRequest(method, "http://" + TestServerAddr + path, body) 336 | 337 | if err != nil { 338 | return err 339 | } 340 | 341 | req.Header.Set("Content-Type", "application/json") 342 | resp, err := http.DefaultClient.Do(req) 343 | 344 | if err != nil { 345 | return err 346 | } 347 | 348 | defer resp.Body.Close() 349 | 350 | statusPresent := false 351 | 352 | for _, status := range acceptableStatuses { 353 | if resp.StatusCode == status { 354 | statusPresent = true 355 | break 356 | } 357 | } 358 | 359 | if !statusPresent { 360 | return fmt.Errorf("Unexpected status code. Expected any of %v, got %d\n", acceptableStatuses, resp.StatusCode) 361 | } 362 | 363 | return nil 364 | } 365 | 366 | func jsonResponseMatchesActual(resp *http.Response, expected string, useArray bool) (bool, error, interface{}, interface{}) { 367 | var ( 368 | expectedData interface{} 369 | actualData interface{} 370 | ) 371 | 372 | if useArray { 373 | expectedData = make(map[string]interface{}) 374 | } else { 375 | expectedData = []interface{}{} 376 | } 377 | 378 | if contentType := resp.Header.Get("Content-Type"); contentType != "application/json" { 379 | return false, fmt.Errorf("Unexpected Content-Type %s\n", contentType), nil, nil 380 | } 381 | 382 | if useArray { 383 | actualData = make(map[string]interface{}) 384 | } else { 385 | actualData = []interface{}{} 386 | } 387 | 388 | err := decodeJson(strings.NewReader(expected), &expectedData) 389 | 390 | if err != nil { 391 | return false, err, nil, nil 392 | } 393 | 394 | err = decodeJson(resp.Body, &actualData) 395 | 396 | if err != nil { 397 | return false, err, nil, nil 398 | } 399 | 400 | return reflect.DeepEqual(expectedData, actualData), nil, expectedData, actualData 401 | } 402 | -------------------------------------------------------------------------------- /qrest.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | // Command qrest is a quick RESTful JSON server 2 | // 3 | // How to use 4 | // 5 | // Create a JSON file containing the data you'd like to be part of your server. An example file might look like: 6 | // 7 | // { 8 | // "posts": [ { "id": 1, "title": "Foo" } ] 9 | // } 10 | // 11 | // Start qrest with this file as an argument: 12 | // 13 | // qrest db.json 14 | // 15 | // Or in a docker container: 16 | // 17 | // $ docker build -t qrest . 18 | // $ docker run --rm -p 3000:3000 qrest "db.json" # assuming db.json is in this source directory 19 | // 20 | // This will create the following routes for you to use: 21 | // 22 | // POST /posts (creates a new post record) 23 | // GET /posts (returns all post records) 24 | // GET /posts/:id (returns a specific record) 25 | // PUT /posts/:id (creates or updates a record with the specified ID) 26 | // PATCH /posts/:id (updates a record with the specified ID) 27 | // DELETE /posts/:id (deletes the specified record) 28 | // 29 | // 30 | package main 31 | 32 | import ( 33 | "os" 34 | 35 | "github.com/Sirupsen/logrus" 36 | "github.com/codegangsta/negroni" 37 | "github.com/julienschmidt/httprouter" 38 | nlogrus "github.com/meatballhat/negroni-logrus" 39 | ) 40 | 41 | var ( 42 | loggerMiddleware *nlogrus.Middleware 43 | logger *logrus.Logger 44 | ) 45 | 46 | func init() { 47 | loggerMiddleware = nlogrus.NewMiddleware() 48 | logger = loggerMiddleware.Logger 49 | } 50 | 51 | func main() { 52 | // TODO: Should probably use `flag` package 53 | if len(os.Args) != 2 { 54 | logger.Println(os.Args) 55 | logger.Fatalf("Invalid number of arguments. Usage: %s /path/to/db.json", os.Args[0]) 56 | } 57 | 58 | parseJsonFile(os.Args[2]) 59 | 60 | port := ":" + os.Getenv("PORT") 61 | if port == ":" { 62 | port = ":3000" 63 | } 64 | 65 | StartServer(port) 66 | } 67 | 68 | func StartServer(addr string) { 69 | 70 | router := httprouter.New() 71 | 72 | addStaticRoutes(router) 73 | addDynamicRoutes(router) 74 | 75 | // This goroutine will flush the JSON to the db.json file every 30 seconds, 76 | // OR before the application exits 77 | go flushJson(JsonFilePath) 78 | 79 | n := negroni.Classic() 80 | n.Use(loggerMiddleware) 81 | n.UseHandler(router) 82 | n.Run(addr) 83 | } 84 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | var ( 11 | TestServerAddr = "localhost:" 12 | ) 13 | 14 | // Setup function. We expect parseJsonFile to succeed before running all subsequent tests 15 | // 16 | func TestMain(m *testing.M) { 17 | // Setup for the JSON file 18 | tempFile, err := ioutil.TempFile("", "qrest") 19 | n, err := tempFile.Write([]byte(jsonTestData)) 20 | tempFile.Close() 21 | 22 | JsonFilePath = tempFile.Name() 23 | 24 | if n != len([]byte(jsonTestData)) { 25 | fmt.Fprintln(os.Stderr, "invalid number of bytes written to temp file") 26 | os.Exit(1) 27 | } 28 | 29 | if err != nil { 30 | fmt.Fprintln(os.Stderr, err) 31 | os.Exit(1) 32 | } 33 | 34 | if err == nil { 35 | parseJsonFile(tempFile.Name()) 36 | 37 | // Attempt to start the server on a (hopefully) unused port 38 | if port := os.Getenv("TEST_PORT"); port != "" { 39 | TestServerAddr += port 40 | } else { 41 | TestServerAddr += "3050" 42 | } 43 | 44 | logger.Out = ioutil.Discard 45 | go StartServer(TestServerAddr) 46 | } else { 47 | fmt.Fprintln(os.Stderr, "could not create temp file") 48 | } 49 | 50 | os.Exit(m.Run()) 51 | } 52 | --------------------------------------------------------------------------------