-Поиск по дневнику

Поиск сообщений в rss_rss_hh_new

 -Подписка по e-mail

 

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 51

Habrahabr/New








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/new/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-new-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

Как накрутить рейтинг на хабре и уйти незамеченным

Вторник, 04 Июля 2017 г. 10:02 + в цитатник


Как-то пятничным вечером я сидел за домашним компом с чашкой черного чая, писал статью и думал о жизни. Работа спорилась, но голова начинала к тому времени заметно притормаживать. И вот когда за окном стало уже совсем темно, я решил отправить статью отдыхать до завтрашнего дня, да и самому пойти спать. Но вместо того, чтобы сохранить все в черновик, как полагается, сонный мозг на автопилоте её зачем-то опубликовал...



Понял я это не сразу, а только спустя несколько минут, зайдя напоследок обновить свой профиль. Ощущение было, как будто не выключил газ на кухне и все вот-вот умрут. Я молился, чтобы ее можно вернуть в черновики или хотя бы удалить. Кстати, последнего, как оказалось, сделать уже нельзя, если материал попал в общее обозрение. Впоследствии говорил на этот счет с тех поддержкой, но мне порекомендовали просто смириться с провалом)


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


Это не могло не натолкнуть на мысль воспроизвести ситуацию в промышленных масштабах.


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


Будем автоматизировать.


Подслушиваем Habrahabr


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


В качестве веб прокси будем использовать Charles для Mac. Крайне удобная вещь. Если вы разработчик, то рекомендую иметь её в своем джентельменском наборе. Позволяет также слушать трафик с любого подручного девайса, если вы, например, отлаживаете мобильное приложение.


В рамках одной сессии успешно получаем все необходимые нам методы.

Как оказалось, за создание, публикацию и удаление статей отвечает один и тот же запрос, но с разными параметрами. Можно создать пост в качестве черновика или сразу его опубликовать. И именно здесь прописывается будет ли ваш пост в песочнице, переводом или ходовой публикацией. Может быть, это даст возможность обойти требования к публикации в песочнице, но не проверял. Скорее всего, сервер просто развернет домой с какой-нибудь забавной ошибкой.


Проверяем на практике


Уже практически праздную победу, делаю запрос на создание поста:


params = {
    :id => '',
    :post_type => 'simple',
    :flow => 5,
    :hubs => [20742],
    :title => 'test1',
    :text => 'test1',
    :tags_string => 'test1'
}

response = HTTParty.post('https://habrahabr.ru/json/topic/?action=save', headers: headers, body: params)
# Получаем id поста для последующего использования
params[:id] = JSON.parse(response.body)['redirect'].split('/')[2]

Flow равное 5 и Hubs равное 20742 — это не случайные числа, а вполне осознанно выбранный наименее популярный поток 'Разное' и хаб 'Читальный зал', чтобы снизить вероятность быть уличенным в афере.


Но тем не менее, Habr нам почему-то отвечает отказом:


{
    "system_errors": [
        "Неизвестный тип публикации"
    ]
}

Странно. Ведь все параметры верны, post_type установлен и точно совпадает с нужным и все поля на месте. Проверяем запрос из прокси — работает. А из кода — ошибка.


Как оказалось после веселой отладки, если отсутствует заголовок Referer, то любой запрос к API падает. Юзабилити на высоте. И еще один забавный случай: независимо от того какое значение указано в поле draft, публикация все равно будет создаваться черновиком:

Даже если стоит 0 или вообще абракадабра.


Но если поля вообще нет, то статья уже публикуется в общий поток.


Хорошо, пост создали, можно голосовать. Тут все тоже довольно просто и завелось с первого раза:


vote_headers = {
    'Cookie' => HTTP::Cookie.cookie_value(vote_jar.cookies(habr_uri)),
    'User-Agent' => 'Mozilla/5.0',
    'Referer' => 'https://habrahabr.ru/top/',
    'Content-Type' => 'application/x-www-form-urlencoded',
    'Accept' => 'application/json'
}

vote_body = {
# Помните прошлый запрос? 
    'ti' => params[:id],
    'tt' => 2,
    'v' => 1
}

uri = URI.parse('https://habrahabr.ru/json/vote/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path, vote_headers)
request.body = URI.encode_www_form(vote_body)

# Только для отладки
puts http.request(request).body

Запрос через HTTParty отчаянно отказывался работать с голосованием, пришлось пилить его через net/http. Аргументы, конечно, не самые говорящие в API, но в целом все понятно: id публикации и два магических параметра. Элементарно.


Автоматизируем


Теперь все вместе. Достаем через прокси наши Cookies, сначала для одного аккаунта, с которого будем постить, а потом для второго, с которого будем голосовать.


Запускаем запросы последовательно друг за другом и все, вроде как, хорошо. И тут я понял, что для смены аккаунтов мне пришлось явно разлогиниться на сайте. Но при этом моя сессия все равно продолжала работать из кода.
То есть, мои куки никто и не думал обнулять на бэкенде. Хабр, как-то нехорошо так поступать со своими пользователями. Не секьюрно.


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


{
    "system_errors": [
        "Повтор на втором игроке!"
    ]
}

Кто постарше помнит, что раньше была такая передача 'Пойми меня', где дети по цепочке объясняли друг другу слова. И если двое сделали это одинаково, то ведущий говорил: 'повтор на таком-то игроке!'. Ссылка на запись игры.
Мне кажется, это многое говорит о возрасте тех, кто разрабатывал хабр.


Постепенно увеличиваем задержку, и методом перебора выясняем, что таймаут на создание постов стоит 30 секунд. Запускаем цикл и уходим пить любимый кетчуп чай.


Не будем наглеть, остановимся на 30 баллах, исчеркав все черновики хабра:


122 место, неплохо. Осталось чуть-чуть до первой сотни. А вот и сами тестовые посты:



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


Заключение


Вот таким нехитрым способом можно незаметно накрутить своему товарищу рейтинг.


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


Спасибо за внимание.


P.S. Не баньте меня, пожалуйста. Баньте его — sp1nfox, это ему рейтинг накручивали.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332296/


Метки:  

Проблема непрерывной защиты веб-приложений. Взгляд со стороны исследователей и эксплуатантов

Вторник, 04 Июля 2017 г. 10:00 + в цитатник
Сегодня у нас не совсем обычная статья. Как следует из заголовка, она посвящена проблемам непрерывной защиты веб-приложений и разбита на две части, отражающие два взгляда на проблему: с позиции разработчиков WAF (Андрей Петухов, SolidLab) и с точки зрения центра мониторинга и противодействия кибератакам, который использует WAF для оказания сервиса клиентам (@avpavlov, Solar JSOC).

Начнем мы с разработчиков, а через неделю дадим слово эксплуатантам WAF.




Мы из SolidLab. Наша основная экспертиза – это application security во всех проявлениях – offensive (анализ защищенности приложений, инфраструктур, процессов) и defensive (построение SDLC и отдельные аспекты этого – например, ручной анализ кода, обучающие курсы и семинары, внедрение средств защиты вообще и нашего WAF в частности).

Для начала мы хотели бы поговорить о тенденциях в мире разработки и эксплуатации веб-приложений, которые определяют ландшафт угроз и методы защиты. Полагаем, что рассуждения о SOC, непрерывном мониторинге и WAF’ах скоро будут неотделимы от рассуждений о непрерывной разработке и особенностях этого процесса для того или иного приложения/сервиса. Начнем с очевидной тенденции к сокращению длительности релизного цикла. Интересные факты:

  • Тенденция на уменьшение релизного цикла ПО именно как новый тренд отмечалась еще в 2001 году в статье Microsoft Research (визионеры?).
  • Статья о Continuous delivery в Википедии появилась в декабре 2011 года.
  • В 2014 году появилась Continuous Delivery Conference.
  • В последние 5 лет массово появляются научные статьи (см. Google Scholar) по изучению наблюдаемых феноменов (например, “Continuous Software Engineering and Beyond: Trends and Challenges”) с анализом собственного опыта (например, “Continuous delivery? easy! just change everything (well, maybe it is not that easy)”).

Короткие релизные циклы приводят к переосмыслению причин и мотивов, которые делали целесообразным использование традиционных мероприятий – привлечение внешних консультантов для поиска недостатков (неважно, каким методом – «белым» или «черным» ящиком), ручное тестирование собственной QA- или security-командой: схема «проверил код релиза и живи полгода спокойно до следующего» больше не работает. В разработке с короткими релизными циклами перечисленные «традиционные» мероприятия рассматриваются уже более зрело: как источник обратной связи на процессы и их обеспечение всем необходимым – инструментами, людьми, методиками, а не как способ получить некоторое защищенное состояние приложения и зафиксировать его.

Отметим, что непрерывность процессов (особенно в части тестирования и развертывания) подразумевает переход к высокой степени автоматизации рутинных задач. Автоматизация отлично помогает снизить частоту попадания в продукт типичных (понятных для всех участников разработки) недостатков:

  • Недостатков, под которые написали тесты.
  • Недостатков, под которые написали правила для статического/динамического анализатора.
  • Недостатков, которые являются следствием человеческого фактора (автоматическая подготовка окружения, конфигурация и развертывание): вероятность их привнесения минимизируется за счет автоматизации и грамотного управления изменениями в тех самых скриптах автоматизации.

Тут же заметим, что решение рутинных задач современные веб-платформы и фреймворки (RoR, Django, Spring, Struts, ASP.NET MVC и т.п.) стараются забирать на себя, не давая шанса разработчикам выстрелить себе в ногу, реализовав, например, собственную защиту от CSRF или шаблонизатор на основе find/replace. Соответственно, по мере адаптации новых веб-платформ в перспективе можно ожидать снижение вероятности привнесения в код недостатков, позволяющих проводить атаки типа CSRF, SQL injection, XSS. Что уж говорить, даже большинство XML-парсеров сегодня по умолчанию запрещают разрешение внешних сущностей (т.е. применяют принцип safe defaults).

Не такой прямолинейной с идейной точки зрения оказывается тактика борьбы с недостатками других типов:

  • Нетипичные недостатки уровня логики в отдельных модулях (функции, которые не может забрать на себя веб-платформа). Например, недостатки, связанные с некорректной/недостаточной авторизацией при доступе к объектам (англ. Indirect Object Reference).
  • Недостатки, появляющиеся на стыке ответственности различных групп: например, разработчиков различных сервисов в микросервисной архитектуре, возникающие в отсутствие явных контрактов, или администраторов и devops’ов). Примерами являются недостатки, связанные с импортом или загрузкой файлов (англ. Insecure File Upload), а также недостатки, связанные с некорректной интеграцией (например, SSO, платежной системы, облачного чата).
  • Недостатки, связанные с использованием 3rd-party библиотек/платформ/фреймворков с опубликованными уязвимостями. Рассуждения касаются в том числе и бинарных библиотек, обертки над которыми использует приложение, и утилит, которые запускаются как отдельные процессы (ярким примером будут уязвимости в ImageMagic – CVE-2016-3714, CVE-2016-3715, CVE-2016-3716, CVE-2016-3717, CVE-2016-3718) или ffmpeg. Важность своевременной защиты от атак на недостатки в 3rd-party-компонентах трудно переоценить. В 2017 году практическая реализуемость массовых автоматизированных атак в масштабах всего Интернета не вызывает вопросов. Примерами служат недавние набеги на Joomla (например, CVE-2016-8870 + CVE-2016-8869) или Apache Struts (CVE-2017-5638).

Для полноты изложения важно упомянуть о вариантах решения перечисленных задач в рамках sSDLC методами, альтернативными от мониторинга.

От нетипичных недостатков можно избавляться через whateverbox-анализ от сторонней организации, проводимый с определенной периодичностью, или через массовое народное непрерывное тестирование aka Bug Bounty. Недостатки третьего типа на этапе сборки можно устранять, реализовав анализ внешних зависимостей (см. Vulners, WhiteSource, OWASP dependency-check), а на этапе эксплуатации – выполнением тех же проверок теми же инструментами, но в виде отдельного задания и с большей частотой. Мы же с коллегами больше будем говорить про защиту от атак через непрерывный мониторинг и реагирование.

С точки зрения управления процессом разработки параметры защищенности создаваемого ПО/сервиса – целевые показатели, подлежащие планированию. Тактика достижения этих показателей, по-хорошему, определяется на этапе инициализации проекта (или на этапе его реформирования) и зависит от модели угроз и оценки рисков, ограничений (бюджетных, временных и других), доступных производственных ресурсов (квалификация, методическое обеспечение) и так далее. Наше видение заключается в том, что правильно организованный мониторинг веб-приложения на этапе эксплуатации обеспечит не только снижение рисков, связанных с реализацией угроз через недостатки веб-приложения и его окружение (что очевидно), но и снижение рисков, связанных с неправильными управленческими решениями, которые принимаются в процессе разработки, или ненадлежащей реализацией хороших и правильных решений.

Другими словами, наличие правильного мониторинга увеличивает число степеней свободы при определении тактики достижения параметров защищенности создаваемого ПО/сервиса на начальном этапе.

Из рассуждений выше видно, что среди прочих, правильный мониторинг должен решать следующие ключевые задачи защиты веб-приложений:

  1. Оперативная защита от 1-day атак на компоненты приложения. Цель – предотвратить атаку на 1-day уязвимость (а желательно и на 0-day) в том случае, если по каким-то причинам атака пришла раньше обновления.
  2. Адаптация к постоянным изменениям приложения (как функциональным, так и технологическим). Цель – в каждый момент времени иметь возможность эффективного контроля взаимодействий клиентов с измененными аспектами приложения (видеть атаки, иметь возможность гранулярной блокировки, иметь возможность гранулярной настройки).
  3. Обнаружение логических атак на приложение. Цель – минимизировать последствия от нетипичных (логических) ошибок, которые попадают в прод. Рационально исходить из предположения, что такие ошибки практически невозможно полностью устранить автоматизированными процедурами в рамках sSDLC, а, следовательно, работающих варианта остается два: Bug Bounty и мониторинг с реагированием.

Помимо перечисленных задач, которые, по нашему мнению, должны решаться правильным мониторингом (и любым WAF’ом как основным инструментом для построения такого процесса), мы еще предлагаем список архиважных фич для WAF'ов.

В отличие от задачи сравнения WAF’ов вообще (по функциональным критериям или критериям производительности) и разговоре об эффективности вообще (еще не один benchmark WAF’ов не был признан сообществом, все были прилично раскритикованы), мы хотим обратить внимание на свойства инструментов, которые позволят оценить, подходит ли данный инструмент для защиты конкретного приложения. Постановка задачи именно такая – оценить применимость WAF для конкретного приложения на конкретном стеке технологий.

WAF должен понимать прикладной протокол защищаемого приложения

Здесь действует простое правило: если WAF не сможет разобрать, каким образом передаются значения параметров, он не сможет обнаружить и манипуляции с этими значениями (injection/tampering).

Под прикладным протоколом приложения мы понимаем:

  1. Способ адресации функций/ресурсов приложения. В самых простых приложениях это часть PATH в URL. Иногда встречаются приложения, где URL-путь всегда одинаковый (например, /do), функции адресуются значением параметра типа “action”, а ресурсы – параметром типа “page” или “res”. В общем же случае функция/ресурс может адресоваться произвольным набором атрибутов HTTP-запроса.
  2. Способ передачи входных параметров, которые параметризуют функции приложения. Отметим, что спецификация протокола HTTP никак не ограничивает фантазию веб-разработчиков в выборе способа транспорта необходимых данных через структуру HTTP-запроса.

Таким образом, разбор протокола защищаемого приложения WAF’ом – это определение по каждому HTTP-запросу, какая функция приложения вызывается или какой ресурс запрашивается, с какими параметрами и от чьего имени, если речь идет о закрытой зоне приложения, доступной после аутентификации.

В качестве примера интересных протоколов передачи параметров можно привести следующие:

  • 3D-Secure. На одном из шагов протокола инкапсуляция выглядит следующим образом: на сервер приходит POST-запрос с типом контента application/x-www-form-urlencoded, в теле запроса есть параметр PaReq. Параметр PaReq является XML-документом, сжатым при помощи алгоритма DEFLATE, затем закодированного в Base64 и URL-кодированного. Соответственно, реальные параметры приложения передаются в тегах и атрибутах этого XML-документа. Если WAF не может раскрыть такую «матрешку» и применить политики анализа к параметрам внутри XML (и/или валидировать его структуру), значит WAF по сути работает в режиме Fail Open. Другие примеры из этой же серии – это многочисленные XML в JSON и наоборот, конечно, не без помощи упаковки в BASE64.
  • Google Web Toolkit и другие протоколы, использующие собственную сериализацию (не JSON/XML/YAML/...). Вместо тысячи слов – один пример запроса:



    Соответственно, если WAF не может получить из запроса конечные значения параметров, с которыми оперирует защищаемое приложение, то WAF не работает. Отметим, что существует приличное количество способов сериализации бинарных объектов (а еще можно запилить свой собственный!).
  • Oracle Application Express (APEX). URL приложения в APEX выглядят примерно так:
    http://apex.app:8090/apex/f?p=30:180:3426793174520701::::P180_ARTICLE_ID:5024

    POST-запросы в URL-части – аналогично, а параметры передаются в теле (x-www-urlencoded), но названия вне зависимости от вызываемой операции – одинаковые: x01=val1&x02=val2… и т.п. Вот пример запроса:





Если в первых двух примерах разбор протокола требовался для определения значений входных параметров, то в приложениях такого типа WAF дополнительно должен понять, какая запрашивается операция или ресурс. Заметим, что никто не мешает разработчикам приложений на APEX в параметрах x01, x02,… передавать, например, XML/JSON, закодированный в base64, или, как на последнем снимке, сериализованные X-WWW-URLENCODED параметры.

WAF должен иметь гранулярность уровня операций защищаемого приложения

Приложения на APEX отлично иллюстрируют следующий тезис: WAF должен применять свои механизмы/политики/правила не с гранулярностью сущностей HTTP-протокола (секции URL, заголовки и их значения, имена параметров и их значения), а с гранулярностью функций приложения и их входных параметров.

Действительно, для приложения на APEX параметры x01, x02 и т.п. будут являться транспортом значений для всех его функций, но:

  • Инкапсуляция значений этих параметров может быть разной (см. значения x01 на снимках экрана выше).
  • Как следствие, типы, области значений и семантика этих параметров для каждой функции приложения будут тоже отличаться.

Получается, что от механизмов WAF мы хотим следующего:

  • Подсистема построения и применения позитивных моделей должна строить не одну общую позитивную модель на основе всех наблюдаемых значений параметра x01, а N моделей по числу функций приложения, принимающих этот параметр.
  • Подсистема сигнатурного анализа должна применять к параметру x01 не один и тот же набор сигнатур, а K наборов (К<N) в зависимости от пожеланий оператора по покрытию сигнатурами значений x01 для действий или их групп.
  • Если же оператор захочет настроить для параметра x01 какое-то дополнительное правило (например, подавление ложного срабатывания), то он должен иметь возможность выбирать scope работы этого правила не в терминах протокола HTTP (регулярное выражение над URL, к примеру), а опять же в терминах функций приложения, принимающих x01 (например, в функции регистрации применять, а в функции сброса пароля — нет).

Авторы данной статьи встречались с ситуацией, когда на WAF одного крупного вендора не работал User Tracking[1] на APEX-приложении как раз из-за того, что правила для разделения успешного login-действия, неуспешного login-действия, logout-действия и остальных действий невозможно было задать предоставляемыми выразительными средствами — это были регулярные выражения над полями HTTP-запроса.

Приведенные рассуждения справедливы, конечно, не только для APEX-приложений, но и для различных приложений со сложной маршрутизацией не на основе URL: XML-RPC, JSON-RPC, SOAP и т.п.

WAF должен иметь возможность задания политик на уровне операций и объектов защищаемого приложения

Не секрет, что основными способами обнаружения атак на веб-приложения являются синтаксический анализ для обнаружения синтаксических аномалий (соответствует injection-атакам) и статистический анализ для обнаружения аномалий, связанных со слишком большим количеством запросов (подбор паролей/токенов/OTP, дирбастинг, перечисление объектов приложения – например, существующих пользователей, умный DoS и так далее). Намного сложнее ловятся атаки, которые не вызывают синтаксических или статистических аномалий – типичным примером будет запрос чужих объектов (англ. Insecure Direct Object Reference). Такие атаки часто еще называют «логическими» или атаками на бизнес-логику.

Возникает справедливый вопрос – на каком уровне абстракции происходят аномалии при атаках такого типа?

Мы считаем, что аномалии, возникающие при атаках на бизнес-логику – это аномалии уровня операций и объектов защищаемого приложения, для чего требуется понимать сценарии использования приложения, жизненный цикл и принадлежность его объектов пользователям приложения, вычислять зависимости по данным и по объектам между отдельными шагами одного сценария использования или между различными сценариями использования, можно выявить аномалии, возникающие при атаках на уровне логики.

На наш взгляд, перспективой развития WAF’ов как инструментов мониторинга является именно понимание уровня предметной области приложений, работа на уровне операций и объектов, построение между ними зависимостей и, как следствие, обнаружение атак на логику сценариев в этой предметной области.

  1. Механизм User Tracking позволяет связать запросы, отправляемые пользователями приложения после аутентификации, с их логинами. Для конфигурации этого механизма инструменты обычно требуют ввести критерии успешного логина, неуспешного логина и критерии инвалидации сессии (тайм-аут по бездействию при наличии, новый вход под тем же пользователем, отправка logout-запроса по заданному URL и т.п.).

    Картинка до ката взята с сайта gamer.ru
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/331786/


Метки:  

Имитируем управление устройствами с помощью акторов

Вторник, 04 Июля 2017 г. 09:59 + в цитатник

Корни SObjectizer берут свое начало в теме автоматизированных систем управления технологическими процессами (АСУТП). Но использовали мы SObjectizer в далеких от АСУТП областях. Поэтому иногда возникает ностальгия из категории «эх, давно не брал в руки шашек...» Однажды из-за этого в составе SObjectizer появился один из самых объемных примеров — machine_control. Уж очень тогда захотелось «тряхнуть стариной», смоделировать задачку управления оборудованием на современном SObjectizer-е. Ну и под шумок запихнуть в пример разные вкусные фичи SObjectizer-а вроде фильтров доставки, шаблонных агентов и диспетчера с поддержкой приоритетов. Сегодня попробуем рассказать и показать, как это все работает.


The Engine Room - Steel Stacks, Bethlehem, PA
Photo by Mike Boening



Что мы моделируем?


Предположим, что мы имеем дело с какой-то машиной или со станком на производстве, у которого внутри есть двигатель. Ну, скажем, привод ленты конвейера. Или это насос, качающий воду. Не суть важно. Важно то, что когда двигатель работает, он нагревается. А когда он нагревается, его нужно охлаждать. Поэтому рядом с двигателем установлен охлаждающий вентилятор. Этот вентилятор нужно включить, если двигатель разогрелся свыше 70 градусов. Вентилятор следует выключить, если в результате охлаждения двигатель остыл до 50 градусов. Если же двигатель, не смотря на охлаждение, продолжает нагреваться и его температура достигает 95 градусов, то двигатель вовсе нужно выключить и подождать, пока он не остынет до 50 градусов.


Естественно, что нам бы хотелось видеть, как все это происходит в динамике. Нам нужно видеть работает ли сейчас двигатель, какова температура у двигателя, включен ли охлаждающий вентилятор. Для этого в реализации machine_control мы используем простую периодическую печать всей этой информации на консоль.


Ну и для того, чтобы нам было интереснее, сделаем работу не с одним двигателем, а с несколькими. Все они работают одинаковым образом, но их свойства отличаются. Какой-то из них нагревается быстрее, какой-то медленнее. Поэтому если наблюдать за работой, скажем, четырех двигателей, то будет казаться, что каждый из них живет своей жизнью.


Поехали!


Пара общих слов о выбранном решении


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


Как именно работает пример?


Есть несколько агентов-машин, есть специальный общий почтовый ящик. Агенты-машины время от времени посылают в этот общий почтовый ящик сообщения о своем текущем статусе.



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



Второй агент — это statuses_analyser. Он получает информацию о статусах для того, чтобы контролировать происходящее с машинами и определять моменты, когда какой-то из машин требуется внешнее воздействие. Когда воздействие требуется, агент statuses_analyser отсылает еще одно сообщение в этот же общий почтовый ящик. На это сообщения реагируют агенты machine_controller. Они уже решают какое именно управляющее воздействие должно быть применено к конкретной машине и отсылают соответствующее сообщение напрямую соответствующему агенту-машине.


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


Агент a_machine_t


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


Но у нас вымышленный пример, поэтому нам не нужно опрашивать никаких портов и считывать данные с реального оборудования. Нам нужно что-то изображающее из себя работающую машину. Для этого у нас есть агент a_machine_t, который:


  • периодически выдает во внешний мир информацию о своем состоянии, как то: работает ли двигатель, работает ли вентилятор, какова текущая температура двигателя;
  • принимает команды на включение/выключение двигателя и выполняет их;
  • принимает команды на включение/выключение вентилятора и выполняет их.

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


Посмотрим на то, как выглядит этот самый агент a_machine_t, но сперва введем несколько определений, которые нам понадобятся в реализации a_machine_t:


// Специальный тип для обозначения состояния двигателя.
enum class engine_state_t { on, off };
// Специальный тип для обозначения состояния вентилятора.
enum class cooler_state_t { on, off };
 
// Управляющие сообщения-сигналы для включения и выключения двигателя.
struct turn_engine_on : public so_5::signal_t {};
struct turn_engine_off : public so_5::signal_t {};
 
// Управляющие сообщения-сигналы для включения и выключения вентилятора.
struct turn_cooler_on : public so_5::signal_t {};
struct turn_cooler_off : public so_5::signal_t {};
 
// Описание текущего состояния машины.
struct machine_status
{
	// Уникальное название-идентификатор для машины.
	// Необходимо для того, чтобы отличать машины друг от друга.
	const std::string m_id;
	// Статус двигателя машины.
	const engine_state_t m_engine_status;
	// Статус вентилятора машины.
	const cooler_state_t m_cooler_status;
	// Текущая температура двигателя.
	const float m_engine_temperature;
};

Соответственно, наш агент a_machine_t будет получать управляющие команды в виде сообщений-сигналов turn_engine_on/turn_engine_off и turn_cooler_on/turn_cooler_off, а о своем состоянии будет извещать посредством отсылки сообщения machine_status.


Теперь уже можно перейти к рассмотрению самого агента a_machine_t. Начнем с самых потрохов:


class a_machine_t : public so_5::agent_t
{
	// Этот сигнал будет использоваться для периодического информирования
	// о текущем статусе машины.
	struct update_status : public so_5::signal_t {};
 
	// Агент-машина может находиться в двух состояниях:
	// состояние, когда двигатель включен,
	const state_t st_engine_on{ this, "on" };
	// состояние, когда двигатель выключен.
	const state_t st_engine_off{ this, "off" };
 
	// Уникальный идентификатор-название этой машины.
	const std::string m_id;
	// В этот почтовый ящик будет отсылаться сообщение machine_status.
	const so_5::mbox_t m_status_distrib_mbox;
 
	// Уникальные параметры конкретной машины:
	// начальная температура,
	const float m_initial_temperature;
	// шаг изменения температуры двигателя во время нагревания,
	const float m_engine_heating_step;
	// шаг изменения температуры двигателя во время охлаждения.
	const float m_cooler_impact_step;
 
	// Текущая температура двигателя.
	float m_engine_temperature;
 
	// Текущие состояния двигателя и вентилятора.
	engine_state_t m_engine_status = engine_state_t::off;
	cooler_state_t m_cooler_status = cooler_state_t::off;
 
