1 # Copyright 2011, Google Inc. 2 # All rights reserved. 3 # 4 # Redistribution and use in source and binary forms, with or without 5 # modification, are permitted provided that the following conditions are 6 # met: 7 # 8 # * Redistributions of source code must retain the above copyright 9 # notice, this list of conditions and the following disclaimer. 10 # * Redistributions in binary form must reproduce the above 11 # copyright notice, this list of conditions and the following disclaimer 12 # in the documentation and/or other materials provided with the 13 # distribution. 14 # * Neither the name of Google Inc. nor the names of its 15 # contributors may be used to endorse or promote products derived from 16 # this software without specific prior written permission. 17 # 18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 31 """PythonHeaderParserHandler for mod_pywebsocket. 32 33 Apache HTTP Server and mod_python must be configured such that this 34 function is called to handle WebSocket request. 35 """ 36 37 38 import logging 39 40 from mod_python import apache 41 42 from mod_pywebsocket import common 43 from mod_pywebsocket import dispatch 44 from mod_pywebsocket import handshake 45 from mod_pywebsocket import util 46 47 48 # PythonOption to specify the handler root directory. 49 _PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root' 50 51 # PythonOption to specify the handler scan directory. 52 # This must be a directory under the root directory. 53 # The default is the root directory. 54 _PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan' 55 56 # PythonOption to allow handlers whose canonical path is 57 # not under the root directory. It's disallowed by default. 58 # Set this option with value of 'yes' to allow. 59 _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT = ( 60 'mod_pywebsocket.allow_handlers_outside_root_dir') 61 # Map from values to their meanings. 'Yes' and 'No' are allowed just for 62 # compatibility. 63 _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION = { 64 'off': False, 'no': False, 'on': True, 'yes': True} 65 66 # PythonOption to specify to allow draft75 handshake. 67 # The default is None (Off) 68 _PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75' 69 # Map from values to their meanings. 70 _PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True} 71 72 73 class ApacheLogHandler(logging.Handler): 74 """Wrapper logging.Handler to emit log message to apache's error.log.""" 75 76 _LEVELS = { 77 logging.DEBUG: apache.APLOG_DEBUG, 78 logging.INFO: apache.APLOG_INFO, 79 logging.WARNING: apache.APLOG_WARNING, 80 logging.ERROR: apache.APLOG_ERR, 81 logging.CRITICAL: apache.APLOG_CRIT, 82 } 83 84 def __init__(self, request=None): 85 logging.Handler.__init__(self) 86 self._log_error = apache.log_error 87 if request is not None: 88 self._log_error = request.log_error 89 90 # Time and level will be printed by Apache. 91 self._formatter = logging.Formatter('%(name)s: %(message)s') 92 93 def emit(self, record): 94 apache_level = apache.APLOG_DEBUG 95 if record.levelno in ApacheLogHandler._LEVELS: 96 apache_level = ApacheLogHandler._LEVELS[record.levelno] 97 98 msg = self._formatter.format(record) 99 100 # "server" parameter must be passed to have "level" parameter work. 101 # If only "level" parameter is passed, nothing shows up on Apache's 102 # log. However, at this point, we cannot get the server object of the 103 # virtual host which will process WebSocket requests. The only server 104 # object we can get here is apache.main_server. But Wherever (server 105 # configuration context or virtual host context) we put 106 # PythonHeaderParserHandler directive, apache.main_server just points 107 # the main server instance (not any of virtual server instance). Then, 108 # Apache follows LogLevel directive in the server configuration context 109 # to filter logs. So, we need to specify LogLevel in the server 110 # configuration context. Even if we specify "LogLevel debug" in the 111 # virtual host context which actually handles WebSocket connections, 112 # DEBUG level logs never show up unless "LogLevel debug" is specified 113 # in the server configuration context. 114 # 115 # TODO(tyoshino): Provide logging methods on request object. When 116 # request is mp_request object (when used together with Apache), the 117 # methods call request.log_error indirectly. When request is 118 # _StandaloneRequest, the methods call Python's logging facility which 119 # we create in standalone.py. 120 self._log_error(msg, apache_level, apache.main_server) 121 122 123 def _configure_logging(): 124 logger = logging.getLogger() 125 # Logs are filtered by Apache based on LogLevel directive in Apache 126 # configuration file. We must just pass logs for all levels to 127 # ApacheLogHandler. 128 logger.setLevel(logging.DEBUG) 129 logger.addHandler(ApacheLogHandler()) 130 131 132 _configure_logging() 133 134 _LOGGER = logging.getLogger(__name__) 135 136 137 def _parse_option(name, value, definition): 138 if value is None: 139 return False 140 141 meaning = definition.get(value.lower()) 142 if meaning is None: 143 raise Exception('Invalid value for PythonOption %s: %r' % 144 (name, value)) 145 return meaning 146 147 148 def _create_dispatcher(): 149 _LOGGER.info('Initializing Dispatcher') 150 151 options = apache.main_server.get_options() 152 153 handler_root = options.get(_PYOPT_HANDLER_ROOT, None) 154 if not handler_root: 155 raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT, 156 apache.APLOG_ERR) 157 158 handler_scan = options.get(_PYOPT_HANDLER_SCAN, handler_root) 159 160 allow_handlers_outside_root = _parse_option( 161 _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT, 162 options.get(_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT), 163 _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION) 164 165 dispatcher = dispatch.Dispatcher( 166 handler_root, handler_scan, allow_handlers_outside_root) 167 168 for warning in dispatcher.source_warnings(): 169 apache.log_error('mod_pywebsocket: %s' % warning, apache.APLOG_WARNING) 170 171 return dispatcher 172 173 174 # Initialize 175 _dispatcher = _create_dispatcher() 176 177 178 def headerparserhandler(request): 179 """Handle request. 180 181 Args: 182 request: mod_python request. 183 184 This function is named headerparserhandler because it is the default 185 name for a PythonHeaderParserHandler. 186 """ 187 188 handshake_is_done = False 189 try: 190 # Fallback to default http handler for request paths for which 191 # we don't have request handlers. 192 if not _dispatcher.get_handler_suite(request.uri): 193 request.log_error('No handler for resource: %r' % request.uri, 194 apache.APLOG_INFO) 195 request.log_error('Fallback to Apache', apache.APLOG_INFO) 196 return apache.DECLINED 197 except dispatch.DispatchException, e: 198 request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) 199 if not handshake_is_done: 200 return e.status 201 202 try: 203 allow_draft75 = _parse_option( 204 _PYOPT_ALLOW_DRAFT75, 205 apache.main_server.get_options().get(_PYOPT_ALLOW_DRAFT75), 206 _PYOPT_ALLOW_DRAFT75_DEFINITION) 207 208 try: 209 handshake.do_handshake( 210 request, _dispatcher, allowDraft75=allow_draft75) 211 except handshake.VersionException, e: 212 request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) 213 request.err_headers_out.add(common.SEC_WEBSOCKET_VERSION_HEADER, 214 e.supported_versions) 215 return apache.HTTP_BAD_REQUEST 216 except handshake.HandshakeException, e: 217 # Handshake for ws/wss failed. 218 # Send http response with error status. 219 request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) 220 return e.status 221 222 handshake_is_done = True 223 request._dispatcher = _dispatcher 224 _dispatcher.transfer_data(request) 225 except handshake.AbortedByUserException, e: 226 request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) 227 except Exception, e: 228 # DispatchException can also be thrown if something is wrong in 229 # pywebsocket code. It's caught here, then. 230 231 request.log_error('mod_pywebsocket: %s\n%s' % 232 (e, util.get_stack_trace()), 233 apache.APLOG_ERR) 234 # Unknown exceptions before handshake mean Apache must handle its 235 # request with another handler. 236 if not handshake_is_done: 237 return apache.DECLINED 238 # Set assbackwards to suppress response header generation by Apache. 239 request.assbackwards = 1 240 return apache.DONE # Return DONE such that no other handlers are invoked. 241 242 243 # vi:sts=4 sw=4 et 244