Skip to content

Commit 12e0d25

Browse files
committedJun 5, 2019
Support caching the request body
1 parent 97d1681 commit 12e0d25

File tree

5 files changed

+54
-12
lines changed

5 files changed

+54
-12
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/monohook
22
/release
3+
/tmp
34
/vendor

‎README.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,12 @@ default. You may choose to forward part or all of the following data:
358358
or the `$MONOHOOK_FORWARD_REQUEST_BODY` environment variable.
359359
360360
If enabled, the request body will be piped to the command's standard input.
361+
362+
If the `-Y, --cache-request-body` command-line flag or the
363+
`$MONOHOOK_CACHE_REQUEST_BODY` environment variable is also enabled, the
364+
request body will be read immediately and cached for when the hook executes
365+
(which might take a while depending on concurrency settings). Otherwise, the
366+
request will wait to complete until the hook can actually execute.
361367
* HTTP request headers with the `-H, --forward-request-headers` command-line
362368
flag or the `$MONOHOOK_FORWARD_REQUEST_HEADERS` environment variable.
363369
@@ -391,10 +397,12 @@ $> cat body.txt
391397
0123456789
392398
```
393399
394-
> Note that forwarding the request body means that the execution of the command
395-
> will not end before the HTTP request body has been completely consumed. This
396-
> may have a performance impact. For example, with a concurrency of 1, each
397-
> request will have to wait for the previous one to upload its data.
400+
> Note that forwarding the request body without caching it (using the `-Y,
401+
> --cache-request-body` command-line flag or the `$MONOHOOK_CACHE_REQUEST_BODY`
402+
> environment variable) means that the execution of the command will not end
403+
> before the HTTP request body has been completely consumed. This may have a
404+
> performance impact. For example, with a concurrency of 1, each request will
405+
> have to wait for the previous one to upload its data.
398406
399407
### Port number
400408
@@ -413,6 +421,7 @@ Usage:
413421
Options:
414422
-a, --authorization string Authentication token that must be sent as a Bearer token in the 'Authorization' header or as the 'authorization' URL query parameter
415423
-b, --buffer uint Maximum number of requests to queue before refusing subsequent ones until the queue is freed (zero for infinite) (default 10)
424+
-Y, --cache-request-body Whether to cache the HTTP request's body so the hook can return right away
416425
-c, --concurrency uint Maximum number of times the command should be executed in parallel (zero for infinite concurrency) (default 1)
417426
-C, --cwd string Working directory in which to run the command
418427
-B, --forward-request-body Whether to forward each HTTP request's body to the the command's standard input

‎TODO.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# TODO
2+
3+
* `GET /` to get list of buffered/executing requests (with authentication).
4+
* `GET /` to get stats.
5+
* `GET /requests/:id` to get information about pending request.

‎data.txt

-1
This file was deleted.

‎monohook.go

+35-7
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
package main
33

44
import (
5+
"bytes"
56
"fmt"
67
"io"
8+
"io/ioutil"
79
"net/http"
810
"os"
911
"os/exec"
@@ -40,6 +42,7 @@ type commandOptions struct {
4042
env []string
4143
quiet bool
4244
reader *io.PipeReader
45+
cache []byte
4346
}
4447

4548
var httpRequestHeaderToEnvVarRegexp = regexp.MustCompile("-")
@@ -51,6 +54,7 @@ func main() {
5154
var concurrency uint64
5255
var cwd string
5356
var forwardRequestBody bool
57+
var cacheRequestBody bool
5458
var forwardRequestHeaders bool
5559
var forwardRequestURL bool
5660
var port uint64
@@ -67,6 +71,7 @@ func main() {
6771
utils.Uint64Option(&buffer, "buffer", "b", "BUFFER", 10, "Maximum number of requests to queue before refusing subsequent ones until the queue is freed (zero for infinite)", errHandler)
6872
utils.Uint64Option(&concurrency, "concurrency", "c", "CONCURRENCY", 1, "Maximum number of times the command should be executed in parallel (zero for infinite concurrency)", errHandler)
6973
utils.BoolOption(&forwardRequestBody, "forward-request-body", "B", "FORWARD_REQUEST_BODY", false, "Whether to forward each HTTP request's body to the the command's standard input", errHandler)
74+
utils.BoolOption(&cacheRequestBody, "cache-request-body", "Y", "CACHE_REQUEST_BODY", false, "Whether to cache the HTTP request's body so the hook can return right away", errHandler)
7075
utils.BoolOption(&forwardRequestHeaders, "forward-request-headers", "H", "FORWARD_REQUEST_HEADERS", false, "Whether to forward each HTTP request's headers to the the command as environment variables (e.g. Content-Type becomes $MONOHOOK_REQUEST_HEADER_CONTENT_TYPE)", errHandler)
7176
utils.BoolOption(&forwardRequestURL, "forward-request-url", "U", "FORWARD_REQUEST_URL", false, "Whether to forward each HTTP request's URL to the the command as the $MONOHOOK_REQUEST_URL environment variable", errHandler)
7277
utils.StringOption(&cwd, "cwd", "C", "CWD", "", "Working directory in which to run the command")
@@ -80,7 +85,9 @@ func main() {
8085

8186
flag.Parse()
8287

83-
if port > 65535 {
88+
if cacheRequestBody && !forwardRequestBody {
89+
utils.Fail(1, quiet, "option -Y, --cache-request-body can only be used together with option -B, --forward-request-body")
90+
} else if port > 65535 {
8491
utils.Fail(1, quiet, "option -p, --port or environment variable $PORT must be an integer smaller than or equal to 65535")
8592
}
8693

@@ -140,9 +147,20 @@ func main() {
140147
// Forward HTTP request body to the command's standard input.
141148
var pw *io.PipeWriter
142149
if forwardRequestBody {
143-
reader, writer := io.Pipe()
144-
opts.reader = reader
145-
pw = writer
150+
if cacheRequestBody {
151+
body, err := ioutil.ReadAll(r.Body)
152+
if err != nil {
153+
utils.Print(quiet, "Warning: could not read request body: %s\n", err)
154+
w.WriteHeader(400)
155+
return
156+
}
157+
158+
opts.cache = body
159+
} else {
160+
reader, writer := io.Pipe()
161+
opts.reader = reader
162+
pw = writer
163+
}
146164
}
147165

148166
var env []string
@@ -171,8 +189,16 @@ func main() {
171189
}
172190

173191
if pw != nil {
174-
io.Copy(pw, r.Body)
175-
pw.Close()
192+
193+
_, err := io.Copy(pw, r.Body)
194+
if err != nil {
195+
utils.Print(quiet, "Warning: could not read entire request body: %s\n", err)
196+
}
197+
198+
pwErr := pw.Close()
199+
if pwErr != nil {
200+
utils.Print(quiet, "Warning: could not close request body pipe: %s\n", pwErr)
201+
}
176202
}
177203
})
178204

@@ -220,7 +246,9 @@ func execCommand(opts *commandOptions, waitGroup *sync.WaitGroup) {
220246
cmd.Stderr = os.Stdout
221247
cmd.Stdout = os.Stdout
222248

223-
if opts.reader != nil {
249+
if opts.cache != nil {
250+
cmd.Stdin = bytes.NewBuffer(opts.cache)
251+
} else if opts.reader != nil {
224252
cmd.Stdin = opts.reader
225253
}
226254

0 commit comments

Comments
 (0)
Please sign in to comment.