github.com/grafana/pyroscope@v1.18.0/pkg/util/delayhandler/http.go (about) 1 package delayhandler 2 3 import ( 4 "context" 5 "net/http" 6 "time" 7 8 "github.com/opentracing/opentracing-go" 9 ) 10 11 func wrapResponseWriter(w http.ResponseWriter, end time.Time) (http.ResponseWriter, *delayedResponseWriter) { 12 wrapped := &delayedResponseWriter{wrapped: w, end: end} 13 14 // check if the writer implements the Flusher interface 15 flusher, ok := w.(http.Flusher) 16 if ok { 17 return &delayedResponseWriterWithFlush{ 18 delayedResponseWriter: wrapped, 19 flusher: flusher, 20 }, wrapped 21 } 22 23 return wrapped, wrapped 24 } 25 26 type delayedResponseWriterWithFlush struct { 27 *delayedResponseWriter 28 flusher http.Flusher 29 } 30 31 func (w *delayedResponseWriterWithFlush) Flush() { 32 w.flusher.Flush() 33 } 34 35 type delayedResponseWriter struct { 36 wrapped http.ResponseWriter 37 end time.Time 38 statusWritten bool 39 requestError bool 40 } 41 42 func (w *delayedResponseWriter) WriteHeader(statusCode int) { 43 // do not forget to write the status code to the wrapped writer 44 defer w.wrapped.WriteHeader(statusCode) 45 w.statusWritten = true 46 47 // errors shouldn't be delayed 48 if statusCode/100 != 2 { 49 w.requestError = true 50 return 51 } 52 53 delayLeft := w.end.Sub(timeNow()) 54 if delayLeft > 0 { 55 addDelayHeader(w.wrapped.Header(), delayLeft) 56 } 57 } 58 59 func (w *delayedResponseWriter) Header() http.Header { 60 return w.wrapped.Header() 61 } 62 63 func (w *delayedResponseWriter) Write(p []byte) (int, error) { 64 return w.wrapped.Write(p) 65 } 66 67 func NewHTTP(limits Limits) func(h http.Handler) http.Handler { 68 return func(h http.Handler) http.Handler { 69 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 70 start := timeNow() 71 ctx := r.Context() 72 73 delay := getDelay(ctx, limits) 74 var delayRw *delayedResponseWriter 75 delayCtx := context.Background() 76 if delay > 0 { 77 var cancel context.CancelFunc 78 delayCtx, cancel = context.WithCancel(delayCtx) 79 defer cancel() 80 ctx = WithDelayCancel(ctx, cancel) 81 w, delayRw = wrapResponseWriter(w, start.Add(delay)) 82 83 // only add a span when delay is active 84 var sp opentracing.Span 85 sp, ctx = opentracing.StartSpanFromContext(ctx, "delayhandler.Handler") 86 defer sp.Finish() 87 } 88 89 // now run the chain after me 90 h.ServeHTTP(w, r.WithContext(ctx)) 91 92 // if we didn't delay, return immediately 93 if delayRw == nil { 94 return 95 } 96 97 // if request errored we skip the delay 98 if delayRw.requestError { 99 return 100 } 101 102 // The delay has been canceled down the chain. 103 if delayCtx.Err() != nil { 104 return 105 } 106 107 delayLeft := delayRw.end.Sub(timeNow()) 108 // nothing to do if we're past the end time 109 if delayLeft <= 0 { 110 return 111 } 112 113 // when headers are not written, we add the delay header 114 if !delayRw.statusWritten { 115 addDelayHeader(w.Header(), delayLeft) 116 } 117 118 // create a separate span to make the artificial delay clear 119 sp, _ := opentracing.StartSpanFromContext(ctx, "delayhandler.Delay") 120 sp.SetTag("delayed_by", delayLeft.String()) 121 defer sp.Finish() 122 123 // wait for the delay to elapse 124 <-timeAfter(delayLeft) 125 }) 126 } 127 }