Пример #1
0
def create_app(config_class=Config):
    static_folder = 'static'
    if os.environ.get('ENVIRONMENT') and os.environ.get('ENVIRONMENT') != "local":
        static_folder = os.environ.get('STATIC_PATH', "static")

    app = Flask(__name__, static_folder=static_folder)
    app.config.from_object(config_class)
    login.init_app(app)
    babel.init_app(app)
    csrf.init_app(app)
    if static_folder != 'static':
        CDN(app)

    app.register_blueprint(admin_blueprint, url_prefix='/admin')
    app.register_blueprint(rsvp_blueprint, url_prefix='/rsvp')
    login.login_message = None
    login.blueprint_login_views = {
        'admin': 'admin.login',
        'rsvp': 'rsvp.rsvp_captcha',
    }

    @app.errorhandler(404)
    def page_not_found(e):
        print(str(e))
        return render_template('errors/404.html'), 404

    return app
Пример #2
0
 def client_get(self, ufs, secure=False):
     CDN(self.app)
     client = self.app.test_client()
     if secure:
         return client.get('/%s' % ufs, base_url='https://localhost')
     else:
         return client.get('/%s' % ufs)
Пример #3
0
    def setUp(self):
        self.app = Flask(__name__)
        self.app.testing = True
        self.app.config['CDN_DOMAIN'] = 'mycdnname.cloudfront.net'
        self.app.config['CDN_TIMESTAMP'] = True
        CDN(self.app)

        test_bp = Blueprint('test_bp', 'test')

        @test_bp.route('/without_static/<url_for_string>')
        def a(url_for_string):
            return render_template_string(url_for_string)

        self.app.register_blueprint(test_bp)

        test2_bp = Blueprint('test2_bp',
                             'test2',
                             static_folder=self.app.static_folder + '_bp',
                             static_url_path='/test2_bp/static')

        @test2_bp.route('/with_static/<url_for_string>')
        def b(url_for_string):
            return render_template_string(url_for_string)

        self.app.register_blueprint(test2_bp)
def create_web_app(app: App) -> Flask:
    wsgi = Flask(__name__)
    wsgi.register_blueprint(ep)
    wsgi.register_blueprint(admin)
    wsgi.teardown_request(close_session)
    wsgi.errorhandler(NoResultFound)(handle_no_result_found)
    wsgi.jinja_env.filters['kst'] = to_kst
    wsgi.config.update(app.web_config)
    wsgi.config['APP'] = app
    wsgi.secret_key = app.secret_key
    wsgi.wsgi_app = SassMiddleware(wsgi.wsgi_app, {
        'pycon2018': ('static/css', 'static/css', 'static/css')
    })
    login_manager.init_app(wsgi)
    if wsgi.config.get('CDN_DOMAIN'):
        cdn = CDN()
        cdn.init_app(wsgi)
    if app.sentry_dsn:
        Sentry(wsgi, dsn=app.sentry_dsn)
    return wsgi
Пример #5
0
def init_app(app, views=None):
    views = views or VIEWS

    init_markdown(app)

    from . import helpers, error_handlers  # noqa

    for view in views:
        _load_views(app, 'udata.{}.views'.format(view))

    # Load all plugins views and blueprints
    for module in entrypoints.get_enabled('udata.views', app).values():
        _load_views(app, module)

    # Load core manifest
    with app.app_context():
        assets.register_manifest('udata')
        for dist in entrypoints.get_plugins_dists(app, 'udata.views'):
            if assets.has_manifest(dist.project_name):
                assets.register_manifest(dist.project_name)

    # Optionally register debug views
    if app.config.get('DEBUG'):

        @front.route('/403/')
        def test_403():
            abort(403)

        @front.route('/404/')
        def test_404():
            abort(404)

        @front.route('/500/')
        def test_500():
            abort(500)

    # Load front only views and helpers
    app.register_blueprint(front)

    # Enable CDN if required
    if app.config['CDN_DOMAIN'] is not None:
        from flask_cdn import CDN
        CDN(app)

    # Load debug toolbar if enabled
    if app.config.get('DEBUG_TOOLBAR'):
        from flask_debugtoolbar import DebugToolbarExtension
        DebugToolbarExtension(app)
Пример #6
0
def init_app(app):
    from udata_gouvfr import theme

    nav.init_app(app)
    theme.init_app(app)

    from . import helpers, error_handlers  # noqa

    if app.config['RESOURCES_SCHEMAGOUVFR_ENABLED']:
        VIEWS.append('schema')

    for view in VIEWS:
        _load_views(app, 'udata_gouvfr.views.{}'.format(view))

    # Load all plugins views and blueprints
    for module in entrypoints.get_enabled('udata.views', app).values():
        _load_views(app, module)

    # Optionally register debug views
    if app.config.get('DEBUG'):

        @front.route('/403/')
        def test_403():
            abort(403)

        @front.route('/404/')
        def test_404():
            abort(404)

        @front.route('/500/')
        def test_500():
            abort(500)

    # Load front only views and helpers
    app.register_blueprint(front)

    # Enable CDN if required
    if app.config['CDN_DOMAIN'] is not None:
        from flask_cdn import CDN
        CDN(app)

    # Load debug toolbar if enabled
    if app.config.get('DEBUG_TOOLBAR'):
        from flask_debugtoolbar import DebugToolbarExtension
        DebugToolbarExtension(app)
Пример #7
0
    def setUp(self):
        self.app = Flask(__name__)
        self.app.testing = True

        CDN(self.app)
Пример #8
0
from flask_login import LoginManager
from flask_compress import Compress
from flask_cdn import CDN
from flask_redis import Redis

# Socket Init Setting
async_mode = "eventlet"

# db variable initialization
db = SQLAlchemy()

# flask-login initialization
login_manager = LoginManager()

# cdn initialization
cdn = CDN()

# compress initialization
compress = Compress()

# App Config
app = Flask(__name__)
socketio = SocketIO(app, async_mode=async_mode)
app.config.from_object(app_config[FLASK_CONFIG])
app.config.from_pyfile('config.py')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_ECHO'] = False
app.config['SQLALCHEMY_POOL_SIZE'] = 100
app.config['CDN_DOMAIN'] = 'd30bzjua38r0w8.cloudfront.net'
app.config['CDN_HTTPS'] = True
app.config['CDN_DEBUG'] = True
Пример #9
0
def create_app() -> Flask:
    """创建 flask app"""
    from everyclass.server.db.dao import new_user_id_sequence
    from everyclass.server.utils.logbook_logstash.formatter import LOG_FORMAT_STRING
    from everyclass.server.consts import MSG_INTERNAL_ERROR
    from everyclass.server.utils import plugin_available

    print("Creating app...")

    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
    """
    每课统一日志机制


    规则如下:
    - WARNING 以下 log 输出到 stdout
    - WARNING 以上输出到 stderr
    - DEBUG 以上日志以 json 形式通过 TCP 输出到 Logstash,然后发送到日志中心
    - 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['CONFIG_NAME'] in app.config['DEBUG_LOG_AVAILABLE_IN']:
        stdout_handler = logbook.StreamHandler(
            stream=sys.stdout, bubble=True, filter=lambda r, h: r.level < 13)
    else:
        # ignore debug when not in debug
        stdout_handler = logbook.StreamHandler(
            stream=sys.stdout,
            bubble=True,
            filter=lambda r, h: 10 < r.level < 13)
    stdout_handler.format_string = LOG_FORMAT_STRING
    logger.handlers.append(stdout_handler)

    stderr_handler = logbook.StreamHandler(stream=sys.stderr,
                                           bubble=True,
                                           level='WARNING')
    stderr_handler.format_string = LOG_FORMAT_STRING
    logger.handlers.append(stderr_handler)

    # CDN
    CDN(app)

    # moment
    Moment(app)

    # 导入并注册 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')

    # 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,方便 Elastic APM 记录用户请求"""
        if not session.get('user_id',
                           None) and request.endpoint != "main.health_check":
            session['user_id'] = new_user_id_sequence()

    @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
