Closed17

Next.js のサーバ起動とリクエストを処理する流れを追いかけてみる

snakasnaka

start-server.ts

packages/next/src/server/lib/start-server.ts#L105-L119

  // setup server listener as fast as possible
  const server = http.createServer(async (req, res) => {
    try {
      if (handlersPromise) {
        await handlersPromise
        handlersPromise = undefined
      }
      await requestHandler(req, res)
    } catch (err) {
      res.statusCode = 500
      res.end('Internal Server Error')
      Log.error(`Failed to handle request for ${req.url}`)
      console.error(err)
    }
  })

createServer

snakasnaka

handleRequest

packages/next/src/server/lib/router-server.ts#L442C2-L442C59

    const handleRequest = async (handleIndex: number) => {

packages/next/src/server/lib/router-server.ts#L555-L560C13

          await invokeRender(parsedUrl, 'pages', handleIndex, '/_error', {
            'x-invoke-status': '500',
            'x-invoke-error': JSON.stringify({
              message: `A conflicting public file and page file was found for path ${matchedOutput.itemPath} https://nextjs.org/docs/messages/conflicting-public-file-page`,
            }),
          })
snakasnaka

invokeRender

packages/next/src/server/lib/router-server.ts#L322-L328

    async function invokeRender(
      parsedUrl: NextUrlWithParsedQuery,
      type: keyof typeof renderWorkers,
      handleIndex: number,
      invokePath: string,
      additionalInvokeHeaders: Record<string, string> = {}
    ) {

packages/next/src/server/lib/router-server.ts#L374-L382C10

        invokeRes = await invokeRequest(
          renderUrl,
          {
            headers: invokeHeaders,
            method: req.method,
            signal: signalFromNodeResponse(res),
          },
          getRequestMeta(req, '__NEXT_CLONABLE_BODY')?.cloneBodyStream()
        )
snakasnaka

invokeRequest

packages/next/src/server/lib/server-ipc/invoke-request.ts#L5-L13

export const invokeRequest = async (
  targetUrl: string,
  requestInit: {
    headers: IncomingMessage['headers']
    method: IncomingMessage['method']
    signal?: AbortSignal
  },
  readableBody?: Readable | ReadableStream
) => {

fetch で targetUrl にリクエストを投げている

  return await fetch(targetUrl, {
    headers: invokeHeaders as any as Headers,
    method: requestInit.method,
    redirect: 'manual',
    signal: requestInit.signal,


    ...(requestInit.method !== 'GET' &&
    requestInit.method !== 'HEAD' &&
    readableBody
      ? {
          body: readableBody as BodyInit,
          duplex: 'half',
        }
      : {}),


    next: {
      // @ts-ignore
      internal: true,
    },
  })
snakasnaka

createWorker

packages/next/src/lib/worker.ts#L36-L51

    const createWorker = () => {
      this._worker = new JestWorker(workerPath, {
        ...farmOptions,
        forkOptions: {
          ...farmOptions.forkOptions,
          env: {
            ...((farmOptions.forkOptions?.env || {}) as any),
            ...process.env,
            // we don't pass down NODE_OPTIONS as it can
            // extra memory usage
            NODE_OPTIONS: getNodeOptionsWithoutInspect()
              .replace(/--max-old-space-size=[\d]{1,}/, '')
              .trim(),
          } as any,
        },
      }) as JestWorker

jest-worker って使ってるんだ
Jest という名前からテストをイメージするけど、それをプロダクションのサービスで利用しているの以外 ( 名前がミスリード )

https://www.npmjs.com/package/jest-worker

snakasnaka

サーバ起動処理のつづき

getRequestHandlers - http.server が listening イベント発火したら呼び出される

packages/next/src/server/lib/start-server.ts#L31-L61

export async function getRequestHandlers({
  dir,
  port,
  isDev,
  hostname,
  minimalMode,
  isNodeDebugging,
  keepAliveTimeout,
  experimentalTestProxy,
}: {
  dir: string
  port: number
  isDev: boolean
  hostname: string
  minimalMode?: boolean
  isNodeDebugging?: boolean
  keepAliveTimeout?: number
  experimentalTestProxy?: boolean
}): ReturnType<typeof initialize> {
  return initialize({
    dir,
    port,
    hostname,
    dev: isDev,
    minimalMode,
    workerType: 'router',
    isNodeDebugging: isNodeDebugging || false,
    keepAliveTimeout,
    experimentalTestProxy,
  })
}
snakasnaka

router-server ( next-router-worker )

packages/next/src/server/lib/router-server.ts#L58-L69

export async function initialize(opts: {
  dir: string
  port: number
  dev: boolean
  minimalMode?: boolean
  hostname?: string
  workerType: 'router' | 'render'
  isNodeDebugging: boolean
  keepAliveTimeout?: number
  customServer?: boolean
  experimentalTestProxy?: boolean
}): Promise<[WorkerRequestHandler, WorkerUpgradeHandler]> {

createIpcServer - 内部的な http サーバを立ち上げている

packages/next/src/server/lib/router-server.ts#L142C1-L150C7

  const { ipcPort, ipcValidationKey } = await createIpcServer({
    async ensurePage(
      match: Parameters<
        InstanceType<typeof import('../dev/hot-reloader').default>['ensurePage']
      >[0]
    ) {
      // TODO: remove after ensure is pulled out of server
      return await devInstance?.hotReloader.ensurePage(match)
    },
  renderWorkers.pages = await createWorker(
    ipcPort,
    ipcValidationKey,
    opts.isNodeDebugging,
    'pages',
    config,
    initialEnv
  )
snakasnaka

server-ipc

createIpcServer

packages/next/src/server/lib/server-ipc/index.ts#L13C1-L21

// we can't use process.send as jest-worker relies on
// it already and can cause unexpected message errors
// so we create an IPC server for communicating
export async function createIpcServer(
  server: InstanceType<typeof NextServer>
): Promise<{
  ipcPort: number
  ipcServer: import('http').Server
  ipcValidationKey: string
}> {
このスクラップは5ヶ月前にクローズされました