spoolman: implement alternate proxy responses
The exisiting implementation of spoolman's proxy endpoint returns responses and errors exactly as they are received by spoolman. This creates a problem of ambiguity, as the frontend cannot easily diffentiate between an error returned by Moonraker and an error returned by Spoolman. This implements a "v2" alternate response to proxy requests. All requests to spoolman will return success, with responses wrapped in a top level object. Successful requests will be returned in a "spoolman_response" object, errors in a "spoolman_error" object. Initially v2 responses will be opt-in to prevent breaking existing spoolman implementations. However, as of this commit the v1 response is deprecated and will be removed in a future version of Moonraker. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
e9ad278286
commit
3102391234
|
@ -8,6 +8,7 @@ from __future__ import annotations
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
import contextlib
|
||||||
import tornado.websocket as tornado_ws
|
import tornado.websocket as tornado_ws
|
||||||
from ..common import RequestType, Sentinel
|
from ..common import RequestType, Sentinel
|
||||||
from ..utils import json_wrapper as jsonw
|
from ..utils import json_wrapper as jsonw
|
||||||
|
@ -16,7 +17,8 @@ from typing import (
|
||||||
Dict,
|
Dict,
|
||||||
Any,
|
Any,
|
||||||
Optional,
|
Optional,
|
||||||
Union
|
Union,
|
||||||
|
cast
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -210,14 +212,9 @@ class SpoolManager:
|
||||||
|
|
||||||
def _get_response_error(self, response: HttpResponse) -> str:
|
def _get_response_error(self, response: HttpResponse) -> str:
|
||||||
err_msg = f"HTTP error: {response.status_code} {response.error}"
|
err_msg = f"HTTP error: {response.status_code} {response.error}"
|
||||||
try:
|
with contextlib.suppress(Exception):
|
||||||
resp = response.json()
|
msg: Optional[str] = cast(dict, response.json())["message"]
|
||||||
assert isinstance(resp, dict)
|
err_msg += f", Spoolman message: {msg}"
|
||||||
json_msg: str = resp["message"]
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
err_msg += f", Spoolman message: {json_msg}"
|
|
||||||
return err_msg
|
return err_msg
|
||||||
|
|
||||||
def _eposition_from_status(self, status: Dict[str, Any]) -> Optional[float]:
|
def _eposition_from_status(self, status: Dict[str, Any]) -> Optional[float]:
|
||||||
|
@ -315,6 +312,7 @@ class SpoolManager:
|
||||||
path = web_request.get_str("path")
|
path = web_request.get_str("path")
|
||||||
query = web_request.get_str("query", None)
|
query = web_request.get_str("query", None)
|
||||||
body = web_request.get("body", None)
|
body = web_request.get("body", None)
|
||||||
|
use_v2_response = web_request.get_boolean("use_v2_response", False)
|
||||||
if method not in {"GET", "POST", "PUT", "PATCH", "DELETE"}:
|
if method not in {"GET", "POST", "PUT", "PATCH", "DELETE"}:
|
||||||
raise self.server.error(f"Invalid HTTP method: {method}")
|
raise self.server.error(f"Invalid HTTP method: {method}")
|
||||||
if body is not None and method == "GET":
|
if body is not None and method == "GET":
|
||||||
|
@ -326,15 +324,41 @@ class SpoolManager:
|
||||||
query = f"?{query}" if query is not None else ""
|
query = f"?{query}" if query is not None else ""
|
||||||
full_url = f"{self.spoolman_url}{path}{query}"
|
full_url = f"{self.spoolman_url}{path}{query}"
|
||||||
if not self.ws_connected:
|
if not self.ws_connected:
|
||||||
|
if not use_v2_response:
|
||||||
raise self.server.error("Spoolman server not available", 503)
|
raise self.server.error("Spoolman server not available", 503)
|
||||||
|
return {
|
||||||
|
"response": None,
|
||||||
|
"error": {
|
||||||
|
"status_code": 503,
|
||||||
|
"message": "Spoolman server not available"
|
||||||
|
}
|
||||||
|
}
|
||||||
logging.debug(f"Proxying {method} request to {full_url}")
|
logging.debug(f"Proxying {method} request to {full_url}")
|
||||||
response = await self.http_client.request(
|
response = await self.http_client.request(
|
||||||
method=method,
|
method=method,
|
||||||
url=full_url,
|
url=full_url,
|
||||||
body=body,
|
body=body,
|
||||||
)
|
)
|
||||||
|
if not use_v2_response:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
|
if response.has_error():
|
||||||
|
msg: str = str(response.error or "")
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
spoolman_msg = cast(dict, response.json()).get("message", msg)
|
||||||
|
msg = spoolman_msg
|
||||||
|
return {
|
||||||
|
"response": None,
|
||||||
|
"error": {
|
||||||
|
"status_code": response.status_code,
|
||||||
|
"message": msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"response": response.json(),
|
||||||
|
"error": None
|
||||||
|
}
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
self.is_closing = True
|
self.is_closing = True
|
||||||
|
|
Loading…
Reference in New Issue