Skip to content

Commit 030dfbb

Browse files
committed
Add stream.isReadonly property to detect read-only streams
Fixes #1927
1 parent 398c11a commit 030dfbb

File tree

3 files changed

+54
-4
lines changed

3 files changed

+54
-4
lines changed

documentation/3-streams.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ This constructor takes the same arguments as the Got promise.
2121
> To filter which headers are copied, listen to the `response` event and modify `response.headers` before piping to the destination.
2222
2323
**Note:**
24-
> If the `body`, `json` or `form` option is used, this stream will be read-only.
24+
> If the `body`, `json` or `form` option is used, this stream will be read-only. Check [`stream.isReadonly`](#streamisreadonly) to detect this condition.
2525
2626
**Note:**
2727
> - While `got.post('https://example.com')` resolves, `got.stream.post('https://example.com')` will hang indefinitely until a body is provided.
@@ -169,6 +169,12 @@ Whether the response has been fetched from cache.
169169

170170
Whether the socket was used for other previous requests.
171171

172+
### `stream.isReadonly`
173+
174+
**Type: `boolean`**
175+
176+
Whether the stream is read-only. Returns `true` when `body`, `json`, or `form` options are provided.
177+
172178
## Events
173179

174180
### `stream.on('response', …)`

source/core/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,14 +1471,14 @@ export default class Request extends Duplex implements RequestEvents<Request> {
14711471

14721472
if (isClientRequest(requestOrResponse!)) {
14731473
this._onRequest(requestOrResponse);
1474-
} else if (this.writable) {
1474+
} else if (this.writableEnded) {
1475+
void this._onResponse(requestOrResponse as IncomingMessageWithTimings);
1476+
} else {
14751477
this.once('finish', () => {
14761478
void this._onResponse(requestOrResponse as IncomingMessageWithTimings);
14771479
});
14781480

14791481
this._sendBody();
1480-
} else {
1481-
void this._onResponse(requestOrResponse as IncomingMessageWithTimings);
14821482
}
14831483
} catch (error) {
14841484
if (error instanceof CacheableCacheError) {
@@ -1642,4 +1642,11 @@ export default class Request extends Duplex implements RequestEvents<Request> {
16421642
get reusedSocket(): boolean | undefined {
16431643
return this._request?.reusedSocket;
16441644
}
1645+
1646+
/**
1647+
Whether the stream is read-only. Returns `true` when `body`, `json`, or `form` options are provided.
1648+
*/
1649+
get isReadonly(): boolean {
1650+
return !is.undefined(this.options?.body) || !is.undefined(this.options?.json) || !is.undefined(this.options?.form);
1651+
}
16451652
}

test/stream.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,3 +657,40 @@ test('validates content-length for gzip compressed responses', withServer, async
657657
},
658658
);
659659
});
660+
661+
test('isReadonly is false for streams without body', withServer, async (t, server, got) => {
662+
server.post('/', postHandler);
663+
664+
const stream = got.stream.post('');
665+
t.false(stream.isReadonly);
666+
667+
stream.end('data');
668+
await getStream(stream);
669+
});
670+
671+
test('isReadonly is true when body option is provided', withServer, async (t, server, got) => {
672+
server.post('/', postHandler);
673+
674+
const stream = got.stream.post({body: 'test data'});
675+
t.true(stream.isReadonly);
676+
677+
await t.notThrowsAsync(getStream(stream));
678+
});
679+
680+
test('isReadonly is true when json option is provided', withServer, async (t, server, got) => {
681+
server.post('/', postHandler);
682+
683+
const stream = got.stream.post({json: {foo: 'bar'}});
684+
t.true(stream.isReadonly);
685+
686+
await t.notThrowsAsync(getStream(stream));
687+
});
688+
689+
test('isReadonly is true when form option is provided', withServer, async (t, server, got) => {
690+
server.post('/', postHandler);
691+
692+
const stream = got.stream.post({form: {foo: 'bar'}});
693+
t.true(stream.isReadonly);
694+
695+
await t.notThrowsAsync(getStream(stream));
696+
});

0 commit comments

Comments
 (0)