Repository: sindresorhus/got
Pull request #2098 (2022-08-03 18:55:12)

Add note about empty request body
#### Checklist

- [x] I have read the documentation.
- [x] I have included a pull request description of my changes.
- [ ] I have included some tests.
- [ ] If it's a new feature, I have included documentation updates in both the README and the types.

I literally spent hours trying to figure out why my requests were hanging, when sending a POST, but not when sending a GET. And only when using isStream: true, not with isStream: false

sindresorhus: We should either just gracefully handle this or throw an error.
szmarczak: The note is already here.

![image](https://user-images.githubusercontent.com/36894700/182821309-86d3d66a-151b-4e78-8ef9-11a7706fa6a1.png)

mifi: Thanks! I missed that. Maybe I can add an example for the no-body use case too?

Patch:
diff --git a/documentation/3-streams.md b/documentation/3-streams.md
index ec029dc..9070e43 100644
--- a/documentation/3-streams.md
+++ b/documentation/3-streams.md
@@ -22,6 +22,10 @@ This constructor takes the same arguments as the Got promise.
 **Note:**
 > If the `body`, `json` or `form` option is used, this stream will be read-only.
 
+**Note:**
+> - While `got.post('https://example.com')` resolves, `got.stream.post('https://example.com')` will hang indefinitely until a body is provided.
+> - If there's no body on purpose, remember to `stream.end()` or set the body option to an empty string.
+
 ```js
 import {promisify} from 'node:util';
 import stream from 'node:stream';
@@ -30,17 +34,25 @@ import got from 'got';
 
 const pipeline = promisify(stream.pipeline);
 
+// This example streams the GET response of a URL to a file.
 await pipeline(
 	got.stream('https://sindresorhus.com'),
 	fs.createWriteStream('index.html')
 );
 
 // For POST, PUT, PATCH, and DELETE methods, `got.stream` returns a `stream.Writable`.
+// This example POSTs the contents of a file to a URL.
 await pipeline(
 	fs.createReadStream('index.html'),
 	got.stream.post('https://sindresorhus.com'),
 	new stream.PassThrough()
 );
+
+// In order to POST, PUT, PATCH, or DELETE without a request body, explicitly specify an empty body:
+await pipeline(
+	got.stream.post('https://sindresorhus.com', { body: '' }),
+	new stream.PassThrough()
+)
 ```
 
 Please note that `new stream.PassThrough()` is required in order to catch read errors.\
@@ -50,10 +62,6 @@ In other words, it would only check errors when writing.
 **Tip:**
 > - Avoid `from.pipe(to)` as it doesn't forward errors.
 
-**Note:**
-> - While `got.post('https://example.com')` resolves, `got.stream.post('https://example.com')` will hang indefinitely until a body is provided.
-> - If there's no body on purpose, remember to `stream.end()` or set the body option to an empty string.
-
 ### `stream.options`
 
 **Type: [`Options`](2-options.md)**
diff --git a/readme.md b/readme.md
index 8718b25..3195ec3 100644
--- a/readme.md
+++ b/readme.md
@@ -139,6 +139,8 @@ For advanced JSON usage, check out the [`parseJson`](documentation/2-options.md#
 
 ## Documentation
 
+By default, Got will retry on failure. To disable this option, set [`options.retry.limit`](documentation/7-retry.md#retry) to 0.
+
 #### Main API
 
 - [x] [Promise API](documentation/1-promise.md)
diff --git a/source/core/options.ts b/source/core/options.ts
index 38191f3..110e763 100644
--- a/source/core/options.ts
+++ b/source/core/options.ts
@@ -2521,6 +2521,5 @@ export default class Options {
 		Object.freeze(options.retry.methods);
 		Object.freeze(options.retry.statusCodes);
 		Object.freeze(options.context);
-		Object.freeze(options.signal);
 	}
 }


Test patch:
diff --git a/test/abort.ts b/test/abort.ts
index 3312b09..fea2c1c 100644
--- a/test/abort.ts
+++ b/test/abort.ts
@@ -271,4 +271,17 @@ if (globalThis.AbortController !== undefined) {
 			message: 'This operation was aborted.',
 		});
 	});
+
+	test('support setting the signal as a default option', async t => {
+		const controller = new AbortController();
+
+		const got2 = got.extend({signal: controller.signal});
+		const p = got2('http://example.com', {signal: controller.signal});
+		controller.abort();
+
+		await t.throwsAsync(p, {
+			code: 'ERR_ABORTED',
+			message: 'This operation was aborted.',
+		});
+	});
 }
diff --git a/test/normalize-arguments.ts b/test/normalize-arguments.ts
index dc0edcc..ca25c67 100644
--- a/test/normalize-arguments.ts
+++ b/test/normalize-arguments.ts
@@ -167,3 +167,18 @@ test('searchParams - multiple values for one key', t => {
 		['100', '200', '300'],
 	);
 });
+
+if (globalThis.AbortSignal !== undefined) {
+	test('signal does not get frozen', t => {
+		const controller = new AbortController();
+		const {signal} = controller;
+
+		const options = new Options({
+			url: new URL('http://localhost'),
+			signal,
+		});
+		options.freeze();
+
+		t.is(Object.isFrozen(options.signal), false);
+	});
+}