Расширение для браузера на основе htaccess.ru: как сделать самому

На htaccess.ru нет готового расширения для браузера — но собрать своё несложно. Здесь идея и минимальный пример (Manifest V3): кнопка на панели, которая по текущему сайту показывает тип сервера и относящиеся к .htaccess заголовки, подсвечивает проблемы и даёт быстрые ссылки на инструменты сайта. Это учебный шаблон, а не готовый продукт.

Зачем такое расширение

Расширение даёт быстрый доступ к информации о сервере прямо из браузера — без открытия DevTools и без переключения вкладок. Основные сценарии:

  • Быстро глянуть «что отдаёт мой сайт»: Server, Strict-Transport-Security (HSTS), Cache-Control, Content-Encoding, заголовки безопасности — прямо из браузера одним кликом.
  • Быстрый доступ к инструментам htaccess.ru: /check/, /rewrite/, /explain/, /server-check/ — кнопки прямо в попапе.
  • Подсветка проблем: нет HSTS на HTTPS-сайте, X-Powered-By торчит наружу, сервер — nginx (и .htaccess не читается).

Что расширение НЕ делает: оно не «исполняет .htaccess» на клиенте. .htaccess живёт на сервере и обрабатывается Apache — расширение только читает заголовки ответа, которые сервер уже отдал.

Как устроено расширение (кратко)

Расширение для Chrome/Firefox на базе Manifest V3 состоит из четырёх частей:

  • manifest.json — манифест, описывает расширение, его разрешения и точки входа.
  • Фоновый сервис-воркер — выполняется в фоне, может слушать события браузера (в нашем простом примере не нужен).
  • Content-script — JS, выполняемый в контексте страницы; может читать DOM. В нашем случае не нужен — всё делаем в popup.
  • Popup — HTML-файл, открывается при клике на иконку расширения. Здесь живут popup.html и popup.js.

Для чтения заголовков ответа в Manifest V3 проще всего: popup.js делает fetch() к URL текущей вкладки с методом HEAD (или GET) и читает response.headers. Разрешений нужно минимум: activeTab + host_permissions.

manifest.json

Минимальный файл манифеста Manifest V3:

manifest.jsonкопировать
{
  "manifest_version": 3,
  "name": "htaccess.ru helper",
  "version": "0.1",
  "action": {
    "default_popup": "popup.html"
  },
  "permissions": ["activeTab"],
  "host_permissions": ["<all_urls>"]
}

Что означает каждое поле:

  • "manifest_version": 3 — обязательно для новых расширений Chrome; MV2 устарел и не принимается в Web Store.
  • "action" — описывает кнопку на панели и задаёт popup.html как попап при клике.
  • "permissions": ["activeTab"] — доступ к активной вкладке по клику пользователя (URL, заголовки). Не требует постоянного фонового доступа.
  • "host_permissions": ["<all_urls>"] — разрешает fetch() к любому домену из popup.js. По возможности сузьте до конкретных доменов вашего проекта.

Простейший popup:

