├── .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
[](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 | 
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
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
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
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 [](http://godoc.org/github.com/codegangsta/negroni) [](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 [](https://travis-ci.org/julienschmidt/httprouter) [](http://gocover.io/github.com/julienschmidt/httprouter) [](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 | [](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 |
--------------------------------------------------------------------------------