Added the ability to enable debug logging.

This commit is contained in:
Jeromy Streets 2018-10-18 13:45:43 -07:00
parent 053077e1b2
commit f2f41ef96a
9 changed files with 189 additions and 120 deletions

View File

@ -8,6 +8,9 @@ Available on GitHub at https://github.com/halverneus/static-file-server
Environment variables with defaults:
```bash
# Enable debugging for troubleshooting. If set to 'true' this prints extra
# information during execution. Default value is 'false'.
DEBUG=false
# Optional Hostname for binding. Leave black to accept any incoming HTTP request
# on the prescribed port.
HOST=
@ -34,6 +37,7 @@ configuration file with defaults. Pass in the path to the configuration file
using the command line option ('-c', '-config', '--config').
```yaml
debug: false
host: ""
port: 8080
show-listing: true

View File

@ -33,6 +33,10 @@ DEPENDENCIES
None... not even libc!
ENVIRONMENT VARIABLES
DEBUG
When set to 'true' enables additional logging, including the
configuration used and an access log for each request. Default value is
'false'.
FOLDER
The path to the folder containing the contents to be served over
HTTP(s). If not supplied, defaults to '/web' (for Docker reasons).
@ -69,6 +73,7 @@ CONFIGURATION FILE
Example config.yml with defaults:
----------------------------------------------------------------------------
debug: false
folder: /web
host: ""
port: 8080

View File

