Как стать автором
Обновить

Как я с Microsoft Word воевал!

Время на прочтение4 мин
Количество просмотров2.1K
Я хотел бы рассказать одну историю, которая произошла со мной. На мой взгляд, она достаточно интересная, и может помочь, кому-нибудь с аналогичной проблемой. Сразу скажу — это мой первый пост на хабре :-) Он оказался немного затянутым, поэтому основные выводы я вынес отдельно — в конец статьи.

Итак, задача:
Есть портал, на который загружаются документы Microsoft Word в формате Doc. Перед публикацией в общий доступ они должны быть обработаны. Как именно — не важно, поэтому для упрощения возьмем такой алгоритм:
  1. Создать новый документ.
  2. Вставить данные из исходного (вставить файл).
  3. Сохранить полученный файл вместо оригинала.


Скажу сразу: Doc-файлами занимается дополнительный сервер с Win2k3+Office+Apache+PHP5 на борту. Возможно, в комментариях, я найду ответ, как мне производить аналогичные манипуляции на FreeBSD :-)

Ответ на первый вопрос: Зачем нужна эта обработка?
Путем проб и ошибок, было выведено, что это минимальная последовательность действий, которую требуется выполнять для всех документов.
Причины для этого две:
  • Во-первых, при вставке файла вставляется только его содержимое (текст, форматирование, картинки), а макросы остаются «за бортом». Это позволяет избавиться от вредоносного кода, который может попасть в ваш Normal.dot :-) Разумеется, если вам требуется сохранить макросы, этот вариант не подойдет, но для большинства задач вирусы макросы не нужны.
  • Во-вторых, если исходный файл был заблокирован для записи, то вы не сможете его изменить. А новый документ эту блокировку не унаследует.


Ответ на второй вопрос: Как была реализована обработка документов?
Методом проб и ошибок было сделано несколько поколений обработчиков. Дело в том, что, по-началу, нормально работающая программа, через некоторое время начинала выдавать ошибки. В результате обработка останавливалась. А когда остановки становились слишком частыми приходилось признавать несостоятельность предыдущего решения и изобретать новое.

Итак.
Первое поколение: «mainscript.exe»
Этот скрипт запускал Word с параметрами командной строки. Один из них — имя обрабатываемого файла, а второй имя макроса обработки. По истечении 2-х минут программа убивала запущенный ею процесс. Сам скрипт запускался через CGI.
Из минусов, можно отметить:
  1. работала программы ровно 2 минуты
  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 секунд он проверяет в базе таблицу, на наличие очередной заявки на обработку (так называемая очередь).
Если заявка есть выполняем следующие действия:
  1. Удалить все файлы из временных папок (в т.ч. %SYSTEM_ROOT%\TEMP)
  2. Убить все процессы word (killword.exe)
  3. Скопировать исходный файл во временную папку
  4. Запустить handler.php (через curl)
  5. Скопировать получившийся файл в общую папку
  6. Почистить временные файлы и лишние процессы
  7. Удалить запись из очереди БД


2. handler.php
Это сам обработчик. В отдельный файл он вынесен на случай зависания. Если через какое-то время он не вернет результат, то tmanager.php запустит killword.
вот его упрощенный код:
  1. <?php
  2. try {  
  3.   $doc = new COM("Word.Document");
  4.   $doc->Range->InsertFile('E:\\tmp\\tmp1.doc');
  5.  
  6.     // Здесь код для обработки файла
  7.  
  8.   $doc->SaveAs('E:\\tmp\\tmp2.doc');
  9.   $doc = null;
  10. } catch (Exception $e) {
  11.   print '3';
  12.   $doc = null;
  13.   die();
  14. }
  15. print 0;
  16. ?>
* This source code was highlighted with Source Code Highlighter.


Скрипт tmanager.php передает скрипту handler.php файл для обработки. Последний его обрабатывает и создает новый файл. В случае зависания handler.php, tmanager.php просто убивает winword.

Выводы
  1. Не открывайте присланные вам файлы напрямую, делайте вставку файла.
  2. Не запускайте несколько процессов Winword.exe — они могут конфликтовать.
  3. Не открывайте и не сохраняйте файлы в папке с большим количеством файлов — Word будет пытаться просканировать эти файлы и при этом «тормозить».
  4. Отделите процесс работающий с COM от основного процесса. Это позволит при зависании первого предпринять некоторые действия.


Update: Нашел ссылку на статью по поводу серверной автоматизации. С момента прошлого прочтения она стала на русском и немного подросла.
Теги:
Хабы:
+5
Комментарии27

Публикации

Изменить настройки темы

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн