Skip to content

Commit

Permalink
feat: auto-detect binary response and add binarySettings
Browse files Browse the repository at this point in the history
binaryMimeTypes now deprecated
  • Loading branch information
brett-vendia committed Jan 28, 2021
1 parent e361f70 commit 64a99dc
Show file tree
Hide file tree
Showing 11 changed files with 101 additions and 48 deletions.
13 changes: 1 addition & 12 deletions examples/basic-starter-api-gateway-v2/src/lambda.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,4 @@ require('source-map-support/register')
const serverlessExpress = require('@vendia/serverless-express')
const app = require('./app')

// NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this is likely
// due to a compressed response (e.g. gzip) which has not been handled correctly
// by serverless-express and/or API Gateway. Add the necessary MIME types to
// binaryMimeTypes below, then redeploy (`npm run package-deploy`)
const binaryMimeTypes = [
'*/*'
]

exports.handler = serverlessExpress({
app,
binaryMimeTypes
}).handler
exports.handler = serverlessExpress({ app }).handler
8 changes: 6 additions & 2 deletions package-lock.json

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

5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,9 @@
"lint": "eslint src examples",
"install-example-dependencies": "cd examples && npm install --prefix basic-starter-api-gateway-v1 basic-starter-api-gateway-v1 && cd .."
},
"dependencies": {
"type-is": "^1.6.16"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
}
}
}
8 changes: 7 additions & 1 deletion src/configure.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import ProxyParams from "./proxy"

interface ConfigureParams {
app: RequestListener,
binaryMimeTypes?: string[]
binaryMimeTypes?: string[],
binarySettings: BinarySettings
}

interface BinarySettings {
isBinary?: Function | boolean,
contentTypes: string[]
}

