Skip to content

std/net: resolve send with the response for an HTTP error status - #604

Open
notactuallytreyanastasio wants to merge 1 commit into
temperlang:mainfrom
notactuallytreyanastasio:fix/net-http-error-status
Open

notactuallytreyanastasio wants to merge 1 commit into
temperlang:mainfrom
notactuallytreyanastasio:fix/net-http-error-status

Conversation

@notactuallytreyanastasio

Copy link
Copy Markdown
Contributor

NetResponse.status is documented as the HTTP status code, and js and
java already resolve send() with the response for any status. py and
rust treated a 404 as a failure, in two different ways, and java dropped
the body of one.

For a GET of a path the server does not have, then a GET of port 1,
where nothing listens, the new functional test printed:

js     status 404 / body not here / no response from port 1
java   status 404 / broken promise
rust   broken promise / no response from port 1
py     (nothing; the test timed out after 60 seconds)

and now prints the js lines on all four.

py: urlopen raises HTTPError for 4xx and 5xx and URLError when it
cannot connect. Neither was caught, so the exception ended the worker
generator and send's promise was never settled; await_safe_to_exit
then waited for it forever. HTTPError is itself a response with
.status, .headers and .read(), so it now resolves the promise as
the response. OSError (which URLError is) and http.client.HTTPException
break both the response promise and the body promise. The body promise
has to be settled too, or temper run still hangs at exit with the
response promise broken, since await_safe_to_exit counts it.

Reading the body had the same hang in another form. Only IOError was
caught there, so a body that is not UTF-8 raised UnicodeDecodeError, the
worker ended, and temper run never returned, now more likely since
error pages reach that read. Decode errors and http.client errors there
now break the body promise; the response promise still resolves. The
backends still disagree about such a body: js and java put U+FFFD for
each bad byte, rust puts "?", and py now gives a broken body promise.
The functional test does not cover this; it was checked by running a
404 with a 0xFF 0xFE body, which hung py before and now prints the
status.

rust: ureq 3 returns Err(StatusCode(404)) by default, and send
breaks the promise on any Err. The request now runs on an agent with
http_status_as_error(false).

java: the status was right, but getInputStream throws for a 4xx or
5xx, so bodyContent was broken. The body of an error response comes
from getErrorStream, which is null when there is no body; that reads
as an empty body, as an empty 2xx body does.

Breaking the promise for every non-2xx status everywhere would be the
smaller change on py and rust, but it would leave status unable to
say anything other than success, and it would change js and java.

The test server gains a /missing route that answers 404 with a short
body. The new functional test types/netresponse-not-found fails on py,
rust and java 17 before this change and passes on js, py, java 17 and
rust after it. It carries the same #214 marks as types/netresponse for
cpp, the interpreter and lua. csharp was not run (no dotnet here); by
reading, HttpClient already returns the response for any status.

Fixes #553.

Repro case: https://github.com/notactuallytreyanastasio/temper-issue-repros/tree/main/31-net-404-per-backend

🤖 Generated with Claude Code

`NetResponse.status` is documented as the HTTP status code, and js and
java already resolve `send()` with the response for any status. py and
rust treated a 404 as a failure, in two different ways, and java dropped
the body of one.

For a GET of a path the server does not have, then a GET of port 1,
where nothing listens, the new functional test printed:

    js     status 404 / body not here / no response from port 1
    java   status 404 / broken promise
    rust   broken promise / no response from port 1
    py     (nothing; the test timed out after 60 seconds)

and now prints the js lines on all four.

py: `urlopen` raises HTTPError for 4xx and 5xx and URLError when it
cannot connect. Neither was caught, so the exception ended the worker
generator and `send`'s promise was never settled; `await_safe_to_exit`
then waited for it forever. HTTPError is itself a response with
`.status`, `.headers` and `.read()`, so it now resolves the promise as
the response. OSError (which URLError is) and http.client.HTTPException
break both the response promise and the body promise. The body promise
has to be settled too, or `temper run` still hangs at exit with the
response promise broken, since `await_safe_to_exit` counts it.

Reading the body had the same hang in another form. Only IOError was
caught there, so a body that is not UTF-8 raised UnicodeDecodeError, the
worker ended, and `temper run` never returned, now more likely since
error pages reach that read. Decode errors and http.client errors there
now break the body promise; the response promise still resolves. The
backends still disagree about such a body: js and java put U+FFFD for
each bad byte, rust puts "?", and py now gives a broken body promise.
The functional test does not cover this; it was checked by running a
404 with a 0xFF 0xFE body, which hung py before and now prints the
status.

rust: ureq 3 returns `Err(StatusCode(404))` by default, and `send`
breaks the promise on any `Err`. The request now runs on an agent with
`http_status_as_error(false)`.

java: the status was right, but `getInputStream` throws for a 4xx or
5xx, so `bodyContent` was broken. The body of an error response comes
from `getErrorStream`, which is null when there is no body; that reads
as an empty body, as an empty 2xx body does.

Breaking the promise for every non-2xx status everywhere would be the
smaller change on py and rust, but it would leave `status` unable to
say anything other than success, and it would change js and java.

The test server gains a `/missing` route that answers 404 with a short
body. The new functional test types/netresponse-not-found fails on py,
rust and java 17 before this change and passes on js, py, java 17 and
rust after it. It carries the same temperlang#214 marks as types/netresponse for
cpp, the interpreter and lua. csharp was not run (no dotnet here); by
reading, HttpClient already returns the response for any status.

Fixes temperlang#553.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Bobby Grayson <bobbbygrayson+github@gmail.com>
}
// A path the server does not have, for checking that an HTTP error
// status reaches Temper code as a response with that status.
server.createContext("/missing") { exchange ->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

214.let { issue(Ft.TypesNetresponse, cpp(it), interp(it), lua(it)) }
214.let {
issue(Ft.TypesNetresponse, cpp(it), interp(it), lua(it))
issue(Ft.TypesNetresponseNotFound, cpp(it), interp(it), lua(it))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

std/net: a 404 response is a status on js and java, a broken promise on rust, and a hang on py

2 participants