Пример #10
0
def create_app(config_import="config.Config"):
    app = Flask(__name__)
    app.config.from_object(config_import)

    app.add_url_rule('/textures/<path:filename>',
                     endpoint='textures',
                     view_func=send_texture)

    from .models import db, SecretSanity
    db.app = app
    db.init_app(app)
    cdn = CDN(app)

    raygun = raygunprovider.RaygunSender(app.config['RAYGUN_APIKEY'])

    @app.errorhandler(500)
    def on_internal_server_error(e):
        raygun.send_exception(e.original_exception, sys.exc_info())
        return e

    def fix_externals(func):
        """Fixes an issue where undesired values are send to the url by flask_cdn.

        Should probably be fixed in the library
        """
        @functools.wraps(func)
        def decorator(self, endpoint, values=None, **kwargs):
            if values:
                values.pop('_external', None)
            return func(self, endpoint, values=values, **kwargs)

        return decorator

    MapAdapter.build = fix_externals(MapAdapter.build)
    app.wsgi_app = ProxyFix(app.wsgi_app)

    from .util import UserConverter
    app.url_map.converters['user'] = UserConverter

    from .api.v0 import apiv0
    from .api.v1 import apiv1

    app.register_blueprint(apiv0)
    app.register_blueprint(apiv1)

    from . import cli
    cli.init_app(app)

    @app.before_first_request
    def init_auth():
        app.config['server_id'] = random_string(20)

    @app.before_request
    def secret_sanity_check():
        secret = app.config['SECRET_KEY']
        try:
            saved_secret = SecretSanity.query.one()
            if saved_secret.secret != secret:
                abort(
                    500,
                    "Sanity error! Secret does not match, did the secret change?"
                )
        except NoResultFound:
            db.session.add(SecretSanity(secret=secret))
            db.session.commit()
        except MultipleResultsFound:
            abort(
                500,
                "Multiple secrets found. Something has gone terribly wrong.")

    return app
Пример #11
0
from flask import Flask, flash, render_template, request
from flask_cdn import CDN
from flask_wtf import FlaskForm
from flask_wtf.file import FileField
from wtforms import SubmitField
from werkzeug.utils import secure_filename
import os

app = Flask(__name__)
app.config.from_object('config')
cdn = CDN(app)


class UploadForm(FlaskForm):
    image = FileField('image')
    submit = SubmitField('Create')


def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']


@app.route('/', methods=['GET', 'POST'])
def file_upload():
    form = UploadForm()
    if form.validate_on_submit():
        file_upload = form.image.data
        file_name = secure_filename(file_upload.filename)
        if allowed_file(file_name):
            s3 = app.config['S3']
