Source code for girder.utility.server

import logging
import mimetypes
import os
import sys

import cherrypy
import mako

from girder import __version__, constants
from girder.constants import ServerMode
from girder.utility import config
from girder.utility._cache import _setupCache

logger = logging.getLogger(__name__)


class RootHandler:
    @cherrypy.expose
    def index(*args, **kwargs):
        path = os.path.join(
            os.getenv(
                'GIRDER_STATIC_ROOT_DIR',
                constants.STATIC_ROOT_DIR),
            'index.html')
        with open(path) as f:
            content = f.read()
        modified_content = content.replace('root=""', f'root="{os.getenv("GIRDER_URL_ROOT")}"')
        return modified_content


with open(os.path.join(os.path.dirname(__file__), 'error.mako')) as f:
    _errorTemplate = f.read()


def _errorDefault(status, message, *args, **kwargs):
    """
    This is used to render error pages outside of the normal Girder app, such as
    404's. This overrides the default cherrypy error pages.
    """
    return mako.template.Template(_errorTemplate).render(status=status, message=message)


def create_app(mode: str) -> dict:
    curConfig = config.getConfig()
    appconf = {
        '/': {
            'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
            'request.show_tracebacks': mode == ServerMode.TESTING,
            'request.methods_with_bodies': ('POST', 'PUT', 'PATCH'),
            'response.headers.server': 'Girder %s' % __version__,
            'error_page.default': _errorDefault
        }
    }
    # Add MIME types for serving Fontello files from staticdir;
    # these may be missing or incorrect in the OS. This is idempotent.
    mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
    mimetypes.add_type('application/x-font-ttf', '.ttf')
    mimetypes.add_type('application/font-woff', '.woff')
    mimetypes.add_type('text/javascript', '.cjs')

    curConfig.update(appconf)
    curConfig['server'] = {'mode': mode}

    logging.basicConfig(stream=sys.stdout, level=os.environ.get('LOGLEVEL', 'INFO'))

    logger.info('Running in mode: %s', curConfig['server']['mode'])
    cherrypy.config.update({
        'log.screen': False,
        'log.access_file': '',
        'log.error_file': '',
        'engine.autoreload.on': curConfig['server']['mode'] == ServerMode.DEVELOPMENT,
    })

    _setupCache(curConfig)

    # Don't import this until after the configs have been read; some module
    # initialization code requires the configuration to be set up.
    from girder.api.api_main import buildApi

    apiRoot = buildApi()
    tree = cherrypy._cptree.Tree()

    info = dict(config=appconf, serverRoot=tree, apiRoot=apiRoot.v1)

    custom_root = os.getenv('GIRDER_URL_ROOT')
    custom_root = None if not custom_root or custom_root == '/' else custom_root
    static_opts = {
        'tools.staticdir.on': True,
        'tools.staticdir.dir': os.getenv('GIRDER_STATIC_ROOT_DIR', constants.STATIC_ROOT_DIR),
        'request.show_tracebacks': appconf['/']['request.show_tracebacks'],
        'response.headers.server': f'Girder {__version__}',
        'error_page.default': _errorDefault
    }
    if not custom_root:
        static_opts['tools.staticdir.index'] = 'index.html'
    # Mount static files
    tree.mount(RootHandler if custom_root else None, '', {
        '/': static_opts
    })

    # Mount the web API at /api (always).
    tree.mount(apiRoot, '/api', appconf)

    # When GIRDER_URL_ROOT names a public path prefix (e.g. "girder" for
    # https://host/girder/...), mount the same API object at /<prefix>/api so
    # /api/v1 and /<prefix>/api/v1 hit identical handlers
    if custom_root:
        prefix = '/' + custom_root.strip('/')
        tree.mount(apiRoot, f'{prefix}/api', appconf)

    return info


class _StaticFileRoute:
    exposed = True

    def __init__(self, path, contentType=None):
        self.path = os.path.abspath(path)
        self.contentType = contentType

    def GET(self):
        return cherrypy.lib.static.serve_file(self.path, content_type=self.contentType)


[docs] def staticFile(path, contentType=None): """ Helper function to serve a static file. This should be bound as the route object, i.e. info['serverRoot'].route_name = staticFile('...') :param path: The path of the static file to serve from this route. :type path: str :param contentType: The MIME type of the static file. If set to None, the content type will be guessed by the file extension of the 'path' argument. """ return _StaticFileRoute(path, contentType)