2016-10-19 12:41:45 +00:00
import { resolve , join } from 'path'
import { parse } from 'url'
2017-01-11 20:16:18 +00:00
import fs from 'mz/fs'
2017-01-12 15:38:43 +00:00
import http , { STATUS _CODES } from 'http'
2016-12-16 20:33:08 +00:00
import {
renderToHTML ,
renderErrorToHTML ,
renderJSON ,
renderErrorJSON ,
2016-12-31 12:46:23 +00:00
sendHTML ,
serveStatic ,
serveStaticWithGzip
2016-12-16 20:33:08 +00:00
} from './render'
2016-10-05 23:52:50 +00:00
import Router from './router'
2016-10-17 07:05:46 +00:00
import HotReloader from './hot-reloader'
2016-10-19 12:41:45 +00:00
import { resolveFromList } from './resolve'
2016-12-19 15:27:47 +00:00
import getConfig from './config'
// We need to go up one more level since we are in the `dist` directory
import pkg from '../../package'
2016-10-05 23:52:50 +00:00
export default class Server {
2016-12-16 20:33:08 +00:00
constructor ( { dir = '.' , dev = false , staticMarkup = false , quiet = false } = { } ) {
2016-10-06 11:05:52 +00:00
this . dir = resolve ( dir )
this . dev = dev
2016-12-16 20:33:08 +00:00
this . quiet = quiet
this . renderOpts = { dir : this . dir , dev , staticMarkup }
2016-10-05 23:52:50 +00:00
this . router = new Router ( )
2016-12-21 14:39:08 +00:00
this . hotReloader = dev ? new HotReloader ( this . dir , { quiet } ) : null
2016-12-16 20:33:08 +00:00
this . http = null
2016-12-27 23:27:51 +00:00
this . config = getConfig ( this . dir )
2016-12-16 20:33:08 +00:00
this . defineRoutes ( )
}
2016-10-05 23:52:50 +00:00
2016-12-16 20:33:08 +00:00
getRequestHandler ( ) {
2017-02-02 06:51:08 +00:00
return ( req , res , parsedUrl ) => {
2017-02-09 03:22:48 +00:00
if ( ! parsedUrl ) {
2017-02-02 06:51:08 +00:00
parsedUrl = parse ( req . url , true )
}
2017-02-09 03:22:48 +00:00
if ( ! parsedUrl . query ) {
throw new Error ( 'Please provide a parsed url to `handle` as third parameter. See https://github.com/zeit/next.js#custom-server-and-routing for an example.' )
}
2017-02-13 02:12:32 +00:00
return this . run ( req , res , parsedUrl )
2016-10-09 09:25:38 +00:00
. catch ( ( err ) => {
2016-12-16 20:33:08 +00:00
if ( ! this . quiet ) console . error ( err )
2016-11-24 14:03:16 +00:00
res . statusCode = 500
2017-01-12 15:38:43 +00:00
res . end ( STATUS _CODES [ 500 ] )
2016-10-05 23:52:50 +00:00
} )
2016-12-16 20:33:08 +00:00
}
2016-10-05 23:52:50 +00:00
}
2016-12-16 20:33:08 +00:00
async prepare ( ) {
2016-10-17 07:07:41 +00:00
if ( this . hotReloader ) {
await this . hotReloader . start ( )
}
2017-01-11 20:16:18 +00:00
this . renderOpts . buildId = await this . readBuildId ( )
2016-10-17 07:07:41 +00:00
}
2016-12-17 04:04:40 +00:00
async close ( ) {
if ( this . hotReloader ) {
await this . hotReloader . stop ( )
}
2017-01-12 04:14:49 +00:00
if ( this . http ) {
await new Promise ( ( resolve , reject ) => {
this . http . close ( ( err ) => {
if ( err ) return reject ( err )
return resolve ( )
} )
} )
}
2016-12-17 04:04:40 +00:00
}
2016-10-17 07:07:41 +00:00
defineRoutes ( ) {
2017-01-12 15:38:43 +00:00
const routes = {
'/_next-prefetcher.js' : async ( req , res , params ) => {
const p = join ( _ _dirname , '../client/next-prefetcher-bundle.js' )
await this . serveStatic ( req , res , p )
} ,
'/_next/:buildId/main.js' : async ( req , res , params ) => {
this . handleBuildId ( params . buildId , res )
const p = join ( this . dir , '.next/main.js' )
await this . serveStaticWithGzip ( req , res , p )
} ,
'/_next/:buildId/commons.js' : async ( req , res , params ) => {
this . handleBuildId ( params . buildId , res )
const p = join ( this . dir , '.next/commons.js' )
await this . serveStaticWithGzip ( req , res , p )
} ,
'/_next/:buildId/pages/:path*' : async ( req , res , params ) => {
this . handleBuildId ( params . buildId , res )
const paths = params . path || [ 'index' ]
const pathname = ` / ${ paths . join ( '/' ) } `
await this . renderJSON ( req , res , pathname )
} ,
'/_next/:path+' : async ( req , res , params ) => {
const p = join ( _ _dirname , '..' , 'client' , ... ( params . path || [ ] ) )
await this . serveStatic ( req , res , p )
} ,
'/static/:path+' : async ( req , res , params ) => {
const p = join ( this . dir , 'static' , ... ( params . path || [ ] ) )
await this . serveStatic ( req , res , p )
} ,
2017-02-02 06:51:08 +00:00
'/:path*' : async ( req , res , params , parsedUrl ) => {
const { pathname , query } = parsedUrl
2017-01-12 15:38:43 +00:00
await this . render ( req , res , pathname , query )
}
}
2016-10-05 23:52:50 +00:00
2017-01-12 15:38:43 +00:00
for ( const method of [ 'GET' , 'HEAD' ] ) {
for ( const p of Object . keys ( routes ) ) {
this . router . add ( method , p , routes [ p ] )
}
}
2016-12-16 20:33:08 +00:00
}
2016-10-05 23:52:50 +00:00
2017-02-12 11:26:10 +00:00
async start ( port , hostname ) {
2016-12-16 20:33:08 +00:00
await this . prepare ( )
this . http = http . createServer ( this . getRequestHandler ( ) )
await new Promise ( ( resolve , reject ) => {
2017-02-01 20:36:23 +00:00
// This code catches EADDRINUSE error if the port is already in use
this . http . on ( 'error' , reject )
this . http . on ( 'listening' , ( ) => resolve ( ) )
2017-02-12 16:23:42 +00:00
this . http . listen ( port , hostname )
2016-10-05 23:52:50 +00:00
} )
}
2017-02-02 06:51:08 +00:00
async run ( req , res , parsedUrl ) {
2016-11-23 18:32:49 +00:00
if ( this . hotReloader ) {
await this . hotReloader . run ( req , res )
}
2017-02-02 06:51:08 +00:00
const fn = this . router . match ( req , res , parsedUrl )
2016-10-05 23:52:50 +00:00
if ( fn ) {
await fn ( )
2017-01-12 15:38:43 +00:00
return
}
if ( req . method === 'GET' || req . method === 'HEAD' ) {
2017-02-02 06:51:08 +00:00
await this . render404 ( req , res , parsedUrl )
2017-01-12 15:38:43 +00:00
} else {
res . statusCode = 501
res . end ( STATUS _CODES [ 501 ] )
2016-10-05 23:52:50 +00:00
}
}
2016-12-16 20:33:08 +00:00
async render ( req , res , pathname , query ) {
2016-12-19 15:27:47 +00:00
if ( this . config . poweredByHeader ) {
res . setHeader ( 'X-Powered-By' , ` Next.js ${ pkg . version } ` )
}
2016-12-16 20:33:08 +00:00
const html = await this . renderToHTML ( req , res , pathname , query )
2017-01-12 15:38:43 +00:00
sendHTML ( res , html , req . method )
2016-12-16 20:33:08 +00:00
}
2016-10-19 12:41:45 +00:00
2016-12-16 20:33:08 +00:00
async renderToHTML ( req , res , pathname , query ) {
if ( this . dev ) {
const compilationErr = this . getCompilationError ( pathname )
if ( compilationErr ) {
res . statusCode = 500
return this . renderErrorToHTML ( compilationErr , req , res , pathname , query )
}
2016-11-24 14:03:16 +00:00
}
try {
2016-12-16 20:33:08 +00:00
return await renderToHTML ( req , res , pathname , query , this . renderOpts )
2016-11-24 14:03:16 +00:00
} catch ( err ) {
2016-12-16 20:33:08 +00:00
if ( err . code === 'ENOENT' ) {
res . statusCode = 404
return this . renderErrorToHTML ( null , req , res , pathname , query )
} else {
if ( ! this . quiet ) console . error ( err )
res . statusCode = 500
return this . renderErrorToHTML ( err , req , res , pathname , query )
2016-10-05 23:52:50 +00:00
}
}
2016-11-24 14:03:16 +00:00
}
2016-12-16 20:33:08 +00:00
async renderError ( err , req , res , pathname , query ) {
const html = await this . renderErrorToHTML ( err , req , res , pathname , query )
2017-01-12 15:38:43 +00:00
sendHTML ( res , html , req . method )
2016-10-05 23:52:50 +00:00
}
2016-12-16 20:33:08 +00:00
async renderErrorToHTML ( err , req , res , pathname , query ) {
if ( this . dev ) {
const compilationErr = this . getCompilationError ( '/_error' )
if ( compilationErr ) {
res . statusCode = 500
return renderErrorToHTML ( compilationErr , req , res , pathname , query , this . renderOpts )
}
2016-11-24 14:03:16 +00:00
}
2016-10-19 12:41:45 +00:00
2016-11-24 14:03:16 +00:00
try {
2016-12-16 20:33:08 +00:00
return await renderErrorToHTML ( err , req , res , pathname , query , this . renderOpts )
} catch ( err2 ) {
if ( this . dev ) {
if ( ! this . quiet ) console . error ( err2 )
res . statusCode = 500
return renderErrorToHTML ( err2 , req , res , pathname , query , this . renderOpts )
2016-11-24 14:03:16 +00:00
} else {
2016-12-16 20:33:08 +00:00
throw err2
2016-10-05 23:52:50 +00:00
}
}
2016-11-24 14:03:16 +00:00
}
2017-02-02 06:51:08 +00:00
async render404 ( req , res , parsedUrl = parse ( req . url , true ) ) {
const { pathname , query } = parsedUrl
2016-12-16 20:33:08 +00:00
res . statusCode = 404
2017-01-12 15:38:43 +00:00
this . renderError ( null , req , res , pathname , query )
2016-12-16 20:33:08 +00:00
}
2016-11-03 15:12:37 +00:00
2016-12-31 12:46:23 +00:00
async renderJSON ( req , res , page ) {
2016-12-16 20:33:08 +00:00
if ( this . dev ) {
const compilationErr = this . getCompilationError ( page )
if ( compilationErr ) {
2016-12-31 12:46:23 +00:00
return this . renderErrorJSON ( compilationErr , req , res )
2016-12-16 20:33:08 +00:00
}
2016-11-03 15:12:37 +00:00
}
2016-10-10 04:24:30 +00:00
2016-11-24 14:03:16 +00:00
try {
2016-12-31 12:46:23 +00:00
await renderJSON ( req , res , page , this . renderOpts )
2016-11-24 14:03:16 +00:00
} catch ( err ) {
2016-12-16 20:33:08 +00:00
if ( err . code === 'ENOENT' ) {
res . statusCode = 404
2016-12-31 12:46:23 +00:00
return this . renderErrorJSON ( null , req , res )
2016-11-24 14:03:16 +00:00
} else {
2016-12-16 20:33:08 +00:00
if ( ! this . quiet ) console . error ( err )
res . statusCode = 500
2016-12-31 12:46:23 +00:00
return this . renderErrorJSON ( err , req , res )
2016-11-24 14:03:16 +00:00
}
}
2016-10-10 04:24:30 +00:00
}
2016-12-31 12:46:23 +00:00
async renderErrorJSON ( err , req , res ) {
2016-12-16 20:33:08 +00:00
if ( this . dev ) {
const compilationErr = this . getCompilationError ( '/_error' )
if ( compilationErr ) {
res . statusCode = 500
2016-12-31 12:46:23 +00:00
return renderErrorJSON ( compilationErr , req , res , this . renderOpts )
2016-12-16 20:33:08 +00:00
}
}
2016-12-31 12:46:23 +00:00
return renderErrorJSON ( err , req , res , this . renderOpts )
2016-10-05 23:52:50 +00:00
}
2016-10-19 12:41:45 +00:00
2017-01-01 19:36:37 +00:00
async serveStaticWithGzip ( req , res , path ) {
2017-02-13 02:12:32 +00:00
await this . _serveStatic ( req , res , ( ) => {
2017-01-01 19:36:37 +00:00
return serveStaticWithGzip ( req , res , path )
} )
}
serveStatic ( req , res , path ) {
this . _serveStatic ( req , res , ( ) => {
return serveStatic ( req , res , path )
} )
}
async _serveStatic ( req , res , fn ) {
try {
await fn ( )
} catch ( err ) {
if ( err . code === 'ENOENT' ) {
this . render404 ( req , res )
} else {
throw err
}
}
}
2017-01-11 20:16:18 +00:00
async readBuildId ( ) {
const buildIdPath = join ( this . dir , '.next' , 'BUILD_ID' )
try {
const buildId = await fs . readFile ( buildIdPath , 'utf8' )
return buildId . trim ( )
} catch ( err ) {
if ( err . code === 'ENOENT' ) {
return '-'
} else {
throw err
}
}
}
handleBuildId ( buildId , res ) {
if ( this . dev ) return
if ( buildId !== this . renderOpts . buildId ) {
const errorMessage = 'Build id mismatch!' +
'Seems like the server and the client version of files are not the same.'
throw new Error ( errorMessage )
}
res . setHeader ( 'Cache-Control' , 'max-age=365000000, immutable' )
}
2016-12-16 20:33:08 +00:00
getCompilationError ( page ) {
2016-10-19 12:41:45 +00:00
if ( ! this . hotReloader ) return
const errors = this . hotReloader . getCompilationErrors ( )
if ( ! errors . size ) return
2016-12-16 20:33:08 +00:00
const id = join ( this . dir , '.next' , 'bundles' , 'pages' , page )
const p = resolveFromList ( id , errors . keys ( ) )
if ( p ) return errors . get ( p ) [ 0 ]
2016-10-19 12:41:45 +00:00
}
2016-10-05 23:52:50 +00:00
}