-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes to the incremental responses #204
Changes to the incremental responses #204
Conversation
@@ -50,6 +50,10 @@ function startServerAndCreateNextHandler< | |||
if (httpGraphQLResponse.body.kind === 'complete') { | |||
res.send(httpGraphQLResponse.body.string); | |||
} else { | |||
res.writeHead(200, { | |||
'Content-Type': 'multipart/mixed; boundary="-"', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this header isn't set, then Apollo Client throws an error that looks like this
Error: No number after minus sign in JSON at position 3 (line 2 column 2)
Looking at the data being sent, it lines up with the ---
After setting it to 'multipart/mixed; boundary="-"'
the Preview
and Response
panels display nothing, as I mentioned in the other comment. Not sure why this is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking below, where I actually spread in the existing headers
headers: {
...headers,
'Content-Type': 'application/json',
'Transfer-Encoding': 'chunked',
},
those requests have multipart/mixed; boundary="-"; deferSpec=20220824
so I probably need to do something similar here instead of hard coding
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fixed this by ensuring the passed in headers are applied in all cases
Thank you for the thorough explanation! I'll try to take a closer look this week. |
82f0180
to
36d9317
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added some suggestions! Could you also remove noIncrementalDelivery: true
from src/__tests__/integration.test.ts
so we make sure to test incremental delivery as well?
I also noticed Chrome's Preview
and Response
panes aren't showing any data but I think that's normal for multipart responses.
const headers: Record<string, string> = {}; | ||
for (const [key, value] of httpGraphQLResponse.headers) { | ||
headers[key] = value; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since headers
isn't used in the if (isNextApiRequest(req))
code block it would be better to move this below the if statement.
@@ -50,6 +55,8 @@ function startServerAndCreateNextHandler< | |||
if (httpGraphQLResponse.body.kind === 'complete') { | |||
res.send(httpGraphQLResponse.body.string); | |||
} else { | |||
res.writeHead(200, headers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is necessary if we use res.send
below.
async start(controller) { | ||
for await (const chunk of responseBody.asyncIterator) { | ||
controller.enqueue(chunk); | ||
} | ||
controller.close(); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's more common to use pull, and then use the iterator without looping like this:
async start(controller) { | |
for await (const chunk of responseBody.asyncIterator) { | |
controller.enqueue(chunk); | |
} | |
controller.close(); | |
}, | |
async pull(controller) { | |
if (httpGraphQLResponse.body.kind === 'chunked') { | |
const { value, done } = await httpGraphQLResponse.body.asyncIterator.next(); | |
if (done) { | |
controller.close(); | |
} else { | |
controller.enqueue(value); | |
} | |
} | |
}, |
@@ -50,6 +55,8 @@ function startServerAndCreateNextHandler< | |||
if (httpGraphQLResponse.body.kind === 'complete') { | |||
res.send(httpGraphQLResponse.body.string); | |||
} else { | |||
res.writeHead(200, headers); | |||
|
|||
for await (const chunk of httpGraphQLResponse.body.asyncIterator) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should support incremental responses for API routes as well. We can just replace the for loop with this:
res.send(Readable.from(httpGraphQLResponse.body.asyncIterator));
@@ -59,24 +66,27 @@ function startServerAndCreateNextHandler< | |||
return; | |||
} | |||
|
|||
const body = []; | |||
let responseObject: string | ReadableStream; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer if we could use a ternary instead of a re-assignable variable, like this:
return new Response(
httpGraphQLResponse.body.kind === 'complete'
? httpGraphQLResponse.body.string
: new ReadableStream({ ... }),
{ headers, status: httpGraphQLResponse.status || 200 },
);
…ses back instead of batching them all together
36d9317
to
2288ed3
Compare
Thank you for all the feedback! It is a much cleaner implementation. On the Preview panel, there definitely is some way for the data to show up. This screenshot is from my work's application, which uses Apollo Client + graphql-ruby. I'll spend some time this morning and see if I can't uncover any information as to what is going on |
Okay I figured it out and I believe it is an upstream issue in Apollo Server and improperly setting the header.
When the headers are written in and the I changed the code to
|
Thank you for looking into this. I don't think we want to change any headers coming from Apollo Server, so I'll go ahead and add a changeset and release this as is. Thank you for the pull request! |
e7b02be
into
apollo-server-integrations:main
Fixes: #203
Preface: This is my first exposure to working with these Node objects on the server side so I'm not entirely sure if what I've done is correct.
I changed the way the incremental responses are sent back to the client. In the issue I noted
this
await
is stopping the server from incrementally delivering the responses and instead they're all delivered at once.These changes allow the responses to be streamed back instead of all at once.
The logs in the client now show
Where the second
book: {}
has the deferred data and is delivered 1 second after the first in which the 1 second is hard coded in my project using this package.