Skip to content

Conversation

@ronag
Copy link
Member

@ronag ronag commented Jul 13, 2019

This tries to solve #27916. Where an error can be emitted from a HttpClient request after the response has been ended.

In particular see the comment #27916 (comment)

Depends on #28621.

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@nodejs-github-bot nodejs-github-bot added the http Issues or PRs related to the http subsystem. label Jul 13, 2019
@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

@benjamingr ping

@ronag ronag force-pushed the fix-error-after-response branch 6 times, most recently from b24c8f7 to 74add0b Compare July 13, 2019 12:42
@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

The main question here is:

"What should happen if writing to a request who's response has ended?"

Should it return true/false? Should it emit an error?

Even though emitting some form of error might seem the most semantically correct it makes the API very hard to use. I would prefer a noop + a dump (true) or no more data (false).

@ronag ronag force-pushed the fix-error-after-response branch 6 times, most recently from 8601f70 to 1915854 Compare July 13, 2019 12:50
@lpinca
Copy link
Member

lpinca commented Jul 13, 2019

Even though emitting some form of error might seem the most semantically correct it makes the API very hard to use. I would prefer a noop + a dump (true) or no more data (false).

Users have no way to know they are doing something wrong if it fails silently.

@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

Users have no way to know they are doing something wrong if it fails silently.

I would argue that they are not doing anything wrong... it's perfectly valid to end a response before receiving the whole request.

@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

Hm, maybe you are right. I'm having trouble wrapping my head around this one.

@lpinca
Copy link
Member

lpinca commented Jul 13, 2019

In my opinion intentional or unintentional writing to ClientRequest after the 'response' event is emitted is a user error.

@ronag ronag force-pushed the fix-error-after-response branch from 1915854 to 44c3fef Compare July 13, 2019 13:07
@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

@lpinca how about this version?

@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

Though ERR_WRITE_AFTER_END is not strictly true either. Do we need a new error for this?

@ronag ronag force-pushed the fix-error-after-response branch 2 times, most recently from 8d883c8 to 57fd0b3 Compare July 13, 2019 13:10
@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

@lpinca: Question. Is it valid to write data to a request after having received a response and the response has not ended? I'm a bit unsure. If no, then this issue is easier to resolve.

i.e. is something like this valid:

server.on('request', (req, res) => req.pipe(res))

@ronag ronag force-pushed the fix-error-after-response branch 3 times, most recently from e165a88 to f1ffdb6 Compare July 13, 2019 13:52
@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

@lpinca another try...

@lpinca
Copy link
Member

lpinca commented Jul 13, 2019

Is it valid to write data to a request after having received a response and the response has not ended? I'm a bit unsure.

I think it is invalid, it does not make much sense.

@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

Ok. That works for me. Should we have a new error? e.g. ‘WRITE_AFTER_RESPONSE’?

@lpinca
Copy link
Member

lpinca commented Jul 13, 2019

All data is actually discarded

req._dump();

if the user is not consuming the data.

@lpinca
Copy link
Member

lpinca commented Jul 13, 2019

Ok. That works for me. Should we have a new error? e.g. ‘WRITE_AFTER_RESPONSE’?

Works for me.

@lpinca
Copy link
Member

lpinca commented Jul 13, 2019

On the server, does it make sense to keep reading the request (if not complete) if you are already writing the response? I'm not sure, I think not.

@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

What does the spec say?

@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

All data is actually discarded

req._dump();

if the user is not consuming the data.

This is after response has finished.

@lpinca
Copy link
Member

lpinca commented Jul 13, 2019

The issue happens after response is finished (server-side) when socket is destroyed

@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

I’m confused. Is the PR fine as is? I.e. we error if the response is finished. Or when the response has started?

@lpinca
Copy link
Member

lpinca commented Jul 13, 2019

I think it will not work if we wait for it to be finished because the write can occur before res.closed is set to true, and the server might have already destroyed the socket. Hope it makes sense.

@ronag ronag force-pushed the fix-error-after-response branch 2 times, most recently from daa197d to 504e6b5 Compare July 13, 2019 16:50
@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

