85cc454023
A middleware can work as a proxy intercepting requests and then performing a `fetch` to the destination adding headers to the request / response as a "man in the middle". When using `fetch` from a middleware we are not in the context of a browser so we can't really use relative URLs, they must be always absolute. Now consider the previous case when middleware is running in *server mode*. Typically in order to know the host where we are fetching we can use the `request.nextUrl` which is given to the middleware but in this case the invoker (which is next-server) has no context of the hostname, nor the port. To solve this use case we must make the invoker of the middleware aware of the origin hostname and port. This PR: - Introduces `hostname` and `port` as options for `NextServer`. - Refactors types in `NextServer` and `NextDevServer` moving type only imports to the top of the file. - Refactors `startServer` to do a best guess on the `hostname` and `port`, passing them down. - Exposes `.port` and `.hostname` to be retrieved from the `app`. In an upcoming PR we will pass the host guess to the middleware to solve the relative URL issue.
186 lines
5.1 KiB
TypeScript
186 lines
5.1 KiB
TypeScript
import type { IncomingMessage, ServerResponse } from 'http'
|
|
import type { Options as DevServerOptions } from './dev/next-dev-server'
|
|
import type { RequestHandler } from './next-server'
|
|
import type { UrlWithParsedQuery } from 'url'
|
|
|
|
import './node-polyfill-fetch'
|
|
import { default as Server } from './next-server'
|
|
import * as log from '../build/output/log'
|
|
import loadConfig from './config'
|
|
import { resolve } from 'path'
|
|
import { NON_STANDARD_NODE_ENV } from '../lib/constants'
|
|
import { PHASE_DEVELOPMENT_SERVER } from '../shared/lib/constants'
|
|
import { PHASE_PRODUCTION_SERVER } from '../shared/lib/constants'
|
|
|
|
let ServerImpl: typeof Server
|
|
|
|
const getServerImpl = async () => {
|
|
if (ServerImpl === undefined)
|
|
ServerImpl = (await Promise.resolve(require('./next-server'))).default
|
|
return ServerImpl
|
|
}
|
|
|
|
export type NextServerOptions = Partial<DevServerOptions>
|
|
|
|
export class NextServer {
|
|
private serverPromise?: Promise<Server>
|
|
private server?: Server
|
|
private reqHandlerPromise?: Promise<RequestHandler>
|
|
private preparedAssetPrefix?: string
|
|
public options: NextServerOptions
|
|
|
|
constructor(options: NextServerOptions) {
|
|
this.options = options
|
|
}
|
|
|
|
get hostname() {
|
|
return this.options.hostname
|
|
}
|
|
|
|
get port() {
|
|
return this.options.port
|
|
}
|
|
|
|
getRequestHandler(): RequestHandler {
|
|
return async (
|
|
req: IncomingMessage,
|
|
res: ServerResponse,
|
|
parsedUrl?: UrlWithParsedQuery
|
|
) => {
|
|
const requestHandler = await this.getServerRequestHandler()
|
|
return requestHandler(req, res, parsedUrl)
|
|
}
|
|
}
|
|
|
|
setAssetPrefix(assetPrefix: string) {
|
|
if (this.server) {
|
|
this.server.setAssetPrefix(assetPrefix)
|
|
} else {
|
|
this.preparedAssetPrefix = assetPrefix
|
|
}
|
|
}
|
|
|
|
logError(...args: Parameters<Server['logError']>) {
|
|
if (this.server) {
|
|
this.server.logError(...args)
|
|
}
|
|
}
|
|
|
|
async render(...args: Parameters<Server['render']>) {
|
|
const server = await this.getServer()
|
|
return server.render(...args)
|
|
}
|
|
|
|
async renderToHTML(...args: Parameters<Server['renderToHTML']>) {
|
|
const server = await this.getServer()
|
|
return server.renderToHTML(...args)
|
|
}
|
|
|
|
async renderError(...args: Parameters<Server['renderError']>) {
|
|
const server = await this.getServer()
|
|
return server.renderError(...args)
|
|
}
|
|
|
|
async renderErrorToHTML(...args: Parameters<Server['renderErrorToHTML']>) {
|
|
const server = await this.getServer()
|
|
return server.renderErrorToHTML(...args)
|
|
}
|
|
|
|
async render404(...args: Parameters<Server['render404']>) {
|
|
const server = await this.getServer()
|
|
return server.render404(...args)
|
|
}
|
|
|
|
async serveStatic(...args: Parameters<Server['serveStatic']>) {
|
|
const server = await this.getServer()
|
|
return server.serveStatic(...args)
|
|
}
|
|
|
|
async prepare() {
|
|
const server = await this.getServer()
|
|
return server.prepare()
|
|
}
|
|
|
|
async close() {
|
|
const server = await this.getServer()
|
|
return (server as any).close()
|
|
}
|
|
|
|
private async createServer(options: DevServerOptions): Promise<Server> {
|
|
if (options.dev) {
|
|
const DevServer = require('./dev/next-dev-server').default
|
|
return new DevServer(options)
|
|
}
|
|
const ServerImplementation = await getServerImpl()
|
|
return new ServerImplementation(options)
|
|
}
|
|
|
|
private async loadConfig() {
|
|
const phase = this.options.dev
|
|
? PHASE_DEVELOPMENT_SERVER
|
|
: PHASE_PRODUCTION_SERVER
|
|
const dir = resolve(this.options.dir || '.')
|
|
const conf = await loadConfig(phase, dir, this.options.conf)
|
|
return conf
|
|
}
|
|
|
|
private async getServer() {
|
|
if (!this.serverPromise) {
|
|
setTimeout(getServerImpl, 10)
|
|
this.serverPromise = this.loadConfig().then(async (conf) => {
|
|
this.server = await this.createServer({
|
|
...this.options,
|
|
conf,
|
|
})
|
|
if (this.preparedAssetPrefix) {
|
|
this.server.setAssetPrefix(this.preparedAssetPrefix)
|
|
}
|
|
return this.server
|
|
})
|
|
}
|
|
return this.serverPromise
|
|
}
|
|
|
|
private async getServerRequestHandler() {
|
|
// Memoize request handler creation
|
|
if (!this.reqHandlerPromise) {
|
|
this.reqHandlerPromise = this.getServer().then((server) =>
|
|
server.getRequestHandler().bind(server)
|
|
)
|
|
}
|
|
return this.reqHandlerPromise
|
|
}
|
|
}
|
|
|
|
// This file is used for when users run `require('next')`
|
|
function createServer(options: NextServerOptions): NextServer {
|
|
if (options == null) {
|
|
throw new Error(
|
|
'The server has not been instantiated properly. https://nextjs.org/docs/messages/invalid-server-options'
|
|
)
|
|
}
|
|
|
|
if (
|
|
!('isNextDevCommand' in options) &&
|
|
process.env.NODE_ENV &&
|
|
!['production', 'development', 'test'].includes(process.env.NODE_ENV)
|
|
) {
|
|
log.warn(NON_STANDARD_NODE_ENV)
|
|
}
|
|
|
|
if (options.dev && typeof options.dev !== 'boolean') {
|
|
console.warn(
|
|
"Warning: 'dev' is not a boolean which could introduce unexpected behavior. https://nextjs.org/docs/messages/invalid-server-options"
|
|
)
|
|
}
|
|
|
|
return new NextServer(options)
|
|
}
|
|
|
|
// Support commonjs `require('next')`
|
|
module.exports = createServer
|
|
exports = module.exports
|
|
|
|
// Support `import next from 'next'`
|
|
export default createServer
|
|
export type { RequestHandler }
|