|
@@ -1,6 +1,7 @@
|
|
|
package dpcore
|
|
|
|
|
|
import (
|
|
|
+ "context"
|
|
|
"errors"
|
|
|
"io"
|
|
|
"log"
|
|
@@ -8,12 +9,9 @@ import (
|
|
|
"net/http"
|
|
|
"net/url"
|
|
|
"strings"
|
|
|
- "sync"
|
|
|
"time"
|
|
|
)
|
|
|
|
|
|
-var onExitFlushLoop func()
|
|
|
-
|
|
|
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
|
|
// sends it to another server, proxying the response back to the
|
|
|
// client, support http, also support https tunnel using http.hijacker
|
|
@@ -68,7 +66,12 @@ type requestCanceler interface {
|
|
|
CancelRequest(req *http.Request)
|
|
|
}
|
|
|
|
|
|
-func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerification bool) *ReverseProxy {
|
|
|
+type DpcoreOptions struct {
|
|
|
+ IgnoreTLSVerification bool
|
|
|
+ FlushInterval time.Duration
|
|
|
+}
|
|
|
+
|
|
|
+func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy {
|
|
|
targetQuery := target.RawQuery
|
|
|
director := func(req *http.Request) {
|
|
|
req.URL.Scheme = target.Scheme
|
|
@@ -95,16 +98,17 @@ func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerificatio
|
|
|
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
|
|
|
thisTransporter.(*http.Transport).DisableCompression = true
|
|
|
|
|
|
- if ignoreTLSVerification {
|
|
|
+ if dpcOptions.IgnoreTLSVerification {
|
|
|
//Ignore TLS certificate validation error
|
|
|
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
|
|
|
}
|
|
|
|
|
|
return &ReverseProxy{
|
|
|
- Director: director,
|
|
|
- Prepender: prepender,
|
|
|
- Verbal: false,
|
|
|
- Transport: thisTransporter,
|
|
|
+ Director: director,
|
|
|
+ Prepender: prepender,
|
|
|
+ FlushInterval: dpcOptions.FlushInterval,
|
|
|
+ Verbal: false,
|
|
|
+ Transport: thisTransporter,
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -178,62 +182,64 @@ var hopHeaders = []string{
|
|
|
//"Upgrade",
|
|
|
}
|
|
|
|
|
|
-func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
|
|
|
- if p.FlushInterval != 0 {
|
|
|
- if wf, ok := dst.(writeFlusher); ok {
|
|
|
- mlw := &maxLatencyWriter{
|
|
|
- dst: wf,
|
|
|
- latency: p.FlushInterval,
|
|
|
- done: make(chan bool),
|
|
|
- }
|
|
|
-
|
|
|
- go mlw.flushLoop()
|
|
|
- defer mlw.stop()
|
|
|
- dst = mlw
|
|
|
+// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
|
|
|
+func (p *ReverseProxy) copyResponse(dst http.ResponseWriter, src io.Reader, flushInterval time.Duration) error {
|
|
|
+ var w io.Writer = dst
|
|
|
+ if flushInterval != 0 {
|
|
|
+ mlw := &maxLatencyWriter{
|
|
|
+ dst: dst,
|
|
|
+ flush: http.NewResponseController(dst).Flush,
|
|
|
+ latency: flushInterval,
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- io.Copy(dst, src)
|
|
|
-}
|
|
|
+ defer mlw.stop()
|
|
|
+ // set up initial timer so headers get flushed even if body writes are delayed
|
|
|
+ mlw.flushPending = true
|
|
|
+ mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
|
|
|
+ w = mlw
|
|
|
+ }
|
|
|
|
|
|
-type writeFlusher interface {
|
|
|
- io.Writer
|
|
|
- http.Flusher
|
|
|
-}
|
|
|
+ var buf []byte
|
|
|
+ _, err := p.copyBuffer(w, src, buf)
|
|
|
+ return err
|
|
|
|
|
|
-type maxLatencyWriter struct {
|
|
|
- dst writeFlusher
|
|
|
- latency time.Duration
|
|
|
- mu sync.Mutex
|
|
|
- done chan bool
|
|
|
}
|
|
|
|
|
|
-func (m *maxLatencyWriter) Write(b []byte) (int, error) {
|
|
|
- m.mu.Lock()
|
|
|
- defer m.mu.Unlock()
|
|
|
- return m.dst.Write(b)
|
|
|
-}
|
|
|
+// Copy with given buffer size. Default to 64k
|
|
|
+func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
|
|
|
+ if len(buf) == 0 {
|
|
|
+ buf = make([]byte, 64*1024)
|
|
|
+ }
|
|
|
|
|
|
-func (m *maxLatencyWriter) flushLoop() {
|
|
|
- t := time.NewTicker(m.latency)
|
|
|
- defer t.Stop()
|
|
|
+ var written int64
|
|
|
for {
|
|
|
- select {
|
|
|
- case <-m.done:
|
|
|
- if onExitFlushLoop != nil {
|
|
|
- onExitFlushLoop()
|
|
|
+ nr, rerr := src.Read(buf)
|
|
|
+ if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
|
|
|
+ p.logf("dpcore read error during body copy: %v", rerr)
|
|
|
+ }
|
|
|
+
|
|
|
+ if nr > 0 {
|
|
|
+ nw, werr := dst.Write(buf[:nr])
|
|
|
+ if nw > 0 {
|
|
|
+ written += int64(nw)
|
|
|
+ }
|
|
|
+
|
|
|
+ if werr != nil {
|
|
|
+ return written, werr
|
|
|
+ }
|
|
|
+
|
|
|
+ if nr != nw {
|
|
|
+ return written, io.ErrShortWrite
|
|
|
}
|
|
|
- return
|
|
|
- case <-t.C:
|
|
|
- m.mu.Lock()
|
|
|
- m.dst.Flush()
|
|
|
- m.mu.Unlock()
|
|
|
}
|
|
|
- }
|
|
|
-}
|
|
|
|
|
|
-func (m *maxLatencyWriter) stop() {
|
|
|
- m.done <- true
|
|
|
+ if rerr != nil {
|
|
|
+ if rerr == io.EOF {
|
|
|
+ rerr = nil
|
|
|
+ }
|
|
|
+ return written, rerr
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
func (p *ReverseProxy) logf(format string, args ...interface{}) {
|
|
@@ -438,7 +444,10 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- p.copyResponse(rw, res.Body)
|
|
|
+ //Get flush interval in real time and start copying the request
|
|
|
+ flushInterval := p.getFlushInterval(req, res)
|
|
|
+ p.copyResponse(rw, res.Body, flushInterval)
|
|
|
+
|
|
|
// close now, instead of defer, to populate res.Trailer
|
|
|
res.Body.Close()
|
|
|
copyHeader(rw.Header(), res.Trailer)
|