popup.htmlкопировать
<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <title>htaccess.ru helper</title>
  <style>
    body { font: 13px/1.5 sans-serif; min-width: 280px; padding: 8px; }
    #out { white-space: pre-wrap; font-family: monospace; font-size: 12px; }
    .warn { color: #c00; }
    .info { color: #666; }
  </style>
</head>
<body>
  <div id="out">Загрузка...</div>
  <script src="popup.js"></script>
</body>
</html>
popup.jsкопировать
// Получаем активную вкладку и читаем заголовки ответа
chrome.tabs.query({ active: true, currentWindow: true }, async ([tab]) => {
  const out = document.getElementById('out');
  if (!tab || !tab.url || !tab.url.startsWith('http')) {
    out.textContent = 'Откройте HTTP/HTTPS-страницу';
    return;
  }

  try {
    // HEAD быстрее, но некоторые серверы блокируют его или CORS не пропускает —
    // в таком случае замените на 'GET'
    const r = await fetch(tab.url, { method: 'HEAD' });
    const h = r.headers;

    const server  = h.get('server')                    || '(не указан)';
    const hsts    = h.get('strict-transport-security') || 'нет';
    const enc     = h.get('content-encoding')          || 'нет (без сжатия)';
    const xpb     = h.get('x-powered-by')              || '(скрыт — хорошо)';

    out.textContent = [
      'Server:          ' + server,
      'HSTS:            ' + hsts,
      'Content-Encoding:' + enc,
      'X-Powered-By:    ' + xpb,
    ].join('\n');

  } catch (err) {
    out.textContent = 'Ошибка: ' + err.message +
      '\n(CORS может блокировать HEAD — попробуйте GET)';
  }
});

Замечания: из-за CORS заголовки доступны не все — браузер фильтрует их для кросс-доменных запросов. Для своего сайта (где вы контролируете CORS-заголовки) обычно всё работает. Метод HEAD иногда блокируется сервером или политикой CORS — тогда замените на GET.

Добавьте в popup.html кнопки, а в popup.js — обработчики, открывающие инструменты htaccess.ru в новой вкладке:

popup.html — блок кнопок (добавить после #out)копировать
<hr>
<button id="btn-check">Проверить .htaccess (/check/)</button>
<button id="btn-server">Чекер сервера (/server-check/)</button>
<button id="btn-explain">Объяснить .htaccess (/explain/)</button>
<button id="btn-rewrite">Тест RewriteRule (/rewrite/)</button>
popup.js — обработчики кнопоккопировать
// Открываем инструменты в новой вкладке
const tools = {
  'btn-check':   'https://htaccess.ru/check/',
  'btn-server':  'https://htaccess.ru/server-check/',
  'btn-explain': 'https://htaccess.ru/explain/',
  'btn-rewrite': 'https://htaccess.ru/rewrite/',
};

Object.entries(tools).forEach(([id, url]) => {
  const btn = document.getElementById(id);
  if (btn) {
    btn.addEventListener('click', () => chrome.tabs.create({ url }));
  }
});

Можно передавать текущий домен в параметрах: получите tab.url, разберите через new URL(tab.url).hostname и подставьте в адрес инструмента, если он принимает URL-параметр.

Подсветка проблем

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

popup.js — добавить после чтения заголовковкопировать
const warnings = [];
const isHttps = tab.url.startsWith('https://');

// Нет HSTS на HTTPS-сайте
if (isHttps && !h.get('strict-transport-security')) {
  warnings.push('⚠ Нет HSTS. Добавьте в .htaccess:\n' +
    '  Header always set Strict-Transport-Security ' +
    '"max-age=31536000" — подробнее: /headers/');
}

// X-Powered-By раскрывает технологии — убрать
if (h.get('x-powered-by')) {
  warnings.push('⚠ X-Powered-By виден. Скройте:\n' +
    '  Header always unset X-Powered-By');
}

// nginx — .htaccess не читается
if ((h.get('server') || '').toLowerCase().includes('nginx')) {
  warnings.push('ℹ Сервер nginx — .htaccess не читается.\n' +
    '  Используйте конфиг nginx. Подробнее: /info/htaccess-vs-nginx/');
}

if (warnings.length) {
  out.textContent += '\n\n' + warnings.join('\n\n');
}

Как запустить локально

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

  • Chrome / Edge: откройте chrome://extensions → включите «Режим разработчика» (правый верхний угол) → нажмите «Загрузить распакованное» → выберите папку с manifest.json, popup.html, popup.js. Иконка появится на панели сразу.
  • Firefox: откройте about:debugging → «Этот Firefox» → «Загрузить временное дополнение» → выберите manifest.json. Расширение живёт до перезапуска браузера.

Для публикации в Chrome Web Store нужен аккаунт разработчика (разовый взнос $5), сборка в ZIP, прохождение автоматической и ручной проверки Google. Для AMO (Firefox) — аккаунт на addons.mozilla.org. В обоих случаях: минимизируйте запрашиваемые permissions — чем меньше, тем быстрее пройдёт проверка.

Ограничения и безопасность

  • CORS: браузер не даст прочитать все заголовки ответа с чужого сайта — фильтруются по политике CORS. Для своего сайта, где вы управляете заголовками, ограничений нет.
  • Минимум разрешений: не запрашивайте webRequest, tabs (без необходимости), history, bookmarks — лишние разрешения пугают пользователей и усложняют проверку в магазине.
  • Приватность: не отправляйте URL и заголовки пользователя на сторонние серверы без явного согласия — это нарушение политики магазина.
  • Что видит расширение: ровно то же, что DevTools → Network → заголовки ответа. Не больше и не меньше.
  • Это шаблон: htaccess.ru не предоставляет готовое расширение и не несёт ответственности за код, написанный на основе этой статьи. Проверяйте и тестируйте перед публикацией.