Skip to content

Commit 2aff4e4

Browse files
authored
Clean up onDataV2 and collectBody code, and update their documentation (#1256)
1 parent 0b0c8f9 commit 2aff4e4

2 files changed

Lines changed: 42 additions & 64 deletions

File tree

docs/index.d.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,17 +195,18 @@ export interface HttpResponse {
195195
/** Resume HTTP request body streaming (unthrottle). */
196196
resume() : void;
197197

198-
/** Accumulates all data chunks and calls handler with the complete body as an ArrayBuffer once all data has arrived.
198+
/** collectBody is a helper function making optimal use of the new onDataV2.
199+
* It allows efficient and easy collection of smallish HTTP request body data into RAM.
200+
* It accumulates all data chunks and calls handler with the complete body as an ArrayBuffer once all data has arrived.
199201
* If the total body size exceeds maxSize bytes, handler is called with null instead. */
200202
collectBody(maxSize: number, handler: (fullBody: ArrayBuffer | null) => void) : HttpResponse;
201203

202204
/** Handler for reading HTTP request body data. V2.
203205
* Must be attached before performing any asynchronous operation, otherwise data may be lost.
204-
* You MUST copy the data of chunk if maxRemainingBodyLength is not 0. We Neuter ArrayBuffers on return, making them zero length.
205-
*
206+
* You MUST copy the data of chunk if maxRemainingBodyLength is not 0n. We Neuter ArrayBuffers on return, making them zero length.
206207
* maxRemainingBodyLength is the known maximum of the remaining body length. Can be used to preallocate a receive buffer.
207208
*/
208-
onDataV2(handler: (chunk: ArrayBuffer | null, maxRemainingBodyLength: bigint) => void) : HttpResponse;
209+
onDataV2(handler: (chunk: ArrayBuffer, maxRemainingBodyLength: bigint) => void) : HttpResponse;
209210

210211
/** Returns the remote IP address in binary format (4 or 16 bytes). */
211212
getRemoteAddress() : ArrayBuffer;

src/HttpResponseWrapper.h

Lines changed: 37 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -122,16 +122,6 @@ struct HttpResponseWrapper {
122122
Isolate *isolate = args.GetIsolate();
123123
auto *res = getHttpResponse<SSL>(args);
124124
if (res) {
125-
126-
/* This is how we capture res (C++ this in invocation of this function) */
127-
UniquePersistent<Object> resObject(isolate, args.This());
128-
129-
res->onAborted([resObject = std::move(resObject), isolate]() {
130-
HandleScope hs(isolate);
131-
/* Mark this resObject invalid */
132-
Local<Object>::New(isolate, resObject)->SetAlignedPointerInInternalField(0, nullptr);
133-
});
134-
135125
size_t maxSize = (size_t) args[0]->NumberValue(isolate->GetCurrentContext()).ToChecked();
136126

137127
/* This thing perfectly fits in with unique_function, and will Reset on destructor */
@@ -142,49 +132,43 @@ struct HttpResponseWrapper {
142132
std::unique_ptr<std::vector<char>> buffer;
143133
bool overflow = false;
144134

145-
res->onDataV2([res, p = std::move(p), buffer = std::move(buffer), overflow, maxSize, isolate](std::string_view data, uint64_t maxRemainingBodyLength) mutable {
135+
res->onDataV2([p = std::move(p), buffer = std::move(buffer), overflow, maxSize, isolate](std::string_view data, uint64_t maxRemainingBodyLength) mutable {
146136
HandleScope hs(isolate);
147137

148-
if (!overflow) {
149-
if (!buffer) {
150-
/* Fast path: this is the very first (and possibly only) chunk */
151-
if (maxRemainingBodyLength == 0) {
152-
if (data.size() <= maxSize) {
153-
/* Single-chunk zero-copy: wrap data directly, detach after call like onData */
154-
Local<ArrayBuffer> ab = ArrayBuffer_New(isolate, (void *) data.data(), data.size());
155-
Local<Value> argv[] = {ab};
156-
CallJS(isolate, Local<Function>::New(isolate, p), 1, argv);
157-
ab->Detach();
158-
} else {
159-
Local<Value> argv[] = {Null(isolate)};
160-
CallJS(isolate, Local<Function>::New(isolate, p), 1, argv);
161-
}
162-
return;
163-
}
138+
if (overflow) {
139+
return;
140+
} else if (!buffer) {
141+
/* First and possibly only chunk */
142+
if (data.size() > maxSize) {
143+
/* Overflow: return to JS with null */
144+
overflow = true;
145+
Local<Value> argv[] = {Null(isolate)};
146+
CallJS(isolate, Local<Function>::New(isolate, p), 1, argv);
147+
} else if (maxRemainingBodyLength == 0) {
148+
/* Fast path: Single-chunk zero-copy: wrap data directly, detach after call like onData */
149+
Local<ArrayBuffer> ab = ArrayBuffer_New(isolate, (void *) data.data(), data.size());
150+
Local<Value> argv[] = {ab};
151+
CallJS(isolate, Local<Function>::New(isolate, p), 1, argv);
152+
ab->Detach();
153+
} else {
164154
/* Slow path begins: allocate buffer lazily for first non-terminal chunk */
165-
if (data.size() <= maxSize) {
166-
buffer = std::make_unique<std::vector<char>>();
155+
buffer = std::make_unique<std::vector<char>>();
156+
if (maxRemainingBodyLength <= maxSize - data.size()) {
167157
/* Preallocate with hint */
168-
if (maxRemainingBodyLength <= maxSize) {
169-
buffer->reserve(maxRemainingBodyLength); // this includes the total size on first call (look over this)
170-
}
171-
buffer->assign(data.begin(), data.end());
172-
} else {
173-
overflow = true;
174-
}
175-
} else {
176-
/* Subsequent chunks: accumulate or mark overflow; guard both sides of subtraction */
177-
if (buffer->size() <= maxSize && data.size() <= maxSize - buffer->size()) {
178-
buffer->insert(buffer->end(), data.begin(), data.end());
179-
} else {
180-
buffer.reset();
181-
overflow = true;
158+
buffer->reserve(maxRemainingBodyLength + data.size());
182159
}
160+
buffer->assign(data.begin(), data.end());
183161
}
184-
}
185-
186-
if (maxRemainingBodyLength == 0) {
187-
if (!overflow) {
162+
} else if (data.size() > maxSize - buffer->size()) {
163+
/* Subsequent chunks Overflow: return to JS with null */
164+
buffer.reset();
165+
overflow = true;
166+
Local<Value> argv[] = {Null(isolate)};
167+
CallJS(isolate, Local<Function>::New(isolate, p), 1, argv);
168+
} else {
169+
/* Subsequent chunks: accumulate */
170+
buffer->insert(buffer->end(), data.begin(), data.end());
171+
if (maxRemainingBodyLength == 0) {
188172
/* Zero-copy: hand V8 the vector's own memory via a custom deleter */
189173
auto *rawBuffer = buffer.release();
190174
auto backingStore = ArrayBuffer::NewBackingStore(
@@ -197,9 +181,6 @@ struct HttpResponseWrapper {
197181
Local<ArrayBuffer> ab = ArrayBuffer::New(isolate, std::move(backingStore));
198182
Local<Value> argv[] = {ab};
199183
CallJS(isolate, Local<Function>::New(isolate, p), 1, argv);
200-
} else {
201-
Local<Value> argv[] = {Null(isolate)};
202-
CallJS(isolate, Local<Function>::New(isolate, p), 1, argv);
203184
}
204185
}
205186
});
@@ -208,28 +189,24 @@ struct HttpResponseWrapper {
208189
}
209190
}
210191

211-
/* Takes a function of (chunk, maxRemainingBodyLength). Combines onAborted and onData into a single callback.
212-
* If chunk is null, the connection was aborted. If maxRemainingBodyLength is 0, the last chunk has arrived.
213-
* The JS object is invalidated before the abort callback is called. Returns this. */
192+
/* Takes function of chunk and maxRemainingBodyLength. Returns this.
193+
* If maxRemainingBodyLength is 0, the last chunk has arrived. */
214194
template <int SSL>
215195
static void res_onDataV2(const FunctionCallbackInfo<Value> &args) {
216196
Isolate *isolate = args.GetIsolate();
217197
auto *res = getHttpResponse<SSL>(args);
218198
if (res) {
219-
/* Share the persistent function between both onAborted and onData lambdas */
220-
auto sharedP = std::make_shared<UniquePersistent<Function>>(isolate, Local<Function>::Cast(args[0]));
221-
222-
/* This is how we capture res (C++ this in invocation of this function) */
223-
UniquePersistent<Object> resObject(isolate, args.This());
199+
/* This thing perfectly fits in with unique_function, and will Reset on destructor */
200+
UniquePersistent<Function> p(isolate, Local<Function>::Cast(args[0]));
224201

225-
res->onDataV2([res, sharedP, isolate](std::string_view data, uint64_t maxRemainingBodyLength) {
202+
res->onDataV2([p = std::move(p), isolate](std::string_view data, uint64_t maxRemainingBodyLength) {
226203
HandleScope hs(isolate);
227204

228205
Local<ArrayBuffer> dataArrayBuffer = ArrayBuffer_New(isolate, (void *) data.data(), data.length());
229206

230207
/* Pass maxRemainingBodyLength so user can preallocate; 0 signals the last chunk */
231208
Local<Value> argv[] = {dataArrayBuffer, BigInt::NewFromUnsigned(isolate, maxRemainingBodyLength)};
232-
CallJS(isolate, Local<Function>::New(isolate, *sharedP), 2, argv);
209+
CallJS(isolate, Local<Function>::New(isolate, p), 2, argv);
233210

234211
dataArrayBuffer->Detach();
235212
});

0 commit comments

Comments
 (0)