From f2f41ef96af7aa58efa1a8318720d6d50ab56e89 Mon Sep 17 00:00:00 2001 From: Jeromy Streets Date: Thu, 18 Oct 2018 13:45:43 -0700 Subject: [PATCH] Added the ability to enable debug logging. --- README.md | 4 + cli/help/help.go | 5 + cli/server/server.go | 17 +++- cli/server/server_test.go | 24 +++-- cli/version/version.go | 4 +- config/config.go | 16 +++ config/config_test.go | 14 +++ handle/handle.go | 29 +++++- handle/handle_test.go | 208 ++++++++++++++++++-------------------- 9 files changed, 195 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index 1d0ac09..189a9e7 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cli/help/help.go b/cli/help/help.go index 9a00cd1..3d6504f 100644 --- a/cli/help/help.go +++ b/cli/help/help.go @@ -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 diff --git a/cli/server/server.go b/cli/server/server.go index a8b9d82..0d12604 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -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. diff --git a/cli/server/server_test.go b/cli/server/server_test.go index 31b151a..a4ca8fb 100644 --- a/cli/server/server_test.go +++ b/cli/server/server_test.go @@ -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() }) diff --git a/cli/version/version.go b/cli/version/version.go index dd3eb30..426a505 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -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( diff --git a/config/config.go b/config/config.go index 4e970bc..0a6eea7 100644 --- a/config/config.go +++ b/config/config.go @@ -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) diff --git a/config/config_test.go b/config/config_test.go index 1aa9e24..0209384 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -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) diff --git a/handle/handle.go b/handle/handle.go index ed04c44..17ca3bc 100644 --- a/handle/handle.go +++ b/handle/handle.go @@ -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)) } } diff --git a/handle/handle_test.go b/handle/handle_test.go index 20df1f3..85a4f09 100644 --- a/handle/handle_test.go +++ b/handle/handle_test.go @@ -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() - - 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, - ) - } - }) + 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) + + 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() - - 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, - ) - } - }) + 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) + + 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() - - 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, - ) - } - }) + 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) + + 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