mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-31 01:51:53 +08:00
初步整合:
1. python 内置服务器 2. 将配置文件统一目录
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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())})
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user