michael@0: # Copyright 2011, Google Inc. michael@0: # All rights reserved. michael@0: # michael@0: # Redistribution and use in source and binary forms, with or without michael@0: # modification, are permitted provided that the following conditions are michael@0: # met: michael@0: # michael@0: # * Redistributions of source code must retain the above copyright michael@0: # notice, this list of conditions and the following disclaimer. michael@0: # * Redistributions in binary form must reproduce the above michael@0: # copyright notice, this list of conditions and the following disclaimer michael@0: # in the documentation and/or other materials provided with the michael@0: # distribution. michael@0: # * Neither the name of Google Inc. nor the names of its michael@0: # contributors may be used to endorse or promote products derived from michael@0: # this software without specific prior written permission. michael@0: # michael@0: # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: michael@0: """PythonHeaderParserHandler for mod_pywebsocket. michael@0: michael@0: Apache HTTP Server and mod_python must be configured such that this michael@0: function is called to handle WebSocket request. michael@0: """ michael@0: michael@0: michael@0: import logging michael@0: michael@0: from mod_python import apache michael@0: michael@0: from mod_pywebsocket import common michael@0: from mod_pywebsocket import dispatch michael@0: from mod_pywebsocket import handshake michael@0: from mod_pywebsocket import util michael@0: michael@0: michael@0: # PythonOption to specify the handler root directory. michael@0: _PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root' michael@0: michael@0: # PythonOption to specify the handler scan directory. michael@0: # This must be a directory under the root directory. michael@0: # The default is the root directory. michael@0: _PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan' michael@0: michael@0: # PythonOption to allow handlers whose canonical path is michael@0: # not under the root directory. It's disallowed by default. michael@0: # Set this option with value of 'yes' to allow. michael@0: _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT = ( michael@0: 'mod_pywebsocket.allow_handlers_outside_root_dir') michael@0: # Map from values to their meanings. 'Yes' and 'No' are allowed just for michael@0: # compatibility. michael@0: _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION = { michael@0: 'off': False, 'no': False, 'on': True, 'yes': True} michael@0: michael@0: # PythonOption to specify to allow draft75 handshake. michael@0: # The default is None (Off) michael@0: _PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75' michael@0: # Map from values to their meanings. michael@0: _PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True} michael@0: michael@0: michael@0: class ApacheLogHandler(logging.Handler): michael@0: """Wrapper logging.Handler to emit log message to apache's error.log.""" michael@0: michael@0: _LEVELS = { michael@0: logging.DEBUG: apache.APLOG_DEBUG, michael@0: logging.INFO: apache.APLOG_INFO, michael@0: logging.WARNING: apache.APLOG_WARNING, michael@0: logging.ERROR: apache.APLOG_ERR, michael@0: logging.CRITICAL: apache.APLOG_CRIT, michael@0: } michael@0: michael@0: def __init__(self, request=None): michael@0: logging.Handler.__init__(self) michael@0: self._log_error = apache.log_error michael@0: if request is not None: michael@0: self._log_error = request.log_error michael@0: michael@0: # Time and level will be printed by Apache. michael@0: self._formatter = logging.Formatter('%(name)s: %(message)s') michael@0: michael@0: def emit(self, record): michael@0: apache_level = apache.APLOG_DEBUG michael@0: if record.levelno in ApacheLogHandler._LEVELS: michael@0: apache_level = ApacheLogHandler._LEVELS[record.levelno] michael@0: michael@0: msg = self._formatter.format(record) michael@0: michael@0: # "server" parameter must be passed to have "level" parameter work. michael@0: # If only "level" parameter is passed, nothing shows up on Apache's michael@0: # log. However, at this point, we cannot get the server object of the michael@0: # virtual host which will process WebSocket requests. The only server michael@0: # object we can get here is apache.main_server. But Wherever (server michael@0: # configuration context or virtual host context) we put michael@0: # PythonHeaderParserHandler directive, apache.main_server just points michael@0: # the main server instance (not any of virtual server instance). Then, michael@0: # Apache follows LogLevel directive in the server configuration context michael@0: # to filter logs. So, we need to specify LogLevel in the server michael@0: # configuration context. Even if we specify "LogLevel debug" in the michael@0: # virtual host context which actually handles WebSocket connections, michael@0: # DEBUG level logs never show up unless "LogLevel debug" is specified michael@0: # in the server configuration context. michael@0: # michael@0: # TODO(tyoshino): Provide logging methods on request object. When michael@0: # request is mp_request object (when used together with Apache), the michael@0: # methods call request.log_error indirectly. When request is michael@0: # _StandaloneRequest, the methods call Python's logging facility which michael@0: # we create in standalone.py. michael@0: self._log_error(msg, apache_level, apache.main_server) michael@0: michael@0: michael@0: def _configure_logging(): michael@0: logger = logging.getLogger() michael@0: # Logs are filtered by Apache based on LogLevel directive in Apache michael@0: # configuration file. We must just pass logs for all levels to michael@0: # ApacheLogHandler. michael@0: logger.setLevel(logging.DEBUG) michael@0: logger.addHandler(ApacheLogHandler()) michael@0: michael@0: michael@0: _configure_logging() michael@0: michael@0: _LOGGER = logging.getLogger(__name__) michael@0: michael@0: michael@0: def _parse_option(name, value, definition): michael@0: if value is None: michael@0: return False michael@0: michael@0: meaning = definition.get(value.lower()) michael@0: if meaning is None: michael@0: raise Exception('Invalid value for PythonOption %s: %r' % michael@0: (name, value)) michael@0: return meaning michael@0: michael@0: michael@0: def _create_dispatcher(): michael@0: _LOGGER.info('Initializing Dispatcher') michael@0: michael@0: options = apache.main_server.get_options() michael@0: michael@0: handler_root = options.get(_PYOPT_HANDLER_ROOT, None) michael@0: if not handler_root: michael@0: raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT, michael@0: apache.APLOG_ERR) michael@0: michael@0: handler_scan = options.get(_PYOPT_HANDLER_SCAN, handler_root) michael@0: michael@0: allow_handlers_outside_root = _parse_option( michael@0: _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT, michael@0: options.get(_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT), michael@0: _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION) michael@0: michael@0: dispatcher = dispatch.Dispatcher( michael@0: handler_root, handler_scan, allow_handlers_outside_root) michael@0: michael@0: for warning in dispatcher.source_warnings(): michael@0: apache.log_error('mod_pywebsocket: %s' % warning, apache.APLOG_WARNING) michael@0: michael@0: return dispatcher michael@0: michael@0: michael@0: # Initialize michael@0: _dispatcher = _create_dispatcher() michael@0: michael@0: michael@0: def headerparserhandler(request): michael@0: """Handle request. michael@0: michael@0: Args: michael@0: request: mod_python request. michael@0: michael@0: This function is named headerparserhandler because it is the default michael@0: name for a PythonHeaderParserHandler. michael@0: """ michael@0: michael@0: handshake_is_done = False michael@0: try: michael@0: # Fallback to default http handler for request paths for which michael@0: # we don't have request handlers. michael@0: if not _dispatcher.get_handler_suite(request.uri): michael@0: request.log_error('No handler for resource: %r' % request.uri, michael@0: apache.APLOG_INFO) michael@0: request.log_error('Fallback to Apache', apache.APLOG_INFO) michael@0: return apache.DECLINED michael@0: except dispatch.DispatchException, e: michael@0: request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) michael@0: if not handshake_is_done: michael@0: return e.status michael@0: michael@0: try: michael@0: allow_draft75 = _parse_option( michael@0: _PYOPT_ALLOW_DRAFT75, michael@0: apache.main_server.get_options().get(_PYOPT_ALLOW_DRAFT75), michael@0: _PYOPT_ALLOW_DRAFT75_DEFINITION) michael@0: michael@0: try: michael@0: handshake.do_handshake( michael@0: request, _dispatcher, allowDraft75=allow_draft75) michael@0: except handshake.VersionException, e: michael@0: request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) michael@0: request.err_headers_out.add(common.SEC_WEBSOCKET_VERSION_HEADER, michael@0: e.supported_versions) michael@0: return apache.HTTP_BAD_REQUEST michael@0: except handshake.HandshakeException, e: michael@0: # Handshake for ws/wss failed. michael@0: # Send http response with error status. michael@0: request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) michael@0: return e.status michael@0: michael@0: handshake_is_done = True michael@0: request._dispatcher = _dispatcher michael@0: _dispatcher.transfer_data(request) michael@0: except handshake.AbortedByUserException, e: michael@0: request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) michael@0: except Exception, e: michael@0: # DispatchException can also be thrown if something is wrong in michael@0: # pywebsocket code. It's caught here, then. michael@0: michael@0: request.log_error('mod_pywebsocket: %s\n%s' % michael@0: (e, util.get_stack_trace()), michael@0: apache.APLOG_ERR) michael@0: # Unknown exceptions before handshake mean Apache must handle its michael@0: # request with another handler. michael@0: if not handshake_is_done: michael@0: return apache.DECLINED michael@0: # Set assbackwards to suppress response header generation by Apache. michael@0: request.assbackwards = 1 michael@0: return apache.DONE # Return DONE such that no other handlers are invoked. michael@0: michael@0: michael@0: # vi:sts=4 sw=4 et