jaydenseric / graphql-upload

Middleware and a scalar Upload to add support for GraphQL multipart requests (file uploads via queries and mutations) to various Node.js GraphQL servers.
https://npm.im/graphql-upload
MIT License
1.43k stars 132 forks source link

upload video with heroku server #122

Closed neneji closed 6 years ago

neneji commented 6 years ago

im trying to upload video using a heroku server, but i got H18 error https://devcenter.heroku.com/articles/error-codes#h18-server-request-interrupted

im using this flow:

  1. upload video with apollo-upload-client
  2. the resolver gets the video stream
  3. then i use youtube-api npm package to upload video to youtube

all worked fine on my local machine, but once i deployed it to heroku server i got error H18 during uploading.

error:

client log:

Network error: Failed to fetch POST https://staging-www.italianfishingtv.it/graphql net::ERR_CONNECTION_RESET

server log

2018-11-07T12:31:53.985268+00:00 heroku[router]: sock=backend at=error code=H18 desc="Server Request Interrupted" method=POST path="/graphql" host=staging-www.italianfishingtv.it request_id=d64d92f2-4518-4c15-83ce-42531998a16b fwd="87.20.112.78" dyno=web.1 connect=0ms service=469ms status=503 bytes=340 protocol=https

client config:

const authLink = new ApolloLink((operation, forward) => {
    const token = Accounts._storedLoginToken();

    operation.setContext(() => ({
        headers: {
            'meteor-login-token': token
        }
    }));

    return forward(operation);
});

const uploadLink = createUploadLink({
    uri: Meteor.absoluteUrl('graphql')
});

const cache = new InMemoryCache();

const apolloClient = new ApolloClient({
    cache,
    link: authLink.concat(uploadLink)
    // link: uploadLink
});

server config:

createApolloServer(
        {schema},
        {
            graphiql: true,
            configServer: expressServer => {
                expressServer.use(bodyParser.json({limit: '1000mb'}));
                expressServer.use(graphqlUploadExpress());
            }
        }
    );

resolver:

export const resolvers = {
    Mutation: {
        async submitFishWithFather(obj, {data}, {user}) {
            const {stream, filename, mimetype, encoding} = await data.upload;

            const req = Youtube.videos.insert({
                resource: {
                    // Video title and description
                    snippet: {
                        title: `a pesca con papà - ${data.name} ${data.surname} - ${data.email} - ${new Date()}`,
                    },
                    status: {
                        privacyStatus: "private"
                    }
                },
                part: "snippet,status",
                media: {
                    body: stream
                }
            }, (err, videoData) => {
                if (err) {
                    throw  err;
                }
                console.log(videoData);

                sendNotificationEmail(data, videoData);
            });

            return {};
        }
    }
};
mike-marcacci commented 6 years ago

Hi @neneji, can you check that your upload isn’t exceeding any of Heroku’s limits?

https://devcenter.heroku.com/articles/limits#router

neneji commented 6 years ago

hi @mike-marcacci , i've resolved the problem. The issue is created when the resolve returns when the upload operation is not completed. As mentioned in tips https://github.com/jaydenseric/graphql-upload#tips

so i created a promise and resolved it with an await

async function uploadVideo(data) {
    const {filename, mimetype, createReadStream} = await data.upload;
    const stream = createReadStream();

    return new Promise((resolve, reject) => {
        const req = Youtube.videos.insert({
            resource: {
                // Video title and description
                snippet: {
                    title: `a pesca con papà - ${data.name} ${data.surname} - ${data.email} - ${new Date()}`,
                    description: filename
                },
                status: {
                    privacyStatus: "private"
                }
            },
            part: "snippet,status",
            media: {
                body: stream
            }
        }, (err, videoData) => {
            if (err) {
                reject(err);
            } else {
                resolve(videoData);
            }
        });
    })
}

export const resolvers = {
    Mutation: {
        async submitFishWithFather(obj, {data}, {user}) {
            // const {stream, filename, mimetype, encoding, createReadStream} = data.upload;
            const videoData = await uploadVideo(data);

            sendNotificationEmail(data, videoData);

            return {};
        }
    }
};
neneji commented 6 years ago

this issue appears again without doing anything :| and i'm also having this error BadRequestError: Misordered multipart fields; files should follow ‘map’(https://github.com/jaydenseric/graphql-multipart-request-spec).

mike-marcacci commented 6 years ago

Hi @neneji, regarding the first error, the request can really be disconnected anywhere in the network – your client might cancel the request; heroku may abort it (such as if it exceeds 30 seconds); any other proxies (like nginx) might abort it; node may send a response prematurely over a non-multiplexed HTTP 1.1 stream (as you described above). Even though you provided a bunch of great info, this is pretty hard to diagnose from afar. I see the HTTP log from the server, but did you find any actual error in your node logs? This would be the first place to check.

I am quite curious about the multipart field order error. This would suggest that your client is incorrectly formatting the request. Is there any chance you could grab the raw HTTP request?

neneji commented 6 years ago

hi @mike-marcacci , i've resolve the multipart field order error. It was my fault, i didn't use correctly the Upload component of antd. I was using the beforeUpload handler to get the file info and it should return false to prevent the build-in upload event.

And now a fresh deployed application doesn't give me any error, but if i try to upload a new video after few hours BOOM, h18.

neneji commented 6 years ago

@mike-marcacci , i've resolved the problem. The issue is generated by an auth problem with youtube-api package. Now i'm using the google's official package and it's all ok now.