mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-22 17:47:53 +00:00
Prerendering imposes its own (overridable) timeout with descriptive error
This commit is contained in:
@@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
||||
private const string PrerenderExportAttributeName = "asp-prerender-export";
|
||||
private const string PrerenderWebpackConfigAttributeName = "asp-prerender-webpack-config";
|
||||
private const string PrerenderDataAttributeName = "asp-prerender-data";
|
||||
private const string PrerenderTimeoutAttributeName = "asp-prerender-timeout";
|
||||
private static INodeServices _fallbackNodeServices; // Used only if no INodeServices was registered with DI
|
||||
|
||||
private readonly string _applicationBasePath;
|
||||
@@ -50,6 +51,9 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
||||
[HtmlAttributeName(PrerenderDataAttributeName)]
|
||||
public object CustomDataParameter { get; set; }
|
||||
|
||||
[HtmlAttributeName(PrerenderTimeoutAttributeName)]
|
||||
public int TimeoutMillisecondsParameter { get; set; }
|
||||
|
||||
[HtmlAttributeNotBound]
|
||||
[ViewContext]
|
||||
public ViewContext ViewContext { get; set; }
|
||||
@@ -79,7 +83,8 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
||||
},
|
||||
unencodedAbsoluteUrl,
|
||||
unencodedPathAndQuery,
|
||||
CustomDataParameter);
|
||||
CustomDataParameter,
|
||||
TimeoutMillisecondsParameter);
|
||||
output.Content.SetHtmlContent(result.Html);
|
||||
|
||||
// Also attach any specified globals to the 'window' object. This is useful for transferring
|
||||
|
||||
@@ -23,7 +23,8 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
||||
JavaScriptModuleExport bootModule,
|
||||
string requestAbsoluteUrl,
|
||||
string requestPathAndQuery,
|
||||
object customDataParameter)
|
||||
object customDataParameter,
|
||||
int timeoutMilliseconds)
|
||||
{
|
||||
return nodeServices.InvokeExportAsync<RenderToStringResult>(
|
||||
NodeScript.Value.FileName,
|
||||
@@ -32,7 +33,8 @@ namespace Microsoft.AspNetCore.SpaServices.Prerendering
|
||||
bootModule,
|
||||
requestAbsoluteUrl,
|
||||
requestPathAndQuery,
|
||||
customDataParameter);
|
||||
customDataParameter,
|
||||
timeoutMilliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import * as domain from 'domain';
|
||||
import { run as domainTaskRun } from 'domain-task/main';
|
||||
import { baseUrl } from 'domain-task/fetch';
|
||||
|
||||
const defaultTimeoutMilliseconds = 30 * 1000;
|
||||
|
||||
export interface RenderToStringCallback {
|
||||
(error: any, result: RenderToStringResult): void;
|
||||
}
|
||||
@@ -33,7 +35,7 @@ export interface BootModuleInfo {
|
||||
webpackConfig?: string;
|
||||
}
|
||||
|
||||
export function renderToString(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any) {
|
||||
export function renderToString(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number) {
|
||||
findBootFunc(applicationBasePath, bootModule, (findBootFuncError, bootFunc) => {
|
||||
if (findBootFuncError) {
|
||||
callback(findBootFuncError, null);
|
||||
@@ -66,8 +68,22 @@ export function renderToString(callback: RenderToStringCallback, applicationBase
|
||||
// Make the base URL available to the 'domain-tasks/fetch' helper within this execution context
|
||||
baseUrl(absoluteRequestUrl);
|
||||
|
||||
// Begin rendering, and apply a timeout
|
||||
const bootFuncPromise = bootFunc(params);
|
||||
if (!bootFuncPromise || typeof bootFuncPromise.then !== 'function') {
|
||||
callback(`Prerendering failed because the boot function in ${bootModule.moduleName} did not return a promise.`, null);
|
||||
return;
|
||||
}
|
||||
const timeoutMilliseconds = overrideTimeoutMilliseconds || defaultTimeoutMilliseconds; // e.g., pass -1 to override as 'never time out'
|
||||
const bootFuncPromiseWithTimeout = timeoutMilliseconds > 0
|
||||
? wrapWithTimeout(bootFuncPromise, timeoutMilliseconds,
|
||||
`Prerendering timed out after ${timeoutMilliseconds}ms because the boot function in '${bootModule.moduleName}' `
|
||||
+ 'returned a promise that did not resolve or reject. Make sure that your boot function always resolves or '
|
||||
+ 'rejects its promise. You can change the timeout value using the \'asp-prerender-timeout\' tag helper.')
|
||||
: bootFuncPromise;
|
||||
|
||||
// Actually perform the rendering
|
||||
bootFunc(params).then(successResult => {
|
||||
bootFuncPromiseWithTimeout.then(successResult => {
|
||||
callback(null, { html: successResult.html, globals: successResult.globals });
|
||||
}, error => {
|
||||
callback(error, null);
|
||||
@@ -84,6 +100,25 @@ export function renderToString(callback: RenderToStringCallback, applicationBase
|
||||
});
|
||||
}
|
||||
|
||||
function wrapWithTimeout<T>(promise: Promise<T>, timeoutMilliseconds: number, timeoutRejectionValue: any): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const timeoutTimer = setTimeout(() => {
|
||||
reject(timeoutRejectionValue);
|
||||
}, timeoutMilliseconds);
|
||||
|
||||
promise.then(
|
||||
resolvedValue => {
|
||||
clearTimeout(timeoutTimer);
|
||||
resolve(resolvedValue);
|
||||
},
|
||||
rejectedValue => {
|
||||
clearTimeout(timeoutTimer);
|
||||
reject(rejectedValue);
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
function findBootModule<T>(applicationBasePath: string, bootModule: BootModuleInfo, callback: (error: any, foundModule: T) => void) {
|
||||
const bootModuleNameFullPath = path.resolve(applicationBasePath, bootModule.moduleName);
|
||||
if (bootModule.webpackConfig) {
|
||||
|
||||
Reference in New Issue
Block a user