diff options
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | README.rst | 8 | ||||
-rw-r--r-- | docs/cmds.rst | 2 | ||||
-rw-r--r-- | docs/docker.rst | 4 | ||||
-rw-r--r-- | docs/man/glances.1 | 2 | ||||
-rw-r--r-- | glances/README.txt | 4 | ||||
-rw-r--r-- | glances/main.py | 2 | ||||
-rw-r--r-- | glances/outputs/glances_bottle.py | 668 | ||||
-rw-r--r-- | glances/outputs/static/README.md | 2 | ||||
-rw-r--r-- | glances/plugins/processlist/__init__.py | 2 | ||||
-rw-r--r-- | glances/webserver.py | 10 | ||||
-rw-r--r-- | optional-requirements.txt | 3 | ||||
-rw-r--r-- | requirements.txt | 4 | ||||
-rwxr-xr-x | setup.py | 7 | ||||
-rw-r--r-- | snap/snapcraft.yaml | 22 | ||||
-rw-r--r-- | tox.ini | 3 | ||||
-rw-r--r-- | webui-requirements.txt | 3 |
17 files changed, 49 insertions, 700 deletions
@@ -80,6 +80,9 @@ test-min: ## Run unit tests in minimal environment test-min-with-upgrade: venv-min-upgrade ## Upgrade deps and run unit tests in minimal environment ./venv-min/bin/python ./unitest.py +test-restful-api: ## Run unit tests of the RESTful API + ./venv-min/bin/python ./unitest-restful.py + # =================================================================== # Linters and profilers # =================================================================== @@ -92,11 +92,11 @@ Optional dependencies: - ``batinfo`` (for battery monitoring) - ``bernhard`` (for the Riemann export module) -- ``bottle`` (for Web server mode) - ``cassandra-driver`` (for the Cassandra export module) - ``chevron`` (for the action script feature) - ``docker`` (for the Containers Docker monitoring support) - ``elasticsearch`` (for the Elastic Search export module) +- ``FastAPI`` and ``Uvicorn`` (for Web server mode) - ``graphitesender`` (For the Graphite export module) - ``hddtemp`` (for HDD temperature monitoring support) [Linux-only] - ``influxdb`` (for the InfluxDB version 1 export module) @@ -207,10 +207,10 @@ Get the Glances container: The following tags are availables: - *latest-full* for a full Alpine Glances image (latest release) with all dependencies -- *latest* for a basic Alpine Glances (latest release) version with minimal dependencies (Bottle and Docker) +- *latest* for a basic Alpine Glances (latest release) version with minimal dependencies (FastAPI and Docker) - *dev* for a basic Alpine Glances image (based on development branch) with all dependencies (Warning: may be instable) - *ubuntu-latest-full* for a full Ubuntu Glances image (latest release) with all dependencies -- *ubuntu-latest* for a basic Ubuntu Glances (latest release) version with minimal dependencies (Bottle and Docker) +- *ubuntu-latest* for a basic Ubuntu Glances (latest release) version with minimal dependencies (FastAPI and Docker) - *ubuntu-dev* for a basic Ubuntu Glances image (based on development branch) with all dependencies (Warning: may be instable) Run last version of Glances container in *console mode*: @@ -319,7 +319,7 @@ Start Termux on your device and enter: $ apt update $ apt upgrade $ apt install clang python - $ pip install bottle + $ pip install fastapi uvicorn $ pip install glances And start Glances: diff --git a/docs/cmds.rst b/docs/cmds.rst index cffeb153..91a8ed53 100644 --- a/docs/cmds.rst +++ b/docs/cmds.rst @@ -172,7 +172,7 @@ Command-Line Options .. option:: -w, --webserver - run Glances in web server mode (bottle lib needed) + run Glances in web server mode (FastAPI lib needed) .. option:: --cached-time CACHED_TIME diff --git a/docs/docker.rst b/docs/docker.rst index ad91fd3d..9f4aa63e 100644 --- a/docs/docker.rst +++ b/docs/docker.rst @@ -28,7 +28,7 @@ Available tags (all images are based on both Alpine and Ubuntu Operating System) * - `latest` - Alpine - Latest Release - - Minimal + (Bottle & Docker) + - Minimal + (FastAPI & Docker) * - `dev` - Alpine - develop @@ -40,7 +40,7 @@ Available tags (all images are based on both Alpine and Ubuntu Operating System) * - `ubuntu-latest` - Ubuntu - Latest Release - - Minimal + (Bottle & Docker) + - Minimal + (FastAPI & Docker) * - `ubuntu-dev` - Ubuntu - develop diff --git a/docs/man/glances.1 b/docs/man/glances.1 index 87e52bf9..b2888dd8 100644 --- a/docs/man/glances.1 +++ b/docs/man/glances.1 @@ -254,7 +254,7 @@ set refresh time in seconds [default: 3 sec] .INDENT 0.0 .TP .B \-w, \-\-webserver -run Glances in web server mode (bottle lib needed) +run Glances in web server mode (FastAPI lib needed) .UNINDENT .INDENT 0.0 .TP diff --git a/glances/README.txt b/glances/README.txt index c5fd13eb..3e6de7eb 100644 --- a/glances/README.txt +++ b/glances/README.txt @@ -12,7 +12,7 @@ globals.py Share variables upon modules main.py Main script to rule them up... client.py Glances client server.py Glances server -webserver.py Glances web server (Bottle-based) +webserver.py Glances web server (Based on FastAPI) autodiscover.py Glances autodiscover module (via zeroconf) standalone.py Glances standalone (curses interface) password.py Manage password for Glances client/server @@ -27,7 +27,7 @@ plugins outputs => Glances UI glances_curses.py The curses interface - glances_bottle.py The web interface + glances_restful-api.py The HTTP/API & Web based interface ... exports => Glances exports diff --git a/glances/main.py b/glances/main.py index 03acfd7a..85db14aa 100644 --- a/glances/main.py +++ b/glances/main.py @@ -363,7 +363,7 @@ Examples of use: action='store_true', default=False, dest='webserver', - help='run Glances in web server mode (bottle needed)', + help='run Glances in web server mode (FastAPI and Uvicorn lib needed)', ) parser.add_argument( '--cached-time', diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py deleted file mode 100644 index df6cfc71..00000000 --- a/glances/outputs/glances_bottle.py +++ /dev/null @@ -1,668 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Glances. -# -# SPDX-FileCopyrightText: 2023 Nicolas Hennion <nicolas@nicolargo.com> -# -# SPDX-License-Identifier: LGPL-3.0-only -# - -"""RestFull API interface class.""" - -import os -import sys -import tempfile -from io import open -import webbrowser -import zlib -import socket -from urllib.parse import urljoin - -from glances.globals import b, json_dumps -from glances.timer import Timer -from glances.logger import logger - -try: - from bottle import Bottle, static_file, abort, response, request, auth_basic, template, TEMPLATE_PATH -except ImportError: - logger.critical('Bottle module not found. Glances cannot start in web server mode.') - sys.exit(2) - - -def compress(func): - """Compress result with deflate algorithm if the client ask for it.""" - - def wrapper(*args, **kwargs): - """Wrapper that take one function and return the compressed result.""" - ret = func(*args, **kwargs) - logger.debug( - 'Receive {} {} request with header: {}'.format( - request.method, - request.url, - ['{}: {}'.format(h, request.headers.get(h)) for h in request.headers.keys()], - ) - ) - if 'deflate' in request.headers.get('Accept-Encoding', ''): - response.headers['Content-Encoding'] = 'deflate' - ret = deflate_compress(ret) - else: - response.headers['Content-Encoding'] = 'identity' - return ret - - def deflate_compress(data, compress_level=6): - """Compress given data using the DEFLATE algorithm""" - # Init compression - zobj = zlib.compressobj( - compress_level, zlib.DEFLATED, zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, zlib.Z_DEFAULT_STRATEGY - ) - - # Return compressed object - return zobj.compress(b(data)) + zobj.flush() - - return wrapper - - -class GlancesBottle(object): - """This class manages the Bottle Web server.""" - - API_VERSION = '3' - - def __init__(self, config=None, args=None): - # Init config - self.config = config - - # Init args - self.args = args - - # Init stats - # Will be updated within Bottle route - self.stats = None - - # cached_time is the minimum time interval between stats updates - # i.e. HTTP/RESTful calls will not retrieve updated info until the time - # since last update is passed (will retrieve old cached info instead) - self.timer = Timer(0) - - # Load configuration file - self.load_config(config) - - # Set the bind URL (only used for log information purpose) - self.bind_url = urljoin('http://{}:{}/'.format(self.args.bind_address, self.args.port), self.url_prefix) - - # Init Bottle - self._app = Bottle() - # Enable CORS (issue #479) - self._app.install(EnableCors()) - # Password - if args.password != '': - self._app.install(auth_basic(self.check_auth)) - # Define routes - self._route() - - # Path where the statics files are stored - self.STATIC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/public') - - # Paths for templates - TEMPLATE_PATH.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/templates')) - - def load_config(self, config): - """Load the outputs section of the configuration file.""" - # Limit the number of processes to display in the WebUI - self.url_prefix = '/' - if config is not None and config.has_section('outputs'): - n = config.get_value('outputs', 'max_processes_display', default=None) - logger.debug('Number of processes to display in the WebUI: {}'.format(n)) - self.url_prefix = config.get_value('outputs', 'url_prefix', default='/') - logger.debug('URL prefix: {}'.format(self.url_prefix)) - - def __update__(self): - # Never update more than 1 time per cached_time - if self.timer.finished(): - self.stats.update() - self.timer = Timer(self.args.cached_time) - - def app(self): - return self._app() - - def check_auth(self, username, password): - """Check if a username/password combination is valid.""" - if username == self.args.username: - from glances.password import GlancesPassword - - pwd = GlancesPassword(username=username, config=self.config) - return pwd.check_password(self.args.password, pwd.get_hash(password)) - else: - return False - - def _route(self): - """Define route.""" - # REST API - self._app.route('/api/%s/status' % self.API_VERSION, method="GET", callback=self._api_status) - self._app.route('/api/%s/config' % self.API_VERSION, method="GET", callback=self._api_config) - self._app.route('/api/%s/config/<item>' % self.API_VERSION, method="GET", callback=self._api_config_item) - self._app.route('/api/%s/args' % self.API_VERSION, method="GET", callback=self._api_args) - self._app.route('/api/%s/args/<item>' % self.API_VERSION, method="GET", callback=self._api_args_item) - self._app.route('/api/%s/help' % self.API_VERSION, method="GET", callback=self._api_help) - self._app.route('/api/%s/pluginslist' % self.API_VERSION, method="GET", callback=self._api_plugins) - self._app.route('/api/%s/all' % self.API_VERSION, method="GET", callback=self._api_all) - self._app.route('/api/%s/all/limits' % self.API_VERSION, method="GET", callback=self._api_all_limits) - self._app.route('/api/%s/all/views' % self.API_VERSION, method="GET", callback=self._api_all_views) - self._app.route('/api/%s/<plugin>' % self.API_VERSION, method="GET", callback=self._api) - self._app.route('/api/%s/<plugin>/history' % self.API_VERSION, method="GET", callback=self._api_history) - self._app.route( - '/api/%s/<plugin>/history/<nb:int>' % self.API_VERSION, method="GET", callback=self._api_history - ) - self._app.route('/api/%s/<plugin>/top/<nb:int>' % self.API_VERSION, method="GET", callback=self._api_top) - self._app.route('/api/%s/<plugin>/limits' % self.API_VERSION, method="GET", callback=self._api_limits) - self._app.route('/api/%s/<plugin>/views' % self.API_VERSION, method="GET", callback=self._api_views) - self._app.route('/api/%s/<plugin>/<item>' % self.API_VERSION, method="GET", callback=self._api_item) - self._app.route( - '/api/%s/<plugin>/<item>/history' % self.API_VERSION, method="GET", callback=self._api_item_history - ) - self._app.route( - '/api/%s/<plugin>/<item>/history/<nb:int>' % self.API_VERSION, method="GET", callback=self._api_item_history - ) - self._app.route('/api/%s/<plugin>/<item>/<value>' % self.API_VERSION, method="GET", callback=self._api_value) - self._app.route( - '/api/%s/<plugin>/<item>/<value:path>' % self.API_VERSION, method="GET", callback=self._api_value - ) - bindmsg = 'Glances RESTful API Server started on {}api/{}'.format(self.bind_url, self.API_VERSION) - logger.info(bindmsg) - - # WEB UI - if not self.args.disable_webui: - self._app.route('/', method="GET", callback=self._index) - self._app.route('/<refresh_time:int>', method=["GET"], callback=self._index) - self._app.route('/<filepath:path>', method="GET", callback=self._resource) - bindmsg = 'Glances Web User Interface started on {}'.format(self.bind_url) - else: - bindmsg = 'The WebUI is disable (--disable-webui)' - - logger.info(bindmsg) - print(bindmsg) - - def start(self, stats): - """Start the bottle.""" - # Init stats - self.stats = stats - - # Init plugin list - self.plugins_list = self.stats.getPluginsList() - - # Bind the Bottle TCP address/port - if self.args.open_web_browser: - # Implementation of the issue #946 - # Try to open the Glances Web UI in the default Web browser if: - # 1) --open-web-browser option is used - # 2) Glances standalone mode is running on Windows OS - webbrowser.open(self.bind_url, new=2, autoraise=1) - - # Run the Web application - if self.url_prefix != '/': - # Create an outer Bottle class instance to manage url_prefix - self.main_app = Bottle() - self.main_app.mount(self.url_prefix, self._app) - try: - self.main_app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug) - except socket.error as e: - logger.critical('Error: Can not ran Glances Web server ({})'.format(e)) - else: - try: - self._app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug) - except socket.error as e: - logger.critical('Error: Can not ran Glances Web server ({})'.format(e)) - - def end(self): - """End the bottle.""" - logger.info("Close the Web server") - self._app.close() - if self.url_prefix != '/': - self.main_app.close() - - def _index(self, refresh_time=None): - """Bottle callback for index.html (/) file.""" - - if refresh_time is None or refresh_time < 1: - refresh_time = int(self.args.time) - - # Update the stat - self.__update__() - - # Display - return template("index.html", refresh_time=refresh_time) - - def _resource(self, filepath): - """Bottle callback for resources files.""" - # Return the static file - return static_file(filepath, root=self.STATIC_PATH) - - @compress - def _api_status(self): - """Glances API RESTful implementation. - - Return a 200 status code. - This entry point should be used to check the API health. - - See related issue: Web server health check endpoint #1988 - """ - response.status = 200 - - return "Active" - - @compress - def _api_help(self): - """Glances API RESTful implementation. - - Return the help data or 404 error. - """ - response.content_type = 'application/json; charset=utf-8' - - # Update the stat - view_data = self.stats.get_plugin("help").get_view_data() - try: - plist = json_dumps(view_data) - except Exception as e: - abort(404, "Cannot get help view data (%s)" % str(e)) - return plist - - @compress - def _api_plugins(self): - """Glances API RESTFul implementation. - - @api {get} /api/%s/pluginslist Get plugins list - @apiVersion 2.0 - @apiName pluginslist - @apiGroup plugin - - @apiSuccess {String[]} Plugins list. - - @apiSuccessExample Success-Response: - HTTP/1.1 200 OK - [ - "load", - "help", - "ip", - "memswap", - "processlist", - ... - ] - - @apiError Cannot get plugin list. - - @apiErrorExample Error-Response: - HTTP/1.1 404 Not Found - """ - response.content_type = 'application/json; charset=utf-8' - - # Update the stat - self.__update__() - - try: - plist = json_dumps(self.plugins_list) - except Exception as e: - abort(404, "Cannot get plugin list (%s)" % str(e)) - return plist - - @compress - def _api_all(self): - """Glances API RESTful implementation. - - Return the JSON representation of all the plugins - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if self.args.debug: - fname = os.path.join(tempfile.gettempdir(), 'glances-debug.json') - try: - with open(fname) as f: - return f.read() - except IOError: - logger.debug("Debug file (%s) not found" % fname) - - # Update the stat - self.__update__() - - try: - # Get the JSON value of the stat ID - statval = json_dumps(self.stats.getAllAsDict()) - except Exception as e: - abort(404, "Cannot get stats (%s)" % str(e)) - - return statval - - @compress - def _api_all_limits(self): - """Glances API RESTful implementation. - - Return the JSON representation of all the plugins limits - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - try: - # Get the JSON value of the stat limits - limits = json_dumps(self.stats.getAllLimitsAsDict()) - except Exception as e: - abort(404, "Cannot get limits (%s)" % (str(e))) - return limits - - @compress - def _api_all_views(self): - """Glances API RESTful implementation. - - Return the JSON representation of all the plugins views - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - try: - # Get the JSON value of the stat view - limits = json_dumps(self.stats.getAllViewsAsDict()) - except Exception as e: - abort(404, "Cannot get views (%s)" % (str(e))) - return limits - - @compress - def _api(self, plugin): - """Glances API RESTful implementation. - - Return the JSON representation of a given plugin - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - self.__update__() - - try: - # Get the JSON value of the stat ID - statval = self.stats.get_plugin(plugin).get_stats() - except Exception as e: - abort(404, "Cannot get plugin %s (%s)" % (plugin, str(e))) - - return statval - - @compress - def _api_top(self, plugin, nb=0): - """Glances API RESTful implementation. - - Return the JSON representation of a given plugin limited to the top nb items. - It is used to reduce the payload of the HTTP response (example: processlist). - - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - self.__update__() - - try: - # Get the value of the stat ID - statval = self.stats.get_plugin(plugin).get_export() - except Exception as e: - abort(404, "Cannot get plugin %s (%s)" % (plugin, str(e))) - - if isinstance(statval, list): - return json_dumps(statval[:nb]) - else: - return json_dumps(statval) - - @compress - def _api_history(self, plugin, nb=0): - """Glances API RESTful implementation. - - Return the JSON representation of a given plugin history - Limit to the last nb items (all if nb=0) - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - self.__update__() - - try: - # Get the JSON value of the stat ID - statval = self.stats.get_plugin(plugin).get_stats_history(nb=int(nb)) - except Exception as e: - abort(404, "Cannot get plugin history %s (%s)" % (plugin, str(e))) - return statval - - @compress - def _api_limits(self, plugin): - """Glances API RESTful implementation. - - Return the JSON limits of a given plugin - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - # self.__update__() - - try: - # Get the JSON value of the stat limits - ret = self.stats.get_plugin(plugin).limits - except Exception as e: - abort(404, "Cannot get limits for plugin %s (%s)" % (plugin, str(e))) - return ret - - @compress - def _api_views(self, plugin): - """Glances API RESTful implementation. - - Return the JSON views of a given plugin - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - # self.__update__() - - try: - # Get the JSON value of the stat views - ret = self.stats.get_plugin(plugin).get_views() - except Exception as e: - abort(404, "Cannot get views for plugin %s (%s)" % (plugin, str(e))) - return ret - - # No compression see issue #1228 - # @compress - def _api_itemvalue(self, plugin, item, value=None, history=False, nb=0): - """Father method for _api_item and _api_value.""" - response.content_type = 'application/json; charset=utf-8' - - if plugin not in self.plugins_list: - abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list)) - - # Update the stat - self.__update__() - - if value is None: - if history: - ret = self.stats.get_plugin(plugin).get_stats_history(item, nb=int(nb)) - else: - ret = self.stats.get_plugin(plugin).get_stats_item(item) - - if ret is None: - abort(404, "Cannot get item %s%s in plugin %s" % (item, 'history ' if history else '', plugin)) - else: - if history: - # Not available - ret = None - else: - ret = self.stats.get_plugin(plugin).get_stats_value(item, value) - - if ret is None: - abort( - 404, "Cannot get item %s(%s=%s) in plugin %s" % ('history ' if history else '', item, value, plugin) - ) - - return ret - - @compress - def _api_item(self, plugin, item): - """Glances API RESTful implementation. - - Return the JSON representation of the couple plugin/item - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - - """ - return self._api_itemvalue(plugin, item) - - @compress - def _api_item_history(self, plugin, item, nb=0): - """Glances API RESTful implementation. - - Return the JSON representation of the couple plugin/history of item - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - - """ - return self._api_itemvalue(plugin, item, history=True, nb=int(nb)) - - @compress - def _api_value(self, plugin, item, value): - """Glances API RESTful implementation. - - Return the process stats (dict) for the given item=value - HTTP/200 if OK - HTTP/400 if plugin is not found - HTTP/404 if others error - """ - return self._api_itemvalue(plugin, item, value) - - @compress - def _api_config(self): - """Glances API RESTful implementation. - - Return the JSON representation of the Glances configuration file - HTTP/200 if OK - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - try: - # Get the JSON value of the config' dict - args_json = json_dumps(self.config.as_dict()) - except Exception as e: - abort(404, "Cannot get config (%s)" % str(e)) - return args_json - - @compress - def _api_config_item(self, item): - """Glances API RESTful implementation. - - Return the JSON representation of the Glances configuration item - HTTP/200 if OK - HTTP/400 if item is not found - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - config_dict = self.config.as_dict() - if item not in config_dict: - abort(400, "Unknown configuration item %s" % item) - - try: - # Get the JSON value of the config' dict - args_json = json_dumps(config_dict[item]) - except Exception as e: - abort(404, "Cannot get config item (%s)" % str(e)) - return args_json - - @compress - def _api_args(self): - """Glances API RESTful implementation. - - Return the JSON representation of the Glances command line arguments - HTTP/200 if OK - HTTP/404 if others error - """ - response.content_type = 'application/json; charset=utf-8' - - try: - # Get the JSON value of the args' dict - # Use vars to convert namespace to dict - # Source: https://docs.python.org/%s/library/functions.html#vars - args_json = json_dumps(vars(self.args)) - except Exception as e: - abort(404, "Cannot get args (%s)" % str(e)) - return args_json - - @compress - def _api_args_item(self, item): - """Glances API RESTful implementation. - - Return the JSON representation of the Glances command line arguments item - HTTP/200 if OK - HTTP/400 if item is not found - HTTP/404 if others error - """ - response.content_type = 'app |