import json import logging import os import sys import certifi from bottle import run, response, Bottle, request, ServerAdapter from bottle_plugins.error_plugin import error_plugin from bottle_plugins.logger_plugin import logger_plugin from bottle_plugins import prometheus_plugin from dtos import V1RequestBase import flaresolverr_service import utils class JSONErrorBottle(Bottle): """ Handle 404 errors """ def default_error_handler(self, res): response.content_type = 'application/json' return json.dumps(dict(error=res.body, status_code=res.status_code)) app = JSONErrorBottle() @app.route('/') def index(): """ Show welcome message """ res = flaresolverr_service.index_endpoint() return utils.object_to_dict(res) @app.route('/health') def health(): """ Healthcheck endpoint. This endpoint is special because it doesn't print traces """ res = flaresolverr_service.health_endpoint() return utils.object_to_dict(res) @app.post('/v1') def controller_v1(): """ Controller v1 """ req = V1RequestBase(request.json) res = flaresolverr_service.controller_v1_endpoint(req) if res.__error_500__: response.status = 500 return utils.object_to_dict(res) if __name__ == "__main__": # check python version if sys.version_info < (3, 9): raise Exception("The Python version is less than 3.9, a version equal to or higher is required.") # fix for HEADLESS=false in Windows binary # https://stackoverflow.com/a/27694505 if os.name == 'nt': import multiprocessing multiprocessing.freeze_support() # fix ssl certificates for compiled binaries # https://github.com/pyinstaller/pyinstaller/issues/7229 # https://stackoverflow.com/questions/55736855/how-to-change-the-cafile-argument-in-the-ssl-module-in-python3 os.environ["REQUESTS_CA_BUNDLE"] = certifi.where() os.environ["SSL_CERT_FILE"] = certifi.where() # validate configuration log_level = os.environ.get('LOG_LEVEL', 'info').upper() log_html = utils.get_config_log_html() headless = utils.get_config_headless() server_host = os.environ.get('HOST', '0.0.0.0') server_port = int(os.environ.get('PORT', 8191)) # configure logger logger_format = '%(asctime)s %(levelname)-8s %(message)s' if log_level == 'DEBUG': logger_format = '%(asctime)s %(levelname)-8s ReqId %(thread)s %(message)s' logging.basicConfig( format=logger_format, level=log_level, datefmt='%Y-%m-%d %H:%M:%S', handlers=[ logging.StreamHandler(sys.stdout) ] ) # disable warning traces from urllib3 logging.getLogger('urllib3').setLevel(logging.ERROR) logging.getLogger('selenium.webdriver.remote.remote_connection').setLevel(logging.WARNING) logging.getLogger('undetected_chromedriver').setLevel(logging.WARNING) logging.info(f'FlareSolverr {utils.get_flaresolverr_version()}') logging.debug('Debug log enabled') # Get current OS for global variable utils.get_current_platform() # test browser installation flaresolverr_service.test_browser_installation() # start bootle plugins # plugin order is important app.install(logger_plugin) app.install(error_plugin) prometheus_plugin.setup() app.install(prometheus_plugin.prometheus_plugin) # start webserver # default server 'wsgiref' does not support concurrent requests # https://github.com/FlareSolverr/FlareSolverr/issues/680 # https://github.com/Pylons/waitress/issues/31 class WaitressServerPoll(ServerAdapter): def run(self, handler): from waitress import serve serve(handler, host=self.host, port=self.port, asyncore_use_poll=True) run(app, host=server_host, port=server_port, quiet=True, server=WaitressServerPoll)