Commit 8baead61 authored by Mathias Beke's avatar Mathias Beke

Merge remote-tracking branch 'upstream/master'

parents aa5a5957 4f5a29d6
...@@ -17,8 +17,9 @@ func Browse(c *Controller) (middleware.Middleware, error) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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 ( ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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)
......
...@@ -3,6 +3,12 @@ CHANGES ...@@ -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 ( ...@@ -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 { ...@@ -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 { ...@@ -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 ...@@ -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) { ...@@ -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 { ...@@ -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 ( ...@@ -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 ...@@ -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 ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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)
......
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 ...@@ -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) { ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 @@ ...@@ -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) ...@@ -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 { ...@@ -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) { ...@@ -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 { ...@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment