Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit 4ca1669

Browse files
Prerendering imposes its own (overridable) timeout with descriptive error
1 parent 4111004 commit 4ca1669

File tree

3 files changed

+47
-5
lines changed

3 files changed

+47
-5
lines changed

src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class PrerenderTagHelper : TagHelper
1818
private const string PrerenderExportAttributeName = "asp-prerender-export";
1919
private const string PrerenderWebpackConfigAttributeName = "asp-prerender-webpack-config";
2020
private const string PrerenderDataAttributeName = "asp-prerender-data";
21+
private const string PrerenderTimeoutAttributeName = "asp-prerender-timeout";
2122
private static INodeServices _fallbackNodeServices; // Used only if no INodeServices was registered with DI
2223

2324
private readonly string _applicationBasePath;
@@ -50,6 +51,9 @@ public PrerenderTagHelper(IServiceProvider serviceProvider)
5051
[HtmlAttributeName(PrerenderDataAttributeName)]
5152
public object CustomDataParameter { get; set; }
5253

54+
[HtmlAttributeName(PrerenderTimeoutAttributeName)]
55+
public int TimeoutMillisecondsParameter { get; set; }
56+
5357
[HtmlAttributeNotBound]
5458
[ViewContext]
5559
public ViewContext ViewContext { get; set; }
@@ -79,7 +83,8 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
7983
},
8084
unencodedAbsoluteUrl,
8185
unencodedPathAndQuery,
82-
CustomDataParameter);
86+
CustomDataParameter,
87+
TimeoutMillisecondsParameter);
8388
output.Content.SetHtmlContent(result.Html);
8489

8590
// Also attach any specified globals to the 'window' object. This is useful for transferring

src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public static Task<RenderToStringResult> RenderToString(
2323
JavaScriptModuleExport bootModule,
2424
string requestAbsoluteUrl,
2525
string requestPathAndQuery,
26-
object customDataParameter)
26+
object customDataParameter,
27+
int timeoutMilliseconds)
2728
{
2829
return nodeServices.InvokeExportAsync<RenderToStringResult>(
2930
NodeScript.Value.FileName,
@@ -32,7 +33,8 @@ public static Task<RenderToStringResult> RenderToString(
3233
bootModule,
3334
requestAbsoluteUrl,
3435
requestPathAndQuery,
35-
customDataParameter);
36+
customDataParameter,
37+
timeoutMilliseconds);
3638
}
3739
}
3840
}

src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import * as domain from 'domain';
55
import { run as domainTaskRun } from 'domain-task/main';
66
import { baseUrl } from 'domain-task/fetch';
77

8+
const defaultTimeoutMilliseconds = 30 * 1000;
9+
810
export interface RenderToStringCallback {
911
(error: any, result: RenderToStringResult): void;
1012
}
@@ -33,7 +35,7 @@ export interface BootModuleInfo {
3335
webpackConfig?: string;
3436
}
3537

36-
export function renderToString(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any) {
38+
export function renderToString(callback: RenderToStringCallback, applicationBasePath: string, bootModule: BootModuleInfo, absoluteRequestUrl: string, requestPathAndQuery: string, customDataParameter: any, overrideTimeoutMilliseconds: number) {
3739
findBootFunc(applicationBasePath, bootModule, (findBootFuncError, bootFunc) => {
3840
if (findBootFuncError) {
3941
callback(findBootFuncError, null);
@@ -66,8 +68,22 @@ export function renderToString(callback: RenderToStringCallback, applicationBase
6668
// Make the base URL available to the 'domain-tasks/fetch' helper within this execution context
6769
baseUrl(absoluteRequestUrl);
6870

71+
// Begin rendering, and apply a timeout
72+
const bootFuncPromise = bootFunc(params);
73+
if (!bootFuncPromise || typeof bootFuncPromise.then !== 'function') {
74+
callback(`Prerendering failed because the boot function in ${bootModule.moduleName} did not return a promise.`, null);
75+
return;
76+
}
77+
const timeoutMilliseconds = overrideTimeoutMilliseconds || defaultTimeoutMilliseconds; // e.g., pass -1 to override as 'never time out'
78+
const bootFuncPromiseWithTimeout = timeoutMilliseconds > 0
79+
? wrapWithTimeout(bootFuncPromise, timeoutMilliseconds,
80+
`Prerendering timed out after ${timeoutMilliseconds}ms because the boot function in '${bootModule.moduleName}' `
81+
+ 'returned a promise that did not resolve or reject. Make sure that your boot function always resolves or '
82+
+ 'rejects its promise. You can change the timeout value using the \'asp-prerender-timeout\' tag helper.')
83+
: bootFuncPromise;
84+
6985
// Actually perform the rendering
70-
bootFunc(params).then(successResult => {
86+
bootFuncPromiseWithTimeout.then(successResult => {
7187
callback(null, { html: successResult.html, globals: successResult.globals });
7288
}, error => {
7389
callback(error, null);
@@ -84,6 +100,25 @@ export function renderToString(callback: RenderToStringCallback, applicationBase
84100
});
85101
}
86102

103+
function wrapWithTimeout<T>(promise: Promise<T>, timeoutMilliseconds: number, timeoutRejectionValue: any): Promise<T> {
104+
return new Promise<T>((resolve, reject) => {
105+
const timeoutTimer = setTimeout(() => {
106+
reject(timeoutRejectionValue);
107+
}, timeoutMilliseconds);
108+
109+
promise.then(
110+
resolvedValue => {
111+
clearTimeout(timeoutTimer);
112+
resolve(resolvedValue);
113+
},
114+
rejectedValue => {
115+
clearTimeout(timeoutTimer);
116+
reject(rejectedValue);
117+
}
118+
)
119+
});
120+
}
121+
87122
function findBootModule<T>(applicationBasePath: string, bootModule: BootModuleInfo, callback: (error: any, foundModule: T) => void) {
88123
const bootModuleNameFullPath = path.resolve(applicationBasePath, bootModule.moduleName);
89124
if (bootModule.webpackConfig) {

0 commit comments

Comments
 (0)