WebView2加载Node.js应用时localhost不可达,如何等待npm start完成?
解决WebView2等待npm服务启动完成后加载页面的方案
针对你遇到的WebView2因npm服务未完全启动而短暂显示"localhost不可达"的问题,以下是几种可行的解决方式:
方法1:监听npm进程输出,等待启动完成标识
React和Node服务启动时会输出特定就绪日志(比如React的Compiled successfully!、Node服务的Server running on port 5000),通过监听进程输出捕获这些标识,再触发WebView2加载页面。
修改LaunchProcessApp函数,启用输出监听
public static Process LaunchProcessApp(string WorkingDirectory, string appExe, string args, Action<string> onOutputReceived = null) { try { var psi = new ProcessStartInfo { FileName = appExe, RedirectStandardInput = true, RedirectStandardOutput = true, // 启用标准输出重定向 RedirectStandardError = true, // 启用错误输出重定向 CreateNoWindow = true, UseShellExecute = false, }; if (psi.EnvironmentVariables.ContainsKey("PATH")) { if (!psi.EnvironmentVariables["PATH"].ToLower().Contains(@"\nodejs\")) { string njspath = GetNodejsPathInRegistry(); psi.EnvironmentVariables["PATH"] += ";" + njspath+";"; } if (!psi.EnvironmentVariables["PATH"].ToLower().Contains(@"\npm\")) { string appdatalocalpath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); string npmpath = appdatalocalpath + @"Roaming\npm"; psi.EnvironmentVariables["PATH"] += npmpath + ";"; } } Process p = new Process { StartInfo = psi }; // 绑定输出事件 if (onOutputReceived != null) { p.OutputDataReceived += (sender, e) => { if (!string.IsNullOrEmpty(e.Data)) { onOutputReceived(e.Data); } }; p.ErrorDataReceived += (sender, e) => { if (!string.IsNullOrEmpty(e.Data)) { onOutputReceived(e.Data); } }; } p.Start(); p.BeginOutputReadLine(); p.BeginErrorReadLine(); p.StandardInput.WriteLine(@"cd " + WorkingDirectory); p.StandardInput.WriteLine(args); return p; } catch (Exception ex) { CLogger.WriteLog(ELogLevel.ERROR, "Helpers --> LanchReactApplication : error:" + ex); } return null; }
修改按钮点击事件,等待服务就绪再显示主窗体
// 标记服务是否就绪 private bool isFrontendReady = false; private bool isBackendReady = false; // 按钮点击逻辑 bool x = checkIfReactAndNodeIsRunning(56513); bool y = checkIfReactAndNodeIsRunning(5000); if (!x) { string frontendlocation = ThisAddIn.dataFolder + @"FrontEnd"; ThisAddIn.clientCodeProcess = LaunchProcessApp(frontendlocation, "cmd", "npm start", (output) => { // 检测React启动完成的标识,可根据实际日志调整 if (output.Contains("Compiled successfully!") || output.Contains("Started development server")) { isFrontendReady = true; CheckIfAllServicesReady(); } }); } else { isFrontendReady = true; } if (!y) { string serverlocation = ThisAddIn.dataFolder + @"Server"; ThisAddIn.processTSCode = LaunchProcessApp(serverlocation, "cmd", "npm start", (output) => { // 检测Node服务启动完成的标识,可根据实际日志调整 if (output.Contains("Server is running on port 5000")) { isBackendReady = true; CheckIfAllServicesReady(); } }); } else { isBackendReady = true; } // 若服务已就绪,直接显示主窗体 CheckIfAllServicesReady(); // 检查所有服务是否就绪的方法 private void CheckIfAllServicesReady() { if (isFrontendReady && isBackendReady) { // 确保在UI线程执行 if (InvokeRequired) { Invoke(new Action(CheckIfAllServicesReady)); return; } loaderfrm.Close(); mainFrm.Show(); } }
方法2:轮询检查目标端口连通性
如果监听进程输出不可靠,可通过轮询检查端口是否能建立连接,确认服务就绪后再加载页面。
添加端口连通性检查方法
private bool IsPortReady(int port, int timeoutMs = 100) { try { using (var client = new TcpClient()) { var result = client.BeginConnect("localhost", port, null, null); var success = result.AsyncWaitHandle.WaitOne(timeoutMs); client.EndConnect(result); return success; } } catch { return false; } } // 异步等待端口就绪的方法 private async Task WaitForPortReady(int port, int maxAttempts = 60) { int attempts = 0; while (attempts < maxAttempts) { if (IsPortReady(port)) return; await Task.Delay(1000); // 每秒检查一次 attempts++; } throw new TimeoutException($"Port {port} did not become ready within {maxAttempts} seconds."); }
修改按钮点击事件,等待端口就绪
// 按钮点击逻辑 bool x = checkIfReactAndNodeIsRunning(56513); bool y = checkIfReactAndNodeIsRunning(5000); if (!x) { string frontendlocation = ThisAddIn.dataFolder + @"FrontEnd"; ThisAddIn.clientCodeProcess = LaunchProcessApp(frontendlocation, "cmd", "npm start"); } if (!y) { string serverlocation = ThisAddIn.dataFolder + @"Server"; ThisAddIn.processTSCode = LaunchProcessApp(serverlocation, "cmd", "npm start"); } // 异步等待所有端口就绪 Task.Run(async () => { try { if (!x) await WaitForPortReady(56513); if (!y) await WaitForPortReady(5000); // 回到UI线程显示主窗体 Invoke(new Action(() => { loaderfrm.Close(); mainFrm.Show(); })); } catch (TimeoutException ex) { Invoke(new Action(() => { MessageBox.Show($"服务启动超时:{ex.Message}"); loaderfrm.Close(); })); } });
方法3:在MainForm中添加加载重试机制
如果不想修改启动逻辑,可在InitBrowser方法中添加重试逻辑,页面加载失败时自动重试,直到服务就绪:
private async void InitBrowser(string args) { try { webView21.Dock = DockStyle.Fill; var targetUri = new Uri("http://localhost:56513?mode=" + args); int retryCount = 0; const int maxRetries = 30; bool loadedSuccessfully = false; while (retryCount < maxRetries && !loadedSuccessfully) { try { webView21.Source = targetUri; await webView21.EnsureCoreWebView2Async(); await webView21.CoreWebView2.NavigateAndWait(targetUri.ToString()); loadedSuccessfully = true; } catch { retryCount++; await Task.Delay(1000); } } if (!loadedSuccessfully) { MessageBox.Show("页面加载超时,请检查服务是否正常启动"); } } catch(Exception ex) { CLogger.WriteLog(ELogLevel.ERROR, "Start :: MAINFORM.cs - Function name: InitBrowser - " + ex); } }
内容的提问来源于stack exchange,提问作者Sora
相关产品推荐
相关产品推荐

