HTTP response code, `err` returned by request handlers, and middlewares
sio4 opened this issue · 2 comments
From the definition of buffalo Handler
: https://pkg.go.dev/github.com/gobuffalo/buffalo#Handler
It is the responsibility of the Handler to handle the request/response correctly.
This statement also includes the responsibility of setting the HTTP response status correctly. However, sometimes we could omit to set the response code and just returns with nil
or non-nil
err
which indicates the request was successfully completed (could be 200 by default) or failed (could be 500 by default). In this case, the buffalo.Response
will not be configured completely, and it could make some confusion on middlewares and the result of what middlewares do.
One effect of this issue is gobuffalo/buffalo-pop#19
In this case, the handler did its database job correctly but just returned nil
without the setting response. As a result, even though the response to the client became 200 (since the returned error is nil
) but the pop
middleware detected it as a failure and the database rollback happened.
Another issue affected by this behavior is https://gophers.slack.com/archives/C3MSAFD40/p1658983223965079
(Coping from the original description):
Hello! Question regarding something I've noticed when logging via the app error handlers, e.g.
app.ErrorHandlers[http.StatusInternalServerError] = someFunc
. I noticed that the logstatus
gets set to 0 if the response has has not yet been written with the relevant status code prior to execution of the Error Handler callback. Here is a log item:time="2022-07-28T04:32:02Z" level=error msg="An internal error occurred: unhealthy" content_type=application/json duration="17.711µs" human_size="0 B" method=GET params="{}" path=/v2/health/ request_id=86619d31654c9c78b952-06d343033a282f7ef78e size=0 status=0
The error handling itself is behaving as I would expect, and it is executing my custom error function as desired. I am then writing the appropriate status code to the response and the client sees it as a 500 error. However, because the log is setting a status of 0, my log aggregators pick this up with
EMERGENCY
level logging... which is not desirable. I verified that writing the response error to the response body before the error gets handled byapp.ErrorHandlers
sets the status code of the log appropriately. In the above example, it would bestatus=500
.
(for the reference) My answer to this issue was:
<...> Let's start with the output of
buffalo task middleware
from my test project:$ buffalo task middleware -> http://127.0.0.1:3000/ github.com/gobuffalo/buffalo.*App.defaultErrorMiddleware github.com/gobuffalo/buffalo.*App.PanicHandler github.com/gobuffalo/buffalo.RequestLoggerFunc github.com/gobuffalo/mw-forcessl.Middleware.func1 github.com/gobuffalo/mw-paramlogger.ParameterLogger github.com/gobuffalo/mw-csrf.glob..func1 github.com/gobuffalo/buffalo-pop/v3/pop/popmw.Transaction.func2 github.com/gobuffalo/mw-i18n/v2.*Translator.Middleware.func1
This application is just a vanilla scaffolded app with
buffalo new coke ...
and this middleware stack is the default. The first three middlewares are configured by default and the others are configured from the scaffoldedactions/app.go
. If my request handler is invoked with this stack, the control travels through them, reaches to the handler, and travels back to the root of the stack.
In your case, when the handler returns with non-nilerr
but had not been set response, the control reaches back to theParameterLogger
first and it logs the exact status without altering the response or something, so thestatus
will remain as0
when it reached to thedefaultErrorMiddleware
. In thisdefaultErrorMiddleware
, the middleware function sets HTTP status as500
if the response has no specific one, so your error handler for500
will be picked for error handling correctly but still the original status will remain as0
(even though the error handler was called with the first argument500
. So this symptom is expected from the perspective of the middleware stack, and the logger did its own job which is representing the exact situation with log output.
The current definition of the buffalo request handler stated this behavior as the following:
It is the responsibility of the Handler to handle the request/response correctly.
So basically you need to set a correct response code from your handler. However, I understand sometimes it is annoying, inconvenient to set it on all handlers, could be a duplication of code, or even not possible in some cases. One possible solution is adding a "status correction" middleware at the top of the middleware stack so the status can be set just after the handler returns. Doing that within the buffalo could be nice but it will hurt the flexibility of the user's own configuration, so I don't think that could be the best solution.
I am researching on this though, but I feel like the solution could be to document this behavior more clearly.
We need to consider the following two things.
- add a clear description of this behavior in the documentation so developers can understand it more clearly.
- consider implementing status fixer as a last/default middleware by default so that we can make sure the response code will not be 0, but in a flexible way.
This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.