chg: restful_api - use internal serialization features

This commit is contained in:
RazCrimson 2024-09-08 14:28:48 +05:30 committed by RazCrimson
parent 538013d192
commit 2a5251ca2d

View File

@ -15,13 +15,14 @@ import webbrowser
from urllib.parse import urljoin from urllib.parse import urljoin
try: try:
from typing import Annotated from typing import Annotated, Any
except ImportError: except ImportError:
# Only for Python 3.8 # Only for Python 3.8
# To be removed when Python 3.8 support will be dropped # To be removed when Python 3.8 support will be dropped
from typing_extensions import Annotated from typing_extensions import Annotated
from glances import __apiversion__, __version__ from glances import __apiversion__, __version__
from glances.globals import json_dumps
from glances.logger import logger from glances.logger import logger
from glances.password import GlancesPassword from glances.password import GlancesPassword
from glances.timer import Timer from glances.timer import Timer
@ -31,7 +32,7 @@ try:
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, status from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, status
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware from fastapi.middleware.gzip import GZipMiddleware
from fastapi.responses import HTMLResponse, ORJSONResponse from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
@ -50,6 +51,18 @@ import contextlib
import threading import threading
import time import time
class GlancesJSONResponse(JSONResponse):
"""
Glances impl of fastapi's JSONResponse to use internal JSON Serialization features
Ref: https://fastapi.tiangolo.com/advanced/custom-response/
"""
def render(self, content: Any) -> bytes:
return json_dumps(content)
security = HTTPBasic() security = HTTPBasic()
@ -106,11 +119,11 @@ class GlancesRestfulApi:
# FastAPI Init # FastAPI Init
if self.args.password: if self.args.password:
self._app = FastAPI(dependencies=[Depends(self.authentication)]) self._app = FastAPI(default_response_class=GlancesJSONResponse, dependencies=[Depends(self.authentication)])
self._password = GlancesPassword(username=args.username, config=config) self._password = GlancesPassword(username=args.username, config=config)
else: else:
self._app = FastAPI() self._app = FastAPI(default_response_class=GlancesJSONResponse)
self._password = None self._password = None
# Set path for WebUI # Set path for WebUI
@ -178,102 +191,49 @@ class GlancesRestfulApi:
headers={"WWW-Authenticate": "Basic"}, headers={"WWW-Authenticate": "Basic"},
) )
def _router(self): def _router(self) -> APIRouter:
"""Define a custom router for Glances path.""" """Define a custom router for Glances path."""
# Create une main router base_path = f'/api/{self.API_VERSION}'
plugin_path = f"{base_path}/{{plugin}}"
# Create the main router
router = APIRouter(prefix=self.url_prefix) router = APIRouter(prefix=self.url_prefix)
# REST API # REST API
router.add_api_route( router.add_api_route(f'{base_path}/status', self._api_status, methods=['HEAD', 'GET'])
f'/api/{self.API_VERSION}/status',
status_code=status.HTTP_200_OK,
methods=['HEAD', 'GET'],
response_class=ORJSONResponse,
endpoint=self._api_status,
)
router.add_api_route( route_mapping = {
f'/api/{self.API_VERSION}/config', response_class=ORJSONResponse, endpoint=self._api_config f'{base_path}/config': self._api_config,
) f'{base_path}/config/{{section}}': self._api_config_section,
router.add_api_route( f'{base_path}/config/{{section}}/{{item}}': self._api_config_section_item,
f'/api/{self.API_VERSION}/config/{{section}}', f'{base_path}/args': self._api_args,
response_class=ORJSONResponse, f'{base_path}/args/{{item}}': self._api_args_item,
endpoint=self._api_config_section, f'{base_path}/help': self._api_help,
) f'{base_path}/all': self._api_all,
router.add_api_route( f'{base_path}/all/limits': self._api_all_limits,
f'/api/{self.API_VERSION}/config/{{section}}/{{item}}', f'{base_path}/all/views': self._api_all_views,
response_class=ORJSONResponse, f'{base_path}/pluginslist': self._api_plugins,
endpoint=self._api_config_section_item, f'{plugin_path}': self._api,
) f'{plugin_path}/history': self._api_history,
f'{plugin_path}/history/{{nb}}': self._api_history,
f'{plugin_path}/top/{{nb}}': self._api_top,
f'{plugin_path}/limits': self._api_limits,
f'{plugin_path}/views': self._api_views,
f'{plugin_path}/{{item}}': self._api_item,
f'{plugin_path}/{{item}}/history': self._api_item_history,
f'{plugin_path}/{{item}}/history/{{nb}}': self._api_item_history,
f'{plugin_path}/{{item}}/description': self._api_item_description,
f'{plugin_path}/{{item}}/unit': self._api_item_unit,
f'{plugin_path}/{{item}}/{{value:path}}': self._api_value,
}
router.add_api_route(f'/api/{self.API_VERSION}/args', response_class=ORJSONResponse, endpoint=self._api_args) for path, endpoint in route_mapping.items():
router.add_api_route( router.add_api_route(path, endpoint)
f'/api/{self.API_VERSION}/args/{{item}}', response_class=ORJSONResponse, endpoint=self._api_args_item
)
router.add_api_route(
f'/api/{self.API_VERSION}/pluginslist', response_class=ORJSONResponse, endpoint=self._api_plugins
)
router.add_api_route(f'/api/{self.API_VERSION}/all', response_class=ORJSONResponse, endpoint=self._api_all)
router.add_api_route(
f'/api/{self.API_VERSION}/all/limits', response_class=ORJSONResponse, endpoint=self._api_all_limits
)
router.add_api_route(
f'/api/{self.API_VERSION}/all/views', response_class=ORJSONResponse, endpoint=self._api_all_views
)
router.add_api_route(f'/api/{self.API_VERSION}/help', response_class=ORJSONResponse, endpoint=self._api_help)
router.add_api_route(f'/api/{self.API_VERSION}/{{plugin}}', response_class=ORJSONResponse, endpoint=self._api)
router.add_api_route(
f'/api/{self.API_VERSION}/{{plugin}}/history', response_class=ORJSONResponse, endpoint=self._api_history
)
router.add_api_route(
f'/api/{self.API_VERSION}/{{plugin}}/history/{{nb}}',
response_class=ORJSONResponse,
endpoint=self._api_history,
)
router.add_api_route(
f'/api/{self.API_VERSION}/{{plugin}}/top/{{nb}}', response_class=ORJSONResponse, endpoint=self._api_top
)
router.add_api_route(
f'/api/{self.API_VERSION}/{{plugin}}/limits', response_class=ORJSONResponse, endpoint=self._api_limits
)
router.add_api_route(
f'/api/{self.API_VERSION}/{{plugin}}/views', response_class=ORJSONResponse, endpoint=self._api_views
)
router.add_api_route(
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}', response_class=ORJSONResponse, endpoint=self._api_item
)
router.add_api_route(
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/history',
response_class=ORJSONResponse,
endpoint=self._api_item_history,
)
router.add_api_route(
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/history/{{nb}}',
response_class=ORJSONResponse,
endpoint=self._api_item_history,
)
router.add_api_route(
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/description',
response_class=ORJSONResponse,
endpoint=self._api_item_description,
)
router.add_api_route(
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/unit',
response_class=ORJSONResponse,
endpoint=self._api_item_unit,
)
router.add_api_route(
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/{{value:path}}',
response_class=ORJSONResponse,
endpoint=self._api_value,
)
# WEB UI # WEB UI
if not self.args.disable_webui: if not self.args.disable_webui:
# Template for the root index.html file # Template for the root index.html file
router.add_api_route('/', response_class=HTMLResponse, endpoint=self._index) router.add_api_route('/', self._index, response_class=HTMLResponse)
# Statics files # Statics files
self._app.mount(self.url_prefix + '/static', StaticFiles(directory=self.STATIC_PATH), name="static") self._app.mount(self.url_prefix + '/static', StaticFiles(directory=self.STATIC_PATH), name="static")
@ -361,7 +321,7 @@ class GlancesRestfulApi:
See related issue: Web server health check endpoint #1988 See related issue: Web server health check endpoint #1988
""" """
return ORJSONResponse({'version': __version__}) return GlancesJSONResponse({'version': __version__})
def _api_help(self): def _api_help(self):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -373,7 +333,7 @@ class GlancesRestfulApi:
except Exception as e: except Exception as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get help view data ({str(e)})") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get help view data ({str(e)})")
return ORJSONResponse(plist) return GlancesJSONResponse(plist)
def _api_plugins(self): def _api_plugins(self):
"""Glances API RESTFul implementation. """Glances API RESTFul implementation.
@ -409,7 +369,7 @@ class GlancesRestfulApi:
except Exception as e: except Exception as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get plugin list ({str(e)})") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get plugin list ({str(e)})")
return ORJSONResponse(plist) return GlancesJSONResponse(plist)
def _api_all(self): def _api_all(self):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -436,7 +396,7 @@ class GlancesRestfulApi:
except Exception as e: except Exception as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get stats ({str(e)})") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get stats ({str(e)})")
return ORJSONResponse(statval) return GlancesJSONResponse(statval)
def _api_all_limits(self): def _api_all_limits(self):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -452,7 +412,7 @@ class GlancesRestfulApi:
except Exception as e: except Exception as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get limits ({str(e)})") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get limits ({str(e)})")
return ORJSONResponse(limits) return GlancesJSONResponse(limits)
def _api_all_views(self): def _api_all_views(self):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -468,7 +428,7 @@ class GlancesRestfulApi:
except Exception as e: except Exception as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get views ({str(e)})") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get views ({str(e)})")
return ORJSONResponse(limits) return GlancesJSONResponse(limits)
def _api(self, plugin): def _api(self, plugin):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -493,7 +453,7 @@ class GlancesRestfulApi:
except Exception as e: except Exception as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get plugin {plugin} ({str(e)})") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get plugin {plugin} ({str(e)})")
return ORJSONResponse(statval) return GlancesJSONResponse(statval)
def _api_top(self, plugin, nb: int = 0): def _api_top(self, plugin, nb: int = 0):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -525,7 +485,7 @@ class GlancesRestfulApi:
if isinstance(statval, list): if isinstance(statval, list):
statval = statval[:nb] statval = statval[:nb]
return ORJSONResponse(statval) return GlancesJSONResponse(statval)
def _api_history(self, plugin, nb: int = 0): def _api_history(self, plugin, nb: int = 0):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -577,7 +537,7 @@ class GlancesRestfulApi:
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get limits for plugin {plugin} ({str(e)})" status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get limits for plugin {plugin} ({str(e)})"
) )
return ORJSONResponse(ret) return GlancesJSONResponse(ret)
def _api_views(self, plugin): def _api_views(self, plugin):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -601,7 +561,7 @@ class GlancesRestfulApi:
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get views for plugin {plugin} ({str(e)})" status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get views for plugin {plugin} ({str(e)})"
) )
return ORJSONResponse(ret) return GlancesJSONResponse(ret)
def _api_item(self, plugin, item): def _api_item(self, plugin, item):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -629,7 +589,7 @@ class GlancesRestfulApi:
detail=f"Cannot get item {item} in plugin {plugin} ({str(e)})", detail=f"Cannot get item {item} in plugin {plugin} ({str(e)})",
) )
return ORJSONResponse(ret) return GlancesJSONResponse(ret)
def _api_item_history(self, plugin, item, nb: int = 0): def _api_item_history(self, plugin, item, nb: int = 0):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -657,7 +617,7 @@ class GlancesRestfulApi:
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get history for plugin {plugin} ({str(e)})" status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get history for plugin {plugin} ({str(e)})"
) )
else: else:
return ORJSONResponse(ret) return GlancesJSONResponse(ret)
def _api_item_description(self, plugin, item): def _api_item_description(self, plugin, item):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -682,7 +642,7 @@ class GlancesRestfulApi:
detail=f"Cannot get {item} description for plugin {plugin} ({str(e)})", detail=f"Cannot get {item} description for plugin {plugin} ({str(e)})",
) )
else: else:
return ORJSONResponse(ret) return GlancesJSONResponse(ret)
def _api_item_unit(self, plugin, item): def _api_item_unit(self, plugin, item):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -707,7 +667,7 @@ class GlancesRestfulApi:
detail=f"Cannot get {item} unit for plugin {plugin} ({str(e)})", detail=f"Cannot get {item} unit for plugin {plugin} ({str(e)})",
) )
else: else:
return ORJSONResponse(ret) return GlancesJSONResponse(ret)
def _api_value(self, plugin, item, value): def _api_value(self, plugin, item, value):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -735,7 +695,7 @@ class GlancesRestfulApi:
detail=f"Cannot get {item} = {value} for plugin {plugin} ({str(e)})", detail=f"Cannot get {item} = {value} for plugin {plugin} ({str(e)})",
) )
else: else:
return ORJSONResponse(ret) return GlancesJSONResponse(ret)
def _api_config(self): def _api_config(self):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -750,7 +710,7 @@ class GlancesRestfulApi:
except Exception as e: except Exception as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get config ({str(e)})") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get config ({str(e)})")
else: else:
return ORJSONResponse(args_json) return GlancesJSONResponse(args_json)
def _api_config_section(self, section): def _api_config_section(self, section):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -772,7 +732,7 @@ class GlancesRestfulApi:
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get config section {section} ({str(e)})" status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get config section {section} ({str(e)})"
) )
return ORJSONResponse(ret_section) return GlancesJSONResponse(ret_section)
def _api_config_section_item(self, section, item): def _api_config_section_item(self, section, item):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -803,7 +763,7 @@ class GlancesRestfulApi:
detail=f"Cannot get item {item} in config section {section} ({str(e)})", detail=f"Cannot get item {item} in config section {section} ({str(e)})",
) )
return ORJSONResponse(ret_item) return GlancesJSONResponse(ret_item)
def _api_args(self): def _api_args(self):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -820,7 +780,7 @@ class GlancesRestfulApi:
except Exception as e: except Exception as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get args ({str(e)})") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get args ({str(e)})")
return ORJSONResponse(args_json) return GlancesJSONResponse(args_json)
def _api_args_item(self, item): def _api_args_item(self, item):
"""Glances API RESTful implementation. """Glances API RESTful implementation.
@ -841,4 +801,4 @@ class GlancesRestfulApi:
except Exception as e: except Exception as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get args item ({str(e)})") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get args item ({str(e)})")
return ORJSONResponse(args_json) return GlancesJSONResponse(args_json)