Merge pull request #7163 from influxdata/js-close-notify-fix
Remove redundant code the from response formatter and fix CloseNotifypull/7171/head^2
commit
58afeb0b71
|
@ -440,7 +440,12 @@ func (h *Handler) serveQuery(w http.ResponseWriter, r *http.Request, user *meta.
|
||||||
resp := Response{Results: make([]*influxql.Result, 0)}
|
resp := Response{Results: make([]*influxql.Result, 0)}
|
||||||
|
|
||||||
// Status header is OK once this point is reached.
|
// Status header is OK once this point is reached.
|
||||||
|
// Attempt to flush the header immediately so the client gets the header information
|
||||||
|
// and knows the query was accepted.
|
||||||
h.writeHeader(rw, http.StatusOK)
|
h.writeHeader(rw, http.StatusOK)
|
||||||
|
if w, ok := w.(http.Flusher); ok {
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// pull all results from the channel
|
// pull all results from the channel
|
||||||
rows := 0
|
rows := 0
|
||||||
|
@ -460,7 +465,7 @@ func (h *Handler) serveQuery(w http.ResponseWriter, r *http.Request, user *meta.
|
||||||
n, _ := rw.WriteResponse(Response{
|
n, _ := rw.WriteResponse(Response{
|
||||||
Results: []*influxql.Result{r},
|
Results: []*influxql.Result{r},
|
||||||
})
|
})
|
||||||
atomic.AddInt64(&h.stats.QueryRequestBytesTransmitted, int64(n+1))
|
atomic.AddInt64(&h.stats.QueryRequestBytesTransmitted, int64(n))
|
||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -940,6 +945,9 @@ func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
|
||||||
func (w gzipResponseWriter) Flush() {
|
func (w gzipResponseWriter) Flush() {
|
||||||
w.Writer.(*gzip.Writer).Flush()
|
w.Writer.(*gzip.Writer).Flush()
|
||||||
|
if w, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w gzipResponseWriter) CloseNotify() <-chan bool {
|
func (w gzipResponseWriter) CloseNotify() <-chan bool {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -365,6 +366,73 @@ func TestHandler_Query_ErrResult(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that closing the HTTP connection causes the query to be interrupted.
|
||||||
|
func TestHandler_Query_CloseNotify(t *testing.T) {
|
||||||
|
// Avoid leaking a goroutine when this fails.
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
interrupted := make(chan struct{})
|
||||||
|
h := NewHandler(false)
|
||||||
|
h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx influxql.ExecutionContext) error {
|
||||||
|
select {
|
||||||
|
case <-ctx.InterruptCh:
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
close(interrupted)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s := httptest.NewServer(h)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
// Parse the URL and generate a query request.
|
||||||
|
u, err := url.Parse(s.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
u.Path = "/query"
|
||||||
|
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("q", "SELECT * FROM cpu")
|
||||||
|
values.Set("db", "db0")
|
||||||
|
values.Set("rp", "rp0")
|
||||||
|
values.Set("chunked", "true")
|
||||||
|
u.RawQuery = values.Encode()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the request and retrieve the response.
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that the interrupted channel has NOT been closed yet.
|
||||||
|
timer := time.NewTimer(100 * time.Millisecond)
|
||||||
|
select {
|
||||||
|
case <-interrupted:
|
||||||
|
timer.Stop()
|
||||||
|
t.Fatal("query interrupted unexpectedly")
|
||||||
|
case <-timer.C:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the response body which should abort the query in the handler.
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
// The query should abort within 100 milliseconds.
|
||||||
|
timer.Reset(100 * time.Millisecond)
|
||||||
|
select {
|
||||||
|
case <-interrupted:
|
||||||
|
timer.Stop()
|
||||||
|
case <-timer.C:
|
||||||
|
t.Fatal("timeout while waiting for query to abort")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure the handler handles ping requests correctly.
|
// Ensure the handler handles ping requests correctly.
|
||||||
// TODO: This should be expanded to verify the MetaClient check in servePing is working correctly
|
// TODO: This should be expanded to verify the MetaClient check in servePing is working correctly
|
||||||
func TestHandler_Ping(t *testing.T) {
|
func TestHandler_Ping(t *testing.T) {
|
||||||
|
|
|
@ -18,13 +18,15 @@ type ResponseWriter interface {
|
||||||
// in the request that wraps the ResponseWriter.
|
// in the request that wraps the ResponseWriter.
|
||||||
func NewResponseWriter(w http.ResponseWriter, r *http.Request) ResponseWriter {
|
func NewResponseWriter(w http.ResponseWriter, r *http.Request) ResponseWriter {
|
||||||
pretty := r.URL.Query().Get("pretty") == "true"
|
pretty := r.URL.Query().Get("pretty") == "true"
|
||||||
|
rw := &responseWriter{ResponseWriter: w}
|
||||||
switch r.Header.Get("Accept") {
|
switch r.Header.Get("Accept") {
|
||||||
case "application/json":
|
case "application/json":
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
return &jsonResponseWriter{Pretty: pretty, ResponseWriter: w}
|
rw.formatter = &jsonFormatter{Pretty: pretty, Writer: w}
|
||||||
}
|
}
|
||||||
|
return rw
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteError is a convenience function for writing an error response to the ResponseWriter.
|
// WriteError is a convenience function for writing an error response to the ResponseWriter.
|
||||||
|
@ -32,12 +34,41 @@ func WriteError(w ResponseWriter, err error) (int, error) {
|
||||||
return w.WriteResponse(Response{Err: err})
|
return w.WriteResponse(Response{Err: err})
|
||||||
}
|
}
|
||||||
|
|
||||||
type jsonResponseWriter struct {
|
// responseWriter is an implementation of ResponseWriter.
|
||||||
Pretty bool
|
type responseWriter struct {
|
||||||
|
formatter interface {
|
||||||
|
WriteResponse(resp Response) (int, error)
|
||||||
|
}
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *jsonResponseWriter) WriteResponse(resp Response) (n int, err error) {
|
// WriteResponse writes the response using the formatter.
|
||||||
|
func (w *responseWriter) WriteResponse(resp Response) (int, error) {
|
||||||
|
return w.formatter.WriteResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flushes the ResponseWriter if it has a Flush() method.
|
||||||
|
func (w *responseWriter) Flush() {
|
||||||
|
if w, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseNotify calls CloseNotify on the underlying http.ResponseWriter if it
|
||||||
|
// exists. Otherwise, it returns a nil channel that will never notify.
|
||||||
|
func (w *responseWriter) CloseNotify() <-chan bool {
|
||||||
|
if notifier, ok := w.ResponseWriter.(http.CloseNotifier); ok {
|
||||||
|
return notifier.CloseNotify()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonFormatter struct {
|
||||||
|
io.Writer
|
||||||
|
Pretty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *jsonFormatter) WriteResponse(resp Response) (n int, err error) {
|
||||||
var b []byte
|
var b []byte
|
||||||
if w.Pretty {
|
if w.Pretty {
|
||||||
b, err = json.MarshalIndent(resp, "", " ")
|
b, err = json.MarshalIndent(resp, "", " ")
|
||||||
|
@ -55,10 +86,3 @@ func (w *jsonResponseWriter) WriteResponse(resp Response) (n int, err error) {
|
||||||
n++
|
n++
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush flushes the ResponseWriter if it has a Flush() method.
|
|
||||||
func (w *jsonResponseWriter) Flush() {
|
|
||||||
if w, ok := w.ResponseWriter.(http.Flusher); ok {
|
|
||||||
w.Flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue