Skip to content

Repository files navigation

mitmproxy-plugin

Локальный инструмент разработчика: подменяет HTTP-ответы на лету по правилам из файла mocks.json — статус-код, тело целиком или отдельные поля в нём.

Административная панель управления моками служит для упрощения их настройки и управления.

Подмена работает только пока запущен прокси с плагином:

mitmproxy --scripts /путь/к/mitmproxy-plugin/mocker.py

Без этого процесса mocks.json (он лежит рядом с mocker.py) — просто текстовый файл: ни один ответ не изменится.

Плагин — аддон mitmproxy, поэтому подмена идёт на уровне прокси, а не внутри вкладки браузера. Мок применяется к уже полученному ответу бэкенда, поэтому он может не заменять ответ целиком, а поправить в нём одно поле: остальные данные придут от сервера настоящими.

Рядом лежит dashboard.py — необязательная локальная веб-панель для редактирования mocks.json. Она ничего не перехватывает.


Как это устроено: два процесса и один файл

  HTTP-клиент  ──запрос──▶  mitmproxy + mocker.py  ──запрос──▶  бэкенд
               ◀──ответ───  (подмена по правилам)  ◀──ответ───
                                     ▲
                        читает файл  │  (сверяет на каждом ответе)
                                     │
                                mocks.json
                                     ▲
                                     │  пишет
                                     │
                    dashboard.py  или  любой текстовый редактор

mocks.json — единственная связь между этими двумя процессами. Панель пишет файл, плагин его читает: на каждом ответе он сверяет отпечаток файла и перечитывает его только при изменении, поэтому правка применяется к следующему же запросу — перезапускать mitmproxy не нужно. Других каналов между ними нет: панель не знает адреса прокси, не проверяет, запущен ли он, и не видит трафик.

Важно: одна лишь панель ничего не подменяет — пока не запущен mitmproxy с mocker.py, моки не применяются, сколько бы их ни было создано. Верно и обратное: панель необязательна, mocks.json — обычный JSON, его правит любой текстовый редактор, и горячая перезагрузка сработает точно так же.


Содержание

Как это устроено: два процесса и один файл
Что умеет
Требования
Быстрый старт
   • HTTPS: сертификат mitmproxy
Как направить трафик в прокси
Как убедиться, что мок сработал
Формат mocks.json
   • Корень файла
   • Блок variables
   • Параметры папки
   • Параметры мока
Рецепты
Динамические значения: code::
Что происходит с ответом
   • Сбор оригинальных ответов
Ограничения
Если не работает
Панель управления (необязательно)
   • Запуск панели
   • Что умеет панель
   • Ручная правка файла и панель
   • Горячие клавиши панели
Безопасность
Структура репозитория
Вклад и лицензия


Что умеет

• Подменяет ответы любому HTTP-клиенту, а не только браузеру: точка перехвата — прокси, поэтому под моки попадают и curl, и мобильное приложение, и десктопная программа.
• Матчинг идёт по URL, тип содержимого не ограничен: подменить можно и ответ JSON API, и HTML-страницу ошибки, и ответ несуществующего эндпоинта — тело и статус задаёт мок.
• Мягкое слияние: содержимое changes накладывается поверх настоящего ответа бэкенда — правится одно поле, остальные данные остаются живыми.
• Мок только на статус: без changes меняется лишь статус-код, тело ответа остаётся настоящим (500 или 403 поверх реальных данных).
• Динамические значения: строка с code:: внутри changes исполняется как Python при каждом сработавшем ответе — случайные числа, текущее время, вычисляемые поля.
• Горячая перезагрузка: изменённый mocks.json перечитывается прокси сам, правка действует на следующем же запросе, перезапуск mitmproxy не нужен.
• Папка — это сценарий: набор моков включается и выключается одним флагом isFolderActive, а ключ mockerServiceEnabled гасит подмену целиком.
• Нормализация числовых сегментов пути: один мок на /api/users/_ID_ ловит и /api/users/1, и /api/users/9999.
• Опциональный сбор оригинальных ответов в файлы — готовые заготовки для будущих моков.


Требования

• Python — той версии, которую требует устанавливаемая версия mitmproxy: плагин исполняется её интерпретатором (mitmproxy 12 требует Python 3.12 или новее).
• mitmproxy — ставится из pip. Своих зависимостей, кроме него, у mocker.py нет.
• Панели дополнительные пакеты не нужны: только стандартная библиотека, Python 3.9 или новее.

pip install mitmproxy

Если в системе несколько версий Python, используйте pip3 install mitmproxy.


Быстрый старт

От чистого клона до первой подменённой строки — шесть шагов. Седьмой, с панелью, необязателен.

1. Установить mitmproxy

pip install mitmproxy

2. Забрать репозиторий

git clone https://github.com/kolindes/mitmproxy-plugin.git
cd mitmproxy-plugin

3. Проверить, что mocks.json лежит рядом с mocker.py

Путь к файлу моков зашит в плагин: он читает mocks.json строго из своей папки, задать другой нечем. После клона файл уже на месте — он отслеживается git. Копировать пример нужно, только если mocker.py взят отдельно от репозитория:

cp default_mocks.json mocks.json

В Windows — copy default_mocks.json mocks.json.

4. Запустить прокси с плагином

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

mitmdump --scripts /путь/к/mitmproxy-plugin/mocker.py

Появятся две строки — плагин загружен, прокси слушает 8080 (порт меняется опцией --listen-port):

Loading script /путь/к/mitmproxy-plugin/mocker.py
HTTP(S) proxy listening at *:8080.

Путь в первой строке — тот, что передан в --scripts. Той же опцией плагин подключается к mitmproxy (полноэкранный интерфейс в терминале) и mitmweb (веб-интерфейс); в mitmproxy те же две строки уходят в окно событий, оно открывается клавишей E. Ограничить перехват одним доменом — --allow-hosts "example\.com" (значение читается как регулярное выражение).

5. Отправить запрос через прокси

Во втором терминале — первый занят прокси. URL совпадает с демо-моком из поставляемого mocks.json:

curl -i -x http://127.0.0.1:8080 "http://example.com/api/path/method?param1=foo&param2=123"

6. Посмотреть результат

Ответ клиенту (часть заголовков опущена):

HTTP/1.1 200 OK
Content-Type: application/json
mitmproxyplugin: true

{"data": {"userInfo": {"id": 1, "username": "test_user_123"}, "anotherUsefulData": {"egg": "foo", "important": true}}}

И строка в логе прокси:

Successfully mocked => targetUrl:/api/path/method?param1=foo&param2=123 matched mockUrl:/api/path/method?param1=foo&param2=123 (mock 'my_mocks_folder / my-first-mock-data')

Рядом с ней в этом примере встретится предупреждение Unable to read the original response JSON; using an empty dict. — example.com отдаёт HTML, разбирать как JSON нечего. Для мока с shouldApplyChangesSoftly: false это не мешает: тело заменяется целиком.

7. (необязательно) Запустить панель

python dashboard.py

Если python в системе не найден — python3 dashboard.py. Панель откроется на http://127.0.0.1:46625 и будет править тот же mocks.json. Подробности — в разделе Панель управления.

HTTPS: сертификат mitmproxy

Для http:// ничего дополнительно не нужно. Для https:// mitmproxy расшифровывает соединение своим сертификатом, и клиент должен ему доверять: при запущенном прокси откройте http://mitm.it через этот же прокси и установите сертификат для своей ОС, браузера или устройства.

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


Как направить трафик в прокси

mocker.py маршрутизацией не занимается — это целиком зона mitmproxy, см. его документацию. Рабочие варианты:

• Отдельный запрос через curl:

curl -x http://127.0.0.1:8080 "https://api.example.com/v1/users"

• Переменные окружения — их читают многие CLI-инструменты и SDK:

export HTTP_PROXY=http://127.0.0.1:8080
export HTTPS_PROXY=http://127.0.0.1:8080

В PowerShell:

$env:HTTP_PROXY = 'http://127.0.0.1:8080'
$env:HTTPS_PROXY = 'http://127.0.0.1:8080'

