Skip to content
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

feat(lambda) File uploads #3926

Merged
merged 17 commits into from
Apr 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The version headers in this history reflect the versions of Apollo Server itself

> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the the appropriate changes within that release will be moved into the new section.

- _Nothing yet! Stay tuned._
- `apollo-server-lambda`: Support file uploads on AWS Lambda [Issue #1419](https://github.com/apollographql/apollo-server/issues/1419) [Issue #1703](https://github.com/apollographql/apollo-server/issues/1703) [PR #3926](https://github.com/apollographql/apollo-server/pull/3926)

### v2.12.0

Expand All @@ -28,6 +28,8 @@ The version headers in this history reflect the versions of Apollo Server itself

- The range of accepted `peerDepedencies` versions for `graphql` has been widened to include `graphql@^15.0.0-rc.2` so as to accommodate the latest release-candidate of the `graphql@15` package, and an intention to support it when it is finally released on the `latest` npm tag. While this change will subdue peer dependency warnings for Apollo Server packages, many dependencies from outside of this repository will continue to raise similar warnings until those packages own `peerDependencies` are updated. It is unlikely that all of those packages will update their ranges prior to the final version of `graphql@15` being released, but if everything is working as expected, the warnings can be safely ignored. [PR #3825](https://github.com/apollographql/apollo-server/pull/3825)

- `apollo-server-lambda`: Support file uploads on AWS Lambda [Issue #1419](https://github.com/apollographql/apollo-server/issues/1419) [Issue #1703](https://github.com/apollographql/apollo-server/issues/1703) [PR #3926](https://github.com/apollographql/apollo-server/pull/3926)

### v2.10.1

> [See complete versioning details.](https://github.com/apollographql/apollo-server/commit/dba97895485d6444535a684d4646f1363954f698)
Expand Down
25 changes: 25 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"@types/qs-middleware": "1.0.1",
"@types/request": "2.48.4",
"@types/request-promise": "4.1.46",
"@types/supertest": "^2.0.8",
"@types/test-listen": "1.1.0",
"@types/type-is": "1.6.3",
"@types/ws": "6.0.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import testSuite, {
import { Config } from 'apollo-server-core';
import express = require('express');
import bodyParser = require('body-parser');
import request = require('supertest');
import request from 'supertest';

type GcfRequest = {
path: string | null;
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-integration-testsuite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
getOperationAST,
} from 'graphql';

import request = require('supertest');
import request from 'supertest';

import { GraphQLOptions, Config } from 'apollo-server-core';
import gql from 'graphql-tag';
Expand Down
67 changes: 61 additions & 6 deletions packages/apollo-server-lambda/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@ import {
APIGatewayProxyEvent,
Context as LambdaContext,
} from 'aws-lambda';
import { ApolloServerBase, GraphQLOptions, Config } from 'apollo-server-core';
import {
formatApolloErrors,
processFileUploads,
FileUploadOptions,
ApolloServerBase,
GraphQLOptions,
Config,
} from 'apollo-server-core';
import {
renderPlaygroundPage,
RenderPageOptions as PlaygroundRenderPageOptions,
} from '@apollographql/graphql-playground-html';
import {
ServerResponse,
IncomingHttpHeaders,
IncomingMessage,
} from 'http';

import { graphqlLambda } from './lambdaApollo';
import { Headers } from 'apollo-server-env';
import { Readable, Writable } from 'stream';

export interface CreateHandlerOptions {
cors?: {
Expand All @@ -21,9 +34,14 @@ export interface CreateHandlerOptions {
credentials?: boolean;
maxAge?: number;
};
uploadsConfig?: FileUploadOptions;
onHealthCheck?: (req: APIGatewayProxyEvent) => Promise<any>;
}

export class FileUploadRequest extends Readable {
headers!: IncomingHttpHeaders;
}

export class ApolloServer extends ApolloServerBase {
// If you feel tempted to add an option to this constructor. Please consider
// another place, since the documentation becomes much more complicated when
Expand All @@ -38,6 +56,11 @@ export class ApolloServer extends ApolloServerBase {
super(options);
}

// Uploads are supported in this integration
protected supportsUploads(): boolean {
return true;
}

// This translates the arguments from the middleware into graphQL options It
// provides typings for the integration specific behavior, ideally this would
// be propagated with a generic to the super class
Expand Down Expand Up @@ -184,9 +207,9 @@ export class ApolloServer extends ApolloServerBase {
},
});
});
} else {
return callback(null, successfulResponse);
}
} else {
return callback(null, successfulResponse);
}
}

if (this.playgroundOptions && event.httpMethod === 'GET') {
Expand All @@ -213,7 +236,9 @@ export class ApolloServer extends ApolloServerBase {
}
}

const response = new Writable() as ServerResponse;
const callbackFilter: APIGatewayProxyCallback = (error, result) => {
response.end();
callback(
error,
result && {
Expand All @@ -226,7 +251,37 @@ export class ApolloServer extends ApolloServerBase {
);
};

graphqlLambda(async () => {
const fileUploadHandler = (next: Function) => {
const contentType =
event.headers["content-type"] || event.headers["Content-Type"];
if (contentType && contentType.startsWith("multipart/form-data")
&& typeof processFileUploads === "function") {
const request = new FileUploadRequest() as IncomingMessage;
request.push(
Buffer.from(
<any>event.body,
event.isBase64Encoded ? "base64" : "ascii"
)
);
request.push(null);
request.headers = event.headers;
processFileUploads(request, response, this.uploadsConfig || {})
.then(body => {
event.body = body as any;
return next();
})
.catch(error => {
throw formatApolloErrors([error], {
formatter: this.requestOptions.formatError,
debug: this.requestOptions.debug,
});
});
} else {
return next();
}
};

fileUploadHandler(() => graphqlLambda(async () => {
// In a world where this `createHandler` was async, we might avoid this
// but since we don't want to introduce a breaking change to this API
// (by switching it to `async`), we'll leverage the
Expand All @@ -236,7 +291,7 @@ export class ApolloServer extends ApolloServerBase {
// its contract) prior to processing the request.
await promiseWillStart;
return this.createGraphQLServerOptions(event, context);
})(event, context, callbackFilter);
})(event, context, callbackFilter));
};
}
}
Loading