mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-24 16:59:00 +01:00
Revert recent Content-Length-related changes and fix fastcgi return
fastcgi's ServeHTTP method originally returned the correct value (0) inb51e8bc191
. Later, I mistakenly suggested we change that to return the status code because I forgot that status codes aren't logged by the return value. So fastcgi broke due in3966936bd6
due to my error. We later had to try to make up for this with ugly Content-Length checks like inc37ad7f677
. Turns out that all we had to do was fix the returned status here back to 0. The proxy middleware behaves the same way, and returning 0 is correct. We should only return a status code if the response has not been written, but with upstream servers, we do write a response; they do not know about our error handler. Also clarifed this in the middleware.Handler documentation.
This commit is contained in:
parent
741880a38b
commit
2a46f2a14e
7 changed files with 64 additions and 100 deletions
|
@ -43,9 +43,7 @@ func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, er
|
||||||
}
|
}
|
||||||
|
|
||||||
if status >= 400 {
|
if status >= 400 {
|
||||||
if w.Header().Get("Content-Length") == "" {
|
|
||||||
h.errorPage(w, r, status)
|
h.errorPage(w, r, status)
|
||||||
}
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,13 +79,6 @@ func TestErrors(t *testing.T) {
|
||||||
expectedLog: "",
|
expectedLog: "",
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
next: genErrorHandler(http.StatusNotFound, nil, "normal"),
|
|
||||||
expectedCode: 0,
|
|
||||||
expectedBody: "normal",
|
|
||||||
expectedLog: "",
|
|
||||||
expectedErr: nil,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
next: genErrorHandler(http.StatusForbidden, nil, ""),
|
next: genErrorHandler(http.StatusForbidden, nil, ""),
|
||||||
expectedCode: 0,
|
expectedCode: 0,
|
||||||
|
@ -168,8 +161,8 @@ func genErrorHandler(status int, err error, body string) middleware.Handler {
|
||||||
return middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
return middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if len(body) > 0 {
|
if len(body) > 0 {
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
|
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
|
||||||
}
|
|
||||||
fmt.Fprint(w, body)
|
fmt.Fprint(w, body)
|
||||||
|
}
|
||||||
return status, err
|
return status, err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
36
middleware/fastcgi/fastcgi.go
Normal file → Executable file
36
middleware/fastcgi/fastcgi.go
Normal file → Executable file
|
@ -4,7 +4,6 @@
|
||||||
package fastcgi
|
package fastcgi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -106,43 +105,28 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
|
||||||
return http.StatusBadGateway, err
|
return http.StatusBadGateway, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseBody io.Reader = resp.Body
|
// Write response header
|
||||||
if resp.Header.Get("Content-Length") == "" {
|
|
||||||
// If the upstream app didn't set a Content-Length (shame on them),
|
|
||||||
// we need to do it to prevent error messages being appended to
|
|
||||||
// an already-written response, and other problematic behavior.
|
|
||||||
// So we copy it to a buffer and read its size before flushing
|
|
||||||
// the response out to the client. See issues #567 and #614.
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
_, err := io.Copy(buf, resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusBadGateway, err
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
|
|
||||||
responseBody = buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the status code and header fields
|
|
||||||
writeHeader(w, resp)
|
writeHeader(w, resp)
|
||||||
|
|
||||||
// Write the response body
|
// Write the response body
|
||||||
_, err = io.Copy(w, responseBody)
|
_, err = io.Copy(w, resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadGateway, err
|
return http.StatusBadGateway, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FastCGI stderr outputs
|
// Log any stderr output from upstream
|
||||||
if fcgiBackend.stderr.Len() != 0 {
|
if fcgiBackend.stderr.Len() != 0 {
|
||||||
// Remove trailing newline, error logger already does this.
|
// Remove trailing newline, error logger already does this.
|
||||||
err = LogError(strings.TrimSuffix(fcgiBackend.stderr.String(), "\n"))
|
err = LogError(strings.TrimSuffix(fcgiBackend.stderr.String(), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normally we should only return a status >= 400 if no response
|
// Normally we would return the status code if it is an error status (>= 400),
|
||||||
// body is written yet, however, upstream apps don't know about
|
// however, upstream FastCGI apps don't know about our contract and have
|
||||||
// this contract and we still want the correct code logged, so error
|
// probably already written an error page. So we just return 0, indicating
|
||||||
// handling code in our stack needs to check Content-Length before
|
// that the response body is already written. However, we do return any
|
||||||
// writing an error message... oh well.
|
// error value so it can be logged.
|
||||||
return resp.StatusCode, err
|
// Note that the proxy middleware works the same way, returning status=0.
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,18 +10,17 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServeHTTPContentLength(t *testing.T) {
|
func TestServeHTTP(t *testing.T) {
|
||||||
testWithBackend := func(body string, setContentLength bool) {
|
body := "This is some test body content"
|
||||||
|
|
||||||
bodyLenStr := strconv.Itoa(len(body))
|
bodyLenStr := strconv.Itoa(len(body))
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("BackendSetsContentLength=%v: Unable to create listener for test: %v", setContentLength, err)
|
t.Fatalf("Unable to create listener for test: %v", err)
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if setContentLength {
|
|
||||||
w.Header().Set("Content-Length", bodyLenStr)
|
w.Header().Set("Content-Length", bodyLenStr)
|
||||||
}
|
|
||||||
w.Write([]byte(body))
|
w.Write([]byte(body))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -31,28 +30,24 @@ func TestServeHTTPContentLength(t *testing.T) {
|
||||||
}
|
}
|
||||||
r, err := http.NewRequest("GET", "/", nil)
|
r, err := http.NewRequest("GET", "/", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("BackendSetsContentLength=%v: Unable to create request: %v", setContentLength, err)
|
t.Fatalf("Unable to create request: %v", err)
|
||||||
}
|
}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
status, err := handler.ServeHTTP(w, r)
|
status, err := handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
if got, want := status, http.StatusOK; got != want {
|
if got, want := status, 0; got != want {
|
||||||
t.Errorf("BackendSetsContentLength=%v: Expected returned status code to be %d, got %d", setContentLength, want, got)
|
t.Errorf("Expected returned status code to be %d, got %d", want, got)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("BackendSetsContentLength=%v: Expected nil error, got: %v", setContentLength, err)
|
t.Errorf("Expected nil error, got: %v", err)
|
||||||
}
|
}
|
||||||
if got, want := w.Header().Get("Content-Length"), bodyLenStr; got != want {
|
if got, want := w.Header().Get("Content-Length"), bodyLenStr; got != want {
|
||||||
t.Errorf("BackendSetsContentLength=%v: Expected Content-Length to be '%s', got: '%s'", setContentLength, want, got)
|
t.Errorf("Expected Content-Length to be '%s', got: '%s'", want, got)
|
||||||
}
|
}
|
||||||
if got, want := w.Body.String(), body; got != want {
|
if got, want := w.Body.String(), body; got != want {
|
||||||
t.Errorf("BackendSetsContentLength=%v: Expected response body to be '%s', got: '%s'", setContentLength, want, got)
|
t.Errorf("Expected response body to be '%s', got: '%s'", want, got)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
testWithBackend("Backend does NOT set Content-Length", false)
|
|
||||||
testWithBackend("Backend sets Content-Length", true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRuleParseAddress(t *testing.T) {
|
func TestRuleParseAddress(t *testing.T) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// The error must be handled here so the log entry will record the response size.
|
// The error must be handled here so the log entry will record the response size.
|
||||||
if l.ErrorFunc != nil {
|
if l.ErrorFunc != nil {
|
||||||
l.ErrorFunc(responseRecorder, r, status)
|
l.ErrorFunc(responseRecorder, r, status)
|
||||||
} else if responseRecorder.Header().Get("Content-Length") == "" { // ensure no body written since proxy backends may write an error page
|
} else {
|
||||||
// Default failover error handler
|
// Default failover error handler
|
||||||
responseRecorder.WriteHeader(status)
|
responseRecorder.WriteHeader(status)
|
||||||
fmt.Fprintf(responseRecorder, "%d %s", status, http.StatusText(status))
|
fmt.Fprintf(responseRecorder, "%d %s", status, http.StatusText(status))
|
||||||
|
|
|
@ -13,30 +13,24 @@ type (
|
||||||
// passed the next Handler in the chain.
|
// passed the next Handler in the chain.
|
||||||
Middleware func(Handler) Handler
|
Middleware func(Handler) Handler
|
||||||
|
|
||||||
// Handler is like http.Handler except ServeHTTP returns a status code
|
// Handler is like http.Handler except ServeHTTP may return a status
|
||||||
// and an error. The status code is for the client's benefit; the error
|
// code and/or error.
|
||||||
// value is for the server's benefit. The status code will be sent to
|
|
||||||
// the client while the error value will be logged privately. Sometimes,
|
|
||||||
// an error status code (4xx or 5xx) may be returned with a nil error
|
|
||||||
// when there is no reason to log the error on the server.
|
|
||||||
//
|
//
|
||||||
// If a HandlerFunc returns an error (status >= 400), it should NOT
|
// If ServeHTTP writes to the response body, it should return a status
|
||||||
// write to the response. This philosophy makes middleware.Handler
|
// code of 0. This signals to other handlers above it that the response
|
||||||
// different from http.Handler: error handling should happen at the
|
// body is already written, and that they should not write to it also.
|
||||||
// application layer or in dedicated error-handling middleware only
|
|
||||||
// rather than with an "every middleware for itself" paradigm.
|
|
||||||
//
|
//
|
||||||
// The application or error-handling middleware should incorporate logic
|
// If ServeHTTP encounters an error, it should return the error value
|
||||||
// to ensure that the client always gets a proper response according to
|
// so it can be logged by designated error-handling middleware.
|
||||||
// the status code. For security reasons, it should probably not reveal
|
|
||||||
// the actual error message. (Instead it should be logged, for example.)
|
|
||||||
//
|
//
|
||||||
// Handlers which do write to the response should return a status value
|
// If writing a response after calling another ServeHTTP method, the
|
||||||
// < 400 as a signal that a response has been written. In other words,
|
// returned status code SHOULD be used when writing the response.
|
||||||
// only error-handling middleware or the application will write to the
|
//
|
||||||
// response for a status code >= 400. When ANY handler writes to the
|
// If handling errors after calling another ServeHTTP method, the
|
||||||
// response, it should return a status code < 400 to signal others to
|
// returned error value SHOULD be logged or handled accordingly.
|
||||||
// NOT write to the response again, which would be erroneous.
|
//
|
||||||
|
// Otherwise, return values should be propagated down the middleware
|
||||||
|
// chain by returning them unchanged.
|
||||||
Handler interface {
|
Handler interface {
|
||||||
ServeHTTP(http.ResponseWriter, *http.Request) (int, error)
|
ServeHTTP(http.ResponseWriter, *http.Request) (int, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -319,7 +319,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
status, _ := vh.stack.ServeHTTP(w, r)
|
status, _ := vh.stack.ServeHTTP(w, r)
|
||||||
|
|
||||||
// Fallback error response in case error handling wasn't chained in
|
// Fallback error response in case error handling wasn't chained in
|
||||||
if status >= 400 && w.Header().Get("Content-Length") == "" {
|
if status >= 400 {
|
||||||
DefaultErrorFunc(w, r, status)
|
DefaultErrorFunc(w, r, status)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue