v1.1
This commit is contained in:
@@ -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()
|
||||||
Reference in New Issue
Block a user