@ -16,6 +16,9 @@ var (
// Run server.
func Run() error {
if config.Get.Debug {
config.Log()
}
// Choose and set the appropriate, optimized static file serving function.
handler := selectHandler()
@ -30,11 +33,21 @@ func Run() error {
// handlerSelector returns the appropriate request handler based on
// configuration.
func handlerSelector() (handler http.HandlerFunc) {
var serveFileHandler handle.FileServerFunc
serveFileHandler = http.ServeFile
if config.Get.Debug {
serveFileHandler = handle.WithLogging(serveFileHandler)
}
// Choose and set the appropriate, optimized static file serving function.
if 0 == len(config.Get.URLPrefix) {
handler = handle.Basic(config.Get.Folder)
handler = handle.Basic(serveFileHandler, config.Get.Folder)
} else {
handler = handle.Prefix(config.Get.Folder, config.Get.URLPrefix)
handler = handle.Prefix(
serveFileHandler,
config.Get.Folder,
config.Get.URLPrefix,
)
}
// Determine whether index files should hidden.

View File

@ -17,8 +17,14 @@ func TestRun(t *testing.T) {
}
}
config.Get.Debug = false
if err := Run(); listenerError != err {
t.Errorf("Expected %v but got %v", listenerError, err)
t.Errorf("Without debug expected %v but got %v", listenerError, err)
}
config.Get.Debug = true
if err := Run(); listenerError != err {
t.Errorf("With debug expected %v but got %v", listenerError, err)
}
}
@ -32,18 +38,24 @@ func TestHandlerSelector(t *testing.T) {
folder string
prefix string
listing bool
debug bool
}{
{"Basic handler", testFolder, "", true},
{"Prefix handler", testFolder, testPrefix, true},
{"Basic and hide listing handler", testFolder, "", false},
{"Prefix and hide listing handler", testFolder, testPrefix, false},
{"Basic handler w/o debug", testFolder, "", true, false},
{"Prefix handler w/o debug", testFolder, testPrefix, true, false},
{"Basic and hide listing handler w/o debug", testFolder, "", false, false},
{"Prefix and hide listing handler w/o debug", testFolder, testPrefix, false, false},
{"Basic handler w/debug", testFolder, "", true, true},
{"Prefix handler w/debug", testFolder, testPrefix, true, true},
{"Basic and hide listing handler w/debug", testFolder, "", false, true},
{"Prefix and hide listing handler w/debug", testFolder, testPrefix, false, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
config.Get.Debug = tc.debug
config.Get.Folder = tc.folder
config.Get.URLPrefix = tc.prefix
config.Get.ShowListing = tc.listing
config.Get.URLPrefix = tc.prefix
handlerSelector()
})

View File

@ -16,10 +16,10 @@ var (
MajorVersion = 1
// MinorVersion of static-file-server.
MinorVersion = 3
MinorVersion = 4
// FixVersion of static-file-server.
FixVersion = 3
FixVersion = 0
// Text for directly accessing the static-file-server version.
Text = fmt.Sprintf(

View File

@ -14,6 +14,7 @@ import (
var (
// Get the desired configuration value.
Get struct {
Debug bool `yaml:"debug"`
Folder string `yaml:"folder"`
Host string `yaml:"host"`
Port uint16 `yaml:"port"`
@ -25,6 +26,7 @@ var (
)
const (
debugKey = "DEBUG"
folderKey = "FOLDER"
hostKey = "HOST"
portKey = "PORT"
@ -35,6 +37,7 @@ const (
)
const (
defaultDebug = false
defaultFolder = "/web"
defaultHost = ""
defaultPort = uint16(8080)
@ -50,6 +53,7 @@ func init() {
}
func setDefaults() {
Get.Debug = defaultDebug
Get.Folder = defaultFolder
Get.Host = defaultHost
Get.Port = defaultPort
@ -82,9 +86,21 @@ func Load(filename string) (err error) {
return validate()
}
// Log the current configuration.
func Log() {
// YAML marshalling should never error, but if it could, the result is that
// the contents of the configuration are not logged.
contents, _ := yaml.Marshal(&Get)
// Log the configuration.
fmt.Println("Using the following configuration:")
fmt.Println(string(contents))
}
// overrideWithEnvVars the default values and the configuration file values.
func overrideWithEnvVars() {
// Assign envvars, if set.
Get.Debug = envAsBool(debugKey, Get.Debug)
Get.Folder = envAsStr(folderKey, Get.Folder)
Get.Host = envAsStr(hostKey, Get.Host)
Get.Port = envAsUint16(portKey, Get.Port)

View File

@ -6,6 +6,8 @@ import (
"os"
"strconv"
"testing"
"gopkg.in/yaml.v2"
)
func TestLoad(t *testing.T) {
@ -65,8 +67,17 @@ func TestLoad(t *testing.T) {
}(t)
}
func TestLog(t *testing.T) {
// Test whether YAML marshalling works, as that is the only error case.
if _, err := yaml.Marshal(&Get); nil != err {
t.Errorf("While testing YAML marshalling for config Log() got %v", err)
}
Log()
}
func TestOverrideWithEnvvars(t *testing.T) {
// Choose values that are different than defaults.
testDebug := true
testFolder := "/my/directory"
testHost := "apets.life"
testPort := uint16(666)
@ -76,6 +87,7 @@ func TestOverrideWithEnvvars(t *testing.T) {
testURLPrefix := "/url/prefix"
// Set all environment variables with test values.
os.Setenv(debugKey, fmt.Sprintf("%t", testDebug))
os.Setenv(folderKey, testFolder)
os.Setenv(hostKey, testHost)
os.Setenv(portKey, strconv.Itoa(int(testPort)))
@ -113,6 +125,7 @@ func TestOverrideWithEnvvars(t *testing.T) {
// Verify defaults.
setDefaults()
phase := "defaults"
equalBool(t, phase, debugKey, defaultDebug, Get.Debug)
equalStrings(t, phase, folderKey, defaultFolder, Get.Folder)
equalStrings(t, phase, hostKey, defaultHost, Get.Host)
equalUint16(t, phase, portKey, defaultPort, Get.Port)
@ -126,6 +139,7 @@ func TestOverrideWithEnvvars(t *testing.T) {
// Verify overrides.
phase = "overrides"
equalBool(t, phase, debugKey, testDebug, Get.Debug)
equalStrings(t, phase, folderKey, testFolder, Get.Folder)
equalStrings(t, phase, hostKey, testHost, Get.Host)
equalUint16(t, phase, portKey, testPort, Get.Port)

View File

@ -1,6 +1,7 @@
package handle
import (
"log"
"net/http"
"strings"
)
@ -21,23 +22,43 @@ var (
// occur.
type ListenerFunc func(string, http.HandlerFunc) error
// FileServerFunc is used to serve the file from the local file system to the
// requesting client.
type FileServerFunc func(http.ResponseWriter, *http.Request, string)
// WithLogging returns a function that logs information about the request prior
// to serving the requested file.
func WithLogging(serveFile FileServerFunc) FileServerFunc {
return func(w http.ResponseWriter, r *http.Request, name string) {
log.Printf(
"REQ: %s %s %s%s -> %s\n",
r.Method,
r.Proto,
r.Host,
r.URL.Path,
name,
)
serveFile(w, r, name)
}
}
// Basic file handler servers files from the passed folder.
func Basic(folder string) http.HandlerFunc {
func Basic(serveFile FileServerFunc, folder string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, folder+r.URL.Path)
serveFile(w, r, folder+r.URL.Path)
}
}
// Prefix file handler is an alternative to Basic where a URL prefix is removed
// prior to serving a file (http://my.machine/prefix/file.txt will serve
// file.txt from the root of the folder being served (ignoring 'prefix')).
func Prefix(folder, urlPrefix string) http.HandlerFunc {
func Prefix(serveFile FileServerFunc, folder, urlPrefix string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, urlPrefix) {
http.NotFound(w, r)
return
}
http.ServeFile(w, r, folder+strings.TrimPrefix(r.URL.Path, urlPrefix))
serveFile(w, r, folder+strings.TrimPrefix(r.URL.Path, urlPrefix))
}
}

View File

@ -46,6 +46,11 @@ var (
baseDir + tmpSubDeepIndexName: tmpSubDeepIndex,
baseDir + tmpSubDeepFileName: tmpSubDeepFile,
}
serveFileFuncs = []FileServerFunc{
http.ServeFile,
WithLogging(http.ServeFile),
}
)
func TestMain(m *testing.M) {
@ -79,7 +84,7 @@ func teardown() (err error) {
return os.RemoveAll("tmp")
}
func TestBasic(t *testing.T) {
func TestBasicWithAndWithoutLogging(t *testing.T) {
testCases := []struct {
name string
path string
@ -95,34 +100,36 @@ func TestBasic(t *testing.T) {
{"Good subdir file", tmpSubFileName, ok, tmpSubFile},
}
handler := Basic(baseDir)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fullpath := "http://localhost/" + tc.path
req := httptest.NewRequest("GET", fullpath, nil)
w := httptest.NewRecorder()
for _, serveFile := range serveFileFuncs {
handler := Basic(serveFile, baseDir)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fullpath := "http://localhost/" + tc.path
req := httptest.NewRequest("GET", fullpath, nil)
w := httptest.NewRecorder()
handler(w, req)
handler(w, req)
resp := w.Result()
body, err := ioutil.ReadAll(resp.Body)
if nil != err {
t.Errorf("While reading body got %v", err)
}
contents := string(body)
if tc.code != resp.StatusCode {
t.Errorf(
"While retrieving %s expected status code of %d but got %d",
fullpath, tc.code, resp.StatusCode,
)
}
if tc.contents != contents {
t.Errorf(
"While retrieving %s expected contents '%s' but got '%s'",
fullpath, tc.contents, contents,
)
}
})
resp := w.Result()
body, err := ioutil.ReadAll(resp.Body)
if nil != err {
t.Errorf("While reading body got %v", err)
}
contents := string(body)
if tc.code != resp.StatusCode {
t.Errorf(
"While retrieving %s expected status code of %d but got %d",
fullpath, tc.code, resp.StatusCode,
)
}
if tc.contents != contents {
t.Errorf(
"While retrieving %s expected contents '%s' but got '%s'",
fullpath, tc.contents, contents,
)
}
})
}
}
}
@ -145,34 +152,36 @@ func TestPrefix(t *testing.T) {
{"Unknown prefix", tmpFileName, missing, notFound},
}
handler := Prefix(baseDir, prefix)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fullpath := "http://localhost" + tc.path
req := httptest.NewRequest("GET", fullpath, nil)
w := httptest.NewRecorder()
for _, serveFile := range serveFileFuncs {
handler := Prefix(serveFile, baseDir, prefix)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fullpath := "http://localhost" + tc.path
req := httptest.NewRequest("GET", fullpath, nil)
w := httptest.NewRecorder()
handler(w, req)
handler(w, req)
resp := w.Result()
body, err := ioutil.ReadAll(resp.Body)
if nil != err {
t.Errorf("While reading body got %v", err)
}
contents := string(body)
if tc.code != resp.StatusCode {
t.Errorf(
"While retrieving %s expected status code of %d but got %d",
fullpath, tc.code, resp.StatusCode,
)
}
if tc.contents != contents {
t.Errorf(
"While retrieving %s expected contents '%s' but got '%s'",
fullpath, tc.contents, contents,
)
}
})
resp := w.Result()
body, err := ioutil.ReadAll(resp.Body)
if nil != err {
t.Errorf("While reading body got %v", err)
}
contents := string(body)
if tc.code != resp.StatusCode {
t.Errorf(
"While retrieving %s expected status code of %d but got %d",
fullpath, tc.code, resp.StatusCode,
)
}
if tc.contents != contents {
t.Errorf(
"While retrieving %s expected contents '%s' but got '%s'",
fullpath, tc.contents, contents,
)
}
})
}
}
}
@ -192,64 +201,39 @@ func TestIgnoreIndex(t *testing.T) {
{"Good subdir file", tmpSubFileName, ok, tmpSubFile},
}
handler := IgnoreIndex(Basic(baseDir))
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fullpath := "http://localhost/" + tc.path
req := httptest.NewRequest("GET", fullpath, nil)
w := httptest.NewRecorder()
for _, serveFile := range serveFileFuncs {
handler := IgnoreIndex(Basic(serveFile, baseDir))
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fullpath := "http://localhost/" + tc.path
req := httptest.NewRequest("GET", fullpath, nil)
w := httptest.NewRecorder()
handler(w, req)
handler(w, req)
resp := w.Result()
body, err := ioutil.ReadAll(resp.Body)
if nil != err {
t.Errorf("While reading body got %v", err)
}
contents := string(body)
if tc.code != resp.StatusCode {
t.Errorf(
"While retrieving %s expected status code of %d but got %d",
fullpath, tc.code, resp.StatusCode,
)
}
if tc.contents != contents {
t.Errorf(
"While retrieving %s expected contents '%s' but got '%s'",
fullpath, tc.contents, contents,
)
}
})
resp := w.Result()
body, err := ioutil.ReadAll(resp.Body)
if nil != err {
t.Errorf("While reading body got %v", err)
}
contents := string(body)
if tc.code != resp.StatusCode {
t.Errorf(
"While retrieving %s expected status code of %d but got %d",
fullpath, tc.code, resp.StatusCode,
)
}
if tc.contents != contents {
t.Errorf(
"While retrieving %s expected contents '%s' but got '%s'",
fullpath, tc.contents, contents,
)
}
})
}
}
}
// func TestIgnoreIndex(t *testing.T) {
// handler := IgnoreIndex(Basic("tmp"))
// testCases := []struct {
// name string
// path string
// code int
// contents string
// }{}
// // Build test cases for directories.
// var dirs []string
// for filename, contents := range files {
// dir := path.Dir(filename)
// found := false
// for _, other := range dirs {
// if other == dir {
// found = true
// break
// }
// }
// if !found {
// dirs = append(dirs, dir)
// }
// }
// }
func TestListening(t *testing.T) {
// Choose values for testing.
called := false