[BUG] Anvil.http.request throws info away on http_422 and http_400

I am making an anvil.http.request to a fastapi based back-end which fails on data validation.

FastAPI uses Pydantic to validate request data. If your endpoint is correct but the request is in error you’ll end up with an error 400 or 422. The HTTP_422 comes if the Pydantic validation discovers data that doesn’t agree with the validation model. One of the strong points of FastAPI are the extensive validation error messages, which are returned as json payload in the response.
Unfortunately when anvil.http.request gets an error status back it raises an exception. There is no reply from the request call, so no Pydantic error messages there. The anvil.http.HTTPError exception object as raised by anvil.http.request does contain the content attribute which is supposed to contain the response payload according to Anvil Docs | Making HTTP requests.
But it comes up empty :slightly_frowning_face:

Code Sample:

def login(username, password):
  url = AUTH_API_URL + "browse/login"
  data = f"username={username}&password={password}"
  
  try:
    response = request(   
      url = url,
      method = "POST",
      data = url_encode(data),
      headers = {
        "Content-Type" : "application/x-www-form-urlencoded",
        "Accept" : "application/json",
      }
    )
  except Exception as e:
    response = f"Exception status={e.status}, content={e.content}, args={e.args}"
          
  return response

Found the error:

Here is the actual $.ajax call:

        return PyDefUtils.suspensionPromise(function(resolve, reject) {
            $.ajax(params).done(function(r, ts, xhr) {
                window.setLoading(false);
                resolve(pyGetResponse(r, xhr, kwargs["json"]))
            }).fail(function(xhr, textStatus, errorThrown) {
                window.setLoading(false);
                const status = xhr.status;
                const content = pyGetResponse(xhr, kwargs["json"]);    // WRONG!
                let message = textStatus || errorThrown;
                if (message === "error") {
                    message = null; // instead use a HttpError's nicer message.
                }
                reject(PyDefUtils.pyCall(pyMod["HttpError"], [Sk.ffi.toPy(status), content, Sk.ffi.toPy(message)]));
            });

This is wrong because:
const content = pyGetResponse(xhr, kwargs["json"]);
will always set content to null because xhr is not an ArrayBuffer!

I think the line should be:
const content = pyGetResponse(xhr.response, xhr, kwargs["json"]);

Thanks for reporting - same bug as this one - HTTP error content is empty (unexpected per documentation)

Agree. Missed that one in my search. And this one comes with the fix.

1 Like

you reported yours first :wink:

The fix suggested would work - but unfortunately - jQuery.ajax doesn’t provide a xhr.response value.
This is a limitation of jQuery.ajax api so we’ll be adjusting the approach slightly to fix this bug.

2 Likes

Function( jqXHR jqXHR, String textStatus, String errorThrown )

A have tested it. It is on a different position in the error case, first param now. But it does return the response content as part of the jqXHR when I tried.