初步整合:

1. python 内置服务器
2. 将配置文件统一目录
This commit is contained in:
mofeng-git
2025-01-01 14:26:22 +00:00
parent 5db37797ea
commit d5a0b1a8b3
411 changed files with 7387 additions and 7263 deletions

View File

@@ -84,7 +84,7 @@ class AuthApi:
# =====
@exposed_http("POST", "/auth/login", auth_required=False)
@exposed_http("POST", "/api/auth/login", auth_required=False)
async def __login_handler(self, req: Request) -> Response:
if self.__auth_manager.is_auth_enabled():
credentials = await req.post()
@@ -97,13 +97,13 @@ class AuthApi:
raise ForbiddenError()
return make_json_response()
@exposed_http("POST", "/auth/logout")
@exposed_http("POST", "/api/auth/logout")
async def __logout_handler(self, req: Request) -> Response:
if self.__auth_manager.is_auth_enabled():
token = valid_auth_token(req.cookies.get(_COOKIE_AUTH_TOKEN, ""))
self.__auth_manager.logout(token)
return make_json_response()
@exposed_http("GET", "/auth/check")
@exposed_http("GET", "/api/auth/check")
async def __check_handler(self, _: Request) -> Response:
return make_json_response()

View File

@@ -49,7 +49,7 @@ class ExportApi:
# =====
@exposed_http("GET", "/export/prometheus/metrics")
@exposed_http("GET", "/api/export/prometheus/metrics")
async def __prometheus_metrics_handler(self, _: Request) -> Response:
return Response(text=(await self.__get_prometheus_metrics()))

View File

@@ -73,7 +73,7 @@ class HidApi:
async def __state_handler(self, _: Request) -> Response:
return make_json_response(await self.__hid.get_state())
@exposed_http("POST", "/hid/set_params")
@exposed_http("POST", "/api/hid/set_params")
async def __set_params_handler(self, req: Request) -> Response:
params = {
key: validator(req.query.get(key))
@@ -87,12 +87,12 @@ class HidApi:
self.__hid.set_params(**params) # type: ignore
return make_json_response()
@exposed_http("POST", "/hid/set_connected")
@exposed_http("POST", "/api/hid/set_connected")
async def __set_connected_handler(self, req: Request) -> Response:
self.__hid.set_connected(valid_bool(req.query.get("connected")))
return make_json_response()
@exposed_http("POST", "/hid/reset")
@exposed_http("POST", "/api/hid/reset")
async def __reset_handler(self, _: Request) -> Response:
await self.__hid.reset()
return make_json_response()
@@ -112,11 +112,11 @@ class HidApi:
},
}
@exposed_http("GET", "/hid/keymaps")
@exposed_http("GET", "/api/hid/keymaps")
async def __keymaps_handler(self, _: Request) -> Response:
return make_json_response(await self.get_keymaps())
@exposed_http("POST", "/hid/print")
@exposed_http("POST", "/api/hid/print")
async def __print_handler(self, req: Request) -> Response:
text = await req.text()
limit = int(valid_int_f0(req.query.get("limit", 1024)))
@@ -243,7 +243,7 @@ class HidApi:
# =====
@exposed_http("POST", "/hid/events/send_key")
@exposed_http("POST", "/api/hid/events/send_key")
async def __events_send_key_handler(self, req: Request) -> Response:
key = valid_hid_key(req.query.get("key"))
if "state" in req.query:
@@ -253,7 +253,7 @@ class HidApi:
self.__hid.send_key_events([(key, True), (key, False)])
return make_json_response()
@exposed_http("POST", "/hid/events/send_mouse_button")
@exposed_http("POST", "/api/hid/events/send_mouse_button")
async def __events_send_mouse_button_handler(self, req: Request) -> Response:
button = valid_hid_mouse_button(req.query.get("button"))
if "state" in req.query:
@@ -264,18 +264,18 @@ class HidApi:
self.__hid.send_mouse_button_event(button, False)
return make_json_response()
@exposed_http("POST", "/hid/events/send_mouse_move")
@exposed_http("POST", "/api/hid/events/send_mouse_move")
async def __events_send_mouse_move_handler(self, req: Request) -> Response:
to_x = valid_hid_mouse_move(req.query.get("to_x"))
to_y = valid_hid_mouse_move(req.query.get("to_y"))
self.__hid.send_mouse_move_event(to_x, to_y)
return make_json_response()
@exposed_http("POST", "/hid/events/send_mouse_relative")
@exposed_http("POST", "/api/hid/events/send_mouse_relative")
async def __events_send_mouse_relative_handler(self, req: Request) -> Response:
return self.__process_http_delta_event(req, self.__hid.send_mouse_relative_event)
@exposed_http("POST", "/hid/events/send_mouse_wheel")
@exposed_http("POST", "/api/hid/events/send_mouse_wheel")
async def __events_send_mouse_wheel_handler(self, req: Request) -> Response:
return self.__process_http_delta_event(req, self.__hid.send_mouse_wheel_event)

View File

@@ -20,8 +20,18 @@
# ========================================================================== #
import os
from aiohttp import ClientConnectionError, ClientSession
from aiohttp import UnixConnector
from aiohttp.web import Request
from aiohttp.web import Response
from aiohttp.web import HTTPForbidden
from aiohttp.web import HTTPNotFound
from aiohttp.web import FileResponse
from aiohttp.web import HTTPInternalServerError
from aiohttp.web import StreamResponse
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode
from ....htserver import exposed_http
from ....htserver import make_json_response
@@ -35,10 +45,13 @@ from ..info import InfoManager
class InfoApi:
def __init__(self, info_manager: InfoManager) -> None:
self.__info_manager = info_manager
self.static_dir = 'kvmd_data/usr/share/kvmd/web'
self.target_stream_server = 'http://127.0.0.1:8081'
# =====
@exposed_http("GET", "/info")
@exposed_http("GET", "/api/info")
async def __common_state_handler(self, req: Request) -> Response:
fields = self.__valid_info_fields(req)
return make_json_response(await self.__info_manager.get_state(fields))
@@ -49,3 +62,52 @@ class InfoApi:
arg=req.query.get("fields", ",".join(available)),
variants=available,
) or available)
@exposed_http("GET", "/streamer/stream")
async def proxy_stream_handler(self, request):
socket_path = '/home/mofeng/One-KVM/kvmd_data/run/kvmd/ustreamer.sock'
query_string = urlencode(request.query)
headers = request.headers.copy()
try:
async with ClientSession(connector=UnixConnector(path=socket_path)) as session:
backend_url = f'http://localhost/stream?{query_string}' if query_string else 'http://localhost/stream'
async with session.get(backend_url, headers=headers) as resp:
response = StreamResponse(status=resp.status, reason=resp.reason, headers=resp.headers)
await response.prepare(request)
while True:
chunk = await resp.content.read(512000)
if not chunk:
break
await response.write(chunk)
return response
except ClientConnectionError:
return Response(status=500, text="Client connection was closed")
@exposed_http("GET", "/{path:.*}", auth_required=False)
async def __html_file_handler(self, req: Request) -> Response:
path = req.match_info['path']
full_path = os.path.normpath(os.path.join(self.static_dir, path))
print("---------------")
print(full_path)
# 安全检查:确保请求的文件在允许的基础目录内
if not full_path.startswith(self.static_dir):
raise HTTPForbidden(text="Access denied.")
if os.path.isdir(full_path):
index_path = os.path.join(full_path, 'index.html')
if os.path.isfile(index_path):
full_path = index_path
else:
raise HTTPNotFound(text="Directory does not contain an index.html file.")
# 检查调整后的路径是否为现有文件
if not (os.path.exists(full_path) and os.path.isfile(full_path)):
raise HTTPNotFound(text="File not found.")
try:
return FileResponse(full_path)
except IOError as e:
raise HTTPInternalServerError(text=str(e))

View File

@@ -46,7 +46,7 @@ class LogApi:
# =====
@exposed_http("GET", "/log")
@exposed_http("GET", "/api/log")
async def __log_handler(self, req: Request) -> StreamResponse:
if self.__log_reader is None:
raise LogReaderDisabledError()

View File

