mirror of
https://github.com/halverneus/static-file-server.git
synced 2024-11-24 09:05:30 +00:00
Added command-line utility packages and integrated.
This commit is contained in:
parent
755e0114f1
commit
055f1621cb
@ -1,182 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/halverneus/static-file-server/config"
|
"github.com/halverneus/static-file-server/cli"
|
||||||
"github.com/halverneus/static-file-server/handle"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
version = "Version 1.3"
|
|
||||||
|
|
||||||
help = `
|
|
||||||
NAME
|
|
||||||
static-file-server
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
static-file-server
|
|
||||||
static-file-server [ help | -help | --help ]
|
|
||||||
static-file-server [ version | -version | --version ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
The Static File Server is intended to be a tiny, fast and simple solution
|
|
||||||
for serving files over HTTP. The features included are limited to make to
|
|
||||||
binding to a host name and port, selecting a folder to serve, choosing a
|
|
||||||
URL path prefix and selecting TLS certificates. If you want really awesome
|
|
||||||
reverse proxy features, I recommend Nginx.
|
|
||||||
|
|
||||||
DEPENDENCIES
|
|
||||||
None... not even libc!
|
|
||||||
|
|
||||||
ENVIRONMENT VARIABLES
|
|
||||||
FOLDER
|
|
||||||
The path to the folder containing the contents to be served over
|
|
||||||
HTTP(s). If not supplied, defaults to '/web' (for Docker reasons).
|
|
||||||
HOST
|
|
||||||
The hostname used for binding. If not supplied, contents will be served
|
|
||||||
to a client without regard for the hostname.
|
|
||||||
PORT
|
|
||||||
The port used for binding. If not supplied, defaults to port '8080'.
|
|
||||||
SHOW_LISTING
|
|
||||||
Automatically serve the index file for the directory if requested. For
|
|
||||||
example, if the client requests 'http://127.0.0.1/' the 'index.html'
|
|
||||||
file in the root of the directory being served is returned. If the value
|
|
||||||
is set to 'false', the same request will return a 'NOT FOUND'. Default
|
|
||||||
value is 'true'.
|
|
||||||
TLS_CERT
|
|
||||||
Path to the TLS certificate file to serve files using HTTPS. If supplied
|
|
||||||
then TLS_KEY must also be supplied. If not supplied, contents will be
|
|
||||||
served via HTTP.
|
|
||||||
TLS_KEY
|
|
||||||
Path to the TLS key file to serve files using HTTPS. If supplied then
|
|
||||||
TLS_CERT must also be supplied. If not supplied, contents will be served
|
|
||||||
via HTTPS
|
|
||||||
URL_PREFIX
|
|
||||||
The prefix to use in the URL path. If supplied, then the prefix must
|
|
||||||
start with a forward-slash and NOT end with a forward-slash. If not
|
|
||||||
supplied then no prefix is used.
|
|
||||||
|
|
||||||
USAGE
|
|
||||||
FILE LAYOUT
|
|
||||||
/var/www/sub/my.file
|
|
||||||
/var/www/index.html
|
|
||||||
|
|
||||||
COMMAND
|
|
||||||
export FOLDER=/var/www/sub
|
|
||||||
static-file-server
|
|
||||||
Retrieve with: wget http://localhost:8080/my.file
|
|
||||||
wget http://my.machine:8080/my.file
|
|
||||||
|
|
||||||
export FOLDER=/var/www
|
|
||||||
export HOST=my.machine
|
|
||||||
export PORT=80
|
|
||||||
static-file-server
|
|
||||||
Retrieve with: wget http://my.machine/sub/my.file
|
|
||||||
|
|
||||||
export FOLDER=/var/www/sub
|
|
||||||
export HOST=my.machine
|
|
||||||
export PORT=80
|
|
||||||
export URL_PREFIX=/my/stuff
|
|
||||||
static-file-server
|
|
||||||
Retrieve with: wget http://my.machine/my/stuff/my.file
|
|
||||||
|
|
||||||
export FOLDER=/var/www/sub
|
|
||||||
export TLS_CERT=/etc/server/my.machine.crt
|
|
||||||
export TLS_KEY=/etc/server/my.machine.key
|
|
||||||
static-file-server
|
|
||||||
Retrieve with: wget https://my.machine:8080/my.file
|
|
||||||
|
|
||||||
export FOLDER=/var/www/sub
|
|
||||||
export PORT=443
|
|
||||||
export TLS_CERT=/etc/server/my.machine.crt
|
|
||||||
export TLS_KEY=/etc/server/my.machine.key
|
|
||||||
static-file-server
|
|
||||||
Retrieve with: wget https://my.machine/my.file
|
|
||||||
|
|
||||||
export FOLDER=/var/www
|
|
||||||
export PORT=80
|
|
||||||
export SHOW_LISTING=true # Default behavior
|
|
||||||
static-file-server
|
|
||||||
Retrieve 'index.html' with: wget http://my.machine/
|
|
||||||
|
|
||||||
export FOLDER=/var/www
|
|
||||||
export PORT=80
|
|
||||||
export SHOW_LISTING=false
|
|
||||||
static-file-server
|
|
||||||
Returns 'NOT FOUND': wget http://my.machine/
|
|
||||||
`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Evaluate and execute subcommand if supplied.
|
if err := cli.Execute(); nil != err {
|
||||||
if 1 < len(os.Args) {
|
log.Fatalf("Error: %v\n", err)
|
||||||
arg := os.Args[1]
|
|
||||||
switch {
|
|
||||||
case strings.Contains(arg, "help"):
|
|
||||||
fmt.Println(help)
|
|
||||||
case strings.Contains(arg, "version"):
|
|
||||||
fmt.Println(version)
|
|
||||||
default:
|
|
||||||
name := os.Args[0]
|
|
||||||
log.Fatalf("Unknown argument: %s. Try '%s help'.", arg, name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect environment variables.
|
|
||||||
if err := config.Load(""); nil != err {
|
|
||||||
log.Fatalf("While loading configuration got %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If HTTPS is to be used, verify both TLS_* environment variables are set.
|
|
||||||
if 0 < len(config.Get.TLSCert) || 0 < len(config.Get.TLSKey) {
|
|
||||||
if 0 == len(config.Get.TLSCert) || 0 == len(config.Get.TLSKey) {
|
|
||||||
log.Fatalln(
|
|
||||||
"If value for environment variable 'TLS_CERT' or 'TLS_KEY' is set " +
|
|
||||||
"then value for environment variable 'TLS_KEY' or 'TLS_CERT' must " +
|
|
||||||
"also be set.",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the URL path prefix is to be used, verify it is properly formatted.
|
|
||||||
if 0 < len(config.Get.URLPrefix) &&
|
|
||||||
(!strings.HasPrefix(config.Get.URLPrefix, "/") || strings.HasSuffix(config.Get.URLPrefix, "/")) {
|
|
||||||
log.Fatalln(
|
|
||||||
"Value for environment variable 'URL_PREFIX' must start " +
|
|
||||||
"with '/' and not end with '/'. Example: '/my/prefix'",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose and set the appropriate, optimized static file serving function.
|
|
||||||
var handler http.HandlerFunc
|
|
||||||
if 0 == len(config.Get.URLPrefix) {
|
|
||||||
handler = handle.Basic(config.Get.Folder)
|
|
||||||
} else {
|
|
||||||
handler = handle.Prefix(config.Get.Folder, config.Get.URLPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine whether index files should hidden.
|
|
||||||
if !config.Get.ShowListing {
|
|
||||||
handler = handle.IgnoreIndex(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve files over HTTP or HTTPS based on paths to TLS files being provided.
|
|
||||||
var listener handle.ListenerFunc
|
|
||||||
if 0 < len(config.Get.TLSCert) {
|
|
||||||
listener = handle.TLSListening(
|
|
||||||
config.Get.TLSCert,
|
|
||||||
config.Get.TLSKey,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
listener = handle.Listening()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding := fmt.Sprintf("%s:%d", config.Get.Host, config.Get.Port)
|
|
||||||
log.Fatalln(listener(binding, handler))
|
|
||||||
}
|
}
|
||||||
|
27
cli/args.go
Normal file
27
cli/args.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
// Args parsed from the command-line.
|
||||||
|
type Args []string
|
||||||
|
|
||||||
|
// Parse command-line arguments into Args. Value is returned to support daisy
|
||||||
|
// chaining.
|
||||||
|
func Parse(values []string) Args {
|
||||||
|
args := Args(values)
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches is used to determine if the arguments match the provided pattern.
|
||||||
|
func (args Args) Matches(pattern ...string) bool {
|
||||||
|
// If lengths don't match then nothing does.
|
||||||
|
if len(pattern) != len(args) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare slices using '*' as a wildcard.
|
||||||
|
for index, value := range pattern {
|
||||||
|
if "*" != value && value != args[index] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
81
cli/args_test.go
Normal file
81
cli/args_test.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
matches := func(args Args, orig []string) bool {
|
||||||
|
if nil == orig {
|
||||||
|
return nil == args
|
||||||
|
}
|
||||||
|
if len(orig) != len(args) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for index, value := range args {
|
||||||
|
if orig[index] != value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
value []string
|
||||||
|
}{
|
||||||
|
{"Nil arguments", nil},
|
||||||
|
{"No arguments", []string{}},
|
||||||
|
{"Arguments", []string{"first", "second", "*"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if args := Parse(tc.value); !matches(args, tc.value) {
|
||||||
|
t.Errorf("Expected [%v] but got [%v]", tc.value, args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatches(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
value []string
|
||||||
|
pattern []string
|
||||||
|
result bool
|
||||||
|
}{
|
||||||
|
{"Nil args and nil pattern", nil, nil, true},
|
||||||
|
{"No args and nil pattern", []string{}, nil, true},
|
||||||
|
{"Nil args and no pattern", nil, []string{}, true},
|
||||||
|
{"No args and no pattern", []string{}, []string{}, true},
|
||||||
|
{"Nil args and pattern", nil, []string{"test"}, false},
|
||||||
|
{"No args and pattern", []string{}, []string{"test"}, false},
|
||||||
|
{"Args and nil pattern", []string{"test"}, nil, false},
|
||||||
|
{"Args and no pattern", []string{"test"}, []string{}, false},
|
||||||
|
{"Simple single compare", []string{"test"}, []string{"test"}, true},
|
||||||
|
{"Simple double compare", []string{"one", "two"}, []string{"one", "two"}, true},
|
||||||
|
{"Bad single", []string{"one"}, []string{"two"}, false},
|
||||||
|
{"Bad double", []string{"one", "two"}, []string{"one", "owt"}, false},
|
||||||
|
{"Count mismatch", []string{"one", "two"}, []string{"one"}, false},
|
||||||
|
{"Nil args and wild", nil, []string{"*"}, false},
|
||||||
|
{"No args and wild", []string{}, []string{"*"}, false},
|
||||||
|
{"Single arg and wild", []string{"one"}, []string{"*"}, true},
|
||||||
|
{"Double arg and first wild", []string{"one", "two"}, []string{"*", "two"}, true},
|
||||||
|
{"Double arg and second wild", []string{"one", "two"}, []string{"one", "*"}, true},
|
||||||
|
{"Double arg and first wild mismatched", []string{"one", "two"}, []string{"*", "owt"}, false},
|
||||||
|
{"Double arg and second wild mismatched", []string{"one", "two"}, []string{"eno", "*"}, false},
|
||||||
|
{"Double arg and double wild", []string{"one", "two"}, []string{"*", "*"}, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
args := Parse(tc.value)
|
||||||
|
if resp := args.Matches(tc.pattern...); tc.result != resp {
|
||||||
|
msg := "For arguments [%v] matched to pattern [%v] expected " +
|
||||||
|
"%b but got %b"
|
||||||
|
t.Errorf(msg, tc.value, tc.pattern, tc.result, resp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
95
cli/execute.go
Normal file
95
cli/execute.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/halverneus/static-file-server/cli/help"
|
||||||
|
"github.com/halverneus/static-file-server/cli/server"
|
||||||
|
"github.com/halverneus/static-file-server/cli/version"
|
||||||
|
"github.com/halverneus/static-file-server/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
option struct {
|
||||||
|
configFile string
|
||||||
|
helpFlag bool
|
||||||
|
versionFlag bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assignments used to simplify testing.
|
||||||
|
var (
|
||||||
|
selectRoutine = selectionRoutine
|
||||||
|
unknownArgsFunc = unknownArgs
|
||||||
|
runServerFunc = server.Run
|
||||||
|
runHelpFunc = help.Run
|
||||||
|
runVersionFunc = version.Run
|
||||||
|
loadConfig = config.Load
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
setupFlags()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupFlags() {
|
||||||
|
flag.StringVar(&option.configFile, "config", "", "")
|
||||||
|
flag.StringVar(&option.configFile, "c", "", "")
|
||||||
|
flag.BoolVar(&option.helpFlag, "help", false, "")
|
||||||
|
flag.BoolVar(&option.helpFlag, "h", false, "")
|
||||||
|
flag.BoolVar(&option.versionFlag, "version", false, "")
|
||||||
|
flag.BoolVar(&option.versionFlag, "v", false, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute CLI arguments.
|
||||||
|
func Execute() (err error) {
|
||||||
|
// Parse flag options, then parse commands arguments.
|
||||||
|
flag.Parse()
|
||||||
|
args := Parse(flag.Args())
|
||||||
|
|
||||||
|
job := selectRoutine(args)
|
||||||
|
return job()
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectionRoutine(args Args) func() error {
|
||||||
|
switch {
|
||||||
|
|
||||||
|
// serve help
|
||||||
|
// serve --help
|
||||||
|
// serve -h
|
||||||
|
case args.Matches("help") || option.helpFlag:
|
||||||
|
return runHelpFunc
|
||||||
|
|
||||||
|
// serve version
|
||||||
|
// serve --version
|
||||||
|
// serve -v
|
||||||
|
case args.Matches("version") || option.versionFlag:
|
||||||
|
return runVersionFunc
|
||||||
|
|
||||||
|
// serve
|
||||||
|
case args.Matches():
|
||||||
|
return withConfig(runServerFunc)
|
||||||
|
|
||||||
|
// Unknown arguments.
|
||||||
|
default:
|
||||||
|
return unknownArgsFunc(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unknownArgs(args Args) func() error {
|
||||||
|
return func() error {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"unknown arguments provided [%v], try: 'help'",
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withConfig(routine func() error) func() error {
|
||||||
|
return func() (err error) {
|
||||||
|
if err = loadConfig(option.configFile); nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return routine()
|
||||||
|
}
|
||||||
|
}
|
162
cli/execute_test.go
Normal file
162
cli/execute_test.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetupFlags(t *testing.T) {
|
||||||
|
app := os.Args[0]
|
||||||
|
|
||||||
|
file := "file.txt"
|
||||||
|
wConfig := "Config (file.txt)"
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
config string
|
||||||
|
help bool
|
||||||
|
version bool
|
||||||
|
}{
|
||||||
|
{"Empty args", []string{app}, "", false, false},
|
||||||
|
{"Help (--help)", []string{app, "--help"}, "", true, false},
|
||||||
|
{"Help (-help)", []string{app, "-help"}, "", true, false},
|
||||||
|
{"Help (-h)", []string{app, "-h"}, "", true, false},
|
||||||
|
{"Version (--version)", []string{app, "--version"}, "", false, true},
|
||||||
|
{"Version (-version)", []string{app, "-version"}, "", false, true},
|
||||||
|
{"Version (-v)", []string{app, "-v"}, "", false, true},
|
||||||
|
{"Config ()", []string{app, "--config", ""}, "", false, false},
|
||||||
|
{wConfig, []string{app, "--config", file}, file, false, false},
|
||||||
|
{wConfig, []string{app, "--config=file.txt"}, file, false, false},
|
||||||
|
{wConfig, []string{app, "-config", file}, file, false, false},
|
||||||
|
{wConfig, []string{app, "-config=file.txt"}, file, false, false},
|
||||||
|
{wConfig, []string{app, "-c", file}, file, false, false},
|
||||||
|
{"All set", []string{app, "-h", "-v", "-c", file}, file, true, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
reset := func() {
|
||||||
|
option.configFile = ""
|
||||||
|
option.helpFlag = false
|
||||||
|
option.versionFlag = false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
reset()
|
||||||
|
os.Args = tc.args
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if option.configFile != tc.config {
|
||||||
|
t.Errorf(
|
||||||
|
"For options [%v] expected a config file of %s but got %s",
|
||||||
|
tc.args, tc.config, option.configFile,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if option.helpFlag != tc.help {
|
||||||
|
t.Errorf(
|
||||||
|
"For options [%v] expected help flag of %t but got %t",
|
||||||
|
tc.args, tc.help, option.helpFlag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if option.versionFlag != tc.version {
|
||||||
|
t.Errorf(
|
||||||
|
"For options [%v] expected version flag of %t but got %t",
|
||||||
|
tc.args, tc.version, option.versionFlag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecuteAndSelection(t *testing.T) {
|
||||||
|
app := os.Args[0]
|
||||||
|
|
||||||
|
runHelpFuncError := errors.New("help")
|
||||||
|
runHelpFunc = func() error {
|
||||||
|
return runHelpFuncError
|
||||||
|
}
|
||||||
|
runVersionFuncError := errors.New("version")
|
||||||
|
runVersionFunc = func() error {
|
||||||
|
return runVersionFuncError
|
||||||
|
}
|
||||||
|
runServerFuncError := errors.New("server")
|
||||||
|
runServerFunc = func() error {
|
||||||
|
return runServerFuncError
|
||||||
|
}
|
||||||
|
unknownArgsFuncError := errors.New("unknown")
|
||||||
|
unknownArgsFunc = func(Args) func() error {
|
||||||
|
return func() error {
|
||||||
|
return unknownArgsFuncError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset := func() {
|
||||||
|
option.configFile = ""
|
||||||
|
option.helpFlag = false
|
||||||
|
option.versionFlag = false
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
result error
|
||||||
|
}{
|
||||||
|
{"Help", []string{app, "help"}, runHelpFuncError},
|
||||||
|
{"Help", []string{app, "--help"}, runHelpFuncError},
|
||||||
|
{"Version", []string{app, "version"}, runVersionFuncError},
|
||||||
|
{"Version", []string{app, "--version"}, runVersionFuncError},
|
||||||
|
{"Serve", []string{app}, runServerFuncError},
|
||||||
|
{"Unknown", []string{app, "unknown"}, unknownArgsFuncError},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
reset()
|
||||||
|
os.Args = tc.args
|
||||||
|
|
||||||
|
if err := Execute(); tc.result != err {
|
||||||
|
t.Errorf(
|
||||||
|
"Expected error for %v but got %v",
|
||||||
|
tc.result, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnknownArgs(t *testing.T) {
|
||||||
|
errFunc := unknownArgs(Args{"unknown"})
|
||||||
|
if err := errFunc(); nil == err {
|
||||||
|
t.Errorf(
|
||||||
|
"Expected a given unknown argument error but got %v",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithConfig(t *testing.T) {
|
||||||
|
configError := errors.New("config")
|
||||||
|
routineError := errors.New("routine")
|
||||||
|
routine := func() error { return routineError }
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
loadConfig func(string) error
|
||||||
|
result error
|
||||||
|
}{
|
||||||
|
{"Config error", func(string) error { return configError }, configError},
|
||||||
|
{"Routine error", func(string) error { return nil }, routineError},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
loadConfig = tc.loadConfig
|
||||||
|
errFunc := withConfig(routine)
|
||||||
|
if err := errFunc(); tc.result != err {
|
||||||
|
t.Errorf("Expected error %v but got %v", tc.result, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
111
cli/help/help.go
Normal file
111
cli/help/help.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package help
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run print operation.
|
||||||
|
func Run() error {
|
||||||
|
fmt.Println(Text)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Text for directly accessing help.
|
||||||
|
Text = `
|
||||||
|
NAME
|
||||||
|
static-file-server
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
static-file-server
|
||||||
|
static-file-server [ help | -help | --help ]
|
||||||
|
static-file-server [ version | -version | --version ]
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
The Static File Server is intended to be a tiny, fast and simple solution
|
||||||
|
for serving files over HTTP. The features included are limited to make to
|
||||||
|
binding to a host name and port, selecting a folder to serve, choosing a
|
||||||
|
URL path prefix and selecting TLS certificates. If you want really awesome
|
||||||
|
reverse proxy features, I recommend Nginx.
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
None... not even libc!
|
||||||
|
|
||||||
|
ENVIRONMENT VARIABLES
|
||||||
|
FOLDER
|
||||||
|
The path to the folder containing the contents to be served over
|
||||||
|
HTTP(s). If not supplied, defaults to '/web' (for Docker reasons).
|
||||||
|
HOST
|
||||||
|
The hostname used for binding. If not supplied, contents will be served
|
||||||
|
to a client without regard for the hostname.
|
||||||
|
PORT
|
||||||
|
The port used for binding. If not supplied, defaults to port '8080'.
|
||||||
|
SHOW_LISTING
|
||||||
|
Automatically serve the index file for the directory if requested. For
|
||||||
|
example, if the client requests 'http://127.0.0.1/' the 'index.html'
|
||||||
|
file in the root of the directory being served is returned. If the value
|
||||||
|
is set to 'false', the same request will return a 'NOT FOUND'. Default
|
||||||
|
value is 'true'.
|
||||||
|
TLS_CERT
|
||||||
|
Path to the TLS certificate file to serve files using HTTPS. If supplied
|
||||||
|
then TLS_KEY must also be supplied. If not supplied, contents will be
|
||||||
|
served via HTTP.
|
||||||
|
TLS_KEY
|
||||||
|
Path to the TLS key file to serve files using HTTPS. If supplied then
|
||||||
|
TLS_CERT must also be supplied. If not supplied, contents will be served
|
||||||
|
via HTTPS
|
||||||
|
URL_PREFIX
|
||||||
|
The prefix to use in the URL path. If supplied, then the prefix must
|
||||||
|
start with a forward-slash and NOT end with a forward-slash. If not
|
||||||
|
supplied then no prefix is used.
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
FILE LAYOUT
|
||||||
|
/var/www/sub/my.file
|
||||||
|
/var/www/index.html
|
||||||
|
|
||||||
|
COMMAND
|
||||||
|
export FOLDER=/var/www/sub
|
||||||
|
static-file-server
|
||||||
|
Retrieve with: wget http://localhost:8080/my.file
|
||||||
|
wget http://my.machine:8080/my.file
|
||||||
|
|
||||||
|
export FOLDER=/var/www
|
||||||
|
export HOST=my.machine
|
||||||
|
export PORT=80
|
||||||
|
static-file-server
|
||||||
|
Retrieve with: wget http://my.machine/sub/my.file
|
||||||
|
|
||||||
|
export FOLDER=/var/www/sub
|
||||||
|
export HOST=my.machine
|
||||||
|
export PORT=80
|
||||||
|
export URL_PREFIX=/my/stuff
|
||||||
|
static-file-server
|
||||||
|
Retrieve with: wget http://my.machine/my/stuff/my.file
|
||||||
|
|
||||||
|
export FOLDER=/var/www/sub
|
||||||
|
export TLS_CERT=/etc/server/my.machine.crt
|
||||||
|
export TLS_KEY=/etc/server/my.machine.key
|
||||||
|
static-file-server
|
||||||
|
Retrieve with: wget https://my.machine:8080/my.file
|
||||||
|
|
||||||
|
export FOLDER=/var/www/sub
|
||||||
|
export PORT=443
|
||||||
|
export TLS_CERT=/etc/server/my.machine.crt
|
||||||
|
export TLS_KEY=/etc/server/my.machine.key
|
||||||
|
static-file-server
|
||||||
|
Retrieve with: wget https://my.machine/my.file
|
||||||
|
|
||||||
|
export FOLDER=/var/www
|
||||||
|
export PORT=80
|
||||||
|
export SHOW_LISTING=true # Default behavior
|
||||||
|
static-file-server
|
||||||
|
Retrieve 'index.html' with: wget http://my.machine/
|
||||||
|
|
||||||
|
export FOLDER=/var/www
|
||||||
|
export PORT=80
|
||||||
|
export SHOW_LISTING=false
|
||||||
|
static-file-server
|
||||||
|
Returns 'NOT FOUND': wget http://my.machine/
|
||||||
|
`
|
||||||
|
)
|
9
cli/help/help_test.go
Normal file
9
cli/help/help_test.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package help
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestRun(t *testing.T) {
|
||||||
|
if err := Run(); nil != err {
|
||||||
|
t.Errorf("While running help got %v", err)
|
||||||
|
}
|
||||||
|
}
|
39
cli/server/server.go
Normal file
39
cli/server/server.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/halverneus/static-file-server/config"
|
||||||
|
"github.com/halverneus/static-file-server/handle"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run server.
|
||||||
|
func Run() error {
|
||||||
|
// Choose and set the appropriate, optimized static file serving function.
|
||||||
|
var handler http.HandlerFunc
|
||||||
|
if 0 == len(config.Get.URLPrefix) {
|
||||||
|
handler = handle.Basic(config.Get.Folder)
|
||||||
|
} else {
|
||||||
|
handler = handle.Prefix(config.Get.Folder, config.Get.URLPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether index files should hidden.
|
||||||
|
if !config.Get.ShowListing {
|
||||||
|
handler = handle.IgnoreIndex(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve files over HTTP or HTTPS based on paths to TLS files being provided.
|
||||||
|
var listener handle.ListenerFunc
|
||||||
|
if 0 < len(config.Get.TLSCert) {
|
||||||
|
listener = handle.TLSListening(
|
||||||
|
config.Get.TLSCert,
|
||||||
|
config.Get.TLSKey,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
listener = handle.Listening()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding := fmt.Sprintf("%s:%d", config.Get.Host, config.Get.Port)
|
||||||
|
return listener(binding, handler)
|
||||||
|
}
|
35
cli/version/version.go
Normal file
35
cli/version/version.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run print operation.
|
||||||
|
func Run() error {
|
||||||
|
fmt.Printf("%s\n%s\n", Text, GoVersionText)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// MajorVersion of static-file-server.
|
||||||
|
MajorVersion = 1
|
||||||
|
|
||||||
|
// MinorVersion of static-file-server.
|
||||||
|
MinorVersion = 3
|
||||||
|
|
||||||
|
// FixVersion of static-file-server.
|
||||||
|
FixVersion = 0
|
||||||
|
|
||||||
|
// Text for directly accessing the static-file-server version.
|
||||||
|
Text = fmt.Sprintf(
|
||||||
|
"Version %d.%d.%d",
|
||||||
|
MajorVersion,
|
||||||
|
MinorVersion,
|
||||||
|
FixVersion,
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoVersionText for directly accessing the version of the Go runtime
|
||||||
|
// compiled with the static-file-server.
|
||||||
|
GoVersionText = runtime.Version()
|
||||||
|
)
|
9
cli/version/version_test.go
Normal file
9
cli/version/version_test.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestVersion(t *testing.T) {
|
||||||
|
if err := Run(); nil != err {
|
||||||
|
t.Errorf("While running version got %v", err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user