diff --git a/README.md b/README.md index cb15effa..7fe45ff0 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ See also: [Classic Watchdog](https://github.com/openfaas/faas/tree/master/watchd History/context: the original watchdog supported mode the Serializing fork mode only and Afterburn was available for testing via a pull request. -When the of-watchdog is complete this version will support four modes as listed below. We may consolidate or remove some of these modes before going to 1.0 so please consider modes 2-4 experimental. +When the of-watchdog is complete this version will support five modes as listed below. We may consolidate or remove some of these modes before going to 1.0 so please consider modes 2-4 experimental. ### 1. HTTP (mode=http) @@ -143,6 +143,10 @@ Vastly accelerated processing speed but requires a client library for each langu * Exec timeout: not supported. +### 5. Static (mode=static) + +This mode starts an HTTP file server for serving static content found at the directory specified by `static_path`. + ## Configuration Environmental variables: @@ -152,6 +156,7 @@ Environmental variables: | Option | Implemented | Usage | |------------------------|--------------|-------------------------------| | `function_process` | Yes | Process to execute a server in `http` mode or to be executed for each request in the other modes. For non `http` mode the process must accept input via STDIN and print output via STDOUT. Alias: `fprocess` | +| `static_path` | Yes | Absolute or relative path to the directory that will be served if `mode="static"` | | `read_timeout` | Yes | HTTP timeout for reading the payload from the client caller (in seconds) | | `write_timeout` | Yes | HTTP timeout for writing a response body from your function (in seconds) | | `exec_timeout` | Yes | Exec timeout for process exec'd for each incoming request (in seconds). Disabled if set to 0. | diff --git a/config/config.go b/config/config.go index 82165ea5..f035315a 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,7 @@ type WatchdogConfig struct { OperationalMode int SuppressLock bool UpstreamURL string + StaticPath string // BufferHTTPBody buffers the HTTP body in memory // to prevent transfer type of chunked encoding @@ -48,7 +49,7 @@ func (w WatchdogConfig) Process() (string, []string) { } // New create config based upon environmental variables. -func New(env []string) (WatchdogConfig, error) { +func New(env []string) WatchdogConfig { envMap := mapEnv(env) @@ -74,11 +75,17 @@ func New(env []string) (WatchdogConfig, error) { contentType = val } + staticPath := "/home/app/public" + if val, exists := envMap["static_path"]; exists { + staticPath = val + } + config := WatchdogConfig{ TCPPort: getInt(envMap, "port", 8080), HTTPReadTimeout: getDuration(envMap, "read_timeout", time.Second*10), HTTPWriteTimeout: getDuration(envMap, "write_timeout", time.Second*10), FunctionProcess: functionProcess, + StaticPath: staticPath, InjectCGIHeaders: true, ExecTimeout: getDuration(envMap, "exec_timeout", time.Second*10), OperationalMode: ModeStreaming, @@ -94,7 +101,7 @@ func New(env []string) (WatchdogConfig, error) { config.OperationalMode = WatchdogModeConst(val) } - return config, nil + return config } func mapEnv(env []string) map[string]string { diff --git a/config/config_modes.go b/config/config_modes.go index 4fa3cb87..dc8a636b 100644 --- a/config/config_modes.go +++ b/config/config_modes.go @@ -10,8 +10,11 @@ const ( // ModeAfterBurn for performance tuning ModeAfterBurn = 3 - //ModeHTTP for routing requests over HTTP + // ModeHTTP for routing requests over HTTP ModeHTTP = 4 + + // ModeStatic for serving static content + ModeStatic = 5 ) // WatchdogModeConst as a const int @@ -25,6 +28,8 @@ func WatchdogModeConst(mode string) int { return ModeSerializing case "http": return ModeHTTP + case "static": + return ModeStatic default: return 0 } @@ -41,6 +46,8 @@ func WatchdogMode(mode int) string { return "serializing" case ModeHTTP: return "http" + case ModeStatic: + return "static" default: return "unknown" } diff --git a/config/config_test.go b/config/config_test.go index 0a258b2a..d3f30528 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -6,20 +6,14 @@ import ( ) func TestNew(t *testing.T) { - defaults, err := New([]string{}) - if err != nil { - t.Errorf("Expected no errors") - } + defaults := New([]string{}) if defaults.TCPPort != 8080 { t.Errorf("Want TCPPort: 8080, got: %d", defaults.TCPPort) } } func Test_OperationalMode_Default(t *testing.T) { - defaults, err := New([]string{}) - if err != nil { - t.Errorf("Expected no errors") - } + defaults := New([]string{}) if defaults.OperationalMode != ModeStreaming { t.Errorf("Want %s. got: %s", WatchdogMode(ModeStreaming), WatchdogMode(defaults.OperationalMode)) } @@ -27,10 +21,7 @@ func Test_OperationalMode_Default(t *testing.T) { func Test_BufferHttpModeDefaultsToFalse(t *testing.T) { env := []string{} - actual, err := New(env) - if err != nil { - t.Errorf("Expected no errors") - } + actual := New(env) want := false if actual.BufferHTTPBody != want { t.Errorf("Want %v. got: %v", want, actual.BufferHTTPBody) @@ -42,10 +33,7 @@ func Test_BufferHttpMode_CanBeSetToTrue(t *testing.T) { "buffer_http=true", } - actual, err := New(env) - if err != nil { - t.Errorf("Expected no errors") - } + actual := New(env) want := true if actual.BufferHTTPBody != want { t.Errorf("Want %v. got: %v", want, actual.BufferHTTPBody) @@ -57,23 +45,29 @@ func Test_OperationalMode_AfterBurn(t *testing.T) { "mode=afterburn", } - actual, err := New(env) - if err != nil { - t.Errorf("Expected no errors") - } + actual := New(env) if actual.OperationalMode != ModeAfterBurn { t.Errorf("Want %s. got: %s", WatchdogMode(ModeAfterBurn), WatchdogMode(actual.OperationalMode)) } } +func Test_OperationalMode_Static(t *testing.T) { + env := []string{ + "mode=static", + } + + actual := New(env) + + if actual.OperationalMode != ModeStatic { + t.Errorf("Want %s. got: %s", WatchdogMode(ModeStatic), WatchdogMode(actual.OperationalMode)) + } +} + func Test_ContentType_Default(t *testing.T) { env := []string{} - actual, err := New(env) - if err != nil { - t.Errorf("Expected no errors") - } + actual := New(env) if actual.ContentType != "application/octet-stream" { t.Errorf("Default (ContentType) Want %s. got: %s", actual.ContentType, "octet-stream") @@ -85,10 +79,7 @@ func Test_ContentType_Override(t *testing.T) { "content_type=application/json", } - actual, err := New(env) - if err != nil { - t.Errorf("Expected no errors") - } + actual := New(env) if actual.ContentType != "application/json" { t.Errorf("(ContentType) Want %s. got: %s", actual.ContentType, "application/json") @@ -100,10 +91,7 @@ func Test_FunctionProcessLegacyName(t *testing.T) { "fprocess=env", } - actual, err := New(env) - if err != nil { - t.Errorf("Expected no errors") - } + actual := New(env) if actual.FunctionProcess != "env" { t.Errorf("Want %s. got: %s", "env", actual.FunctionProcess) @@ -115,10 +103,7 @@ func Test_FunctionProcessAlternativeName(t *testing.T) { "function_process=env", } - actual, err := New(env) - if err != nil { - t.Errorf("Expected no errors") - } + actual := New(env) if actual.FunctionProcess != "env" { t.Errorf("Want %s. got: %s", "env", actual.FunctionProcess) @@ -161,10 +146,7 @@ func Test_FunctionProcess_Arguments(t *testing.T) { for _, testCase := range cases { t.Run(testCase.scenario, func(t *testing.T) { - actual, err := New([]string{testCase.env}) - if err != nil { - t.Errorf("Expected no errors") - } + actual := New([]string{testCase.env}) process, args := actual.Process() if process != testCase.wantProcess { @@ -192,10 +174,7 @@ func Test_PortOverride(t *testing.T) { "port=8081", } - actual, err := New(env) - if err != nil { - t.Errorf("Expected no errors") - } + actual := New(env) if actual.TCPPort != 8081 { t.Errorf("Want %d. got: %d", 8081, actual.TCPPort) @@ -241,10 +220,7 @@ func Test_Timeouts(t *testing.T) { } for _, testCase := range cases { - actual, err := New(testCase.env) - if err != nil { - t.Errorf("(%s) Expected no errors", testCase.name) - } + actual := New(testCase.env) if testCase.readTimeout != actual.HTTPReadTimeout { t.Errorf("(%s) HTTPReadTimeout want: %s, got: %s", testCase.name, actual.HTTPReadTimeout, testCase.readTimeout) } diff --git a/main.go b/main.go index 2666c360..c7efb5b8 100644 --- a/main.go +++ b/main.go @@ -28,13 +28,9 @@ var ( func main() { atomic.StoreInt32(&acceptingConnections, 0) - watchdogConfig, configErr := config.New(os.Environ()) - if configErr != nil { - fmt.Fprintf(os.Stderr, configErr.Error()) - os.Exit(-1) - } + watchdogConfig := config.New(os.Environ()) - if len(watchdogConfig.FunctionProcess) == 0 { + if len(watchdogConfig.FunctionProcess) == 0 && watchdogConfig.OperationalMode != config.ModeStatic { fmt.Fprintf(os.Stderr, "Provide a \"function_process\" or \"fprocess\" environmental variable for your function.\n") os.Exit(-1) } @@ -150,6 +146,8 @@ func buildRequestHandler(watchdogConfig config.WatchdogConfig) http.Handler { case config.ModeHTTP: requestHandler = makeHTTPRequestHandler(watchdogConfig) break + case config.ModeStatic: + requestHandler = makeStaticRequestHandler(watchdogConfig) default: log.Panicf("unknown watchdog mode: %d", watchdogConfig.OperationalMode) break @@ -338,6 +336,15 @@ func makeHTTPRequestHandler(watchdogConfig config.WatchdogConfig) func(http.Resp } } +func makeStaticRequestHandler(watchdogConfig config.WatchdogConfig) http.HandlerFunc { + if watchdogConfig.StaticPath == "" { + log.Fatal(`For mode=static you must specify the "static_path" to serve`) + } + + log.Printf("Serving files at %s", watchdogConfig.StaticPath) + return http.FileServer(http.Dir(watchdogConfig.StaticPath)).ServeHTTP +} + func lockFilePresent() bool { path := filepath.Join(os.TempDir(), ".lock") if _, err := os.Stat(path); os.IsNotExist(err) {