mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-08 11:58:49 +03:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
8baead6107
15 changed files with 255 additions and 105 deletions
|
@ -17,8 +17,9 @@ func Browse(c *Controller) (middleware.Middleware, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
browse := browse.Browse{
|
browse := browse.Browse{
|
||||||
Root: c.Root,
|
Root: c.Root,
|
||||||
Configs: configs,
|
Configs: configs,
|
||||||
|
IgnoreIndexes: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
|
|
@ -25,16 +25,24 @@ func Errors(c *Controller) (middleware.Middleware, error) {
|
||||||
var err error
|
var err error
|
||||||
var writer io.Writer
|
var writer io.Writer
|
||||||
|
|
||||||
if handler.LogFile == "stdout" {
|
switch handler.LogFile {
|
||||||
|
case "visible":
|
||||||
|
handler.Debug = true
|
||||||
|
case "stdout":
|
||||||
writer = os.Stdout
|
writer = os.Stdout
|
||||||
} else if handler.LogFile == "stderr" {
|
case "stderr":
|
||||||
writer = os.Stderr
|
writer = os.Stderr
|
||||||
} else if handler.LogFile == "syslog" {
|
case "syslog":
|
||||||
writer, err = gsyslog.NewLogger(gsyslog.LOG_ERR, "LOCAL0", "caddy")
|
writer, err = gsyslog.NewLogger(gsyslog.LOG_ERR, "LOCAL0", "caddy")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if handler.LogFile != "" {
|
default:
|
||||||
|
if handler.LogFile == "" {
|
||||||
|
writer = os.Stderr // default
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
var file *os.File
|
var file *os.File
|
||||||
file, err = os.OpenFile(handler.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
file, err = os.OpenFile(handler.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -80,15 +88,19 @@ func errorsParse(c *Controller) (*errors.ErrorHandler, error) {
|
||||||
where := c.Val()
|
where := c.Val()
|
||||||
|
|
||||||
if what == "log" {
|
if what == "log" {
|
||||||
handler.LogFile = where
|
if where == "visible" {
|
||||||
if c.NextArg() {
|
handler.Debug = true
|
||||||
if c.Val() == "{" {
|
} else {
|
||||||
c.IncrNest()
|
handler.LogFile = where
|
||||||
logRoller, err := parseRoller(c)
|
if c.NextArg() {
|
||||||
if err != nil {
|
if c.Val() == "{" {
|
||||||
return hadBlock, err
|
c.IncrNest()
|
||||||
|
logRoller, err := parseRoller(c)
|
||||||
|
if err != nil {
|
||||||
|
return hadBlock, err
|
||||||
|
}
|
||||||
|
handler.LogRoller = logRoller
|
||||||
}
|
}
|
||||||
handler.LogRoller = logRoller
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -121,12 +133,14 @@ func errorsParse(c *Controller) (*errors.ErrorHandler, error) {
|
||||||
return handler, err
|
return handler, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, the only argument would be an error log file name
|
// Otherwise, the only argument would be an error log file name or 'visible'
|
||||||
if !hadBlock {
|
if !hadBlock {
|
||||||
if c.NextArg() {
|
if c.NextArg() {
|
||||||
handler.LogFile = c.Val()
|
if c.Val() == "visible" {
|
||||||
} else {
|
handler.Debug = true
|
||||||
handler.LogFile = errors.DefaultLogFilename
|
} else {
|
||||||
|
handler.LogFile = c.Val()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestErrors(t *testing.T) {
|
func TestErrors(t *testing.T) {
|
||||||
|
|
||||||
c := NewTestController(`errors`)
|
c := NewTestController(`errors`)
|
||||||
|
|
||||||
mid, err := Errors(c)
|
mid, err := Errors(c)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -28,8 +26,8 @@ func TestErrors(t *testing.T) {
|
||||||
t.Fatalf("Expected handler to be type ErrorHandler, got: %#v", handler)
|
t.Fatalf("Expected handler to be type ErrorHandler, got: %#v", handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
if myHandler.LogFile != errors.DefaultLogFilename {
|
if myHandler.LogFile != "" {
|
||||||
t.Errorf("Expected %s as the default LogFile", errors.DefaultLogFilename)
|
t.Errorf("Expected '%s' as the default LogFile", "")
|
||||||
}
|
}
|
||||||
if myHandler.LogRoller != nil {
|
if myHandler.LogRoller != nil {
|
||||||
t.Errorf("Expected LogRoller to be nil, got: %v", *myHandler.LogRoller)
|
t.Errorf("Expected LogRoller to be nil, got: %v", *myHandler.LogRoller)
|
||||||
|
@ -37,6 +35,15 @@ func TestErrors(t *testing.T) {
|
||||||
if !SameNext(myHandler.Next, EmptyNext) {
|
if !SameNext(myHandler.Next, EmptyNext) {
|
||||||
t.Error("'Next' field of handler was not set properly")
|
t.Error("'Next' field of handler was not set properly")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test Startup function
|
||||||
|
if len(c.Startup) == 0 {
|
||||||
|
t.Fatal("Expected 1 startup function, had 0")
|
||||||
|
}
|
||||||
|
err = c.Startup[0]()
|
||||||
|
if myHandler.Log == nil {
|
||||||
|
t.Error("Expected Log to be non-nil after startup because Debug is not enabled")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorsParse(t *testing.T) {
|
func TestErrorsParse(t *testing.T) {
|
||||||
|
@ -46,11 +53,19 @@ func TestErrorsParse(t *testing.T) {
|
||||||
expectedErrorHandler errors.ErrorHandler
|
expectedErrorHandler errors.ErrorHandler
|
||||||
}{
|
}{
|
||||||
{`errors`, false, errors.ErrorHandler{
|
{`errors`, false, errors.ErrorHandler{
|
||||||
LogFile: errors.DefaultLogFilename,
|
LogFile: "",
|
||||||
}},
|
}},
|
||||||
{`errors errors.txt`, false, errors.ErrorHandler{
|
{`errors errors.txt`, false, errors.ErrorHandler{
|
||||||
LogFile: "errors.txt",
|
LogFile: "errors.txt",
|
||||||
}},
|
}},
|
||||||
|
{`errors visible`, false, errors.ErrorHandler{
|
||||||
|
LogFile: "",
|
||||||
|
Debug: true,
|
||||||
|
}},
|
||||||
|
{`errors { log visible }`, false, errors.ErrorHandler{
|
||||||
|
LogFile: "",
|
||||||
|
Debug: true,
|
||||||
|
}},
|
||||||
{`errors { log errors.txt
|
{`errors { log errors.txt
|
||||||
404 404.html
|
404 404.html
|
||||||
500 500.html
|
500 500.html
|
||||||
|
@ -101,9 +116,13 @@ func TestErrorsParse(t *testing.T) {
|
||||||
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
|
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
|
||||||
}
|
}
|
||||||
if actualErrorsRule.LogFile != test.expectedErrorHandler.LogFile {
|
if actualErrorsRule.LogFile != test.expectedErrorHandler.LogFile {
|
||||||
t.Errorf("Test %d expected LogFile to be %s , but got %s",
|
t.Errorf("Test %d expected LogFile to be %s, but got %s",
|
||||||
i, test.expectedErrorHandler.LogFile, actualErrorsRule.LogFile)
|
i, test.expectedErrorHandler.LogFile, actualErrorsRule.LogFile)
|
||||||
}
|
}
|
||||||
|
if actualErrorsRule.Debug != test.expectedErrorHandler.Debug {
|
||||||
|
t.Errorf("Test %d expected Debug to be %v, but got %v",
|
||||||
|
i, test.expectedErrorHandler.Debug, actualErrorsRule.Debug)
|
||||||
|
}
|
||||||
if actualErrorsRule.LogRoller != nil && test.expectedErrorHandler.LogRoller == nil || actualErrorsRule.LogRoller == nil && test.expectedErrorHandler.LogRoller != nil {
|
if actualErrorsRule.LogRoller != nil && test.expectedErrorHandler.LogRoller == nil || actualErrorsRule.LogRoller == nil && test.expectedErrorHandler.LogRoller != nil {
|
||||||
t.Fatalf("Test %d expected LogRoller to be %v, but got %v",
|
t.Fatalf("Test %d expected LogRoller to be %v, but got %v",
|
||||||
i, test.expectedErrorHandler.LogRoller, actualErrorsRule.LogRoller)
|
i, test.expectedErrorHandler.LogRoller, actualErrorsRule.LogRoller)
|
||||||
|
|
6
dist/CHANGES.txt
vendored
6
dist/CHANGES.txt
vendored
|
@ -3,6 +3,12 @@ CHANGES
|
||||||
<master>
|
<master>
|
||||||
- basicauth: Support for legacy htpasswd files
|
- basicauth: Support for legacy htpasswd files
|
||||||
- browse: JSON response with file listing given Accept header
|
- browse: JSON response with file listing given Accept header
|
||||||
|
- core: Caddyfile as command line argument
|
||||||
|
- errors: Can write full stack trace to HTTP response for debugging
|
||||||
|
- errors, log: Roll log files after certain size or age
|
||||||
|
- proxy: Fix for 32-bit architectures
|
||||||
|
- templates: Added .StripExt and .StripHTML methods
|
||||||
|
- Internal improvements and minor bug fixes
|
||||||
|
|
||||||
|
|
||||||
0.7.5 (August 5, 2015)
|
0.7.5 (August 5, 2015)
|
||||||
|
|
|
@ -23,14 +23,16 @@ import (
|
||||||
// Browse is an http.Handler that can show a file listing when
|
// Browse is an http.Handler that can show a file listing when
|
||||||
// directories in the given paths are specified.
|
// directories in the given paths are specified.
|
||||||
type Browse struct {
|
type Browse struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
Root string
|
Root string
|
||||||
Configs []Config
|
Configs []Config
|
||||||
|
IgnoreIndexes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is a configuration for browsing in a particular path.
|
// Config is a configuration for browsing in a particular path.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
PathScope string
|
PathScope string
|
||||||
|
Variables interface{}
|
||||||
Template *template.Template
|
Template *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +56,9 @@ type Listing struct {
|
||||||
// And which order
|
// And which order
|
||||||
Order string
|
Order string
|
||||||
|
|
||||||
|
// Optional custom variables for use in browse templates
|
||||||
|
User interface{}
|
||||||
|
|
||||||
middleware.Context
|
middleware.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,25 +138,18 @@ func (fi FileInfo) HumanModTime(format string) string {
|
||||||
return fi.ModTime.Format(format)
|
return fi.ModTime.Format(format)
|
||||||
}
|
}
|
||||||
|
|
||||||
var IndexPages = []string{
|
func directoryListing(files []os.FileInfo, r *http.Request, canGoUp bool, root string, ignoreIndexes bool, vars interface{}) (Listing, error) {
|
||||||
"index.html",
|
|
||||||
"index.htm",
|
|
||||||
"index.txt",
|
|
||||||
"default.html",
|
|
||||||
"default.htm",
|
|
||||||
"default.txt",
|
|
||||||
}
|
|
||||||
|
|
||||||
func directoryListing(files []os.FileInfo, r *http.Request, canGoUp bool, root string) (Listing, error) {
|
|
||||||
var fileinfos []FileInfo
|
var fileinfos []FileInfo
|
||||||
var urlPath = r.URL.Path
|
var urlPath = r.URL.Path
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
name := f.Name()
|
name := f.Name()
|
||||||
|
|
||||||
// Directory is not browsable if it contains index file
|
// Directory is not browsable if it contains index file
|
||||||
for _, indexName := range IndexPages {
|
if !ignoreIndexes {
|
||||||
if name == indexName {
|
for _, indexName := range middleware.IndexPages {
|
||||||
return Listing{}, errors.New("Directory contains index file, not browsable!")
|
if name == indexName {
|
||||||
|
return Listing{}, errors.New("Directory contains index file, not browsable!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +179,7 @@ func directoryListing(files []os.FileInfo, r *http.Request, canGoUp bool, root s
|
||||||
Req: r,
|
Req: r,
|
||||||
URL: r.URL,
|
URL: r.URL,
|
||||||
},
|
},
|
||||||
|
User: vars,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,7 +233,7 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Assemble listing of directory contents
|
// Assemble listing of directory contents
|
||||||
listing, err := directoryListing(files, r, canGoUp, b.Root)
|
listing, err := directoryListing(files, r, canGoUp, b.Root, b.IgnoreIndexes, bc.Variables)
|
||||||
if err != nil { // directory isn't browsable
|
if err != nil { // directory isn't browsable
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,53 @@ func (c Context) Truncate(input string, length int) string {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StripHTML returns s without HTML tags. It is fairly naive
|
||||||
|
// but works with most valid HTML inputs.
|
||||||
|
func (c Context) StripHTML(s string) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var inTag, inQuotes bool
|
||||||
|
var tagStart int
|
||||||
|
for i, ch := range s {
|
||||||
|
if inTag {
|
||||||
|
if ch == '>' && !inQuotes {
|
||||||
|
inTag = false
|
||||||
|
} else if ch == '<' && !inQuotes {
|
||||||
|
// false start
|
||||||
|
buf.WriteString(s[tagStart:i])
|
||||||
|
tagStart = i
|
||||||
|
} else if ch == '"' {
|
||||||
|
inQuotes = !inQuotes
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ch == '<' {
|
||||||
|
inTag = true
|
||||||
|
tagStart = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.WriteRune(ch)
|
||||||
|
}
|
||||||
|
if inTag {
|
||||||
|
// false start
|
||||||
|
buf.WriteString(s[tagStart:])
|
||||||
|
inTag = false
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StripExt returns the input string without the extension,
|
||||||
|
// which is the suffix starting with the final '.' character
|
||||||
|
// but not before the final path separator ('/') character.
|
||||||
|
// If there is no extension, the whole input is returned.
|
||||||
|
func (c Context) StripExt(path string) string {
|
||||||
|
for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
|
||||||
|
if path[i] == '.' {
|
||||||
|
return path[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
// Replace replaces instances of find in input with replacement.
|
// Replace replaces instances of find in input with replacement.
|
||||||
func (c Context) Replace(input, find, replacement string) string {
|
func (c Context) Replace(input, find, replacement string) string {
|
||||||
return strings.Replace(input, find, replacement, -1)
|
return strings.Replace(input, find, replacement, -1)
|
||||||
|
|
|
@ -14,13 +14,14 @@ import (
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrorHandler handles HTTP errors (or errors from other middleware).
|
// ErrorHandler handles HTTP errors (and errors from other middleware).
|
||||||
type ErrorHandler struct {
|
type ErrorHandler struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
ErrorPages map[int]string // map of status code to filename
|
ErrorPages map[int]string // map of status code to filename
|
||||||
LogFile string
|
LogFile string
|
||||||
Log *log.Logger
|
Log *log.Logger
|
||||||
LogRoller *middleware.LogRoller
|
LogRoller *middleware.LogRoller
|
||||||
|
Debug bool // if true, errors are written out to client rather than to a log
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
@ -29,12 +30,21 @@ func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, er
|
||||||
status, err := h.Next.ServeHTTP(w, r)
|
status, err := h.Next.ServeHTTP(w, r)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.Log.Printf("%s [ERROR %d %s] %v", time.Now().Format(timeFormat), status, r.URL.Path, err)
|
errMsg := fmt.Sprintf("%s [ERROR %d %s] %v", time.Now().Format(timeFormat), status, r.URL.Path, err)
|
||||||
|
|
||||||
|
if h.Debug {
|
||||||
|
// Write error to response instead of to log
|
||||||
|
w.WriteHeader(status)
|
||||||
|
fmt.Fprintln(w, errMsg)
|
||||||
|
return 0, err // returning < 400 signals that a response has been written
|
||||||
|
} else {
|
||||||
|
h.Log.Println(errMsg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if status >= 400 {
|
if status >= 400 {
|
||||||
h.errorPage(w, status)
|
h.errorPage(w, r, status)
|
||||||
return 0, err // status < 400 signals that a response has been written
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return status, err
|
return status, err
|
||||||
|
@ -43,7 +53,7 @@ func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, er
|
||||||
// errorPage serves a static error page to w according to the status
|
// errorPage serves a static error page to w according to the status
|
||||||
// code. If there is an error serving the error page, a plaintext error
|
// code. If there is an error serving the error page, a plaintext error
|
||||||
// message is written instead, and the extra error is logged.
|
// message is written instead, and the extra error is logged.
|
||||||
func (h ErrorHandler) errorPage(w http.ResponseWriter, code int) {
|
func (h ErrorHandler) errorPage(w http.ResponseWriter, r *http.Request, code int) {
|
||||||
defaultBody := fmt.Sprintf("%d %s", code, http.StatusText(code))
|
defaultBody := fmt.Sprintf("%d %s", code, http.StatusText(code))
|
||||||
|
|
||||||
// See if an error page for this status code was specified
|
// See if an error page for this status code was specified
|
||||||
|
@ -52,8 +62,9 @@ func (h ErrorHandler) errorPage(w http.ResponseWriter, code int) {
|
||||||
// Try to open it
|
// Try to open it
|
||||||
errorPage, err := os.Open(pagePath)
|
errorPage, err := os.Open(pagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// An error handling an error... <insert grumpy cat here>
|
// An additional error handling an error... <insert grumpy cat here>
|
||||||
h.Log.Printf("HTTP %d could not load error page %s: %v", code, pagePath, err)
|
h.Log.Printf("%s [NOTICE %d %s] could not load error page: %v",
|
||||||
|
time.Now().Format(timeFormat), code, r.URL.String(), err)
|
||||||
http.Error(w, defaultBody, code)
|
http.Error(w, defaultBody, code)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -66,7 +77,8 @@ func (h ErrorHandler) errorPage(w http.ResponseWriter, code int) {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Epic fail... sigh.
|
// Epic fail... sigh.
|
||||||
h.Log.Printf("HTTP %d could not respond with %s: %v", code, pagePath, err)
|
h.Log.Printf("%s [NOTICE %d %s] could not respond with %s: %v",
|
||||||
|
time.Now().Format(timeFormat), code, r.URL.String(), pagePath, err)
|
||||||
http.Error(w, defaultBody, code)
|
http.Error(w, defaultBody, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,10 +120,18 @@ func (h ErrorHandler) recovery(w http.ResponseWriter, r *http.Request) {
|
||||||
file = file[pkgPathPos+len(delim):]
|
file = file[pkgPathPos+len(delim):]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently we don't use the function name, as file:line is more conventional
|
panicMsg := fmt.Sprintf("%s [PANIC %s] %s:%d - %v", time.Now().Format(timeFormat), r.URL.String(), file, line, rec)
|
||||||
h.Log.Printf("%s [PANIC %s] %s:%d - %v", time.Now().Format(timeFormat), r.URL.String(), file, line, rec)
|
if h.Debug {
|
||||||
h.errorPage(w, http.StatusInternalServerError)
|
// Write error and stack trace to the response rather than to a log
|
||||||
|
var stackBuf [4096]byte
|
||||||
|
stack := stackBuf[:runtime.Stack(stackBuf[:], false)]
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "%s\n\n%s", panicMsg, stack)
|
||||||
|
} else {
|
||||||
|
// Currently we don't use the function name, since file:line is more conventional
|
||||||
|
h.Log.Printf(panicMsg)
|
||||||
|
h.errorPage(w, r, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultLogFilename = "error.log"
|
|
||||||
const timeFormat = "02/Jan/2006:15:04:05 -0700"
|
const timeFormat = "02/Jan/2006:15:04:05 -0700"
|
||||||
|
|
|
@ -33,11 +33,12 @@ func TestErrors(t *testing.T) {
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
em := ErrorHandler{
|
em := ErrorHandler{
|
||||||
ErrorPages: make(map[int]string),
|
ErrorPages: map[int]string{
|
||||||
Log: log.New(&buf, "", 0),
|
http.StatusNotFound: path,
|
||||||
|
http.StatusForbidden: "not_exist_file",
|
||||||
|
},
|
||||||
|
Log: log.New(&buf, "", 0),
|
||||||
}
|
}
|
||||||
em.ErrorPages[http.StatusNotFound] = path
|
|
||||||
em.ErrorPages[http.StatusForbidden] = "not_exist_file"
|
|
||||||
_, notExistErr := os.Open("not_exist_file")
|
_, notExistErr := os.Open("not_exist_file")
|
||||||
|
|
||||||
testErr := errors.New("test error")
|
testErr := errors.New("test error")
|
||||||
|
@ -82,8 +83,8 @@ func TestErrors(t *testing.T) {
|
||||||
expectedCode: 0,
|
expectedCode: 0,
|
||||||
expectedBody: fmt.Sprintf("%d %s\n", http.StatusForbidden,
|
expectedBody: fmt.Sprintf("%d %s\n", http.StatusForbidden,
|
||||||
http.StatusText(http.StatusForbidden)),
|
http.StatusText(http.StatusForbidden)),
|
||||||
expectedLog: fmt.Sprintf("HTTP %d could not load error page %s: %v\n",
|
expectedLog: fmt.Sprintf("[NOTICE %d /] could not load error page: %v\n",
|
||||||
http.StatusForbidden, "not_exist_file", notExistErr),
|
http.StatusForbidden, notExistErr),
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -117,6 +118,44 @@ func TestErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVisibleErrorWithPanic(t *testing.T) {
|
||||||
|
const panicMsg = "I'm a panic"
|
||||||
|
eh := ErrorHandler{
|
||||||
|
ErrorPages: make(map[int]string),
|
||||||
|
Debug: true,
|
||||||
|
Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
panic(panicMsg)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
code, err := eh.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Errorf("Expected error handler to return 0 (it should write to response), got status %d", code)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected error handler to return nil error (it should panic!), but got '%v'", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := rec.Body.String()
|
||||||
|
|
||||||
|
if !strings.Contains(body, "[PANIC /] middleware/errors/errors_test.go") {
|
||||||
|
t.Errorf("Expected response body to contain error log line, but it didn't:\n%s", body)
|
||||||
|
}
|
||||||
|
if !strings.Contains(body, panicMsg) {
|
||||||
|
t.Errorf("Expected response body to contain panic message, but it didn't:\n%s", body)
|
||||||
|
}
|
||||||
|
if len(body) < 500 {
|
||||||
|
t.Errorf("Expected response body to contain stack trace, but it was too short: len=%d", len(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func genErrorHandler(status int, err error, body string) middleware.Handler {
|
func genErrorHandler(status int, err error, body string) middleware.Handler {
|
||||||
return middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
return middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
fmt.Fprint(w, body)
|
fmt.Fprint(w, body)
|
||||||
|
|
|
@ -1,25 +1,34 @@
|
||||||
package server
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
|
||||||
"github.com/mholt/caddy/middleware/browse"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// This file contains a standard way for Caddy middleware
|
||||||
|
// to load files from the file system given a request
|
||||||
|
// URI and path to site root. Other middleware that load
|
||||||
|
// files should use these facilities.
|
||||||
|
|
||||||
|
// FileServer implements a production-ready file server
|
||||||
|
// and is the 'default' handler for all requests to Caddy.
|
||||||
|
// It simply loads and serves the URI requested. If Caddy is
|
||||||
|
// run without any extra configuration/directives, this is the
|
||||||
|
// only middleware handler that runs. It is not in its own
|
||||||
|
// folder like most other middleware handlers because it does
|
||||||
|
// not require a directive. It is a special case.
|
||||||
|
//
|
||||||
// FileServer is adapted from the one in net/http by
|
// FileServer is adapted from the one in net/http by
|
||||||
// the Go authors. Significant modifications have been made.
|
// the Go authors. Significant modifications have been made.
|
||||||
//
|
//
|
||||||
//
|
// Original license:
|
||||||
// License:
|
|
||||||
//
|
//
|
||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
func FileServer(root http.FileSystem, hide []string) middleware.Handler {
|
func FileServer(root http.FileSystem, hide []string) Handler {
|
||||||
return &fileHandler{root: root, hide: hide}
|
return &fileHandler{root: root, hide: hide}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +91,7 @@ func (fh *fileHandler) serveFile(w http.ResponseWriter, r *http.Request, name st
|
||||||
|
|
||||||
// use contents of an index file, if present, for directory
|
// use contents of an index file, if present, for directory
|
||||||
if d.IsDir() {
|
if d.IsDir() {
|
||||||
for _, indexPage := range browse.IndexPages {
|
for _, indexPage := range IndexPages {
|
||||||
index := strings.TrimSuffix(name, "/") + "/" + indexPage
|
index := strings.TrimSuffix(name, "/") + "/" + indexPage
|
||||||
ff, err := fh.root.Open(index)
|
ff, err := fh.root.Open(index)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -134,3 +143,14 @@ func redirect(w http.ResponseWriter, r *http.Request, newPath string) {
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, newPath, http.StatusMovedPermanently)
|
http.Redirect(w, r, newPath, http.StatusMovedPermanently)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IndexPages is a list of pages that may be understood as
|
||||||
|
// the "index" files to directories.
|
||||||
|
var IndexPages = []string{
|
||||||
|
"index.html",
|
||||||
|
"index.htm",
|
||||||
|
"index.txt",
|
||||||
|
"default.html",
|
||||||
|
"default.htm",
|
||||||
|
"default.txt",
|
||||||
|
}
|
|
@ -33,10 +33,9 @@ type UpstreamHostDownFunc func(*UpstreamHost) bool
|
||||||
|
|
||||||
// UpstreamHost represents a single proxy upstream
|
// UpstreamHost represents a single proxy upstream
|
||||||
type UpstreamHost struct {
|
type UpstreamHost struct {
|
||||||
// The hostname of this upstream host
|
Conns int64 // must be first field to be 64-bit aligned on 32-bit systems
|
||||||
Name string
|
Name string // hostname of this upstream host
|
||||||
ReverseProxy *ReverseProxy
|
ReverseProxy *ReverseProxy
|
||||||
Conns int64
|
|
||||||
Fails int32
|
Fails int32
|
||||||
FailTimeout time.Duration
|
FailTimeout time.Duration
|
||||||
Unhealthy bool
|
Unhealthy bool
|
||||||
|
|
|
@ -3,6 +3,7 @@ package middleware
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -63,6 +64,14 @@ func NewReplacer(r *http.Request, rr *responseRecorder, emptyValue string) Repla
|
||||||
"{when}": func() string {
|
"{when}": func() string {
|
||||||
return time.Now().Format(timeFormat)
|
return time.Now().Format(timeFormat)
|
||||||
}(),
|
}(),
|
||||||
|
"{file}": func() string {
|
||||||
|
_, file := path.Split(r.URL.Path)
|
||||||
|
return file
|
||||||
|
}(),
|
||||||
|
"{dir}": func() string {
|
||||||
|
dir, _ := path.Split(r.URL.Path)
|
||||||
|
return dir
|
||||||
|
}(),
|
||||||
},
|
},
|
||||||
emptyValue: emptyValue,
|
emptyValue: emptyValue,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,8 @@
|
||||||
package rewrite
|
package rewrite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -96,15 +95,6 @@ func NewRegexpRule(base, pattern, to string, ext []string) (*RegexpRule, error)
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// regexpVars are variables that can be used for To (rewrite destination path).
|
|
||||||
var regexpVars = []string{
|
|
||||||
"{path}",
|
|
||||||
"{query}",
|
|
||||||
"{file}",
|
|
||||||
"{dir}",
|
|
||||||
"{frag}",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rewrite rewrites the internal location of the current request.
|
// Rewrite rewrites the internal location of the current request.
|
||||||
func (r *RegexpRule) Rewrite(req *http.Request) bool {
|
func (r *RegexpRule) Rewrite(req *http.Request) bool {
|
||||||
rPath := req.URL.Path
|
rPath := req.URL.Path
|
||||||
|
@ -119,32 +109,19 @@ func (r *RegexpRule) Rewrite(req *http.Request) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// include trailing slash in regexp if present
|
||||||
|
start := len(r.Base)
|
||||||
|
if strings.HasSuffix(r.Base, "/") {
|
||||||
|
start -= 1
|
||||||
|
}
|
||||||
|
|
||||||
// validate regexp
|
// validate regexp
|
||||||
if !r.MatchString(rPath[len(r.Base):]) {
|
if !r.MatchString(rPath[start:]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
to := r.To
|
// replace variables
|
||||||
|
to := path.Clean(middleware.NewReplacer(req, nil, "").Replace(r.To))
|
||||||
// check variables
|
|
||||||
for _, v := range regexpVars {
|
|
||||||
if strings.Contains(r.To, v) {
|
|
||||||
switch v {
|
|
||||||
case "{path}":
|
|
||||||
to = strings.Replace(to, v, req.URL.Path[1:], -1)
|
|
||||||
case "{query}":
|
|
||||||
to = strings.Replace(to, v, req.URL.RawQuery, -1)
|
|
||||||
case "{frag}":
|
|
||||||
to = strings.Replace(to, v, req.URL.Fragment, -1)
|
|
||||||
case "{file}":
|
|
||||||
_, file := path.Split(req.URL.Path)
|
|
||||||
to = strings.Replace(to, v, file, -1)
|
|
||||||
case "{dir}":
|
|
||||||
dir, _ := path.Split(req.URL.Path)
|
|
||||||
to = path.Clean(strings.Replace(to, v, dir, -1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate resulting path
|
// validate resulting path
|
||||||
url, err := url.Parse(to)
|
url, err := url.Parse(to)
|
||||||
|
|
|
@ -28,7 +28,7 @@ func TestRewrite(t *testing.T) {
|
||||||
[]string{"/ab/", "ab", "/ab?type=html&{query}", ".html|"},
|
[]string{"/ab/", "ab", "/ab?type=html&{query}", ".html|"},
|
||||||
[]string{"/abc/", "ab", "/abc/{file}", ".html|"},
|
[]string{"/abc/", "ab", "/abc/{file}", ".html|"},
|
||||||
[]string{"/abcd/", "ab", "/a/{dir}/{file}", ".html|"},
|
[]string{"/abcd/", "ab", "/a/{dir}/{file}", ".html|"},
|
||||||
[]string{"/abcde/", "ab", "/a#{frag}", ".html|"},
|
[]string{"/abcde/", "ab", "/a#{fragment}", ".html|"},
|
||||||
[]string{"/ab/", `.*\.jpg`, "/ajpg", ""},
|
[]string{"/ab/", `.*\.jpg`, "/ajpg", ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ type virtualHost struct {
|
||||||
// on its config. This method should be called last before
|
// on its config. This method should be called last before
|
||||||
// ListenAndServe begins.
|
// ListenAndServe begins.
|
||||||
func (vh *virtualHost) buildStack() error {
|
func (vh *virtualHost) buildStack() error {
|
||||||
vh.fileServer = FileServer(http.Dir(vh.config.Root), []string{vh.config.ConfigFile})
|
vh.fileServer = middleware.FileServer(http.Dir(vh.config.Root), []string{vh.config.ConfigFile})
|
||||||
|
|
||||||
// TODO: We only compile middleware for the "/" scope.
|
// TODO: We only compile middleware for the "/" scope.
|
||||||
// Partial support for multiple location contexts already
|
// Partial support for multiple location contexts already
|
||||||
|
|
Loading…
Reference in a new issue