为什么这个PhantomJS进程导致“目录 不存在。“错误?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么这个PhantomJS进程导致“目录 不存在。“错误?相关的知识,希望对你有一定的参考价值。

这是我的第一篇文章,如果问题需要一些修改,请道歉。我已经把这个问题尽可能地煮了,但是这里有很多组件,所以这个帖子非常庞大......

我们的ASP.NET MVC站点在Azure上部署为应用服务。我正在使用API​​控制器方法来生成存在于同一站点上的页面的PDF。为此,控制器创建一个PhantomJS进程,等待成功,并返回它创建的文件的内容。这一切都很好,但之后在网站上的几个视图产生这样的错误:

Server Error in '/' Application.

目录'D:\ home \ site \ wwwroot \ Views \ Location'不存在。无法开始监视文件更改。

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Web.HttpException: Directory 'D:\home\site\wwwroot\Views\Location' does not exist. Failed to start monitoring file changes.

一段时间后,错误发生了变化:

Server Error in '/' Application.

未找到视图“LocationList”或其主节点,或者视图引擎不支持搜索的位置。搜索了以下位置: 〜/查看/位置/ LocationList.aspx 〜/查看/位置/ LocationList.ascx 〜/查看/共享/ LocationList.aspx 〜/查看/共享/ LocationList.ascx 〜/查看/位置/ LocationList.cshtml 〜/查看/位置/ LocationList.vbhtml 〜/查看/共享/ LocationList.cshtml 〜/查看/共享/ LocationList.vbhtml

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: The view 'LocationList' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Location/LocationList.aspx
~/Views/Location/LocationList.ascx
~/Views/Shared/LocationList.aspx
~/Views/Shared/LocationList.ascx
~/Views/Location/LocationList.cshtml
~/Views/Location/LocationList.vbhtml
~/Views/Shared/LocationList.cshtml
~/Views/Shared/LocationList.vbhtml

这仅适用于尚未编译的视图或之前未访问过的任何其他文件。解决此问题的唯一方法是手动停止和启动Web应用程序。我可以确认所有进程都不会发生这种情况(运行“echo.exe”而不是“phantomjs.exe”不会导致损坏的行为)。

我查看了我能想到的所有日志,但没有找到任何与众不同的东西。我最好的猜测是,一个过程被强行或意外终止,但至于什么和为什么,我不知道。也许有一些我不知道的关键日志?

这是相关的c#代码:

private static async Task<int> ExecuteSimpleAsync(string workingDir, double? timeout,
    string command, params string[] parameters)
{
    var paramStr = string.Join(" ", parameters.Select(x => x == null ? "" : $"\"{x}\"").ToList());
    var processInfo = new ProcessStartInfo(command, paramStr) {
        WorkingDirectory = workingDir,
        UseShellExecute  = false,                    
        CreateNoWindow   = true,
    };

    Process process = null;
    int exitCode = -1;
    using (process = new Process() { StartInfo = processInfo }) {
        process.Start();
        await process.WaitForExitAsync(timeout); // simple extension function to check for 'Process.HasExited' periodically
        exitCode = process.ExitCode;
    }
    return exitCode;
}

private static async Task<byte[]> GetFileContents(string filePath) {
    byte[] bytes = null;
    using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read)) {
        bytes = new byte[file.Length];
        await file.ReadAsync(bytes, 0, (int) file.Length);
    }
    return bytes;
}

public static async Task<byte[]> RenderPdfAsync(
    string cookiesB64, string localUrl, string baseFilename, double? timeout = 60)
{
    ....

    // filesPath:  (directory for temporary output)
    // timeout:    60.000 (60 seconds)
    // PhantomJSExePath: (absolute path containing 'phantomjs.exe')
    // scriptFile: "rasterize_simple.js"
    // requestUrl: "TestReport/ForUserAndTestPdf/1002/10"
    // outputFile: "phantomjs-output-<timestamp>.pdf"
    // cookiesB64: (base64-encoded authentication cookies passed to request in PhantomJS)

    var exitCode = await ExecuteSimpleAsync(filesPath, timeout, PhantomJSExePath + @"\phantomjs.exe",
    scriptFile, requestUrl, outputFile, cookiesB64);
    if (exitCode != 0)
        return null;
    return await GetFileContents(outputFile);
}