@@ -61,7 +61,7 @@ class MsdApi:
# =====
@exposed_http("GET", "/msd")
@exposed_http("GET", "/api/msd")
async def __state_handler(self, _: Request) -> Response:
state = await self.__msd.get_state()
if state["storage"] and state["storage"]["parts"]:
@@ -69,7 +69,7 @@ class MsdApi:
state["storage"]["free"] = state["storage"]["parts"][""]["free"] # Legacy API
return make_json_response(state)
@exposed_http("POST", "/msd/set_params")
@exposed_http("POST", "/api/msd/set_params")
async def __set_params_handler(self, req: Request) -> Response:
params = {
key: validator(req.query.get(param))
@@ -83,19 +83,19 @@ class MsdApi:
await self.__msd.set_params(**params) # type: ignore
return make_json_response()
@exposed_http("POST", "/msd/set_connected")
@exposed_http("POST", "/api/msd/set_connected")
async def __set_connected_handler(self, req: Request) -> Response:
await self.__msd.set_connected(valid_bool(req.query.get("connected")))
return make_json_response()
@exposed_http("POST", "/msd/make_image")
@exposed_http("POST", "/api/msd/make_image")
async def __set_zipped_handler(self, req: Request) -> Response:
await self.__msd.make_image(valid_bool(req.query.get("zipped")))
return make_json_response()
# =====
@exposed_http("GET", "/msd/read")
@exposed_http("GET", "/api/msd/read")
async def __read_handler(self, req: Request) -> StreamResponse:
name = valid_msd_image_name(req.query.get("image"))
compressors = {
@@ -143,7 +143,7 @@ class MsdApi:
# =====
@exposed_http("POST", "/msd/write")
@exposed_http("POST", "/api/msd/write")
async def __write_handler(self, req: Request) -> Response:
unsafe_prefix = req.query.get("prefix", "") + "/"
name = valid_msd_image_name(unsafe_prefix + req.query.get("image", ""))
@@ -159,7 +159,7 @@ class MsdApi:
written = await writer.write_chunk(chunk)
return make_json_response(self.__make_write_info(name, size, written))
@exposed_http("POST", "/msd/write_remote")
@exposed_http("POST", "/api/msd/write_remote")
async def __write_remote_handler(self, req: Request) -> (Response | StreamResponse): # pylint: disable=too-many-locals
unsafe_prefix = req.query.get("prefix", "") + "/"
url = valid_url(req.query.get("url"))
@@ -223,12 +223,12 @@ class MsdApi:
# =====
@exposed_http("POST", "/msd/remove")
@exposed_http("POST", "/api/msd/remove")
async def __remove_handler(self, req: Request) -> Response:
await self.__msd.remove(valid_msd_image_name(req.query.get("image")))
return make_json_response()
@exposed_http("POST", "/msd/reset")
@exposed_http("POST", "/api/msd/reset")
async def __reset_handler(self, _: Request) -> Response:
await self.__msd.reset()
return make_json_response()

View File

@@ -65,7 +65,7 @@ class RedfishApi:
# =====
@exposed_http("GET", "/redfish/v1", auth_required=False)
@exposed_http("GET", "/api/redfish/v1", auth_required=False)
async def __root_handler(self, _: Request) -> Response:
return make_json_response({
"@odata.id": "/redfish/v1",
@@ -76,7 +76,7 @@ class RedfishApi:
"Systems": {"@odata.id": "/redfish/v1/Systems"},
}, wrap_result=False)
@exposed_http("GET", "/redfish/v1/Systems")
@exposed_http("GET", "/api/redfish/v1/Systems")
async def __systems_handler(self, _: Request) -> Response:
return make_json_response({
"@odata.id": "/redfish/v1/Systems",
@@ -86,7 +86,7 @@ class RedfishApi:
"Name": "Computer System Collection",
}, wrap_result=False)
@exposed_http("GET", "/redfish/v1/Systems/0")
@exposed_http("GET", "/api/redfish/v1/Systems/0")
async def __server_handler(self, _: Request) -> Response:
(atx_state, info_state) = await asyncio.gather(*[
self.__atx.get_state(),
@@ -110,7 +110,7 @@ class RedfishApi:
"PowerState": ("On" if atx_state["leds"]["power"] else "Off"), # type: ignore
}, wrap_result=False)
@exposed_http("POST", "/redfish/v1/Systems/0/Actions/ComputerSystem.Reset")
@exposed_http("POST", "/api/redfish/v1/Systems/0/Actions/ComputerSystem.Reset")
async def __power_handler(self, req: Request) -> Response:
try:
action = check_string_in_list(

View File

@@ -47,11 +47,11 @@ class StreamerApi:
# =====
@exposed_http("GET", "/streamer")
@exposed_http("GET", "/api/streamer")
async def __state_handler(self, _: Request) -> Response:
return make_json_response(await self.__streamer.get_state())
@exposed_http("GET", "/streamer/snapshot")
@exposed_http("GET", "/api/streamer/snapshot")
async def __take_snapshot_handler(self, req: Request) -> Response:
snapshot = await self.__streamer.take_snapshot(
save=valid_bool(req.query.get("save", False)),
@@ -92,11 +92,11 @@ class StreamerApi:
)
raise UnavailableError()
@exposed_http("DELETE", "/streamer/snapshot")
@exposed_http("DELETE", "/api/streamer/snapshot")
async def __remove_snapshot_handler(self, _: Request) -> Response:
self.__streamer.remove_snapshot()
return make_json_response()
@exposed_http("GET", "/streamer/ocr")
@exposed_http("GET", "/api/streamer/ocr")
async def __ocr_handler(self, _: Request) -> Response:
return make_json_response({"ocr": (await self.__ocr.get_state())})

View File

@@ -197,7 +197,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
# ===== STREAMER CONTROLLER
@exposed_http("POST", "/streamer/set_params")
@exposed_http("POST", "/api/streamer/set_params")
async def __streamer_set_params_handler(self, req: Request) -> Response:
current_params = self.__streamer.get_params()
for (name, validator, exc_cls) in [
@@ -218,7 +218,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
self.__streamer_notifier.notify()
return make_json_response()
@exposed_http("POST", "/streamer/reset")
@exposed_http("POST", "/api/streamer/reset")
async def __streamer_reset_handler(self, _: Request) -> Response:
self.__reset_streamer = True
self.__streamer_notifier.notify()
@@ -226,7 +226,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
# ===== WEBSOCKET
@exposed_http("GET", "/ws")
@exposed_http("GET", "/api/ws")
async def __ws_handler(self, req: Request) -> WebSocketResponse:
stream = valid_bool(req.query.get("stream", True))
legacy = valid_bool(req.query.get("legacy", True))

View File

@@ -72,13 +72,23 @@ class BaseHttpClient:
def make_session(self) -> BaseHttpClientSession:
raise NotImplementedError
def _make_http_session(self, headers: (dict[str, str] | None)=None) -> aiohttp.ClientSession:
def _make_http_session(self, headers: dict[str, str] | None = None) -> aiohttp.ClientSession:
connector = None
#这里临时使用 socket ,后期考虑是否使用 http 方式
use_unix_socket = True
if use_unix_socket:
connector = aiohttp.UnixConnector(path=self.__unix_path)
base_url = "http://localhost:0" # 继续使用 Unix 域套接字
else:
base_url = "http://127.0.0.1:8001" # 使用指定的 IP 和端口
#print("base_url:", base_url)
return aiohttp.ClientSession(
base_url="http://localhost:0",
base_url=base_url,
headers={
"User-Agent": self.__user_agent,
**(headers or {}),
},
connector=aiohttp.UnixConnector(path=self.__unix_path),
connector=connector,
timeout=aiohttp.ClientTimeout(total=self.__timeout),
)

View File

@@ -291,13 +291,18 @@ class HttpServer:
) -> None:
self.__ws_heartbeat = heartbeat
if unix_rm and os.path.exists(unix_path):
os.remove(unix_path)
server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server_socket.bind(unix_path)
if unix_mode:
os.chmod(unix_path, unix_mode)
# 默认绑定到所有地址
host = '0.0.0.0'
port = 8080
#if unix_rm and os.path.exists(unix_path):
#os.remove(unix_path)
#server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
#server_socket.bind(unix_path)
#if unix_mode:
# os.chmod(unix_path, unix_mode)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 允许重用地址
server_socket.bind((host, port))
run_app(
sock=server_socket,