Пример #12
0
def create_app(outside_container=False) -> Flask:
    """创建 flask app
    @param outside_container: 是否不在容器内运行
    """
    from everyclass.server.db.dao import new_user_id_sequence
    from everyclass.server.db.mysql import init_pool
    from everyclass.server.utils.logbook_logstash.formatter import LOG_FORMAT_STRING

    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)
    """
    每课统一日志机制


    规则如下:
    - WARNING 以下 log 输出到 stdout
    - WARNING 以上输出到 stderr
    - DEBUG 以上日志以 json 形式通过 TCP 输出到 Logstash,然后发送到日志中心
    - 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
    """
    stdout_handler = logbook.StreamHandler(stream=sys.stdout,
                                           bubble=True,
                                           filter=lambda r, h: r.level < 13)
    stdout_handler.format_string = LOG_FORMAT_STRING
    logger.handlers.append(stdout_handler)

    stderr_handler = logbook.StreamHandler(stream=sys.stderr,
                                           bubble=True,
                                           level='WARNING')
    stderr_handler.format_string = LOG_FORMAT_STRING
    logger.handlers.append(stderr_handler)

    # CDN
    CDN(app)

    # 容器外运行(无 uWSGI)时初始化数据库
    if outside_container and (app.config['CONFIG_NAME'] == "development"):
        init_pool(app)

    # 导入并注册 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.api import api_v1 as api_blueprint
    app.register_blueprint(cal_blueprint)
    app.register_blueprint(query_blueprint)
    app.register_blueprint(main_blueprint)
    app.register_blueprint(api_blueprint, url_prefix='/api/v1')

    @app.before_request
    def set_user_id():
        """在请求之前设置 session uid,方便 Elastic APM 记录用户请求"""
        if not session.get('user_id', None):
            session['user_id'] = new_user_id_sequence()

    @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):
        return render_template(
            '500.html',
            event_id=g.sentry_event_id,
            public_dsn=sentry.client.get_public_dsn('https'))

    global __app
    __app = app

    return app
Пример #13
0
import logging
from datetime import timedelta

from cidc_utils.loghandler import StackdriverJsonFormatter
from flask import Flask, session
from flask_cdn import CDN

from constants import FLASK_SECRET_KEY, SESSION_TIMEOUT_MINUTES

APP = Flask(__name__, static_folder='static')
APP.config["SECRET_KEY"] = FLASK_SECRET_KEY
APP.config['CDN_DOMAIN'] = "https://storage.googleapis.com/cidc-js-build/"
APP.url_map.strict_slashes = False
CDN_INSTANCE = CDN()


def configure_logging():
    """
    Configures the loghandler to send formatted logs to stackdriver.
    """
    # Configure Stackdriver logging.
    logger = logging.getLogger()
    logger.setLevel("INFO")
    log_handler = logging.StreamHandler()
    log_handler.setFormatter(StackdriverJsonFormatter())
    logger.addHandler(log_handler)
    logging.info({"message": "LOGGER CONFIGURED", "category": "INFO-EVE-LOGGING"})