interface ConfigureResult {
Expand Down
5 changes: 4 additions & 1 deletion src/configure.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ const proxy = require('./proxy')
function configure ({
app: configureApp,
framework: configureFramework = getFramework({ app: configureApp }),
binaryMimeTypes: configureBinaryMimeTypes = [],
binaryMimeTypes: configureBinaryMimeTypes,
binarySettings: configureBinarySettings,
resolutionMode: configureResolutionMode = 'PROMISE',
eventSourceName: configureEventSourceName,
eventSource: configureEventFns,
Expand All @@ -20,6 +21,7 @@ function configure ({
callback,
eventSourceName = configureEventSourceName,
binaryMimeTypes = configureBinaryMimeTypes,
binarySettings = configureBinarySettings,
eventSource = configureEventFns,
log = configureLogger,
respondWithErrors = configureRespondWithErrors
Expand All @@ -32,6 +34,7 @@ function configure ({
callback,
eventSourceName,
binaryMimeTypes,
binarySettings,
eventSource,
log,
respondWithErrors
Expand Down
42 changes: 42 additions & 0 deletions src/is-binary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// ATTRIBUTION: https://github.com/dougmoscrop/serverless-http

function isContentEncodingBinary ({ headers, binaryEncodingTypes }) {
const contentEncoding = headers['content-encoding']

if (typeof contentEncoding !== 'string') return false

return contentEncoding
.split(',')
.some(value => binaryEncodingTypes.some(binaryEncoding => value.includes(binaryEncoding)))
}

function getContentType ({ headers }) {
const contentTypeHeader = headers['content-type'] || ''

// only compare mime type; ignore encoding part
return contentTypeHeader.split(';')[0]
}

function isContentTypeBinary ({ headers, binaryContentTypes }) {
if (!binaryContentTypes || !Array.isArray(binaryContentTypes)) return false

const binaryContentTypesRegexes = binaryContentTypes.map(binaryContentType => new RegExp(`^${binaryContentType.replace(/\*/g, '.*')}$`))
const contentType = getContentType({ headers })

if (!contentType) return false

return binaryContentTypesRegexes.some(binaryContentType => binaryContentType.test(contentType))
}

module.exports = function isBinary ({ headers, binarySettings }) {
if (binarySettings.isBinary === false) {
return false
}

if (typeof binarySettings.isBinary === 'function') {
return binarySettings.isBinary({ headers })
}

return isContentEncodingBinary({ headers, binaryEncodingTypes: binarySettings.contentEncodings }) ||
isContentTypeBinary({ headers, binaryContentTypes: binarySettings.contentTypes })
}
8 changes: 7 additions & 1 deletion src/proxy.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { RequestListener } from "http"

interface ProxyParams {
app: RequestListener,
binaryMimeTypes?: string[]
binaryMimeTypes?: string[],
binarySettings: BinarySettings
}

interface BinarySettings {
isBinary?: Function | boolean,
contentTypes: string[]
}

declare function proxy(proxyParams: ProxyParams): Promise<any>
Expand Down
20 changes: 18 additions & 2 deletions src/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ const { getFramework } = require('./frameworks')
const makeResolver = require('./make-resolver')
const { forwardRequestToNodeServer, respondToEventSourceWithError } = require('./transport')

const DEFAULT_BINARY_ENCODINGS = ['gzip', 'deflate', 'br']
const DEFAULT_BINARY_CONTENT_TYPES = ['image/*']

function getDefaultBinarySettings (deprecatedBinaryMimeTypes) {
return {
contentTypes: deprecatedBinaryMimeTypes || DEFAULT_BINARY_CONTENT_TYPES,
contentEncodings: DEFAULT_BINARY_ENCODINGS
}
}

function proxy ({
app,
framework = getFramework({ app }),
Expand All @@ -14,6 +24,7 @@ function proxy ({
resolutionMode = 'PROMISE',
eventSourceName = getEventSourceNameBasedOnEvent({ event }),
binaryMimeTypes,
binarySettings = getDefaultBinarySettings(binaryMimeTypes),
eventSource = getEventSource({ eventSourceName }),
log,
respondWithErrors
Expand All @@ -23,9 +34,14 @@ function proxy ({
context,
resolutionMode,
eventSourceName,
binaryMimeTypes,
binarySettings,
respondWithErrors
})

if (binaryMimeTypes) {
console.warn('{ binaryMimeTypes: [] } is deprecated. base64 encoding is now automatically determined based on response content-type and content-encoding. If you need to manually set binary content types, instead, use { binarySettings: { contentTypes: [] } }')
}

setCurrentInvoke({ event, context })
return new Promise((resolve, reject) => {
const promise = {
Expand All @@ -47,7 +63,7 @@ function proxy ({
context,
resolver,
eventSourceName,
binaryMimeTypes,
binarySettings,
eventSource,
log
})
Expand Down
1 change: 0 additions & 1 deletion src/request.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use strict'
// ATTRIBUTION: https://github.com/dougmoscrop/serverless-http

const http = require('http')
Expand Down
1 change: 0 additions & 1 deletion src/response.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use strict'
// ATTRIBUTION: https://github.com/dougmoscrop/serverless-http

const http = require('http')
Expand Down
38 changes: 15 additions & 23 deletions src/transport.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,27 @@

const isType = require('type-is')
const { getEventSource } = require('./event-sources')
const Response = require('./response')

function isContentTypeBinaryMimeType ({ contentType, binaryMimeTypes }) {
return binaryMimeTypes.length > 0 && Boolean(isType.is(contentType, binaryMimeTypes))
}

function getContentType ({ contentTypeHeader }) {
// only compare mime type; ignore encoding part
return contentTypeHeader ? contentTypeHeader.split(';')[0] : ''
}
const isBinary = require('./is-binary')

function forwardResponse ({
binaryMimeTypes,
binarySettings,
response,
resolver,
eventSource,
log
}) {
const statusCode = response.statusCode
const headers = Response.headers(response)
const contentType = getContentType({
contentTypeHeader: headers['content-type']
})
const isBase64Encoded = isContentTypeBinaryMimeType({
contentType,
binaryMimeTypes
const isBase64Encoded = isBinary({
headers,
binarySettings
})
const body = Response.body(response).toString(isBase64Encoded ? 'base64' : 'utf8')
const encoding = isBase64Encoded ? 'base64' : 'utf8'
const body = Response.body(response).toString(encoding)
const logBody = isBase64Encoded ? '[BASE64_ENCODED]' : body

log.debug('SERVERLESS_EXPRESS:FORWARD_RESPONSE:EVENT_SOURCE_RESPONSE_PARAMS', {
statusCode,
body,
body: logBody,
headers,
isBase64Encoded
})
Expand All @@ -45,7 +34,10 @@ function forwardResponse ({
response
})

log.debug('SERVERLESS_EXPRESS:FORWARD_RESPONSE:EVENT_SOURCE_RESPONSE', { successResponse })
log.debug('SERVERLESS_EXPRESS:FORWARD_RESPONSE:EVENT_SOURCE_RESPONSE', {
successResponse,
body: logBody
})

resolver.succeed({
response: successResponse
Expand Down Expand Up @@ -79,7 +71,7 @@ async function forwardRequestToNodeServer ({
context,
resolver,
eventSourceName,
binaryMimeTypes,
binarySettings,
eventSource = getEventSource({ eventSourceName }),
log
}) {
Expand All @@ -88,7 +80,7 @@ async function forwardRequestToNodeServer ({
const response = await framework.sendRequest({ app, requestValues })
log.debug('SERVERLESS_EXPRESS:FORWARD_REQUEST_TO_NODE_SERVER:RESPONSE', { response })
forwardResponse({
binaryMimeTypes,
binarySettings,
response,
resolver,
eventSource,
Expand Down

0 comments on commit 64a99dc

Please sign in to comment.