• Системные настройки прокси: Windows — «Параметры → Сеть и Интернет → Прокси», macOS — «Системные настройки → Сеть → Прокси». Браузеры по умолчанию берут их оттуда.
• Мобильное устройство в той же сети: в настройках Wi-Fi указать вручную IP компьютера и порт 8080, затем поставить сертификат с http://mitm.it. По умолчанию mitmproxy слушает все интерфейсы (*:8080), так что отдельно открывать доступ не нужно.
• Порт прокси меняется опцией --listen-port.


Как убедиться, что мок сработал

1. Заголовок-маркер. На каждый подменённый ответ плагин ставит mitmproxyplugin: true — в том числе когда менялся только статус-код. Виден в DevTools и в выводе curl -i.

2. Строка в логе прокси. Она пишется на каждое срабатывание и не зависит от globalSkippedMocksLoggingEnabled — тот включает только лог промахов. Уровень записи — INFO: при обычной verbosity строка видна без дополнительных ключей, при пониженной (-q) её не будет:

Successfully mocked => targetUrl:/api/path/method?param1=foo&param2=123 matched mockUrl:/api/path/method?param1=foo&param2=123 (mock 'my_mocks_folder / my-first-mock-data')

В mitmdump лог идёт прямо в консоль, в mitmproxy окно событий открывается клавишей E.

3. Если мок молчит. Поставьте globalSkippedMocksLoggingEnabled: true в блоке variables — в лог пойдут промахи:

Skipping mock 'my_mocks_folder / my-first-mock-data' [no match] => mockUrl:[/api/path/method?param1=foo&param2=123] <-> targetUrl:[/api/users/_ID_] :: findExactPath=True, useNormalizedIds=True

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


Формат mocks.json

Рабочий файл — mocks.json рядом с mocker.py. Файл default_mocks.json лежит в репозитории только как пример: плагин его не читает.

Ниже — сокращённый пример структуры со всеми ключами, а не содержимое поставляемого файла:

{
  "mockerServiceEnabled": true,
  "variables": {
    "globalUseNormalizedIds": true,
    "globalFindExactPath": false,
    "globalSkippedMocksLoggingEnabled": true
  },
  "folders": {
    "example_folder": {
      "isFolderActive": true,
      "description": "Описание папки — его показывает панель, mocker.py его не читает",
      "mocks": {
        "example-mock": {
          "isMockActive": true,
          "url": "/api/example/method?param1=foo",
          "statusCode": 200,
          "useNormalizedIds": true,
          "findExactPath": true,
          "shouldApplyChangesSoftly": false,
          "changes": {
            "data": {
              "userInfo": {
                "id": 1,
                "username": "test_user_123"
              }
            }
          }
        },
        "example-status-only": {
          "isMockActive": false,
          "url": "/api/example/anotherMethod",
          "statusCode": 500
        }
      }
    }
  }
}

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

Корень файла

mockerServiceEnabled — общий выключатель сервиса. Сервис включён только при литеральном true или при отсутствии ключа; любое другое значение (false, null, 0, 1, "true") считается «выключено», и в лог уходит Mocker service is disabled: ....
variables — значения по умолчанию для всех моков. Ключ необязателен.
folders — папки с моками. Ключ необязателен.

Неизвестные ключи корня плагин игнорирует, а панель сохраняет как есть и перечисляет на экране «Настройки».

Блок variables

Все три флага при отсутствии ключа считаются false.

globalUseNormalizedIds — значение по умолчанию для useNormalizedIds.
globalFindExactPath — значение по умолчанию для findExactPath.
globalSkippedMocksLoggingEnabled — писать в лог (уровень INFO) не подошедшие моки и пропущенные CORS-preflight. Пер-мокового аналога у флага нет, на перебор моков он не влияет.

В поставляемых mocks.json и default_mocks.json первый и третий выставлены в true — это данные примера, а не умолчание кода.

Параметры папки

isFolderActive — включает и выключает папку целиком: при отсутствии ключа или false её моки в перебор не попадают.
mocks — объект с моками этой папки.
description — необязательное описание. mocker.py его не читает; панель показывает его под заголовком папки и ищет по нему.

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

Параметры мока

