From b8b633a442631982078fd582082e15862fefa769 Mon Sep 17 00:00:00 2001 From: Marais Rossouw Date: Sat, 14 Nov 2020 18:59:06 +1000 Subject: [PATCH 1/7] feat: remove the need for content-length --- src/__tests__/http-test.ts | 28 ----------------------- src/index.ts | 46 ++++++++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 45 deletions(-) diff --git a/src/__tests__/http-test.ts b/src/__tests__/http-test.ts index 1fec0933..8418d2b0 100644 --- a/src/__tests__/http-test.ts +++ b/src/__tests__/http-test.ts @@ -1074,16 +1074,12 @@ function runTests(server: Server) { '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 26', '', '{"data":{},"hasNext":true}', - '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 78', '', '{"data":{"test":"Hello World"},"path":[],"label":"deferLabel","hasNext":false}', - '', '-----', '', ].join('\r\n'), @@ -1204,13 +1200,10 @@ function runTests(server: Server) { '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 35', '', ['{', ' "data": {},', ' "hasNext": true', '}'].join('\n'), - '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 79', '', [ '{', @@ -1221,7 +1214,6 @@ function runTests(server: Server) { ' "hasNext": false', '}', ].join('\n'), - '', '-----', '', ].join('\r\n'), @@ -1413,16 +1405,12 @@ function runTests(server: Server) { '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 94', '', '{"errors":[{"message":"Custom error format: Throws!"}],"data":{"thrower":null},"hasNext":true}', - '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 57', '', '{"data":{"test":"Hello World"},"path":[],"hasNext":false}', - '', '-----', '', ].join('\r\n'), @@ -1464,16 +1452,12 @@ function runTests(server: Server) { '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 46', '', '{"data":{"test":"Hello World"},"hasNext":true}', - '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 105', '', '{"data":{"thrower":null},"path":[],"errors":[{"message":"Custom error format: Throws!"}],"hasNext":false}', - '', '-----', '', ].join('\r\n'), @@ -2426,16 +2410,12 @@ function runTests(server: Server) { '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 48', '', '{"data":{"test2":"Modification"},"hasNext":true}', - '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 64', '', '{"errors":[{"message":"I did something wrong"}],"hasNext":false}', - '', '-----', '', ].join('\r\n'), @@ -2721,16 +2701,12 @@ function runTests(server: Server) { '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 124', '', '{"data":{"hello":"Hello Rob"},"hasNext":true,"extensions":{"preservedResult":{"data":{"hello":"Hello Rob"},"hasNext":true}}}', - '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 148', '', '{"data":{"test":"Hello World"},"path":[],"hasNext":false,"extensions":{"preservedResult":{"data":{"test":"Hello World"},"path":[],"hasNext":false}}}', - '', '-----', '', ].join('\r\n'), @@ -2801,16 +2777,12 @@ function runTests(server: Server) { '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 45', '', '{"data":{"hello":"Hello Rob"},"hasNext":true}', - '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 57', '', '{"data":{"test":"Hello World"},"path":[],"hasNext":false}', - '', '-----', '', ].join('\r\n'), diff --git a/src/index.ts b/src/index.ts index 05161631..ab46dde1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -462,7 +462,8 @@ export function graphqlHTTP(options: Options): Middleware { if (isAsyncIterable(executeResult)) { response.setHeader('Content-Type', 'multipart/mixed; boundary="-"'); - sendPartialResponse(pretty, response, formattedResult); + // There will always be a next, either a data payload, or an error + sendPartialResponse(pretty, response, formattedResult, true, true); try { for await (let payload of executeResult) { // Collect and apply any metadata extensions if a function was provided. @@ -479,15 +480,25 @@ export function graphqlHTTP(options: Options): Middleware { ...(payload as ExecutionPatchResult), errors: payload.errors?.map(formatErrorFn), }; - sendPartialResponse(pretty, response, formattedPayload); + sendPartialResponse( + pretty, + response, + formattedPayload, + formattedPayload.hasNext, + ); } } catch (rawError: unknown) { const graphqlError = getGraphQlError(rawError); - sendPartialResponse(pretty, response, { - data: undefined, - errors: [formatErrorFn(graphqlError)], - hasNext: false, - }); + sendPartialResponse( + pretty, + response, + { + data: undefined, + errors: [formatErrorFn(graphqlError)], + hasNext: false, + }, + false, + ); } response.write('\r\n-----\r\n'); response.end(); @@ -622,19 +633,20 @@ function sendPartialResponse( pretty: boolean, response: Response, result: FormattedExecutionResult | FormattedExecutionPatchResult, + hasNext: boolean, + initial: boolean = false, ): void { const json = JSON.stringify(result, null, pretty ? 2 : 0); const chunk = Buffer.from(json, 'utf8'); - const data = [ - '', - '---', - 'Content-Type: application/json; charset=utf-8', - 'Content-Length: ' + String(chunk.length), - '', - chunk, - '', - ].join('\r\n'); - response.write(data); + const data = ['Content-Type: application/json; charset=utf-8', '', chunk]; + if (initial) { + data.unshift('---'); + } + data.unshift(''); + if (hasNext) { + data.push('---'); + } + response.write(data.join('\r\n')); // flush response if compression middleware is used if ( typeof response.flush === 'function' && From 198a37edd1bc45e50489f75daae39d0f8610ee86 Mon Sep 17 00:00:00 2001 From: Marais Rossouw Date: Wed, 18 Nov 2020 11:01:00 +1000 Subject: [PATCH 2/7] chore: simplify initial/hasNext arguments --- src/index.ts | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/src/index.ts b/src/index.ts index ab46dde1..65a56219 100644 --- a/src/index.ts +++ b/src/index.ts @@ -462,8 +462,12 @@ export function graphqlHTTP(options: Options): Middleware { if (isAsyncIterable(executeResult)) { response.setHeader('Content-Type', 'multipart/mixed; boundary="-"'); - // There will always be a next, either a data payload, or an error - sendPartialResponse(pretty, response, formattedResult, true, true); + response.write('\r\n---\r\n'); + sendPartialResponse( + pretty, + response, + formattedResult as FormattedExecutionPatchResult, + ); try { for await (let payload of executeResult) { // Collect and apply any metadata extensions if a function was provided. @@ -480,25 +484,15 @@ export function graphqlHTTP(options: Options): Middleware { ...(payload as ExecutionPatchResult), errors: payload.errors?.map(formatErrorFn), }; - sendPartialResponse( - pretty, - response, - formattedPayload, - formattedPayload.hasNext, - ); + sendPartialResponse(pretty, response, formattedPayload); } } catch (rawError: unknown) { const graphqlError = getGraphQlError(rawError); - sendPartialResponse( - pretty, - response, - { - data: undefined, - errors: [formatErrorFn(graphqlError)], - hasNext: false, - }, - false, - ); + sendPartialResponse(pretty, response, { + data: undefined, + errors: [formatErrorFn(graphqlError)], + hasNext: false, + }); } response.write('\r\n-----\r\n'); response.end(); @@ -632,19 +626,13 @@ function canDisplayGraphiQL(request: Request, params: GraphQLParams): boolean { function sendPartialResponse( pretty: boolean, response: Response, - result: FormattedExecutionResult | FormattedExecutionPatchResult, - hasNext: boolean, - initial: boolean = false, + result: FormattedExecutionPatchResult, ): void { const json = JSON.stringify(result, null, pretty ? 2 : 0); const chunk = Buffer.from(json, 'utf8'); const data = ['Content-Type: application/json; charset=utf-8', '', chunk]; - if (initial) { - data.unshift('---'); - } - data.unshift(''); - if (hasNext) { - data.push('---'); + if (result.hasNext) { + data.push('---\r\n'); } response.write(data.join('\r\n')); // flush response if compression middleware is used From 263dd7203bed2e6c1e1989670f31a725f6fab2cb Mon Sep 17 00:00:00 2001 From: Marais Rossouw Date: Wed, 18 Nov 2020 11:17:46 +1000 Subject: [PATCH 3/7] chore: ts-ignore for now --- src/index.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index 65a56219..6a5593be 100644 --- a/src/index.ts +++ b/src/index.ts @@ -463,11 +463,7 @@ export function graphqlHTTP(options: Options): Middleware { if (isAsyncIterable(executeResult)) { response.setHeader('Content-Type', 'multipart/mixed; boundary="-"'); response.write('\r\n---\r\n'); - sendPartialResponse( - pretty, - response, - formattedResult as FormattedExecutionPatchResult, - ); + sendPartialResponse(pretty, response, formattedResult); try { for await (let payload of executeResult) { // Collect and apply any metadata extensions if a function was provided. @@ -626,12 +622,13 @@ function canDisplayGraphiQL(request: Request, params: GraphQLParams): boolean { function sendPartialResponse( pretty: boolean, response: Response, - result: FormattedExecutionPatchResult, + result: FormattedExecutionResult | FormattedExecutionPatchResult, ): void { const json = JSON.stringify(result, null, pretty ? 2 : 0); const chunk = Buffer.from(json, 'utf8'); const data = ['Content-Type: application/json; charset=utf-8', '', chunk]; - if (result.hasNext) { + // @ts-expect-error + if (result.hasNext === true) { data.push('---\r\n'); } response.write(data.join('\r\n')); From 00aad81d5776c94944dc591fb12f712fff2d2e85 Mon Sep 17 00:00:00 2001 From: Marais Rossouw Date: Wed, 18 Nov 2020 11:25:07 +1000 Subject: [PATCH 4/7] chore: push epilogue boundary into fn --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 6a5593be..9cc5c51f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -490,7 +490,6 @@ export function graphqlHTTP(options: Options): Middleware { hasNext: false, }); } - response.write('\r\n-----\r\n'); response.end(); finishedIterable = true; return; @@ -630,6 +629,8 @@ function sendPartialResponse( // @ts-expect-error if (result.hasNext === true) { data.push('---\r\n'); + } else { + data.push('-----\r\n'); } response.write(data.join('\r\n')); // flush response if compression middleware is used From 7c6b70e0113f971c2952f162ec5fdb6b5bef83f3 Mon Sep 17 00:00:00 2001 From: Marais Rossouw Date: Wed, 18 Nov 2020 11:31:36 +1000 Subject: [PATCH 5/7] test: fixes snapshots --- src/__tests__/http-test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/__tests__/http-test.ts b/src/__tests__/http-test.ts index 8418d2b0..186f5768 100644 --- a/src/__tests__/http-test.ts +++ b/src/__tests__/http-test.ts @@ -2480,7 +2480,6 @@ function runTests(server: Server) { '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 47', '', '{"data":{"test":"Hello, World"},"hasNext":true}', '', @@ -2545,7 +2544,6 @@ function runTests(server: Server) { '', '---', 'Content-Type: application/json; charset=utf-8', - 'Content-Length: 47', '', '{"data":{"test":"Hello, World"},"hasNext":true}', '', From e7302b91bc1bb29a9c4aae370d2257d66fff8ea7 Mon Sep 17 00:00:00 2001 From: Marais Rossouw Date: Wed, 25 Nov 2020 08:14:22 +1000 Subject: [PATCH 6/7] chore: fix unit tests --- src/__tests__/http-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__tests__/http-test.ts b/src/__tests__/http-test.ts index 186f5768..376193d2 100644 --- a/src/__tests__/http-test.ts +++ b/src/__tests__/http-test.ts @@ -2482,7 +2482,7 @@ function runTests(server: Server) { 'Content-Type: application/json; charset=utf-8', '', '{"data":{"test":"Hello, World"},"hasNext":true}', - '', + '---\r\n' ].join('\r\n'), ); expect(fakeReturn.callCount).to.equal(1); @@ -2546,7 +2546,7 @@ function runTests(server: Server) { 'Content-Type: application/json; charset=utf-8', '', '{"data":{"test":"Hello, World"},"hasNext":true}', - '', + '---\r\n' ].join('\r\n'), ); }); From 1afbfff766a602453558ed5ab2bafd5199a91e33 Mon Sep 17 00:00:00 2001 From: Marais Rossouw Date: Wed, 2 Dec 2020 12:21:29 +1000 Subject: [PATCH 7/7] chore: fix lint --- src/__tests__/http-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__tests__/http-test.ts b/src/__tests__/http-test.ts index 376193d2..7af96620 100644 --- a/src/__tests__/http-test.ts +++ b/src/__tests__/http-test.ts @@ -2482,7 +2482,7 @@ function runTests(server: Server) { 'Content-Type: application/json; charset=utf-8', '', '{"data":{"test":"Hello, World"},"hasNext":true}', - '---\r\n' + '---\r\n', ].join('\r\n'), ); expect(fakeReturn.callCount).to.equal(1); @@ -2546,7 +2546,7 @@ function runTests(server: Server) { 'Content-Type: application/json; charset=utf-8', '', '{"data":{"test":"Hello, World"},"hasNext":true}', - '---\r\n' + '---\r\n', ].join('\r\n'), ); });