提交 da1ef506 编写于 作者: J JJ Kasper 提交者: Joe Haddad

Refactor next-server some for easier page checking (#9671)

* De-dupe pagesManifest

* Update handleApiRequest a bit

* Simplify handleApiRequest a bit

* Update packages/next/next-server/server/next-server.ts
Co-authored-by: NJoe Haddad <joe.haddad@zeit.co>
上级 6c103ef5
import { IncomingMessage } from 'http'
import { IncomingMessage, ServerResponse } from 'http'
import { NextApiResponse, NextApiRequest } from '../lib/utils'
import { Stream } from 'stream'
import getRawBody from 'raw-body'
......@@ -11,12 +11,15 @@ export type NextApiRequestCookies = { [key: string]: string }
export type NextApiRequestQuery = { [key: string]: string | string[] }
export async function apiResolver(
req: NextApiRequest,
res: NextApiResponse,
req: IncomingMessage,
res: ServerResponse,
params: any,
resolverModule: any,
onError?: ({ err }: { err: any }) => Promise<void>
) {
const apiReq = req as NextApiRequest
const apiRes = res as NextApiResponse
try {
let config: PageConfig = {}
let bodyParser = true
......@@ -33,32 +36,32 @@ export async function apiResolver(
}
}
// Parsing of cookies
setLazyProp({ req }, 'cookies', getCookieParser(req))
setLazyProp({ req: apiReq }, 'cookies', getCookieParser(req))
// Parsing query string
setLazyProp({ req, params }, 'query', getQueryParser(req))
setLazyProp({ req: apiReq, params }, 'query', getQueryParser(req))
// // Parsing of body
if (bodyParser) {
req.body = await parseBody(
req,
apiReq.body = await parseBody(
apiReq,
config.api && config.api.bodyParser && config.api.bodyParser.sizeLimit
? config.api.bodyParser.sizeLimit
: '1mb'
)
}
res.status = statusCode => sendStatusCode(res, statusCode)
res.send = data => sendData(res, data)
res.json = data => sendJson(res, data)
apiRes.status = statusCode => sendStatusCode(apiRes, statusCode)
apiRes.send = data => sendData(apiRes, data)
apiRes.json = data => sendJson(apiRes, data)
const resolver = interopDefault(resolverModule)
resolver(req, res)
} catch (err) {
if (err instanceof ApiError) {
sendError(res, err.statusCode, err.message)
sendError(apiRes, err.statusCode, err.message)
} else {
console.error(err)
if (onError) await onError({ err })
sendError(res, 500, 'Internal Server Error')
sendError(apiRes, 500, 'Internal Server Error')
}
}
}
......
......@@ -76,7 +76,8 @@ export default class Server {
pagesDir?: string
publicDir: string
hasStaticDir: boolean
pagesManifest: string
serverBuildDir: string
pagesManifest?: { [name: string]: string }
buildId: string
renderOpts: {
poweredByHeader: boolean
......@@ -114,13 +115,6 @@ export default class Server {
this.distDir = join(this.dir, this.nextConfig.distDir)
this.publicDir = join(this.dir, CLIENT_PUBLIC_FILES_PATH)
this.hasStaticDir = fs.existsSync(join(this.dir, 'static'))
this.pagesManifest = join(
this.distDir,
this.nextConfig.target === 'server'
? SERVER_DIRECTORY
: SERVERLESS_DIRECTORY,
PAGES_MANIFEST
)
// Only serverRuntimeConfig needs the default
// publicRuntimeConfig gets it's default in client/index.js
......@@ -164,19 +158,26 @@ export default class Server {
})
}
this.serverBuildDir = join(
this.distDir,
this._isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY
)
const pagesManifestPath = join(this.serverBuildDir, PAGES_MANIFEST)
if (!dev) {
this.pagesManifest = require(pagesManifestPath)
}
this.router = new Router(this.generateRoutes())
this.setAssetPrefix(assetPrefix)
// call init-server middleware, this is also handled
// individually in serverless bundles when deployed
if (!dev && this.nextConfig.experimental.plugins) {
const serverPath = join(
this.distDir,
this._isLikeServerless ? 'serverless' : 'server'
)
const initServer = require(join(serverPath, 'init-server.js')).default
const initServer = require(join(this.serverBuildDir, 'init-server.js'))
.default
this.onErrorMiddleware = require(join(
serverPath,
this.serverBuildDir,
'on-error-server.js'
)).default
initServer()
......@@ -495,6 +496,24 @@ export default class Server {
return routes
}
private async getPagePath(pathname: string) {
return getPagePath(
pathname,
this.distDir,
this._isLikeServerless,
this.renderOpts.dev
)
}
protected async hasPage(pathname: string): Promise<boolean> {
let found = false
try {
found = !!(await this.getPagePath(pathname))
} catch (_) {}
return found
}
protected async _beforeCatchAllRender(
_req: IncomingMessage,
_res: ServerResponse,
......@@ -504,6 +523,9 @@ export default class Server {
return false
}
// Used to build API page in development
protected async ensureApiPage(pathname: string) {}
/**
* Resolves `API` request, in development builds on demand
* @param req http request
......@@ -511,77 +533,53 @@ export default class Server {
* @param pathname path of request
*/
private async handleApiRequest(
req: NextApiRequest,
res: NextApiResponse,
req: IncomingMessage,
res: ServerResponse,
pathname: string
) {
let page = pathname
let params: Params | boolean = false
let resolverFunction: any
try {
resolverFunction = await this.resolveApiRequest(pathname)
} catch (err) {}
let pageFound = await this.hasPage(page)
if (
this.dynamicRoutes &&
this.dynamicRoutes.length > 0 &&
!resolverFunction
) {
if (!pageFound && this.dynamicRoutes) {
for (const dynamicRoute of this.dynamicRoutes) {
params = dynamicRoute.match(pathname)
if (params) {
resolverFunction = await this.resolveApiRequest(dynamicRoute.page)
page = dynamicRoute.page
pageFound = true
break
}
}
}
if (!resolverFunction) {
if (!pageFound) {
return this.render404(req, res)
}
// Make sure the page is built before getting the path
// or else it won't be in the manifest yet
await this.ensureApiPage(page)
const builtPagePath = await this.getPagePath(page)
const pageModule = require(builtPagePath)
if (!this.renderOpts.dev && this._isLikeServerless) {
const mod = require(resolverFunction)
if (typeof mod.default === 'function') {
return mod.default(req, res)
if (typeof pageModule.default === 'function') {
return pageModule.default(req, res)
}
}
await apiResolver(
req,
res,
params,
resolverFunction ? require(resolverFunction) : undefined,
this.onErrorMiddleware
)
}
/**
* Resolves path to resolver function
* @param pathname path of request
*/
protected async resolveApiRequest(pathname: string): Promise<string | null> {
return getPagePath(
pathname,
this.distDir,
this._isLikeServerless,
this.renderOpts.dev
)
await apiResolver(req, res, params, pageModule, this.onErrorMiddleware)
}
protected generatePublicRoutes(): Route[] {
const routes: Route[] = []
const publicFiles = recursiveReadDirSync(this.publicDir)
const serverBuildPath = join(
this.distDir,
this._isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY
)
const pagesManifest = require(join(serverBuildPath, PAGES_MANIFEST))
publicFiles.forEach(path => {
const unixPath = path.replace(/\\/g, '/')
// Only include public files that will not replace a page path
if (!pagesManifest[unixPath]) {
// this should not occur now that we check this during build
if (!this.pagesManifest![unixPath]) {
routes.push({
match: route(unixPath),
type: 'route',
......@@ -601,8 +599,9 @@ export default class Server {
}
protected getDynamicRoutes() {
const manifest = require(this.pagesManifest)
const dynamicRoutedPages = Object.keys(manifest).filter(isDynamicRoute)
const dynamicRoutedPages = Object.keys(this.pagesManifest!).filter(
isDynamicRoute
)
return getSortedRoutes(dynamicRoutedPages).map(page => ({
page,
match: getRouteMatcher(getRouteRegex(page)),
......
......@@ -246,6 +246,15 @@ export default class DevServer extends Server {
}
}
protected async hasPage(pathname: string): Promise<boolean> {
const pageFile = await findPageFile(
this.pagesDir!,
normalizePagePath(pathname),
this.nextConfig.pageExtensions
)
return !!pageFile
}
protected async _beforeCatchAllRender(
req: IncomingMessage,
res: ServerResponse,
......@@ -257,13 +266,7 @@ export default class DevServer extends Server {
// check for a public file, throwing error if there's a
// conflicting page
if (await this.hasPublicFile(pathname!)) {
const pageFile = await findPageFile(
this.pagesDir!,
normalizePagePath(pathname!),
this.nextConfig.pageExtensions
)
if (pageFile) {
if (await this.hasPage(pathname!)) {
const err = new Error(
`A conflicting public file and page file was found for path ${pathname} https://err.sh/zeit/next.js/conflicting-public-file-page`
)
......@@ -381,21 +384,8 @@ export default class DevServer extends Server {
return !snippet.includes('data-amp-development-mode-only')
}
/**
* Check if resolver function is build or request new build for this function
* @param {string} pathname
*/
protected async resolveApiRequest(pathname: string): Promise<string | null> {
try {
await this.hotReloader!.ensurePage(pathname)
} catch (err) {
// API route dosn't exist => return 404
if (err.code === 'ENOENT') {
return null
}
}
const resolvedPath = await super.resolveApiRequest(pathname)
return resolvedPath
protected async ensureApiPage(pathname: string) {
return this.hotReloader!.ensurePage(pathname)
}
async renderToHTML(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册