@marcel91200 / caddy Mirror · read-only / commits / 5ef76ff3
Fork 0

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)