	// ID таймера для периодического сообщения update_status.
	// В SO-5 для периодических сообщений ID таймера нужно сохранять
	// в течении всего времени работы, иначе произойдет автоматическая
	// отмена периодического сообщения.
	so_5::timer_id_t m_update_status_timer;

Агент a_machine_t представляет из себя очень простой конечный автомат с двумя состояниями: «двигатель включен» и «двигатель выключен». В каждом из них он реагирует на некоторые сообщения по-разному. Для того, чтобы представить агента в виде конечного автомата в SObjectizer нам и потребовались два отдельных атрибута st_engine_on и st_engine_off.


При своем старте агент инициирует периодическое сообщение update_status. Каждый раз, когда он получает это сообщение, агент пересчитывает значение m_engine_temperature с учетом того, работают ли сейчас двигатель и вентилятор или нет. После чего в почтовый ящик m_status_distrib_mbox отсылается сообщение machine_status с текущими показаниями.


Конструктор агента выглядит объемным из-за обилия начальных параметров, но на самом деле он тривиален, поэтому мы его рассматривать не будем.


А вот дальше уже интереснее. Во-первых, это специальный метод so_define_agent(), который используется для того, чтобы агент мог настроить себя для работы внутри SObjectizer. Нашему a_machine_t нужно перейти в свое начальное состояние и подписаться на нужные ему сообщения. Вот как это выглядит:


virtual void so_define_agent() override
{
	this >>= st_engine_off;
 
	st_engine_on
		.event< turn_engine_off >( &a_machine_t::evt_turn_engine_off )
		.event< turn_cooler_on >( &a_machine_t::evt_turn_cooler_on )
		.event< turn_cooler_off >( &a_machine_t::evt_turn_cooler_off )
		.event< update_status >( &a_machine_t::evt_update_status_when_engine_on );
	st_engine_off
		.event< turn_engine_on >( &a_machine_t::evt_turn_engine_on )
		.event< turn_cooler_on >( &a_machine_t::evt_turn_cooler_on )
		.event< turn_cooler_off >( &a_machine_t::evt_turn_cooler_off )
		.event< update_status >( &a_machine_t::evt_update_status_when_engine_off );
}

Можно обратить внимание, что на сигнал update_status агент в разных состояниях реагирует посредством разных обработчиков. Также можно увидеть, что в состоянии st_engine_on сигнал turn_engine_on игнорируется, поскольку нет смысла включать уже работающий двигатель. Аналогично и с turn_engine_off в состоянии st_engine_off.


По поводу метода so_define_agent() нельзя не сделать маленького лирического отступления: на первый взгляд этот метод выглядит избыточным и кажется, что без него можно было бы обойтись. Ведь можно все настройки агента выполнить прямо в конструкторе.


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


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


Следующий важный метод — это so_evt_start(). SObjectizer автоматически вызывает его у всех агентов, которые начинают работать внутри SObjectizer-а. Наш a_machine_t использует so_evt_start() для того, чтобы начать отсылку периодического сообщения update_status:


virtual void so_evt_start() override
{
	// Запускаем периодическое сообщение типа update_status и сохраняем
	// ID таймера, чтобы сообщение продолжало отсылаться до тех пор,
	// пока агент существует.
	m_update_status_timer = so_5::send_periodic< update_status >(
			// Указываем себя в качестве получателя сообщения.
			*this,
			// Нет задержки перед появлением сообщения в первый раз.
			std::chrono::milliseconds(0),
			// Далее сообщение будет отсылаться каждые 200ms.
			std::chrono::milliseconds(200) );
}

Далее идут обработчики событий агента a_machine_t. Обработчиком события называется метод, который SObjectizer вызовет когда агент получит соответствующее сообщение-инцидент. Соответствие между сообщением-инцидентом и обработчиком задается при подписке на сообщение. Так, подписка вида:


st_engine_on
	.event< turn_engine_off >( &a_machine_t::evt_turn_engine_off )

указывает SObjectizer-у, что когда агенту приходит сообщение типа turn_engine_off, то у агента нужно вызвать метод evt_turn_engine_off().


У агента a_machine_t есть четыре простых обработчика, пояснять работу которых нет смысла:


void evt_turn_engine_off()
{
	// Меняем текущее состояние агента.
	this >>= st_engine_off;
	// Обновляем соответствующий статус.
	m_engine_status = engine_state_t::off;
}
 
void evt_turn_engine_on()
{
	this >>= st_engine_on;
	m_engine_status = engine_state_t::on;
}
 
void evt_turn_cooler_off()
{
	// Состояние агента менять не нужно.
	// Поэтому просто обновляем соответствующий статус.
	m_cooler_status = cooler_state_t::off;
}
 
void evt_turn_cooler_on()
{
	m_cooler_status = cooler_state_t::on;
}

А вот по поводу реакций на периодическое сообщение update_status нужно будет дать несколько пояснений. Сперва посмотрим на реакцию на update_status когда двигатель работает:


void evt_update_status_when_engine_on()
{
	m_engine_temperature += m_engine_heating_step;
	if( cooler_state_t::on == m_cooler_status )
		m_engine_temperature -= m_cooler_impact_step;
 
	distribute_status();
}

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


Когда сигнал update_status обрабатывается в состояние st_engine_off, т.е. когда двигатель отключен, нам нужно только учесть влияние охлаждение, если оно сейчас включено:


void evt_update_status_when_engine_off()
{
	if( cooler_state_t::on == m_cooler_status )
	{
		m_engine_temperature -= m_cooler_impact_step;
		// В данной имитации мы не позволяем температуре двигателя
		// опуститься слишком низко.
		if( m_engine_temperature < m_initial_temperature )
			m_engine_temperature = m_initial_temperature;
	}
 
	distribute_status();
}

Ну а вспомогательный метод distribute_status() имеет совсем тривиальную реализацию, т.к. его единственная задача — это отсылка сообщения machine_status в специально предназначенный для этого почтовый ящик:


void distribute_status()
{
	// Внутри send-а создается экземпляр типа machine_status,
	// который инициализируется параметрами из вызова send.
	// После чего этот экземпляр будет отослан в почтовый ящик
	// m_status_distrib_mbox.
	so_5::send< machine_status >(
			// Куда направляется сообщение.
			m_status_distrib_mbox,
			// Все остальные аргументы используются для конструирования
			// экземпляра сообщения machine_status.
			m_id,
			m_engine_status,
			m_cooler_status,
			m_engine_temperature );
}

Куда же улетают сообщения machine_status?


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


Это потому, что декомпозиция :)


Когда мы проектируем какую-то систему на основе объектно-ориентированного подхода, то мы стремимся к тому, чтобы каждый объект отвечал за свою задачу и нужный нам эффект достигался бы за счет их комбинирования. Точно так же происходит и при проектировании системы на базе акторов (агентов): пусть каждый актор отвечает за свою задачу, а мы сформируем решение за счет их комбинации.


Поэтому агент a_machine_t решает только одну задачу: реализует интерфейс с оборудованием (в нашем примере он имитирует этот интерфейс). В реальной задаче a_machine_t бы занимался выставлением/сбросом битиков, чтением-записью байтиков в какой-то коммуникационный порт с проверками успешности операций ввода-вывода, контролем скорости передачи данных, тайм-аутов и пр. низкоуровневыми вещами.


Так что задача a_machine_t — это получить от устройства осмысленную информацию, пригодную для дальнейшей обработки, отдать эту информацию кому-то наверх, принять сверху команду для устройства, преобразовать эту команду в последовательность понимаемых устройством воздействий. Что a_machine_t в меру ограничений конкретной имитации и делает.


Агент a_total_status_dashboard_t


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


Для выполнения своей работы a_total_status_dashboard_t подписывается на два сообщения:


virtual void so_define_agent() override
{
	so_subscribe( m_status_distrib_mbox )
		.event( &a_total_status_dashboard_t::evt_machine_status );
 
	so_subscribe_self().event< show_dashboard >(
			&a_total_status_dashboard_t::evt_show_dashboard );
}

Первое сообщение, machine_status, агент ожидает из специального почтового ящика, в который агенты a_machine_t отсылают свои machine_status-сообщения. А второе сообщение, show_dashboard, агент a_total_status_dashboard_t отсылает себе сам в виде периодического сообщения:


virtual void so_evt_start() override
{
	// Запускаем периодический сигнал для отображения
	// текущей информации на консоль.
	const auto period = std::chrono::milliseconds( 1500 );
	m_show_timer = so_5::send_periodic< show_dashboard >( *this,
			period, period );
}

Здесь a_total_status_dashboard_t использует тот же самый подход, как и агент a_machine_t — инициирует периодическое сообщение в своем методе so_evt_start(), который SObjectizer автоматически вызывает в самом начале работы a_total_status_dashboard_t.


Обработка machine_status очень простая: нужно всего лишь сохранить очередную порцию информации в ассоциативный контейнер:


void evt_machine_status( const machine_status & status )
{
	m_machine_statuses[ status.m_id ] = one_machine_status_t{
			status.m_engine_status, status.m_cooler_status,
			status.m_engine_temperature
		};
}

Да и обработчик show_dashboard не содержит ничего сложного: всего лишь итерация по содержимому ассоциативного контейнера с печатью на стандартный поток вывода:


void evt_show_dashboard()
{
	auto old_precision = std::cout.precision( 5 );
	std::cout << "=== The current status ===" << std::endl;
 
	for( const auto & m : m_machine_statuses )
	{
		show_one_status( m );
	}
 
	std::cout << "==========================" << std::endl;
	std::cout.precision( old_precision );
}

Поскольку агент a_total_status_dashboard_t не представляет большого интереса и его реализация весьма проста, то углубляться дальше в его реализацию мы не будем. Кому интересно, полный исходный код a_total_status_dashboard_t можной увидеть здесь.


Агенты a_statuses_analyser_t и a_machine_controller_t


В данной реализации примера machine_control анализом информации от агентов a_machine_t и выдачей управляющих команд агентам-машинам занимается связка из нескольких агентов.


Во-первых, это агент a_statuses_analyser_t, который получает сообщения machine_status, анализирует их и определяет, что конкретный a_machine_t нуждается в каком-то воздействии.


Во-вторых, есть группа агентов-шаблонов типа a_machine_controller_t, которые реагируют на сигналы от a_statuses_analyser_t и выдают то или иное воздействие на конкретную машину. Так, один агент a_machine_controlle_t реагирует на ситуацию, когда следует включить охлаждающий вентилятор и отсылает сообщение turn_cooler_on соответствующему агенту a_machine_t. Другой агент a_machine_controller_t реагирует на ситуацию, когда следует отключить двигатель и отсылает сообщение turn_engine_off. И т.д.


Вообще говоря, такое деление на a_statuses_analyser_t и a_machine_controller_t — это явное усложнение нашего примера. Можно было бы вполне обойтись всего одним агентом a_statuses_analyser_t, который бы сам мог и информацию анализировать, и управляющие команды отсылать. Хотя, скорее всего, агент a_statuses_analyser_t сильно бы увеличился в объеме при этом.


Изначально в machaine_control хотелось показать разнообразные фичи SObjectizer-а, в частности, использование агентов-шаблонов и приоритетов агентов, поэтому мы пошли на разделение логики между a_statuses_analyser_t и a_machine_controller_t.


Итак, суть взаимодействия a_machine_t, a_statuses_analyser_t и a_machine_controller_t в следующем:


  • a_machine_t периодически рассылает информацию о себе в виде сообщения machine_status;
  • a_statuses_analyser_t собирает информацию из machine_status и определяет, нужно ли какое-то воздействие на конкретный a_machine_t. Если воздействие нужно, то a_status_analyser_t отсылает сообщение machine_needs_attention с информацией о том, какой машине какое воздействие требуется;
  • агенты a_machine_controller_t реагируют на сообщения machine_needs_attention и генерируют сообщения turn_engine_on/turn_engine_off и/или turn_cooler_on/turn_cooler_off для конкретного агента a_machine_t.


Сообщение machine_needs_attention имеет следующий вид:


// Перечисление, которое определяет какую именно ситуацию диагностировали.
enum class attention_t 
{
	none,
	engine_cooling_done,
	engine_cooling_needed,
	engine_overheat_detected
};
 
// Сообщение о том, что с конкретной машиной что-то произошло.
struct machine_needs_attention
{
	// Уникальный идентификатор-название машины.
	const std::string m_id;
	// Что именно диагностировали.
	const attention_t m_attention;
	// Текущий статус двигателя у машины (включен/выключен).
	const engine_state_t m_engine_status;
	// Текущий статус охлаждающего вентилятора (включен/выключен).
	const cooler_state_t m_cooler_status;
};

Агент a_statuses_analyser_t хранит прошлую информацию о каждой машине и сравнивает ее с новой информацией, поступающей с сообщением machine_status. Если обнаруживается, что двигатель требует охлаждения или что двигатель перегрелся, или что двигатель достиг безопасной температуры, то a_statuses_analyser_t генерирует сообщение machine_needs_attention. Это сообщение подхватывается соответствующим a_machine_controller_t и нужному агенту a_machine_t будет отослана нужная команда.


Подробнее об агенте a_statuses_analyser_t


Потроха у агента a_statuses_analyser_t довольно объемные. Но большая их часть относится к тому, чтобы хранить и анализировать текущее состояние агентов-машин. Разбирать эту часть в деталях мы не будем (если возникнут какие-то вопросы, то я на них отвечу в комментариях), просто поясним в двух словах:


  • агент a_statuses_analyser_t хранит ассоциативный контейнер с информацией о последнем известном статусе каждого агента-машины. Ключем в этом контейнере является уникальное имя-идентификатор машины;
  • при получении очередного сообщения machine_status агент сверяет свежую информацию с тем, что у него было сохранено ранее. Если обнаруживаются ситуации, когда требуется воздействие на агента-машину, то генерируется сообщение machine_needs_attention;
  • после чего агент a_statuses_analyser_t обновляет данные в своем ассоциативном контейнере.

В коде все это занимает приличное количество строк, но ничего сложного там нет.


А вот та часть агента a_statuses_analyser_t, которая относится к взаимодействию с SObjectizer-ом, вообще минимальна: всего лишь подписка на одно-единственное сообщение в so_define_agent() и одно-единственное событие для этого сообщения:


virtual void so_define_agent() override
{
	so_subscribe( m_status_distrib_mbox ).event(
			&a_statuses_analyzer_t::evt_machine_status );
}
void evt_machine_status( const machine_status & status )
{
	auto it = m_last_infos.find( status.m_id );
	if( it == m_last_infos.end() )
		// Об этой машине мы еще информацию не получали.
		// Добавим информацию в наше хранилище.
		it = m_last_infos.insert( last_info_map_t::value_type {
				status.m_id,
				last_machine_info_t {
					attention_t::none,
					status.m_engine_temperature
				} } ).first;
 
	handle_new_status( status, it->second );
}

Где метод handle_new_status(), который вызывается внутри evt_machine_status(), это уже часть той прикладной логики контроля за статусом агента-машины, о которой мы коротко рассказали ранее.


Подробнее об агентах a_machine_controller_t


В чем вообще смысл a_machine_controller_t?


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


Если бы мы разместили всю эту логику внутри a_statuses_analyser_t, то его код бы разросся и понимать его было бы труднее. Вместо этого мы просто заставили a_statuses_analyser_t объявлять на что именно следует обратить внимание в работе агента-машины. А уже реагировать на это объявление будут агенты a_machine_controller_t. И каждый a_machine_controller_t сам определяет, нужно ли ему реагировать или нет. Если нужно, то a_machine_contoller_t получает сообщение machine_needs_attention и инициирует соответствующую команду агенту-машине. Если не нужно, то a_machine_controller_t сообщение machine_needs_attention просто игнорирует.


Агент a_machine_controller_t


Код агента-шаблона a_machine_controller_t небольшой, поэтому для простоты работы с ним приведем весь код этого агента полностью:


template< class LOGIC >
class a_machine_controller_t : public so_5::agent_t
{
public :
	a_machine_controller_t(
		context_t ctx,
		so_5::priority_t priority,
		so_5::mbox_t status_distrib_mbox,
		const machine_dictionary_t & machines )
		:	so_5::agent_t( ctx + priority )
		,	m_status_distrib_mbox( std::move( status_distrib_mbox ) )
		,	m_machines( machines )
		,	m_logic()
	{}
 
	virtual void so_define_agent() override
	{
		so_set_delivery_filter( m_status_distrib_mbox,
			[this]( const machine_needs_attention & msg ) {
				return m_logic.filter( msg );
			} );
 
		so_subscribe( m_status_distrib_mbox )
			.event( [this]( const machine_needs_attention & evt ) {
					m_logic.action( m_machines, evt );
				} );
	}
 
private :
	const so_5::mbox_t m_status_distrib_mbox;
 
	const machine_dictionary_t & m_machines;
 
	const LOGIC m_logic;
};

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


struct LOGIC
{
	bool filter( const machine_needs_attention & msg ) const;
 
	void action(
		const machine_dictionary_t & machines,
		const machine_needs_attention & evt ) const;
};

Если бы в C++11 были концепты, то можно было бы объявить соответствующий концепт для того, чтобы проще было определять, какой тип может быть параметром для шаблона a_machine_controller_t, а какой — нет. Но, т.к. в C++11 концепты не завезли, то приходится полагаться на утиную типизацию.


Агент a_machine_controller_t создает у себя внутри экземпляр типа LOGIC и делегирует все свои действия этому экземпляру. И этот экземпляр есть не что иное, как контроллер, который выдает управляющие воздействия агенту-машине.


Действий же у контроллера всего два:


Во-первых, нужно отфильтровать сообщения, которые конкретному контроллеру не интересны. Для этого a_machine_controller_t назначает фильтр доставки сообщений:


so_set_delivery_filter( m_status_distrib_mbox,
	[this]( const machine_needs_attention & msg ) {
		return m_logic.filter( msg );
	} );

Фильтр доставки — это специальный механизм SObjectizer-а. Он нужен в ситуациях, когда агент подписывается на некоторое сообщение типа T из почтового ящика M, но хочет получать не все сообщения типа T, а только те, внутри которых содержится интересная агенту информация.


В этих случаях агент посредством своего метода so_set_delivery_filter() задает фильтр, лямбда-функцию, которую почтовый ящик M будет автоматически вызывать каждый раз, когда в него приходит сообщение типа T. Если фильтр возвращает true, значит экземпляр сообщения агенту интересен и доставка сообщения до агента выполняется. Если же фильтр возвращает false, то сообщение этому агенту не доставляется, тем самым агент не отвлекается на то, что ему не интересно.



Наглядная схема работы фильтра доставки. Агент Subscriber-1 не использует фильтр доставки и поэтому получает все сообщения из mbox-а. Тогда как агент Subscriber-2 установил фильтр доставки и получает только те сообщения, которые проходят через фильтр.


Вот метод LOGIC::filter() и должен играть роль фильтра доставки, он пропускает только те экземпляры machine_needs_attention, которые интересны контроллеру.


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


За это отвечает метод LOGIC::action(). Агент a_machine_controller_t вызывает этот метод в своем обработчике machine_needs_attention:


so_subscribe( m_status_distrib_mbox )
	.event( [this]( const machine_needs_attention & evt ) {
		m_logic.action( m_machines, evt );
	} );

Примеры нескольких controller-ов


Ну а для того, чтобы было понятнее, как же именно работают a_machine_controller_t, приведем пару примеров контроллеров. Первый очень простой, он отвечает за выдачу команды turn_engine_off, если обнаружили перегрев двигателя:


struct engine_stopper_t
{
	bool filter( const machine_needs_attention & msg ) const
	{
		return msg.m_attention == attention_t::engine_overheat_detected;
	}
 
	void action(
		const machine_dictionary_t & machines,
		const machine_needs_attention & evt ) const
	{
		so_5::send< turn_engine_off >( machines.find_mbox( evt.m_id ) );
	}
};

Т.е. фильтруются только те сообщения machine_needs_attention, которые говорят о перегреве двигателя. Когда же такое сообщение поступает, то соответствующему агенту-машине отсылается сигнал turn_engine_off.


А вот этот контроллер уже посложнее:


struct cooler_starter_t
{
	bool filter( const machine_needs_attention & msg ) const
	{
		return (msg.m_attention == attention_t::engine_overheat_detected ||
				msg.m_attention == attention_t::engine_cooling_needed) &&
				msg.m_cooler_status == cooler_state_t::off;
	}
 
	void action(
		const machine_dictionary_t & machines,
		const machine_needs_attention & evt ) const
	{
		so_5::send< turn_cooler_on >( machines.find_mbox( evt.m_id ) );
	}
};

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


Всего таких контроллеров в примере четыре:


  • engine_stopper_t отвечает за останов двигателя;
  • engine_starter_t отвечает за запуск двигателя;
  • cooler_starter_t отвечает за запуск вентилятора;
  • cooler_stopper_t отвечает за останов вентилятора.

Это означает, что и агентов a_machine_controller_t будет четыре — по одному на каждый тип контроллера.


Приоритеты для агентов a_machine_controller_t


При работе примера machine_control может случиться так, что один и тот же экземпляр machine_needs_attention будет получен несколькими контроллерами. Например, если в machine_needs_attention передается attention_t::engine_overheat_detected и cooler_status_t::off, то такое сообщение будет получено двумя контроллерами: и контроллером engine_stopper_t, и контроллером cooler_starter_t.


И тут нам важно, в каком порядке эти контроллеры обработают сообщение. Логично же, чтобы в ситуации engine_overheat_detected сперва была дана команда turn_engine_off, а уже затем команда turn_cooler_on. Ведь выполнение команды будет занимать какое-то время, скажем несколько секунд. Если сперва потратить эти секунды на включение вентилятора, то двигатель перегреется еще больше. Поэтому лучше сразу выключить двигатель, а потом уже тратить время на все остальное.


Каким же образом в SObjectizer-е можно управлять порядком обработки сообщений?


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


В SObjectizer есть такой тип диспетчера: он запускает всех привязанных к нему агентов на одной общей рабочей нити, при этом агенты с более высоким приоритетом обрабатывают свои сообщения перед агентами с более низкими приоритетами. Это диспетчер so_5::disp::prio_one_thread::strictly_ordered. Именно к нему привязываются агенты a_machine_controller_t в данном примере.


При этом приоритеты для агентов-контроллеров распределены следующим образом:


  • самый высокий приоритет у агента с контроллером типа engine_stopper_t, т.к. команды на выключение двигателя должны выполняться в первую очередь;
  • далее следует агент с контроллером типа cooler_starter_t, поскольку включение охлаждающего вентилятора — это вторая по значимости команда;
  • далее идет агент с контроллером типа engine_starter_t;
  • ну и самый низкий приоритет у агента с контроллером типа cooler_stopper_t.

Следы того, как задаются приоритеты для агентов a_machine_controller_t можно найти в конструкторе:


a_machine_controller_t(
	...,
	so_5::priority_t priority,
	... )
	:	so_5::agent_t( ctx + priority )
	,	...
{}

И при непосредственном создании таких агентов:


coop.make_agent_with_binder< a_machine_controller_t< engine_stopper_t > >(
		disp->binder(),
		so_5::prio::p4,
		status_distrib_mbox,
		machines );
 
coop.make_agent_with_binder< a_machine_controller_t< cooler_starter_t > >(
		disp->binder(),
		so_5::prio::p3,
		status_distrib_mbox,
		machines );

Здесь значения so_5::prio::p4 и so_5::prio::p3 — это и есть приоритеты агентов.


Агент-стартер


Можно было обратить внимание, что агенты a_machine_t начинают свою работу в состоянии st_engine_off, т.е. двигатель считается выключенным. Но запустив пример можно увидеть, что у машин двигатель сразу же включается. Как же так происходит, если ни a_machine_t, ни a_machine_controller_t изначально никаких команд на включение двигателя не выдают?


Для того, чтобы у агентов-машин заработал двигатель используется отдельный агент-стартер, единственной задачей которого является выдача команды turn_engine_on каждому агенту-машине.


Этот агент настолько прост, что для него мы даже не делаем отдельный C++ класс, как это происходило у нас с a_machine_t, a_total_status_dashboard_t, a_statuses_analyser_t и a_machine_controller_t. Вместо этого мы используем такую штуку, как ad-hoc агент. Т.е. агент, который создается из набора заданных пользователем лямбда-функций. В примере machine_control это делается следующим образом:


coop.define_agent().on_start( [&dict] {
		dict.for_each(
			[]( const std::string &, const so_5::mbox_t & mbox ) {
				so_5::send< turn_engine_on >( mbox );
			} );
	} );

Метод define_agent() создает пустого ad-hoc агента, которого пользователь может наполнить нужной функциональностью. Здесь мы задаем всего лишь реакцию на начало работы ad-hoc агента внутри SObjectizer-а: при старте всем агентам просто отсылается turn_engine_on.


Распределение агентов по диспетчерам


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


Каждому агенту нужно обрабатывать доставленные ему сообщения. Делать это нужно на контексте какой-то рабочей нити. В SObjectizer рабочую нить агентам предоставляют диспетчеры. Поэтому программист при создании своего агента должен привязать агента к тому диспетчеру, который обеспечит агенту нужный режим работы.


В примере machine_control используются несколько диспетчеров, благодаря чему происходит следующее распределение агентов по рабочим нитям:


  • все агенты-машины работают на одной общей рабочей нити, для чего эти агенты привязываются к отдельному диспетчеру типа one_thread;
  • агент a_total_status_dashboard_t работает на собственной рабочей нити (привязан к отдельному диспетчеру типа one_thread);
  • агент a_statuses_analyser_t работает на собственной рабочей нити (привязан к отдельному диспетчеру типа one_thread);
  • все агенты a_machine_controller_t работают на одной общей рабочей нити отдельного диспетчера prio_one_thread::strictly_ordered, который обслуживает события агентов с учетом приоритетов;
  • агент-стартер отрабатывает свое единственное событие на диспетчере по умолчанию.


Что еще осталось «за кадром»?


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


Этот словарь используется для того, чтобы по имени агента-машины (которое присутствует в сообщениях machine_status и machine_needs_attention) получить доступ к почтовому ящику соответствующего агента. Ведь сообщения в SObjectizer отсылаются в почтовые ящики, поэтому, для того, чтобы отослать turn_engine_on, нужно по имени получить почтовый ящик. Что и делается посредством machine_dictionary.


Ну а что же самое важное в этом примере?


Теперь же, как в любом уважающем себя произведении, вслед за долгим прологом должна последовать основная часть, никакого отношения к прологу не имеющая.


Если читатель подумал, что статья была написана для того, чтобы показать, как можно разрабатывать агентов, похожих на a_machine_t или a_machine_controller_t, то он оказался не совсем прав.


Демонстрация конкретных фич SObjectizer-а, вроде привязки агентов к разным диспетчерам или использование ad-hoc агентов для простых действий — это лишь одна из задач данной статьи. Мы надеемся, что знакомство с тем, как может выглядеть более-менее объемный код на SObjectizer, поможет читателям лучше понять, нравится ли им то, что они видят или нет, возникнет ли у них желание разрабатывать свои программы в таком стили или же лучше пойти «другим путем». В конце-концов, примеры чуть посложнее, чем ставший классическим и совершенно бесполезным ping-pong, дают намного лучшее представление о том, как будет выглядеть продакшен код. И захочется ли связываться после подобного знакомства с таким фреймворком, как SObjectizer.


Однако, есть у данного примера и другая задача, не менее важная. Эта задача состоит в том, чтобы показать, как посредством Actor Model и Publish/Subscribe организовать взаимодействие между независимыми сущностями в программе.


Сделаем акцент на взаимодействии


Ключевой момент в рассмотренном примере machine_control в том, что взаимодействующие сущности (т.е. агенты) не имеют никакого понятия друг о друге и о взаимосвязях между собой.


Действительно, агент a_machine_t просто отсылает сообщение machine_status в некоторый почтовый ящик и понятия не имеет, кто стоит за этим ящиком, куда реально попадает сообщение и что с этим сообщением происходит дальше. Просто агент a_machine_t выполняет свою работу (имитирует периодический опрос оборудования) и выдает результат своей работы во внешний мир. Все, больше его ничего не интересует.


Даже когда к агенту a_machine_t приходит внешняя команда (вроде turn_engine_on), он не представляет, кто именно эту команду ему прислал. От a_machine_t требуется лишь выполнить поступившую команду, если в этом есть смысл. А уж из каких соображений эта команда возникла, через что она прошла по дороге — все это для a_machine_t не имеет никакого значения.


Кстати говоря, когда кто-то выдает команду вроде turn_engine_on конкретному агенту a_machine_t, то это классическое взаимодействие в рамках Actor Model, где акторы общаются в режиме 1:1.


Но вот в случае с сообщениями machine_status и machine_needs_attention мы уже оказываемся за рамками Actor Model, т.к. у нас образуется взаимодействие 1:N. И тут мы пользуемся моделью Publish/Subscribe. Специальный почтовый ящик, куда агенты отсылают сообщения machine_status и machine_needs_attention, играет роль брокера. Отсылка сообщения в этот ящик — это просто-напросто операция Publish. А для получения сообщения из ящика необходимо выполнить операцию Subscribe, что, собственно, агенты a_total_status_dashboard_t, a_statuses_analyser_t и a_machine_controller_t и делают в своих so_define_agent().


И тут можно увидеть еще одну важную штуку, которую дает разработчику SObjectizer: возможность использовать один и тот же экземпляр сообщения для совершенно разных целей. Происходит это как раз благодаря тому, что в SObjectizer есть multi-producer/multi-consumer почтовые ящики, через которые и происходит общение агентов в режиме 1:N.


Действительно, сообщение machine_status, например, получают и обрабатывают два разных агента, решающих совершенно разные задачи и ничего не знающие друг о друге. Агент a_total_status_dashboard_t использует machine_status для периодического отображения хода работы примера на консоль. Тогда как a_statuses_analyser_t использует machine_status для контроля за агентами a_machine_t.


В этом заложена большая гибкость. Мы совершенно спокойно можем добавить еще одного агента, который мог бы собирать информацию о температуре двигателей для построения графиков изменения температуры. И наличие этого агента никак не повлияет на работу a_total_status_dashboard_t и a_statuses_analyser_t. Или же мы можем заменить агента a_total_status_dashboard_t на какого-нибудь a_gui_status_dashboard_t, который будет отображать ход работы примера не на std::cout, а в графическое окно. И это, опять же, никак не повлияет на других агентов приложения, которые работают с сообщениями machine_status.


