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 - получение данных
- 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 socketserver
import json
import os
import shutil
import sys
import signal
import atexit
import time
import socket
from urllib.parse import urlparse
# Конфигурация
PORT = 8092
DATA_FILE = 'data.json'
DEFAULT_DATA_FILE = 'data.default.json'
PID_FILE = 'server.pid'
LOG_FILE = 'server.log'
# Дефолтные данные вынесены в отдельный файл data.default.json
# Если нужно создать файл с дефолтом - раскомментировать функцию ниже
class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def end_headers(self):
@@ -39,7 +49,6 @@ class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
parsed_path = urlparse(self.path)
# Отдаём data.json
if parsed_path.path == '/data.json':
self.send_response(200)
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')
return
# Отдаём index.html
if parsed_path.path == '/' or parsed_path.path == '/index.html':
self.path = '/index.html'
# Статические файлы
if self.path.endswith('.html'):
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
@@ -72,14 +79,12 @@ class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_POST(self):
parsed_path = urlparse(self.path)
# Сохранение данных
if parsed_path.path == '/save':
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length)
try:
data = json.loads(post_data.decode('utf-8'))
with open(DATA_FILE, 'w', encoding='utf-8') as f:
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'))
return
# Сброс к дефолту (если есть файл data.default.json)
if parsed_path.path == '/reset':
try:
if os.path.exists(DEFAULT_DATA_FILE):
import shutil
shutil.copy(DEFAULT_DATA_FILE, DATA_FILE)
self.send_response(200)
self.send_header('Content-Type', 'application/json')
@@ -131,17 +136,166 @@ class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
self.end_headers()
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():
"""Проверяет наличие data.json, если нет - создаёт из дефолта или пустой"""
if not os.path.exists(DATA_FILE):
if os.path.exists(DEFAULT_DATA_FILE):
import shutil
shutil.copy(DEFAULT_DATA_FILE, DATA_FILE)
print(f"📄 Создан файл данных из {DEFAULT_DATA_FILE}")
else:
# Создаём минимальный пустой файл
empty_data = {
"organizations": [],
"additionalOrganizations": [],
@@ -150,11 +304,17 @@ def ensure_data_file():
with open(DATA_FILE, 'w', encoding='utf-8') as f:
json.dump(empty_data, f, ensure_ascii=False, indent=2)
print(f"📄 Создан пустой файл данных: {DATA_FILE}")
print(f"⚠️ Для работы с данными создайте файл {DEFAULT_DATA_FILE} с дефолтными данными")
def run_server():
os.chdir(os.path.dirname(os.path.abspath(__file__)))
def run_foreground():
"""Запуск сервера в обычном (не фоновом) режиме"""
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()
with socketserver.TCPServer(("", PORT), CustomHTTPRequestHandler) as httpd:
@@ -163,7 +323,6 @@ def run_server():
print(f"📁 Директория: {os.getcwd()}")
print(f"🌐 Локальный доступ: http://localhost:{PORT}")
print(f"📄 Файл данных: {DATA_FILE}")
print(f"📄 Дефолтный файл (если есть): {DEFAULT_DATA_FILE}")
print(f"⏹️ Для остановки сервера нажмите Ctrl+C")
print("=" * 60)
@@ -171,8 +330,52 @@ def run_server():
httpd.serve_forever()
except KeyboardInterrupt:
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__":
run_server()
main()