question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

apollo-server-micro body stream reading issue ( when using on firebase cloud functions )

See original GitHub issue

apollo-server-micro package tries to receive request body stream that already received before(in some scenarios) and hangs on forever cause never receives events of stream(it already fully obtained)

I gone step by step through all the flow to discover the issue. In brief the issue pops up when request stream that passed to Apollo already read before. It means we already had used for example: body.on(‘data’, onData) & body.on(‘end’, onEnd) or it was executed by another chain in the flow(express server, next.js server, firebase cloud function).

And if it was before what is going in apollo-server-micro code is that it tries to do it again, but it will never occur and we will fail on timeout or never get the response, because: body.on(‘data’, or body.on(‘end’ will never be called again(the stream already parsed before fully, these event will not happen).

So I think some way is needed to treat this situation and to give Apollo option to work with body stream already received. May be we need some way to say Apollo to not try to receive body stream if it already exists and just deliver it already prepared(buffer) by some property. So we don’t need to do it if I already can provide it to the Apollo.

I found some hack I can do, but by far it needed to be done in more proper way. Apollo uses json function from micro package (https://github.com/vercel/micro) to get the body stream. if I change this line there:

const body = rawBodyMap.get(req);

to something like:

const body = rawBodyMap.get(req) || req.rawBody;

I have rawBody because I use firebase cloud function and when it receives body stream it saves received stream buffer in rawBody property of request (and it’s exactly what json function of micro tries to achieve).

full flow:

src/ApolloServer.ts ( from apollo-server-micro package )

import { graphqlMicro } from './microApollo';
 const graphqlHandler = graphqlMicro(() => {
        return this.createGraphQLServerOptions(req, res);
 });
 const responseData = await graphqlHandler(req, res);
 send(res, 200, responseData);

microApollo.ts - we use here json function from ‘micro’ passing req as parameter

import { send, json, RequestHandler } from 'micro';    ( https://github.com/vercel/micro )
  const graphqlHandler = async (req: MicroRequest, res: ServerResponse) => {
    let query;
    try {
      query =
        req.method === 'POST'
          ? req.filePayload || (await json(req))
          : url.parse(req.url, true).query;
    } catch (error) {
      // Do nothing; `query` stays `undefined`
    }

https://github.com/vercel/micro package

const getRawBody = require('raw-body');
exports.json = (req, opts) =>
    exports.text(req, opts).then(body => parseJSON(body));

exports.text = (req, {limit, encoding} = {}) =>
    exports.buffer(req, {limit, encoding}).then(body => body.toString(encoding));

exports.buffer = (req, {limit = '1mb', encoding} = {}) =>
    Promise.resolve().then(() => {
        const type = req.headers['content-type'] || 'text/plain';
        const length = req.headers['content-length'];
        // eslint-disable-next-line no-undefined
        if (encoding === undefined) {
            encoding = contentType.parse(type).parameters.charset;
        }

// my try to hack the behavior

const body = rawBodyMap.get(req) || req.rawBody;
console.log(">>>>>>>>>>>>>>>>>>> ", body);
if (body) {
        return body;
    }
    return getRawBody(req, {limit, length, encoding})
        .then(buf => {
            rawBodyMap.set(req, buf);
            return buf;
        })
        .catch(err => {
            if (err.type === 'entity.too.large') {
                throw createError(413, `Body exceeded ${limit} limit`, err);
            } else {
                throw createError(400, 'Invalid body', err);
            }
        });
});

if I don’t stop by my hack the code from going to receive the body stream it calls to : getRawBody from ‘raw-body’ package;

raw-body package

function getRawBody (stream, options, callback) {
……
  return new Promise(function executor (resolve, reject) {
    readStream(stream, encoding, length, limit, function onRead (err, buf) {
      if (err) return reject(err)
      resolve(buf)
    })
  })
}
function readStream (stream, encoding, length, limit, callback) {
…….
  // attach listeners
// these callbacks never called because body request stream already received before
  stream.on('aborted', onAborted)
  stream.on('close', cleanup)
  stream.on('data', onData)
  stream.on('end', onEnd)
  stream.on('error', onEnd)
…….

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
glassercommented, Aug 17, 2021

As mentioned in several issues such as https://github.com/apollographql/apollo-server/issues/5547#issuecomment-891408105 I am confused as to why folks are trying to use apollo-server-micro with nextjs (rather than, say, apollo-server-express).

0reactions
trevor-scheercommented, Oct 20, 2022

If the proposed outcome of this issue is still desirable, I’d recommend building a v4 integration that fits your specific needs. Integrating with AS4 is much simpler than it once was: https://www.apollographql.com/docs/apollo-server/integrations/building-integrations

Read more comments on GitHub >

github_iconTop Results From Across the Web

firebase cloud functions hosting for next.js with apollo graphql ...
I suppose the problem is somehow in the way how Apollo server treats request body. It looks like it tries to parse the...
Read more >
Deploying with Google Cloud Functions - Apollo GraphQL Docs
Deploying from the Google Cloud Console. 1. Configure the function. From your Google Cloud Console, go to the Cloud Functions page. Click Create...
Read more >
HTTP request body | Cloud Functions Documentation
Parses a request body. ... using TextReader reader = new StreamReader(request.Body); string json = await reader. ... case "application/octet-stream":
Read more >
GraphQL Server on Cloud Functions for Firebase - codeburst
GraphQL is a data query language created by Facebook. Queries to the server specify the data they want to fetch while the GraphQL...
Read more >
referenceerror: cannot access 'main' before initialization
genSalt(10); const hashedPassword =await bcrypt.hash(req.body.password, ... I got a lot of help on the node.js github and someone looked through my project ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found