Skip to content

Commit

Permalink
feat(lambda) File uploads (#3926)
Browse files Browse the repository at this point in the history
Aim to provide file-upload parity support — as already supported within
the other Apollo Server integration packages — via the third-party
`graphql-upload` package.

Co-authored-by: charleswong28 <chung.triniti@gmail.com>
Co-authored-by: Steve Babigian <steve@noisykid.com>
Co-authored-by: Jesse Rosenberger <git@jro.cc>
Closes: #1419
Closes: #1703
Supersedes: #1739
...and therefore...
Closes: #1739
Supersedes: #3676
...and therefore...
Closes: #3676
  • Loading branch information
4 people committed Apr 10, 2020
1 parent 12e548d commit af7f2f0
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 19 deletions.
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));
};
}
}

0 comments on commit af7f2f0

Please sign in to comment.