Так что разработка приложений на SObjectizer — это все про создание агентов, каждый из которых выполняет свою независимую часть работы, и про каналы связи между агентами. А каналы связи в SObjectizer представляют из себя почтовые ящики и сообщения, посредством которых информация распространяется (хотя есть и CSP-шные каналы).


И как раз основная сложность разработки приложений на SObjectizer состоит в том, чтобы выделить в предметной области те сущности, которые в программе могут быть выражены в виде агентов. А также в выделении и формировании каналов связи (т.е. наборов сообщений и почтовых ящиков), посредством которых агенты будут взаимодействовать друг с другом.


Ну а уже внешний вид самих агентов, их привязка к диспетчерам и пр. технические детали — это всего лишь детали. Но детали, которые оказывают важное влияние на привлекательность конкретного фреймворка. Ну вот не понравится кому-то, что для объявления сообщения-сигнала нужно сделать структуру, отнаследованную от типа с именем so_5::signal_t, и ничего уже не поделать. Хотя суть вовсе не в том, нужно ли наследоваться от чего-то или что наследоваться нужно от типа, название которого не отвечает чьим-то эстетическим представлениям. А в том, чтобы выделить в предметной области сущности, для какого-то взаимодействия между которыми вот такое сообщение-сигнал будет необходимо.


Вместо заключения


В финале статьи хочется поблагодарить самых настойчивых читателей за терпение, прекрасно понимаем, что дочитать до заключения было непросто.


Заодно хотим похвастаться тем, что для SObjectizer-а появился сопутствующий проект so_5_extra, который будет содержать дополнения и расширения для SObjectizer. Смысл в том, чтобы в SObjectizer включать только самую базовую функциональность, без которой ну никак не обойтись, и которая будет нужна большинству пользователей. Тогда как so_5_extra может содержать в том числе и экзотические вещи, необходимые лишь узкому кругу пользователей. Так что если вам чего-то не хватает в SObjectizer, то скажите чего именно. Если мы не увидим смысла добавления этого в SObjectizer, то место вполне может найтись в so_5_extra.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332166/


Метки:  

Как использовать implicit'ы в Scala и сохранить рассудок

Вторник, 04 Июля 2017 г. 09:31 + в цитатник

image


Scala богата выразительными средствами, за что ее и не любят опытные программисты на классических ООП-языках. Неявные параметры и преобразования — одна из самых спорных фич языка. Слово "неявные", уже как-бы намекает на что-то неочевидное и сбивающее с толку. Тем не менее, если с ними подружиться, implicit'ы открывают широкие возможности: от сокращения объема кода до возможности многие проверки делать в compile-time.


Хочу поделиться своим опытом по работе с ними и рассказать о том, о чем пока умалчивает официальная документация и блоги разработчиков. Если вы уже знакомы со Scala, пробовали использовать неявные параметры, но все еще испытываете некоторые сложности при работе с ними, либо хотя-бы о них слышали, то этот пост может оказаться вам интересен.



Содержание:



Ключевое слово implicit имеет отношение к трем понятиям в Scala: неявные параметры, неявные преобразования и неявные классы.


Неявные параметры


Неявные параметры — это параметры, которые могут быть автоматически переданы в функцию из контекста ее вызова. Для этого в нем должны быть однозначно определены и помечены ключевым словом implicit переменные соответствующих типов.


def printContext(implicit ctx: Context) = println(ctx.name)

implicit val ctx = Context("Hello world")

printContext

Выведет:


Hello world

В методе printContext мы неявно получаем переменную типа Context и печатаем содержимое ее поля name. Пока не страшно.


Механизм разрешения неявных параметров поддерживает обобщенные типы.


case class Context[T](message: String)

def printContextAwared[T](x: T)(implicit ctx: Context[T]) = println(s"${ctx.message}: $x")

implicit val ctxInt = Context[Int]("This is Integer")
implicit val ctxStr = Context[String]("This is String")

printContextAwared(1)
printContextAwared("string")

Выведет:


This is Integer: 1
This is String: string

Этот код эквивалентен тому, как если бы мы явно передавали в метод printContextAwared параметры ctxInt в первом случае и ctxString во втором.


printContextAwared(1)(ctxInt)
printContextAwared("string")(ctxStr)

Что интересно, неявные параметры не обязательно должны быть полями, они могут быть методами.


implicit def dateTime: LocalDateTime = LocalDateTime.now()

def printCurrentDateTime(implicit dt: LocalDateTime) = println(dt.toString)

printCurrentDateTime
Thread.sleep(1000)
printCurrentDateTime

Выведет:


2017-05-27T16:30:49.332
2017-05-27T16:30:50.476

Более того, неявные параметры-функции могут, в свою очередь, принимать неявные параметры.


implicit def dateTime(implicit zone: ZoneId): ZonedDateTime = ZonedDateTime.now(zone)

def printCurrentDateTime(implicit dt: ZonedDateTime) = println(dt.toString)

implicit val utc = ZoneOffset.UTC

printCurrentDateTime

Выведет:


2017-05-28T07:07:27.322Z

Неявные преобразования


Неявные преобразования позволяют автоматически преобразовывать значения одного типа к другому.
Чтобы задать неявное преобразование вам нужно определить функцию от одного явного аргумента и пометить ее ключевым словом implicit.


case class A(i: Int)
case class B(i: Int)

implicit def aToB(a: A): B = B(a.i)

val a = A(1)
val b: B = a
println(b)

Выведет:


B(1)

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


case class A(i: Int)
case class B(i: Int)
case class PrintContext[T](t: String)

implicit def aToB(a: A): B = B(a.i)
implicit val cContext: PrintContext[B] = PrintContext("The value of type B is")

def printContextAwared[T](t: T)(implicit ctx: PrintContext[T]): Unit = println(s"${ctx.t}: $t")

val a = A(1)
printContextAwared[B](a)

Ограничения
Scala не допускает применение нескольких неявных преобразований подряд, таким образом код:


case class A(i: Int)
case class B(i: Int)
case class C(i: Int)

implicit def aToB(a: A): B = B(a.i)
implicit def bToC(b: B): C = C(b.i)

val a = A(1)
val c: C = a

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


case class A(i: Int)
case class B(i: Int)
case class C(i: Int)

implicit def aToB(a: A): B = B(a.i)
implicit def bToC[T](t: T)(implicit tToB: T => B): C = C(t.i)

val a = A(1)
val c: C = a

Стоит заметить, что если функция принимает значение неявно, то в ее теле оно будет видимо как неявное значение или преобразование. В предыдущем примере для объявления метода bToC, tToB является неявным параметром и при этом внутри метода работает уже как неявное преобразование.


Неявные классы


Ключевое слово implicit перед объявлением класса — это более компактная форма записи неявного преобразования значения аргумента конструктора к данному классу.


implicit class ReachInt(self: Int) {
  def fib: Int =
    self match {
      case 0 | 1 => 1
      case i => (i - 1).fib + (i - 2).fib
    }
}

println(5.fib)

Выведет:


5

Может показаться, что неявные классы это всего лишь способ примешивания функционала к классу, но на самом это понятие несколько шире.


sealed trait Animal
case object Dog extends Animal
case object Bear extends Animal
case object Cow extends Animal

case class Habitat[A <: Animal](name: String)

implicit val dogHabitat = Habitat[Dog.type]("House")
implicit val bearHabitat = Habitat[Bear.type]("Forest")

implicit class AnimalOps[A <: Animal](animal: A) {
  def getHabitat(implicit habitat: Habitat[A]): Habitat[A] = habitat
}

println(Dog.getHabitat)
println(Bear.getHabitat)
//Не скомпилируется:
//println(Cow.getHabitat)

Выведет:


Habitat(House)
Habitat(Forest)

Здесь в неявном классе AnimalOps мы объявляем что тип значения, к которому он будет применен, будет виден нам как A, затем в методе getHabitat мы требуем неявный параметр Habitat[A]. При его отсутствии, как в строчке с Cow, мы получим ошибку компиляции.


Не прибегая к помощи неявных классов, достичь такого же эффекта нам бы мог помочь F-bounded polymorphism:


sealed trait Animal[A <: Animal[A]] { self: A =>
  def getHabitat(implicit habitat: Habitat[A]): Habitat[A] = habitat
}

trait Dog extends Animal[Dog]
trait Bear extends Animal[Bear]
trait Cow extends Animal[Cow]

case object Dog extends Dog
case object Bear extends Bear
case object Cow extends Cow

case class Habitat[A <: Animal[A]](name: String)

implicit val dogHabitat = Habitat[Dog]("House")
implicit val bearHabitat = Habitat[Bear]("Forest")

println(Dog.getHabitat)
println(Bear.getHabitat)

Как видно, в этом случае у типа Animal существенно усложнилось объявление, появился дополнительный рекурсивный параметр A, который играет исключительно служебную роль. Это сбивает с толку.


Цепочки неявных параметров


Этот вопрос рассмотрен в официальном FAQ: http://docs.scala-lang.org/tutorials/FAQ/chaining-implicits.html.
Как я уже говорил в разделе про неявные преобразования, компилятор не умеет рекурсивно применять неявные преобразования. Тем не менее, он поддерживает рекурсивное разрешение неявных параметров.


Пример ниже добавляет для тех типов, для которых неявно определены соответствующие тайп-классы, метод describe, который будет возвращать их описание на неком подобии человеческого языка (как мы знаем, в runtime в JVM определить точный тип невозможно, так что мы его определяем в compile-time):


sealed trait Description[T] {
  def name: String
}

case class ContainerDescr[P, M[_]](name: String)
                                  (implicit childDescr: Description[P]) extends Description[M[P]] {
  override def toString: String = s"$name of $childDescr"
}

case class AtomDescr[P](name: String) extends Description[P] {
  override def toString: String = name
}

implicit class Describable[T](value: T)(implicit descr: Description[T]) {
  def describe: String = descr.toString
}

implicit def listDescr[P](implicit childDescr: Description[P]): Description[List[P]] =
  ContainerDescr[P, List]("List")

implicit def arrayDescr[P](implicit childDescr: Description[P]): Description[Array[P]] =
  ContainerDescr[P, Array]("Array")

implicit def seqDescr[P](implicit childDescr: Description[P]): Description[Seq[P]] =
  ContainerDescr[P, Seq]("Sequence")

implicit val intDescr = AtomDescr[Int]("Integer")

implicit val strDescr = AtomDescr[String]("String")

println(List(1, 2, 3).describe)
println(Array("str1", "str2").describe)
println(Seq(Array(List(1, 2), List(3, 4))).describe)

Выведет:


List of Integer
Array of String
Sequence of Array of List of Integer

Description — базовый тип.
ContainerDescr — рекурсивный класс, который, в свою очередь, требует существования неявного параметра Description для типа описываемого контейнера.
AtomDescr — терминальный класс, описывающий простые типы.


image
Cхема разрешения неявных параметров.


Дебаг неявных параметров


При разработке с использованием цепочек из неявных параметров, время от времени вы будете получать ошибки времени компиляции, с довольно туманными названиями, как правило, это будут: ambiguous implicit values и diverging implicit expansion. Чтобы понимать, что от вас хочет компилятор, необходимо разобраться, что же значат эти сообщения.


Ambiguous implicit values


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


implicit val dog = "Dog"
implicit val cat = "Cat"

def getImplicitString(implicit str: String): String = str

println(getImplicitString)

При попытке скомпилировать этот код, мы получим ошибку:


Error:(7, 11) ambiguous implicit values:
 both value dog in object Example_ambigous of type => String
 and value cat in object Example_ambigous of type => String
 match expected type String
  println(getImplicitString)

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


Diverging implicit expansion


Эта ошибка означает бесконечную рекурсию при поиске неявного значения.


implicit def getString(implicit str: String): String = str

println(getString)

Ошибка:


Error:(5, 11) diverging implicit expansion for type String
starting with method getString in object Example_diverging
  println(getString)

Такого рода ошибки сложнее отслеживать. Убедитесь что у вашей рекурсии есть терминальная ветка. Часто помогает попробовать явно подставить всю цепочку параметров и убедиться, что этот код компилируется.


Флаг компилятора log-implicits


Попробуйте также использовать флаг компилятора -Xlog-implicits — с ним scalac будет логировать шаги разрешения неявных параметров и причины неудач.


image
Cообщения компилятора о кандидатах для неявных параметров.


Аннотация @implicitNotFound


Вы можете помечать свои классы и трейты аннотацией @implicitNotFound чтобы сделать более человечными сообщения компилятора о том, что неявное значение этого типа не было найдено.


@implicitNotFound("No member of type class NumberLike in scope for ${T}")
trait NumberLike[T] {
  def plus(x: T, y: T): T
  def divide(x: T, y: Int): T
  def minus(x: T, y: T): T
}

Порядок объявления неявных параметров


Описания этого аспекта не удалось найти в интернете и пришлось прояснить его экспериментально.


Порядок объявления неявных параметров функции имеет принципиальное значение.


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


sealed trait BaseSought

class Target extends BaseSought
class Alternative extends BaseSought

trait Searchable[T <: BaseSought]

implicit def search[T <: BaseSought](implicit canSearch: Searchable[T], sought: T): T = sought

implicit val target = new Target()
implicit val alt = new Alternative()

implicit val canSearchTarget = new Searchable[Target] {}

search // : Target

В области видимости находятся два параметра, подходящих по искомому типу [T <: BaseSought], но из-за того, что неявный параметр Searchable[T] определен только для одного из них, мы можем его определить однозначно и не получаем ошибки компиляции.


image
Успешное разрешение неявных параметров.


Если бы мы определили неявные параметры в другом порядке:


implicit def search[T <: BaseSought](implicit sought: T, canSearch: Searchable[T]): T = sought

то получили бы ошибку:


Error:(17, 1) ambiguous implicit values:
 both value target in object Example11 of type => Example11.Target
 and value alt in object Example11 of type => Example11.Alternative
 match expected type T
search // : Target

image
Oops...


Заключение


В заключении я хочу сразу ответить на вопрос, который неизбежно будет задан в комментариях: "Зачем нам нужны такие сложности? На Go вообще без дженериков живут, не говоря уже о такой черной магии."


Да, может быть и не нужны. Совершенно точно имплиситы не нужны, если вы с их помощью хотите сделать ваш код сложнее. После таких языков как, например, Java, программисты думают, что если в языке много инструментов, то они должны их все использовать. На самом же деле следует использовать сложные инструменты только для сложных задач.


Если вы можете красиво решить задачу без имплиситов — сделайте это, если нет — подумайте еще раз.


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


image

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/329600/


Метки:  

[Перевод] Вебинар про Petya: Вспышка еще одного шифровальщика

Вторник, 04 Июля 2017 г. 09:11 + в цитатник


На прошлой неделе произошла еще одна глобальная атака шифровальщика, которая затронула свыше 60 стран мира. Эта атака, получившая название Petya/GoldenEye, шифровала не только файлы с данными на тысячах компьютерах во всем мире, но также и MBR их жестких дисков. Подвергшись атаке, жертвы полностью теряли доступ к своей системе после очередной перезагрузки ПК.
Невозможно говорить о данной атаке без упоминания о его предшественнике, теперь уже известной атаке WannaCry, которая поразила в мае сотни тысяч компьютеров различных компаниях, организациях и учреждениях по всему миру.

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

Примечательно, что клиенты Panda не пострадали от атаки GoldenEye/Petya. И не потому, что атака обошла стороной компьютеры клиентов Panda, а потому что она не смогла справиться с технологиями расширенной защиты Adaptive Defense. Фактически, Panda составила профиль «вредоносной нагрузки» Petya за день до того, как их стали обнаруживать другие антивирусные компании.


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

Ниже вы можете посмотреть запись нашего недавнего вебинара, который провел Руи Лопес из филиала компании Panda Security в США. На вебинаре была представлена подробная информация о том, как началась атака, даны рекомендации о том, как подготовиться к будущим атакам, а также были обсуждены вопросы адаптации и внедрения действительно превентивного подхода по отражению неизвестных угроз.
Вебинар на английском языке (для Вашего удобства можно включить субтитры).



Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332388/


Метки:  

Перевод Redmine-плагинов с TelegramCLI на Webogram

Вторник, 04 Июля 2017 г. 09:00 + в цитатник


Ранее мы уже писали о наших плагинах redmine_chat_telegram и redmine_intouch, предназначенных
для того, чтобы ваша работа с Redmine и Telegram была продуктивнее.
А сегодня мы хотели бы рассказать о том, как избавились от TelegramCLI.
Это большой апдейт, призванный упростить работу с нашими telegram-плагинами.


В процессе эксплуатации у нас неоднократно возникали проблемы с настройкой TelegramCLI, поэтому мы решили упростить работу наших плагинов. Для этого мы сочли необходимым избавиться от зависимости TelegramCLI, добавили вместо него Webogram и модифицировали под свои нужды. Это позволило больше не запускать на сервере сервис TelegramCLI.


Webogram


Webogram — официальный веб-клиент к Telegram, написанный на AngularJS. Поэтому, чтобы работать с Webogram через скрипты, приходится использовать headless browser, в нашем случае это PhantomJS.
Мы модифицировали код Webogram, чтобы отдавать ему запросы на нужные действия без необходимости работать с интерфейсом. В форме специального запроса плагин отправляет на URL модифицированного Webogram инструкцию сделать некую операцию в Telegram.


Принцип работы


Сначала плагин дает команду произвести какую-то операцию в Telegram.


telegram = TelegramCommon::Telegram.new
result = telegram.execute('Test')

Далее формируется строка запуска PhantomJS.


module TelegramCommon
  class Telegram
    ...

    def make_request
      @api_result = `#{cli_command}`
      debug(api_result)
      api_result
    end

    def cli_command
      cmd = "#{phantomjs} #{config_path} \"#{api_url}\""
      debug(cmd)
      cmd
    end
  end
end

Затем PhantomJS выполняет запрос и ожидает 10 секунд, чтобы элемент #api-status
получил класс ready, который свидетельствовал бы о том, что Webogram завершил обработку.


// plugins/redmine_telegram_common/config/phantom-proxy.js

...

page.open(url, function() {
  waitFor(
    function () {
      return page.evaluate(function () {
        return $('#api-status').hasClass('ready');
      });
    },
    function () {
      exit()
    }, 10000);
});

AngularJS-контроллер AppApiController занимается обработкой запросов, и когда он заканчивает работу, то на странице у блока #api-status меняется класс на "ready", тем самым уведомляя PhantomJS о завершении операции.


// plugins/redmine_telegram_common/app/webogram/app/js/controllers.js

$scope.promiseStatus = false;
var args = {};

if (typeof $routeParams.args !== 'undefined') {
  args = JSON.parse($routeParams.args)
}

$scope.handle = function () {
  var command = $routeParams.command;
  var handlerName = $scope['api' + command];

  if (typeof handlerName === 'function') {
    handlerName(args)
  } else {
    console.error('There is no ' + handlerName + ' api function.')
  }
};

$scope.apiTest = function () {
  $scope.successApi('api test')
};

...

$scope.successApi = function (msg) {
  console.log('success: ' + msg);
  $scope.resolveApi()
};

$scope.failedApi = function (msg) {
  console.log('failed: ' + msg);
  $scope.resolveApi()
};

$scope.resolveApi = function () {
  $scope.promiseStatus = true
};

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


Заключение


В итоге, чтобы начать работать с нашими плагинами, которые ранее зависели от TelegramCLI, теперь достаточно установить новые зависимости, перейти на страницу настройки плагина redmine_telegram_common и пройти простенькую процедуру авторизации, в процессе которой PhantomJS запомнит, под каким логином отправлять запросы на Telegram.


Telegram-боты в наших плагинах redmine_chat_telegram и redmine_intouch научились работать не только через getUpdates (как ранее фоновые процессы через rake), но и через WebHooks (достаточно инициализировать ботов в настройках соответствующих плагинов, однако необходим HTTPS на Redmine). Таким образом, мы избавились от дополнительных фоновых процессов, которые раньше могли быть преградой для установки плагина неопытными пользователями.


Если вы встретили какие-то ошибки, у вас есть предложения по улучшению плагина или вы желаете принять участие в развитии плагинов, мы будем рады вашим откликам. Наши репозитории перечислены ниже.


Ссылки


Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332292/


Метки:  

[Перевод] Как принципы HumanOps применяются в Sever Density

Вторник, 04 Июля 2017 г. 09:00 + в цитатник


Понятие HumanOps родилось в Server Density в результате накопления значительного опыта работы по мониторингу компьютерных систем и, соответственно, пребывания команды в состоянии постоянной готовности. В первые годы существования компании я долгое время был на связи в режиме 24/7. Однако по мере роста команды мы внедряли процессы и политики, целью которых было распределение нагрузки и снижение негативного влияния случаев, когда сотрудников отрывают от текущей работы или звонят во внеурочное время, в том числе и ночью.


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


Мы надеемся, что аналогично практикам DevOps, ускоряющим развертывание, приносящим новые инструменты, а также объединяющим команды разработки и эксплуатации, HumanOps поможет организациям в освоении более «человечного» подхода к построению систем и работе с ними.


Под катом вы найдете 12 принципов HumanOps и описание их работы на примере Server Density.


1. Люди создают системы и решают возникшие с ними проблемы


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


На практике это означает, что при проектировании систем вы с самого начала учитываете людей, которые будут так или иначе с ними взаимодействовать.


Что нужно принимать во внимание:


  • Какие части системы могут быть автоматизированы? Необходимо стараться избегать участия людей в ежедневных рутинных операциях, прибегая к силе человеческого интеллекта только в тех случаях, когда система не может исправить проблему сама.
  • Какие сигналы тревоги для оповещения людей необходимо настроить? В какой момент потребуется вмешательство человека? Критично ли это? Можно ли отложить решение проблемы на стандартные рабочие часы?
  • После того как вмешательство человека произошло, каким образом об этом становится известно команде и руководству? Вы ведете журнал поступивших во внеурочное время оповещений, анализируете тенденции?

2. Люди устают и испытывают стресс, они бывают счастливы и несчастны


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


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


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


Один из путей улучшения ситуации — это обучение. Обучение должно быть максимально приближено к реальности, чтобы в тот момент, когда проблема произошла в действительности, она воспринималась бы так же, как и при обучении. Таким образом снижается стресс, поскольку сотрудники знают, что нужно делать. Стресс возникает из-за неуверенности вкупе с осознанием того, что система не работает, поэтому нужно всячески стараться смягчать подобное влияние нештатных ситуаций на психику. Для моделирования различных сценариев сбоев мы проводим военные игры. Таким образом, все сотрудники в любой из отработанных ситуаций знают, что нужно делать.


3. У систем пока нет чувств, но есть SLA


Соглашение об уровне предоставления услуги (SLA) — это отработанный метод определения того, что вы можете ожидать от определенного сервиса или API. У вас должна быть возможность легко определить, соответствует ли сервис SLA, а также что делать, если не соответствует.


4. Людям нужно иногда отключаться


Аналогично принципу № 2, в отличие от компьютеров, которые могут безостановочно работать месяцами и годами, людям требуется отдых. Реагируя на нештатные ситуации и имея дело со сложными системами, люди быстро устают, поэтому время на отдых и восстановление должно быть неотъемлемой частью процессов. Человек может сохранять концентрацию лишь в течение 1,5–2 часов, после этого ему потребуется перерыв. В противном случае работоспособность начинает снижаться.


В Server Density мы решаем эту проблему с помощью ротации дежурств. Первичная и вторичная (primary/secondary) роли по очереди переходят членам команды, и у нас есть документы, определяющие время реакции в зависимости от роли. Это позволяет уменьшить ощущение невозможности отойти от своего компьютера. Например, специалист на подстраховке (secondary) не обязан реагировать мгновенно, поэтому ему не нужно постоянно быть рядом с компьютером.


Более того, время на отдых после работы во внеурочные часы у нас выделяется автоматически. Сотрудник по желанию может отказаться от него, но компания никогда не просит это сделать. Таким образом, мы уверены, что у сотрудников есть достаточно времени на восстановление и на них никто не давит с целью вынудить отказаться от него. Например, это достигается за счет именно автоматического предоставления отдыха — сотруднику не надо ничего делать, чтобы его получить.


5. Благополучие людей-операторов влияет на надежность систем


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


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


6. Тревожная усталость == человеческая усталость


Тревожная усталость (alert fatigue) возникает от получения слишком большого количества сигналов тревоги. Этих сигналов так много, что оператор начинает их игнорировать, рискуя пропустить что-то важное. Это явление значительно снижает эффективность системы оповещения, которая должна срабатывать в редких случаях, ставя людей в известность о том, что произошло что-то действительно серьезное.


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


7. Автоматизируйте что только возможно, привлекайте людей лишь в крайнем случае


Этот принцип связан с предыдущим, поскольку сигналы тревоги должны передаваться людям только в тех случаях, когда система не может починить себя сама. Не нужно будить человека, чтобы перезагрузить сервер или выполнить другое простое действие. Если что-то может быть автоматизировано, то лучше так и поступить. Люди должны привлекаться лишь для оценки сложных ситуаций и выполнения нестандартных действий.


К сожалению, после сдачи системы в промышленную эксплуатацию дальше что-то автоматизировать достаточно проблематично. Это происходит по той причине, что, используя такие современные технологии, как Kubernetes и облачные API, можно настроить автоматическое восстановление после практически любого сбоя, но при этом требуется очень много усилий для адаптации новых технологий к старым системам. Безусловно, и использование избыточности, и внедрение чего-то нового стоят денег, но эти затраты окупаются за счет экономии времени работы человека и увеличения общей надежности систем.


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


При модернизации старой инфраструктуры необходимо соблюдать баланс, поскольку, например, контейнеризация основных компонентов системы может оказаться нецелесообразной. Однако есть пути по достижению схожих целей, например, перенос размещенной на собственном оборудовании базы данных в управляемый сервис типа AWS RDS.


8. Документируйте все, учите всех


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


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


Чтобы сделать документацию удобной для поиска и легкодоступной для всех сотрудников компании, мы в Server Density используем Google Drive. Однако существует еще немало других вариантов по ее размещению.


9. Не надо никого стыдить и обвинять


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


Идеальных людей нет. Каждый из нас хоть раз ломал что-то в production. Здесь важно не обвинять конкретного человека, а суметь сделать систему лучше и устойчивее к подобного рода проблемам. Такого практически никогда не бывает, чтобы система была сломана сознательно, поэтому люди не должны испытывать проблем в признании своих ошибок сразу же после осознания содеянного. Это важно для быстрой ликвидации последствий. Неудачи должны рассматриваться как возможность научиться и сделать команду лучше.


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


10. Проблемы людей — это проблемы системы


Существует тенденция рассматривать проблемы людей и проблемы систем по отдельности. Гораздо проще обосновать дополнительные затраты на увеличение производительности серверов и отказоустойчивость, нежели на вопросы, связанные с персоналом. Все вышеперечисленные принципы призваны подчеркнуть, что вопросы, связанные с людьми, не менее важны и им нужно уделять соответствующее время на рассмотрение и бюджет.


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


11. Здоровье людей влияет на здоровье бизнеса


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


12. Люди важнее систем


Несмотря на то что, несомненно, важно рассматривать людей и системы находящимися на одном уровне по степени влияния друг на друга и бизнес в целом, по большому счету люди все же важнее. Ведь для чего существует бизнес? Чтобы продавать товары и услуги другим людям! И зачем люди делают определенную работу, как не для получения средств к существованию?


Улучшение условий работы вашей команды достаточно легко обосновать. Чтобы иметь возможность нанимать и удерживать лучших специалистов, ваши рабочие процессы должны быть хорошо отлажены. Когда людей постоянно поднимают с постели, стыдят за ошибки и не исправляют проблемы, это негативно сказывается на их состоянии. Стресс, испытываемый на протяжении продолжительных периодов времени, может вызвать повышенное кровяное давление, заболевания сердца, ожирение и диабет. Таким образом, многие организации, не уделяя достаточного внимания вопросам благополучия персонала, могут непреднамеренно подрывать здоровье своих сотрудников, причем иногда очень серьезно.


Мы в Server Density считаем, что это недопустимая цена успешности бизнеса.


Ссылки:


  1. Оригинал: How we do HumanOps at Server Density.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/331678/


Метки:  

Нативные переменные в CSS. Уже пора…

Вторник, 04 Июля 2017 г. 08:46 + в цитатник


Всем привет, тема переменных в CSS давно ходит по интернету, однако не все знают о том, что это такое, да и сама технология не так давно вышла в релиз. И хоть использовать её во многих случаях рановато, уже пора понимать что она из себя представляет и как ею пользоваться. Давайте попробуем разобраться с технологией вместе. Обращу ваше внимание, что эта статья для тех, кто не знает о CSS переменных (кастомных свойствах) или только слышал о них. Если вы знакомы и умеете работать с данной фичей, то вам данная статья будет не интересна.

Итак, тема с переменными в стилях уже затерта до дыр, т.к. они давным давно существуют в препроцессорах. Это удобно, я уже плохо представляю себе написание стилей без возможности сохранить где-то определенное значение (цвет, величину, название шрифта, тип маркера у списка, всё что может придти в голову...). Однако препроцессоры не могут дать нам той гибкости, которую дают нативные переменные в CSS, и скоро вы поймете, почему.

