How can an Exception make it to the browser if I wrap my handler in try/catch?

My understanding is that if I use Jetty/Ring, and an Exception happens in the handler, then the Exception will be sent to the browser, unless I wrap the handler in a try/catch block.

So I have a handler like this (with some code removed because it is unimportant here):

(defn handler [request]
  (pprint request)
  (try
    (when (System/getenv "path_to_log")
      (spit (System/getenv "path_to_log")  (str (get request :body) \newline \newline \newline) :append true))
    (if (not (map? (get request :body)))
      {:status 200
       :headers {"Content-Type" "text/html"}
       :body (slurp (System/getenv "path_to_index"))}
      (let [
            headers (get request :headers)
            session-id (get headers "authorization")
            params (get request :body)
            choice (get params :choice)

          ;;; some code removed

            params (sanitized-phone params)
            ]
        (if (nil? choice)
          {:status 200
           :headers {"Content-Type" "text/html"}
           :body (slurp (System/getenv "path_to_index"))}
          (if (and
               (nil? verified)
               (not (= choice "get-session-id")))
            (handler-response {:message (str "You are missing your session-id or the session-id has expired or you forgot to login." params )})
            (handler-response (choices/choices params))))))
    (catch Exception e
      (log/elog e)
      (handler-response {:message "Error: our engineers are working to fix this."}))
    (finally
      {:message "Error: our engineers are working to fix this."})))

And then I feed the handler to Jetty:

(defn initiate
  []
  (try
    (jetty/run-jetty
     (wrap-json-body handler {:keywords? true :bigdecimals? true})
     {:port 7001
      :join? false
      :max-threads 200
      :min-threads 20
      })
    (catch Exception e
      (log/elog e))))

As you can see, almost all the real action happens inside of here:

(handler-response (choices/choices params))

“choices” is a multi method that does different things depending on what is in the params.

I had assumed that if there was an Exception in choices then it would never make it to the browser, because I’ve wrapped the handler code in a try/catch block. And yet, some of the choices do make it to the browser, until I wrap the individual choice in its own try/catch block.

So did I completely misunderstand this?

Do I have to wrap every function in a try/catch block, to ensure the Exceptions happening in the function do not make it to the browser?

I think something is missing from your description. Do you have an example of a full stacktrace that did make it to the browser? Either it’s from a different location, or the actual thrown thing is not an instance of Exception.

BTW, there’s no reason to return anything in the finally block - the returned value is ignored. finally is purely for side-effects.

2 Likes

Thank you for that. I was getting desperate trying to figure out what was going on.

I think you were 100% correct with “the thing thrown is not an Exception” – these were :pre assertions on the function. I see:

Execution error (AssertionError)

Yeah, AssertionError inherits from Error that inherits Throwable - so Exception is not in the inheritance chain.

That’s because assertion errors aren’t meant to be caught. However, there are some instances where catching a Throwable still makes sense - e.g. when you just want to continue some loop with some new data, despite the fact that the current bit of data breaks an assertion.

BTW, if you’re catching an exception just to provide a generic error message to a user, it’s better to do so via a specialized middleware that catches Throwable, logs it properly, and returns an error message to the user. Your HTTP handlers should not concern themselves with how to present an error to a user - at best, they should just add context to an exception (by wrapping in another exception) and send it further up the middleware chain for it to be handled by the error middleware.

1 Like