Skip to content
Merged
48 changes: 48 additions & 0 deletions lib/httplib/httplib.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,51 @@ func SafeRedirect(w http.ResponseWriter, r *http.Request, redirectURL string) er
http.Redirect(w, r, parsedURL.RequestURI(), http.StatusFound)
return nil
}

// ResponseStatusRecorder is an http.ResponseWriter that records the response status code.
type ResponseStatusRecorder struct {
http.ResponseWriter
flusher http.Flusher
status int
}

// NewResponseStatusRecorder makes and returns a ResponseStatusRecorder.
func NewResponseStatusRecorder(w http.ResponseWriter) *ResponseStatusRecorder {
rec := &ResponseStatusRecorder{ResponseWriter: w}
if flusher, ok := w.(http.Flusher); ok {
rec.flusher = flusher
}
return rec
}

// WriteHeader sends an HTTP response header with the provided
// status code and save the status code in the recorder.
func (r *ResponseStatusRecorder) WriteHeader(status int) {
r.status = status
r.ResponseWriter.WriteHeader(status)
}

// Flush optionally flushes the inner ResponseWriter if it supports that.
// Otherwise, Flush is a noop.
//
// Flush is optionally used by github.com/gravitational/oxy/forward to flush
// pending data on streaming HTTP responses (like streaming pod logs).
//
// Without this, oxy/forward will handle streaming responses by accumulating
// ~32kb of response in a buffer before flushing it.
func (r *ResponseStatusRecorder) Flush() {
if r.flusher != nil {
r.flusher.Flush()
}
}

// Status returns the recorded status after WriteHeader is called, or StatusOK if WriteHeader hasn't been called
// explicitly.
func (r *ResponseStatusRecorder) Status() int {
// http.ResponseWriter implicitly sets StatusOK, if WriteHeader hasn't been
// explicitly called.
if r.status == 0 {
return http.StatusOK
}
return r.status
}
46 changes: 2 additions & 44 deletions lib/kube/proxy/forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -1553,7 +1553,7 @@ func (f *Forwarder) catchAll(ctx *authContext, w http.ResponseWriter, req *http.
f.log.Errorf("Failed to set up forwarding headers: %v.", err)
return nil, trace.Wrap(err)
}
rw := newResponseStatusRecorder(w)
rw := httplib.NewResponseStatusRecorder(w)
sess.forwarder.ServeHTTP(rw, req)

if sess.noAuditEvents {
Expand All @@ -1577,7 +1577,7 @@ func (f *Forwarder) catchAll(ctx *authContext, w http.ResponseWriter, req *http.
},
RequestPath: req.URL.Path,
Verb: req.Method,
ResponseCode: int32(rw.getStatus()),
ResponseCode: int32(rw.Status()),
KubernetesClusterMetadata: ctx.eventClusterMeta(),
}
r := parseResourcePath(req.URL.Path)
Expand Down Expand Up @@ -2109,45 +2109,3 @@ func (f *Forwarder) removeKubeDetails(name string) {
}
delete(f.clusterDetails, name)
}

type responseStatusRecorder struct {
http.ResponseWriter
flusher http.Flusher
status int
}

func newResponseStatusRecorder(w http.ResponseWriter) *responseStatusRecorder {
rec := &responseStatusRecorder{ResponseWriter: w}
if flusher, ok := w.(http.Flusher); ok {
rec.flusher = flusher
}
return rec
}

func (r *responseStatusRecorder) WriteHeader(status int) {
r.status = status
r.ResponseWriter.WriteHeader(status)
}

// Flush optionally flushes the inner ResponseWriter if it supports that.
// Otherwise, Flush is a noop.
//
// Flush is optionally used by github.com/gravitational/oxy/forward to flush
// pending data on streaming HTTP responses (like streaming pod logs).
//
// Without this, oxy/forward will handle streaming responses by accumulating
// ~32kb of response in a buffer before flushing it.
func (r *responseStatusRecorder) Flush() {
if r.flusher != nil {
r.flusher.Flush()
}
}

func (r *responseStatusRecorder) getStatus() int {
// http.ResponseWriter implicitly sets StatusOK, if WriteHeader hasn't been
// explicitly called.
if r.status == 0 {
return http.StatusOK
}
return r.status
}
Loading