diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 92db914..00419f4 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -4,27 +4,27 @@ "Deps": [ { "ImportPath": "github.com/chihaya/bencode", - "Rev": "1df51811f3440202aa39df93596654319ea5ff57" + "Rev": "e60878f635e1a61315c413492e133dd39769b1d1" }, { "ImportPath": "github.com/golang/glog", - "Rev": "d1c4472bf2efd3826f2b5bdcc02d8416798d678c" + "Rev": "44145f04b68cf362d9c4df2182967c2275eaefed" }, { "ImportPath": "github.com/julienschmidt/httprouter", - "Rev": "46807412fe50aaceb73bb57061c2230fd26a1640" + "Rev": "00ce1c6a267162792c367acc43b1681a884e1872" }, { "ImportPath": "github.com/pushrax/faststats", - "Rev": "4c069408629f728eb3fe856f0966508492393a90" + "Rev": "0fc2c5e41a187240ffaa09320eea7df9f8071388" }, { "ImportPath": "github.com/pushrax/flatjson", - "Rev": "cce9ff039f0bec46d67ed9ab4ce846b8355c94bb" + "Rev": "86044f1c998d49053e13293029414ddb63f3a422" }, { "ImportPath": "github.com/stretchr/graceful", - "Rev": "0b67b304c1354aa3d1b2c4353a630ee0f589c34f" + "Rev": "8e780ba3fe3d3e7ab15fc52e3d60a996587181dc" }, { "ImportPath": "github.com/stretchr/pat/stop", @@ -32,8 +32,7 @@ }, { "ImportPath": "golang.org/x/net/netutil", - "Comment": "null-204", - "Rev": "8fd8d3a0313cb59e495106ac76df5da29381fa24" + "Rev": "c84eff7014eba178f68bd4c05b86780efe0fbf35" } ] } diff --git a/Godeps/_workspace/src/github.com/chihaya/bencode/LICENSE b/Godeps/_workspace/src/github.com/chihaya/bencode/LICENSE index 126b004..af872fc 100644 --- a/Godeps/_workspace/src/github.com/chihaya/bencode/LICENSE +++ b/Godeps/_workspace/src/github.com/chihaya/bencode/LICENSE @@ -1,6 +1,6 @@ bencode is released under a BSD 2-Clause license, reproduced below. -Copyright (c) 2014, The Chihaya Authors +Copyright (c) 2015, The Chihaya Authors All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Godeps/_workspace/src/github.com/chihaya/bencode/bencode.go b/Godeps/_workspace/src/github.com/chihaya/bencode/bencode.go index c020d10..cef1e07 100644 --- a/Godeps/_workspace/src/github.com/chihaya/bencode/bencode.go +++ b/Godeps/_workspace/src/github.com/chihaya/bencode/bencode.go @@ -1,4 +1,4 @@ -// Copyright 2014 The Chihaya Authors. All rights reserved. +// Copyright 2015 The Chihaya Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/chihaya/bencode/decoder.go b/Godeps/_workspace/src/github.com/chihaya/bencode/decoder.go index 13481e1..24d75ab 100644 --- a/Godeps/_workspace/src/github.com/chihaya/bencode/decoder.go +++ b/Godeps/_workspace/src/github.com/chihaya/bencode/decoder.go @@ -1,4 +1,4 @@ -// Copyright 2014 The Chihaya Authors. All rights reserved. +// Copyright 2015 The Chihaya Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/chihaya/bencode/decoder_test.go b/Godeps/_workspace/src/github.com/chihaya/bencode/decoder_test.go index 2ad47ae..cba5b9a 100644 --- a/Godeps/_workspace/src/github.com/chihaya/bencode/decoder_test.go +++ b/Godeps/_workspace/src/github.com/chihaya/bencode/decoder_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 The Chihaya Authors. All rights reserved. +// Copyright 2015 The Chihaya Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/chihaya/bencode/encoder.go b/Godeps/_workspace/src/github.com/chihaya/bencode/encoder.go index 324d503..8b33fd9 100644 --- a/Godeps/_workspace/src/github.com/chihaya/bencode/encoder.go +++ b/Godeps/_workspace/src/github.com/chihaya/bencode/encoder.go @@ -1,4 +1,4 @@ -// Copyright 2014 The Chihaya Authors. All rights reserved. +// Copyright 2015 The Chihaya Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/chihaya/bencode/encoder_test.go b/Godeps/_workspace/src/github.com/chihaya/bencode/encoder_test.go index f665600..6b3f432 100644 --- a/Godeps/_workspace/src/github.com/chihaya/bencode/encoder_test.go +++ b/Godeps/_workspace/src/github.com/chihaya/bencode/encoder_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 The Chihaya Authors. All rights reserved. +// Copyright 2015 The Chihaya Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/golang/glog/glog.go b/Godeps/_workspace/src/github.com/golang/glog/glog.go index d5e1ac2..3e63fff 100644 --- a/Godeps/_workspace/src/github.com/golang/glog/glog.go +++ b/Godeps/_workspace/src/github.com/golang/glog/glog.go @@ -77,6 +77,7 @@ import ( "flag" "fmt" "io" + stdLog "log" "os" "path/filepath" "runtime" @@ -93,6 +94,9 @@ import ( // the corresponding constants in C++. type severity int32 // sync/atomic int32 +// These constants identify the log levels in order of increasing severity. +// A message written to a high-severity log file is also written to each +// lower-severity log file. const ( infoLog severity = iota warningLog @@ -311,7 +315,7 @@ func (m *moduleSpec) Set(value string) error { // isLiteral reports whether the pattern is a literal string, that is, has no metacharacters // that require filepath.Match to be called to match the pattern. func isLiteral(pattern string) bool { - return !strings.ContainsAny(pattern, `*?[]\`) + return !strings.ContainsAny(pattern, `\*?[]`) } // traceLocation represents the setting of the -log_backtrace_at flag. @@ -466,7 +470,7 @@ func (l *loggingT) setVState(verbosity Level, filter []modulePat, setFilter bool // Turn verbosity off so V will not fire while we are in transition. logging.verbosity.set(0) // Ditto for filter length. - logging.filterLength = 0 + atomic.StoreInt32(&logging.filterLength, 0) // Set the new filters and wipe the pc->Level map if the filter has changed. if setFilter { @@ -513,7 +517,8 @@ var timeNow = time.Now // Stubbed out for testing. /* header formats a log header as defined by the C++ implementation. -It returns a buffer containing the formatted header. +It returns a buffer containing the formatted header and the user's file and line number. +The depth specifies how many stack frames above lives the source line to be identified in the log message. Log lines have this form: Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... @@ -527,10 +532,8 @@ where the fields are defined as follows: line The line number msg The user-supplied message */ -func (l *loggingT) header(s severity) *buffer { - // Lmmdd hh:mm:ss.uuuuuu threadid file:line] - now := timeNow() - _, file, line, ok := runtime.Caller(3) // It's always the same number of frames to the user's call. +func (l *loggingT) header(s severity, depth int) (*buffer, string, int) { + _, file, line, ok := runtime.Caller(3 + depth) if !ok { file = "???" line = 1 @@ -540,6 +543,12 @@ func (l *loggingT) header(s severity) *buffer { file = file[slash+1:] } } + return l.formatHeader(s, file, line), file, line +} + +// formatHeader formats a log header using the provided file name and line number. +func (l *loggingT) formatHeader(s severity, file string, line int) *buffer { + now := timeNow() if line < 0 { line = 0 // not a real line number, but acceptable to someDigits } @@ -552,6 +561,7 @@ func (l *loggingT) header(s severity) *buffer { // It's worth about 3X. Fprintf is hard. _, month, day := now.Date() hour, minute, second := now.Clock() + // Lmmdd hh:mm:ss.uuuuuu threadid file:line] buf.tmp[0] = severityChar[s] buf.twoDigits(1, int(month)) buf.twoDigits(3, day) @@ -562,11 +572,11 @@ func (l *loggingT) header(s severity) *buffer { buf.tmp[11] = ':' buf.twoDigits(12, second) buf.tmp[14] = '.' - buf.nDigits(6, 15, now.Nanosecond()/1000) + buf.nDigits(6, 15, now.Nanosecond()/1000, '0') buf.tmp[21] = ' ' - buf.nDigits(5, 22, pid) // TODO: should be TID - buf.tmp[27] = ' ' - buf.Write(buf.tmp[:28]) + buf.nDigits(7, 22, pid, ' ') // TODO: should be TID + buf.tmp[29] = ' ' + buf.Write(buf.tmp[:30]) buf.WriteString(file) buf.tmp[0] = ':' n := buf.someDigits(1, line) @@ -587,12 +597,18 @@ func (buf *buffer) twoDigits(i, d int) { buf.tmp[i] = digits[d%10] } -// nDigits formats a zero-prefixed n-digit integer at buf.tmp[i]. -func (buf *buffer) nDigits(n, i, d int) { - for j := n - 1; j >= 0; j-- { +// nDigits formats an n-digit integer at buf.tmp[i], +// padding with pad on the left. +// It assumes d >= 0. +func (buf *buffer) nDigits(n, i, d int, pad byte) { + j := n - 1 + for ; j >= 0 && d > 0; j-- { buf.tmp[i+j] = digits[d%10] d /= 10 } + for ; j >= 0; j-- { + buf.tmp[i+j] = pad + } } // someDigits formats a zero-prefixed variable-width integer at buf.tmp[i]. @@ -612,35 +628,50 @@ func (buf *buffer) someDigits(i, d int) int { } func (l *loggingT) println(s severity, args ...interface{}) { - buf := l.header(s) + buf, file, line := l.header(s, 0) fmt.Fprintln(buf, args...) - l.output(s, buf) + l.output(s, buf, file, line, false) } func (l *loggingT) print(s severity, args ...interface{}) { - buf := l.header(s) + l.printDepth(s, 1, args...) +} + +func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) { + buf, file, line := l.header(s, depth) fmt.Fprint(buf, args...) if buf.Bytes()[buf.Len()-1] != '\n' { buf.WriteByte('\n') } - l.output(s, buf) + l.output(s, buf, file, line, false) } func (l *loggingT) printf(s severity, format string, args ...interface{}) { - buf := l.header(s) + buf, file, line := l.header(s, 0) fmt.Fprintf(buf, format, args...) if buf.Bytes()[buf.Len()-1] != '\n' { buf.WriteByte('\n') } - l.output(s, buf) + l.output(s, buf, file, line, false) +} + +// printWithFileLine behaves like print but uses the provided file and line number. If +// alsoLogToStderr is true, the log message always appears on standard error; it +// will also appear in the log file unless --logtostderr is set. +func (l *loggingT) printWithFileLine(s severity, file string, line int, alsoToStderr bool, args ...interface{}) { + buf := l.formatHeader(s, file, line) + fmt.Fprint(buf, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, alsoToStderr) } // output writes the data to the log files and releases the buffer. -func (l *loggingT) output(s severity, buf *buffer) { +func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoToStderr bool) { l.mu.Lock() if l.traceLocation.isSet() { - _, file, line, ok := runtime.Caller(3) // It's always the same number of frames to the user's call (same as header). - if ok && l.traceLocation.match(file, line) { + if l.traceLocation.match(file, line) { buf.Write(stacks(false)) } } @@ -648,7 +679,7 @@ func (l *loggingT) output(s severity, buf *buffer) { if l.toStderr { os.Stderr.Write(data) } else { - if l.alsoToStderr || s >= l.stderrThreshold.get() { + if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() { os.Stderr.Write(data) } if l.file[s] == nil { @@ -672,7 +703,16 @@ func (l *loggingT) output(s severity, buf *buffer) { } } if s == fatalLog { - // Make sure we see the trace for the current goroutine on standard error. + // If we got here via Exit rather than Fatal, print no stacks. + if atomic.LoadUint32(&fatalNoStacks) > 0 { + l.mu.Unlock() + timeoutFlush(10 * time.Second) + os.Exit(1) + } + // Dump all goroutine stacks before exiting. + // First, make sure we see the trace for the current goroutine on standard error. + // If -logtostderr has been specified, the loop below will do that anyway + // as the first stack in the full dump. if !l.toStderr { os.Stderr.Write(stacks(false)) } @@ -861,6 +901,54 @@ func (l *loggingT) flushAll() { } } +// CopyStandardLogTo arranges for messages written to the Go "log" package's +// default logs to also appear in the Google logs for the named and lower +// severities. Subsequent changes to the standard log's default output location +// or format may break this behavior. +// +// Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not +// recognized, CopyStandardLogTo panics. +func CopyStandardLogTo(name string) { + sev, ok := severityByName(name) + if !ok { + panic(fmt.Sprintf("log.CopyStandardLogTo(%q): unrecognized severity name", name)) + } + // Set a log format that captures the user's file and line: + // d.go:23: message + stdLog.SetFlags(stdLog.Lshortfile) + stdLog.SetOutput(logBridge(sev)) +} + +// logBridge provides the Write method that enables CopyStandardLogTo to connect +// Go's standard logs to the logs provided by this package. +type logBridge severity + +// Write parses the standard logging line and passes its components to the +// logger for severity(lb). +func (lb logBridge) Write(b []byte) (n int, err error) { + var ( + file = "???" + line = 1 + text string + ) + // Split "d.go:23: message" into "d.go", "23", and "message". + if parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 { + text = fmt.Sprintf("bad log format: %s", b) + } else { + file = string(parts[0]) + text = string(parts[2][1:]) // skip leading space + line, err = strconv.Atoi(string(parts[1])) + if err != nil { + text = fmt.Sprintf("bad line number: %s", b) + line = 1 + } + } + // printWithFileLine with alsoToStderr=true, so standard log messages + // always appear on standard error. + logging.printWithFileLine(severity(lb), file, line, true, text) + return len(b), nil +} + // setV computes and remembers the V level for a given PC // when vmodule is enabled. // File pattern matching takes the basename of the file, stripped @@ -964,6 +1052,12 @@ func Info(args ...interface{}) { logging.print(infoLog, args...) } +// InfoDepth acts as Info but uses depth to determine which call frame to log. +// InfoDepth(0, "msg") is the same as Info("msg"). +func InfoDepth(depth int, args ...interface{}) { + logging.printDepth(infoLog, depth, args...) +} + // Infoln logs to the INFO log. // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. func Infoln(args ...interface{}) { @@ -982,6 +1076,12 @@ func Warning(args ...interface{}) { logging.print(warningLog, args...) } +// WarningDepth acts as Warning but uses depth to determine which call frame to log. +// WarningDepth(0, "msg") is the same as Warning("msg"). +func WarningDepth(depth int, args ...interface{}) { + logging.printDepth(warningLog, depth, args...) +} + // Warningln logs to the WARNING and INFO logs. // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. func Warningln(args ...interface{}) { @@ -1000,6 +1100,12 @@ func Error(args ...interface{}) { logging.print(errorLog, args...) } +// ErrorDepth acts as Error but uses depth to determine which call frame to log. +// ErrorDepth(0, "msg") is the same as Error("msg"). +func ErrorDepth(depth int, args ...interface{}) { + logging.printDepth(errorLog, depth, args...) +} + // Errorln logs to the ERROR, WARNING, and INFO logs. // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. func Errorln(args ...interface{}) { @@ -1019,6 +1125,12 @@ func Fatal(args ...interface{}) { logging.print(fatalLog, args...) } +// FatalDepth acts as Fatal but uses depth to determine which call frame to log. +// FatalDepth(0, "msg") is the same as Fatal("msg"). +func FatalDepth(depth int, args ...interface{}) { + logging.printDepth(fatalLog, depth, args...) +} + // Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, // including a stack trace of all running goroutines, then calls os.Exit(255). // Arguments are handled in the manner of fmt.Println; a newline is appended if missing. @@ -1032,3 +1144,34 @@ func Fatalln(args ...interface{}) { func Fatalf(format string, args ...interface{}) { logging.printf(fatalLog, format, args...) } + +// fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks. +// It allows Exit and relatives to use the Fatal logs. +var fatalNoStacks uint32 + +// Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Exit(args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.print(fatalLog, args...) +} + +// ExitDepth acts as Exit but uses depth to determine which call frame to log. +// ExitDepth(0, "msg") is the same as Exit("msg"). +func ExitDepth(depth int, args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.printDepth(fatalLog, depth, args...) +} + +// Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +func Exitln(args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.println(fatalLog, args...) +} + +// Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Exitf(format string, args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.printf(fatalLog, format, args...) +} diff --git a/Godeps/_workspace/src/github.com/golang/glog/glog_test.go b/Godeps/_workspace/src/github.com/golang/glog/glog_test.go index e4cac5a..0fb376e 100644 --- a/Godeps/_workspace/src/github.com/golang/glog/glog_test.go +++ b/Godeps/_workspace/src/github.com/golang/glog/glog_test.go @@ -19,8 +19,10 @@ package glog import ( "bytes" "fmt" + stdLog "log" "path/filepath" "runtime" + "strconv" "strings" "testing" "time" @@ -96,20 +98,99 @@ func TestInfo(t *testing.T) { } } +func TestInfoDepth(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + + f := func() { InfoDepth(1, "depth-test1") } + + // The next three lines must stay together + _, _, wantLine, _ := runtime.Caller(0) + InfoDepth(0, "depth-test0") + f() + + msgs := strings.Split(strings.TrimSuffix(contents(infoLog), "\n"), "\n") + if len(msgs) != 2 { + t.Fatalf("Got %d lines, expected 2", len(msgs)) + } + + for i, m := range msgs { + if !strings.HasPrefix(m, "I") { + t.Errorf("InfoDepth[%d] has wrong character: %q", i, m) + } + w := fmt.Sprintf("depth-test%d", i) + if !strings.Contains(m, w) { + t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m) + } + + // pull out the line number (between : and ]) + msg := m[strings.LastIndex(m, ":")+1:] + x := strings.Index(msg, "]") + if x < 0 { + t.Errorf("InfoDepth[%d]: missing ']': %q", i, m) + continue + } + line, err := strconv.Atoi(msg[:x]) + if err != nil { + t.Errorf("InfoDepth[%d]: bad line number: %q", i, m) + continue + } + wantLine++ + if wantLine != line { + t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine) + } + } +} + +func init() { + CopyStandardLogTo("INFO") +} + +// Test that CopyStandardLogTo panics on bad input. +func TestCopyStandardLogToPanic(t *testing.T) { + defer func() { + if s, ok := recover().(string); !ok || !strings.Contains(s, "LOG") { + t.Errorf(`CopyStandardLogTo("LOG") should have panicked: %v`, s) + } + }() + CopyStandardLogTo("LOG") +} + +// Test that using the standard log package logs to INFO. +func TestStandardLog(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + stdLog.Print("test") + if !contains(infoLog, "I", t) { + t.Errorf("Info has wrong character: %q", contents(infoLog)) + } + if !contains(infoLog, "test", t) { + t.Error("Info failed") + } +} + // Test that the header has the correct format. func TestHeader(t *testing.T) { setFlags() defer logging.swap(logging.newBuffers()) defer func(previous func() time.Time) { timeNow = previous }(timeNow) timeNow = func() time.Time { - return time.Date(2006, 1, 2, 15, 4, 5, .678901e9, time.Local) + return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local) } + pid = 1234 Info("test") - var line, pid int - n, err := fmt.Sscanf(contents(infoLog), "I0102 15:04:05.678901 %d glog_test.go:%d] test\n", &pid, &line) - if n != 2 || err != nil { + var line int + format := "I0102 15:04:05.067890 1234 glog_test.go:%d] test\n" + n, err := fmt.Sscanf(contents(infoLog), format, &line) + if n != 1 || err != nil { t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(infoLog)) } + // Scanf treats multiple spaces as equivalent to a single space, + // so check for correct space-padding also. + want := fmt.Sprintf(format, line) + if contents(infoLog) != want { + t.Errorf("log format error: got:\n\t%q\nwant:\t%q", contents(infoLog), want) + } } // Test that an Error log goes to Warning and Info. @@ -328,6 +409,7 @@ func TestLogBacktraceAt(t *testing.T) { func BenchmarkHeader(b *testing.B) { for i := 0; i < b.N; i++ { - logging.putBuffer(logging.header(infoLog)) + buf, _, _ := logging.header(infoLog, 0) + logging.putBuffer(buf) } } diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml index b0cf782..814710d 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml @@ -3,4 +3,5 @@ go: - 1.1 - 1.2 - 1.3 + - 1.4 - tip diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md index 082e4cf..5a25d24 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md @@ -189,8 +189,9 @@ for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers). Or you could [just write your own](http://justinas.org/writing-http-middleware-in-go/), it's very easy! -Alternatively, you could try [a framework building upon HttpRouter](#web-frameworks-building-upon-httprouter). +Alternatively, you could try [a framework building upon HttpRouter](#web-frameworks--co-based-on-httprouter). +### Multi-domain / Sub-domains Here is a quick example: Does your server serve multiple domains / hosts? You want to use sub-domains? Define a router per host! @@ -228,7 +229,85 @@ func main() { } ``` -## Web Frameworks building upon HttpRouter +### Basic Authentication +Another quick example: Basic Authentification (RFC 2617) for handles: + +```go +package main + +import ( + "bytes" + "encoding/base64" + "fmt" + "github.com/julienschmidt/httprouter" + "net/http" + "log" + "strings" +) + +func BasicAuth(h httprouter.Handle, user, pass []byte) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + const basicAuthPrefix string = "Basic " + + // Get the Basic Authentication credentials + auth := r.Header.Get("Authorization") + if strings.HasPrefix(auth, basicAuthPrefix) { + // Check credentials + payload, err := base64.StdEncoding.DecodeString(auth[len(basicAuthPrefix):]) + if err == nil { + pair := bytes.SplitN(payload, []byte(":"), 2) + if len(pair) == 2 && bytes.Equal(pair[0], user) && bytes.Equal(pair[1], pass) { + // Delegate request to the given handle + h(w, r, ps) + return + } + } + } + + // Request Basic Authentication otherwise + w.Header().Set("WWW-Authenticate", "Basic realm=Restricted") + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + } +} + +func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + fmt.Fprint(w, "Not protected!\n") +} + +func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + fmt.Fprint(w, "Protected!\n") +} + +func main() { + user := []byte("gordon") + pass := []byte("secret!") + + router := httprouter.New() + router.GET("/", Index) + router.GET("/protected/", BasicAuth(Protected, user, pass)) + + log.Fatal(http.ListenAndServe(":8080", router)) +} +``` + +## Chaining with the NotFound handler + +**NOTE: It might be required to set [Router.HandleMethodNotAllowed](http://godoc.org/github.com/julienschmidt/httprouter#Router.HandleMethodNotAllowed) to `false` to avoid problems.** + +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. + +### Static files +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): +```go +// Serve static files from the ./public directory +router.NotFound = http.FileServer(http.Dir("public")).ServeHTTP +``` + +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`. + +## Web Frameworks & Co based on HttpRouter 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: * [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance * [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine +* [Hitch](https://github.com/nbio/hitch): Hitch ties httprouter, [httpcontext](https://github.com/nbio/httpcontext), and middleware up in a bow +* [Neko](https://github.com/rocwong/neko): A lightweight web application framework for Golang diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go index 3f3d163..4893950 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go @@ -123,13 +123,21 @@ type Router struct { // handle is registered for it. // First superfluous path elements like ../ or // are removed. // Afterwards the router does a case-insensitive lookup of the cleaned path. - // If a handle can be found for this route, the router makes a redirection + // If a handle can be found for this route, the router makes a redirection // to the corrected path with status code 301 for GET requests and 307 for // all other request methods. // For example /FOO and /..//Foo could be redirected to /foo. // RedirectTrailingSlash is independent of this option. RedirectFixedPath bool + // If enabled, the router checks if another method is allowed for the + // current route, if the current request can not be routed. + // If this is the case, the request is answered with 'Method Not Allowed' + // and HTTP status code 405. + // If no other Method is allowed, the request is delegated to the NotFound + // handler. + HandleMethodNotAllowed bool + // Configurable http.HandlerFunc which is called when no matching route is // found. If it is not set, http.NotFound is used. NotFound http.HandlerFunc @@ -149,8 +157,9 @@ var _ http.Handler = New() // Path auto-correction, including trailing slashes, is enabled by default. func New() *Router { return &Router{ - RedirectTrailingSlash: true, - RedirectFixedPath: true, + RedirectTrailingSlash: true, + RedirectFixedPath: true, + HandleMethodNotAllowed: true, } } @@ -159,6 +168,11 @@ func (r *Router) GET(path string, handle Handle) { r.Handle("GET", path, handle) } +// HEAD is a shortcut for router.Handle("HEAD", path, handle) +func (r *Router) HEAD(path string, handle Handle) { + r.Handle("HEAD", path, handle) +} + // POST is a shortcut for router.Handle("POST", path, handle) func (r *Router) POST(path string, handle Handle) { r.Handle("POST", path, handle) @@ -256,6 +270,9 @@ func (r *Router) recv(w http.ResponseWriter, req *http.Request) { // Lookup allows the manual lookup of a method + path combo. // This is e.g. useful to build a framework around this router. +// If the path was found, it returns the handle function and the path parameter +// values. Otherwise the third return value indicates whether a redirection to +// the same path with an extra / without the trailing slash should be performed. func (r *Router) Lookup(method, path string) (Handle, Params, bool) { if root := r.trees[method]; root != nil { return root.getValue(path) @@ -284,7 +301,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { } if tsr && r.RedirectTrailingSlash { - if path[len(path)-1] == '/' { + if len(path) > 1 && path[len(path)-1] == '/' { req.URL.Path = path[:len(path)-1] } else { req.URL.Path = path + "/" @@ -308,6 +325,25 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } + // Handle 405 + if r.HandleMethodNotAllowed { + for method := range r.trees { + // Skip the requested method - we already tried this one + if method == req.Method { + continue + } + + handle, _, _ := r.trees[method].getValue(req.URL.Path) + if handle != nil { + http.Error(w, + http.StatusText(http.StatusMethodNotAllowed), + http.StatusMethodNotAllowed, + ) + return + } + } + } + // Handle 404 if r.NotFound != nil { r.NotFound(w, req) diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go index ca59066..6292ba8 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go @@ -76,7 +76,7 @@ func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func TestRouterAPI(t *testing.T) { - var get, post, put, patch, delete, handler, handlerFunc bool + var get, head, post, put, patch, delete, handler, handlerFunc bool httpHandler := handlerStruct{&handler} @@ -84,6 +84,9 @@ func TestRouterAPI(t *testing.T) { router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { get = true }) + router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { + head = true + }) router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) { post = true }) @@ -109,6 +112,12 @@ func TestRouterAPI(t *testing.T) { t.Error("routing GET failed") } + r, _ = http.NewRequest("HEAD", "/GET", nil) + router.ServeHTTP(w, r) + if !head { + t.Error("routing HEAD failed") + } + r, _ = http.NewRequest("POST", "/POST", nil) router.ServeHTTP(w, r) if !post { @@ -156,12 +165,28 @@ func TestRouterRoot(t *testing.T) { } } +func TestRouterNotAllowed(t *testing.T) { + handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {} + + router := New() + router.POST("/path", handlerFunc) + + // Test not allowed + r, _ := http.NewRequest("GET", "/path", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, r) + if !(w.Code == http.StatusMethodNotAllowed) { + t.Errorf("NotAllowed handling route %s failed: Code=%d, Header=%v", w.Code, w.Header()) + } +} + func TestRouterNotFound(t *testing.T) { handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {} router := New() router.GET("/path", handlerFunc) router.GET("/dir/", handlerFunc) + router.GET("/", handlerFunc) testRoutes := []struct { route string @@ -170,6 +195,7 @@ func TestRouterNotFound(t *testing.T) { }{ {"/path/", 301, "map[Location:[/path]]"}, // TSR -/ {"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/ + {"", 301, "map[Location:[/]]"}, // TSR +/ {"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case {"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case {"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/ diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go index 933b5cb..121d0c3 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go @@ -379,7 +379,7 @@ walk: // Outer loop for walking the tree return default: - panic("Unknown node type") + panic("Invalid node type") } } } else if path == n.path { @@ -490,7 +490,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa return append(ciPath, path...), true default: - panic("Unknown node type") + panic("Invalid node type") } } } else { diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go index cf4d170..ed1f9a8 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go @@ -557,3 +557,28 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) { } } } + +func TestTreeInvalidNodeType(t *testing.T) { + tree := &node{} + tree.addRoute("/", fakeHandler("/")) + tree.addRoute("/:page", fakeHandler("/:page")) + + // set invalid node type + tree.children[0].nType = 42 + + // normal lookup + recv := catchPanic(func() { + tree.getValue("/test") + }) + if rs, ok := recv.(string); !ok || rs != "Invalid node type" { + t.Fatalf(`Expected panic "Invalid node type", got "%v"`, recv) + } + + // case-insensitive lookup + recv = catchPanic(func() { + tree.findCaseInsensitivePath("/test", true) + }) + if rs, ok := recv.(string); !ok || rs != "Invalid node type" { + t.Fatalf(`Expected panic "Invalid node type", got "%v"`, recv) + } +} diff --git a/Godeps/_workspace/src/github.com/pushrax/faststats/LICENSE b/Godeps/_workspace/src/github.com/pushrax/faststats/LICENSE index 667bfb3..37afa01 100644 --- a/Godeps/_workspace/src/github.com/pushrax/faststats/LICENSE +++ b/Godeps/_workspace/src/github.com/pushrax/faststats/LICENSE @@ -1,6 +1,6 @@ faststats is released under a BSD 2-Clause license, reproduced below. -Copyright (c) 2014, The faststats Authors +Copyright (c) 2015, The faststats Authors All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/Godeps/_workspace/src/github.com/pushrax/faststats/faststats.go b/Godeps/_workspace/src/github.com/pushrax/faststats/faststats.go index 29f928e..aba4b27 100644 --- a/Godeps/_workspace/src/github.com/pushrax/faststats/faststats.go +++ b/Godeps/_workspace/src/github.com/pushrax/faststats/faststats.go @@ -1,4 +1,4 @@ -// Copyright 2014 The faststats Authors. All rights reserved. +// Copyright 2015 The faststats Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/pushrax/faststats/json.go b/Godeps/_workspace/src/github.com/pushrax/faststats/json.go index 9fcf0e8..a7a4861 100644 --- a/Godeps/_workspace/src/github.com/pushrax/faststats/json.go +++ b/Godeps/_workspace/src/github.com/pushrax/faststats/json.go @@ -1,4 +1,4 @@ -// Copyright 2014 The faststats Authors. All rights reserved. +// Copyright 2015 The faststats Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/pushrax/faststats/percentile.go b/Godeps/_workspace/src/github.com/pushrax/faststats/percentile.go index 333d56d..e44fd54 100644 --- a/Godeps/_workspace/src/github.com/pushrax/faststats/percentile.go +++ b/Godeps/_workspace/src/github.com/pushrax/faststats/percentile.go @@ -1,4 +1,4 @@ -// Copyright 2014 The faststats Authors. All rights reserved. +// Copyright 2015 The faststats Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/pushrax/faststats/percentile_test.go b/Godeps/_workspace/src/github.com/pushrax/faststats/percentile_test.go index 82bb9cc..0c898ab 100644 --- a/Godeps/_workspace/src/github.com/pushrax/faststats/percentile_test.go +++ b/Godeps/_workspace/src/github.com/pushrax/faststats/percentile_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 The faststats Authors. All rights reserved. +// Copyright 2015 The faststats Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/pushrax/faststats/util.go b/Godeps/_workspace/src/github.com/pushrax/faststats/util.go index 2073ace..df09a13 100644 --- a/Godeps/_workspace/src/github.com/pushrax/faststats/util.go +++ b/Godeps/_workspace/src/github.com/pushrax/faststats/util.go @@ -1,4 +1,4 @@ -// Copyright 2014 The faststats Authors. All rights reserved. +// Copyright 2015 The faststats Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/pushrax/flatjson/LICENSE b/Godeps/_workspace/src/github.com/pushrax/flatjson/LICENSE index d0cda40..1e26521 100644 --- a/Godeps/_workspace/src/github.com/pushrax/flatjson/LICENSE +++ b/Godeps/_workspace/src/github.com/pushrax/flatjson/LICENSE @@ -1,6 +1,6 @@ -Chihaya is released under a BSD 2-Clause license, reproduced below. +flatjson is released under a BSD 2-Clause license, reproduced below. -Copyright (c) 2014, The Chihaya Authors +Copyright (c) 2015, The flatjson Authors All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/Godeps/_workspace/src/github.com/pushrax/flatjson/README.md b/Godeps/_workspace/src/github.com/pushrax/flatjson/README.md index 08e0126..5bd26f5 100644 --- a/Godeps/_workspace/src/github.com/pushrax/flatjson/README.md +++ b/Godeps/_workspace/src/github.com/pushrax/flatjson/README.md @@ -7,7 +7,7 @@ Example use case: ```json { - "Connections" { + "Connections": { "Open": 2, "Accepted": 4 }, diff --git a/Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson.go b/Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson.go index ebf42a3..fe8b7e8 100644 --- a/Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson.go +++ b/Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson.go @@ -1,4 +1,4 @@ -// Copyright 2014 The flatjson Authors. All rights reserved. +// Copyright 2015 The flatjson Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson_test.go b/Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson_test.go index 3906bdb..8837a55 100644 --- a/Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson_test.go +++ b/Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson_test.go @@ -14,14 +14,11 @@ type Child struct { } func TestBasicFlatten(t *testing.T) { - val := &struct { - A int - B string - }{10, "str"} + val := &Child{10, "str"} expected := flatjson.Map{ - "A": 10.0, // JSON numbers are all float64. - "B": "str", + "CC": 10.0, // JSON numbers are all float64. + "CD": "str", } testFlattening(t, val, expected) diff --git a/Godeps/_workspace/src/github.com/stretchr/graceful/graceful.go b/Godeps/_workspace/src/github.com/stretchr/graceful/graceful.go index 25ce2a2..c43aafe 100644 --- a/Godeps/_workspace/src/github.com/stretchr/graceful/graceful.go +++ b/Godeps/_workspace/src/github.com/stretchr/graceful/graceful.go @@ -12,7 +12,7 @@ import ( "time" "github.com/stretchr/pat/stop" - "code.google.com/p/go.net/netutil" + "golang.org/x/net/netutil" ) // Server wraps an http.Server with graceful connection handling. @@ -115,8 +115,13 @@ func (srv *Server) ListenAndServe() error { // timeout is the duration to wait until killing active requests and stopping the server. // If timeout is 0, the server never times out. It waits for all active requests to finish. func ListenAndServeTLS(server *http.Server, certFile, keyFile string, timeout time.Duration) error { - // Create the listener ourselves so we can control its lifetime srv := &Server{Timeout: timeout, Server: server} + return srv.ListenAndServeTLS(certFile, keyFile) +} + +// ListenAndServeTLS is equivalent to http.Server.ListenAndServeTLS with graceful shutdown enabled. +func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { + // Create the listener ourselves so we can control its lifetime addr := srv.Addr if addr == "" { addr = ":https" diff --git a/Godeps/_workspace/src/golang.org/x/net/netutil/listen.go b/Godeps/_workspace/src/golang.org/x/net/netutil/listen.go index a2591f8..b317ba2 100644 --- a/Godeps/_workspace/src/golang.org/x/net/netutil/listen.go +++ b/Godeps/_workspace/src/golang.org/x/net/netutil/listen.go @@ -4,7 +4,7 @@ // Package netutil provides network utility functions, complementing the more // common ones in the net package. -package netutil +package netutil // import "golang.org/x/net/netutil" import ( "net" diff --git a/example_config.json b/example_config.json index eeb7bbc..d6e02c1 100644 --- a/example_config.json +++ b/example_config.json @@ -11,6 +11,7 @@ "real_ip_header": "", "respect_af": false, "client_whitelist_enabled": false, + "client_whitelist": ["OP1011"], "http_listen_addr": ":6881", "http_request_timeout": "10s", "http_read_timeout": "10s", diff --git a/http/http.go b/http/http.go index 9e3c461..967c7ce 100644 --- a/http/http.go +++ b/http/http.go @@ -83,6 +83,7 @@ func newRouter(s *Server) *httprouter.Router { } if s.config.ClientWhitelistEnabled { + r.GET("/clients/:clientID", makeHandler(s.getClient)) r.PUT("/clients/:clientID", makeHandler(s.putClient)) r.DELETE("/clients/:clientID", makeHandler(s.delClient)) } diff --git a/http/routes.go b/http/routes.go index fc40c30..a7eae79 100644 --- a/http/routes.go +++ b/http/routes.go @@ -178,6 +178,13 @@ func (s *Server) delUser(w http.ResponseWriter, r *http.Request, p httprouter.Pa return http.StatusOK, nil } +func (s *Server) getClient(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) { + if err := s.tracker.ClientApproved(p.ByName("clientID")); err != nil { + return http.StatusNotFound, err + } + return http.StatusOK, nil +} + func (s *Server) putClient(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) { s.tracker.PutClient(p.ByName("clientID")) return http.StatusOK, nil