Skip to content

Commit 85505a7

Browse files
committed
Add http_buffer to "http" mode
Allows servers such as Swoole which do not implement the whole HTTP spec to be used with of-watchdog. Apply this setting when Transfer-Encoding: chunked is not compatible with your http server in http mode. Tested with netcat "nc" outside of a Docker container to see the output either sent with a content-length when turned on or without one when in chunked mode. Signed-off-by: Alex Ellis (VMware) <[email protected]>
1 parent 8df2b25 commit 85505a7

File tree

5 files changed

+66
-23
lines changed

5 files changed

+66
-23
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,16 @@ Environmental variables:
140140

141141
| Option | Implemented | Usage |
142142
|------------------------|--------------|-------------------------------|
143-
| `function_process` | Yes | The process to invoke for each function call function process (alias - fprocess). This must be a UNIX binary and accept input via STDIN and output via STDOUT. |
143+
| `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` |
144144
| `read_timeout` | Yes | HTTP timeout for reading the payload from the client caller (in seconds) |
145145
| `write_timeout` | Yes | HTTP timeout for writing a response body from your function (in seconds) |
146146
| `exec_timeout` | Yes | Exec timeout for process exec'd for each incoming request (in seconds). Disabled if set to 0. |
147-
| `port` | Yes | Specify an alternative TCP port for testing |
148-
| `write_debug` | No | Write all output, error messages, and additional information to the logs. Default is false. |
147+
| `port` | Yes | Specify an alternative TCP port for testing. Default: `8080` |
148+
| `write_debug` | No | Write all output, error messages, and additional information to the logs. Default is `false`. |
149149
| `content_type` | Yes | Force a specific Content-Type response for all responses - only in forking/serializing modes. |
150-
| `suppress_lock` | Yes | The watchdog will attempt to write a lockfile to /tmp/ for swarm healthchecks - set this to true to disable behaviour. |
151-
| `upstream_url` | Yes | `http` mode only - where to forward requests i.e. 127.0.0.1:5000 |
150+
| `suppress_lock` | Yes | When set to `false` the watchdog will attempt to write a lockfile to /tmp/ for healthchecks. Default `false` |
151+
| `upstream_url` | Yes | `http` mode only - where to forward requests i.e. `127.0.0.1:5000` |
152+
| `buffer_http` | Yes | `http` mode only - buffers request body to memory before fowarding. Use if your upstream HTTP server does not accept `Transfer-Encoding: chunked` Default: `false` |
153+
152154

153155
> Note: the .lock file is implemented for health-checking, but cannot be disabled yet. You must create this file in /tmp/.

config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ type WatchdogConfig struct {
2020
OperationalMode int
2121
SuppressLock bool
2222
UpstreamURL string
23+
24+
// BufferHTTPBody buffers the HTTP body in memory
25+
// to prevent transfer type of chunked encoding
26+
// which some servers do not support.
27+
BufferHTTPBody bool
2328
}
2429

2530
// Process returns a string for the process and a slice for the arguments from the FunctionProcess.
@@ -71,6 +76,7 @@ func New(env []string) (WatchdogConfig, error) {
7176
ContentType: contentType,
7277
SuppressLock: getBool(envMap, "suppress_lock"),
7378
UpstreamURL: upstreamURL,
79+
BufferHTTPBody: getBool(envMap, "buffer_http"),
7480
}
7581

7682
if val := envMap["mode"]; len(val) > 0 {

config/config_test.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,33 @@ func Test_OperationalMode_Default(t *testing.T) {
2222
t.Errorf("Want %s. got: %s", WatchdogMode(ModeStreaming), WatchdogMode(defaults.OperationalMode))
2323
}
2424
}
25+
func Test_BufferHttpModeDefaultsToFalse(t *testing.T) {
26+
env := []string{}
27+
28+
actual, err := New(env)
29+
if err != nil {
30+
t.Errorf("Expected no errors")
31+
}
32+
want := false
33+
if actual.BufferHTTPBody != want {
34+
t.Errorf("Want %v. got: %v", want, actual.BufferHTTPBody)
35+
}
36+
}
37+
38+
func Test_BufferHttpMode_CanBeSetToTrue(t *testing.T) {
39+
env := []string{
40+
"buffer_http=true",
41+
}
42+
43+
actual, err := New(env)
44+
if err != nil {
45+
t.Errorf("Expected no errors")
46+
}
47+
want := true
48+
if actual.BufferHTTPBody != want {
49+
t.Errorf("Want %v. got: %v", want, actual.BufferHTTPBody)
50+
}
51+
}
2552

2653
func Test_OperationalMode_AfterBurn(t *testing.T) {
2754
env := []string{
@@ -36,7 +63,6 @@ func Test_OperationalMode_AfterBurn(t *testing.T) {
3663
if actual.OperationalMode != ModeAfterBurn {
3764
t.Errorf("Want %s. got: %s", WatchdogMode(ModeAfterBurn), WatchdogMode(actual.OperationalMode))
3865
}
39-
4066
}
4167

4268
func Test_ContentType_Default(t *testing.T) {

executor/http_runner.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package executor
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"io"
@@ -12,25 +13,24 @@ import (
1213
"os"
1314
"os/exec"
1415
"os/signal"
15-
"sync"
1616
"syscall"
1717
"time"
1818
)
1919

2020
// HTTPFunctionRunner creates and maintains one process responsible for handling all calls
2121
type HTTPFunctionRunner struct {
22-
ExecTimeout time.Duration // ExecTimeout the maxmium duration or an upstream function call
23-
ReadTimeout time.Duration
24-
WriteTimeout time.Duration
25-
Process string
26-
ProcessArgs []string
27-
Command *exec.Cmd
28-
StdinPipe io.WriteCloser
29-
StdoutPipe io.ReadCloser
30-
Stderr io.Writer
31-
Mutex sync.Mutex
32-
Client *http.Client
33-
UpstreamURL *url.URL
22+
ExecTimeout time.Duration // ExecTimeout the maxmium duration or an upstream function call
23+
ReadTimeout time.Duration
24+
WriteTimeout time.Duration
25+
Process string
26+
ProcessArgs []string
27+
Command *exec.Cmd
28+
StdinPipe io.WriteCloser
29+
StdoutPipe io.ReadCloser
30+
Stderr io.Writer
31+
Client *http.Client
32+
UpstreamURL *url.URL
33+
BufferHTTPBody bool
3434
}
3535

3636
// Start forks the process used for processing incoming requests
@@ -108,7 +108,15 @@ func (f *HTTPFunctionRunner) Run(req FunctionRequest, contentLength int64, r *ht
108108
upstreamURL += r.RequestURI
109109
}
110110

111-
request, _ := http.NewRequest(r.Method, upstreamURL, r.Body)
111+
var body io.Reader
112+
if f.BufferHTTPBody {
113+
reqBody, _ := ioutil.ReadAll(r.Body)
114+
body = bytes.NewReader(reqBody)
115+
} else {
116+
body = r.Body
117+
}
118+
119+
request, _ := http.NewRequest(r.Method, upstreamURL, body)
112120
for h := range r.Header {
113121
request.Header.Set(h, r.Header.Get(h))
114122
}

main.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,10 @@ func getEnvironment(r *http.Request) []string {
278278
func makeHTTPRequestHandler(watchdogConfig config.WatchdogConfig) func(http.ResponseWriter, *http.Request) {
279279
commandName, arguments := watchdogConfig.Process()
280280
functionInvoker := executor.HTTPFunctionRunner{
281-
ExecTimeout: watchdogConfig.ExecTimeout,
282-
Process: commandName,
283-
ProcessArgs: arguments,
281+
ExecTimeout: watchdogConfig.ExecTimeout,
282+
Process: commandName,
283+
ProcessArgs: arguments,
284+
BufferHTTPBody: watchdogConfig.BufferHTTPBody,
284285
}
285286

286287
if len(watchdogConfig.UpstreamURL) == 0 {

0 commit comments

Comments
 (0)