Example #1
0
from everyclass.rpc import init as _init_rpc
from everyclass.rpc.entity import Entity as _entity
from everyclass.server.utils.config import get_config
from everyclass.server.utils.encryption import encrypt

_init_rpc(resource_id_encrypt_function=encrypt)  # 为 everyclass.rpc 模块注入 encrypt 函数

_entity.set_base_url(get_config().ENTITY_BASE_URL)
_entity.set_request_token(get_config().ENTITY_TOKEN)
Example #2
0
def create_app() -> Flask:
    """创建 flask app"""
    from everyclass.server.db.dao import new_user_id_sequence
    from everyclass.server.consts import MSG_INTERNAL_ERROR
    from everyclass.server.utils import plugin_available

    app = Flask(__name__,
                static_folder='../../frontend/dist',
                static_url_path='',
                template_folder="../../frontend/templates")

    # load app config
    from everyclass.server.config import get_config
    _config = get_config()
    app.config.from_object(_config)  # noqa: T484
    """
    每课统一日志机制


    规则如下:
    - DEBUG 模式下会输出 DEBUG 等级的日志,否则输出 INFO 及以上等级的日志
    - 日志为 JSON 格式,会被节点上的 agent 采集并发送到 datadog,方便结合 metrics 和 APM 数据分析
    - WARNING 以上级别的输出到 Sentry 做错误聚合


    日志等级:
    critical – for errors that lead to termination
    error – for errors that occur, but are handled
    warning – for exceptional circumstances that might not be errors
    notice – for non-error messages you usually want to see
    info – for messages you usually don’t want to see
    debug – for debug messages


    Sentry:
    https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage
    - stack 默认是 False
    """
    if app.config['DEBUG']:
        logging.getLogger().setLevel(logging.DEBUG)
    else:
        logging.getLogger().setLevel(logging.INFO)

    # CDN
    CDN(app)

    # moment
    Moment(app)

    # encrypted session
    app.session_interface = EncryptedSessionInterface()

    # 导入并注册 blueprints
    from everyclass.server.calendar.views import cal_blueprint
    from everyclass.server.query import query_blueprint
    from everyclass.server.views import main_blueprint as main_blueprint
    from everyclass.server.user.views import user_bp
    from everyclass.server.course_review.views import cr_blueprint
    app.register_blueprint(cal_blueprint)
    app.register_blueprint(query_blueprint)
    app.register_blueprint(main_blueprint)
    app.register_blueprint(user_bp, url_prefix='/user')

    # 初始化 RPC 模块
    from everyclass.server.utils.resource_identifier_encrypt import encrypt
    from everyclass.rpc import init as init_rpc
    from everyclass.rpc.entity import Entity
    from everyclass.rpc.auth import Auth
    init_rpc(resource_id_encrypt_function=encrypt
             )  # 为 everyclass.rpc 模块注入 encrypt 函数
    if 'API_SERVER_BASE_URL' in app.config:
        Entity.set_base_url(app.config['API_SERVER_BASE_URL'])
    if 'API_SERVER_TOKEN' in app.config:
        Entity.set_request_token(app.config['API_SERVER_TOKEN'])
    if 'AUTH_BASE_URL' in app.config:
        Auth.set_base_url(app.config['AUTH_BASE_URL'])

    # course review feature gating
    if app.config['FEATURE_GATING']['course_review']:
        app.register_blueprint(cr_blueprint, url_prefix='/course_review')

    @app.before_request
    def set_user_id():
        """在请求之前设置 session uid,方便 APM 标识用户"""
        from everyclass.server.consts import SESSION_CURRENT_USER

        if not session.get('user_id', None) and request.endpoint not in (
                "main.health_check", "static"):
            logger.info(
                f"Give a new user ID for new user. endpoint: {request.endpoint}"
            )
            session['user_id'] = new_user_id_sequence()
        if session.get('user_id', None):
            tracer.current_root_span().set_tag("user_id",
                                               session['user_id'])  # 唯一用户 ID
        if session.get(SESSION_CURRENT_USER, None):
            tracer.current_root_span().set_tag(
                "username", session[SESSION_CURRENT_USER])  # 学号

    @app.before_request
    def log_request():
        """日志中记录请求"""
        logger.info(f'Request received: {request.method} {request.path}')

    @app.before_request
    def delete_old_session():
        """删除旧的客户端 session(长度过长导致无法在 mongodb 中建立索引)"""
        if request.cookies.get("session") and len(
                request.cookies.get("session")) > 50:
            session.clear()
            return redirect(request.url)

    @app.after_request
    def response_minify(response):
        """用 htmlmin 压缩 HTML,减轻带宽压力"""
        if app.config[
                'HTML_MINIFY'] and response.content_type == u'text/html; charset=utf-8':
            response.set_data(minify(response.get_data(as_text=True)))
        return response

    @app.template_filter('versioned')
    def version_filter(filename):
        """
        模板过滤器。如果 STATIC_VERSIONED,返回类似 'style-v1-c012dr.css' 的文件,而不是 'style-v1.css'

        :param filename: 文件名
        :return: 新的文件名
        """
        if app.config['STATIC_VERSIONED']:
            if filename[:4] == 'css/':
                new_filename = app.config['STATIC_MANIFEST'][filename[4:]]
                return 'css/' + new_filename
            elif filename[:3] == 'js/':
                new_filename = app.config['STATIC_MANIFEST'][filename[3:]]
                return new_filename
            else:
                return app.config['STATIC_MANIFEST'][filename]
        return filename

    @app.errorhandler(500)
    def internal_server_error(error):
        if plugin_available("sentry"):
            return render_template(
                'common/error.html',
                message=MSG_INTERNAL_ERROR,
                event_id=g.sentry_event_id,
                public_dsn=sentry.client.get_public_dsn('https'))
        return "<h4>500 Error: {}</h4><br>You are seeing this page because Sentry is not available.".format(
            repr(error))

    global __app
    __app = app

    return app