Почему этот процесс PhantomJS вызывает "Каталог <x>не существует".ошибки? - PullRequest
0 голосов
/ 02 апреля 2019

Это мой первый пост, извиняюсь, если вопрос нуждается в некоторых изменениях. Я выкинул эту проблему изо всех сил, но здесь много компонентов, так что этот пост довольно большой ...

Наш сайт ASP.NET MVC развернут как служба приложений в Azure. Я использую метод контроллера API для создания PDF страницы, которая существует на том же сайте. Для этого контроллер создает процесс PhantomJS, ожидает успеха и возвращает содержимое файла, который он создает. Это все работает нормально, но после этого несколько просмотров на сайте приводят к таким ошибкам:

Ошибка сервера в приложении '/'.

Каталог 'D: \ home \ site \ wwwroot \ Views \ Location' не существует. Не удалось запустить мониторинг изменений файла.

Описание: Произошло необработанное исключение во время выполнения текущего веб-запроса. Пожалуйста, просмотрите трассировку стека для получения дополнительной информации об ошибке и о том, где она возникла в коде.

Сведения об исключении: System.Web.HttpException: Каталог 'D: \ home \ site \ wwwroot \ Views \ Location' не существует. Не удалось запустить мониторинг изменений файла.

Через некоторое время ошибка изменится:

Ошибка сервера в приложении '/'.

Представление LocationList или его мастер не найдены, или никакой движок представления не поддерживает найденные местоположения. Были найдены следующие местоположения:
~ / Просмотры / 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

Описание: Произошло необработанное исключение во время выполнения текущего веб-запроса. Пожалуйста, просмотрите трассировку стека для получения дополнительной информации об ошибке и о том, где она возникла в коде.

Сведения об исключении: System.InvalidOperationException: представление 'LocationList' или его мастер не найдены, или никакой механизм просмотра не поддерживает найденные местоположения. Были найдены следующие местоположения:
~ / Просмотры / 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

Это относится только к представлениям, которые еще не скомпилированы или к любому другому файлу, к которому ранее не обращались. Единственный способ исправить это - вручную остановить и запустить веб-приложение. Я могу подтвердить, что это происходит не со всеми процессами (запуск «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;
}

Вот соответствующая часть скрипта "rasterize_simple.js", используемого PhantomJS, без настройки размера страницы, файлов cookie и т. Д .:

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

Ожидаемым результатом всего этого является файл PDF, который он генерирует, и все последующие вызовы этого метода API (с другими параметрами) работают отлично. Тем не менее, побочным эффектом является полностью нарушенный сайт: (

Любая помощь здесь будет принята с благодарностью!

Ответы [ 2 ]

0 голосов
/ 09 апреля 2019

Отправляю свой ответ, потому что Ответ Питера Пэна указал мне верное направление, но я нашел другое решение. Похоже, что проблема вызвана записью в защищенную область в песочнице (что-нибудь в D: \ home). Запуск PhantomJS из Path.GetTempPath() и запись файлов там, кажется, полностью решают проблему.

Это не объясняет , что именно происходит , но, по крайней мере, проблема решена.

0 голосов
/ 08 апреля 2019

Я боюсь, что функции вашего приложения ASP.NET не могли нормально работать в Azure WebApp, такие как ветвление процесса для запуска PhantomJS и создание файла PDF, поскольку существует множество ограничений, не позволяющих делать это пожалуйста, обратитесь к странице вики Kudu Azure Web App sandbox, чтобы узнать больше.

Вот некоторые ограничения, я думаю, у вас есть.

  1. Генерация PDF из HTML Есть несколько библиотек, используемых для преобразования HTML в PDF. Многие версии для Windows / .NET используют API-интерфейсы IE и, следовательно, широко используют 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): требуется тарифный план Basic или выше Известная проблема для всех генераторов PDF на основе wkhtmltopdf или phantomjs: пользовательские шрифты не отображаются (вместо них используется системный шрифт) из-за ограничений GDI API изолированной программной среды, которые присутствуют даже в планах Azure Apps на основе виртуальной машины (Basic или новее) .

    Другие сценарии, которые не поддерживаются:

    PhantomJS / Selenium: пытается подключиться к локальному адресу, а также использует GDI +.

    Существуют некоторые фреймворки, которые не используют User32 / GDI32 широко (например, wkhtmltopdf), и мы работаем над тем, чтобы включить их в Basic + так же, как мы включили SQL Reporting.

  3. Запрос локального адреса Попытки соединения с локальными адресами (например, localhost, 127.0.0.1) и собственным IP-адресом машины не будут выполнены, за исключением случаев, когда другой процесс в той же песочнице создал сокет прослушивания на порте назначения.

Решение состоит в том, чтобы развернуть приложение на виртуальной машине Azure, а не в WebApp.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...