Для начала нужно понять, как объявлять и использовать переменные.
Переменные объявляются в селекторах:

:root {
    --body-background: #ccc;
}

body {
  background-color: var(--body-background);
}


Как видно из листинга выше, переменные объявляются двумя дефисами перед именем:
--variable-name

Чтобы использовать переменную, необходимо воспользоваться функцией var.
Она имеет 2 параметра. Это, естественно, имя переменной, а вторым параметром идёт значение свойства, которое необходимо использовать в случае отсутствия переменной.

На этом набор новых возможностей с приходом переменных, разумеется, не заканчивается. Имея переменные в арсенале CSS, мы получаем большую гибкость в написании стилей. Например, теперь чтобы составить медиазапрос для экранов <320px в ширину, не нужно переопределять свойство целиком. Достаточно изменить значение переменной. Т.е.

.title {
  --wrapper-width: 50%;
  width: var(--wrapper-width);
}

@media (max-width: 320px) {
  --wrapper-width: 100%;
}

Всё! Этого достаточно, чтобы свойство width изменило свое значение!

Если CSS способен отслеживать изменения своих переменных, это значит, что с этим можно взаимодействовать различными способами.

Что насчёт JavaScript?


Управляя аттрибутом style, можно изменить стиль, прибегая к минимальным затратам усилий. Приведу грубый пример на React.

.title {
  --background: blue;
  background-color: var(--background);
}

changeColor() {
  this.setState({
    style: {'--background': 'green'}
  });
}
Title

Теперь по клику на элемент с классом title будет меняться цвет фона у элемента. Круто? Ещё бы! Не нужно добавлять новый класс, переопределять свойство или делать другие действия, способствующие изменению фонового цвета у элемента.
Ремарка
Если кто-то не знаком с React или кому-то просто непонятно, что произошло. Мы просто средствами JavaScript изменили аттрибут style у элемента, изменив значение переменной
--background


Используя переменные, изменять css извне стало проще, методов использования можно придумать массу, а мы пойдем дальше.

Области видимости


Нужно сказать пару слов об области видимости CSS переменных, здесь всё просто. Объявленная переменная доступна всем селекторам дочерних элементов данного селектора. Т.е. в листинге ниже использовать переменную --b в тэге html будет нельзя. А вот переменная --a в body и всех дочерних элементах будет работать без проблем (если её конечно не переопределят где-то ниже).
html {
  --a: #ccc;
}
body {
  --b: #a3a3a3;
}

(я знаю, что цвета в примерах скучные, но я плохо помню цвета по hex-коду :))

Переменные и calc


Как и любое числовое значение свойства, вы можете использовать переменную в функции calc.
.title {
  --title-width: 300px;
  width: calc(var(--title-width) + 150px);
}

Круто! Особенно если учесть что переменную --title-width, можно менять как внутри CSS, так и извне.

Заметьте, что величину мы обязаны положить в переменную. Дописать px, %, rem и т.д. к вызванной переменной у нас не получится. Однако ничто не мешает нам умножить с помощью функции calc значение на единицу в необходимой нам величине.

.title {
  --title-width: 300;
  /* так не сработает */
  width: var(--title-width)px;
  /* так сработает */
  width: calc(var(--title-width) * 1px);
}


В заключение


CSS переменные дают много гибкости, это мощный инструмент, который может быть очень полезен в ряде случаев. Однако спешить его применять в боевых проектах я не рекомендую. Если мы заглянем на caniuse, то увидим что IE вообще не поддерживает данную технологию, а Edge частично.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332038/


Метки:  

Xamarin.Forms для WPF и UWP разработчиков

Вторник, 04 Июля 2017 г. 07:42 + в цитатник


Постараюсь коротко, но понятно, рассказать самое интересное о Xamarin. Самые основные концепты, которые необходимо знать UWP и WPF разработчикам, чтобы с места в карьер начать работать с Xamarin.Forms.

Занявшись разработкой на Xamarin я не бросил UWP, а просто расширил свой стек. UWP проекты — это один из типов проектов, поддерживаемых Xamarin.Forms. Мне показалось, что Xamarin ближе всего UWP разработчикам, так как он самый нативный и очень удобно тестировать приложения при работе с Visual Studio на Windows. Но как оказалось, тип проекта UWP не поддерживается Visual Studio для Mac. Так что Xamarin близок всем платформам.

Xamarin был создан той же командой, которая занималась разработкой Mono. Название было взято от вида обезьян Tamarin. Xamarin 2.0 вышел в начале 2013-ого года. И фактически с его выходом стало можно создавать приложения под iOS, Android и Windows на C#. Через год в 2014-ом вышел релиз Xamarin 3, а вместе с ним и Xamarin.Forms.

Есть два типа проектов, которые можно создать с помощью Xamarin — Xamarin.Forms и Native. В первом случае для всех платформ общий интерфейс создается с помощью XAML или C#, а во втором случае интерфейс создается отдельно для каждой из платформ. То есть у Xamarin.Forms еще и интерфейс пишется общим для всех платформ кодом (но возможны и правки для каждой из платформ в отдельности). Зато у Native приложений есть возможность использовать конструктор для создания графического интерфейса. А для того чтобы создать UI для Xamarin.Forms вам придется писать код вручную и каждый раз запускать отладку приложения, чтобы посмотреть на изменения во внешнем виде страницы.



Что выбрать? Стандартный совет: Xamarin.Forms отлично подходит для приложений с обычным дизайном. Если ваш клиент не капризен и не требует каких-то особых графических эффектов и если не требуется использовать специфичные для платформ API в больших объемах, то выбирайте Forms. Кроме того, если у вас опыт разработки на UWP или WPF, то XAML или C# интерфейс Xamarin.Forms будет вам знаком и понятен. В каком-то смысле Native наоборот ближе тем, кто раньше работал с нативными приложениями конкретной платформы.

Да, да. Вы не «ослышались», или точнее, не «очитались». В Xamarin.Forms у вас есть выбор создавать интерфейс на XAML или на C#. Графический редактор XAML пока что не поддерживается Visual Studio.
Если вы создаете контролы из C# кода, то при этом снижается читаемость и повышается сложность разработки. Зато код компилируется и становится чуть более производительным.
Может быть кому-то не знакомому с XAML проще создавать интерфейс на C#. Но как по мне XAML гораздо более удобен и читаем. После выхода XAML Standard он станет и более привычным.

Простейший пример. Вот такая страница MainPage.xaml будет создана у вас по умолчанию:




	


Можно эту страницу удалить и создать класс MainPage.cs со следующим аналогичным содержимым:

using Xamarin.Forms;

namespace App1
{
    public class MainPage : ContentPage
    {
        public MainPage()
        {
            Label lblIntro = new Label
            {
                Text = "Welcome to Xamarin Forms!",
                VerticalOptions = LayoutOptions.Center,
                HorizontalOptions = LayoutOptions.Center,
            };
            Content = lblIntro;
        }
    }
}

Кстати, и XAML тоже можно скомпилировать. В случае удачной компиляции уменьшится размер приложения и ускорится загрузка. К тому же при компиляции проверятся биндинги.
Для этого необходимо разместить

using Xamarin.Forms.Xaml

и

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]

где-либо в коде (не важно в каком файле — главное перед namespace)

Как это все работает


Xamarin с помощью платформы Mono связывает C# код с родным для платформ API. Сам .NET фреймворк включается в пакеты приложения. При этом неиспользуемые классы из него исключаются для того, чтобы уменьшить размер пакета. Получается что-то вроде портативной дистрибуции .NET Core.
Привязка классов C# к родным для платформ классам происходит при компиляции. И для iOS и для Android платформ привязка происходит одинаково, различие только в режиме компилятора.
Xamarin.Android использует just-in-time компиляцию для тонкой оптимизации производительности при компиляции в нативное Android APK.
Ahead-of-Time (AOT) компилятор компилирует Xamarin.iOS проекты сразу в нативный код ARM и получаемый в результате файл IPA тоже является нативным бинарником.
Для написания кода можно использовать языки C# и F#.

Для того чтобы разрабатывать и тестировать приложения желательно иметь физические девайсы. Для тестирования Android проектов есть возможность использовать не только реальные девайсы, но и различные эмуляторы. Настроить эмуляторы Android иногда занятие нетривиальное. Хотя, если у вас на компьютере присутствует Hyper-V, то вам повезло. В состав Visual Studio входит отличный эмулятор Visual Studio Emulator for Android, который требует Hyper-V, x64 и Windows PRO или Enterprise.
Если у вас нет Hyper-V, то вы все-равно можете настроить Google Android эмулятор. Его можно настроить и на процессорах Intel и на AMD, но работать такой эмулятор будет не особо быстро.

Если вы разрабатываете приложение с помощью Visual Studio для Mac, то вы можете воспользоваться эмулятором iOS. Пользователи Visual Studio для Windows лишены возможности тестировать приложения с помощью эмулятора из-за ограничений iOS платформы. Для компиляции и тестирования Xamarin.iOS проекта необходимо реальное устройство.
На конференции Build 2017 был анонсирован Xamarin Live Player с помощью которого можно связать физическое iOS или Android устройство с Visual Studio и тестировать на реальных iOS и Android устройствах без установки гигабайт SDK. Уже доступно превью Xamarin Live Player.

Visual Studio вместе с Xamarin-ом и всеми SDK занимает довольно много места на диске. Если у вас HDD довольно объемный, то для вас это не будет проблемой. Visual Studio 2017 занимает поменьше места, чем 2015-ая, так что если есть выбор, то ставьте ее. В ближайшее время сэкономить место позволит установка Xamarin Live Player вместо SDK.

Небольшая шпаргалка по XAML элементам


Страницы / Pages


На iOS функцию страницы выполняет View Controller, на Windows Phone – Page, а на Android – Activity. Но если вы работаете в Xamarin.Forms приложении, то для вас это Page.

Основные типы страниц:
ContentPage — отображает единственный View (как правило контейнер/элемент компоновки)
MasterDetailPage — страница, отображающая две панели информации
NavigationPage — главную страницу приложения можно обернуть в NavigationPage. Например так: new NavigationPage(new MainPage()); Тогда в приложение добавится навигация.
TabbedPage — страница с несколькими закладками (Tab-ами)
CarouselPage — страница, «листы» которой можно пролистывать. Примерно, как в приложении «Фотографии» можно пролистывать фотографии с помощью жеста свайпа.



Еще есть TemplatedPage – это то, что лежит в основе ContentPage. Фактически благодаря TemplatedPage можно использовать ControlTemplate для ContentPage.

Материал задумывался как шпаргалка для не самых начинающих разработчиков, а пока что все должно быть слишком просто и понятно. Поэтому, чтобы его чуть углубить, разберем пример применения TemplatedPage, немного усложненный биндингом:
В файл App.xaml добавим следующий ControlTemplate:

   
        
            
                
                    
                    
                
            
        
    

В MainPage.xaml добавим ControlTemplate="{StaticResource MainPageTemplate}"




	

Ну и раз у нас в ControlTemplate есть биндинг {TemplateBinding HeaderText}, то необходимо реализовать и его в MainPage.xaml.cs

  public static readonly BindableProperty HeaderTextProperty =
                BindableProperty.Create("HeaderText", typeof(string), typeof(MainPage), "Заголовок");
     public string HeaderText
     {
         get { return (string)GetValue(HeaderTextProperty); }
     }

Хотя даже этот пример должен быть понятен и близок WPF/UWP разработчикам.

Вместо OnNavigatedTo и OnNavigatedFrom у страниц Xamarin есть методы OnAppearing() и OnDisappearing().

Элементы компоновки / Layouts


Layouts как и большинство элементов управления наследуются от класса View.
Давайте рассмотрим самые основные элементы компоновки:
StackLayout — располагает элементы по порядку горизонтально или вертикально
AbsoluteLayout — абсолютное позиционирование (как в WinForms)
RelativeLayout — что-то вроде AbsoluteLayout, но позиции задаются в процентах
Grid – таблица
ContentView – вариант View аналогичного ContentPage (т.е. содержащего в себе только один элемент)
ScrollView — элемент, содержимое которого можно прокручивать если оно не умещается
Frame — содержит в себе только один элемент с Padding отступом по умолчанию равным 20



И пару Layout можно рассматривать отдельно:
TemplatedView – очень похож на TemplatedPage, но в данном случае это View, а не Page.
ContentPresenter – менеджер компоновки для шаблонных вьюшек

Элементы управления


Контролы, которые есть в Xamarin.Forms
Следующие контролы тоже наследуются от View и не требуют особого представления UWP/WPF разработчикам:
Label, Button, Image, ProgressBar, Slider, SearchBar, DatePicker, TimePicker, WebView

А о следующих контролах я расскажу в двух словах:
Stepper — функция как и у slider, но интерфейс в виде двух кнопок (увеличивающих и уменьшающих значение)
Switch — переключатель. Выполняет функцию CheckBox
Entry — текстовое поле
Editor — многострочное текстовое поле
BoxView — прямоугольник, который можно закрасить каким-либо цветом
ActivityIndicator – отображает что что-то происходит (тот момент, когда приложение «задумалось»)

Кроме того, есть элементы для размещения в них различных коллекций: Picker, ListView, TableView

И элементы для особых задач, название которых более-менее соответствует содержимому:
Map, OpenGLView

Полный список элементов управления, унаследованных от View, доступен на следующей странице: Xamarin.Forms Views

Cells


Последним типом элементов управления является тип, унаследованный от Cells. В качестве содержимого, находящегося в ячейках ListView, TableView могут выступать различные контролы с красноречивыми названиями: EntryCell, SwitchCell, TextCell, ImageCell

Соответственно, значением ячейки таблицы может быть: поле ввода текста (EntryCell), переключатель (SwitchCell), текст (TextCell) или изображение (ImageCell).

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

    
        
            
                
                
            
        
    

Кроме четырех уже упомянутых типов ячеек ListView и TableView вы можете задать ячейке какое-то свое содержимое с помощью ViewCell
Например, можно разместить внутри StackLayout с каким-то содержимым

    
        
             
                 
                 
             
        
   

Немного непривычно, но задание ширины и высоты элемента управление происходит с помощью атрибутов WidthRequest и HeightRequest. Как может стать понятным из их названий они не устанавливают размер элементу сразу же, а принимают запрос на установку размера. Установленные значения для платформ устанавливаются равными их типам измерения размера. Для UWP это Effective pixels, для iOS — Points и для Android это Density-independent pixels. Атрибуты Width и Height доступны только для чтения и возвращают текущие размеры.

MessagingCenter


MessagingCenter позволяет различным компонентам приложения коммуницировать друг с другом.
Где это может быть использовано? Например, может быть необходимым прислать сообщение между двумя объектами Page или ViewModel. Или же может быть необходимо сообщить о завершенной фоновой загрузки.

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

MessagingCenter.Subscribe(this, "SomeIdText", GetMessage);

Если после этого MainPage отправит сообщение с текстом SomeIdText, то будет вызван метод GetMessage вот с такой сигнатурой:

     private void GetMessage(object sender, string args)
     {
        // здесь можно что-то сделать
     }

или же можно использовать лямбда-выражение:

     MessagingCenter.Subscribe(this, "SomeIdText", (sender, arg) =>
     {
         // и здесь тоже можно что-то сделать
     });

Отправляется сообщение так:

MessagingCenter.Send(this, "SomeIdText", "значение, которое можно получить в args");

А отписка происходит с помощью следующей строчки:

MessagingCenter.Unsubscribe(this, "SomeIdText");

Полезные ссылки


Если вам мало контролов, то вы можете воспользоваться порталом Xamarin, откуда можно скачать какие-либо готовые платные или бесплатные компоненты.

Кроме того, можно устанавливать пакеты NuGet

Какие-то популярные Open-Source компоненты можно найти на GitHub

Сам открытый код Xamarin тоже можно найти на GitHub

Отправить какой-то баг можно через BugZilla

Shared Projects, PCL или .NET Standard


Вы можете хранить общий код или в общем проекте (Shared Project) или в общей библиотеке PCL.
В зависимости от того что вы выберете у вас будет определенные специфический нюансы в работе.

Shared Projects теоретически позволяют получить доступ к всем возможностям .NET Framework. Но не факт, что все их можно использовать в Xamarin проекте. Так что большие возможности Shared Projects обманчивы.
Возможности PCL ограничиваются в соответствии с количеством назначенных библиотеке платформ. Чем больше платформ назначить библиотеке, там больше ограничены будут ее возможности.
Никто не запрещает в одном решении иметь сразу и Shared Project и PCL.

Для того, чтобы в PCL использовать некоторые пространства имен иногда необходимо чтобы решение было приведено к .NET Standard.
Допустим, вы хотите использовать класс System.Security.Cryptography.
Пока ваша библиотека PCL не будет приведена к .NET Standard у вас это не получится.
Впрочем, можно использовать криптографию в каждом из проектов по отдельности.
Для UWP это можно сделать, используя пространство имен Windows.Security.Cryptography, а для Android и iOS — System.Security.Cryptography.

Минусом приведения к .NET Standard является то, что проекты Android будут в таком случае поддерживать только Android 7.0 и iOS 10.0 и выше. У библиотек PCL для стандартизации используются профили.

В целом использование в проектах PCL приносит больше удобств и возможностей. Для последующих проектов лучше использовать .NET Standard. Со временем библиотеки этого типа заменят PCL.

Как работать с API различных платформ


Особенности Shared Project


Если в качестве источника общего кода вы используете Shared Project, то вы можете использовать директивы компилятора:
#if, #elif, #else и #endif
Например:

#if __ANDROID__
// код, который использует возможности Android
#endif

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

Можно создавать свою имплементацию какого-то класса в каждом из проектов по отдельности с помощью Class Mirroring.
Например, создать в каждом из проектов iOS, Android и UWP свою реализацию Alert

Например, для Android класс может быть таким:

internal class Alert
{
  internal static void Show(string title, string message)
  {
     new AlertDialog.Builder(Application.Context).SetTitle(title).SetMessage(message);
  }
}

Зачем это нужно? В данном примере можно создать свое окошко с сообщением с помощью нативных возможностей платформы.
Обратите внимание на предикат internal. Он в данном случае обязателен.
Из общего проекта метод можно вызвать так:

   Alert.Show("Сообщение", "Привет Xamarin!");


DependencyService


С помощью этого функционала можно писать единый код для различных платформ и в PCL и в общих проектах. Функционал основан на паттерне Dependency Injection. Отсюда и название.

Делается это так. Создается какой-либо интерфейс общий для всех платформ (в общем проекте или PCL) и после, создается реализация этого интерфейса для каждой из платформ (в каждом из проектов платформы).

Приведу официальный пример Xamarin — Introduction to DependencyService, но только с изменениями для UWP.
В общем проекте/PCL создаем интерфейс:

    public interface ITextToSpeech
    {
        void Speak(string text);
    }

В проектах для каждой из платформ создаем реализацию. Для UWP необходимо в проекте создать файл TextToSpeechImplementation.cs со следующим кодом:

    public class TextToSpeechImplementation : ITextToSpeech
    {
        public TextToSpeechImplementation() { }

        public async void Speak(string text)
        {
            MediaElement ml = new MediaElement();
            SpeechSynthesizer synth = new SpeechSynthesizer();
            SpeechSynthesisStream stream = await synth.SynthesizeTextToStreamAsync(text);
            ml.SetSource(stream, stream.ContentType);
            ml.Play();
        }
    }

И нужно в любом файле проекта UWP перед namespace-ом зарегистрировать DependencyService:

[assembly: Xamarin.Forms.Dependency(typeof(AppName.UWP.TextToSpeechImplementation))]
namespace AppName.UWP

Для Android код будет таким:

        
        TextToSpeech _speaker;
        string _toSpeak;

        public TextToSpeechImplementation() { }

        public void OnInit([GeneratedEnum] OperationResult status)
        {
            if (status.Equals(OperationResult.Success))
            {
                var p = new Dictionary();
                _speaker.Speak(_toSpeak, QueueMode.Flush, p);
            }
        }

        public async void Speak(string text)
        {
            var ctx = Forms.Context;
            _toSpeak = text;
            if (_speaker == null)
            {
                _speaker = new TextToSpeech(ctx, this);
            }
            else
            {
                var p = new Dictionary();

                _speaker.Speak(_toSpeak, QueueMode.Flush, p);
            }
        }
    }

И нужно в любом файле проекта Droid перед namespace-ом зарегистрировать имплементацию:

[assembly: Xamarin.Forms.Dependency(typeof(TextToSpeechImplementation))]
namespace AppName.Droid

Код для iOS можете посмотреть на следующей страничке документации StackOverflow: Accessing native features with DependencyService

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

DependencyService.Get().Speak("Hello from Xamarin Forms");

Регистрация с помощью атрибута упрощает применение DI. Но можно применить Dependency Injection и самостоятельно. Например, так. Добавить в PCL класс:

    public static class TextToSpeech
    {
        public static ITextToSpeech Instance { get; set; }
    }

В каждом из проектов инициализировать экземпляр класса:

  TextToSpeech.Instance = new TextToSpeechImplementation();

И можно пользоваться из PCL:
  TextToSpeech.Instance.Speak("Hello! How are you?");

Класс Device


Этот класс позволяет:
Определять текущий тип устройства с помощью Device.Idiom (Desktop, Tablet, Phone, Unsupported)
Определять операционную систему с помощью Device.OS (Android, iOS, Windows, WinPhone)
Для задания различных параметров (очень часто различных размеров) платформам используется Device.RuntimePlatform. Его можно использовать в качестве условия

  if (Device.RuntimePlatform == Device.Android) Margin = new Thickness(0, 20, 0, 0);

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

Hint: Раньше можно было задавать значение с помощью Device.OnPlatform, но сейчас этот метод помечен как устаревший. Хотя стало можно из XAML использовать тэг с атрибутом Platform. Например:


  
   
     0
     0,20,0,0
    
  


Hint 2: Для получения информации о операционной системе и модели используются различные для платформ классы:

UIKit.UIDevice для iOS
Android.OS.Build для Android
Windows.Security.ExchangeActiveSyncProvisioning.EasClientDeviceInformation для Windows

Custom Renderers


В Xamarin Forms можно изменить внешний вид основного контрола с помощью декоратора в виде rendener. Для этого можно создать кастомный renderer – то есть отдельный внешний вид прорисовки контрола для каждой из платформ.

Рассмотрим на простейшем примере:
В Xamarin элемент управления со строкой ввода текста называется Entry.
Сделаем так, чтобы внешний вид строки ввода текста немного отличался от стандартного.
Для этого создаем в PCL или в Shared Project класс

public class MyEntry : Entry
{
}

Теперь в каждом из проектов можно создать экземпляр класса MyEntryRenderer, в котором необходимо override событие прорисовки OnElementChanged и в этом событии сперва прорисовать контрол с помощью base.OnElementChanged(e), а затем уже изменить его интерфейс в соответствии с необходимостью. Следующий класс – это пример того как в iOS приложении изменить цвет фона, элемента управления MyEntry:

using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer (typeof(MyEntry), typeof(MyEntryRenderer))]
namespace YourAppName.iOS
{
    public class MyEntryRenderer : EntryRenderer
    {
        protected override void OnElementChanged (ElementChangedEventArgs e)
        {
            base.OnElementChanged (e);

            if (Control != null) {
                Control.BackgroundColor = UIColor.FromRGB (204, 153, 255);
            }
        }
    }
}

Как вы можете заметить класс унаследован от EntryRenderer. Список всех классов, от которых можно наследоваться, доступен в англоязычной документации: Renderer Base Classes and Native Controls

Для того, чтобы изменить внешний вид MyEntry для платформы Android можно взять тот же класс, но сделать небольшие изменения. В первую очередь имя платформы изменить с iOS на Android. Ну и затем уже заменить в событии OnElementChanged код интерфейса на специфичный для платформы.

   if (Control != null) {
      Control.SetBackgroundColor (global::Android.Graphics.Color.LightGreen);
   }

Как вы понимаете, для того, чтобы изменить стиль элемента, необходимо немного знать особенности платформы.

Кроме проверки на Control != null есть еще 2 проверки:

   if (e.OldElement != null) {
    // Отписаться от событий и очистить ресурсы (если необходимо) 
   }

и

   if (e.NewElement != null) {
    // Здесь можно настроить вид контрола и подписаться на события
   }

Если вам нужно только просто немного изменить дизайн, то вы можете по простому использовать Control != null

e.Old или e.NewElement — это элемент управления Xamarin.Forms, который прорисовывается с помощью renderer. Например Entry или Button.
Control – это версия элемента управления, которая используется текущей платформой. Можно сказать родной для платформы контрол (вы же помните, что Xamarin.Forms связывает свои классы с родными для платформ классами).
Например, для UWP родным аналогом Entry будет TextBox. Для iOS аналог Entry это UITextView, а для Android – EditText.
Или же взять контрол Xamarin.Forms под названием DatePicker. Для UWP его родным аналогом будет класс с таким же названием — DatePicker. На iOS будет прорисован контрол под названием UITextField, а на Android – EditText.

Даже в довольно объемной статье сложно раскрыть полностью тему разработки на Xamarin. Интерфейс Xamarin.Forms не особо далеко ушел от WPF и UWP. Поэтому знакомые классы C# позволяют начать разработку практически сразу. Да и разработчики платформы стараются сделать ее более привычной. К примеру, у приложений Xamarin довольно схожий с UWP life cycle. Приложение точно также может переходить в состояние suspended (sleep). Для обработки можно использовать события: OnStart, OnSleep, OnResume.

Через какое-то время, после окончательного перехода на .NET Standard и XAML Standard разработчикам C# станет еще более комфортно работать с различными технологиями из стека языка.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/331308/


Метки:  

Законы и проекты, которые изменят лицо российского IT. Часть III. Заключительная?

Вторник, 04 Июля 2017 г. 03:17 + в цитатник


Итак часть первая и вторая вышли в свет. Но события развиваются столь стремительно, что буквально за три недели ряд проектов, которые обсуждались ранее мной здесь, на Хабре, а также и на других ресурсах, уже приняты в первом чтении: самые видены из них — «закон» о VPN и проект подобного же о мессенджерах.

Самое удивительно, что МВД, Роспотребнадзор, а главное ФНС уже приняли приказ о возможности блокировки VPN (и выглядит он даже круче, чем белый список Роскомнадзора). И это при том, что три чтения и подпись Президента (и да — ещё вступление в силу) никто не отменял. На Гигтаймсе уже идёт обсуждение.

Нужно учесть, что подобное происходит на фоне вот этого закона, который уже как раз подписан.

Согласно закону, Минкомсвязи России по поступающей информации принимает мотивированное решение о признании сайта копией заблокированного сайта и направляет данное решение владельцу такого сайта и в Роскомнадзор для принятия мер по ограничению доступа к копии заблокированного сайта, которое будет осуществляться во внесудебном порядке

То есть блокировка интернет-ресурсов по всем векторам — это самые важные из не решённых проблем (наверное, нужны кавычки?) в самой большой по площади стране в мире?

Мало кто обратил за последнее время внимание, но на самом деле ровно из этой же оперы, или, назовите как хотите, т.н. закон об онлайн-кинотеатрах. Ведь:

  1. Онлнайн-кинотеатры должны регистрироваться… в РКН
  2. За нарушение требований РКН — их могут прикрыть
  3. Терроризм — зло, его показывать не нужно

И да, контроль за всеми и вся — теперь не только через онлнай-кассы, но и онлайн вообще. А кассы — с 01.07.17 вступили в свои владения. Вот, правда, 1/3 вообще про эту новацию узнала и не более: приобрести кассы до сих пор — трудно, а ФНС — только и делает, что пишет письма.

В этой связи мне лично очень странно читать, что в России будет принят «первый» закон о криптовалютах (а потом ещё и об ICO, майнинге и т.д.). Странно ещё и потому, что якобы по проекту никаких налогов для этой сферы не будет. Возможно. В начале. Недолго. После выявления всех возможных схем — налоги введут и с удвоенной силой, как это было уже на примере электронных денег, e-commerce и множества других сфер?

И самое главное: в каждой сфере прослеживается одна, строго заданная тенденция — монополизация конкретной отрасли рынка, будь это электронная коммерция (где укрупнения, скажем, в продаже бытовой техники достигли просто вселенских масштабов, а прибыли — как не было, так и нет), такси, розница или что-то ещё, скажем — мессенджеры.

Желание подчинить последние Оп.Сот.Связи — весьма странное, с учётом того, как именно эти самые ОСС развивают свои собственные технологии на протяжении уже многих лет (подробней здесь, здесь и здесь).

Всё делается впопыхах: во-первых, чтобы всё успеть до каникул, во-вторых, летом проще — у граждан иные заботы.

И почему-то 451 по Габриелю Фаренгейту мне вспоминается всё чаще...

Не об этом речь: все эти законы, так или иначе, есть нарушение принципа запрета цензуры. И, как уже писал в предыдущих постах, обходить подобные табу будет всё сложнее и сложнее, а тем самым — трафик на ресурсы, которые важны для кого-то конкретно, будет всё меньше и меньше.

