Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http/server: ERR_CONNECTION_RESET when responding without request body consumed #21209

Closed
jespertheend opened this issue Aug 21, 2022 · 14 comments
Labels
bug Something isn't working correctly

Comments

@jespertheend
Copy link
Contributor

jespertheend commented Aug 21, 2022

Describe the bug

Steps to Reproduce

  1. Create main.ts:
async function post() {
	const fileInput = document.getElementById("fileInput") as HTMLInputElement;
	if (!fileInput.files || fileInput.files.length != 1) {
		alert("Please select a file");
		return;
	}
	const file = fileInput.files[0];
	const response = await fetch("/post", {
		method: "POST",
		headers: {
			"content-type": "application/zip"
		},
		body: file,
	});
	alert(await response.text());
}

Deno.serve(async (req: Request) => {
	const url = new URL(req.url);
	if (url.pathname == "/") {
		return new Response(`
<!DOCTYPE html>
<html>
	<body>
		<script>
			${post.toString()}
		</script>
		<input type="file" id="fileInput" />
		<button onclick="post()">POST</button>
	</body>
</html>`, {
			headers: {
				"content-type": "text/html"
			}
		})
	} else if (url.pathname == "/post") {
		console.log("/post received");
		// Uncomment this to fix the issue:
		// const buffer = await req.arrayBuffer();
		return new Response("OK");
	}
	return new Response("Not found", {
		status: 404,
	})
})
  1. Run deno run --allow-net main.ts
  2. Visit http://localhost:8000/
  3. Select a large file (in my case I'm using dist.zip)
  4. Open the network panel in the browser inspector
  5. Click 'POST'.

Expected behavior

The request responds with 'ok'

Actual behavior

The request fails with ERR_CONNECTION_RESET.
Note that the deno console doesn't show any errors and the console.log("/post received"); line is being run.
If you uncomment the await req.arrayBuffer() line, the issue is fixed.

Environment

  • OS: macOS 12.5 (21G72)
  • deno version: 1.38.1
@jespertheend jespertheend added bug Something isn't working correctly needs triage labels Aug 21, 2022
@iuioiua
Copy link
Contributor

iuioiua commented Aug 23, 2022

Hi @jespertheend, this is likely because the req.body needs to be consumed. Otherwise, your handler is leaking resources. It's best practise to always consume your req.body, even if it doesn't need to be used, per se. For example:

async handler(req: Request): Promise<Response> {
  // Consumes `req.body` without really using it.
  await req.body?.cancel();
  return new Response("OK"):
}

@jespertheend
Copy link
Contributor Author

Hmm ok that makes sense.
I could swear I ran into issues earlier where the same ERR_CONNECTION_RESET would happen even when returning 404 responses or throwing errors, but I can't seem to reproduce this anymore. So this is arguably less of a problem than I initially thought.

@iuioiua
Copy link
Contributor

iuioiua commented Aug 24, 2022

I’ll test this out on my machine and see how I go. Also, the dist.zip you shared is only 600kb. However, you say it’s large. Is this definitely the same file you tested with?

@jespertheend
Copy link
Contributor Author

Yes, I'm assuming this happens once a file reaches a certain size, because I tried it with a smaller file of a couple kb and I didn't see this issue.
I'm also now running into the issue on an ubuntu server even though I'm returning a 404 response, and doing the same thing on my local machine doesn't give this issue. So sounds like it's running into some sort of race condition.

@jespertheend
Copy link
Contributor Author

Seems like using serveTls rather than serve has an effect as well. I've run into a case where switching to serveTls causes an ERR_CONNECTION_RESET whereas using serve does not.

@iuioiua
Copy link
Contributor

iuioiua commented Oct 17, 2022

Hi @jespertheend, are you still encountering this issue? If so, what's the minimal reproducible snippet?

@jespertheend
Copy link
Contributor Author

Yes I can still reproduce with the snippet from the first comment. I can reproduce it on both macOS and Ubuntu. It seems a bit flaky though, about 1 in ten times it works fine, but the other 90% it fails with ERR_CONNECTION_RESET

@iuioiua
Copy link
Contributor

iuioiua commented Nov 18, 2022

Frankly, I don't think this is an issue with the server. Instead, I think the code snippet is flawed due to its hackiness. If you're trying to simply make a POST request, you can do so with just HTML. A tutorial on how to do so can be found here.

@jespertheend
Copy link
Contributor Author

That example is missing method="post" and enctype="multipart/form-data" and as a result no file is uploaded. Only the file name is provided in the query string.

If you wish to achieve this using only html, you can use this reduced test case:

import { serve } from "https://deno.land/std@0.165.0/http/server.ts";

serve(async (req: Request) => {
	const url = new URL(req.url);
	if (url.pathname == "/") {
		return new Response(
			`
<!DOCTYPE html>
<html>
	<body>
		<form action="/post" method="post" enctype="multipart/form-data">
			<input type="file" id="file" name="file">
			<input type="submit">
		</form>
	</body>
</html>`,
			{
				headers: {
					"content-type": "text/html",
				},
			},
		);
	} else if (url.pathname == "/post") {
		console.log("/post received");
		// Uncomment this to fix the issue:
		// const buffer = await req.arrayBuffer();
		return new Response("OK");
	}
	return new Response("Not found", {
		status: 404,
	});
});

In this case the issue is arguably worse, because now the user gets redirected to an empty page with a full screen error, rather than only an error being printed in the console.

@kt3k kt3k changed the title http/server: ERR_CONNECTION_RESET when uploading a large file http/server: ERR_CONNECTION_RESET when responding without request body consumed Dec 22, 2022
@kt3k kt3k removed the needs triage label Dec 22, 2022
@iuioiua
Copy link
Contributor

iuioiua commented Nov 9, 2023

Hi @jespertheend, it's been a year since the last update. Is this still an issue for you?

@jespertheend
Copy link
Contributor Author

I can still reproduce this but with Deno.serve() instead of using std.
So I guess this issue should be moved to the deno repository.

New code snippet

async function post() {
	const fileInput = document.getElementById("fileInput") as HTMLInputElement;
	if (!fileInput.files || fileInput.files.length != 1) {
		alert("Please select a file");
		return;
	}
	const file = fileInput.files[0];
	const response = await fetch("/post", {
		method: "POST",
		headers: {
			"content-type": "application/zip"
		},
		body: file,
	});
	alert(await response.text());
}

Deno.serve(async (req: Request) => {
	const url = new URL(req.url);
	if (url.pathname == "/") {
		return new Response(`
<!DOCTYPE html>
<html>
	<body>
		<script>
			${post.toString()}
		</script>
		<input type="file" id="fileInput" />
		<button onclick="post()">POST</button>
	</body>
</html>`, {
			headers: {
				"content-type": "text/html"
			}
		})
	} else if (url.pathname == "/post") {
		console.log("/post received");
		// Uncomment this to fix the issue:
		// const buffer = await req.arrayBuffer();
		return new Response("OK");
	}
	return new Response("Not found", {
		status: 404,
	})
})

But it's not a big issue for me so I'd be fine with closing it as well.

@iuioiua
Copy link
Contributor

iuioiua commented Nov 13, 2023

@bartlomieju, are you able to transfer this to the runtime repo?

@kt3k
Copy link
Member

kt3k commented Nov 15, 2023

curl client also shows the error like the below (though the response itself looks returned correctly):

$ curl -X POST localhost:8000/post -d @$HOME/Downloads/dist.zip
curl: (55) Send failure: Broken pipe
OK

I updated the original example to the updated one.

@kt3k kt3k transferred this issue from denoland/std Nov 15, 2023
@lucacasonato
Copy link
Member

I am going to classify this is as "working as intended". The server is closing the connection after the response body has been entirely sent, to prevent resource leaks. You did not read the entire request body in the server, which means that the client may not have sent the entire request body and thus is still trying to write it. While the client is doing this, the server closed the connection because the request is done. The client notices this and errors out because it was unable to fully send it's request body.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working correctly
Projects
None yet
Development

No branches or pull requests

4 participants