Основы веб-работников

  1. Проблема: параллелизм JavaScript
  2. Знакомство с веб-работниками: привнесение потоков в JavaScript
  3. Типы веб-работников
  4. Начиная
  5. Общение с работником через передачу сообщений
  6. Переносимые объекты
  7. Рабочая среда
  8. Возможности, доступные для работников
  9. Загрузка внешних скриптов
  10. Subworkers
  11. Встроенные работники
  12. URL-адреса BLOB-объектов
  13. Полный пример
  14. Загрузка внешних скриптов
  15. Обработка ошибок
  16. Слово о безопасности
  17. Те же соображения происхождения
  18. Случаи применения

Проблема: параллелизм JavaScript

Существует ряд узких мест, препятствующих переносу интересных приложений (скажем, из серверных реализаций) в клиентский JavaScript. Некоторые из них включают совместимость браузера, статическую типизацию, доступность и производительность. К счастью, последнее быстро уходит в прошлое, поскольку производители браузеров быстро повышают скорость своих движков JavaScript.

Одна вещь, которая осталась помехой для JavaScript, это на самом деле сам язык. JavaScript - это однопоточная среда, что означает, что несколько сценариев не могут выполняться одновременно. В качестве примера представим сайт, который должен обрабатывать события пользовательского интерфейса, запрашивать и обрабатывать большие объемы данных API и манипулировать DOM. Довольно часто, верно? К сожалению, все это не может быть одновременным из-за ограничений времени выполнения JavaScript в браузерах. Выполнение скрипта происходит в одном потоке.

Разработчики имитируют «параллелизм», используя такие методы, как setTimeout (), setInterval (), XMLHttpRequest и обработчики событий. Да, все эти функции работают асинхронно, но неблокирование не обязательно означает параллелизм. Асинхронные события обрабатываются после того, как текущий исполняемый скрипт уступил. Хорошей новостью является то, что HTML5 дает нам что-то лучшее, чем эти хаки!

Знакомство с веб-работниками: привнесение потоков в JavaScript

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

Они помогут положить конец этому отвратительному диалогу «сценарий без ответа», который мы все полюбили:

Обычный не отвечающий скрипт скрипта.

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

Типы веб-работников

Стоит отметить, что Спецификация обсуждает два вида веб-работников, Выделенные работники а также Общие работники , Эта статья будет охватывать только преданных работников, и я буду называть их «веб-работниками» или «работниками».

Начиная

Web Workers работают в изолированном потоке. В результате код, который они выполняют, должен содержаться в отдельном файле. Но прежде чем мы это сделаем, первое, что нужно сделать, это создать новый объект Worker на главной странице. Конструктор берет имя рабочего скрипта:

var worker = new Worker ('task.js');

Если указанный файл существует, браузер создаст новый рабочий поток, который загружается асинхронно. Рабочий не начнется, пока файл не будет полностью загружен и выполнен. Если путь к вашему работнику возвращает 404, работник молча провалится.

После создания работника запустите его, вызвав метод postMessage ():

worker.postMessage (); // Запуск работника.

Общение с работником через передачу сообщений

Связь между работой и ее родительской страницей осуществляется с использованием модели событий и метода postMessage (). В зависимости от вашего браузера / версии, postMessage () может принимать либо строку, либо объект JSON в качестве единственного аргумента. Последние версии современных браузеров поддерживают передачу объекта JSON.

Ниже приведен пример использования строки для передачи «Hello World» работнику в doWork.js. Рабочий просто возвращает сообщение, которое ему передано.

Основной скрипт:

var worker = new Worker ('doWork.js'); worker.addEventListener ('message', function (e) {console.log ('Worker сказал:', e.data);}, false); worker.postMessage ('Hello World'); // Отправить данные нашему работнику.

doWork.js (рабочий):

self.addEventListener ('message', function (e) {self.postMessage (e.data);}, false);

Когда postMessage () вызывается с главной страницы, наш работник обрабатывает это сообщение, определяя обработчик onmessage для события сообщения. Полезная нагрузка сообщения (в данном случае «Hello World») доступна в Event.data. Хотя этот конкретный пример не очень интересен, он демонстрирует, что postMessage () также является вашим средством для передачи данных обратно в основной поток. Удобный!