isMockActive — при отсутствии ключа мок считается выключенным.
url — обязателен, непустая строка. Сравнивается с путём запроса вместе с query-строкой и без схемы, хоста и порта: /api/path/method?param1=foo. URL с хостом (https://example.com/api) не совпадёт никогда; ограничить мок доменом через mocks.json нельзя — для этого есть опция mitmproxy --allow-hosts.
statusCode — обязателен, целое число (панель дополнительно требует диапазон 100..599). Строка "200" — частая опечатка: такой мок пропускается с предупреждением в логе. Вместе со статусом выставляется reason-фраза из таблицы mitmproxy; для кода, которого в ней нет, она становится пустой.
findExactPathtrue: url мока должен совпасть с путём запроса целиком; false: достаточно вхождения подстроки. При отсутствии ключа берётся globalFindExactPath (явный null равносилен false, а не наследованию).
useNormalizedIdstrue: перед сравнением обе стороны нормализуются, каждый целиком числовой сегмент пути заменяется на _ID_ (query-часть после ? не трогается). Поэтому url мока можно писать и как /api/users/123, и как /api/users/_ID_. При отсутствии ключа берётся globalUseNormalizedIds.
shouldApplyChangesSoftlyfalse (умолчание): тело ответа заменяется данными из changes целиком; true: changes рекурсивно накладываются на настоящий ответ — существующий ключ перезаписывается, отсутствующий добавляется, остальное сохраняется. Вложенные словари сливаются на всех уровнях, список заменяется значением целиком. Наследования из variables у этого флага нет.
changes — объект с изменениями тела. Отсутствие ключа, null и {} равнозначны: тело и content-type не трогаются вовсе, меняется только статус-код.

Неизвестные поля мока плагин игнорирует, панель сохраняет и показывает строкой «Дополнительные поля».

Два следствия непустого changes: ответ всегда уходит клиенту как application/json, а мягкое слияние возможно только поверх JSON-объекта. Если корень настоящего ответа объектом не оказался, слияние идёт поверх пустого {} и настоящие данные теряются. Случая два, и различает их только лог:

• тело не разобралось как JSON — в лог уходит Unable to read the original response JSON; using an empty dict. Error: ...;
• тело разобралось, но корень не объект (список, строка, число, null) — данные выбрасываются молча, в логе об этом не будет ничего.


Рецепты

Ниже — фрагменты файла, и уровень видно по ключу: мок кладётся внутрь folders.<папка>.mocks, блок folders — в корень, блок changes — внутрь мока.

Отдать 500, не трогая тело

"payment-500": {
  "isMockActive": true,
  "url": "/api/payment",
  "statusCode": 500
}

Ключа changes нет — тело и content-type остаются настоящими, меняется только статус (в панели такой мок помечается чипом «только статус»).

Подменить одно поле, остальное оставить настоящим

"balance-zero": {
  "isMockActive": true,
  "url": "/api/account",
  "statusCode": 200,
  "shouldApplyChangesSoftly": true,
  "changes": { "data": { "balance": 0 } }
}

Было (пришло от бэкенда):

{ "data": { "balance": 15300, "currency": "EUR" }, "updatedAt": "2026-07-24T10:00:00" }

Стало (ушло клиенту):

{ "data": { "balance": 0, "currency": "EUR" }, "updatedAt": "2026-07-24T10:00:00" }

Вернуть пустой список

"empty-orders": {
  "isMockActive": true,
  "url": "/api/orders",
  "statusCode": 200,
  "changes": { "items": [], "total": 0 }
}

shouldApplyChangesSoftly не задан, значит замена жёсткая: всё, что было в теле ответа, исчезает.

Одно правило на все идентификаторы

"any-user-404": {
  "isMockActive": true,
  "url": "/api/users/_ID_",
  "statusCode": 404,
  "useNormalizedIds": true,
  "findExactPath": true,
  "changes": { "error": "not_found" }
}

Совпадёт и с /api/users/1, и с /api/users/9999.

Папка-сценарий

"folders": {
  "scenario-expired-token": {
    "isFolderActive": true,
    "description": "Профиль отвечает 401",
    "mocks": {
      "profile-401": { "isMockActive": true, "url": "/api/profile", "statusCode": 401 }
    }
  },
  "scenario-empty-cart": {
    "isFolderActive": false,
    "mocks": {
      "cart-empty": {
        "isMockActive": true,
        "url": "/api/cart",
        "statusCode": 200,
        "changes": { "items": [], "total": 0 }
      }
    }
  }
}

Переключение сценария — один флаг isFolderActive, сами моки трогать не нужно.

Меняющиеся значения

"changes": {
  "orderId": "code::import random; result = random.randint(1000, 9999)",
  "createdAt": "code::import time; result = time.strftime('%Y-%m-%dT%H:%M:%S')"
}

Код выполняется заново на каждом сработавшем ответе — подробности в следующем разделе.


Динамические значения: code::

Любое строковое значение внутри changes, содержащее подстроку code::, плагин выполняет как Python и подставляет результат. Обходятся словари и списки любой вложенности; ключи словарей не обрабатываются.

Правила:

• Сниппет обязан присвоить переменную result — подставляется именно её значение. Если присваивания нет, подставится null, а в лог уйдёт code:: snippet did not set 'result', substituting None: ....
code:: — маркер в любом месте строки, а не обязательно префикс.
• Если строка состоит только из кода (code:: в начале и больше нигде), результат подставляется как есть, с сохранением типа: "code::result = 1" даёт число 1.
• Если вокруг кода есть текст, границей кода служит второй code::, а результат вклеивается строкой между префиксом и хвостом: "id: code::result = 1" даёт "id: 1", а "a code::result = 1code:: b""a 1 b". Тип результата при этом теряется.
• Больше двух вхождений code:: в одном значении не разбирается: всё после второго остаётся обычным текстом.
• Код выполняется заново на каждом сработавшем ответе, состояние между вызовами не сохраняется. Импорты пишутся внутри сниппета.

{
  "amount": "code::import random; result = random.randint(20, 1000)",
  "createdAt": "code::import time; result = time.strftime('%Y-%m-%dT%H:%M:%S')",
  "title": "Заказ №code::import random; result = random.randint(1000, 9999)code:: оформлен"
}

Код исполняется только для значений из changes, то есть из вашего файла моков. Строки из настоящего ответа сервера в exec не попадают никогда, в том числе при мягком слиянии. Что из этого следует — см. Безопасность.


Что происходит с ответом

  1. mitmproxy получает ответ от бэкенда и отдаёт его плагину. Запрос при этом уже ушёл на сервер: плагин правит настоящий ответ, а не отменяет обращение к бэкенду. Именно поэтому возможно мягкое слияние — есть с чем сливать.
  2. Если включён сбор ответов, оригинал сохраняется в файл — до применения моков, иначе в коллекцию попало бы подменённое тело.
  3. Плагин сверяет отпечаток mocks.json и перечитывает файл только при изменении. Если файла нет или JSON битый, ответы идут без изменений, а в лог не чаще раза в 10 секунд пишется Failed to load mocks json (...).
  4. Если mockerServiceEnabled не равен true — выход, ответ не трогается.
  5. Собирается список активных моков: моки с isMockActive из папок с isFolderActive. Если он пуст — выход.
  6. CORS-preflight пропускается как есть: запрос OPTIONS с заголовком Access-Control-Request-Method не мокируется никогда. Подменённый ответ на preflight ломает CORS-проверку, и основной запрос вообще не уходит из браузера.
  7. Активные моки перебираются в порядке файла. Мок без url-строки или с нецелым statusCode пропускается с предупреждением в логе. Путь запроса сравнивается с url мока по флагам findExactPath и useNormalizedIds.
  8. Первый совпавший мок применяется, перебор прекращается:
    • новое тело собирается целиком до того, как ответ будет тронут: копия changes → выполнение code:: → слияние или замена → сериализация в JSON. Ошибка на любом шаге — предупреждение Mock '...' failed to build, skipping this response, ответ уходит клиенту оригинальным, и остальные моки для него уже не проверяются;
    • если changes пуст, тело и content-type не трогаются вовсе;
    • выставляются статус-код и reason-фраза, добавляется заголовок mitmproxyplugin: true;
    • если тело менялось, сначала выставляется content-type: application/json и только потом само тело: сеттер кодирует его по текущему заголовку, иначе при экзотическом charset оригинала JSON уехал бы клиенту не в UTF-8;
    • в лог уходит Successfully mocked => ....

Сбор оригинальных ответов

COLLECT_RESPONSES — константа в начале mocker.py, по умолчанию False. Меняется только правкой файла: ни CLI, ни mocks.json, ни панель на неё не влияют.

При True оригиналы ответов складываются в папку responses_collection рядом с mocker.py:

• собираются только ответы с content-type application/json или text/plain — на применение самих моков это ограничение не распространяется;
• имя файла — три последних сегмента пути (числовые заменены на _ID_, небезопасные символы — на _) и код ответа: запрос /api/path/user/100 с кодом 200 даёт path_user__ID___200.json;
• тело, которое не разобралось как JSON, сохраняется сырым текстом в виде JSON-строки;
• папка создаётся только тогда, когда есть что записать.


Ограничения

• Меняются только ответы. Запросы — URL, заголовки, тело — плагин не модифицирует.
• Матчинг только по URL: строгое равенство или вхождение подстроки. Регулярных выражений нет; метод, заголовки и тело запроса не учитываются.
• Сравнивается путь с query-строкой, без схемы и хоста, — ограничить мок доменом через mocks.json нельзя.
• Выигрывает первый совпавший мок в порядке файла. Вариантов «отвечать по очереди» или «сработать N раз» нет.
• Задержек, троттлинга, обрыва соединения и подмены сетевых ошибок нет.
• Изменённое тело всегда JSON: при непустом changes content-type принудительно становится application/json; при пустом тело и content-type не трогаются.
• Мягкое слияние работает только поверх ответа, корень которого — JSON-объект. Массив, строка и число верхнего уровня сливаются поверх пустого {} и теряются, причём без записи в лог.
• CORS-preflight не мокируется.
• HTTPS требует установленного CA-сертификата mitmproxy.
• Путь к mocks.json зашит: файл читается строго рядом с mocker.py, задать другой нечем.
• Файл моков один: профилей, версий и переключения наборов файлов нет.


Если не работает

Симптом Причина Что делать
Ни один ответ не меняется Не запущен mitmproxy с плагином или трафик идёт мимо прокси Проверить в логе прокси строки Loading script .../mocker.py и HTTP(S) proxy listening at *:8080., а сам запрос — в списке перехваченных запросов (flows)
HTTP подменяется, HTTPS — нет Клиент не доверяет сертификату mitmproxy Открыть http://mitm.it через тот же прокси и установить сертификат
В логе Mocker service is disabled: ... is False. mockerServiceEnabled не равен true (в том числе null, 0, "true") Поставить true или убрать ключ
В логе Failed to load mocks json (...) Файла нет рядом с mocker.py или JSON битый; ответы всё это время идут без изменений Проверить путь и валидность JSON; сообщение повторяется не чаще раза в 10 секунд
В логе Mock '...' skipped: 'url' is missing... У мока нет url-строки или statusCode не целое число (частая опечатка — "200") Исправить поля мока
Мок не срабатывает, в логе тишина Выключена папка (isFolderActive) или сам мок (isMockActive), либо раньше в файле совпал другой мок Включить флаги; проверить порядок — применяется первый совпавший
Мок не совпадает по URL В url попали схема и хост, лишний слэш или другая query-строка Включить globalSkippedMocksLoggingEnabled и сравнить mockUrl и targetUrl в логе
Правки из панели ни на что не влияют Прокси не запущен, либо панель пишет другой файл (запуск с --file или вторая копия проекта) Сверить путь из строки Config file : ... при старте панели с папкой, где лежит mocker.py
Панель не стартует: ERROR: port 46625 is already in use. Порт занят python dashboard.py --port 46626
Панель не сохраняет, показывает баннер про моки без обязательных полей Сервер отклоняет запись файла целиком, пока есть мок без url или без корректного statusCode (обязательный ключ, целое 100..599), а также при неверном типе любого другого поля Починить мок по указанному панелью пути до проблемного ключа — накопленные правки уйдут следом

Панель управления (необязательно)

dashboard.py — локальная веб-панель, редактор mocks.json. Она не перехватывает трафик, не применяет моки, не знает адреса прокси и не проверяет, запущен ли он: панель только читает и пишет файл. Без запущенного mitmproxy с mocker.py её правки ни на что не влияют.

Запуск панели

python dashboard.py

В консоли печатаются три строки:

Mock dashboard : http://127.0.0.1:46625
Config file    : G:\src\mitmproxy-plugin\mocks.json  (ok: 3 folders, 2 mocks)
Press Ctrl+C to stop.

В скобках — состояние файла: ok: N folders, M mocks, missing - the UI can create it, invalid JSON - the UI will show details, structure at ... is not editable in the UI или unreadable (...). Сам файл при старте не создаётся — только по кнопке «Создать файл» в интерфейсе.

Браузер открывается автоматически. Зависимостей нет, интерфейс целиком лежит в dashboard.html — один самодостаточный файл без внешних ресурсов, он должен находиться рядом с dashboard.py.

Аргументы запуска:

--port N — слушать другой порт (по умолчанию 46625; 0 — любой свободный, фактический печатается в консоли);
--file путь — какой файл редактировать (по умолчанию — mocks.json рядом с dashboard.py);
--no-open — не открывать браузер при старте.

О --file: mocker.py читает mocks.json строго рядом с собой, и этот путь не настраивается. Панель, запущенная с чужим --file, будет править файл, которого прокси не видит.

Адрес прослушивания зашит: 127.0.0.1, ключа --host нет.

Примечание про порт: 46625 — это слово «MOCK» на телефонной клавиатуре (6-6-2-5) с префиксом 4. Непопулярный порт внутри диапазона 40000–49151, ниже эфемерного диапазона Windows, и ни за одним известным dev-инструментом он не числится.

Что умеет панель

• Тумблер «Сервис моков» (mockerServiceEnabled) и экран «Настройки» с флагами из variables.
• Папки: создание, переименование с сохранением позиции в файле, описание, включение и выключение, удаление — для непустой папки диалог с числом моков, после удаления тост «Вернуть» на 10 секунд.
• Моки: создание, правка, дублирование (копия всегда выключена и встаёт сразу за оригиналом), включение и выключение, перенос между папками, удаление с «Вернуть».
• Проверка code:: без исполнения: панель разбирает сниппет в AST и сообщает о синтаксической ошибке и об отсутствующем присваивании result. Сам код она не запускает никогда.
• Поиск по имени и URL мока, по имени и описанию папки и по содержимому changes — последние помечаются подписью «совпадение в changes». Результат всегда список моков: совпадение по папке выводит её моки, поэтому пустая папка через поиск не находится.
• Атомарная запись с бэкапом: перед каждой записью текущее содержимое уходит в mocks.json.bak (хранится одна последняя версия), сам файл заменяется целиком — прокси никогда не видит половину.
• Контроль конфликтов по etag: если mocks.json изменили снаружи — руками или вторым экземпляром панели, — правка не будет молча перетёрта.
• Внешние изменения подхватываются сами (опрос файла раз в 2 секунды); для открытого в редакторе мока показывается баннер «изменён извне» с возможностью взять версию из файла.
• Экраны для сломанного файла: «Файл не найден» с кнопкой «Создать файл»; «Не удалось прочитать» и «Неподдерживаемая структура» (корень, variables, folders, папка, её mocks или мок — не объект) с кнопками «Проверить снова» и «Заменить пустой конфигурацией…». Пока файл сломан, панель в него ничего не пишет.
• Светлая и тёмная темы, по умолчанию — как в системе.

Чего панель не делает: не показывает трафик и лог прокси, не запускает и не останавливает прокси, не исполняет code::, не управляет сбором ответов, не редактирует несколько файлов сразу и не меняет порядок моков перетаскиванием — порядок задаётся порядком ключей в файле.

Ручная правка файла и панель

Плагин и панель читают один файл, но требования к нему разные:

• Один мок без url или без корректного statusCode (ключ обязателен, целое 100..599) — даже выключенный — заставляет сервер панели отклонить запись файла целиком. Так же отклоняется запись при неверном типе остальных полей мока. Панель покажет путь до проблемного ключа и придержит правки в очереди, пока мок не починят. Плагин такой мок просто пропускает с предупреждением.
• Дублирующийся ключ в JSON панель считает ошибкой разбора и файл не открывает; mocker.py в этом случае молча берёт последнее значение.
• После первого сохранения из панели файл переписывается целиком в её формате: отступ в два пробела, кириллица без экранирования, перевод строки в конце. Ручное выравнивание и пустые строки, как в default_mocks.json, теряются. Порядок ключей при этом сохраняется — кроме полностью числовых названий папок и моков: при сохранении из панели такой ключ встаёт в начало объекта, а значит меняется и приоритет мока. Сама панель предупреждает об этом прямо в диалоге создания.

Горячие клавиши панели

Действие Клавиши
Поиск /
Из поиска — к результатам
Закрыть / отменить Esc
Новый мок N
Новая папка Shift+N
Выбор в списке
Открыть мок Enter
Включить/выключить мок Space
Дублировать D
Удалить мок Delete
Сохранить Ctrl+S (на macOS — ⌘+S)
Сохранить и закрыть Ctrl+Enter (на macOS — ⌘+Enter)
Форматировать JSON Shift+Alt+F
Отступ в JSON Tab
Убрать отступ Shift+Tab
Выйти из JSON-редактора Esc, затем Tab
Эта справка ?

Буквенные клавиши работают и в русской раскладке. Мок удаляется также по Backspace, начало и конец списка — Home и End. Тот же список всегда под рукой в самой панели — кнопка «?» в шапке.


Безопасность

• Значение с code:: исполняется прокси через exec. Поэтому доступ на запись в mocks.json равен выполнению произвольного кода на машине, где работает mitmproxy. Файл моков — это и есть граница доверия: используйте только свои и проверенные моки.
• Исполняются только строки из changes, то есть из самого файла моков. Данные из настоящего ответа сервера в exec не попадают никогда, в том числе при мягком слиянии.
• Панель слушает строго 127.0.0.1, аутентификации у неё нет — граница доверия проходит по loopback. Наружу её выставлять нельзя: доступ к панели равен доступу на запись в mocks.json. Для удалённого доступа используйте SSH-туннель.
• Заголовок Host проверяется у всех запросов, допускаются только 127.0.0.1 и localhost. Это защита от DNS-rebinding, когда чужая страница резолвит своё имя в 127.0.0.1.
• От CSRF защищают три слоя: проверка Origin на пишущих маршрутах (PUT /api/config, POST /api/validate/changes), обязательный Content-Type: application/json и намеренно нереализованный do_OPTIONS — межсайтовый preflight получает 501, и браузер не пускает запись дальше.
• Тело запроса ограничено 10 МБ. Все ответы отдаются с Cache-Control: no-store и X-Content-Type-Options: nosniff, HTML — дополнительно с CSP, запрещающей любые внешние источники.


Структура репозитория

mitmproxy-plugin/
├── mocker.py              # плагин mitmproxy: сама подмена ответов, работает только внутри mitmproxy
├── dashboard.py           # веб-панель: редактор mocks.json, на трафик не влияет (только stdlib)
├── dashboard.html         # интерфейс панели — один самодостаточный файл
├── mocks.json             # рабочий файл моков: mocker.py читает его строго рядом с собой
├── default_mocks.json     # пример конфигурации, в работе не используется
├── responses_collection/  # коллекция ответов; в репозитории лежит один файл-пример
└── LICENSE                # лицензия MIT

Во время работы появляются служебные файлы — первые три рядом с mocks.json, последний рядом с mocker.py:

mocks.json.bak — бэкап, который панель делает перед каждой записью (в .gitignore);
mocks.*.tmp — временные файлы атомарной записи, живут доли секунды (в .gitignore);
mocks.json.lock — межпроцессный замок панели: создаётся при первой записи и никогда не удаляется. В .gitignore его нет, поэтому он виден в git status как untracked;
responses_collection/ — оригиналы ответов при COLLECT_RESPONSES = True (в .gitignore, но сама папка с примером уже в репозитории).

Файл mocks.json отслеживается git: он одновременно и пример, и рабочий файл, поэтому локальные моки будут конфликтовать при обновлении репозитория. Если это мешает, держите свои моки отдельной веткой или скажите git не следить за изменениями файла:

git update-index --skip-worktree mocks.json

Вклад и лицензия

Если вы хотите внести изменения или улучшения в проект — буду рад вашим pull request’ам или issues.

Проект распространяется по лицензии MIT — полный текст в файле LICENSE.

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

About

Mitmproxy plugin example

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages