Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4ca1669

Browse filesBrowse files
Prerendering imposes its own (overridable) timeout with descriptive error
1 parent 4111004 commit 4ca1669
Copy full SHA for 4ca1669

File tree

Expand file treeCollapse file tree

3 files changed

+47
-5
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

3 files changed

+47
-5
lines changed
Open diff view settings
Collapse file

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

Copy file name to clipboardExpand all lines: src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs
+6-1Lines 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
Collapse file

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

Copy file name to clipboardExpand all lines: src/Microsoft.AspNetCore.SpaServices/Prerendering/Prerenderer.cs
+4-2Lines 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
}
Collapse file

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

Copy file name to clipboardExpand all lines: src/Microsoft.AspNetCore.SpaServices/npm/aspnet-prerendering/src/Prerendering.ts
+37-2Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.