Last active
November 8, 2023 16:04
-
-
Save jramseygreen/d1e2aa0e77daeca739371951d6009c57 to your computer and use it in GitHub Desktop.
flask + vue spa + cheroot webserver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from flask import Blueprint, jsonify | |
# required line at the top of every blueprint file | |
api = Blueprint("api", __name__) # match variable name and first arg to file name | |
# register more blueprints here to further split up the api | |
# e.g. | |
# api.register_blueprint(blueprint, url_prefix='/users') | |
# would cascade through /api/users | |
# api routes when hitting /api | |
@api.route("/") | |
def heartbeat(): | |
return jsonify({"status": "healthy"}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from server import Server | |
from api.api import api | |
import os | |
os.chdir(os.path.dirname(__file__)) | |
server = Server(host='localhost', port=8080, spa_path='frontend/dist', index_file='index.html') | |
# Can also use setter functions | |
# server.set_host('localhost') | |
# server.set_port(8080) | |
# server.set_spa_path('frontend/dist') | |
# server.set_index_file('index.html') | |
server.register_blueprint(api, '/api') # add a blueprint containing routes | |
# add a custom 404 method, fires when given route is request prefix | |
@server.errorhandler('/api', 404) | |
def not_found(e): | |
return e | |
server.start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from flask import Flask, request | |
from cheroot.wsgi import Server as WSGIServer, PathInfoDispatcher | |
from threading import Thread | |
import os | |
class Server: | |
def __init__(self, host: str = 'localhost', port: int = 8080, spa_path: str = 'dist', | |
index_file: str = 'index.html'): | |
self.__server = None | |
self.__host = host | |
self.__port = port | |
self.__app = Flask(os.getcwd(), static_folder=spa_path, static_url_path='/') | |
self.__spa_path = spa_path | |
self.__index_file = index_file | |
self.__error_handlers = [] | |
def start(self, threading: bool = False): | |
if threading: | |
Thread(target=self.start).start() | |
return | |
if not self.__server: | |
self.__app.static_folder = self.__spa_path | |
# catches all error codes | |
@self.__app.errorhandler(Exception) | |
def catch_all(e): | |
if getattr(e, 'code', None): | |
# sort in order of the number of / in the route prefix | |
sorted_error_handlers = sorted(self.__error_handlers, key=lambda x: x['route_prefix'].count('/'), reverse=True) | |
for error_handler in sorted_error_handlers: | |
if request.path.startswith(error_handler['route_prefix']) and error_handler['error_code'] == e.code: | |
return error_handler['func'](e) | |
# unless there is an override this runs | |
# webserver always serves the spa unless there is a reason not to | |
if e.code == 404: | |
return self.__app.send_static_file(self.__index_file) | |
# else just default behaviour (return the error) | |
return e | |
# cherrypy production webserver | |
d = PathInfoDispatcher({'/': self.__app}) # load in flask app | |
self.__server = WSGIServer((self.__host, self.__port), d) # create webserver with dispatcher | |
try: | |
print('Server started at ', self.__host, ':', self.__port) | |
self.__server.start() | |
except KeyboardInterrupt: | |
self.stop() | |
def stop(self): | |
if self.__server: | |
self.__server.stop() | |
self.__server = None | |
print('Server stopped') | |
def restart(self): | |
self.stop() | |
self.start() | |
# use as decorator e.g. @server.errorhandler('/api', 404) | |
def errorhandler(self, route_prefix: str, error_code: int): | |
if not route_prefix[0] == '/': | |
route_prefix = '/' + route_prefix | |
if route_prefix[-1] == '/': | |
route_prefix = route_prefix[:-1] | |
def inner(func): | |
self.__error_handlers.append({ | |
'route_prefix': route_prefix, | |
'error_code': error_code, | |
'func': func | |
}) | |
return inner | |
def register_blueprint(self, blueprint, url_prefix: str = ''): | |
self.__app.register_blueprint(blueprint, url_prefix=url_prefix) | |
def set_app_config(self, key: str, value): | |
self.__app.config[key] = value | |
def del_app_config(self, key: str): | |
if key in self.__app.config: | |
del self.__app.config[key] | |
def get_app_config(self, key: str): | |
if key in self.__app.config: | |
return self.__app.config[key] | |
def get_app(self) -> Flask: | |
return self.__app | |
def get_spa_path(self) -> str: | |
return self.__spa_path | |
def set_spa_path(self, spa_folder: str): | |
self.__spa_path = spa_folder | |
self.__app.static_folder = spa_folder | |
def set_index_file(self, index_file: str): | |
self.__index_file = index_file | |
def get_host(self) -> str: | |
return self.__host | |
def get_port(self) -> int: | |
return self.__port | |
def set_host(self, host: str): | |
self.__host = host | |
if self.__server: | |
self.restart() | |
def set_port(self, port: int): | |
self.__port = port | |
if self.__server: | |
self.restart() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
pip install flask
pip install cheroot