[Authorize]
[HttpGet]
[Route("TestReport/ForUserAndTestPdf/{userId}/{testId}")]
public async Task<HttpResponseMessage> ForUserAndTestPdfAsync(int userId, int testId) {
    // produce a slightly-modified version of the current URL:
    //    /TestReport/ForUserAndTest/<userid>/<testid>
    // => /TestReport/ForUserAndTestPdf/<userid>/<testid>?print=true
    var url = Request.RequestUri.GetLocalPathWithParams("print=true").Replace("ForUserAndTest", "ForUserAndTestPdf");

    // get the cookies used in the current request and convert to a base64-encoded JSON object
    var cookiesB64 = Request.GetCookiesJsonB64();
    var bytes = await PhantomJSHelpers.RenderPdfAsync(cookiesB64, url, "phantomjs-output", 60);

    var message = new HttpResponseMessage(HttpStatusCode.OK);
    message.Content = new StreamContent(new MemoryStream(bytes));
    message.Content.Headers.ContentLength = bytes.Length;
    message.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    return message;
}

这是PhantomJS使用的“rasterize_simple.js”脚本的相关部分,没有页面大小,cookie等的设置:

page.open(address, function(status) {
    page.render(outputFilename);
    phantom.exit(0);
});

所有这些的预期结果是它生成的PDF文件,并且对此API方法的所有后续调用(具有不同的参数)都可以完美地工作。然而,副作用是一个完全破碎的网站:(

这里的任何帮助将不胜感激!

答案

我担心你的ASP.NET应用程序的功能在Azure WebApp中无法正常工作,比如分叉一个进程来运行PhantomJS并生成一个PDF文件,因为有很多限制不允许这样做,请参考到Kudu wiki页面Azure Web App sandbox了解更多。

这是我认为你有的一些限制。

  1. 从HTML生成PDF有多个库用于将HTML转换为PDF。许多Windows / .NET特定版本利用IE API,因此广泛利用User32 / GDI32。这些API在沙箱中被大量阻止(无论计划如何),因此这些框架在沙箱中不起作用。
  2. 不受支持的框架以下是由于上述一个或多个限制而被发现无法使用的框架和方案的列表。可以想象,随着沙箱的发展,将来会支持一些人。 由于上述限制,PDF生成器失败: Syncfusion Siberix Spire.PDF支持以下PDF生成器: SQL报告框架:要求站点在Basic或更高版本中运行(请注意,这当前在消费模式下的功能应用程序中不起作用)EVOPDF:请参阅http://www.evopdf.com/azure-html-to-pdf-converter.aspx以获取供应商解决方案Telerik报告:要求站点以Basic或更高版本运行。更多信息Rotativa / wkhtmltopdf:要求网站以Basic或更高版本运行。 NReco PdfGenerator(wkhtmltopdf):要求订阅计划基于或更高版本基于wkhtmltopdf或phantomjs的所有PDF生成器的已知问题:由于沙箱GDI API限制,即使在VM中,也不会呈现自定义字体(而是使用系统安装的字体)基于Azure Apps计划(基本或更高版本)。 其他不受支持的方案: PhantomJS / Selenium:尝试连接到本地地址,也使用GDI +。 有些框架没有广泛利用User32 / GDI32(例如wkhtmltopdf),我们正在使用与启用SQL报告相同的方式在Basic中启用这些框架。
  3. 本地地址请求连接尝试本地地址(例如localhost,127.0.0.1)和计算机自己的IP将失败,除非同一沙箱中的另一个进程在目标端口上创建了侦听套接字。

解决方案是在Azure VM上部署您的应用程序,而不是WebApp。

另一答案

发布我自己的答案,因为Peter Pan's answer指出了我正确的方向,但我发现了一个不同的解决方案。看来问题是由写入沙箱中的受保护区域(D:\ home中的任何内容)引起的。从Path.GetTempPath()运行PhantomJS并在那里编写文件似乎完全解决了这个问题。

这并不能解释究竟发生了什么,但至少问题已经解决了。

以上是关于为什么这个PhantomJS进程导致“目录 不存在。“错误?的主要内容,如果未能解决你的问题,请参考以下文章

python定时杀进程

phantomjs无法打开共享对象文件

Socks5协议错误PhantomJS

如何使用 NodeJS 在 AWS Lambda 上运行 PhantomJS

使用 PhantomJS 来实现 CTF 中的 XSS 题目

多个 PhantomJS 实例挂起