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

Add API config to allow disabling response size warning #34700

Merged
merged 15 commits into from Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/api-routes/api-middlewares.md
Expand Up @@ -68,6 +68,30 @@ export const config = {
}
```

`bodyLimit` is automatically enabled, warning when an API routes' response is over 4MB. If you'd like to disable this warning, you can set this to false.

A use case for disabling the `bodyLimit` warning is if you are deploying outside of the Vercel ecosystem, i/e AWS or self hosting.

```js
export const config = {
api: {
bodyLimit: false,
crice88 marked this conversation as resolved.
Show resolved Hide resolved
},
}
```

`bodyLimit.sizeLimit` is the maximum response size before a warning is displayed.

```js
export const config = {
api: {
bodyLimit: {
sizeLimit: '30mb',
},
},
}
```

## Connect/Express middleware support

You can also use [Connect](https://github.com/senchalabs/connect) compatible middleware.
Expand Down
2 changes: 2 additions & 0 deletions packages/next/package.json
Expand Up @@ -70,6 +70,7 @@
},
"dependencies": {
"@next/env": "12.1.1-canary.1",
"bytes": "3.1.1",
crice88 marked this conversation as resolved.
Show resolved Hide resolved
"caniuse-lite": "^1.0.30001283",
"postcss": "8.4.5",
"styled-jsx": "5.0.0",
Expand Down Expand Up @@ -132,6 +133,7 @@
"@types/babel__generator": "7.6.2",
"@types/babel__template": "7.4.0",
"@types/babel__traverse": "7.11.0",
"@types/bytes": "3.1.1",
"@types/ci-info": "2.0.0",
"@types/compression": "0.0.36",
"@types/content-disposition": "0.5.4",
Expand Down
2 changes: 2 additions & 0 deletions packages/next/server/api-utils/index.ts
Expand Up @@ -82,6 +82,8 @@ export function checkIsManualRevalidate(
export const COOKIE_NAME_PRERENDER_BYPASS = `__prerender_bypass`
export const COOKIE_NAME_PRERENDER_DATA = `__next_preview_data`

export const MAX_CONTENT_LENGTH = 4 * 1024 * 1024
crice88 marked this conversation as resolved.
Show resolved Hide resolved

export const SYMBOL_PREVIEW_DATA = Symbol(COOKIE_NAME_PRERENDER_DATA)
export const SYMBOL_CLEARED_COOKIES = Symbol(COOKIE_NAME_PRERENDER_BYPASS)

Expand Down
19 changes: 17 additions & 2 deletions packages/next/server/api-utils/node.ts
@@ -1,5 +1,6 @@
import type { IncomingMessage, ServerResponse } from 'http'
import type { NextApiRequest, NextApiResponse } from '../../shared/lib/utils'
import bytes from 'bytes'
import type { PageConfig } from 'next/types'
import type { __ApiPreviewProps } from '.'
import type { BaseNextRequest, BaseNextResponse } from '../base-http'
Expand Down Expand Up @@ -28,6 +29,7 @@ import {
COOKIE_NAME_PRERENDER_BYPASS,
COOKIE_NAME_PRERENDER_DATA,
SYMBOL_PREVIEW_DATA,
MAX_CONTENT_LENGTH,
} from './index'

export function tryGetPreviewData(
Expand Down Expand Up @@ -172,6 +174,7 @@ export async function apiResolver(
}
const config: PageConfig = resolverModule.config || {}
const bodyParser = config.api?.bodyParser !== false
const bodyLimit = config.api?.bodyLimit ?? true
const externalResolver = config.api?.externalResolver || false

// Parsing of cookies
Expand All @@ -198,6 +201,7 @@ export async function apiResolver(
}

let contentLength = 0
const maxContentLength = getMaxContentLength(bodyLimit)
const writeData = apiRes.write
const endResponse = apiRes.end
apiRes.write = (...args: any[2]) => {
Expand All @@ -209,9 +213,11 @@ export async function apiResolver(
contentLength += Buffer.byteLength(args[0] || '')
}

if (contentLength >= 4 * 1024 * 1024) {
if (bodyLimit && contentLength >= maxContentLength) {
console.warn(
`API response for ${req.url} exceeds 4MB. This will cause the request to fail in a future version. https://nextjs.org/docs/messages/api-routes-body-size-limit`
`API response for ${req.url} exceeds ${bytes.format(
maxContentLength
)}. API Routes are meant to respond quickly. https://nextjs.org/docs/messages/api-routes-body-size-limit`
)
}

