Skip to content

Commit

Permalink
Only skip past the end boundary if there is a newline character
Browse files Browse the repository at this point in the history
  • Loading branch information
sebmarkbage committed Jun 14, 2023
1 parent 90229eb commit 55e0b47
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 8 deletions.
16 changes: 10 additions & 6 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,7 @@ export function processBinaryChunk(
const buffer = response._buffer;
const chunkLength = chunk.length;
while (i < chunkLength) {
let lastIdx = -1;
let lastIdx;
switch (rowState) {
case ROW_ID: {
const byte = chunk[i++];
Expand Down Expand Up @@ -950,29 +950,33 @@ export function processBinaryChunk(
}
case ROW_CHUNK_BY_LENGTH: {
// We're looking for the remaining byte length
if (i + rowLength <= chunk.length) {
lastIdx = i + rowLength;
lastIdx = i + rowLength;
if (lastIdx > chunk.length) {
lastIdx = -1;
}
break;
}
}
const offset = chunk.byteOffset + i;
if (lastIdx > -1) {
// We found the last chunk of the row
const offset = chunk.byteOffset + i;
const length = lastIdx - i;
const lastChunk = new Uint8Array(chunk.buffer, offset, length);
processFullRow(response, rowID, rowTag, buffer, lastChunk);
// Reset state machine for a new row
i = lastIdx;
if (rowState === ROW_CHUNK_BY_NEWLINE) {
// If we're trailing by a newline we need to skip it.
i++;
}
rowState = ROW_ID;
rowTag = 0;
rowID = 0;
rowLength = 0;
buffer.length = 0;
i = lastIdx + 1;
} else {
// The rest of this row is in a future chunk. We stash the rest of the
// current chunk until we can process the full row.
const offset = chunk.byteOffset + i;
const length = chunk.byteLength - i;
const remainingSlice = new Uint8Array(chunk.buffer, offset, length);
buffer.push(remainingSlice);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,37 @@ describe('ReactFlightDOMEdge', () => {
use = React.use;
});

function passThrough(stream) {
// Simulate more realistic network by splitting up and rejoining some chunks.
// This lets us test that we don't accidentally rely on particular bounds of the chunks.
return new ReadableStream({
async start(controller) {
const reader = stream.getReader();
let prevChunk = new Uint8Array(0);
function push() {
reader.read().then(({done, value}) => {
if (done) {
controller.enqueue(prevChunk);
controller.close();
return;
}
const chunk = new Uint8Array(prevChunk.length + value.length);
chunk.set(prevChunk, 0);
chunk.set(value, prevChunk.length);
if (chunk.length > 50) {
controller.enqueue(chunk.subarray(0, chunk.length - 50));
prevChunk = chunk.subarray(chunk.length - 50);
} else {
prevChunk = chunk;
}
push();
});
}
push();
},
});
}

async function readResult(stream) {
const reader = stream.getReader();
let result = '';
Expand Down Expand Up @@ -101,15 +132,17 @@ describe('ReactFlightDOMEdge', () => {

it('should encode long string in a compact format', async () => {
const testString = '"\n\t'.repeat(500) + '🙃';
const testString2 = 'hello'.repeat(400);

const stream = ReactServerDOMServer.renderToReadableStream({
text: testString,
text2: testString2,
});
const [stream1, stream2] = stream.tee();
const [stream1, stream2] = passThrough(stream).tee();

const serializedContent = await readResult(stream1);
// The content should be compact an unescaped
expect(serializedContent.length).toBeLessThan(2000);
expect(serializedContent.length).toBeLessThan(4000);
expect(serializedContent).not.toContain('\\n');
expect(serializedContent).not.toContain('\\t');
expect(serializedContent).not.toContain('\\"');
Expand All @@ -118,5 +151,6 @@ describe('ReactFlightDOMEdge', () => {
const result = await ReactServerDOMClient.createFromReadableStream(stream2);
// Should still match the result when parsed
expect(result.text).toBe(testString);
expect(result.text2).toBe(testString2);
});
});

0 comments on commit 55e0b47

Please sign in to comment.