| 779 | } |
| 780 | |
| 781 | func (h errorHandler) handleError(w http.ResponseWriter, r *http.Request, lw *loggingResponseWriter, err error) bool { |
| 782 | var logged bool |
| 783 | |
| 784 | // Extract a presentable, loggable error. |
| 785 | var hOK bool |
| 786 | hErr, hAsOK := errors.AsType[HTTPError](err) |
| 787 | if hAsOK { |
| 788 | hOK = true |
| 789 | if hErr.Code == 0 { |
| 790 | lw.logf("[unexpected] HTTPError %v did not contain an HTTP status code, sending internal server error", hErr) |
| 791 | hErr.Code = http.StatusInternalServerError |
| 792 | } |
| 793 | } else if v, ok := vizerror.As(err); ok { |
| 794 | hErr = Error(http.StatusInternalServerError, v.Error(), nil) |
| 795 | } else if reqCancelled(r, err) { |
| 796 | // 499 is the Nginx convention meaning "Client Closed Connection". |
| 797 | if errors.Is(err, context.Canceled) || errors.Is(err, http.ErrAbortHandler) { |
| 798 | hErr = Error(499, "", err) |
| 799 | } else { |
| 800 | hErr = Error(499, "", fmt.Errorf("%w: %w", context.Canceled, err)) |
| 801 | } |
| 802 | } else { |
| 803 | // Omit the friendly message so HTTP logs show the bare error that was |
| 804 | // returned and we know it's not a HTTPError. |
| 805 | hErr = Error(http.StatusInternalServerError, "", err) |
| 806 | } |
| 807 | |
| 808 | // Tell the logger what error we wrote back to the client. |
| 809 | if pb := errCallback.Value(r.Context()); pb != nil { |
| 810 | pb(hErr) |
| 811 | logged = true |
| 812 | } |
| 813 | |
| 814 | if r.Context().Err() != nil { |
| 815 | return logged |
| 816 | } |
| 817 | |
| 818 | if lw.code != 0 { |
| 819 | if hOK && hErr.Code != lw.code { |
| 820 | lw.logf("[unexpected] handler returned HTTPError %v, but already sent response with code %d", hErr, lw.code) |
| 821 | } |
| 822 | return logged |
| 823 | } |
| 824 | |
| 825 | // Set a default error message from the status code. Do this after we pass |
| 826 | // the error back to the logger so that `return errors.New("oh")` logs as |
| 827 | // `"err": "oh"`, not `"err": "Internal Server Error: oh"`. |
| 828 | if hErr.Msg == "" { |
| 829 | switch hErr.Code { |
| 830 | case 499: |
| 831 | hErr.Msg = "Client Closed Request" |
| 832 | default: |
| 833 | hErr.Msg = http.StatusText(hErr.Code) |
| 834 | } |
| 835 | } |
| 836 | |
| 837 | // If OnError panics before a response is written, write a bare 500 back. |
| 838 | // OnError panics are thrown further up the stack. |