Я хотел бы рассказать одну историю, которая произошла со мной. На мой взгляд, она достаточно интересная, и может помочь, кому-нибудь с аналогичной проблемой. Сразу скажу — это мой первый пост на хабре :-) Он оказался немного затянутым, поэтому основные выводы я вынес отдельно — в конец статьи.
Итак, задача:
Есть портал, на который загружаются документы Microsoft Word в формате Doc. Перед публикацией в общий доступ они должны быть обработаны. Как именно — не важно, поэтому для упрощения возьмем такой алгоритм:
Скажу сразу: Doc-файлами занимается дополнительный сервер с Win2k3+Office+Apache+PHP5 на борту. Возможно, в комментариях, я найду ответ, как мне производить аналогичные манипуляции на FreeBSD :-)
Ответ на первый вопрос: Зачем нужна эта обработка?
Путем проб и ошибок, было выведено, что это минимальная последовательность действий, которую требуется выполнять для всех документов.
Причины для этого две:
Ответ на второй вопрос: Как была реализована обработка документов?
Методом проб и ошибок было сделано несколько поколений обработчиков. Дело в том, что, по-началу, нормально работающая программа, через некоторое время начинала выдавать ошибки. В результате обработка останавливалась. А когда остановки становились слишком частыми приходилось признавать несостоятельность предыдущего решения и изобретать новое.
Итак.
Первое поколение: «mainscript.exe»
Этот скрипт запускал Word с параметрами командной строки. Один из них — имя обрабатываемого файла, а второй имя макроса обработки. По истечении 2-х минут программа убивала запущенный ею процесс. Сам скрипт запускался через CGI.
Из минусов, можно отметить:
Второе поколение: простой php — COM/OLE
Если первое поколение было реализовано до меня, то второе — это мои надстройки над первым. Был написан простейший скрипт, реализующий те же действия из макроса. Один скрипт подменили другим. Локально все прекрасно заработало. Обработка среднего файла заняла 15 секунд (вместо 2-х минут). Скрипт перенесен на сервер. Тестирование. Те же 15 секунд. Запускаем в работу, и…
Винворды падают, виснут, тормозят… Постоянно появляются Exceptions. Кроме того php-скрипты зависают на время, превышающее max_execution_time и отвисают только если выкинуть запущенны ими, процесс winword.exe. Причины не ясны, гугл наводит на статью в которой Microsoft предупреждает, что офис не предназначен для серверных приложений и его поведение в моей ситуации непредсказуемо. Так же там не рекомендовалось запускать больше одного процесса winword.
Сказанно — сделанно:
Третье поколение: монополизация доступа к winword.exe
Очередной запрос на обработку проверяет, запущен Word уже или нет. Эту проверку можно сделать через БД или pid-файл. Я сделал через БД. Значение поля-флага увеличивается при каждом запросе. Если запросов было очень много, означает что занявший процесс повис, и тогда запускается магическая программа killword.exe. Она тупо убивает все процессы с названием winword.exe.
Вроде все прекрасно работает, но со временем время работы скрипта увеличивалось, и все чаще требуется запускать killword. Постоянные Exception…
Четвертое поколение: Релиз обработчика
Кропотливый поиск причин ошибок дал очень интересный результат. После открытия/сохранения файла управление возвращается в php скрипт, но при этом COM сервер остается занят какое-то время. И если в этот момент отослать COM-серверу следующую команду, то мы вылетим в Exception. После этого Word можно убивать и начинать все заново.
Решилась проблема простым копированием во временную папку. Оказалось Word сканирует папку, на наличие временных файлов, или еще чего-то. Со временем количество файлов в исходной папке возрастало и скрипт начинал сбоить.
В общем, последняя схема работы оказалась самой работоспособной. Иногда выскакивают ошибки, типа «нехватка памяти», но это достаточно редко и пока является тайной :-)
Итак: схема работы обработчика DOC
Обработчик состоит из двух файлов: tmanager.php и handler.php
1. tmanger.php
Это демон который работает постоянно (или переодически стартует через планировщик задач). Каждые n секунд он проверяет в базе таблицу, на наличие очередной заявки на обработку (так называемая очередь).
Если заявка есть выполняем следующие действия:
2. handler.php
Это сам обработчик. В отдельный файл он вынесен на случай зависания. Если через какое-то время он не вернет результат, то tmanager.php запустит killword.
вот его упрощенный код:
Скрипт tmanager.php передает скрипту handler.php файл для обработки. Последний его обрабатывает и создает новый файл. В случае зависания handler.php, tmanager.php просто убивает winword.
Выводы
Update: Нашел ссылку на статью по поводу серверной автоматизации. С момента прошлого прочтения она стала на русском и немного подросла.
Итак, задача:
Есть портал, на который загружаются документы Microsoft Word в формате Doc. Перед публикацией в общий доступ они должны быть обработаны. Как именно — не важно, поэтому для упрощения возьмем такой алгоритм:
- Создать новый документ.
- Вставить данные из исходного (вставить файл).
- Сохранить полученный файл вместо оригинала.
Скажу сразу: Doc-файлами занимается дополнительный сервер с Win2k3+Office+Apache+PHP5 на борту. Возможно, в комментариях, я найду ответ, как мне производить аналогичные манипуляции на FreeBSD :-)
Ответ на первый вопрос: Зачем нужна эта обработка?
Путем проб и ошибок, было выведено, что это минимальная последовательность действий, которую требуется выполнять для всех документов.
Причины для этого две:
- Во-первых, при вставке файла вставляется только его содержимое (текст, форматирование, картинки), а макросы остаются «за бортом». Это позволяет избавиться от вредоносного кода, который может попасть в ваш Normal.dot :-) Разумеется, если вам требуется сохранить макросы, этот вариант не подойдет, но для большинства задач
вирусымакросы не нужны. - Во-вторых, если исходный файл был заблокирован для записи, то вы не сможете его изменить. А новый документ эту блокировку не унаследует.
Ответ на второй вопрос: Как была реализована обработка документов?
Методом проб и ошибок было сделано несколько поколений обработчиков. Дело в том, что, по-началу, нормально работающая программа, через некоторое время начинала выдавать ошибки. В результате обработка останавливалась. А когда остановки становились слишком частыми приходилось признавать несостоятельность предыдущего решения и изобретать новое.
Итак.
Первое поколение: «mainscript.exe»
Этот скрипт запускал Word с параметрами командной строки. Один из них — имя обрабатываемого файла, а второй имя макроса обработки. По истечении 2-х минут программа убивала запущенный ею процесс. Сам скрипт запускался через CGI.
Из минусов, можно отметить:
- работала программы ровно 2 минуты
- частые зависания и программы и Word. В результате куча висящих процессов winword.exe и mainscript.exe.
Второе поколение: простой php — COM/OLE
Если первое поколение было реализовано до меня, то второе — это мои надстройки над первым. Был написан простейший скрипт, реализующий те же действия из макроса. Один скрипт подменили другим. Локально все прекрасно заработало. Обработка среднего файла заняла 15 секунд (вместо 2-х минут). Скрипт перенесен на сервер. Тестирование. Те же 15 секунд. Запускаем в работу, и…
Винворды падают, виснут, тормозят… Постоянно появляются Exceptions. Кроме того php-скрипты зависают на время, превышающее max_execution_time и отвисают только если выкинуть запущенны ими, процесс winword.exe. Причины не ясны, гугл наводит на статью в которой Microsoft предупреждает, что офис не предназначен для серверных приложений и его поведение в моей ситуации непредсказуемо. Так же там не рекомендовалось запускать больше одного процесса winword.
Сказанно — сделанно:
Третье поколение: монополизация доступа к winword.exe
Очередной запрос на обработку проверяет, запущен Word уже или нет. Эту проверку можно сделать через БД или pid-файл. Я сделал через БД. Значение поля-флага увеличивается при каждом запросе. Если запросов было очень много, означает что занявший процесс повис, и тогда запускается магическая программа killword.exe. Она тупо убивает все процессы с названием winword.exe.
Вроде все прекрасно работает, но со временем время работы скрипта увеличивалось, и все чаще требуется запускать killword. Постоянные Exception…
Четвертое поколение: Релиз обработчика
Кропотливый поиск причин ошибок дал очень интересный результат. После открытия/сохранения файла управление возвращается в php скрипт, но при этом COM сервер остается занят какое-то время. И если в этот момент отослать COM-серверу следующую команду, то мы вылетим в Exception. После этого Word можно убивать и начинать все заново.
Решилась проблема простым копированием во временную папку. Оказалось Word сканирует папку, на наличие временных файлов, или еще чего-то. Со временем количество файлов в исходной папке возрастало и скрипт начинал сбоить.
В общем, последняя схема работы оказалась самой работоспособной. Иногда выскакивают ошибки, типа «нехватка памяти», но это достаточно редко и пока является тайной :-)
Итак: схема работы обработчика DOC
Обработчик состоит из двух файлов: tmanager.php и handler.php
1. tmanger.php
Это демон который работает постоянно (или переодически стартует через планировщик задач). Каждые n секунд он проверяет в базе таблицу, на наличие очередной заявки на обработку (так называемая очередь).
Если заявка есть выполняем следующие действия:
- Удалить все файлы из временных папок (в т.ч. %SYSTEM_ROOT%\TEMP)
- Убить все процессы word (killword.exe)
- Скопировать исходный файл во временную папку
- Запустить handler.php (через curl)
- Скопировать получившийся файл в общую папку
- Почистить временные файлы и лишние процессы
- Удалить запись из очереди БД
2. handler.php
Это сам обработчик. В отдельный файл он вынесен на случай зависания. Если через какое-то время он не вернет результат, то tmanager.php запустит killword.
вот его упрощенный код:
- <?php
- try {
- $doc = new COM("Word.Document");
- $doc->Range->InsertFile('E:\\tmp\\tmp1.doc');
-
- // Здесь код для обработки файла
-
- $doc->SaveAs('E:\\tmp\\tmp2.doc');
- $doc = null;
- } catch (Exception $e) {
- print '3';
- $doc = null;
- die();
- }
- print 0;
- ?>
* This source code was highlighted with Source Code Highlighter.
Скрипт tmanager.php передает скрипту handler.php файл для обработки. Последний его обрабатывает и создает новый файл. В случае зависания handler.php, tmanager.php просто убивает winword.
Выводы
- Не открывайте присланные вам файлы напрямую, делайте вставку файла.
- Не запускайте несколько процессов Winword.exe — они могут конфликтовать.
- Не открывайте и не сохраняйте файлы в папке с большим количеством файлов — Word будет пытаться просканировать эти файлы и при этом «тормозить».
- Отделите процесс работающий с COM от основного процесса. Это позволит при зависании первого предпринять некоторые действия.
Update: Нашел ссылку на статью по поводу серверной автоматизации. С момента прошлого прочтения она стала на русском и немного подросла.