未验证 提交 c156e0c8 编写于 作者: L Lukáš Huvar 提交者: GitHub

Helpers update (#7686)

* Update helper function to invoke only on get

* Tests for body parsing

* Update api-utils.ts

* Update next-server.ts

* Update packages/next-server/server/next-server.ts
Co-Authored-By: NJJ Kasper <jj@jjsweb.site>
上级 ec958a3b
[package]
name = "next-rust"
version = "0.1.0"
authors = ["Tim Neutkens <timneutkens@me.com>"]
[dependencies]
......@@ -2,12 +2,15 @@ import { IncomingMessage } from 'http'
import { NextApiResponse, NextApiRequest } from '../lib/utils'
import { Stream } from 'stream'
import getRawBody from 'raw-body'
import { URL } from 'url'
import { parse } from 'content-type'
import { Params } from './router'
export type NextApiRequestCookies = { [key: string]: string }
export type NextApiRequestQuery = { [key: string]: string | string[] }
/**
* Parse incoming message like `json` or `urlencoded`
* @param req
* @param req request object
*/
export async function parseBody(req: NextApiRequest, limit: string = '1mb') {
const contentType = parse(req.headers['content-type'] || 'text/plain')
......@@ -55,14 +58,35 @@ function parseJson(str: string) {
* @param url of request
* @returns Object with key name of query argument and its value
*/
export function parseQuery({ url }: IncomingMessage) {
if (url) {
// This is just for parsing search params, base it's not important
export function getQueryParser({ url }: IncomingMessage) {
return function parseQuery(): NextApiRequestQuery {
const { URL } = require('url')
// we provide a placeholder base url because we only want searchParams
const params = new URL(url, 'https://n').searchParams
return reduceParams(params.entries())
} else {
return {}
const query: { [key: string]: string | string[] } = {}
for (const [key, value] of params) {
query[key] = value
}
return query
}
}
/**
* Parse cookeies from `req` header
* @param req request object
*/
export function getCookieParser(req: IncomingMessage) {
return function parseCookie(): NextApiRequestCookies {
const header: undefined | string | string[] = req.headers.cookie
if (!header) {
return {}
}
const { parse } = require('cookie')
return parse(Array.isArray(header) ? header.join(';') : header)
}
}
......@@ -131,14 +155,6 @@ export function sendJson(res: NextApiResponse, jsonBody: any): void {
res.send(jsonBody)
}
function reduceParams(params: IterableIterator<[string, string]>) {
const obj: any = {}
for (const [key, value] of params) {
obj[key] = value
}
return obj
}
/**
* Custom error class
*/
......@@ -166,3 +182,39 @@ export function sendError(
res.statusMessage = message
res.end()
}
interface LazyProps {
req: NextApiRequest
params?: Params | boolean
}
/**
* Execute getter function only if its needed
* @param LazyProps `req` and `params` for lazyProp
* @param prop name of property
* @param getter function to get data
*/
export function setLazyProp<T>(
{ req, params }: LazyProps,
prop: string,
getter: () => T
) {
const opts = { configurable: true, enumerable: true }
const optsReset = { ...opts, writable: true }
Object.defineProperty(req, prop, {
...opts,
get: () => {
let value = getter()
if (params && typeof params !== 'boolean') {
value = { ...value, ...params }
}
// we set the property on the object to avoid recalculating it
Object.defineProperty(req, prop, { ...optsReset, value })
return value
},
set: value => {
Object.defineProperty(req, prop, { ...optsReset, value })
},
})
}
......@@ -14,6 +14,9 @@ export function interopDefault(mod: any) {
export interface IPageConfig {
amp?: boolean | 'hybrid'
api?: {
bodyParser?: boolean
}
}
export type LoadComponentsReturnType = {
......
......@@ -23,15 +23,16 @@ import {
} from '../lib/router/utils'
import * as envConfig from '../lib/runtime-config'
import { NextApiRequest, NextApiResponse } from '../lib/utils'
import { parse as parseCookies } from 'cookie'
import {
parseQuery,
getQueryParser,
sendJson,
sendData,
parseBody,
sendError,
ApiError,
sendStatusCode,
setLazyProp,
getCookieParser,
} from './api-utils'
import loadConfig from './config'
import { recursiveReadDirSync } from './lib/recursive-readdir-sync'
......@@ -39,6 +40,7 @@ import {
interopDefault,
loadComponents,
LoadComponentsReturnType,
IPageConfig,
} from './load-components'
import { renderToHTML } from './render'
import { getPagePath } from './require'
......@@ -289,6 +291,7 @@ export default class Server {
res: NextApiResponse,
pathname: string
) {
let bodyParser = true
let params: Params | boolean = false
let resolverFunction = await this.resolveApiRequest(pathname)
......@@ -313,18 +316,28 @@ export default class Server {
}
try {
const resolverModule = require(resolverFunction)
if (resolverModule.config) {
const config: IPageConfig = resolverModule.config
if (config.api && config.api.bodyParser === false) {
bodyParser = false
}
}
// Parsing of cookies
req.cookies = parseCookies(req.headers.cookie || '')
setLazyProp({ req }, 'cookies', getCookieParser(req))
// Parsing query string
req.query = { ...parseQuery(req), ...params }
setLazyProp({ req, params }, 'query', getQueryParser(req))
// // Parsing of body
req.body = await parseBody(req)
if (bodyParser) {
req.body = await parseBody(req)
}
res.status = statusCode => sendStatusCode(res, statusCode)
res.send = data => sendData(res, data)
res.json = data => sendJson(res, data)
const resolver = interopDefault(require(resolverFunction))
const resolver = interopDefault(resolverModule)
resolver(req, res)
} catch (e) {
if (e instanceof ApiError) {
......
......@@ -48,6 +48,9 @@ export type NextPage<P = {}, IP = P> = {
*/
export type PageConfig = {
amp?: boolean | 'hybrid'
api?: {
bodyParser?: boolean
}
}
export { NextPageContext, NextComponentType, NextApiResponse, NextApiRequest }
export const config = {
api: {
bodyParser: false
}
}
export default (req, res) => {
if (!req.body) {
let buffer = ''
req.on('data', chunk => {
buffer += chunk
})
req.on('end', () => {
res.status(200).json(JSON.parse(Buffer.from(buffer).toString()))
})
}
}
export const config = {
api: {
bodyParser: true
}
}
export default (req, res) => {
if (req.body) {
res.status(200).json({ message: 'Parsed body' })
}
}
......@@ -103,6 +103,30 @@ function runTests (serverless = false) {
})
})
it('should parse body in handler', async () => {
const data = await fetchViaHTTP(appPort, '/api/no-parsing', null, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify([{ title: 'Nextjs' }])
}).then(res => res.ok && res.json())
expect(data).toEqual([{ title: 'Nextjs' }])
})
it('should parse body with config', async () => {
const data = await fetchViaHTTP(appPort, '/api/parsing', null, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify([{ title: 'Nextjs' }])
}).then(res => res.ok && res.json())
expect(data).toEqual({ message: 'Parsed body' })
})
it('should return empty cookies object', async () => {
const data = await fetchViaHTTP(appPort, '/api/cookies', null, {}).then(
res => res.ok && res.json()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册