@lpinca updated according to our discussion

@ronag ronag force-pushed the fix-error-after-response branch from 504e6b5 to c5f9627 Compare July 13, 2019 16:52
@lpinca
Copy link
Member

lpinca commented Jul 13, 2019

Ok this is more or less what I had in mind with #27916 (comment). Btw we still have an error so I'm not sure if it is much better than original behavior.


ClientRequest.prototype.write = function write(chunk, encoding, callback) {
if (this.res) {
this.emit('error', new ERR_REQUEST_WRITE_AFTER_RESPONSE());
Copy link
Member

Choose a reason for hiding this comment

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

This will allow the 'error' event to be emitted multiple times, are there other case where this can happen?

Copy link
Member

Choose a reason for hiding this comment

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

The alternative would be worse though (throwing outside) no?

Copy link
Member

Choose a reason for hiding this comment

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

If I'm not wrong the original ECONNRESET is forwarded to the ClientRequest instance.

@benjamingr
Copy link
Member

Hmm, can we get implementor feedback from express/fastify?

I am also more in favor of "this should error" but I am really bit sure.

@ronag
Copy link
Member Author

ronag commented Jul 13, 2019

I'm more in favor of not error. Since that would be less "painful". This error is not harmful per se and will cause things that don't necessarily have to fail, to fail.

But I'm fine with either as long as it is consistent and predictable. But I do require it requires some additional fixes such as #28672.

It's a bit similar to the considerations in #20077.

@lpinca
Copy link
Member

lpinca commented Jul 14, 2019

@dougwilson

@dougwilson
Copy link
Member

Hmm, can we get implementor feedback from express/fastify?

The changes as they are currently in the PR look like they only deal with the Node.js HTTP client (vs the Node.js HTTP server which would be express/fastify/etc.), is that right?

I tried to read though and understand the conversation above as well, though. If looking for general HTTP/1 guidance, though, the HTTP sever is allowed to start writing the response before the client has completed writing the HTTP request to the wire; notably the 1xx series of response codes, where the client expects multiple HTTP responses to a single request.

This can sometimes be taken advantage of by servers to provide upload progress to the clients; I have worked on software that took advantage of this by immediately sending back a HTTP 200 HTML response before a multipart request was completed with incomplete HTML, trickling down more HTML to update a progress bar for the user to see. The down side is that intermediaries between the end-user and the server can make it not work as intended.

This specific technique has become obsolete with XHR 2's progress event tracking in JavaScript if using a FormData object to upload, these days, but just used it as a read-world example of a server sending back response data while the request data is still being sent, if there was a question on that in the conversation. RFC 723x series does not provide specific guidance around this that I know of, so it's open for servers and clients to interpret.

I hope that information helps with this PR.

@lpinca
Copy link
Member

lpinca commented Jul 14, 2019

Ok, then I think we should not move this patch forward.

@ronag
Copy link
Member Author

ronag commented Jul 14, 2019

@lpinca how about doing check for whether the res has ended? i.e.

if (this.res && this.res.closed) {
	    this.emit('error', new ERR_SOCKET_CLOSED());

As you pointed out the problem occurs here:

socket.destroySoon();

And we could set closed = true when detaching the socket (i.e. #28621) which would be before that.

Alternatively we could maybe do:

if (this.res && !this.res.socket) {

@lpinca
Copy link
Member

lpinca commented Jul 14, 2019

The problem as I see it is that you don't know the other peer destroyed the socket until you write to it. On the client res.close may not be set and you could still get ECONNRESET.

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

I prefer an implementation that did not overload write  and end any further.

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

I'm not in agreement in having this change landed at all. I think It's a perfectly valid case to start receiving a response before we have finished sending the request.

@ronag
Copy link
Member Author

ronag commented Jul 14, 2019

I think we are all in agreement about not landing this. I suggest we go back to #27916 and try to find an alternative approach.

@ronag ronag closed this Jul 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

http Issues or PRs related to the http subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants