# encoding: utf-8 import json import os from importlib import import_module from core.config_manager import Config, ConfigManager if os.path.exists('config.py') or os.path.exists('config'): # 导入用户自定义配置 ConfigManager.load(import_module("config").Config) else: # Allow importing the config from a custom path given through an environment variable configPath = os.environ.get("ARCAEA_JSON_CONFIG_PATH") if configPath and os.path.exists(configPath): with open(configPath, 'r') as file: ConfigManager.load_dict(json.load(file)) if Config.DEPLOY_MODE == 'gevent': # 异步 from gevent import monkey monkey.patch_all() import sys from logging.config import dictConfig from multiprocessing import Process, current_process, set_start_method from traceback import format_exc from flask import Flask, make_response, request, send_from_directory import api import server import web.index import web.login # import webapi from core.bundle import BundleDownload from core.constant import Constant from core.download import UserDownload from core.error import ArcError, NoAccess, RateLimit from core.init import FileChecker from core.sql import Connect from server.func import error_return app = Flask(__name__) if Config.USE_PROXY_FIX: # 代理修复 from werkzeug.middleware.proxy_fix import ProxyFix app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1) if Config.USE_CORS: # 服务端跨域 from flask_cors import CORS CORS(app, supports_credentials=True) os.chdir(sys.path[0]) # 更改工作路径,以便于愉快使用相对路径 app.config.from_mapping(SECRET_KEY=Config.SECRET_KEY) app.config['SESSION_TYPE'] = 'filesystem' app.register_blueprint(web.login.bp) app.register_blueprint(web.index.bp) app.register_blueprint(api.bp) list(map(app.register_blueprint, server.get_bps())) # app.register_blueprint(webapi.bp) @app.route('/') def hello(): return "Hello World!" @app.route('/favicon.ico', methods=['GET']) # 图标 def favicon(): # Pixiv ID: 82374369 # 我觉得这张图虽然并不是那么精细,但很有感觉,色彩的强烈对比下给人带来一种惊艳 # 然后在压缩之下什么也看不清了:( return app.send_static_file('favicon.ico') @app.route('/download/', methods=['GET']) # 下载 def download(file_path): with Connect(in_memory=True) as c: try: x = UserDownload(c) x.token = request.args.get('t') x.song_id, x.file_name = file_path.split('/', 1) x.select_for_check() if x.is_limited: raise RateLimit( f'User `{x.user.user_id}` has reached the download limit.', 903) if not x.is_valid: raise NoAccess('Expired token.') x.download_hit() if Config.DOWNLOAD_USE_NGINX_X_ACCEL_REDIRECT: # nginx X-Accel-Redirect response = make_response() response.headers['Content-Type'] = 'application/octet-stream' response.headers['X-Accel-Redirect'] = Config.NGINX_X_ACCEL_REDIRECT_PREFIX + file_path return response return send_from_directory(Constant.SONG_FILE_FOLDER_PATH, file_path, as_attachment=True, conditional=True) except ArcError as e: if Config.ALLOW_WARNING_LOG: app.logger.warning(format_exc()) return error_return(e) return error_return() @app.route('/bundle_download/', methods=['GET']) # 热更新下载 def bundle_download(token: str): with Connect(in_memory=True) as c_m: try: file_path = BundleDownload(c_m).get_path_by_token( token, request.remote_addr) if Config.DOWNLOAD_USE_NGINX_X_ACCEL_REDIRECT: # nginx X-Accel-Redirect response = make_response() response.headers['Content-Type'] = 'application/octet-stream' response.headers['X-Accel-Redirect'] = Config.BUNDLE_NGINX_X_ACCEL_REDIRECT_PREFIX + file_path return response return send_from_directory(Constant.CONTENT_BUNDLE_FOLDER_PATH, file_path, as_attachment=True, conditional=True) except ArcError as e: if Config.ALLOW_WARNING_LOG: app.logger.warning(format_exc()) return error_return(e) return error_return() if Config.DEPLOY_MODE == 'waitress': # 给waitress加个日志 @app.after_request def after_request(response): app.logger.info( f'{request.remote_addr} - - {request.method} {request.path} {response.status_code}') return response # @app.before_request # def before_request(): # print(request.path) # print(request.headers) # print(request.data) def tcp_server_run(): if Config.DEPLOY_MODE == 'gevent': # 异步 gevent WSGI server host_port = (Config.HOST, Config.PORT) app.logger.info('Running gevent WSGI server... (%s:%s)' % host_port) from gevent.pywsgi import WSGIServer WSGIServer(host_port, app, log=app.logger).serve_forever() elif Config.DEPLOY_MODE == 'waitress': # waitress WSGI server import logging from waitress import serve # type: ignore logger = logging.getLogger('waitress') logger.setLevel(logging.INFO) serve(app, host=Config.HOST, port=Config.PORT) else: if Config.SSL_CERT and Config.SSL_KEY: app.run(Config.HOST, Config.PORT, ssl_context=( Config.SSL_CERT, Config.SSL_KEY)) else: app.run(Config.HOST, Config.PORT) def generate_log_file_dict(level: str, filename: str) -> dict: return { "class": "logging.handlers.RotatingFileHandler", "maxBytes": 1024 * 1024, "backupCount": 1, "encoding": "utf-8", "level": level, "formatter": "default", "filename": filename } def pre_main(): log_dict = { 'version': 1, 'root': { 'level': 'INFO', 'handlers': ['wsgi', 'error_file'] }, 'handlers': { 'wsgi': { 'class': 'logging.StreamHandler', 'stream': 'ext://flask.logging.wsgi_errors_stream', 'formatter': 'default' }, "error_file": generate_log_file_dict('ERROR', f'{Config.LOG_FOLDER_PATH}/error.log') }, 'formatters': { 'default': { 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s' } } } if Config.ALLOW_INFO_LOG: log_dict['root']['handlers'].append('info_file') log_dict['handlers']['info_file'] = generate_log_file_dict( 'INFO', f'{Config.LOG_FOLDER_PATH}/info.log') if Config.ALLOW_WARNING_LOG: log_dict['root']['handlers'].append('warning_file') log_dict['handlers']['warning_file'] = generate_log_file_dict( 'WARNING', f'{Config.LOG_FOLDER_PATH}/warning.log') dictConfig(log_dict) Connect.logger = app.logger if not FileChecker(app.logger).check_before_run(): app.logger.error('Some errors occurred. The server will not run.') input('Press ENTER key to exit.') sys.exit() def main(): if Config.LINKPLAY_HOST and Config.SET_LINKPLAY_SERVER_AS_SUB_PROCESS: from linkplay_server import link_play process = [Process(target=link_play, args=( Config.LINKPLAY_HOST, int(Config.LINKPLAY_UDP_PORT), int(Config.LINKPLAY_TCP_PORT)))] [p.start() for p in process] app.logger.info( f"Link Play UDP server is running on {Config.LINKPLAY_HOST}:{Config.LINKPLAY_UDP_PORT} ...") app.logger.info( f"Link Play TCP server is running on {Config.LINKPLAY_HOST}:{Config.LINKPLAY_TCP_PORT} ...") tcp_server_run() [p.join() for p in process] else: tcp_server_run() # must run for init # this ensures avoiding duplicate init logs for some reason if current_process().name == 'MainProcess': pre_main() if __name__ == '__main__': set_start_method("spawn") main() # Made By Lost 2020.9.11