Локальный инструмент разработчика: подменяет 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 mitmproxy2. Забрать репозиторий
git clone https://github.com/kolindes/mitmproxy-plugin.git
cd mitmproxy-plugin3. Проверить, что 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¶m2=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¶m2=123 matched mockUrl:/api/path/method?param1=foo¶m2=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. Подробности — в разделе Панель управления.
Для 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¶m2=123 matched mockUrl:/api/path/method?param1=foo¶m2=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¶m2=123] <-> targetUrl:[/api/users/_ID_] :: findExactPath=True, useNormalizedIds=True
Здесь видно ровно то, что сравнивалось: обе строки уже приведены к виду, в котором идёт сравнение (с нормализацией _ID_, если она включена), и рядом указаны действующие для этого мока флаги. Уровень записи тот же — INFO.
Рабочий файл — 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 — папки с моками. Ключ необязателен.
Неизвестные ключи корня плагин игнорирует, а панель сохраняет как есть и перечисляет на экране «Настройки».
Все три флага при отсутствии ключа считаются 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; для кода, которого в ней нет, она становится пустой.
• findExactPath — true: url мока должен совпасть с путём запроса целиком; false: достаточно вхождения подстроки. При отсутствии ключа берётся globalFindExactPath (явный null равносилен false, а не наследованию).
• useNormalizedIds — true: перед сравнением обе стороны нормализуются, каждый целиком числовой сегмент пути заменяется на _ID_ (query-часть после ? не трогается). Поэтому url мока можно писать и как /api/users/123, и как /api/users/_ID_. При отсутствии ключа берётся globalUseNormalizedIds.
• shouldApplyChangesSoftly — false (умолчание): тело ответа заменяется данными из 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')"
}Код выполняется заново на каждом сработавшем ответе — подробности в следующем разделе.
Любое строковое значение внутри 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 не попадают никогда, в том числе при мягком слиянии. Что из этого следует — см. Безопасность.
- mitmproxy получает ответ от бэкенда и отдаёт его плагину. Запрос при этом уже ушёл на сервер: плагин правит настоящий ответ, а не отменяет обращение к бэкенду. Именно поэтому возможно мягкое слияние — есть с чем сливать.
- Если включён сбор ответов, оригинал сохраняется в файл — до применения моков, иначе в коллекцию попало бы подменённое тело.
- Плагин сверяет отпечаток mocks.json и перечитывает файл только при изменении. Если файла нет или JSON битый, ответы идут без изменений, а в лог не чаще раза в 10 секунд пишется
Failed to load mocks json (...). - Если
mockerServiceEnabledне равенtrue— выход, ответ не трогается. - Собирается список активных моков: моки с
isMockActiveиз папок сisFolderActive. Если он пуст — выход. - CORS-preflight пропускается как есть: запрос OPTIONS с заголовком
Access-Control-Request-Methodне мокируется никогда. Подменённый ответ на preflight ломает CORS-проверку, и основной запрос вообще не уходит из браузера. - Активные моки перебираются в порядке файла. Мок без url-строки или с нецелым
statusCodeпропускается с предупреждением в логе. Путь запроса сравнивается с url мока по флагамfindExactPathиuseNormalizedIds. - Первый совпавший мок применяется, перебор прекращается:
• новое тело собирается целиком до того, как ответ будет тронут: копия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.