Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.6k views
in Technique[技术] by (71.8m points)

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

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)
…….
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...