Сообщения, передаваемые между главной страницей и работниками, копируются, а не передаются. Например, в следующем примере свойство msg сообщения JSON доступно в обоих местах. Похоже, что объект передается непосредственно работнику, даже если он работает в отдельном выделенном пространстве. На самом деле происходит то, что объект сериализуется по мере передачи работнику, а затем десериализуется на другом конце. Страница и работник не совместно используют один и тот же экземпляр, поэтому конечным результатом является создание дубликата на каждом проходе. Большинство браузеров реализуют эту функцию, автоматически кодируя / декодируя значение JSON на любом конце.

Ниже приведен более сложный пример передачи сообщений с использованием объектов JSON.

Основной скрипт:

<button onclick = "sayHI ()"> Say HI </ button> <button onclick = "unknownCmd ()"> Отправить неизвестную команду </ button> <button onclick = "stop ()"> Остановить работника </ button> < output id = "result"> </ output> <script> function sayHI () {worker.postMessage ({'cmd': 'start', 'msg': 'Hi'}); } function stop () {// worker.terminate () из этого скрипта также остановит работника. worker.postMessage ({'cmd': 'stop', 'msg': 'Bye'}); } function unknownCmd () {worker.postMessage ({'cmd': 'foobard', 'msg': '???'}); } var worker = new Worker ('doWork2.js'); worker.addEventListener ('message', function (e) {document.getElementById ('result'). textContent = e.data;}, false); </ Скрипт>

doWork2.js:

self.addEventListener ('message', function (e) {var data = e.data; switch (data.cmd) {case 'start': self.postMessage ('WORKER STARTED:' + data.msg); перерыв; case 'stop': self.postMessage ('WORKER STOPPED:' + data.msg + '. (кнопки больше не работают)'); self.close (); // Завершает рабочий. break; default: self.postMessage ( 'Неизвестная команда:' + data.msg);};}, false);

Примечание . Существует два способа остановить работника: с помощью метода worker.terminate () с главной страницы или вызова self.close () внутри самого работника.

Пример : запустить этого работника!

Скажи HI Отправить неизвестную команду Stop рабочий

Переносимые объекты

Большинство браузеров реализуют структурированное клонирование Алгоритм, который позволяет передавать более сложные типы в / из Workers, такие как объекты File, Blob, ArrayBuffer и JSON. Однако при передаче этих типов данных с помощью postMessage () копия все равно создается. Следовательно, если вы передаете большой файл размером 50 МБ (например), то при передаче этого файла между рабочим и основным потоком возникают заметные накладные расходы.

Структурное клонирование - это хорошо, но его копирование может занять сотни миллисекунд. Для борьбы с перфорированным ударом вы можете использовать Переносимые объекты ,

В переносимых объектах данные переносятся из одного контекста в другой. Это нулевое копирование, что значительно повышает производительность отправки данных в Worker. Думайте об этом как о передаче по ссылке, если вы из мира C / C ++. Однако, в отличие от передачи по ссылке, «версия» из вызывающего контекста больше не доступна после переноса в новый контекст. Например, при переносе ArrayBuffer из основного приложения в Worker исходный ArrayBuffer очищается и больше не используется. Его содержимое (буквально тихо) переносится в контекст Worker.

Чтобы использовать переносимые объекты, используйте немного другую подпись postMessage ():

worker.postMessage (arrayBuffer, [arrayBuffer]); window.postMessage (arrayBuffer, targetOrigin, [arrayBuffer]);

Рабочий случай, первый аргумент - это данные, а второй - список элементов, которые должны быть переданы. Кстати, первый аргумент не обязательно должен быть ArrayBuffer. Например, это может быть объект JSON:

worker.postMessage ({data: int8View, moreData: anotherBuffer}, [int8View.buffer, anotherBuffer]);

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

Чтобы увидеть улучшение скорости передачи, проверьте это DEMO , Для получения дополнительной информации о переводе см. Наш HTML5Rock сообщение ,

Рабочая среда

Рабочая сфера

В контексте работника, и я, и это ссылаются на глобальную область видимости работника. Таким образом, предыдущий пример также может быть записан как:

addEventListener ('message', function (e) {var data = e.data; switch (data.cmd) {case 'start': postMessage ('WORKER STARTED:' + data.msg); break; case 'stop': ... }, ложный);

В качестве альтернативы вы можете установить обработчик события onmessage напрямую (хотя addEventListener всегда поощряется ниндзя JavaScript).

onmessage = function (e) {var data = e.data; ...};

Возможности, доступные для работников

Благодаря многопоточному поведению веб-работники имеют доступ только к подмножеству функций JavaScript:

  • Объект навигатор
  • Местоположение объекта (только для чтения)
  • XMLHttpRequest
  • setTimeout () / clearTimeout () и setInterval () / clearInterval ()
  • Кэш приложения
  • Импорт внешних скриптов с использованием метода importScripts ()
  • Порождение других веб-работников

Работники не имеют доступа к:

  • DOM (это не потокобезопасно)
  • Объект окна
  • Объект документа
  • Родительский объект

Загрузка внешних скриптов

Вы можете загрузить внешние файлы сценариев или библиотеки в работника с помощью функции importScripts (). Метод принимает ноль или более строк, представляющих имена файлов для импортируемых ресурсов.

Этот пример загружает script1.js и script2.js в работника:

worker.js:

importScripts ( 'script1.js'); importScripts ( 'script2.js');

Который также может быть записан как один оператор импорта:

importScripts ('script1.js', 'script2.js');

Subworkers

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

  • Субработники должны быть размещены в том же источнике, что и родительская страница.
  • URI внутри подчиненных разрешаются относительно местоположения их родительского работника (в отличие от главной страницы).

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

Для примера того, как порождать субработника, см. пример в спецификации.

Встроенные работники

Что, если вы хотите создать свой рабочий сценарий на лету или создать автономную страницу без необходимости создания отдельных рабочих файлов? С помощью Blob () вы можете «встроить» своего работника в тот же HTML-файл, что и основная логика, создав дескриптор URL для рабочего кода в виде строки:

var blob = new Blob (["onmessage = function (e) {postMessage ('msg from worker');}"]); // Получить ссылку на BLOB-ссылку на наш рабочий файл. var blobURL = window.URL.createObjectURL (blob); var worker = новый работник (blobURL); worker.onmessage = function (e) {// e.data == 'msg from worker'}; worker.postMessage (); // Запуск работника.

URL-адреса BLOB-объектов

Магия приходит с призывом window.URL.createObjectURL () , Этот метод создает простую строку URL, которая может использоваться для ссылки на данные, хранящиеся в объекте DOM File или Blob. Например:

блоб: HTTP: // локальный / c745ef73-ece9-46da-8f66-ebes574789b1

URL-адреса BLOB-объектов являются уникальными и действуют в течение всего срока службы вашего приложения (например, до тех пор, пока документ не будет выгружен). Если вы создаете много URL-адресов BLOB-объектов, рекомендуется опубликовать ссылки, которые больше не нужны. Вы можете явно освободить URL-адреса Blob, передав его window.URL.revokeObjectURL () :

window.URL.revokeObjectURL (blobURL);

В Chrome есть хорошая страница для просмотра всех созданных URL-адресов BLOB-объектов: chrome: // blob-internals /.

Полный пример

Сделав еще один шаг вперед, мы сможем понять, как код JS работника встроен на нашей странице. Этот метод использует тег <script> для определения работника:

<! DOCTYPE html> <html> <head> <meta charset = "utf-8" /> </ head> <body> <div id = "log"> </ div> <script id = "worker1" type = "javascript / worker"> // Этот скрипт не будет обрабатываться механизмами JS //, потому что его тип - javascript / worker. self.onmessage = function (e) {self.postMessage ('msg from worker'); }; // Остальная часть вашего рабочего кода идет сюда. </ script> <script> function log (msg) {// Использовать фрагмент: браузер будет отображать / перекомпоновывать только один раз. var фрагмент = документ.createDocumentFragment (); fragment.appendChild (document.createTextNode (MSG)); fragment.appendChild (document.createElement ( 'ш')); document.querySelector ( "# журнал") AppendChild (фрагмент). } var blob = new Blob ([document.querySelector ('# worker1'). textContent]); var worker = new Worker (window.URL.createObjectURL (blob)); worker.onmessage = function (e) {log ("Получено:" + e.data); } worker.postMessage (); // Запуск работника. </ script> </ body> </ html>

На мой взгляд, этот новый подход немного чище и понятнее. Он определяет тег сценария с id = "worker1" и type = 'javascript / worker' (поэтому браузер не анализирует JS). Этот код извлекается в виде строки с использованием document.querySelector ('# worker1'). TextContent и передается в Blob () для создания файла.

Загрузка внешних скриптов

При использовании этих методов для встраивания вашего рабочего кода importScripts () будет работать, только если вы предоставите абсолютный URI. Если вы попытаетесь передать относительный URI, браузер выдаст сообщение об ошибке безопасности. Причина в том, что рабочий (теперь созданный из URL-адреса BLOB-объекта) будет разрешен с префиксом blob :, а ваше приложение будет работать по другой схеме (предположительно http: //). Следовательно, сбой будет из-за ограничений перекрестного происхождения.

Один из способов использования importScripts () во встроенном работнике - это «вставить» текущий URL вашего основного скрипта, из которого он выполняется, передав его встроенному работнику и создав абсолютный URL-адрес вручную. Это обеспечит импорт внешнего скрипта из того же источника. Предполагается, что ваше основное приложение запущено с http://example.com/index.html:

... <script id = "worker2" type = "javascript / worker"> self.onmessage = function (e) {var data = e.data; if (data.url) {var url = data.url.href; var index = url.indexOf ('index.html'); if (index! = -1) {url = url.substring (0, index); } importScripts (url + 'engine.js'); } ...}; </ script> <script> var worker = new Worker (window.URL.createObjectURL (bb.getBlob ())); worker.postMessage ( {url: document.location} ); </ Скрипт>

Обработка ошибок

Как и в любой логике JavaScript, вы захотите обрабатывать любые ошибки, возникающие у ваших веб-работников. Если во время выполнения работника возникает ошибка, запускается ErrorEvent. Интерфейс содержит три полезных свойства для определения того, что пошло не так: filename - имя рабочего скрипта, вызвавшего ошибку, lineno - номер строки, в которой произошла ошибка, и message - содержательное описание ошибки. Вот пример настройки обработчика события onerror для печати свойств ошибки:

<output id = "error" style = "color: red;"> </ output> <output id = "result"> </ output> <script> функция onError (e) {document.getElementById ('error'). textContent = ['ERROR: Line', e.lineno, 'in', e.filename, ':', e.message] .join (''); } function onMsg (e) {document.getElementById ('result'). textContent = e.data; } var worker = new Worker ('workerWithError.js'); worker.addEventListener ('message', onMsg, false); worker.addEventListener ('error', onError, false); worker.postMessage (); // Запуск работника без сообщения. </ Скрипт>

Пример : workerWithError.js пытается выполнить 1 / x, где x не определено.

Запустить его

workerWithError.js:

self.addEventListener ('message', function (e) {postMessage (1 / x); // Преднамеренная ошибка.};

Слово о безопасности

Ограничения с локальным доступом

Из-за ограничений безопасности Google Chrome рабочие не будут работать локально (например, из file: //) в последних версиях браузера. Вместо этого они молча терпят неудачу! Чтобы запустить приложение из схемы file: //, запустите Chrome с установленным флагом --allow-file-access-from-files. ПРИМЕЧАНИЕ . Не рекомендуется запускать основной браузер с этим установленным флагом. Он должен использоваться только для целей тестирования, а не для регулярного просмотра.

Другие браузеры не накладывают такое же ограничение.

Те же соображения происхождения

Рабочие сценарии должны быть внешними файлами по той же схеме, что и их вызывающая страница. Таким образом, вы не можете загрузить скрипт из data: URL или javascript: URL, а страница https: не может запустить рабочие скрипты, которые начинаются с http: URL.

Случаи применения

Так какое приложение будет использовать веб-работников? К сожалению, веб-работники все еще относительно новы, и большинство примеров / учебных пособий касаются вычисления простых чисел. Хотя это не очень интересно, это полезно для понимания концепций веб-работников. Вот еще несколько идей, чтобы заставить ваш мозг взбалтывать:

  • Предварительная выборка и / или кэширование данных для последующего использования
  • Подсветка синтаксиса кода или другое форматирование текста в реальном времени
  • Программа проверки орфографии
  • Анализировать видео или аудио данные
  • Фоновый ввод / вывод или опрос веб-сервисов
  • Обработка больших массивов или огромных JSON-ответов
  • Фильтрация изображений в <canvas>
  • Обновление многих строк локальной веб-базы данных

демос

Рекомендации

Похожие

Антивирус плюс дополнения: Avira делает полный пакет бесплатным
Avira Free Security Suite предлагает не только сканер вирусов. (Фото: Авира) Хорошие новости для тех, кто заботится о безопасности: как первый провайдер, Avira не только предоставляет свою антивирусную программу, но также предоставляет полный пакет безопасности с дополнительными услугами. Но вы не должны ожидать слишком многого.
IHS Markit: В Индии содержание местного видео-сервиса так же важно, как и цена
В Индии локализованный контент так же важен, как и цены,
Лысая мельница
... ся всего в 15 км от центра Познани и в 5 км от Бедруски по маршруту Познань - Бедруско (повятская дорога № 2406 P). На расстоянии 300 м от здания Лысого Млына находится лесная автостоянка. Адрес центра: OEL 'Łysy Młyn' Познанская 2Б 62-003 Биедруско Доступ также возможен на общественном транспорте:
Женский день отмечен конференцией «Здоровье и работа женщин»
Мы хвалим вас! Большинство наших клиентов - послы образовательного портала Мы представляем результаты теста удовлетворенности, рекомендаций и рекомендаций для клиентов знаний и практики и образовательного портала, проведенного компанией «Маркетинговые отношения».
WikiZero
открытый дизайн википедии. электронная роботизированная игрушка Furby Модель Furby 2012 года; изображена версия Voodoo Purple . Другие имена Furbee (используется в Швеции, чтобы избежать путаницы с одноименное место ). Тип Электронный
Давайте сделаем A / B тесты - но стоит ли это того? - www.webusability.pl
Измерение влияния изменений в дизайне при прямом переводе на желаемое преобразование, несомненно, является ценным, легкодоступным, недорогим и широко используемым, в том числе такими гигантами, как Amazon или Google. Зачем организовывать исследования для нескольких человек, если мы можем собирать данные из тысяч, не вставая со стола? Что хорошего В A / B тестах мы создаем две версии выбранного элемента страницы и отображаем их случайным образом, отслеживая реакции
CSS: выбор последнего ребенка
Этот урок CSS объясняет, как использовать селектор CSS с названием : last-child с синтаксисом и примеры. Описание Селектор CSS: last-child позволяет вам выбрать элемент, который является последним дочерним элементом в его родительском элементе. Синтаксис Синтаксис для: активного селектора CSS: element: last-child {style_properties} Параметры или Аргументы element Последний из этого типа элемента в
10 самых раздражающих Документов Google (и как их исправить)
... совместимы практически с любой платформой, что делает сотрудничество невероятно простым. Это также бесит, неполноценно и ограничено. Гугл документы в облаке, прямо за кончиками ваших пальцев, и вы по прихоти инженеров Google, которые управляют настройками, исправлениями и улучшениями. С момента запуска Google Spreadsheets в 2006 году Google постоянно совершенствует Документы
Хороший и плохой инструмент автоматизации тестирования селена
Время чтения: 10 минут Кто не любит глубокую историю именования? В начале 2000-х годов компания Mercury Interactive управляла миром автоматизации тестирования с помощью своего инструмента Astra Quick Test. Это был не самый лучший продукт, который можно себе представить. Например, вы могли запускать тесты только на компьютерах с Windows, поддерживалось только небольшое количество версий браузера, и вам приходилось использовать один язык для написания скриптов - VBScript. Когда разработчик
Как исправить отступы и пробелы в электронных письмах HTML
Этот пост был обновлен 9 мая 2018 года. Первоначально он был опубликован в декабре 2010 года. Вы когда-нибудь замечали небольшой интервал под изображениями в Outlook.com а также Gmail ? Каждый браузер отображает этот интервал,
Взлом Бундестага: возможные предпосылки и методы защиты
Здесь, в Univention, мы, конечно, также обеспокоены нападением на ИТ-инфраструктуру парламента Германии, более известную как «взлом Бундестага». Напомним: кажется, что там были некоторые поддельные электронные письма, в том числе ссылки на

Комментарии

Так в чем проблема?
Так в чем проблема? A / B тесты являются надежным инструментом при правильном использовании. Стоит знать о том, что A / B тесты не делают или делают на первый взгляд. Данный вариант дизайна не всегда иллюстрирует концепцию. Мы можем отклонить пароль, использованный в описании, из-за более низких ставок, но мы не знаем, что это была форма, а не содержание. Концепция может быть точной, сравнение с каким-то другим элементом на странице больше
Можете ли вы позволить себе немного лучшие наушники, чем те, которые входят в комплект?
Можете ли вы позволить себе немного лучшие наушники, чем те, которые входят в комплект? Здесь альбом еще более расширен и снова каталогизирует все фотографии и видео, и отображает их по альбомам, местоположению, лицам ... Экран как таковой не идеален для просмотра фильмов, но он будет служить. Видео YouTube вращаются и максимизируют 360p, что будет мешать некоторым. С хорошим плеером из Play Store можно вращать фильмы 1080p. Xperia E4 неплохо подходит для игр. Real Racing
Так почему же ваш старый Super NES или Sega Genesis выглядит как мусор на вашем новом HDTV?
Так почему же ваш старый Super NES или Sega Genesis выглядит как мусор на вашем новом HDTV? Это комбинация факторов, но в основном это сводится к следующему: старые игровые приставки были разработаны для работы со старыми телевизорами, в частности с телевизорами с большими электронно-лучевыми трубками (CRT), о которых мы помним до того, как ЖК-телевизоры захватили мир. Резолюции не совпадают Если вы подключаете классическую систему на основе картриджей впервые за многие
Вся природа Selenium идеально вписывается в основные принципы Agile, DevOps , или же Непрерывная доставка , Как именно это достигается?
Так почему же ваш старый Super NES или Sega Genesis выглядит как мусор на вашем новом HDTV? Это комбинация факторов, но в основном это сводится к следующему: старые игровые приставки были разработаны для работы со старыми телевизорами, в частности с телевизорами с большими электронно-лучевыми трубками (CRT), о которых мы помним до того, как ЖК-телевизоры захватили мир. Резолюции не совпадают Если вы подключаете классическую систему на основе картриджей впервые за многие
Возможен ли такой же сценарий взлома, что и взлом в Бундестаге, с Univention Corporate Server?
Возможен ли такой же сценарий взлома, что и взлом в Бундестаге, с Univention Corporate Server? Как вы можете прочитать здесь, например, теперь мы знаем, что парламент Германии использует OpenLDAP и Active Directory в качестве центральных служб каталогов. OpenLDAP также является центральной службой каталогов в UCS, а службы Active Directory предоставляются в UCS программным обеспечением с открытым исходным кодом Samba. Следовательно, мы были вынуждены спросить себя, был ли бы тот же тип

Довольно часто, верно?
Cmd': 'foobard', 'msg': '?
Зачем организовывать исследования для нескольких человек, если мы можем собирать данные из тысяч, не вставая со стола?
Com а также Gmail ?
Так в чем проблема?
Можете ли вы позволить себе немного лучшие наушники, чем те, которые входят в комплект?
Так почему же ваш старый Super NES или Sega Genesis выглядит как мусор на вашем новом HDTV?
Так почему же ваш старый Super NES или Sega Genesis выглядит как мусор на вашем новом HDTV?
Возможен ли такой же сценарий взлома, что и взлом в Бундестаге, с Univention Corporate Server?