Skip to content

Commit

Permalink
refactor(middlewares): use single response header X-DRAP-ABORTED to f…
Browse files Browse the repository at this point in the history
…lag in case request timed out or was cancelled
  • Loading branch information
jeamon committed Nov 12, 2023
1 parent 7caf0ca commit f40fe78
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 20 deletions.
7 changes: 3 additions & 4 deletions api.middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (api *APIHandler) PanicRecoveryMiddleware(next httprouter.Handle) httproute
}

// TimeoutMiddleware returns a Handler which sets X-Timeout-Reached header to instruct the final handler to not
// respond to client because timeout response was already sent. Similarily it sets X-Request-Cancelled into the
// respond to client because timeout response was already sent. Similarly it sets X-Request-Cancelled into the
// header to notify the final handler to not perform any action towards the client.
func (api *APIHandler) TimeoutMiddleware(next httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
Expand All @@ -158,10 +158,10 @@ func (api *APIHandler) TimeoutMiddleware(next httprouter.Handle) httprouter.Hand
case <-done:
case <-ctx.Done():
if cerr := ctx.Err(); errors.Is(cerr, context.Canceled) {
w.Header().Set("X-Request-Cancelled", "Y")
w.Header().Set("X-DRAP-ABORTED", "C")
w.WriteHeader(499)
} else if errors.Is(cerr, context.DeadlineExceeded) {
w.Header().Set("X-Timeout-Reached", "Y")
w.Header().Set("X-DRAP-ABORTED", "T")
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusGatewayTimeout)
if err := json.NewEncoder(w).Encode(map[string]interface{}{
Expand All @@ -172,7 +172,6 @@ func (api *APIHandler) TimeoutMiddleware(next httprouter.Handle) httprouter.Hand
logger.Error("failed to send timeout response", zap.String("request.id", requestID), zap.Error(err))
}
}

}
}
}
Expand Down
30 changes: 14 additions & 16 deletions helpers.response.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
)

Expand Down Expand Up @@ -31,7 +32,7 @@ func (cw *CustomResponseWriter) Header() http.Header {

// WriteHeader implements http.WriteHeader interface.
func (cw *CustomResponseWriter) WriteHeader(code int) {
if cw.Header().Get("X-Timeout-Reached") != "" || cw.Header().Get("X-Request-Cancelled") != "" {
if cw.Header().Get("X-DRAP-ABORTED") != "" {
cw.code = code
cw.wrote = true
return
Expand All @@ -44,15 +45,12 @@ func (cw *CustomResponseWriter) WriteHeader(code int) {
}
}

// Write implements http.Write interface. If the header X-Timeout-Reached is present
// that means the timeout middleware was already triggered so we do not send anything.
// Write implements http.Write interface. If the header X-DRAP-ABORTED was set
// that means the timeout middleware was already triggered so the final handler
// should not send any response to client.
func (cw *CustomResponseWriter) Write(bytes []byte) (int, error) {
if cw.Header().Get("X-Timeout-Reached") != "" {
return 0, context.DeadlineExceeded
}

if cw.Header().Get("X-Request-Cancelled") != "" {
return 0, context.Canceled
if cw.Header().Get("X-DRAP-ABORTED") != "" {
return 0, fmt.Errorf("handler: request timed out or cancelled")
}

if !cw.wrote {
Expand Down Expand Up @@ -118,10 +116,11 @@ func GenericResponse(requestid string, status int, message string, total *int, d
}
}

// WriteErrorResponse is used to send error response to client. In case the client closes the request
// it logs with the Nginx non standard status code 499 Client Closed Request meaning the client has closed
// the request before the server could send a response. This means the timeout middleware already kicked-in
// and did send the json message with 400 BadRequest as status code (since 499 is not a standard).
// WriteErrorResponse is used to send error response to client. In case the client closes the request,
// it logs the stats with the Nginx non standard status code 499 (Client Closed Request). This means
// the timeout middleware already kicked-in and did send the response. In case of request processing
// timeout we set the status code to 504 which will be used to log the stats. Here also, the middleware
// already kicked-in and sent a json message to client.
func WriteErrorResponse(ctx context.Context, w http.ResponseWriter, errResp *APIError) error {
if err := ctx.Err(); err != nil {
if errors.Is(err, context.DeadlineExceeded) {
Expand All @@ -136,9 +135,8 @@ func WriteErrorResponse(ctx context.Context, w http.ResponseWriter, errResp *API
return json.NewEncoder(w).Encode(errResp)
}

// WriteResponse is used to send success api response to client. In case the client closes the request
// it sends the Nginx non standard status code 499 Client Closed Request meaning the client has closed
// the request before the server could send a response.
// WriteResponse is used to send success api response to client. It sets the status code to 499
// in case client cancelled the request, and to 504 if the request processing timed out.
func WriteResponse(ctx context.Context, w http.ResponseWriter, resp *APIResponse) error {
if err := ctx.Err(); err != nil {
if errors.Is(err, context.DeadlineExceeded) {
Expand Down

0 comments on commit f40fe78

Please sign in to comment.