Title: Problems with cooperation between http and pipeline/"for await" · Issue #38262 · nodejs/node · GitHub
Open Graph Title: Problems with cooperation between http and pipeline/"for await" · Issue #38262 · nodejs/node
X Title: Problems with cooperation between http and pipeline/"for await" · Issue #38262 · nodejs/node
Description: Is your feature request related to a problem? Please describe. Problem is that "for await" and pipeline destroy source stream on error ("for await" also on break) but destroying http request removes possibility to send meaningful respons...
Open Graph Description: Is your feature request related to a problem? Please describe. Problem is that "for await" and pipeline destroy source stream on error ("for await" also on break) but destroying http request remove...
X Description: Is your feature request related to a problem? Please describe. Problem is that "for await" and pipeline destroy source stream on error ("for await" also on break) but destroying...
Opengraph URL: https://github.com/nodejs/node/issues/38262
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Problems with cooperation between http and pipeline/\"for await\"","articleBody":"**Is your feature request related to a problem? Please describe.**\r\n\r\nProblem is that \"for await\" and pipeline destroy source stream on error (\"for await\" also on break) but destroying http request removes possibility to send meaningful response to client.\r\n\r\nExample with \"for await\" (body length limiting):\r\n``` js\r\nlet http = require(\"http\");\r\n\r\nlet server = http.createServer(async function(req, res)\r\n{\r\n\ttry\r\n\t{\r\n\t\tlet len = 0;\r\n\t\tfor await (let chunk of req)\r\n\t\t{\r\n\t\t\tlen += chunk.length;\r\n\t\t\tif(len > 2) throw \"payload too large\";\r\n\t\t}\r\n\t\tres.end(\"ok\");\r\n\t}\r\n\tcatch(err)\r\n\t{\r\n\t\tconsole.log(\"server log:\", err);\r\n\t\tres.end(err);\r\n\t}\r\n});\r\n\r\n(async function()\r\n{\r\n\tawait new Promise(resolve => server.listen(8888, resolve));\r\n\tlet req = http.request({ port: 8888, method: \"post\" });\r\n\treq.end(\"abc\");\r\n\ttry\r\n\t{\r\n\t\tlet res = await new Promise((resolve, reject) => req.on(\"response\", resolve).on(\"error\", reject));\r\n\t\tlet str = \"\";\r\n\t\tfor await (let chunk of res) str += chunk;\r\n\t\tconsole.log(\"client received:\", str);\r\n\t}\r\n\tcatch(err)\r\n\t{\r\n\t\tconsole.log(\"client got unexpected error:\", err);\r\n\t}\r\n\tserver.close();\r\n}());\r\n```\r\n\r\nPossible output:\r\n```\r\nserver log: payload too large\r\nclient got unexpected error: Error: socket hang up\r\n at connResetException (node:internal/errors:642:14)\r\n at Socket.socketOnEnd (node:_http_client:486:23)\r\n at Socket.emit (node:events:381:22)\r\n at endReadableNT (node:internal/streams/readable:1307:12)\r\n at processTicksAndRejections (node:internal/process/task_queues:81:21) {\r\n code: 'ECONNRESET'\r\n}\r\n```\r\n\r\nPossible solution is to not use pipeline but pipe instead. But this requires additional handling like to do `req.unpipe` and `req.resume` to avoid problems for example:\r\n``` js\r\nlet http = require(\"http\");\r\nlet { PassThrough } = require(\"stream\");\r\n\r\nlet server = http.createServer(async function(req, res)\r\n{\r\n\ttry\r\n\t{\r\n\t\tlet len = 0;\r\n\t\tfor await (let chunk of req.pipe(new PassThrough()))\r\n\t\t{\r\n\t\t\tlen += chunk.length;\r\n\t\t\tif(len > 2) throw \"payload too large\";\r\n\t\t}\r\n\t\tres.end(\"ok\");\r\n\t}\r\n\tcatch(err)\r\n\t{\r\n\t\tconsole.log(\"server log:\", err);\r\n\t\treq.unpipe();\r\n\t\treq.resume();\r\n\t\treq.on(\"end\", () => res.end(err));\r\n\t}\r\n});\r\n\r\n(async function()\r\n{\r\n\tawait new Promise(resolve => server.listen(8888, resolve));\r\n\tlet req = http.request({ port: 8888, method: \"post\" });\r\n\treq.end(\"abc\");\r\n\ttry\r\n\t{\r\n\t\tlet res = await new Promise((resolve, reject) => req.on(\"response\", resolve).on(\"error\", reject));\r\n\t\tlet str = \"\";\r\n\t\tfor await (let chunk of res) str += chunk;\r\n\t\tconsole.log(\"client received:\", str);\r\n\t}\r\n\tcatch(err)\r\n\t{\r\n\t\tconsole.log(\"client got unexpected error:\", err);\r\n\t}\r\n\tserver.close();\r\n}());\r\n```\r\n\r\nThis neglected solution with forgotten `unpipe` and `resume` cases EPIPE sometimes or stops after first iteration when using keepalive `Agent`:\r\n\r\n``` js\r\nlet http = require(\"http\");\r\nlet { PassThrough } = require(\"stream\");\r\n\r\nlet server = http.createServer(async function(req, res)\r\n{\r\n\ttry\r\n\t{\r\n\t\tlet len = 0;\r\n\t\tfor await (let chunk of req.pipe(new PassThrough()))\r\n\t\t{\r\n\t\t\tlen += chunk.length;\r\n\t\t\tif(len > 2) throw \"payload too large\";\r\n\t\t}\r\n\t\tres.end(\"ok\");\r\n\t}\r\n\tcatch(err)\r\n\t{\r\n\t\tres.end(err);\r\n\t}\r\n});\r\n\r\n(async function()\r\n{\r\n\tawait new Promise(resolve => server.listen(8888, resolve));\r\n\tlet agent = new http.Agent({ keepAlive: true });\r\n\tlet data = Buffer.alloc(1000000);\r\n\tfor(let i = 0; ; i++)\r\n\t{\r\n\t\tconsole.log(i);\r\n\t\tlet req = http.request({ port: 8888, method: \"post\"/*, agent*/ }).end(data);\r\n\t\tlet res = await new Promise((resolve, reject) => req.on(\"response\", resolve).on(\"error\", reject));\r\n\t\tlet str = \"\";\r\n\t\tfor await (let chunk of res) str += chunk;\r\n\t\tconsole.log(str);\r\n\t}\r\n}());\r\n```\r\n\r\n```\r\n2572\r\npayload too large\r\n2573\r\nnode:internal/process/promises:245\r\n triggerUncaughtException(err, true /* fromPromise */);\r\n ^\r\n\r\nError: write EPIPE\r\n at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:94:16) {\r\n errno: -32,\r\n code: 'EPIPE',\r\n syscall: 'write'\r\n}\r\n```\r\n\r\n**Describe the solution you'd like**\r\n\r\nPipe is too leak-prone for language which builds on GC. But a fixed and more comfortable pipeline and \"for await\" have unexpected consequences for http streams.\r\n\r\nWould be great to have some options for the `pipeline` function to not destroy streams but instead dump them to the end in case of error (so one can send response when the pipeline ends). Or http request could have the option that `destroy` would just dump stream to end (and only then actually send response) instead of causing EPIPE or similar faults at client side.\r\n","author":{"url":"https://github.com/misos1","@type":"Person","name":"misos1"},"datePublished":"2021-04-16T19:03:20Z","url":"https://github.com/nodejs/node/issues/38262"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:90f455c3-f2da-3430-4952-c53bff88e443 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | E56C:20309E:7E038E:A50ADB:6AED364F |
| html-safe-nonce | f46a357934c92792955eb207d31b74e17c4f26046ccda0880cf8807d5ce17fc2 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFNTZDOjIwMzA5RTo3RTAzOEU6QTUwQURCOjZBRUQzNjRGIiwidmlzaXRvcl9pZCI6IjQ1NzczODYzNDI2NzU5ODU5OTkiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | db3ba1a5277ef747cd0fa07deaae5222a35172a57fa130c156a458ca859f6885 |
| hovercard-subject-tag | issue:860091545 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/38262/issue_layout |
| twitter:image | https://opengraph.githubassets.com/16ea08b75c6aaf5d36363303fbbab14944f9d33873ee6703c7a0161bdd495e9b/nodejs/node/issues/38262 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/16ea08b75c6aaf5d36363303fbbab14944f9d33873ee6703c7a0161bdd495e9b/nodejs/node/issues/38262 |
| og:image:alt | Is your feature request related to a problem? Please describe. Problem is that "for await" and pipeline destroy source stream on error ("for await" also on break) but destroying http request remove... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | misos1 |
| hostname | github.com |
| expected-hostname | github.com |
| None | 26c92a9388ddfad3fa88d132b5d5f0b4747030233dcc4e5efb05f1cbae152184 |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | c629ad99c254d306375fc8354d04321c2c362236 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width