Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
reverseproxy: Response buffering & configurable buffer size
Proxy response bodies can now be buffered, and the size of the request body and response body buffer can be limited. Any remaining content that doesn't fit in the buffer will remain on the wire until it can be read; i.e. bodies are not truncated, even if the buffer is not big enough. This fulfills a customer requirement. This was made possible by their sponsorship!
Matthew Holt committed
Feb 9, 2021 at 14:15 UTC
5ef76ff3e6e73282deb59df8b1a14bb966de36be
3 files changed
+80
-9
modules/caddyhttp/reverseproxy/caddyfile.go
+19
@@ -499,6 +499,25 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
499
}
500
h.BufferRequests = true
501
502
+ case "buffer_responses":
503
+ if d.NextArg() {
504
+ return d.ArgErr()
505
+ }
506
+ h.BufferResponses = true
507
+
508
+ case "max_buffer_size":
509
+ if !d.NextArg() {
510
+ return d.ArgErr()
511
+ }
512
+ size, err := strconv.Atoi(d.Val())
513
+ if err != nil {
514
+ return d.Errf("invalid size (bytes): %s", d.Val())
515
+ }
516
+ if d.NextArg() {
517
+ return d.ArgErr()
518
+ }
519
+ h.MaxBufferSize = int64(size)
520
+
521
case "header_up":
522
var err error
523
modules/caddyhttp/reverseproxy/httptransport.go
+1
@@ -334,6 +334,7 @@ func (t TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
334
cfg.Certificates = []tls.Certificate{cert}
335
}
336
if t.ClientCertificateAutomate != "" {
337
+ // TODO: use or enable ctx.IdentityCredentials() ...
338
tlsAppIface, err := ctx.App("tls")
339
if err != nil {
340
return nil, fmt.Errorf("getting tls app: %v", err)
modules/caddyhttp/reverseproxy/reverseproxy.go
+60
-9
@@ -21,7 +21,6 @@ import (
21
"errors"
22
"fmt"
23
"io"
24
- "io/ioutil"
24
"net"
25
"net/http"
26
"regexp"
@@ -93,9 +92,20 @@ type Handler struct {
92
93
// If true, the entire request body will be read and buffered
94
// in memory before being proxied to the backend. This should
96
- // be avoided if at all possible for performance reasons.
95
+ // be avoided if at all possible for performance reasons, but
96
+ // could be useful if the backend is intolerant of read latency.
97
BufferRequests bool `json:"buffer_requests,omitempty"`
98
99
+ // If true, the entire response body will be read and buffered
100
+ // in memory before being proxied to the client. This should
101
+ // be avoided if at all possible for performance reasons, but
102
+ // could be useful if the backend has tighter memory constraints.
103
+ BufferResponses bool `json:"buffer_responses,omitempty"`
104
+
105
+ // If body buffering is enabled, the maximum size of the buffers
106
+ // used for the requests and responses (in bytes).
107
+ MaxBufferSize int64 `json:"max_buffer_size,omitempty"`
108
+
109
// List of handlers and their associated matchers to evaluate
110
// after successful roundtrips. The first handler that matches
111
// the response from a backend will be invoked. The response
@@ -337,12 +347,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
347
// required, if read timeouts are set,
348
// and if body size is limited
349
if h.BufferRequests {
340
- buf := bufPool.Get().(*bytes.Buffer)
341
- buf.Reset()
342
- defer bufPool.Put(buf)
343
- _, _ = io.Copy(buf, r.Body)
344
- r.Body.Close()
345
- r.Body = ioutil.NopCloser(buf)
350
+ r.Body = h.bufferedBody(r.Body)
351
}
352
353
// prepare the request for proxying; this is needed only once
@@ -563,6 +568,11 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
568
}
569
}
570
571
+ // if enabled, buffer the response body
572
+ if h.BufferResponses {
573
+ res.Body = h.bufferedBody(res.Body)
574
+ }
575
+
576
// see if any response handler is configured for this response from the backend
577
for i, rh := range h.HandleResponse {
578
if rh.Match != nil && !rh.Match.Match(res.StatusCode, res.Header) {
@@ -599,7 +609,7 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
609
}
610
}
611
602
- // Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
612
+ // deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
613
if res.StatusCode == http.StatusSwitchingProtocols {
614
h.handleUpgradeResponse(rw, req, res)
615
return nil
@@ -735,6 +745,30 @@ func (h Handler) directRequest(req *http.Request, di DialInfo) {
745
req.URL.Host = reqHost
746
}
747
748
+// bufferedBody reads originalBody into a buffer, then returns a reader for the buffer.
749
+// Always close the return value when done with it, just like if it was the original body!
750
+func (h Handler) bufferedBody(originalBody io.ReadCloser) io.ReadCloser {
751
+ buf := bufPool.Get().(*bytes.Buffer)
752
+ buf.Reset()
753
+ if h.MaxBufferSize > 0 {
754
+ n, err := io.CopyN(buf, originalBody, h.MaxBufferSize)
755
+ if err != nil || n == h.MaxBufferSize {
756
+ return bodyReadCloser{
757
+ Reader: io.MultiReader(buf, originalBody),
758
+ buf: buf,
759
+ body: originalBody,
760
+ }
761
+ }
762
+ } else {
763
+ _, _ = io.Copy(buf, originalBody)
764
+ }
765
+ originalBody.Close() // no point in keeping it open
766
+ return bodyReadCloser{
767
+ Reader: buf,
768
+ buf: buf,
769
+ }
770
+}
771
+
772
func copyHeader(dst, src http.Header) {
773
for k, vv := range src {
774
for _, v := range vv {
@@ -858,6 +892,23 @@ type TLSTransport interface {
892
// roundtrip succeeded, but an error occurred after-the-fact.
893
type roundtripSucceeded struct{ error }
894
895
+// bodyReadCloser is a reader that, upon closing, will return
896
+// its buffer to the pool and close the underlying body reader.
897
+type bodyReadCloser struct {
898
+ io.Reader
899
+ buf *bytes.Buffer
900
+ body io.ReadCloser
901
+}
902
+
903
+func (brc bodyReadCloser) Close() error {
904
+ bufPool.Put(brc.buf)
905
+ if brc.body != nil {
906
+ return brc.body.Close()
907
+ }
908
+ return nil
909
+}
910
+
911
+// bufPool is used for buffering requests and responses.
912
var bufPool = sync.Pool{
913
New: func() interface{} {
914
return new(bytes.Buffer)