Быть может, таким образом обе палаты Федерального Собрания создают, пусть и сугубо искусственно, все условия для эволюции тех, для кого политоним — россиян? Как бы там ни было, но на мой взгляд — безудержное веселье закончилось. Для всех: пора не просто смотреть в сторону нецензурируемого блокчейна, но и следовать дальше?.. Правда, не знаю куда: говорят, Конституционный Суд всё реже открыт для одной из форм своих решений (ака — Определения)?

Быть может у Вас, жители Хабра, будут конструктивные и рабочие предложения на сей счёт?

P.S. Хотел было написать ещё пару законов, но даже не знаю — стоит ли… Лучше, наверное, уж Голем: да каникулы уже скоро — может, за этот перерыв и успеем разобраться.
Какой способ противодействия бесмыссленным и беспощадным законам в IT-сфере считаете эффективным?

Никто ещё не голосовал. Воздержавшихся нет.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332302/


Метки:  

[Перевод] Golem: децентрализация нового уровня

Вторник, 04 Июля 2017 г. 01:31 + в цитатник

Метки:  

test1

Вторник, 04 Июля 2017 г. 00:54 + в цитатник

Метки:  

[Перевод] Полное руководство по переходу с HTTP на HTTPS

Понедельник, 03 Июля 2017 г. 23:51 + в цитатник

В наше время HTTPS обязателен для каждого веб-сайта: пользователи ищут замочек в адресной строке, когда передают личные данные; Chrome и Firefox недвусмысленно помечают как небезопасные веб-сайты с формами на страницах без HTTPS; это влияет на позиции в поисковой выдаче и оказывает серьёзное влияние на приватность в целом. Кроме того, сейчас имеется несколько вариантов получить бесплатный сертификат, так что переход на HTTPS — всего лишь вопрос желания.


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

В этом руководстве я объясню отдельные компоненты и шаги и ясно изложу каждый этап установки. У вас должно всё пройти гладко, особенно если ваш хостер сам предоставляет сертификаты HTTPS — тогда высока вероятность, что вы быстро и просто всё сделаете не выходя из панели управления.

Сюда включены детальные инструкции для владельцев виртуального хостинга на cPanel, администраторов серверов Apache HTTP и nginx под Linux и Unix, а также Internet Information Server под Windows.

Начнём с основ.

HTTP, HTTPS, HTTP/2, SSL, TLS: где что?


Для описания процесса коммуникации между клиентом и сервером используется много акронимов. Люди, незнакомые с технической сутью, часто путают их.

Hypertext Transfer Protocol (HTTP) — основной протокол связи, который должны поддерживать клиент и сервер, чтобы установить соединение. Он описывает такие понятия как запросы и ответы, сессии, кэширование, аутентификация и др. Работу над протоколом, а также над языком гипертекстовой разметки Hypertext Markup Language (HTML) начал в 1989 году сэр Тим Бернерс-Ли и его группа в ЦЕРН. Первая официальная версия протокола (HTTP 1.0) вышла в 1996 году, а вскоре в 1997 году появилась версия HTTP 1.1, которая широко используется сегодня.

Протокол передаёт информацию между браузером и сервером в чистом тексте, позволяя видеть эту информацию в сети, через которую она проходит. Это проблема безопасности, поэтому был изобретён HTTP Secure (HTTPS), позволяющий клиенту и серверу устанавливать зашифрованный канал связи, а затем передавать сообщения чистым текстом по этому каналу, эффективно защищая их от прослушивания.

Термины SSL и TLS часто используются как взаимозаменяемые, поскольку TLS 1.0 приходит на место SSL 3.0. Сам SSL был разработан в компании Netscape, а TLS — это стандарт IETF. На момент написания этой статьи все версии SSL (1.0, 2.0, 3.0) не рекомендуются для использования из-за различных проблем с безопасностью, и современные браузеры выводят предупреждения об этом. Из стандарта TLS используются версии 1.0, 1.1 и 1.2, а версия 1.3 сейчас на стадии черновика.

Так что где-то между 1996 и 1997 годами мы получили текущую стабильную версию Интернета (HTTP 1.1 с или без SSL и TLS), которая по-прежнему поддерживается на большинстве современных веб-сайтов. Ранее HTTP использовался для несущественного трафика (например, чтения новостей), а HTTPS применяли для важного трафика (например, аутентификации и электронной коммерции): однако увеличение значения приватности привело к тому, что браузеры вроде Google Chrome сейчас помечают веб-сайты HTTP как «не конфиденциальные» и в будущем будут выводить новые предупреждения для них.

В следующем обновлении протокола HTTP — HTTP/2 — которую поддерживает всё большее количество сайтов, реализованы новые функции (сжатие, мультиплексирование, приоритет разного трафика), чтобы уменьшить задержки и увеличить производительность и безопасность.

В HTTP версии 1.1 безопасное соединение является необязательным (у вас может быть HTTP и/или HTTPS независимо друг от друга), в то время как в HTTP/2 оно на практике обязательно — даже хотя стандарт допускает HTTP/2 без TLS, но большинство разработчиков браузеров заявили, что они реализуют поддержку HTTP/2 только через TLS.

Что даёт HTTPS?


Почему в первую очередь стоит думать о HTTPS? Его внедряют по трём основным причинам:

  • Конфиденциальность
    В открытой среде, такой как Интернет, он защищает коммуникации между двумя сторонами. Например, в отсутствие HTTPS владелец точки доступа WiFi может видеть приватные данные, такие как кредитные карты, если пользователь этой точки доступа совершает покупки в онлайне.
  • Целостность
    Он гарантирует, что информация достигнет адресата в полном и нетронутом виде. Например, наш друг с точкой доступа WiFi может добавить дополнительную рекламу на наш сайт, снизить качество изображений для экономии трафика или изменить содержимое статей, которые мы читаем. HTTPS гарантирует, что веб-сайт не может быть изменён.
  • Аутентификация
    Он гарантирует, что веб-сайт в реальности является тем, за кого себя выдаёт. Например, тот же самый владелец точки доступа WiFi мог бы отправлять браузеры на поддельный сайт. HTTPS гарантирует, что веб-сайт, который представляется как example.com, действительно является example.com. Некоторые сертификаты даже проверяют правовую идентичность владельца веб-сайта, так что вы знаете, что yourbank.com принадлежит YourBank, Inc.

Криптография в основе


Конфиденциальность, целостность и аутентификация не являются уникальными особенностями HTTPS: это ключевые концепции криптографии. Давайте посмотрим на них более внимательно.

Конфиденциальность


Конфиденциальность представляет собой приватность — так и есть, она защищает информацию от чтения посторонними лицами. Обычно этот процесс предусматривает перевод информации из читаемой формы (в том числе аудио и видео), которая называется открытый текст, в зашифрованную, нечитаемую форму, которая называется шифротекст. Этот процесс именуется шифрованием. Обратный процесс — превращение нечитаемого шифротекста обратно в читаемый открытый текст — называется расшифровкой. Для шифрования и расшифровки информации существует много методов — шифровальных функций (или алгоритмов).

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

  1. Какой алгоритм (шифровальную функцию) использовать в коммуникации.
  2. Какие параметры, пароли или правила (то есть секрет) будут использоваться с выбранным методом.

Есть два основных метода шифрования:

  • симметричное
    Обе стороны владеют общим секретным ключом.
  • асимметричное
    Одна из сторон владеет парой из публичного и секретного ключей, представляя основу инфраструктуры публичных ключей (public key infrastructure, PKI).

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


Симметричное шифрование (см. большую версию)

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

Итак, как это работает? Предположим, что у нас есть две стороны, которые желают безопасно общаться друг с другом — Алиса и Боб (в каждом учебнике всегда используются такие имена вымышленных персонажей, в справочниках по безопасности и прочих, мы уважаем эту традицию). У каждого из них есть своя пара ключей: один секретный, а один публичный. Секретные ключи известны только их соответствующим владельцам; публичные ключи открыты для всех.

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

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


Асимметричное шифрование (см. большую версию)

Когда мы используем симметричное, а когда асимметричное шифрование?

Асимметричное шифрование используется для обмена секретом между клиентом и сервером. В реальной жизни нам обычно не нужна двусторонняя асимметричная коммуникация — достаточно, если одна из сторон (назовём её сервер для простоты) владеет набором ключей, так что она может получать зашифрованные сообщения. В реальности это защищает информацию только в одном направлении — от клиента к серверу, потому что информация, зашифрованная публичным ключом, может быть расшифрована только с помощью парного ему секретного ключа: значит, только сервер может её расшифровать. Другое направление не защищено — информация, зашифрованная секретным ключом сервера, может быть расшифрована его публичным ключом, который открыт для всех. Другая сторона (также для простоты назовём её клиент) начинает коммуникацию с шифрования случайно сгенерированного секрета сессии публичным ключом сервера, затем отправляет шифротекст обратно на сервер, который, в свою очередь, расшифровывает его с помощью своего секретного ключа, и теперь владеет секретом.

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

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

Целостность


Другая проблема, которую решает HTTPS — это целостность данных: 1) гарантия, что вся информация доставляется целиком; 2) гарантия, что никто не изменяет информацию при её передаче. Для обеспечения целостной передачи информации используются алгоритмы дайджеста сообщения. Вычисление кодов аутентификации сообщений (MAC) для каждого сообщения при обмене — это процесс криптографического хеширования. Например, для получения MAC (иногда он называется тегом) используется метод, который гарантирует практическую невозможность (иногда используется термин невыполнимость) осуществить следующее:

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

Аутентификация


Что насчёт аутентификации? Проблема с реальными приложениями публичной инфраструктуры ключей состоит в том, что ни у одной стороны нет способа узнать, кем на самом деле является вторая сторона — они физически разделены друг от друга. Чтобы доказать свою аутентичность второй стороне, вовлекается третья сторона, имеющая взаимное доверие — центр сертификации (CA). Этот CA выпускает сертификат с подтверждением того, что доменное имя example.com (уникальный идентификатор) связано с публичным ключом XXX. В некоторых случаях (с сертификатами EV и OV — см. ниже) CA также проверяет, что конкретная компания контролирует этот домен. Эта информация гарантирована (то есть сертифицирована) центром сертификации Х, и эта гарантия действует не раньше, чем дата Y (то есть сертификат начинает действовать с этой даты), и не позже чем дата Z (то есть сертификат заканчивает своё действие в эту дату). Вся эта информация включена в один документ, который называется сертификат HTTPS. Чтобы привести легко понимаемую аналогию — это как ID или паспорт, который выдаёт правительство страны (то есть третья сторона, которой все доверяют) — и все, кто доверяют правительству, будут также доверять сертификату (паспорту) его владельца и самому владельцу. Предполагается, конечно, что паспорт не поддельный, но подделка сертификатов выходит за рамки данной статьи.

Центры сертификации — это организации, которым доверяют подпись сертификатов. В операционных системах, таких как Windows, macOS, iOS и Android, а также в браузере Firefox есть список доверенных сертификатов.

Вы можете проверить, каким центрам сертификации доверяет ваш браузер:

  • Firefox
    “Options” -> “Advanced” -> “Certificates” -> “View Certificates” -> “Authorities”
  • Windows
    “Control Panel” -> “Internet Options” -> “Content” — “Certificates” -> “Trusted Root Certification Authorities / Intermediate Certification Authorities”
  • Mac
    “Applications” -> “Utilities” -> “Keychain Access.” В “Category” выберите “Certificates”

Все сертификаты затем проверяются и становятся доверенными. Проверка осуществляется либо операционной системой, либо браузером — доверие устанавливается напрямую или через проверенную доверенную сторону. Механизм передачи доверия известен как цепочка доверия:


Цепочка доверия (см. большую версию)

Вы можете добавить дополнительные центры сертификации, что полезно при работе с самоподписанными сертификатами (которые мы обсудим позже).

В большинстве типичных ситуаций клиенту нужно подтвердить идентичность сервера — например, сайта электронной коммерции для покупателей — так что только этому веб-сайту нужен сертификат. В других ситуациях, таких как системы электронного правительства, одновременно и клиент, и сервер должны доказать свою идентичность. Это означает, что обеим сторонам нужно предъявить сертификаты для аутентификации. Такая система тоже выходит за рамки этой статьи.

Типы сертификатов HTTPS


Существует несколько типов сертификатов HTTPS. Их можно классифицировать по следующим критериям.

1. Проверка подлинности


  1. Подтверждённый домен (DV)
    Самый распространённый тип сертификатов DV подтверждает, что домен соответствует определённому публичному ключу. Браузер устанавливает безопасное соединение с сервером и демонстрирует значок закрытого замка. Нажатие по значку выводит сообщение «Этот веб-сайт не предоставил информации о владельце». Для получения этого сертификата не выдвигается никаких дополнительных требований, кроме владения доменом — сертификат DV просто гарантирует, что для этого домена предъявлен правильный публичный ключ. Браузер не показывает название юридического лица. Сертификаты DV часто дёшевы ($10 в год) или бесплатны — см. разделы о Let’s Encrypt и Cloudflare ниже.
  2. Расширенное подтверждение (EV)
    Сертификаты EV подтверждают юридическое лицо, которому принадлежит веб-сайт. Это самый заслуживающий доверия тип сертификатов. Его выдают после того, как центр сертификации проверит юридическое лицо, которое контролирует домен. Юридическое лицо проверяется по нескольким условиям:

    • управление доменом (наличие сертификата DV);
    • государственный реестр для проверки, что компания зарегистрирована и действительна;
    • независимые бизнес-справочники, такие как Dunn и Bradstreet, connect.data.com от Salesforce, Yellow Pages и др.;
    • проверочный телефонный звонок;
    • проверка всех доменных имён в сертификате (подстановочные символы явно запрещены в сертификатах EV).

    Как и значок закрытого замка, сертификаты EV HTTPS показывают перед URL название проверенного юридического лица — обычно зарегистрированной компании. Некоторые устройства, такие как iOS Safari, показывают только подтверждённое юридическое лицо, полностью игнорируя URL. Нажатие на значок покажет подробности об организации, такие как полное название и юридический адрес. Стоимость этих сертификатов составляет от $150 до $300 в год.
  3. Подтверждённая организация (OV)
    Как и EV, сертификаты OV подтверждают юридическое лицо, которому принадлежит веб-сайт. Но в отличие от EV, сертификаты OV HTTPS не отображают название подтверждённого юридического лица в пользовательском интерфейсе. В результате, сертификаты OV не так популярны, поскольку у них высокие требования для проверки, но они не дают преимуществ, видимых для пользователя. Стоимость составляет от $40 до $100 в год.

2. Количество покрываемых доменов


В давние времена сертификаты HTTPS обычно содержали в поле CN единственный домен. Позже было добавлено «альтернативное имя субъекта» (SAN), чтобы один сертификат покрывал и дополнительные домены. В наши дни все сертификаты HTTPS создаются одинаково: даже в сертификате на единственный домен будет поле SAN для этого единственного домена (и второе поле SAN для версии www этого домена). Однако многие продавцы по историческим причинам по-прежнему продают сертификаты HTTPS на один и несколько доменов.

  1. Один домен
    Это самый распространённый тип сертификата, действительный для доменных имён example.com и www.example.com.
  2. Несколько доменов (UCC/SAN)
    Этот тип сертификата, также известный как сертификат Unified Communications Certificate (UCC) или Subject Alternative Names (SAN), может покрывать список доменов (до определённого предела). Он не ограничен единственным доменом — вы можете указать различные домены и поддомены. Стоимость обычно включает в себя определённое количество доменов (от трёх до пяти) с возможностью добавить больше (до определённого предела) за дополнительную плату. Рекомендуется использовать его только с родственными сайтами, потому что клиент при проверке сертификата на любом веб-сайте увидит основной домен, а также все дополнительные.
  3. Поддомены (wildcard)
    Этот тип сертификата покрывает основной домен, а также неограниченное количество поддоменов (*.example.com) — например, example.com, www.example.com, mail.example.com, ftp.example.com и т. д. Ограничение в том, что он покрывает только поддомены основного домена.

Разнообразие различных сертификатов показано в таблице:

Тип сертификата Подтверждённый домен (DV) Подтверждённая организация (OV) Расширенное подтверждение (EV)
HTTPS HTTPS
Проверенный правообладатель
HTTPS
Проверенный правообладатель
Информация о владельце отображается в браузере
Один домен example.com, www.example.com
Несколько доменов example.com, www.example.com, mail.example.com, example.net, example.org и др.
Определённый заранее список, до некоторого лимита (обычно 100)
Поддомены *.example.com
Подходит для любого поддомена
Недоступно — все имена должны быть явно включены в сертификат и проверены центром сертификации

Конфигурация


Чтобы подвести итог, четыре компонента HTTPS требуют шифрования:

  1. Первоначальный обмен ключами
    Используются асимметричные алгоритмы (секретный и публичный ключи).
  2. Сертификация идентичности (сертификат HTTPS, выданный центром сертификации)
    Используются асимметричные алгоритмы (секретный и публичный ключи).
  3. Реальное шифрование сообщений
    Используются симметричные алгоритмы (предварительно разделённый совместный секрет).
  4. Дайджест сообщений
    Используются алгоритмы криптографического хеширования

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

Например, выбор ECDHE-RSA-AES256-GCM-SHA384 означает, что обмен ключами будет производиться по алгоритму Elliptic Curve Diffie-Hellman Ephemeral (ECDHE); центр сертификации подписал сертификат при помощи алгоритма Rivest-Shamir-Adleman (RSA); симметричное шифрование сообщений будет использовать шифр Advanced Encryption Standard (AES) с 256-битным ключом и будет работать в режиме GCM; целостность сообщений будет обеспечивать алгоритм безопасного хеширования SHA, с использованием 384-битных дайджестов. (Доступен полный список комбинаций алгоритмов).

Итак, нужно сделать выбор некоторых конфигураций.

Наборы шифров


Выбор набора шифров для использования — это компромисс между совместимостью и безопасностью:

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

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

Википедия содержит исчерпывающий список алгоритмов для всех компонентов TLS и указывает их поддержку в разных версиях SSL и TLS.

Mozilla SSL Configuration Generator — очень полезный и крайне рекомендуемый справочник, какие криптографические методы использовать на сервере. Мы позже будем использовать его в реальных серверных конфигурациях.

Типы ключей


Сертификаты Elliptic Curve Cryptography (ECC) быстрее обрабатываются и используют меньше CPU, чем сертификаты RSA, что особенно важно для мобильных клиентов. Однако некоторые сервисы, такие как Amazon, CloudFront и Heroku на момент написания этой статьи пока не поддерживают сертификаты ECC.

256-битная длина ключа для ECC считается достаточной.

Сертификаты Rivest Shamir Adleman (RSA) более медленные, но совместимы с большим разнообразием старых серверов. Ключи RSA больше по размеру, так что 2048-битный ключ RSA считается минимально допустимым. Сертификаты RSA с ключами 4096 бит и больше могут ухудшать производительность — к тому же, скорее всего, они подписаны 2048-битным ключом посредника, что по большей части подрывает дополнительную защиту!

Вы могли заметить расплывчатость заявлений, сделанных выше, и отсутствие каких-либо чисел. То, что может нагрузить один сервер, не нагрузит другой сервер. Лучший способ определить влияние на производительность — это проверить загрузку на своём собственном сервере, с реальным веб-сайтом и реальными посетителями. И даже это изменится со временем.

Процедуры


Для получения сертификаты HTTPS выполните следующие шаги:

  1. Создайте пару из секретного и публичного ключей и подготовьте запрос на подпись сертификата (Certificate Signing Request, CSR), включающий информацию об организации и публичном ключе.
  2. Свяжитесь с центром сертификации и запросите сертификат HTTPS на основании CSR.
  3. Получите подписанный сертификат HTTPS и установите его на своём сервере.

Есть набор файлов, содержащих различные компоненты инфраструктуры публичных ключей (PKI): секретный и публичный ключи, CSR и подписанный сертификат HTTPS. Чтобы ещё больше всё усложнить, разные стороны используют разные названия (и расширения) для именования одной и той же вещи.

Для начала, есть два популярных формата хранения информации — DER и PEM. Первый из них (DER) бинарный, а второй (PEM) — это файл DER в кодировке base64 (текст). По умолчанию Windows напрямую использует формат DER, а мир свободных систем (Linux и UNIX) использует формат PEM. Существуют инструменты (OpenSSL) для конвертации файлов из одного формата в другой.

В качестве примеров мы будем использовать такие файлы:

  • example.com.key
    Файл в формате PEM с секретным ключом. Расширение .key не является стандартом, так что кто-то может использовать его, а кто-то нет. Файл должен быть защищён и доступен только для суперпользователя.
  • example.com.pub
    Файл в формате PEM с публичным ключом. Вам на самом деле не нужен этот файл (и он никогда не будет явно присутствовать), потому что его можно сгенерировать из секретного ключа. Он включён сюда только для примера.
  • example.com.csr
    Это запрос на подпись сертификата. Файл в формате PEM содержит информацию об организации, а также публичный ключ сервера. Его нужно отправить в центр сертификации, выдающий сертификаты HTTPS.
  • example.com.crt
    Сертификат HTTPS, выданный центром сертификации. Это файл в формате PEM, который содержит публичный ключ сервера, информацию об организации, подпись центра сертификации, даты начала и окончания срока действия и др. Расширение .crt не является стандартом; часто используются другие расширения, в том числе .cert и .cer.

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

Секретный ключ — это случайно сгенерированная строка определённой длины (мы используем 2048 бит), которая выглядит примерно так:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAm+036O2PlUQbKbSSs2ik6O6TYy6+Zsas5oAk3GioGLl1RW9N
i8kagqdnD69Et29m1vl5OIPsBoW3OWb1aBW5e3J0x9prXI1W/fpvuP9NmrHBUN4E
S17VliRpfVH3aHfPC8rKpv3GvHYOcfOmMN+HfBZlUeKJKs6c5WmSVdnZB0R4UAWu
Q30aHEBVqtrhgHqYDBokVe0/H4wmwZEIQTINWniCOFR5UphJf5nP8ljGbmPxNTnf
b/iHS/chjcjF7TGMG36e7EBoQijZEUQs5IBCeVefOnFLK5jLx+BC//X+FNzByDil
Tt+l28I/3ZN1ujhak73YFbWjjLR2tjtp+LQgNQIDAQABAoIBAEAO2KVM02wTKsWb
dZlXKEi5mrtofLhkbqvTgVE7fbOKnW8FJuqCl+2NMH31F1n03l765p4dNF4JmRhv
/+ne4vCgOPHR/cFsH4z/0d5CpHMlC7JZQ5JjR4QDOYNOpUG51smVamPoZjkOlyih
XGk/q72CxeU6F/gKIdLt6Dx03wBosIq9IAE8LwdMnioeuj18qaVg195OMeIOriIn
tpWP4eFya5rTpIFfIdHdIxyXsd6hF/LrRc9BMWTY1/uOLrpYjTf7chbdNaxhwH7k
buvKxBvCvmXmd6v/AeQQAXbUkdSnbTKDaB9B7IlUTcDJyPBJXvFS1IzzjN6vV+06
XBwHx5ECgYEAyRZLzwnA3bw8Ep9mDw8JHDQoGuQkFEMLqRdRRoZ+hxnBD9V9M0T6
HRiUFOizEVoXxf6zPtHm/T7cRD8AFqB+pA/Nv0ug6KpwUjA4Aihf5ADp0gem0DNw
YlVkCA6Bu7c9IUlE0hwF7RLB7YrryJVJit9AymmUTUUHCQTWW2yBhC8CgYEAxoHS
HGXthin5owOTNPwLwPfU2o7SybkDBKyW69uTi0KxAl3610DjyA/cV2mxIcFlPv1y
HualGd9eNoeCMBy/AUtjzI0K77yeRpjj321rj6k8c8bYWPHH539SiBXLWTY/WQ0w
pxfT3d/Z4QMh5d6p+p5f3UIrXESYQd+fAaG5tNsCgYEAksTdTB4YUT9EsWr6eN9G
jPlclFQUKV3OMvq77bfYvg8EJORz32nnDDmWS7SUjoOtemwutBlMeWbaKk25aMp3
5JNMXuV6apeMJ9Dd8GU7qBUqlIvVK31/96XPvzmnYzWZPqRVwO2HPcRFG3YcJmkg
JmZQyexJvCQ3wFNxiYUm+y0CgYBXQSMhFnCUg4jWbbDcHlnwRT+LnjHrN2arPE3O
eKLfGL6DotmqmjxFaStaRPv2MXMWgAMUsB8sQzG/WEsSaOBQaloAxJJlFIyhzXyE
bi1UZXhMD8BzQDu1dxLI/IN4wE6SDykumVuocEfuDxlsWDZxEgJjWD2E/iXK9seG
yRa+9wKBgEydVz+C1ECLI/dOWb20UC9nGQ+2dMa+3dsmvFwSJJatQv9NGaDUdxmU
hRVzWgogZ8dZ9oH8IY3U0owNRfO65VGe0sN00sQtMoweEQi0SN0J6FePiVCnl7pf
lvYBaemLrW2YI2B7zk5fTm6ng9BW/B1KfrH9Vm5wLQBchAN8Pjbu
-----END RSA PRIVATE KEY-----


Держите ключ в секрете! Это значит, защитите его с помощью очень ограниченных разрешений (600) и никому не разглашайте.

Его напарник — публичный ключ — выглядит примерно так:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm+036O2PlUQbKbSSs2ik
6O6TYy6+Zsas5oAk3GioGLl1RW9Ni8kagqdnD69Et29m1vl5OIPsBoW3OWb1aBW5
e3J0x9prXI1W/fpvuP9NmrHBUN4ES17VliRpfVH3aHfPC8rKpv3GvHYOcfOmMN+H
fBZlUeKJKs6c5WmSVdnZB0R4UAWuQ30aHEBVqtrhgHqYDBokVe0/H4wmwZEIQTIN
WniCOFR5UphJf5nP8ljGbmPxNTnfb/iHS/chjcjF7TGMG36e7EBoQijZEUQs5IBC
eVefOnFLK5jLx+BC//X+FNzByDilTt+l28I/3ZN1ujhak73YFbWjjLR2tjtp+LQg
NQIDAQAB
-----END PUBLIC KEY-----


Запрос на получение сертификата выглядит примерно так:

-----BEGIN CERTIFICATE REQUEST-----
MIICzjCCAbYCAQAwgYgxFDASBgNVBAMMC2V4YW1wbGUuY29tMQswCQYDVQQLDAJJ
VDEPMA0GA1UECAwGTG9uZG9uMRIwEAYDVQQKDAlBQ01FIEluYy4xIDAeBgkqhkiG
9w0BCQEWEWFkbWluQGV4YW1wbGUuY29tMQswCQYDVQQGEwJHQjEPMA0GA1UEBwwG
TG9uZG9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm+036O2PlUQb
KbSSs2ik6O6TYy6+Zsas5oAk3GioGLl1RW9Ni8kagqdnD69Et29m1vl5OIPsBoW3
OWb1aBW5e3J0x9prXI1W/fpvuP9NmrHBUN4ES17VliRpfVH3aHfPC8rKpv3GvHYO
cfOmMN+HfBZlUeKJKs6c5WmSVdnZB0R4UAWuQ30aHEBVqtrhgHqYDBokVe0/H4wm
wZEIQTINWniCOFR5UphJf5nP8ljGbmPxNTnfb/iHS/chjcjF7TGMG36e7EBoQijZ
EUQs5IBCeVefOnFLK5jLx+BC//X+FNzByDilTt+l28I/3ZN1ujhak73YFbWjjLR2
tjtp+LQgNQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAGIQVhXfuWdINNfceNPm
CkAGv4yzpx88L34bhO1Dw4PYWnoS2f7ItuQA5zNk9EJhjkwK8gYspK7mPkvHDbFa
Um7lPSWsm3gjd3pU7dIaHxQ+0AW9lOw5ukiBlO4t3qgt+jTVZ3EhMbR0jDSyjTrY
kTgfuqQrGOQSmLb5XviEtCcN0rseWib3fKIl8DM69JiA2AALxyk7DCkS1BqLNChT
pnbgvtlUhc4yFXNCtwPGskXIvLsCn2LRy+qdsPM776kDLgD36hK0Wu14Lpsoa/p+
ZRuwKqTjdaV23o2aUMULyCRuITlghEEkRdJsaXadHXtNd5I5vDJOAAt46PIXcyEZ
aQY=
-----END CERTIFICATE REQUEST-----


Этот конкретный CSR содержит публичный ключ сервера и информацию о компании ACME Inc., которая находится в Лондоне, Великобритания, и владеет доменом example.com.

Наконец, подписанный сертификат HTTPS выглядит примерно так:

