Open shelooks16 opened 5 years ago
As far as I understand, aws-serverless-express attaches multipart's Buffer as a string but multipart parser expects it to be of type Buffer.
Well, here is the code to make it work. I convert the body object (which is string) to Buffer and specify encoding as 'binary'. Handler:
export const handler: Handler = async (
event: APIGatewayEvent,
context: Context
) => {
if (
event.body &&
event.headers['Content-Type'].includes('multipart/form-data')
) {
// before => typeof event.body === string
event.body = (Buffer.from(event.body, 'binary') as unknown) as string;
// after => typeof event.body === <Buffer ...>
}
cachedServer = await bootstrapServer();
return proxy(cachedServer, event, context, 'PROMISE').promise;
};
Although the setup above works during development, in deployment it causes images being broken. I upload images to S3. Local upload is OK, when deployed - corrupted.
According to this post: https://stackoverflow.com/a/41770688, API gateway needs additional configuration to process binaries. To make it more or less automated, I simply installed serverless-apigw-binary and put */*
wildcard.
serverless.yml:
plugins:
- serverless-apigw-binary
custom:
apigwBinary:
types:
- '*/*'
handler:
const binaryMimeTypes: string[] = [];
if (
event.body &&
event.headers['Content-Type'].includes('multipart/form-data') &&
process.env.NODE_ENV !== 'production' // added
) {
event.body = (Buffer.from(event.body, 'binary') as unknown) as string;
}
Hey @shelooks16 Could you share how you deployed this handler in serverlesss ? I'm followed the same procedure but it shows "errorMessage": "Cannot read property 'REQUEST' of undefined" while trying to access the api endpoint.
Hey @shelooks16 Could you share how you deployed this handler in serverlesss ? I'm followed the same procedure but it shows "errorMessage": "Cannot read property 'REQUEST' of undefined" while trying to access the api endpoint.
Hey!! Here is full code for handler:
// index.ts
import { NestFactory } from '@nestjs/core';
import { Context, Handler, APIGatewayEvent } from 'aws-lambda';
import { createServer, proxy } from 'aws-serverless-express';
import { eventContext } from 'aws-serverless-express/middleware';
import { Server } from 'http';
import { ApiModule } from './api.module';
import { ExpressAdapter } from '@nestjs/platform-express';
// tslint:disable-next-line:no-var-requires
const express = require('express')();
const isProduction = process.env.NODE_ENV === 'production';
let cachedServer: Server;
async function bootstrapServer(): Promise<Server> {
return NestFactory.create(ApiModule, new ExpressAdapter(express))
.then((nestApp) => {
nestApp.use(eventContext());
nestApp.enableCors();
return nestApp.init();
})
.then(() => {
return createServer(express);
});
}
export const handler: Handler = async (
event: APIGatewayEvent,
context: Context
) => {
if (
isProduction &&
// @ts-ignore
event.source === 'serverless-plugin-warmup'
) {
return 'Lambda is warm!';
}
if (
!isProduction &&
event.body &&
event.headers['Content-Type'].includes('multipart/form-data')
) {
event.body = (Buffer.from(event.body, 'binary') as unknown) as string;
}
if (!cachedServer) {
cachedServer = await bootstrapServer();
}
return proxy(cachedServer, event, context, 'PROMISE').promise;
};
I used serverless-webpack with next .base config:
// webpack.config.base.js
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const _ = require('lodash');
const slsw = require('serverless-webpack');
require('source-map-support').install();
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const WebpackBar = require('webpackbar');
const rootDir = path.join(__dirname, '../'); // change it for your case
const buildDir = path.join(rootDir, '.webpack');
const isLocal = slsw.lib.webpack.isLocal;
const defaults = {
mode: isLocal ? 'development' : 'production',
entry: slsw.lib.entries,
target: 'node',
externals: [nodeExternals()],
node: {
__filename: false,
__dirname: false
},
optimization: {
minimize: false
},
resolve: {
extensions: ['.ts', '.js', '.json']
},
output: {
libraryTarget: 'commonjs2',
path: buildDir,
filename: '[name].js'
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: {
transpileOnly: true
}
},
{
test: /\.ts$/,
enforce: 'pre',
loader: 'tslint-loader'
}
]
},
plugins: [new WebpackBar(), new ForkTsCheckerWebpackPlugin()]
};
module.exports.defaults = defaults;
module.exports.merge = function merge(config) {
return _.merge({}, defaults, config);
};
Inside serverless config:
# serverless.yaml
plugins:
- serverless-plugin-warmup
- serverless-webpack
- serverless-apigw-binary
- serverless-prune-plugin
- serverless-offline
# ...
custom:
currentStage: ${opt:stage, 'dev'}
webpack:
webpackConfig: ./webpack/webpack.config.${self:custom.currentStage}.js
packager: 'yarn'
includeModules:
forceInclude:
- mysql2
forceExclude:
- aws-sdk
- typescript
apigwBinary:
types:
- 'multipart/form-data'
prune:
automatic: true
number: 5
warmup:
prewarm: true
concurrency: 2
events:
- schedule: 'cron(0/7 * ? * MON-FRI *)'
# ...
package:
individually: true
functions:
api:
handler: src/api/index.handler
warmup: true
timeout: 30
events:
- http:
path: /
method: any
cors: true
- http:
path: /{proxy+}
method: any
cors: true
I am also experiencing the above issue, but with audio files: Unexpected end of multipart data
@calflegal same with me here. Did you have any luck solving this?
I did not :(. I moved to a dokku deploy, it stopped me from using serverless for now.
@calflegal after digging super deep into it I realised that it was a problem I was facing with serverless-offline not handling multipart/form-data the same way as production apigateway+lambda does. So for local testing for file uploads I am running my regular non-serverless npm run
style command. If you end up getting back onto this try setting apiGateway to accept multipart/form-data in your serverless.yml and you may have some success (just not locally)
@hbthegreat that makes sense to me, nice job! Maybe worth connecting this issue with an issue there? FWIW, I had other issues in my app due to Safari's fetching strategy for audio (and I think video?) content, which, I was able to handle with one line of nginx config in my dokku setup, so I'm not coming back for now :)
Hi, I'm developing a Rest API with Nest.js. I successfully converted it to a monolithic lambda with aws-serverless-express with the next code:
For development, I use serverless-offline and serverless-webpack.
When I try to send an image with multipart/form-data to the /upload controller, it throws me an error regardless of the image type. ** With other file types (like .txt or .env) it works as expected. Before moving the app to lambda, it worked without any issues.
Controller:
Sending text file:
Error log (sending .png):
What I tried:
aws-serverless-express: 3.3.6 nodejs: v10.16.0 Current workaround: send images in base64 format