@APP.before_request
def make_session_permanent():
Пример #14
0
def create_app() -> Flask:
    """创建 flask app"""
    from everyclass.server.utils.web_consts import MSG_INTERNAL_ERROR
    from everyclass.server import plugin_available
    from everyclass.server.utils import generate_error_response, api_helpers, base_exceptions
    from everyclass.common.env import is_production
    from everyclass.server.utils.web_consts import MSG_404

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

    # load app config
    from everyclass.server.utils.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 calendar_bp
    from everyclass.server.calendar.views_api import calendar_api_bp
    from everyclass.server.entity.views import entity_bp
    from everyclass.server.entity.views_api import entity_api_bp
    from everyclass.server.user.views import user_bp
    from everyclass.server.user.views_api import user_api_bp
    from everyclass.server.course.views import course_bp
    from everyclass.server.course.views_api import course_api_bp
    from everyclass.server.views_main import main_blueprint
    app.register_blueprint(calendar_bp)
    app.register_blueprint(entity_bp)
    app.register_blueprint(user_bp, url_prefix='/user')
    app.register_blueprint(entity_api_bp, url_prefix='/mobile/entity')
    app.register_blueprint(user_api_bp, url_prefix='/mobile/user')
    app.register_blueprint(calendar_api_bp, url_prefix='/mobile/calendar')
    if 'course' in _config.FEATURE_GATING and _config.FEATURE_GATING['course']:
        app.register_blueprint(course_api_bp, url_prefix='/mobile/course')
        app.register_blueprint(course_bp, url_prefix='/course')
    app.register_blueprint(main_blueprint)

    # 初始化 RPC 模块
    from everyclass.rpc.auth import Auth
    if 'AUTH_BASE_URL' in app.config:
        Auth.set_base_url(app.config['AUTH_BASE_URL'])

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

        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'] = user_service.get_user_id()
        if session.get('user_id', None):
            tracer.current_root_span().set_tag(
                "user_id", session[SESSION_USER_SEQ])  # 唯一用户 ID
        if session.get(SESSION_CURRENT_USER, None):
            tracer.current_root_span().set_tag(
                "username", session[SESSION_CURRENT_USER].identifier)  # 学号或教工号

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

    @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

    from everyclass.server.utils.db.postgres import db_session

    @app.teardown_appcontext
    def shutdown_db_session(exception=None):
        db_session.remove()

    @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.context_processor
    def inject_consts():
        """允许在模板中使用常量模块,以便使用session key等常量而不用在模板中硬编码"""
        return dict(consts=web_consts,
                    api_base_url=app.config['MOBILE_API_BASE_URL'])

    @app.errorhandler(404)
    def page_not_found(error):
        if request.path.startswith('/mobile'):
            return generate_error_response(
                None, api_helpers.STATUS_CODE_INVALID_REQUEST, "no such API")
        return render_template('common/error.html', message=MSG_404)

    @app.errorhandler(base_exceptions.BizException)
    def handle_biz_exception(error: base_exceptions.BizException):
        if request.path.startswith("/mobile"):
            if isinstance(error, base_exceptions.InternalError):
                logger.error(repr(error))

            # 业务错误的status_message可以对外展示
            actual_error = {'status_message_overwrite': error.status_message}
            return generate_error_response(None, error.status_code,
                                           **actual_error)

    @app.errorhandler(500)
    def internal_server_error(error):
        if request.path.startswith("/mobile"):
            # 对于非业务错误,生产环境中不进行返回,其他环境中可返回
            actual_error = {
                'status_message_overwrite':
                f"server internal error: {repr(error.original_exception)}"
            } if not is_production() else {}
            return generate_error_response(
                None, api_helpers.STATUS_CODE_INTERNAL_ERROR, **actual_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 f"<h4>500 Error: {repr(error.original_exception)}</h4><br>You are seeing this page because Sentry is not available."

    global __app
    __app = app

    return app
Пример #15
0
login_manager = LoginManager()
login_manager.init_app(app)

# init flask principal
principals = Principal(app)

# init sqlmigration manager
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command("db", MigrateCommand)

# init SeaSurf
seasurf = SeaSurf(app)

# init flask CDN
CDN(app)

# init flask HTMLMIN
HTMLMIN(app)

# init assets environment
assets = Environment(app)
register_filter(BabiliFilter)


class MiniJSONEncoder(JSONEncoder):
    """Minify JSON output."""
    item_separator = ','
    key_separator = ':'

    def default(self, obj):
Пример #16
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
Пример #17
0
    Sentry(flapp, dsn=sentry_dsn)
except Exception as e:
    logger.info("no sentry logging")
    logger.error(e)
logging.basicConfig(level=logging.DEBUG)
mc_logger = logging.getLogger('mediacloud')
requests_logger = logging.getLogger('requests')

logger.info(
    "---------------------------------------------------------------------------------------"
)

# Flask app config
if config.get('custom', 'use_cdn') == 'true':
    flapp.config['CDN_DOMAIN'] = 'd31f66kh11e0nw.cloudfront.net'
    CDN(flapp)
    flapp.config['FLASK_ASSETS_USE_CDN'] = True
flapp.secret_key = 'put secret key here'
flapp.config['SEND_FILE_MAX_AGE_DEFAULT'] = 7 * 24 * 60 * 60
assets = Environment(flapp)

# Create media cloud api
app_mc_key = config.get('mediacloud', 'key')
mc = mcapi.AdminMediaCloud(app_mc_key)
logger.info("Connected to MediaCloud with default key %s" % (app_mc_key))
logging.getLogger('MediaCloud').setLevel(logging.DEBUG)

# Create user login manager
login_manager = flask_login.LoginManager()
login_manager.init_app(flapp)
Пример #18
0
naming_convention = {
    "ix": 'ix_%(column_0_label)s',
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(column_0_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s"
}
db = SQLAlchemy(metadata=MetaData(naming_convention=naming_convention))

cache = Cache()
csrf = CsrfProtect()
migrate = Migrate()
manager = VersioningManager(options={'strategy': 'subquery'})
make_versioned(manager=manager, plugins=[FlaskPlugin()])
mail = Mail()
cdn = CDN()
login_manager = LoginManager()
assets = Environment()
toolbar = DebugToolbarExtension()
gocardless_client = None
volunteer_admin = None

pyscss = get_filter('pyscss', style='compressed')
assets.register(
    'css_main',
    Bundle('css/main.scss',
           output='gen/main-packed.css',
           depends='css/*.scss',
           filters=pyscss))
assets.register(
    'css_admin',
Пример #19
0
def create_app(offline=False) -> Flask:
    """创建 flask app
    @param offline: 如果设置为 `True`,则为离线模式。此模式下不会连接到 Sentry 和 ElasticAPM
    """
    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)

    # CDN
    CDN(app)

    # logbook handlers
    # 规则如下:
    # - 全部输出到 stdout(本地开发调试、服务器端文件日志)
    # - Elastic APM 或者 LogStash(日志中心)
    # - 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
    #
    # Elastic APM:
    # log.info("Nothing to see here", stack=False)
    # stack 默认是 True,设置为 False 将不会向 Elastic APM 发送 stack trace
    # https://discuss.elastic.co/t/how-to-use-logbook-handler/146209/6
    #
    # Sentry:
    # https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage
    # - stack 默认是 False,和 Elastic APM 的不一致,所以还是每次手动指定吧...
    # - 默认事件类型是 `raven.events.Message`,设置 `exc_info` 为 `True` 将把事件类型升级为`raven.events.Exception`
    stderr_handler = logbook.StderrHandler(bubble=True)
    logger.handlers.append(stderr_handler)

    if not offline:
        # Sentry
        sentry.init_app(app=app)
        sentry_handler = SentryHandler(
            sentry.client, level='WARNING')  # Sentry 只处理 WARNING 以上的
        logger.handlers.append(sentry_handler)

        # Elastic APM
        apm = ElasticAPM(app)
        elastic_handler = ElasticHandler(client=apm.client, bubble=True)
        logger.handlers.append(elastic_handler)

    # 初始化数据库
    if os.getenv('MODE', None) != "CI":
        init_pool(app)

    # 导入并注册 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.api import api_v1 as api_blueprint
    app.register_blueprint(cal_blueprint)
    app.register_blueprint(query_blueprint)
    app.register_blueprint(main_blueprint)
    app.register_blueprint(api_blueprint, url_prefix='/api/v1')

    @app.before_request
    def set_user_id():
        """在请求之前设置 session uid,方便 Elastic APM 记录用户请求"""
        if not session.get('user_id', None):
            # 数据库中生成唯一 ID,参考 https://blog.csdn.net/longjef/article/details/53117354
            conn = get_local_conn()
            cursor = conn.cursor()
            cursor.execute(
                "REPLACE INTO user_id_sequence (stub) VALUES ('a');")
            session['user_id'] = cursor.lastrowid
            cursor.close()

    @app.teardown_request
    def close_db(error):
        """结束时关闭数据库连接"""
        if hasattr(g, 'mysql_db') and g.mysql_db:
            g.mysql_db.close()

    @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):
        return render_template(
            '500.html',
            event_id=g.sentry_event_id,
            public_dsn=sentry.client.get_public_dsn('https'))

    logger.info('App created with `{}` config'.format(
        app.config['CONFIG_NAME']),
                stack=False)
    return app