This commit is contained in:
2026-06-09 13:31:20 +03:00
parent bbdf19bd05
commit f251e261f9
+222 -19
View File
@@ -6,24 +6,34 @@
Поддерживает: Поддерживает:
- GET /data.json - получение данных - GET /data.json - получение данных
- POST /save - сохранение данных в файл - POST /save - сохранение данных в файл
- POST /reset - сброс к дефолтным данным (загружает из data.default.json) - POST /reset - сброс к дефолтным данным
Запуск:
python server.py # обычный запуск
python server.py --daemon # фоновый режим (демон)
python server.py --stop # остановить фоновый процесс
python server.py --status # проверить статус фонового процесса
""" """
import http.server import http.server
import socketserver import socketserver
import json import json
import os import os
import shutil import sys
import signal
import atexit
import time
import socket
from urllib.parse import urlparse from urllib.parse import urlparse
# Конфигурация
PORT = 8092 PORT = 8092
DATA_FILE = 'data.json' DATA_FILE = 'data.json'
DEFAULT_DATA_FILE = 'data.default.json' DEFAULT_DATA_FILE = 'data.default.json'
PID_FILE = 'server.pid'
LOG_FILE = 'server.log'
# Дефолтные данные вынесены в отдельный файл data.default.json
# Если нужно создать файл с дефолтом - раскомментировать функцию ниже
class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def end_headers(self): def end_headers(self):
@@ -39,7 +49,6 @@ class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self): def do_GET(self):
parsed_path = urlparse(self.path) parsed_path = urlparse(self.path)
# Отдаём data.json
if parsed_path.path == '/data.json': if parsed_path.path == '/data.json':
self.send_response(200) self.send_response(200)
self.send_header('Content-Type', 'application/json; charset=utf-8') self.send_header('Content-Type', 'application/json; charset=utf-8')
@@ -51,11 +60,9 @@ class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
self.send_error(404, 'data.json not found') self.send_error(404, 'data.json not found')
return return
# Отдаём index.html
if parsed_path.path == '/' or parsed_path.path == '/index.html': if parsed_path.path == '/' or parsed_path.path == '/index.html':
self.path = '/index.html' self.path = '/index.html'
# Статические файлы
if self.path.endswith('.html'): if self.path.endswith('.html'):
self.send_response(200) self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8') self.send_header('Content-Type', 'text/html; charset=utf-8')
@@ -72,14 +79,12 @@ class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_POST(self): def do_POST(self):
parsed_path = urlparse(self.path) parsed_path = urlparse(self.path)
# Сохранение данных
if parsed_path.path == '/save': if parsed_path.path == '/save':
content_length = int(self.headers.get('Content-Length', 0)) content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length) post_data = self.rfile.read(content_length)
try: try:
data = json.loads(post_data.decode('utf-8')) data = json.loads(post_data.decode('utf-8'))
with open(DATA_FILE, 'w', encoding='utf-8') as f: with open(DATA_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2) json.dump(data, f, ensure_ascii=False, indent=2)
@@ -103,10 +108,10 @@ class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
self.wfile.write(response.encode('utf-8')) self.wfile.write(response.encode('utf-8'))
return return
# Сброс к дефолту (если есть файл data.default.json)
if parsed_path.path == '/reset': if parsed_path.path == '/reset':
try: try:
if os.path.exists(DEFAULT_DATA_FILE): if os.path.exists(DEFAULT_DATA_FILE):
import shutil
shutil.copy(DEFAULT_DATA_FILE, DATA_FILE) shutil.copy(DEFAULT_DATA_FILE, DATA_FILE)
self.send_response(200) self.send_response(200)
self.send_header('Content-Type', 'application/json') self.send_header('Content-Type', 'application/json')
@@ -131,17 +136,166 @@ class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
self.end_headers() self.end_headers()
def log_message(self, format, *args): def log_message(self, format, *args):
print(f"[{self.address_string()}] {format % args}") """Логирование в файл или консоль в зависимости от режима"""
message = f"[{self.address_string()}] {format % args}"
if os.environ.get('DAEMON_MODE') == '1':
with open(LOG_FILE, 'a', encoding='utf-8') as f:
f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} {message}\n")
else:
print(message)
def is_port_in_use(port):
"""Проверка, занят ли порт"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind(('', port))
return False
except OSError:
return True
def write_pid_file():
"""Запись PID в файл"""
with open(PID_FILE, 'w') as f:
f.write(str(os.getpid()))
def remove_pid_file():
"""Удаление PID файла"""
if os.path.exists(PID_FILE):
os.remove(PID_FILE)
def read_pid_file():
"""Чтение PID из файла"""
if os.path.exists(PID_FILE):
with open(PID_FILE, 'r') as f:
return int(f.read().strip())
return None
def is_process_running(pid):
"""Проверка, запущен ли процесс с указанным PID"""
try:
os.kill(pid, 0)
return True
except OSError:
return False
def stop_daemon():
"""Остановка фонового процесса"""
pid = read_pid_file()
if pid is None:
print("❌ PID файл не найден. Сервер не запущен в фоновом режиме.")
return False
if not is_process_running(pid):
print(f"⚠️ Процесс с PID {pid} не найден. Возможно, сервер уже остановлен.")
remove_pid_file()
return False
try:
os.kill(pid, signal.SIGTERM)
time.sleep(1)
if is_process_running(pid):
os.kill(pid, signal.SIGKILL)
remove_pid_file()
print(f"✅ Сервер (PID: {pid}) остановлен.")
return True
except OSError as e:
print(f"❌ Ошибка при остановке сервера: {e}")
return False
def check_status():
"""Проверка статуса фонового процесса"""
pid = read_pid_file()
if pid is None:
print("❌ Сервер не запущен в фоновом режиме.")
return
if is_process_running(pid):
print(f"✅ Сервер запущен (PID: {pid})")
print(f"🌐 http://localhost:{PORT}")
else:
print(f"⚠️ PID файл существует, но процесс {pid} не найден.")
remove_pid_file()
def run_daemon():
"""Запуск сервера в фоновом режиме"""
# Проверка, не запущен ли уже сервер
pid = read_pid_file()
if pid and is_process_running(pid):
print(f"❌ Сервер уже запущен (PID: {pid})")
print(f"🌐 http://localhost:{PORT}")
return
# Проверка порта
if is_port_in_use(PORT):
print(f"❌ Порт {PORT} уже занят. Сервер не может быть запущен.")
return
# Форк процесса для демонизации
try:
pid = os.fork()
if pid > 0:
# Родительский процесс завершается
print(f"🚀 Сервер запущен в фоновом режиме (PID: {pid})")
print(f"🌐 http://localhost:{PORT}")
print(f"📄 Лог-файл: {LOG_FILE}")
print(f"📄 PID-файл: {PID_FILE}")
print("\nУправление:")
print(f" python server.py --stop - остановить сервер")
print(f" python server.py --status - проверить статус")
sys.exit(0)
except OSError as e:
print(f"❌ Ошибка форка: {e}")
sys.exit(1)
# Демонизация: отсоединяемся от терминала
os.setsid()
os.umask(0)
# Перенаправление stdout/stderr в файл
sys.stdout.flush()
sys.stderr.flush()
with open(LOG_FILE, 'a') as log:
os.dup2(log.fileno(), sys.stdout.fileno())
os.dup2(log.fileno(), sys.stderr.fileno())
# Установка флага демона для логирования
os.environ['DAEMON_MODE'] = '1'
write_pid_file()
atexit.register(remove_pid_file)
# Обработка сигналов
def signal_handler(sig, frame):
sys.exit(0)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
ensure_data_file()
# Создание и запуск сервера
with socketserver.TCPServer(("", PORT), CustomHTTPRequestHandler) as httpd:
print(f"Сервер запущен на порту {PORT}")
httpd.serve_forever()
def ensure_data_file(): def ensure_data_file():
"""Проверяет наличие data.json, если нет - создаёт из дефолта или пустой""" """Проверяет наличие data.json, если нет - создаёт из дефолта или пустой"""
if not os.path.exists(DATA_FILE): if not os.path.exists(DATA_FILE):
if os.path.exists(DEFAULT_DATA_FILE): if os.path.exists(DEFAULT_DATA_FILE):
import shutil
shutil.copy(DEFAULT_DATA_FILE, DATA_FILE) shutil.copy(DEFAULT_DATA_FILE, DATA_FILE)
print(f"📄 Создан файл данных из {DEFAULT_DATA_FILE}") print(f"📄 Создан файл данных из {DEFAULT_DATA_FILE}")
else: else:
# Создаём минимальный пустой файл
empty_data = { empty_data = {
"organizations": [], "organizations": [],
"additionalOrganizations": [], "additionalOrganizations": [],
@@ -150,11 +304,17 @@ def ensure_data_file():
with open(DATA_FILE, 'w', encoding='utf-8') as f: with open(DATA_FILE, 'w', encoding='utf-8') as f:
json.dump(empty_data, f, ensure_ascii=False, indent=2) json.dump(empty_data, f, ensure_ascii=False, indent=2)
print(f"📄 Создан пустой файл данных: {DATA_FILE}") print(f"📄 Создан пустой файл данных: {DATA_FILE}")
print(f"⚠️ Для работы с данными создайте файл {DEFAULT_DATA_FILE} с дефолтными данными")
def run_server(): def run_foreground():
os.chdir(os.path.dirname(os.path.abspath(__file__))) """Запуск сервера в обычном (не фоновом) режиме"""
if is_port_in_use(PORT):
print(f"❌ Порт {PORT} уже занят.")
print("Возможные решения:")
print(f" 1. Остановите другой сервер: python server.py --stop")
print(f" 2. Измените PORT в файле server.py")
return False
ensure_data_file() ensure_data_file()
with socketserver.TCPServer(("", PORT), CustomHTTPRequestHandler) as httpd: with socketserver.TCPServer(("", PORT), CustomHTTPRequestHandler) as httpd:
@@ -163,7 +323,6 @@ def run_server():
print(f"📁 Директория: {os.getcwd()}") print(f"📁 Директория: {os.getcwd()}")
print(f"🌐 Локальный доступ: http://localhost:{PORT}") print(f"🌐 Локальный доступ: http://localhost:{PORT}")
print(f"📄 Файл данных: {DATA_FILE}") print(f"📄 Файл данных: {DATA_FILE}")
print(f"📄 Дефолтный файл (если есть): {DEFAULT_DATA_FILE}")
print(f"⏹️ Для остановки сервера нажмите Ctrl+C") print(f"⏹️ Для остановки сервера нажмите Ctrl+C")
print("=" * 60) print("=" * 60)
@@ -171,8 +330,52 @@ def run_server():
httpd.serve_forever() httpd.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
print("\n🛑 Сервер остановлен.") print("\n🛑 Сервер остановлен.")
httpd.shutdown() return True
def print_help():
"""Вывод справки"""
print("""
╔══════════════════════════════════════════════════════════════════╗
║ Сервер управления данными ДНР ║
╚══════════════════════════════════════════════════════════════════╝
Использование:
python server.py # Обычный запуск (в консоли)
python server.py --daemon # Фоновый запуск (демон)
python server.py --stop # Остановка фонового процесса
python server.py --status # Проверка статуса фонового процесса
python server.py --help # Показать эту справку
Настройки:
PORT = 8092 # Порт сервера
DATA_FILE = data.json # Файл с данными
LOG_FILE = server.log # Файл логов (для фонового режима)
PID_FILE = server.pid # Файл с PID процесса
После запуска:
Откройте в браузере: http://localhost:8000
""")
def main():
"""Главная функция"""
if len(sys.argv) > 1:
arg = sys.argv[1].lower()
if arg == '--daemon' or arg == '-d':
run_daemon()
elif arg == '--stop' or arg == '-s':
stop_daemon()
elif arg == '--status' or arg == '-st':
check_status()
elif arg == '--help' or arg == '-h':
print_help()
else:
print(f"❌ Неизвестный аргумент: {arg}")
print_help()
else:
run_foreground()
if __name__ == "__main__": if __name__ == "__main__":
run_server() main()