Created
February 5, 2019 21:09
-
-
Save Toxicable/228d72c2a1e5c17c2a0d9d21608bacf5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'zone.js/dist/zone-node'; | |
import 'reflect-metadata'; | |
import * as http from 'http'; | |
import * as fs from 'fs'; | |
import * as util from 'util'; | |
import * as path from 'path'; | |
import * as url from 'url'; | |
import * as mime from 'mime-types'; | |
import * as os from 'os'; | |
import { ɵCommonEngine as CommonEngine } from '@nguniversal/common/engine'; | |
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader'; | |
import { enableProdMode } from '@angular/core'; | |
const statAsync = util.promisify(fs.stat); | |
const { Worker, isMainThread, parentPort, threadId } = require('worker_threads'); | |
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./server/main'); | |
const STATIC_FILE_ROOT = '/dist/browser'; | |
const documentFilePath = path.join(process.cwd(), STATIC_FILE_ROOT, 'index.html'); | |
let requestId = 0; | |
function startServer() { | |
let workerPoolCurrentIndex = 0; | |
// leave 1 cpu for the main thread | |
const numberOfWorkers = os.cpus().length - 1; | |
const workerPool: any[] = Array(numberOfWorkers).fill(null).map(_ => new Worker(__filename)); | |
const PORT = process.env.PORT || 4000; | |
const server = http.createServer(async (req, res) => { | |
const uri = url.parse(req.url).pathname; | |
console.log(uri); | |
// we use this to set the browser folder as a static root | |
const fileUri = path.join(process.cwd(), STATIC_FILE_ROOT, uri); | |
// might not fit all use cases, but in general your application urls shouldn't have dots in them | |
if (fileUri.includes('.')) { | |
// apparently fs.exists (async version is deprecated) | |
const exists = fs.existsSync(fileUri); | |
if (!exists) { | |
// if there's a dot in the path and nothing on disk, tell them we cant find anything | |
res.writeHead(404, { 'Content-Type': 'text/plain' }); | |
res.write('404 Not Found\n'); | |
res.end(); | |
return; | |
} | |
const stat = await statAsync(fileUri); | |
// send thatd down | |
res.writeHead(200, { | |
'Content-Type': mime.lookup(fileUri) || 'application/octet-stream', | |
'Content-Length': stat.size | |
}); | |
// send the file down as a stream | |
// avoids in issues with reading the file into RAM | |
const readStream = fs.createReadStream(fileUri); | |
readStream.pipe(res); | |
return; | |
} else { | |
console.log(`using worker index=${workerPoolCurrentIndex}`); | |
const worker = workerPool[workerPoolCurrentIndex]; | |
// round robin through out workers | |
workerPoolCurrentIndex = workerPoolCurrentIndex === workerPool.length - 1 ? 0 : workerPoolCurrentIndex + 1; | |
const myId = requestId; | |
requestId = requestId + 1; | |
const msgHander = (buff: WorkerRenderResponse) => { | |
if (buff.id === myId) { | |
const html = bufferToString(buff.html); | |
res.writeHead(200, { | |
'Content-Type': 'text/html', | |
}); | |
res.end(html); | |
worker.removeListener('message', msgHander); | |
worker.removeListener('error', errorHandler); | |
} | |
}; | |
const errorHandler = (err: any) => { | |
res.writeHead(400); | |
res.end(err); | |
worker.removeListener('message', msgHander); | |
worker.removeListener('error', errorHandler); | |
}; | |
worker.on('message', msgHander); | |
worker.once('error', errorHandler); | |
worker.postMessage({id: myId, uri}); | |
} | |
}); | |
server.listen(PORT); | |
} | |
function startWorker() { | |
console.log(`worker started, threadId=${threadId}`); | |
enableProdMode(); | |
const engine = new CommonEngine(AppServerModuleNgFactory, [ | |
provideModuleMap(LAZY_MODULE_MAP) | |
]); | |
parentPort.on('message', async (msg: WorkerRenderRequest) => { | |
console.log(`worker threadId=${threadId}, rendering`); | |
const html = await engine.render({ | |
url: msg.uri, | |
bootstrap: AppServerModuleNgFactory, | |
documentFilePath | |
}); | |
const response = stringToBuffer(html); | |
parentPort.postMessage({id: msg.id, html: response}); | |
}); | |
} | |
function bufferToString(buf: SharedArrayBuffer): string { | |
const array = new Uint8Array(buf); | |
return String.fromCharCode.apply(null, array); | |
} | |
function stringToBuffer(str: string) { | |
// since the html charset will be UTF-8 we only need 1 byte per character | |
const bytes = str.length * 1; | |
const buffer = new SharedArrayBuffer(bytes); | |
const arrayBuffer = new Uint8Array(buffer); | |
for (let i = 0, strLen = str.length; i < strLen; i++) { | |
arrayBuffer[i] = str.charCodeAt(i); | |
} | |
return buffer; | |
} | |
function main() { | |
if (isMainThread) { | |
startServer(); | |
} else { | |
startWorker(); | |
} | |
} | |
main(); | |
interface WorkerRenderRequest { | |
id: number; | |
uri: string; | |
} | |
interface WorkerRenderResponse { | |
id: number; | |
html: SharedArrayBuffer; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment