UNPKG

@sitecore-jss/sitecore-jss-rendering-host

Version:

This module is provided as a part of Sitecore JavaScript Rendering SDK (JSS). It contains the rendering host implementation.

136 lines (135 loc) 4.44 kB
import zlib from 'zlib'; // node.js standard lib // may be used by a "standalone" JSS rendering host / express server. export const ssrMiddleware = ({ appInvocationInfoResolver, }) => { if (!appInvocationInfoResolver) { throw new Error('No AppInvocationInfo resolver was provided for SSR middleware'); } return (req, res) => { let callback; readRequestBodyAsJson(req) .then((bodyJson) => { if (!bodyJson) { throw new Error(`Request body was not JSON: ${req.url}`); } const invocationInfo = appInvocationInfoResolver(bodyJson, req, res); callback = invocationInfo.renderFunctionCallback || getDefaultAppRendererCallback(res); invocationInfo.renderFunction(callback, ...invocationInfo.renderFunctionArgs); }) .catch((err) => { console.error(err); callback(err, null); }); }; }; // todo: add hook for modifying html / response before end /** * @param {ServerResponse} res */ export function getDefaultAppRendererCallback(res) { const callback = (errorValue, successValue) => { if (errorValue) { respondWithError(res, errorValue); } else if (typeof successValue !== 'string') { // Arbitrary object/number/etc - JSON-serialize it let successValueJson = {}; try { successValueJson = JSON.stringify(successValue); } catch (ex) { // JSON serialization error - pass it back to http caller. respondWithError(res, ex); return; } res.setHeader('Content-Type', 'application/json'); res.end(successValueJson); } else { // String - can bypass JSON-serialization altogether res.setHeader('Content-Type', 'text/plain'); res.end(successValue); } }; return callback; } /** * @param {IncomingMessage} request */ export function readRequestBodyAsJson(request) { const dataWriter = { output: Buffer.from('') }; request.on('data', onReadableStreamDataHandler(dataWriter)); return new Promise((resolve, reject) => { request.on('end', () => { const contentEncoding = request.headers['content-encoding']; extractJsonFromStreamData(dataWriter.output, contentEncoding) .then((json) => resolve(json)) .catch((err) => reject(err)); }); }); } /** * @param {ServerResponse} res * @param {Error} errorValue */ export function respondWithError(res, errorValue) { console.error(errorValue); res.statusCode = 500; res.end(JSON.stringify({ errorMessage: errorValue.message || errorValue, errorDetails: errorValue.stack || null, })); } /** * @param {object} dataWriter * @param {Buffer} dataWriter.output */ export function onReadableStreamDataHandler(dataWriter) { return (data) => { if (Buffer.isBuffer(data)) { dataWriter.output = Buffer.concat([dataWriter.output, data]); // append raw buffer } else { dataWriter.output = Buffer.concat([dataWriter.output, Buffer.from(data)]); } }; } /** * @param {Buffer} data * @param {string} [contentEncoding] */ export function extractJsonFromStreamData(data, contentEncoding) { let responseString; if (contentEncoding && (contentEncoding.indexOf('gzip') !== -1 || contentEncoding.indexOf('deflate') !== -1)) { responseString = new Promise((resolve, reject) => { zlib.unzip(data, (error, result) => { if (error) { reject(error); } if (result) { resolve(result.toString('utf-8')); } }); }); } else { responseString = Promise.resolve(data.toString('utf-8')); } return responseString.then(tryParseJson); } /** * @param {string} jsonString */ export function tryParseJson(jsonString) { try { const json = JSON.parse(jsonString); // handle non-exception-throwing cases if (json && typeof json === 'object' && json !== null) { return json; } } catch (e) { console.error(`error parsing json string '${jsonString}'`, e); } return null; }