-----BEGIN CERTIFICATE-----
MIIDjjCCAnYCCQCJdR6v1+W5RzANBgkqhkiG9w0BAQUFADCBiDEUMBIGA1UEAwwL
ZXhhbXBsZS5jb20xCzAJBgNVBAsMAklUMQ8wDQYDVQQIDAZMb25kb24xEjAQBgNV
BAoMCUFDTUUgSW5jLjEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBsZS5jb20x
CzAJBgNVBAYTAkdCMQ8wDQYDVQQHDAZMb25kb24wHhcNMTYwNDE5MTAzMjI1WhcN
MTcwNDE5MTAzMjI1WjCBiDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xCzAJBgNVBAsM
AklUMQ8wDQYDVQQIDAZMb25kb24xEjAQBgNVBAoMCUFDTUUgSW5jLjEgMB4GCSqG
SIb3DQEJARYRYWRtaW5AZXhhbXBsZS5jb20xCzAJBgNVBAYTAkdCMQ8wDQYDVQQH
DAZMb25kb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCb7Tfo7Y+V
RBsptJKzaKTo7pNjLr5mxqzmgCTcaKgYuXVFb02LyRqCp2cPr0S3b2bW+Xk4g+wG
hbc5ZvVoFbl7cnTH2mtcjVb9+m+4/02ascFQ3gRLXtWWJGl9Ufdod88Lysqm/ca8
dg5x86Yw34d8FmVR4okqzpzlaZJV2dkHRHhQBa5DfRocQFWq2uGAepgMGiRV7T8f
jCbBkQhBMg1aeII4VHlSmEl/mc/yWMZuY/E1Od9v+IdL9yGNyMXtMYwbfp7sQGhC
KNkRRCzkgEJ5V586cUsrmMvH4EL/9f4U3MHIOKVO36Xbwj/dk3W6OFqTvdgVtaOM
tHa2O2n4tCA1AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBABwwkE7wX5gmZMRYugSS
7peSx83Oac1ikLnUDMMOU8WmqxaLTTZQeuoq5W23xWQWgcTtfjP9vfV50jFzXwat
5Ch3OQUS53d06hX5EiVrmTyDgybPVlfbq5147MBEC0ePGxG6uV+Ed+oUYX4OM/bB
XiFa4z7eamG+Md2d/A1cB54R3LH6vECLuyJrF0+sCGJJAGumJGhjcOdpvUVt5gvD
FIgT9B04VJnaBatEgWbn9x50EP4j41PNFGx/A0CCLgbTs8kZCdhE4QFMxU9T+T9t
rXgaspIi7RA4xkSE7x7B8NbvSlgP79/qUe80Z7d8Oolva6dTZduByr0CejdfhLhi
mNU=
-----END CERTIFICATE-----


Все части связаны и должны соответствовать друг другу. Последний сертификат был сгенерирован только ради примера — это так называемый самоподписанный сертификат, потому что он не подписан признанным центром сертификации.

Мы проиллюстрируем этот процесс реальными шагами, которые нужно сделать в cPanel, Linux, FreeBSD и Windows. Это универсальный процесс, подходящий для всех типов сертификатов. Если вы хотите получить бесплатный сертификат DV, то следуйте другим процедурам, описанным в разделах Let’s Encrypt и Cloudflare.

Шаг 1. Создать секретный ключ и запрос на получение сертификата


В следующих примерах мы будем использовать 2048-битные сертификаты RSA, по причине их большей совместимости. Если ваш провайдер, у которого установлен сервер, поддерживает ECC (например, если вы не пользуетесь услугами Heroku или AWS), то можете предпочесть использовать ECC.

cPanel


  1. Войдите в cPanel своего хоста.
  2. Прокрутите вниз до раздела “Security” и нажмите “SSL/TLS”.


    Раздел “Security” в cPanel (см. большую версию)

  3. Теперь вы в разделе “SSL/TLS Manager”. Нажмите “Private Keys (KEY)” для создания нового секретного ключа.


    “SSL/TLS Manager” в cPanel (см. большую версию)

  4. Вас перенаправят на страницу “Generate, Paste or Upload” для нового “Private
    Key”. Там выберете 2048 бит в выпадающем меню и нажмите “Generate”.


    Управление секретным ключом (“Private Key”) в cPanel (см. большую версию)

  5. Будет сгенерирован новый секретный ключ, а вы получите подтверждение на экране:


    Подтверждение секретного ключа в cPanel (см. большую версию)

  6. Если вернётесь назад в раздел “Private Keys”, то увидите там новый ключ:


    Раздел “Private Keys” в cPanel с новым сгенерированным ключом (см. большую версию)

  7. Вернитесь в раздел “SSL/TLS Manager”. Нажмите “Certificate Signing Requests (CSR)” для создания нового запроса на получение сертификата.


    Раздел “SSL/TLS Manager” в cPanel (см. большую версию)

  8. Теперь вы увидите форму “Generate Service Request”. Выберите созданный ранее секретный ключ и заполните поля. Правильно ответьте на все вопросы (они будут открыты для просмотра в вашем подписанном сертификате!), особенное внимание уделите разделу “Domains”, который должен в точности совпадать с доменным именем, для которого вы запрашиваете сертификат HTTPS. Включите туда только домен верхнего уровня (example.com); центр сертификации обычно сам добавляет поддомен www (то есть www.example.com). По окончании нажмите кнопку “Generate”.


    Форма “Create New Certificate Signing Request” в cPanel (см. большую версию)

  9. Будет сгенерирован новый CSR, а вы увидите окно с подтверждением:


    Подтверждение создания CSR в cPanel (см. большую версию)

  10. Если вы вернётесь назад в раздел “Certificate Signing Request”, то увидите там новый CSR:


    Раздел “Certificate Signing Request” в cPanel с новым сгенерированным CSR (см. большую версию)


Linux, FreeBSD


Убедитесь, что установлен OpenSSL. Вы можете проверить это:

openssl version

Если нет, то откройте консоль и установите его для своей платформы:

  • Debian, Ubuntu и клоны
    sudo apt-get install openssl
  • Red Hat, CentOS и клоны
    sudo yum install openssl
  • FreeBSD
    make -C /usr/ports/security/openssl install clean

Затем сгенерируйте секретный ключ и CSR одной командой:

openssl req -newkey rsa:2048 -nodes -keyout example.com.key -out example.com.csr

Секретный ключ будет сгенерирован, а вам нужно ответить на несколько вопросов для CSR:

Generating a 2048 bit RSA private key
........................+++
................................................................+++
writing new private key to 'example.com.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter ‘.', the field will be left blank.


Правильно ответьте на все вопросы (они будут открыты для просмотра в вашем подписанном сертификате!), особенное внимание уделите разделу “Common Name” (например, сервер FQDN или ВАШЕ имя), который должен в точности совпадать с доменным именем, для которого вы запрашиваете сертификат HTTPS. Включите туда только домен верхнего уровня (example.com); центр сертификации обычно сам добавляет поддомен www (то есть www.example.com):

Country Name (2 letter code) [AU]:GB
State or Province Name (full name) [Some-State]:London
Locality Name (eg, city) []:London
Organization Name (eg, company) [Internet Widgits Pty Ltd]:ACME Inc.
Organizational Unit Name (eg, section) []:IT
Common Name (e.g. server FQDN or YOUR name) []:example.com
Email Address []:admin@example.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:


Internet Information Server под Windows


  1. Откройте “Start” -> “Administrative Tools” -> “Internet Information Services (IIS) Manager”. Нажмите на имя сервера. Двойным щелчком откройте “Server Certificates” в средней колонке:


    Откройте “Internet Information Services (IIS) Manager”. Двойным щелчком откройте “Server Certificates”. (см. большую версию)

  2. Нажмите “Create Certificate Request” в правой колонке.


    Нажмите “Create Certificate Request” в правой колонке. (см. большую версию)

  3. Введите информацию о своей организации, особое внимание уделите графе “Common Name”, значение в которой должно соответсвовать вашему доменному имени. Нажмите “Next”.


    Введите информацию о своей организации. (см. большую версию)

  4. Оставьте значение по умолчанию в поле “Cryptographic Service Provider.” Установите “Bit length” на 2048. Нажмите “Next”.


    Установите “Bit length” на 2048. (см. большую версию)

  5. Выберите место для сохранения сгенерированного CSR и нажмите “Finish”.


    Выберите место для сохранения сгенерированного CSR и нажмите “Finish”. (см. большую версию)


Шаг 2. Приобретение сертификата HTTPS


Чтобы получить сертификат для вашего веб-сайта, сначала купите кредит на сертификат HTTPS выбранного типа (DV, OV, EV, один сайт, несколько сайтов, поддомены — см. выше) у продавца сертификатов. По окончании процесса вам нужно будет отправить запрос на получение сертификата, который потратит купленный кредит для выбранного домена. Вас попросят предоставить (то есть вставить в поле для загрузки) весь текст CSR, включая строки -----BEGIN CERTIFICATE REQUEST----- и -----END CERTIFICATE REQUEST-----. Если вам нужен сертификат EV или OV, то нужно будет указать юридическое лицо, для которого вы запрашиваете сертификат. У вас также могут попросить дополнительные документы, подтверждающие тот факт, что вы представляете эту компанию. Затем регистратор сертификатов проверит ваш запрос (и все сопутствующие документы) и выдаст подписанный сертификат HTTPS.

Получение сертификата HTTPS


У вашего хостинг-провайдера или регистратора HTTPS может быть другая процедура регистрации, но общая логика одинакова.

  1. Найти продавца сертификатов HTTPS.
  2. Выбрать тип сертификата (DV, OV, EV, один сайт, несколько сайтов, поддомены) и добавить его в корзину. Выбрать предпочитаемый метод оплаты и совершить платёж.
  3. Активировать новый сертификат HTTPS для своего домена. Вы можете или вставить в форму, или загрузить файл с запросом на подпись сертификата. Система извлечёт информацию для сертификата из CSR.
  4. Вас попросят выбрать метод «утверждения контроля домена» (“Domain Control Validation”, DCV) — либо по электронной почте, либо загрузкой файла HTML (на основе HTML), либо путём добавления записи TXT к своему файлу доменной зоны (на основе DNS). Следуйте инструкциям по указанному методу DCV для утверждения.
  5. Подождите несколько минут, пока осуществляется утверждение и готовится сертификат HTTPS. Скачайте сертификат.

Самоподписанные сертификаты


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

Например, выше опубликован самоподписанный сертификат — вы можете использовать его для домена example.com, и он будет работать в течение своего срока действия.

Можно создать самоподписанный сертификат на любой платформе, где есть OpenSSL.

openssl x509 -signkey example.com.key -in example.com.csr -req -days 365 -out example.com.crt

Как только сертификат сгенерирован, вам нужно установить его на свой сервер. Если у вас хостинг и сервис регистрации HTTPS от одного провайдера (многие хостинг-провайдеры также продают сертификаты HTTPS), то может действовать автоматическая процедура установки и активации вашего нового сертификата HTTPS для веб-сайта. Если вы хоститесь где-то в другом месте, то нужно скачать сертификат и сконфигурировать сервер для его использования.

Шаг 3. Установка сертификата HTTPS для своего веб-сайта


cPanel


  1. Вернитесь в “SSL/TLS Manager”. Нажмите “Certificates (CRT)” для импортирования нового сертификата.


    Раздел “SSL/TLS Manager” в cPanel (см. большую версию)

  2. Вас перенаправят на страницу “Paste, Upload or Generate” для нового “Certificate”. Вставьте содержимое файла сертификата, полученного от регистратора HTTPS, или загрузите его с помощью кнопки “Browse”.


    Импорт нового сертификата HTTPS в cPanel (см. большую версию)

  3. После того как вы вставили содержимое нового сертификата HTTPS, оно будет проанализировано, а чисто текстовые значения покажут вам для подтверждения. Просмотрите содержимое и нажмите кнопку “Save Certificate”.


    Просмотр содержимого и подтверждение сертификата HTTPS в cPanel (см. большую версию)

  4. Новый сертификат будет сохранён, а вы увидите окно с подтверждением.


    Подтверждение сертификата HTTPS в cPanel (см. большую версию)

  5. Если вернётесь в раздел “Certificates (CRT)”, то увидите там свой новый сертификат HTTPS.


    Страница “Certificates” в cPanel с новым сертификатом HTTPS. (см. большую версию)

  6. Вернитесь в раздел “SSL/TLS Manager”. Нажмите “Install and Manage SSL for your website (HTTPS)”, чтобы присвоить существующему веб-сайту новый сертификат.


    Раздел “SSL/TLS Manager” в cPanel. (см. большую версию)

  7. Вам будет предложена новая форма “Install an SSL Website”. Нажмите кнопку “Browse Certificates” и выберите свой сертификат HTTPS. Выберите домен своего веб-сайта из выпадающего меню (если он не выбран автоматически) и проверьте значения полей “Certificate” и “Private Key”.


    Форма “Install an SSL Website” в cPanel. (см. большую версию)


Проверьте, что у вас есть доступ к веб-сайту по адресу https://www.example.com. Если всё работает нормально, то вы, вероятно, захотите поставить постоянный редирект HTTP-трафика на HTTPS. Для этого нужно добавить несколько строчек в файл .htaccess (если у вас веб-сервер Apache) в корневой директории на своём сервере.

RewriteEngine On

RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]


Если файл .htaccess уже существует, то вставьте только строчки RewriteCond и RewriteRule сразу после существующей директивы RewriteEngine On.

Linux, FreeBSD


Разместите в соответствующих директориях сгенерированный секретный ключ (example.com.key), запрос на подпись сертификата (example.com.csr) и действительный сертификат HTTPS (example.com.crt):

  • Debian, Ubuntu и клоны, FreeBSD
    cp example.com.crt /etc/ssl/certs/
    cp example.com.key /etc/ssl/private/
    cp example.com.csr /etc/ssl/private/
  • Red Hat, CentOS и клоны
    cp example.com.crt /etc/pki/tls/certs/
    cp example.com.key /etc/pki/tls/private/
    cp example.com.csr /etc/pki/tls/private/
    restorecon -RvF /etc/pki

Файлы должны принадлежать руту и быть защищены настройкой разрешения 600.

  • Debian, Ubuntu и клоны
    chown -R root. /etc/ssl/certs /etc/ssl/private
    chmod -R 0600 /etc/ssl/certs /etc/ssl/private
  • Red Hat, CentOS и клоны
    chown -R root. /etc/pki/tls/certs /etc/pki/tls/private
    chmod -R 0600 /etc/pki/tls/certs /etc/pki/tls/private
  • FreeBSD
    chown -R root:wheel /etc/ssl/certs /etc/ssl/private
    chmod -R 0600 /etc/ssl/certs /etc/ssl/private

Apache


Чтобы активировать HTTPS на своём сайте, нужно сделать следующее:

  • убедиться, что на сервере установлен mod_ssl,
  • загрузить на сервер файл полученного сертификата HTTPS (.crt),
  • отредактировать файлы конфигурации сервера Apache.

Начните с проверки mod_ssl. В зависимости от операционной системы, должен работать один из вариантов:

apache2 -M | grep ssl
или
httpd -M | grep ssl

Если mod_ssl установлен, то вы получите такой ответ…

ssl_module (shared)
Syntax OK


… или нечто похожее.

Если он не установлен или не работает, то попробуйте это:

  • Debian, Ubuntu и клоны
    sudo a2enmod ssl
    sudo service apache2 restart
  • Red Hat, CentOS и клоны
    sudo yum install mod_ssl
    sudo service httpd restart
  • FreeBSD
    make -C /usr/ports/www/apache24 config install clean
    apachectl restart

Отредактируйте файл конфигурации Apache (httpd.conf):

  • Debian, Ubuntu
    /etc/apache2/apache2.conf
  • Red Hat, CentOS
    /etc/httpd/conf/httpd.conf
  • FreeBSD
    /usr/local/etc/apache2x/httpd.conf


Listen			80
Listen			443


	ServerName example.com
	ServerAlias www.example.com
	Redirect 301 / https://www.example.com/



	ServerName example.com
	Redirect 301 / https://www.example.com/



	ServerName www.example.com
	...
	SSLEngine on
	SSLCertificateFile/path/to/signed_certificate_followed_by_intermediate_certs
	SSLCertificateKeyFile /path/to/private/key

	# Uncomment the following directive when using client certificate authentication
	#SSLCACertificateFile  /path/to/ca_certs_for_client_authentication

	# HSTS (mod_headers is required) (15768000 seconds = 6 months)
	Header always set Strict-Transport-Security "max-age=15768000"
	...


# intermediate configuration, tweak to your needs
SSLProtocol			all -SSLv3
SSLCipherSuite		ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
SSLHonorCipherOrder	on
SSLCompression			off
SSLSessionTickets		off

# OCSP Stapling, only in httpd 2.3.3 and later
SSLUseStapling					on
SSLStaplingResponderTimeout		5
SSLStaplingReturnResponderErrors	off
SSLStaplingCache				shmcb:/var/run/ocsp(128000)

Эта конфигурация была сгенерирована с помощью упомянутого ранее Mozilla SSL Configuration Generator. С его помощью проверьте актуальность конфигурации. Отредактируйте правильные пути для сертификата и секретного ключа. Показанная здесь конфигурация сгенерирована с промежуточными настройками — прочитайте об ограничениях и конфигурациях браузера для каждой настройки, прежде чем выбрать наиболее подходящую для себя.

В коде были сделаны некоторые изменения, чтобы обрабатывать редиректы с HTTP на HTTPS, а также с не-www на домен с www (полезно для задач SEO).

Nginx


Отредактируйте файл конфигурации nginx (nginx.conf):

  • Debian, Ubuntu, Red Hat, CentOS
    /etc/nginx/nginx.conf
  • FreeBSD
    /usr/local/etc/nginx/nginx.conf

    server {
    	listen 80 default_server;
    	listen [::]:80 default_server;
    
    	# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
    	return 301 https://$host$request_uri;
    }
    
    server {
    	listen 443 ssl http2;
    	listen [::]:443 ssl http2;
    
    	# certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
    	ssl_certificate /path/to/signed_cert_plus_intermediates;
    	ssl_certificate_key /path/to/private_key;
    	ssl_session_timeout 1d;
    	ssl_session_cache shared:SSL:50m;
    	ssl_session_tickets off;
    
    	# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
    	ssl_dhparam /path/to/dhparam.pem;
    
    	# intermediate configuration. tweak to your needs.
    	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    	ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
    	ssl_prefer_server_ciphers on;
    
    	# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
    	add_header Strict-Transport-Security max-age=15768000;
    
    	# OCSP Stapling	---
    	# fetch OCSP records from URL in ssl_certificate and cache them
    	ssl_stapling on;
    	ssl_stapling_verify on;
    
    	## verify chain of trust of OCSP response using Root CA and Intermediate certs
    	ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;
    	resolver ;
    
    	....
    }

Эта конфигурация была сгенерирована с помощью упомянутого ранее Mozilla SSL Configuration Generator. С его помощью проверьте актуальность конфигурации. Отредактируйте правильные пути для сертификата и секретного ключа. Показанная здесь конфигурация сгенерирована с промежуточными настройками — прочитайте об ограничениях и конфигурациях браузера для каждой настройки, прежде чем выбрать наиболее подходящую для себя.

Генератор автоматически генерирует код для обработки редиректов с HTTP на HTTPS и изначально активирует поддержку HTTP/2!

Internet Information Server под Windows


  1. Откройте “Start” -> “Administrative Tools” -> “Internet Information Services (IIS) Manager”. Нажмите на название сервера. Двойным щелчком откройте “Server Certificates” в средней колонке.


    Откройте “Internet Information Services (IIS) Manager”. Двойным щелчком откройте “Server Certificates”. (см. большую версию)

  2. Нажмите “Complete Certificate Request” в правой колонке.


    Нажмите “Complete Certificate Request” в правой колонке. (см. большую версию)

  3. Выберите файл подписанного сертификата (example.com.crt), который вы получили от центра сертификации. Введите какое-нибудь название в поле “Friendly name”, чтобы различать сертификаты впоследствии. Поместите новый сертификат в хранилище сертификатов “Personal” (IIS 8+). Нажмите “OK”.


    Выберите файл подписанного сертификата. (см. большую версию)

  4. Если процесс прошёл нормально, вы должны увидеть сертификат на вкладке “Server Certificates”.


    Вы должны увидеть сертификат на вкладке “Server Certificates”. (см. большую версию)

  5. Расширьте вкладку с названием сервера. В разделе “Sites” выберите веб-сайт, которому вы хотите присвоить сертификат HTTPS. Нажмите “Bindings” в правой колонке.


    Выберите веб-сайт и нажмите “Bindings”. (см. большую версию)

  6. В окне “Site Bindings” нажмите кнопку “Add”.


    Нажмите кнопку “Add”. (см. большую версию)

  7. В новом окне выберите:
    • “Type”: “https”
    • “IP address”: “All Unassigned”
    • “Port”: “443”

    В поле “SSL Certificate” выберите установленный сертификат HTTPS по присвоенному ему имени. Нажмите “OK”.


    Выберите “HTTPS” и укажите установленный сертификат HTTPS. (см. большую версию)<

IBM Watson и кибербезопасность: как когнитивная система защищает ценные данные

Понедельник, 03 Июля 2017 г. 18:28 + в цитатник


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

Интересно, что в большинстве случаев проблема даже не сам взлом, а ликвидация его последствий – это долго, дорого, плюс негативно отражается на репутации компании. Примером можно считать проникновение ransomware в сеть компании — не так давно хостинг-провайдеру из Южной Кореи пришлось заплатить около 1 млн долларов США злоумышленникам за расшифровку своих данных и данных клиентов. Но в наше время $1 млн — это вовсе не предел. Избежать подобных проблем помогает когнитивная система IBM Watson, которую не первый год обучают премудростям информационной безопасности.

И в этом нет ничего удивительного, поскольку новые угрозы появляются буквально каждый день. Если корпоративные системы не «знают» о новой опасности, то они могут пропустить угрозу, и тогда сеть компании может быть скомпрометирована. Кроме того, многие инструменты злоумышленников работают настолько быстро и эффективно, что человек просто не способен заметить проблему сразу. Поэтому все чаще специалисты по кибербезопасности прибегают к помощи разного рода программных платформ, включая нейросети. На данный момент когнитивная система Watson, а точнее, Watson for CyberSecurity, один из наиболее эффективных для защиты данных инструментов. Сервис работает и самостоятельно, и в качестве элемента других облачных сервисов.




Стоит отметить, что Watson for CyberSecurity– это один из относительно новых облачных сервисов IBM. Кибербезопасность было решено выделить в качестве самостоятельного инструмента около двух лет назад. Идея основывалась на том, что каким бы хорошим специалистом ни был бы сотрудник компании, он просто не в состоянии отслеживать все новые угрозы в сфере кибербезопасности, поскольку их слишком много. Необходим цифровой помощник, который сам бы анализировал многочисленные источники по инфобезу (в качестве примера можно привести сотни тысяч статей специалистов, которые они публикуют в своих блогах, а также на страницах специализированных ресурсов).

Эта информация включает данные об уязвимостях, вирусах, новых программных инструментах, эксплоитах и т.п. (сейчас, по данным ряда источников, количество разнообразных интернет-угроз достигло 75 000). Watson for CyberSecurity все это усваивает и затем может применить на практике. Исходные материалы анализируются, составляется индекс, проставляются зависимости. Другими словами, данные структурируются, и в дальнейшем их можно использовать для работы как с новыми, так и со старыми угрозами. Речь идет о глубоком машинном обучении и самообучении. Специалисты IBM работают над тем, чтобы система могла искать и осваивать новые источники данных, анализируя связанную с ними информацию.

Watson for CyberSecurity работает со всеми этими данными, используя информацию о десятках тысяч интернет-угроз, которые внесены в базу IBMX-ForceExchange. Плюс ко всему, постоянно анализируются и новые материалы, которые появляются в сети.




Как уже говорилось выше, Watson for CyberSecurity может использоваться в качестве элемента других сервисов. Одним из них является IBMQradar. Его создали в качестве цифрового ассистента для аналитиков, занятых вопросами сетевой безопасности. Работает этот сервис со скоростью, которая недоступна никому из людей. А в сфере кибербезопасности скорость, зачастую, решает все. Обнаружив проблему, IBM Watson ищет все связанные с ней данные, а затем показывает специалисту уже обработанную и структурированную информацию, плюс предложения по решению проблемы.

Работает все это по достаточно простому алгоритму:
• Выявляется угроза и причины, которые привели к ее появлению;
• Анализируется вся доступная информация;
• Данные структурируются и отправляются в Watson for CyberSecurity;
• Идет поиск эффективных средств решения проблемы.

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

При необходимости можно выделять приоритет для различных угроз, чтобы системы занимались наиболее опасными проблемами, а затем уже работали с тем, что можно оставить «на потом».

В целом, сервисы, связанные с Watson for CyberSecurity, помогают работать людям. Самые важные решения принимает человек, а компьютерная система помогает решить проблему, подготавливая объективную и полную информацию о ситуации и предлагая несколько вариантов для ликвидации возникающих угроз.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332214/


Метки:  

Шесть мифов о Big Data

Понедельник, 03 Июля 2017 г. 18:15 + в цитатник
Наталья Гараханова, директор по маркетингу в агентстве Black Engine и аспирант курса «Управление продуктом», развенчивает мифы в области Big Data.

Big Data в последнее время стали трендом. Но что это такое, понятно не всем.

Многие думают, что большие данные — это либо просто огромный массив данных, либо простой и дешевый способ их хранения.

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



Миф 1. Машины на основе Big Data заменят людей


Миф основан на статистике, которая говорит, что около 80% рабочих задач можно автоматизировать. Это большая проблема с точки зрения образования: огромному количеству кадров надо будет переучиваться на другие профессии. В чем-то этот миф прав. Многим людям придется отказаться от текущей деятельности. Но кардинальной безработицы не будет.

Новые области знаний рождают новые профессии.

Например, если мы станем проектировать систему эффективной таргетированной рекламы с использованием Big Data, то потребуется специалист в новой области знаний на пересечении математики и маркетинга. Именно он будет следить за работой системы и исправностью механизма. Сейчас на рынке уже начинают появляться такие люди на позиции «Data Management Platform Operator».

Контролировать и мониторить процессы всегда должен человек.

Не стоит бояться, что люди останутся без работы. Появятся другие профессии, как это было много раз в истории человечества. Ведь и печатный станок когда-то изменил историю книгопечатания и сделал ненужной перепись книг.

Миф 2. Данные нужно собирать в одном месте


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

Агрегация данных для обучения искусственного интеллекта, алгоритмов и машин уже перестала быть настолько важной, как прежде. Сейчас корпорации вроде Google и Apple активно работают над тем, чтобы сделать свои устройства частью распределенной сети машинного обучения.

Google по своим устройствам имеет одну сеть, которая работает одновременно. Apple идет по его стопам в области новых технологий на основе больших данных. Например, в патенте Google «Federated Learning» все построено на распределенном обучении. Данные с телефона не утекают в определенный Data-центр, а приезжает модель, обучается и начинает коммуницировать с другими моделями мобильных телефонов или через общий хаб. Таким образом сохраняется приватность.

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

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

Миф 3. Big Data нужны всем


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

Компания Gartner провела исследования, какие кейсы наиболее популярны в различных индустриях. Все данные были представлены в виде тепловой карты кейсов, реализованных в бизнесе. Чем краснее квадратик (чем больше процент), тем больше из опрошенных компаний реализовали кейсы на основе Big Data у себя в бизнесе.



Как видно из исследования, больше всех кейсов реализовано в области маркетинга, таргетирования и customer experience. То есть в самой актуальной области клиентской аналитики и работы с клиентами — в сфере продаж b2c. Меньше всего кейсов реализовали в государственном секторе, но область эффективности процессов для этого сектора наиболее актуальна. Сфера новых продуктов пока остается для этого сектора не существенной.

А вот такой опрос проводил Tech Pro Research:



Интересно, что в области образования и здравоохранения большие данные оказались не так популярны. А вот финансовые и государственные структуры, инжиниринг и ИТ активно пользуются этими методами. Причем телеком — самая популярная сфера для внедрения технологий Big Data.

Big Data не нужны, если в вашей компании:
  • сотрудники в состоянии обработать и автоматизировать данные по клиентам с помощью обычных CRM-систем;
  • планирование, учет и контроль бизнес-процессов вполне реализуем с помощью ERP-систем;
  • раньше объединяли данные из различных источников информации, обрабатывали их, оценивали полученный результат с помощью BI-систем и не испытывали со всем вышеперечисленным никаких трудностей.

Миф 4. Big Data подходят только большим компаниям


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

Такие системы, как Google Analytics или Яндекс.Метрика используются исключительно для оценки посещаемости ресурса, и никаких дополнительных отчетов с их помощью не формируется. Многие компании до сих пор хранят свои данные в старом добром Excel, и понятно, что в этом случае говорить об использовании методов Big Data пока рано. В российском ИТ в основном все ссылаются на те же Google Analytics, простейшую систему с использованием больших данных, но эффективную для сбора и систематизации данных. Некоторые успешно пользуются Retail Rocket — платформой для мультиканальной персонализации интернет-магазинов на основе Big Data.

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

На Западе эта тема более активно обсуждается. В пример приводятся сервисы Followerwonk, YouTube Analytics и Tweriod. Некоторые представители малого и среднего бизнеса успешно пользуются облачными решениями, например, платформой Amazon, построенной специально для вычислений на основе Big Data.

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

Если малый и средний бизнес хочет расширить объемы задач в исследовательских и производственных целях, то использование Big Data позволит не только систематизировать данные, но и увеличить скорость их обработки.

Миф 5. Все данные должны быть обработаны


Этот миф часто встречается, когда необходимо построить модель из распределенной файловой системы. Поставщики решений утверждают, что «все данные должны быть обработаны». Здесь кроется противоречие. Между количеством обработанной информации и результатом работы нет взаимосвязи. Увеличение объема обработанных данных не влияет на увеличение точности итоговой модели.

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

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

Больше не значит лучше.

Реальная значимость Big Data не в том, чтобы обработать как можно больше данных, а в том, чтобы весь объем данных был сегментирован и разбит на кластеры, и в построении большого количества моделей для небольших кластеров.

Миф 6. Big Data дают мгновенный и волшебный результат


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

С Big Data необходимо много работать. Это подразумевает создание сложного проекта, сбор данных, создание инфраструктуры, проектирование модели и затем поиск необходимых данных, которые помогут улучшить процессы.

Главная задача — встроить модели в бизнес-процессы в продакшене и выгодно использовать найденные решения.

Это всего лишь малая часть суеверий и заблуждений о больших данных. Big Data — не волшебная палочка. Это инструмент, который в руках умелого аналитика поможет правильно выстроить ваши бизнес-процессы.

От редакции Нетологии


Нетология проводит набор на курсы по Big Data:

1. Программа «Big Data: основы работы с большими массивами данных»


Для кого: инженеры, программисты, аналитики, маркетологи — все, кто только начинает вникать в технологию Big Data.

  • введение в историю и основы технологии;
  • способы сбора больших данных;
  • типы данных;
  • основные и продвинутые методы анализа больших данных;
  • основы программирования, архитектуры хранения и обработки для работы с большими массивами данных.

Формат занятий: онлайн.

Подробности по ссылке -> http://netolo.gy/dAZ

2. Программа «Data Scientist»


Для кого: специалисты, работающие или собирающиеся работать с Big Data, а также те, кто планирует построить карьеру в области Data Science. Для обучения необходимо владеть как минимум одним из языков программирования (желательно Python) и помнить программу по математике старших классов (а лучше вуза).

Темы курса:
  • экспресс-обучение основным инструментам, Hadoop, кластерные вычисления;
  • деревья решений, метод k-ближайших соседей, логистическая регрессия, кластеризация;
  • уменьшение размерности данных, методы декомпозиции, спрямляющие пространства;
  • введение в рекомендательные системы;
  • распознавание изображений, машинное зрение, нейросети;
  • обработка текста, дистрибутивная семантика, чатботы;
  • временные ряды, модели ARMA/ARIMA, сложные модели прогнозирования.

Формат занятий: офлайн, г. Москва, центр Digital October. Преподают специалисты из Yandex Data Factory, Ростелеком, «Сбербанк-Технологии», Microsoft, OWOX, Clever DATA, МТС.

Подробности по ссылке -> http://netolo.gy/dA0
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332184/


Метки:  

[Из песочницы] Решение задачи коммивояжера алгоритмом Литтла с визуализацией на плоскости

Понедельник, 03 Июля 2017 г. 18:10 + в цитатник

Известная как минимум с 19 века задача коммивояжера имеет множество способов решения и неоднократно описана. Ее оптимизационная версия является NP-трудной, поэтому оптимальное решение можно получить либо полным перебором, либо оптимизированным полным перебором — методом ветвей и границ.


Пытаясь запрограммировать алгоритм Литтла (частный случай метода ветвей и границ), я понял, что в рунете крайне трудно найти его правильное описание понятным языком и разобранную программную реализацию. Однако имеющиеся в изобилии описания обманчиво правдоподобны на данных малого размера и с трудом проверяются без визуализации.


animation


Метод ветвей и границ


Алгоритм Литтла является частным случаем МВиГ, т.е. в худшем случае его сложность равна сложности полного перебора. Теоретическое описание выглядит следующим образом:


Имеется множетво S всех гамильтоновых циклов рафа. На каждом шаге в S ищется ребро (i, j), исключение которого из маршрута максимально увеличит оценку снизу. Далее происходит разбиение множества на два непересекающихся S1 и S2. S1 — все циклы, содержащие ребро (i, j) и не содержащие (j, i). S2 — все циклы, не содержащие (i, j). Далее вычисляется оценка снизу для длины пути каждого множества и, если она превышает длину уже найденного решения, множество отбрасывается. Если нет — множества S1 и S2 обрабатываются так же, как и S.


Алгоритмическое описание


Имеется матрица расстояний M. Диагональ заполняется бесконечными значениями, т.к. не должно возникать преждевременных циклов. Также имеется переменная, хранящая нижнюю границу.


Стоит оговориться, что нужно вести учет двух видов бесконечностей — одна добавляется после удаления строки и столбца из матрицы, чтобы не возникало преждевременных циклов, другая — при отбрасывании ребер. Случаи будут рассмотрены чуть позже. Первую бесконечность обозначим как inf1, вторую — inf2. Диагональ заполнена inf1.


  1. Из каждого элемента каждой строки вычитается минимальный элемент данной строки. При этом минимальный элемент строки прибавляется к нижней границе
  2. Из каждого столбца аналогично вычитается минимальный элемент и прибавляется к нижней границе.
  3. Для каждого нулевого элемента M(i, j) вычисляется коэффициент, равный сумме минимальных элементов строки i и столбца j, исключая сам элемент (i, j). Этот коэффициент показывает, насколько гарантированно увеличится нижняя граница решения, если исключить из него ребро (i, j)
  4. Ищется элемент с максимальным коэффициентом. Если их несколько, можно выбрать любой (все равно оставшиеся будут рассмотрены на следующих шагах рекурсии)
  5. Рассматриваются 2 матрицы — M1 и M2. M1 равна M с удаленными строкой i и столбцом j. В ней находится столбец k и строка l, в которых не содержится inf1 и элемент M(k, l) приравнивается inf1. Как было сказано ранее, это необходимо во избежание преждевременных циклов (т.е. на первых этапах (k, l) == (j, i)). Матрица M1 соответствует множеству, сожержащему ребро (i, j). Вместе с удалением столбца и строки ребро (i, j) включается в путь.
  6. M2 равна матрице M, у которой элемент (i, j) равен inf2. Матрица соответствует множетсву путей, не сожержащих ребро (i, j) (важно понимать, что ребро (j, i) при этом не исключается).
  7. Переход к п.1 для матриц M1 и M2.

Эвристика состоит в том, что у матрицы M1 нижняя граница не больше, чем у матрицы M2 и в первую очередь рассматривается ветвь, содержащая ребро (i, j).


Пример


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


Так что приведу шаги поиска оптимального пути для этой матрицы.


0 1 2 3 4
0 inf 20 18 12 8
1 5 inf 14 7 11
2 12 18 inf 6 11
3 11 17 11 inf 12
4 5 5 5 5 inf

Пошаговое решение
         0        1        2        3        4
0     inf1    20.00    18.00    12.00     8.00
1     5.00     inf1    14.00     7.00    11.00
2    12.00    18.00     inf1     6.00    11.00
3    11.00    17.00    11.00     inf1    12.00
4     5.00     5.00     5.00     5.00     inf1

After subtracting:
         0        1        2        3        4
0     inf1    12.00    10.00     4.00     0.00
1     0.00     inf1     9.00     2.00     6.00
2     6.00    12.00     inf1     0.00     5.00
3     0.00     6.00     0.00     inf1     1.00
4     0.00     0.00     0.00     0.00     inf1

edge (4, 1)
         0        2        3        4
0     inf1    10.00     4.00     0.00
1     0.00     9.00     2.00     inf1
2     6.00     inf1     0.00     5.00
3     0.00     0.00     inf1     1.00

After subtracting:
         0        2        3        4
0     inf1    10.00     4.00     0.00
1     0.00     9.00     2.00     inf1
2     6.00     inf1     0.00     5.00
3     0.00     0.00     inf1     1.00

edge (3, 2)
         0        3        4
0     inf1     4.00     0.00
1     0.00     2.00     inf1
2     6.00     inf1     5.00

After subtracting:
         0        3        4
0     inf1     2.00     0.00
1     0.00     0.00     inf1
2     1.00     inf1     0.00

edge (0, 4)
         0        3
1     inf1     0.00
2     1.00     inf1

candidate solution(4 1) (3 2) (0 4) (1 3) (2 0)
cost: 43; record: 1.79769e+308
NEW RECORD
         0        3        4
0     inf1     2.00     0.00
1     0.00     0.00     inf1
2     1.00     inf1     0.00

not edge (0, 4)
         0        3        4
0     inf1     2.00     inf2
1     0.00     0.00     inf1
2     1.00     inf1     0.00

After subtracting:
         0        3        4
0     inf1     0.00     inf2
1     0.00     0.00     inf1
2     1.00     inf1     0.00

limit: 44; record:43
DISCARDING BRANCH
         0        2        3        4
0     inf1    10.00     4.00     0.00
1     0.00     9.00     2.00     inf1
2     6.00     inf1     0.00     5.00
3     0.00     0.00     inf1     1.00

not edge (3, 2)
         0        2        3        4
0     inf1    10.00     4.00     0.00
1     0.00     9.00     2.00     inf1
2     6.00     inf1     0.00     5.00
3     0.00     inf2     inf1     1.00

After subtracting:
         0        2        3        4
0     inf1     1.00     4.00     0.00
1     0.00     0.00     2.00     inf1
2     6.00     inf1     0.00     5.00
3     0.00     inf2     inf1     1.00

limit: 44; record:43
DISCARDING BRANCH
         0        1        2        3        4
0     inf1    12.00    10.00     4.00     0.00
1     0.00     inf1     9.00     2.00     6.00
2     6.00    12.00     inf1     0.00     5.00
3     0.00     6.00     0.00     inf1     1.00
4     0.00     0.00     0.00     0.00     inf1

not edge (4, 1)
         0        1        2        3        4
0     inf1    12.00    10.00     4.00     0.00
1     0.00     inf1     9.00     2.00     6.00
2     6.00    12.00     inf1     0.00     5.00
3     0.00     6.00     0.00     inf1     1.00
4     0.00     inf2     0.00     0.00     inf1

After subtracting:
         0        1        2        3        4
0     inf1     6.00    10.00     4.00     0.00
1     0.00     inf1     9.00     2.00     6.00
2     6.00     6.00     inf1     0.00     5.00
3     0.00     0.00     0.00     inf1     1.00
4     0.00     inf2     0.00     0.00     inf1

edge (3, 1)
         0        2        3        4
0     inf1    10.00     4.00     0.00
1     0.00     9.00     inf1     6.00
2     6.00     inf1     0.00     5.00
4     0.00     0.00     0.00     inf1

After subtracting:
         0        2        3        4
0     inf1    10.00     4.00     0.00
1     0.00     9.00     inf1     6.00
2     6.00     inf1     0.00     5.00
4     0.00     0.00     0.00     inf1

edge (0, 4)
         0        2        3
1     0.00     9.00     inf1
2     6.00     inf1     0.00
4     inf1     0.00     0.00

After subtracting:
         0        2        3
1     0.00     9.00     inf1
2     6.00     inf1     0.00
4     inf1     0.00     0.00

edge (0, 0)
         2        3
2     inf1     0.00
4     0.00     inf1

candidate solution(3 1) (0 4) (1 0) (2 3) (4 2)
cost: 41; record: 43
NEW RECORD
         0        2        3
1     0.00     9.00     inf1
2     6.00     inf1     0.00
4     inf1     0.00     0.00

not edge (1, 0)
         0        2        3
1     inf2     9.00     inf1
2     6.00     inf1     0.00
4     inf1     0.00     0.00

After subtracting:
         0        2        3
1     inf2     0.00     inf1
2     0.00     inf1     0.00
4     inf1     0.00     0.00

limit: 56; record:41
DISCARDING BRANCH
         0        2        3        4
0     inf1    10.00     4.00     0.00
1     0.00     9.00     inf1     6.00
2     6.00     inf1     0.00     5.00
4     0.00     0.00     0.00     inf1

not edge (0, 4)
         0        2        3        4
0     inf1    10.00     4.00     inf2
1     0.00     9.00     inf1     6.00
2     6.00     inf1     0.00     5.00
4     0.00     0.00     0.00     inf1

After subtracting:
         0        2        3        4
0     inf1     6.00     0.00     inf2
1     0.00     9.00     inf1     1.00
2     6.00     inf1     0.00     0.00
4     0.00     0.00     0.00     inf1

limit: 50; record:41
DISCARDING BRANCH
         0        1        2        3        4
0     inf1     6.00    10.00     4.00     0.00
1     0.00     inf1     9.00     2.00     6.00
2     6.00     6.00     inf1     0.00     5.00
3     0.00     0.00     0.00     inf1     1.00
4     0.00     inf2     0.00     0.00     inf1

not edge (3, 1)
         0        1        2        3        4
0     inf1     6.00    10.00     4.00     0.00
1     0.00     inf1     9.00     2.00     6.00
2     6.00     6.00     inf1     0.00     5.00
3     0.00     inf2     0.00     inf1     1.00
4     0.00     inf2     0.00     0.00     inf1

After subtracting:
         0        1        2        3        4
0     inf1     0.00    10.00     4.00     0.00
1     0.00     inf1     9.00     2.00     6.00
2     6.00     0.00     inf1     0.00     5.00
3     0.00     inf2     0.00     inf1     1.00
4     0.00     inf2     0.00     0.00     inf1

limit: 47; record:41
DISCARDING BRANCH
Solution tour:
0 4 2 3 1 0
Tour length:
41

Реализация


Шаг 1

Получение нулей в каждой строке и каждом столбце.


double LittleSolver::subtractFromMatrix(MatrixD &m) const {
    // сумма всех вычтенных значений
    double subtractSum = 0;
    // массивы с минимальными элементами строк и столбцов
    vector minRow(m.size(), DBL_MAX),
        minColumn(m.size(), DBL_MAX);
    // обход всей матрицы
    for (size_t i = 0; i < m.size(); ++i) {
        for (size_t j = 0; j < m.size(); ++j)
            // поиск минимального элемента в строке
            if (m(i, j) < minRow[i])
                minRow[i] = m(i, j);

        for (size_t j = 0; j < m.size(); ++j) {
            // вычитание минимальных элементов из всех
            // элементов строки, кроме бесконечностей
            if (m(i, j) < _infinity) {
                m(i, j) -= minRow[i];
            }
            // поиск минимального элемента в столбце после вычитания строк
            if ((m(i, j) < minColumn[j]))
                minColumn[j] = m(i, j);
        }
    }

    // вычитание минимальных элементов из всех
    // элементов столбца, кроме бесконечностей
    for (size_t j = 0; j < m.size(); ++j)
        for (size_t i = 0; i < m.size(); ++i)
            if (m(i, j) < _infinity) {
                m(i, j) -= minColumn[j];
            }

    // суммирование вычтенных значений
    for (auto i : minRow)
        subtractSum += i;

    for (auto i : minColumn)
        subtractSum += i;

    return subtractSum;
}

Шаг 2

Увеличение нижней границы и сравнение ее с рекордом.


// вычитание минимальных элементов строк и столбцов
// увеличение нижней границы
bottomLimit += subtractFromMatrix(matrix);
// сравнение верхней и нижней границ
if (bottomLimit > _record) {
    return;
}

Шаг 3

Расчет коэффициентов.


double LittleSolver::getCoefficient(const MatrixD &m, size_t r, size_t c) {
    double rmin, cmin;
    rmin = cmin = DBL_MAX;
    // обход строки и столбца
    for (size_t i = 0; i < m.size(); ++i) {
        if (i != r)
            rmin = std::min(rmin, m(i, c));
        if (i != c)
            cmin = std::min(cmin, m(r, i));
    }

    return rmin + cmin;
}

Поиск всех нулевых элементов и вычисление их коэффициентов.


// список координат нулевых элементов
list> zeros;
// список их коэффициентов
list coeffList;

// максимальный коэффициент
double maxCoeff = 0;
// поиск нулевых элементов
for (size_t i = 0; i < matrix.size(); ++i)
    for (size_t j = 0; j < matrix.size(); ++j)
        // если равен нулю
        if (!matrix(i, j)) {
            // добавление в список координат
            zeros.emplace_back(i, j);
            // расчет коэффициена и добавление в список
            coeffList.push_back(getCoefficient(matrix, i, j));
            // сравнение с максимальным
            maxCoeff = std::max(maxCoeff, coeffList.back());
        }

Шаг 4

Отбрасывание нулевых элементов с немаксимальными коэффициентами.


{ // область видимости итераторов
    auto zIter = zeros.begin();
    auto cIter = coeffList.begin();
    while (zIter != zeros.end()) {
        if (*cIter != maxCoeff) {
            // если коэффициент не максимальный, удаление элемента из списка
            zIter = zeros.erase(zIter);
            cIter = coeffList.erase(cIter);
        }
        else {
            ++zIter;
            ++cIter;
        }
    }
}

return zeros;

Шаг 5

Переход к множеству, содержащему ребро с максимальным штрафом.


auto edge = zeros.front();
// копия матрицы
auto newMatrix(matrix);
// из матрицы удаляются строка и столбец, соответствующие вершинам ребра
newMatrix.removeRowColumn(edge.first, edge.second);
// ребро iter добавляется к пути
auto newPath(path);
newPath.emplace_back(matrix.rowIndex(edge.first),
                     matrix.columnIndex(edge.second));
// добавление бесконечности для избежания преждевремнного цикла
addInfinity(newMatrix);
// обработка множества, содержащего ребро edge
handleMatrix(newMatrix, newPath, bottomLimit);

Шаг 6

Переход к множеству, не содержащему ребро с максимальным штрафом.


// переход к множеству, не сожержащему ребро edge
// снова копирование матрицы текущего шага
newMatrix = matrix;
// добавление бесконечности на место iter
newMatrix(edge.first, edge.second) = _infinity + 1;
// обработка множества, не сожержащего ребро edge
handleMatrix(newMatrix, path, bottomLimit);

Сравнение МВиГ с полным перебором


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


Ниже представлены графики сравнения МВиГ с полным перебором и среднее время работы реализованного мной алгоритма на различном количестве городов. Тестировалось на матрицах для случайно сгенерированных точек.


Сравнение метода ветвей и границ с полным перебором.


compare


Начиная с 9 городов полный перебор заметно проигрывает МВиГ. Начиная с 13 городов полный перебор занимает больше минуты.


Время работы МВиГ на различном количестве городов.


time


Графическое приложение


Также был сделан графический интерфейс на Qt с возможностью динамически смотреть на процесс решения. Как раз его рабочая область на гифке в шапке статьи. Итересующиеся могут скомпилировать и потрогать прогу руками.


Желтым цветом обозначен наилучший найденный путь и его длина находится в поле "Tour length".


Черным обозначены ребра, находящиеся в последнем просмотренном наборе.


gui


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


Вместо заключения


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


-> Исходный код

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332208/


Метки:  

[Перевод] Вышел GitLab 9.3: Code Quality и межпроектные графики конвейеров

Понедельник, 03 Июля 2017 г. 17:54 + в цитатник

Вышел GitLab 9.3: Code Quality и межпроектные графики конвейеров


image


В GitLab 9.3 мы представляем Code Quality, межпроектные графики конвейеров, индекс совместной разработки, улучшения локализации, описания сниппетов и многое другое!


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



В GitLab 9.3 добавлены отчеты Code Quality (качества кода), которые отображаются прямо в виджете мерж-реквеста. Эти отчеты позволяют мгновенно оценить влияние предложенных изменений на состояние вашего кода и проекта в целом. Благодаря этому сокращается время на ревью, а также появляется возможность находить ошибки перед мержем.


Современное production-level ПО, особенно то, что использует архитектуру микро-сервисов, зачастую состоит из множества различных проектов. В таком ПО очень важно понимать взаимодействие проектов. В GitLab 9.3 вы можете увидеть взаимосвязь upstream и downstream конвейеров при помощи межпроектных графиков конвейеров.


Также GitLab 9.3 предоставляет возможность оценить, как вы используете каждый элемент GitLab в сравнении с другими людьми. Для этого используется индекс совместной разработки. С помощью данного индекса можно легко оценить насколько эффективно вы используете систему по сравнению с другими пользователями, а также увидеть, какие элементы вашего рабочего процесса вы могли бы улучшить.


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





Приглашаем на нашу встречу!


MVP этого месяца — Huang Tao


Huang внес значительный вклад в локализацию GitLab на несколько новых языков. Спасибо, Huang!


GitLab Code Quality (EES, EEP)


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


Мы создали GitLab Code Quality для того, чтобы ускорить процесс код-ревью и предоставить пользователям возможность проводить сравнительную оценку качества кода и создавать соответствующие отчеты прямо в окне мерж-реквеста. Теперь, если качество вашего кода ухудшится, GitLab укажет вам на это, и вы сможете исправить ситуацию еще до мержа.


GitLab Code Quality


Больше информации о GitLab Code Quality в нашей документации


Межпроектные графики конвееров (EEP)


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


В GItLab 9.3 появилась возможность отображения связей upstream и downstream проектов прямо на графиках конвейеров, благодаря чему можно легко увидеть статус всей цепочки в одном окне. Теперь для отображения связей между проектами не нужно использовать переменную $CI_JOB_TOKEN с триггерами — графики создаются автоматически.


Больше информации о межпроектных графиках конвейеров в нашей документации


Multi-Project Pipeline Graphs


Индекс совместной разработки (CE, EES, EEP)


В прошлом сентябре мы анонсировали принципы совместной разработки (Conversational Development — ConvDev). Внедрение этой методологии ускоряет процесс разработки ПО от идеи до внедрения.


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


В данной версии этот индекс доступен только системным администраторам. Индекс формируется на основе деятельности всех активных пользователей вашего инстанса GitLab.


Больше информации об индексе совместной разработки в нашей документации


Conversational Development Index


Защищенные переменные для повышения безопасности конвейеров (CE, EES, EEP)


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


В GitLab 9.3 с введением защищенных переменных появляется дополнительный уровень безопасности для секретной информации, такой как полномочия развертывания. Теперь в Settings -> CI/CD Pipelines можно отметить переменную как «защищенную» (“protected”). Такая переменная будет доступна только для задач, выполняемых на защищенных ветках, благодаря чему доступ к ней будет открыт только для пользователей с соответствующими правами доступа.


Больше информации о защищенных секретных переменных в нашей документации


Protected Variables for Enhanced Pipelines Security


Централизованный лог пользовательских действий и более подробные ревизии действий пользователей (EEP)


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


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


Больше информации о логах ревизий в нашей документации


Centralized Audit Log & Extended User Audit Actions


Упрощенные настройки репозиториев (CE, EES, EEP)


С течением времени мы добавили много новой функциональности и вариантов конфигурации проектов и групп, и, как следствие, количество настроек GitLab значительно увеличилось.


В GitLab 9.3 мы начинаем работу по упрощению страниц настроек. Первым шагом стало улучшение читаемости страницы настроек репозиториев.


Мы стремимся улучшить группирование настроек и добавить обзор всех настроек для каждой секции.


Repository Settings Made Simple


Другие улучшения


Улучшения настроек JIRA (CE, EES, EEP)


В данном релизе мы улучшили настройки интеграции JIRA, что упрощает установку и тестирование соединения между проектом GitLab и сервером JIRA. Также добавлены отдельные поля Web URL и JIRA API URL. Такое разделение полезно в тех случаях, когда JIRA REST API и задачи JIRA находятся на разных адресах.


JIRA Settings Improvements


Больше информации об интеграции JIRA в нашей документации


Разрешения для ярлыков групп (CE, EES, EEP)


Пользователи с правами Reporter, Developer, Master и Owner вдобавок к ярлыкам проектов теперь могут создавать и редактировать ярлыки групп. Ранее это могли делать только Master and Owner.


Больше информации о модели прав доступа GitLab в нашей документации


Редактирование описания задач без перехода на другую страницу (CE, EES, EEP)


Описание задачи является опорной точкой и главным источником информации при совместной работе нескольких команд. В GitLab 9.3 мы убрали переход на отдельную страницу при редактировании описания задачи. Просто нажмите Edit, внесите изменения и нажмите Save changes — все это время вы остаетесь на странице задачи. Поскольку перенаправление на другую страницу теперь отсутствует, во время редактирования вы можете пролистать комментарии к задаче и даже скопировать и вставить в описание текст GFM


Edit Issue Description Inline, Without Losing Context


Больше информации о задачах GitLab в нашей документации


Улучшения интерфейса доски задач (CE, EES, EEP)


Мы понимаем, что у различных команд свои подходы к использованию досок задач. Для упрощения взаимодействия с ними мы добавили возможность сворачивать столбцы Backlog и Closed. Также мы внесли изменения в процесс добавления новых задач на доску: при нажатии на + задача автоматически добавляется в начало списка, что серьезно упрощает работу с длинными списками.


Issue Board Usability Improvements


Больше информации о досках задач GitLab в нашей документации


Локализация домашней страницы проекта и страницы файлов репозитория (CE, EES, EEP)


В GitLab 9.2 мы начали процесс локализации с перевода страницы аналитики цикла разработки на немецкий и испанский. В GitLab 9.3 мы перевели самые часто используемые страницы: домашнюю страницу проекта и страницу файлов репозитория.


Сообщество GitLab уже начало добавлять другие языки — китайский, болгарский и бразильский португальский. Вы можете наблюдать за изменениями и взглянуть на их гайдлайны, если хотите подключиться.


Internationalization of Project Home & Repository Files Pages


Узнайте, как построено взаимодействие сообщества по переводу GitLab


Доступ к приватным образам реестра контейнеров (CE, EES, EEP)


Когда вы разворачиваете проект, основанный на Docker, вам нужно подтягивать образ каждый раз, когда окружению нужно его запустить. Поскольку встроенный реестр контейнеров GitLab является очевидным выбором для хранения ваших контейнеров, в GitLab 9.3 вы сможете использовать его и для их распространения.


Создайте токен личного доступа с новой областью read_registry, и у вас будет постоянный токен развертывания, который смогут использовать внешние сервисы вроде Kubernetes, чтобы при необходимости получить доступ к вашим образам — без ввода всех ваших данных или использования фиктивного пользователя для этой задачи.


Access private Container Registry images


Побробнее вы можете узнать в документации о токенах личного доступа


Системная заметка об изменениях в обсуждении устаревшего диффа (CE, EES, EEP)


На протяжении ревью кода мерж-реквеста мы постоянно изменяем код и комментируем эти изменения. Зачастую это сложно отследить. В частности, когда мы начинаем комментировать определенную строчку кода, и кто-то другой одновременно вносит туда изменения. Может закончиться тем, что вы обсуждаете что-то, что уже изменили.


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


System Note with Link to Change in Outdated Diff Discussion


Автоотмена лишних конвейеров включена для всех проектов (CE, EES, EEP)


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


Если вам не нужно отменять лишние конвейеры, вы можете настроить соответствующие параметры под свои нужды.


Грядущее обновление Nginx (CE, EES, EEP)


В рамках предстоящего релиза GitLab 9.4 мы обновим версию nginx, входящую в Omnibus, до новой стабильной версии 1.12. В эту версию включается несколько улучшений предыдущей стабильной версии. Например, появилась возможность фильтровать вывод лога.


Для пользователей это обновление должно быть понятным, но если вы изменяли настройки в конфигурации nginx, пожалуйста, после обновления до 1.12 удостоверьтесь, что они работают корректно.


Поддержка Debian 9 (CE, EES, EEP)


Доступна поддержка последнего релиза Debian 9. Вы можете скачать официальные пакеты Omnibus для GitLab 9.3 из раздела установки.


GitLab Mattermost 3.10 (CE, EES, EEP)


GitLab 9.3 включает Mattermost 3.10, альтернативу Slack с открытым исходным кодом. В этой версии появилась поддержка турецкого языка, новые горячие клавиши и многое другое.


Подробнее об изменениях GitLab Mattermost читайте в документации


Улучшения Omnibus (CE, EES, EEP)


Обновились связанные с ним пакеты Git и PostgreSQL. Git обновился до версии 2.13.0, а Postgres до 9.6.3.


Документация об Omnibus GitLab


Описания сниппетов (CE, EES, EEP)


У сниппетов теперь есть описания. Это касается только персональных сниппетов, которые мы добавили в предыдущем релизе. Сниппеты — мощный инструмент для быстрой и непринужденной совместной работы над кодом. В этом релизе совместно работать над ними стало так же просто, как и над мерж-реквестами.


Snippet Descriptions


Документация о сниппетах GitLab


Улучшения производительности (CE, EES, EEP)


Мы продолжаем улучшать производительность GitLab с каждым релизом. Это не только сделает каждый инстанс GitLab еще быстрее, но также увеличит производительность инстанса с миллионом пользователей — GitLab.com.


В GitLab 9.3 мы продолжаем ускорять вывод списка проектов, а также в целом улучшаем производительность сервера за счет изменений в том, как мы зеркалируем репозитории. Синтаксическая подсветка файлов теперь кэшируется, это повышает общую производительность и ускоряет отображение коммитов.


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