Expand Down Expand Up @@ -484,3 +490,12 @@ function setPreviewData<T>(
])
return res
}

function getMaxContentLength(
bodyLimit?: { sizeLimit?: number | string } | boolean
) {
if (bodyLimit && typeof bodyLimit !== 'boolean' && bodyLimit.sizeLimit) {
return bytes.parse(bodyLimit.sizeLimit)
}
return MAX_CONTENT_LENGTH
}
6 changes: 6 additions & 0 deletions packages/next/types/index.d.ts
Expand Up @@ -63,6 +63,12 @@ export type NextPage<P = {}, IP = P> = NextComponentType<NextPageContext, IP, P>
export type PageConfig = {
amp?: boolean | 'hybrid'
api?: {
/**
* Configures or disables body size limit warning. Can take a number or
* any string format supported by `bytes`, for example `1000`, `'500kb'` or
* `'3mb'`.
*/
bodyLimit?: { sizeLimit?: number | string } | boolean
/**
* The byte limit of the body. This is the number of bytes or any string
* format supported by `bytes`, for example `1000`, `'500kb'` or `'3mb'`.
Expand Down
@@ -1,5 +1,5 @@
export default (req, res) => {
for (let i = 0; i <= 5 * 1024 * 1024; i++) {
for (let i = 0; i <= 4 * 1024 * 1024; i++) {
res.write('.')
}
res.end()
Expand Down
@@ -0,0 +1,12 @@
export const config = {
api: {
bodyLimit: {
sizeLimit: '5mb',
},
},
}

export default (req, res) => {
let body = '.'.repeat(6 * 1024 * 1024)
res.send(body)
}
@@ -0,0 +1,10 @@
export const config = {
api: {
bodyLimit: false,
},
}

export default (req, res) => {
let body = '.'.repeat(4 * 1024 * 1024)
res.send(body)
}
2 changes: 1 addition & 1 deletion test/integration/api-support/pages/api/large-response.js
@@ -1,4 +1,4 @@
export default (req, res) => {
let body = '.'.repeat(5 * 1024 * 1024)
let body = '.'.repeat(4 * 1024 * 1024)
res.send(body)
}
23 changes: 21 additions & 2 deletions test/integration/api-support/test/index.test.js
Expand Up @@ -451,13 +451,32 @@ function runTests(dev = false) {
let res = await fetchViaHTTP(appPort, '/api/large-response')
expect(res.ok).toBeTruthy()
expect(stderr).toContain(
'API response for /api/large-response exceeds 4MB. This will cause the request to fail in a future version.'
'API response for /api/large-response exceeds 4MB. API Routes are meant to respond quickly.'
)

res = await fetchViaHTTP(appPort, '/api/large-chunked-response')
expect(res.ok).toBeTruthy()
expect(stderr).toContain(
'API response for /api/large-chunked-response exceeds 4MB. This will cause the request to fail in a future version.'
'API response for /api/large-chunked-response exceeds 4MB. API Routes are meant to respond quickly.'
)
})

it('should not warn if response body is larger than 4MB with bodyLimit config = false', async () => {
let res = await fetchViaHTTP(appPort, '/api/large-response-with-config')
expect(res.ok).toBeTruthy()
expect(stderr).not.toContain(
'API response for /api/large-response-with-config exceeds 4MB. API Routes are meant to respond quickly.'
)
})

it('should warn with configured size if response body is larger than configured size', async () => {
let res = await fetchViaHTTP(
appPort,
'/api/large-response-with-config-size'
)
expect(res.ok).toBeTruthy()
expect(stderr).toContain(
'API response for /api/large-response-with-config-size exceeds 5MB. API Routes are meant to respond quickly.'
)
})

Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Expand Up @@ -4796,6 +4796,11 @@
resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.1.tgz#5a284d193cfc61abb2e5a50d36ebbc50d942a32b"
integrity sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==

"@types/bytes@3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@types/bytes/-/bytes-3.1.1.tgz#67a876422e660dc4c10a27f3e5bcfbd5455f01d0"
integrity sha512-lOGyCnw+2JVPKU3wIV0srU0NyALwTBJlVSx5DfMQOFuuohA8y9S8orImpuIQikZ0uIQ8gehrRjxgQC1rLRi11w==

"@types/cacheable-request@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976"
Expand Down Expand Up @@ -7049,6 +7054,11 @@ bytes@3.1.0, bytes@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"

bytes@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a"
integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==

cacache@^12.0.2:
version "12.0.3"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390"
Expand Down