Редизайн массового редактирования задач (CE, EES, EEP)


Мы сделали массовое редактирование задач проще и понятнее, чем в последнем релизе. Теперь при одновременном обновлении нескольких задач используется такая же боковая панель, как и во многих других элементах GitLab.


Bulk-Editing Issues Re-Design


Подробнее в нашей документации о задачах в GitLab


Улучшения поиска и фильтрации (CE, EES, EEP)


Мы продолжаем постепенно улучшать поиск и фильтрацию. Теперь вы сможете видеть пользовательские аватарки.


Также клик на фильтр вызывает выпадающее меню, позволяющее быстро менять фильтры.


Search/Filter Bar Improvements


Подробности вы сможете прочитать в документации о поиске по GitLab


Улучшения подгрупп GitLab (CE, EES, EEP)


В GitLab 9.0 мы добавили подгруппы, сделав управление группами и проектами более гибким. С каждым релизом мы продолжаем улучшать эту функциональность: в этот раз мы сделали более понятную навигацию по подгруппам. На странице групп вы сможете увидеть расширяемое дерево со всеми проектами и подгруппами — вместо того, чтобы искать и загружать по странице за раз.


Improvements to GitLab Subgroups


Документация по подгруппам GitLab


Расширенный просмотр файлов репозитория (CE, EES, EEP)


При просмотре файлов в репозитории теперь на той же странице автоматически отображается дополнительная информация.


Начиная с версии 9.3 вы сможете видеть, действительны ли файлы .gitlab-ci.yml или .gitlab/route-map.yml, а также конкретные ошибки парсинга. Также анализируется файл LICENSE, что упрощает доступ к информации о конкретной лицензии, если вам нужны дополнительные детали. А чтобы понять, на что опираются проекты, можно изучить системы управления зависимостями, которые теперь тоже отображаются автоматически.


Enhanced View for Repository Files


Директории, связывающиеся автоматически (CE, EES, EEP)


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


Эта функциональность будет работать для следующих директорий и языков:


  • *.gemspec (Ruby)
  • package.json (Node.js)
  • composer.json (PHP)
  • Podfile (Objective-C)
  • *.podspec (Objective-C)
  • *.podspec.json (Objective-C)
  • Cartfile (Objective-C)
  • Godeps.json (Go)
  • requirements.txt (Python)

Autolinking Package Names


Поддержка API для конвейеров по расписанию (CE, EES, EEP)


В GitLab 9.2 мы выпустили конвейеры по расписанию, которые можно настраивать с помощью пользовательского интерфейса. В GitLab 9.3 мы также добавили возможность создавать и управлять конвейерами по расписанию через набор API, чтобы сделать интеграцию с другими инструментами проще и эффективнее.


Подробнее в документации об API конвейеров по расписанию


Количественное измерение влияния мерж-реквеста на производительность (CE, EES, EEP)


В прошлой версии GitLab мы добавили возможность видеть влияние мерж-реквеста на память прямо на странице мерж-реквеста. В GitLab 9.3 мы решили пойти дальше: теперь мы замеряем изменения в среднем использовании памяти за 30 минут до мержа и через 30 минут после него.


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


Performance Impact of Merge Requests now Quantified


Подробнее в документации о мониторинге приложений с помощью Prometheus


GitLab Runner 9.3 (CE, EES, EEP)


Также с этим релизом мы выпустили GitLab Runner 9.3.


Самые важные изменения:


Список всех изменений вы найдете в CHANGELOG.


Полная документация о GitLab Runner.


Дополнительные метрики сервиса GitLab (CE, EES, EEP)


В GitLab 9.0 был представлен мониторинг сервиса GitLab с помощью Prometheus, с помощью которого можно оценить производительность Redis, PostgreSQL, а также системы в целом.


В рамках GitLab 9.3 мы добавили экспериментальный сервис мониторинга кодовой базы ruby. Пока в нем отображаются только несколько показателей вроде статуса конвейера и авторизаций пользователей. В последующих релизах мы будем добавлять мониторинг и других частей GitLab.


Подробнее в документации о дополнительных метриках




Подробные release notes и инструкции по обновлению/установке можно прочитать в оригинальном англоязычном посте: https://about.gitlab.com/2017/06/22/gitlab-9-3-released/


Перевод с английского выполнен переводческой артелью «Надмозг и партнеры», http://nadmosq.ru. Над переводом работали rishavant и sgnl_05 .

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332204/


Отечество амбиций: неповторимое путешествие по заводам TP-Link

Понедельник, 03 Июля 2017 г. 16:50 + в цитатник
Одно дело – читать исторические байки про события 50-70 летней давности, когда простые американские парни в гаражах кустарными методами создавали будущее крупных международных корпораций с многомиллиардными оборотами. И совсем другое дело – видеть своими глазами зарождение не менее амбициозных и, может быть, даже более перспективных компаний на другом конце Света, теперь уже в Китае. На новое масштабное производство одной из них, – TP-Link – удалось попасть нашему автору. Читать и смотреть далее

https://habrahabr.ru/post/331198/


Как с помощью больших данных получить целевые лиды

Понедельник, 03 Июля 2017 г. 16:49 + в цитатник
Внушительная доля рекламного рынка перешла в digital, трансформируясь по мере развития цифровых технологий. Растет конкуренция между участниками рынка, появляются новые маркетинговые инструменты и техники, а старые переживают качественные изменения. В частности, меняется подход к лидогенерации и привлечению целевого трафика. Статьей на эту тему мы начинаем блог маркетингового агентства ИНСТАМ.

Доля рынка онлайн-рекламы продолжит расти вплоть до 2021 года по прогнозам PwC. Большинство организаций стараются выйти с маркетинговыми кампаниями в интернет.

image

Источник: PwC

Интернет-маркетинг неразрывно связан с IT-сферой. Развитие технологий позволяет работать с огромными массивами данных и получать выгоду от их анализа и использования в рекламе. Представители компании Zenitmedia (входит в Publicis Group) прогнозируют рост доли сегмента программатик (алгоритмической программируемой закупки рекламы на основе данных о целевых пользователях): он составит до 31% в 2017 году. Ручной метод закупки рекламы постепенно уступает свои позиции, как менее прозрачный и эффективный.

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

Поставка обработанных и грамотно детализированных данных в программатик влияет в том числе и на качество лидогенерации. Данные — ключевая составляющая удачной кампании в программатик-рекламе. Туманные предположения о целевом клиенте не сработают, ведь акцент в лидогенерации смещается с абстрактной заявки на конкретный образ «горячего» пользователя, готового к сделке, и конвертацию его в клиента.

Используя IT-разработки, можно учитывать сотни и тысячи разнообразных пользовательских параметров — от марки автомобиля до таких частных моментов, как любимый цвет одежды — и дотянуться до узкой целевой аудитории с адекватным предложением в нужный момент.

Качественные лиды — где их брать?


Почему так важно точно попасть в аудиторию со своим предложением?

На пользователей из интернета обрушился информационный шквал, и они стали избирательнее в потреблении контента. По этой причине инструменты пассивной лидогенерации все чаще предоставляют заказчикам нецелевой трафик. Старые модели закупки интернет-рекламы дают плохие результаты — отходят в прошлое модели СРМ (оплата за показы рекламы) и СРС (оплата за клики и переходы на сайт).

Долгое время на рынке интернет-рекламы была популярна модель СРА (оплата за оставленные пользователем заявки и звонки с сайта), в которой рекламодатель-заказчик получал оговоренное количество лидов. Под лидом в основном подразумевалось действие, которое должен осуществить пользователь: добровольно оставленный контакт, заявка или заполнение анкеты, участие в опросе, звонок и т.п.

Зачастую эта модель монетизировалась достаточно успешно. Однако в ряде случаев заказчик мог заплатить 400-600-1200 рублей за лид, который был настолько холодным, что при прямом контакте с ним отдел телемаркетинга покрывался слоем инея, а маркетологи вынуждены были нести последствия за невыполнение KPI.

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

Приоритетной задачей 63% digital-специалистов, опрошенных компанией Hubspot, является генерация трафика и лидов на сайт. При этом на первый план выходит качество получаемого трафика: 70% специалистов, опрошенных Hubspot, указывают конверсионность лидов в числе основных приоритетов. То есть теперь недостаточно просто привести на площадку условно подходящую аудиторию и назвать «лидом» любое целевое действие. Важно, чтобы с повышенной вероятностью лид сконвертировался в реального клиента. Поиск целевых лидов и их конверсия позволяют быстрее отбивать бюджет на маркетинг и закрывать KPI.

image
Источник: State of Inbound

Теперь в приоритете модель работы CPL (оплата за лид), которая, несмотря на более кропотливый процесс отбора трафика, позволяет точнее прогнозировать ROMI (коэффициент возврата инвестиций в маркетинг) и достигать поставленных перед маркетологом целей.

Мы в ИНСТАМ подошли к вопросу лидогенерации с точки зрения анализа огромного массива данных о пользователях, которые мы накопили за последние 10 лет, автоматизируя различные маркетинговые направления.

Ну а там, где есть большие данные об аудитории, должна работать Data Management Platform (DMP) — платформа управления данными. Она служит ядром для хранения и обработки массива информации в несколько сотен миллионов профилей.

DMP: вся информация об аудитории в одном месте


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

Задачу сбора и систематизации данных о пользователях и решают DMP-системы.

Data management platform не является рекламной биржей или кабинетом отдельной рекламной системы, хотя DMP может быть её частью. Говоря простым языком, DMP — это многофункциональная база данных, в которой собирается и обрабатывается информация из различных источников об одном и том же пользователе.

Источники делятся на три типа:

  1. Данные первого порядка или first-party data, которые берутся из собственной аналитики. Источником этих данных становится собственный ресурс, например, сайт и его статистика.
  2. Данные второго порядка или second-party data. Это косвенная информация, собранная из партнерских и конкурентных источников: данные по эффективности рекламных кампаний, прочтения, клики. Сюда же относятся лайки и просмотры в соцсетях.
  3. Данные третьего порядка или third-party data, приобретенные у третьих лиц, обладающих информацией о действиях пользователей. Источник: сторонние DMP и Data Exchanges, прочие поставщики данных, информация сайтов-партнеров.

Данные первого и второго порядков для нашей DMP собраны из офлайн и онлайн-источников: из собственной базы данных, сформированной в процессе работы ИНСТАМ как SMS-агрегатора, и из открытых онлайн-источников, на которых установлен пиксель ИНСТАМ.

Преимущество DMP в том, что она решает задачу сопоставления данных. Если просто собирать данные из разных источников, то они будут обезличенные, и нельзя будет сопоставить, например, данные по покупкам пользователя и данные по тому, на каких сайтах он проводит своё свободное время, если только такая информация не хранится в доступной нам аналитике или CRM-системе.

Вся собранная в DMP информация детализируется и объединяется в комбинации, которые позволяют четче сегментировать целевую аудиторию на основе потребительских предпочтений в совершенно разных областях. Нерелевантная информация отсеивается. Таким образом строятся «портреты» пользователей с определенным набором свойств-атрибутов, например, поведением в соцсетях, историей браузера, информацией о покупках и участии в программах лояльности.

В частности, статистику в DMP собирает установленный на сайт заказчика пиксель (программный код), отслеживающий данные по посещениям и наиболее показываемым страницам сайта. Полученная информация обрабатывается, сопоставляется с уже имеющимися в базе данными и комбинируется в сегменты. На основе этого анализа легко проследить поведение пользователей и скорректировать показываемую им рекламу.

Здесь важно сразу оговориться, что релевантность данных по «портретам» клиентов в DMP не бывает на 100% полной.

Для детализации пользовательских профилей в рамках DMP можно применить скоринговую модель. Это возможность разделить очень похожую по составу, но не по своим намерениям массу аудитории, которую сложно отфильтровать при помощи других технологий. Например, риэлторы и желающие купить недвижимость. И те, и те будут посещать сайты с недвижимостью, но первая группа не будет заинтересована в ипотечном предложении, а вторая группа — да.

Также DMP-провайдер обязательно учитывает правовой аспект. Информация собирается, хранится и используется в соответствии с законодательством о персональных данных. «Портреты» в DMP обезличены и используются без привязки к конкретному человеку.

Целевой маркетинг: лидогенерация при помощи DMP


Допустим, данные собраны, правильно обработаны и хранятся в базе. Что дальше? Как заставить их работать на нас?

image

А дальше начинается самое интересное. Получив данные «повышенного уровня детализации», можно:

  • Отправить их в CRM-систему с целью обогатить данные по лидам и opportunities. Переосмыслив потребности аудитории и подвергнув ее дополнительному анализу, сейлзам и телемаркетингу заказчика легче будет «спускать» клиента по воронке продаж до заветной цели. Можно оптимизировать персональное предложение, основываясь на том, в чем пользователь нуждается именно сейчас, и оно с большой долей вероятности заинтересует пользователя.

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

  • Использовать данные для углубленного таргетирования в рекламных кампаниях: в то время как соцдем, гео, ключевые слова или интересы дают слишком широкий охват аудитории, настройка рекламной кампании по данным из DMP позволяет «бить прицельно», снижая количество показов, необходимых для совершения клика.

Пример: потенциальный клиент компании — автомобилист и покупает детское питание. Можно показать ему рекламу детского автомобильного кресла.

  • Проводить эффективные ремаркетинговые кампании. «Вшивание» скрытых пикселей с JavaScript-кодом на сайт позволяет собирать и анализировать в DMP статистику по заинтересованности его посетителей в тех или иных услугах и товарах. В том числе можно разместить пиксель на постконверсионной странице и спрогнозировать, когда клиент, который уже совершал покупку, начнет снова интересоваться этим товаром и будет готов купить его еще раз. Можно спланировать, где и когда показывать потенциальному клиенту рекламное предложение, чтобы не отпугнуть, а, наоборот, снова привлечь его к сделке.

  • Использовать данные для Look-alike моделирования. Когда Facebook создает look-alike аудиторию, это, конечно, очень ценно, но этот процесс абсолютно непрозрачный. Неизвестно, на каких данных формируется аудитория у сторонних платформ. DMP-система позволяет осознанно строить новую целевую аудиторию, по поведенческим характеристикам похожую на текущую аудиторию компании. Look-alike можно использовать в ремаркетинге, отслеживая поведение покупателей на сайте и приводя туда похожих пользователей.

Валидация и превращение лидов в заказы


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

Мы в ИНСТАМ считаем, что за такой лид клиент не должен платить, и решаем задачу валидации лидов усилиями собственного call-центра.

Операторы call-центра ИНСТАМ связываются с потенциальным покупателем и действуют по скрипту, утвержденному со стороны заказчика. Таким образом отсеиваются нецелевые контакты, и в отдел продаж или на электронный адрес заказчика поступают обработанные заявки только от тех клиентов, которые заинтересованы в сделке. Причем мы можем не только отдать данные пользователя, но и сразу соединить его с отделом продаж напрямую.

PROFIT


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

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

DMP можно объединять с почти любыми рекламными биржами, системами и кабинетами. Таким образом получается действенный инструмент таргетинга, который ищет нужную целевую аудиторию по её актуальным параметрам и генерирует целевые заявки.
Как вы сегментируете аудиторию, чтобы привлечь целевых лидов?

Никто ещё не голосовал. Воздержавшихся нет.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/331942/


Метки:  

[Перевод] ArrayBuffer и SharedArrayBuffer в JavaScript, часть 3: гонки потоков и Atomics

Понедельник, 03 Июля 2017 г. 16:29 + в цитатник
-> ArrayBuffer и SharedArrayBuffer в JavaScript, часть 1: краткий курс по управлению памятью
-> ArrayBuffer и SharedArrayBuffer в JavaScript, часть 2: знакомство с новыми объектами языка
-> ArrayBuffer и SharedArrayBuffer в JavaScript, часть 3: гонки потоков и Atomics



В прошлый раз, рассматривая SharedArrayBuffer, мы говорили о том, что работа с этим объектом может привести к состоянию гонки потоков. Это усложняет разработку, поэтому мы ожидаем, что этим средством будут пользоваться создатели библиотек, имеющие опыт в многопоточном программировании. Они смогут применить новые низкоуровневые API для создания высокоуровневых инструментов, с которыми будут работать обычные программисты, не касаясь ни SharedArrayBuffer, ни Atomics.


SharedArrayBuffer и Atomics как основа реализации многопоточности в JS-библиотеках и WebAssembly

Если вы не относитесь к разработчикам библиотек, то, вероятнее всего, работать напрямую с Atomics и SharedArrayBuffer вам не следует. Однако, полагаем, вам будет интересно узнать, как всё это устроено. Поэтому в данном материале мы поговорим о состояниях гонок потоков, которые могут возникать при многопоточном программировании, и о том, как использование объекта Atomics поможет библиотекам, основанным на новых средствах JS, этих состояний избежать.

Начнём с более подробного разговора о том, что же такое гонка потоков.

Гонка потоков: классический пример


Состояние гонки потоков может возникнуть в ситуации, когда имеется переменная, доступ к которой есть у двух потоков. Предположим, первый поток, основываясь на значении переменной fileExists, загружает некий файл, а второй проверяет, существует ли файл, и, если это так, устанавливает эту переменную в значение true. В детали мы не вдаёмся, код проверки наличия файла опущен.

Изначально переменная, которую процессы используют для обмена данными, установлена в значение false.


Первый поток, слева, загружает файл, если переменная fileExists установлена в true, второй поток, после проверки существования файла, устанавливает эту переменную в true

Если код во втором потоке будет выполнен первым, установит переменную в true, первый поток загрузит файл.


Сначала второй поток устанавливает переменную в значение true, потом второй поток загружает файл

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


Если сначала, когда переменная ещё установлена в false, выполняется код в первом потоке, выводится сообщение об ошибке

На самом же деле проблема заключается не в том, что файл найти не удалось. В нашем, весьма упрощённом случае, настоящая причина сообщения об ошибке — состояние гонки потоков.

Многие JS-разработчики сталкиваются с состоянием гонки такого рода даже в однопоточном коде. И, на самом деле, не нужно ничего знать о многопоточности для того, чтобы увидеть, почему возникают подобные ошибки.

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

Различные виды гонок потоков и Atomics


Поговорим о различных видах гонок потоков, с которыми можно столкнуться в многопоточном коде, и о том, как Atomics может помочь их предотвратить. Надо отметить, что тут мы не стремимся рассмотреть абсолютно все возможные состояния гонок, но этот обзор поможет вам понять, зачем те или иные методы предусмотрены в API Atomics.

Прежде чем мы начнём, хотелось бы снова напомнить, обращаясь к обычным разработчикам, что не ожидается, что они будут напрямую использовать возможности Atomics. Многопоточная разработка отличается повышенной сложностью, мы полагаем, что новыми средствами JavaScript будут, в основном, пользоваться создатели библиотек.

Многопоточное программирование таит в себе множество опасностей

Теперь приступим к рассказу о состояниях гонок.

Гонка потоков при обработке отдельной операции


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


Два потока поочерёдно инкрементируют переменную

Хотя, в JS-программе, инкрементирование выглядит как одна операция, если взглянуть на скомпилированный код, окажется, что перед нами несколько операций. Процессору, для инкрементирования переменной, надо выполнить три операции.

Дело тут в том, что у компьютера имеются разные виды памяти. В данном случае речь идёт об обычной оперативной памяти, которая хранит данные сравнительно длительное время, и о регистрах процессора, которые используются для кратковременного хранения данных.


Оперативная память, регистры процессора и арифметико-логическое устройство

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


Выполнение действий с данными, хранящимися в оперативной памяти. Первый шаг — загрузка данных из памяти в регистр. Второй — выполнение вычислений. Третий — сохранение результата вычислений в оперативной памяти

Если все необходимые низкоуровневые операции сначала выполнит первый поток, а затем — второй, мы получим ожидаемый результат.


Сначала все операции по инкрементированию переменной выполняет первый поток, потом — второй

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


Гонка потоков и операции с регистрами

Избежать подобных ситуаций можно с помощью реализации так называемых «атомарных операций». Суть тут заключается в том, чтобы то, что для программиста выглядит как одна операция, а для компьютера — как несколько, выглядело бы как одна операция и для компьютера.

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


Набор инструкций, формирующих атомарную операцию

С использованием атомарных операций код для инкрементирования переменной будет выглядеть по-новому. Вместо обычного sharedVar++ это будет нечто вроде Atomics.add(sabView, index, 1). Первый аргумент метода add представляет собой структуру данных для доступа к SharedArrayBuffer (Int8Array, например). Второй — это индекс, по которому sharedVar находится в массиве. Третий аргумент — число, которое нужно прибавить к sharedVar.


Атомарная операция, которая позволяет увеличить переменную на 1

Теперь, когда мы пользуемся методом Atomics.add, действия процессора, связанные с инкрементированием переменной по команде из одного потока, не смешиваются с операциями, инициированными из другого потока. Вместо этого сначала все необходимые действия, в рамках одной атомарной операции, выполняются первым потоком, после чего выполняются операции второго потока, опять же, заключённые в атомарную операцию.


Сначала инструкции, необходимые для инкрементирования переменной, выполняет первый поток, затем — второй

Вот методы Atomics, которые помогают избежать состояния гонок при выполнении отдельных операций.


Можно заметить, что список этот довольно-таки ограниченный. В нём, например, нет методов для выполнения деления или умножения. Однако, разработчики вспомогательных библиотек могут самостоятельно реализовать атомарные методы для выполнения различных операций.

Для реализации подобных вещей можно воспользоваться методом Atomics.compareExchange. С помощью этого метода можно считать некое значение из SharedArrayBuffer, выполнить с ним какие-то действия и записать обратно только в том случае, если оно, во время выполнения действий с ним, не было изменено другим потоком. Если, при попытке записи, оказалось, что значение в SharedArrayBuffer изменилось, запись не выполняется, вместо этого можно взять новое значение и попытаться выполнить ту же операцию снова.

Состояние гонок при выполнении нескольких операций


Атомарные операции, о которых мы говорили выше, позволяют избежать состояния гонок при выполнении одиночных команд. Однако иногда нужно изменить несколько значений в объекте (с использованием нескольких операций) и быть при этом уверенным в том, что никто другой не попытается в то же самое время менять эти значения. В целом, это означает, что, в ходе каждого сеанса обновления состояния объекта, объект должен быть заблокирован и недоступен для других потоков.

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


Два потока, общая область памяти и блокировка

Если код хочет использовать общие данные, он должен захватить блокировку. На рисунке выше ни один из потоков не заблокировал общую область памяти, это представлено в виде открытого замка. После захвата поток может использовать блокировку для защиты данных от других потоков. Только он может получить доступ к этим данным или изменить их, пока блокировка активна.

Для того, чтобы создать механизм блокировок, авторы библиотек могут воспользоваться методами Atomics.wait, и Atomics.wake, а так же такими, как Atomics.compareExchange и Atomics.store. Здесь можно взглянуть на базовую реализацию подобного механизма.

При таком подходе, показанном на следующем рисунке, поток №2 захватит блокировку данных и установит значение locked в true. Это означает, что поток №1 не сможет получить доступ к данным до тех пор, пока поток №2 их не разблокирует.


Поток №2 блокирует данные

Если потоку №1 нужен доступ к данным, он попытается захватить блокировку. Но так как блокировка уже используется, сделать он этого не сможет. Поэтому потоку придётся ждать до тех пор, пока данные не окажутся разблокированными.


Поток №1 ожидает снятия блокировки

Как только поток №2 завершит работу, он снимет блокировку. После этого механизм блокировок оповестит ожидающие потоки о том, что они могут захватить блокировку.


Поток №1 получил оповещение о том, что блокировка снята

Теперь поток №1 может захватить блокировку и заблокировать данные для собственного использования.


Поток №1 блокирует данные

Библиотека, реализующая механизм блокировок, могла бы использовать различные методы объекта Atomics, однако, самыми важными из них являются Atomics.wait и Atomics.wake.

Состояние гонок, вызванное изменением порядка выполнения операций


Вот третья проблема синхронизации, в решении которой может помочь объект Atomics. Для кого-то она может оказаться полной неожиданностью.

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

Например, предположим, что была написана некая функция, код которой вычисляет общую сумму по чеку. Завершив вычисления, мы устанавливаем соответствующий флаг.


Фрагмент кода, выполняющего вычисления общей суммы и устанавливающего флаг, сигнализирующий об окончании выполнения операции

Для того, чтобы это скомпилировать, системе надо решить, какой регистр использовать для каждой переменной. После этого JS-код можно перевести в машинные инструкции.


Упрощённый пример преобразования кода на JS в машинные инструкции

Пока всё выглядит так, как и ожидается.

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

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

Вот описание этапов, из которых состоит выполнение инструкции.

Этап №1


Выборка инструкции из памяти

Этап №2


Декодирование инструкции, то есть, выяснение, какие именно действия нужно произвести для выполнения этой инструкции, и загрузка необходимых данных из регистров

Этап №3


Выполнение инструкции

Этап №4


Запись результатов выполнения инструкции в регистры

Вот, очень упрощённо, путь, который проходит по конвейеру первая инструкция. В идеале нам хотелось бы, чтобы выполнение второй инструкции шло сразу за первой. То есть, как только первая инструкция переходит к шагу №2, осуществляется выполнение шага №1 для второй инструкции, и так далее.
Проблема заключается в том, что инструкция №2 зависит от инструкции №1.


Инструкции №2 нужно получить значение переменной subTotal из R3, но инструкция №1 ещё не записала туда результат вычислений

В подобной ситуации можно приостановить обработку процессором инструкций до тех пор, пока инструкция №1 не обновит значение переменной subTotal в регистре. Однако, при таком подходе пострадает производительность.

Для того, чтобы использовать ресурсы системы эффективно, множество компиляторов и процессоры меняют порядок выполнения операций. А именно, в нашем случае выполняется поиск инструкций, которые не используют переменные subTotal и total, и выполнение этих инструкций между инструкциями №1 и №2, которые следуют в коде друг за другом.


Изменение порядка выполнения инструкций, инструкция №3 будет выполнена между инструкциями №1 и №2

Это позволяет, в идеале, не прерывать поток инструкций, следующих по конвейеру.

Так как третья строка кода не зависит ни от первой, ни от второй, компилятор или процессор могут решить, что вполне безопасно изменить порядок выполнения инструкций. Когда всё это происходит в однопоточном режиме, внутри функции, другой код не получит доступ к этим значениям до тех пор, пока не завершится выполнение всей функции.

Однако, если имеется ещё один поток, выполняющийся параллельно, всё меняется. Другому потоку не обязательно ждать до тех пор, пока функция завершит работу, для того, чтобы узнать состояние переменных. Эти изменения другой поток может заметить едва ли не сразу же после того, как они произойдут. В результате окажется, что флаг isDone, видимый другому потоку, будет установлен до завершения вычислений. codeisDone как флаг, указывающий на то, что вычисление total завершено, и полученное значение готово к использованию в другом потоке, изменение порядка выполнения операций приведёт к состоянию гонки потоков.

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


Если обновление некоей переменной выполняется в коде JS-функции выше команды Atomics.store, оно гарантированно будет завершено до того, как Atomics.store сохранит значение переменной в памяти. Если даже порядок выполнения неатомарных инструкций будет изменён по отношению друг к другу, ни одна из них не будет перемещена ниже вызова Atomics.store, размещённого в нижней части кода.

Аналогично, загрузка переменной после вызова Atomics.load гарантированно будет выполнена после того, как Atomics.load прочтёт её значение. Опять же, если даже порядок выполнения неатомарных инструкций будет изменён, ни одна из них не будет перемещена так, что будет выполнена до Atomics.load, которая размещена в коде выше неё.


Обновление переменной выше Atomics.store гарантированно будет выполнено до завершения Atomics.store. Загрузка переменной ниже Atomics.load гарантированно будет выполнена после того, как Atomics.load завершит работу

Обратите внимание на то, что цикл while из этого примера реализует циклическую блокировку, это весьма неэффективная конструкция. Если нечто подобное окажется в главном потоке приложения, программа может попросту зависнуть. В реальном коде, практически наверняка, циклическими блокировками пользоваться не стоит.

Итоги


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


Многопоточное программирование с разделяемой памятью полно опасностей

Мы, в этой серии из трёх материалов, постоянно говорили о том, что новые средства языка позволяют разработчикам библиотек создать простые, удобные и надёжные инструменты, предназначенные для обычных программистов. Путь SharedArrayBuffer и Atomics во вселенной JavaScript только начинается, поэтому библиотеки, построенные на их основе, пока ещё не созданы. Однако, теперь у JS-сообщества есть всё необходимое для разработки таких библиотек.

Уважаемые читатели! Если вы — из тех высококлассных программистов, не понаслышке знающих о многопоточности, в расчёте на которых созданы SharedArrayBuffer и Atomics, уверены, многим новичкам будет интересно узнать о том, в каком направлении стоит двигаться для того, чтобы стать разработчиком качественных библиотек, реализующих параллельные вычисления. Поделитесь, пожалуйста, опытом.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332194/


Метки:  

Поиск сообщений в rss_rss_hh_new
Страницы: 1437 ... 1035 1034 [1033] 1032 1031 ..
.. 1 Календарь