def test_scan_sub_dir(self): disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, _TEST_HANDLERS_SUB_DIR) self.assertEqual(2, len(disp._handler_suite_map)) self.failIf('/origin_check' in disp._handler_suite_map) self.failUnless( '/sub/exception_in_transfer' in disp._handler_suite_map) self.failUnless('/sub/plain' in disp._handler_suite_map)
def test_scan_dir(self): disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) self.assertEqual(4, len(disp._handler_suite_map)) self.assertTrue('/origin_check' in disp._handler_suite_map) self.assertTrue( '/sub/exception_in_transfer' in disp._handler_suite_map) self.assertTrue('/sub/plain' in disp._handler_suite_map)
def _create_dispatcher(): _LOGGER.info('Initializing Dispatcher') options = apache.main_server.get_options() handler_root = options.get(_PYOPT_HANDLER_ROOT, None) if not handler_root: raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT, apache.APLOG_ERR) handler_scan = options.get(_PYOPT_HANDLER_SCAN, handler_root) allow_handlers_outside_root = _parse_option( _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT, options.get(_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT), _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION) dispatcher = dispatch.Dispatcher( handler_root, handler_scan, allow_handlers_outside_root) for warning in dispatcher.source_warnings(): apache.log_error( 'mod_pywebsocket: Warning in source loading: %s' % warning, apache.APLOG_WARNING) return dispatcher
def __init__(self, server_address, transfer_data, RequestHandlerClass): """Override SocketServer.TCPServer.__init__ to set SSL enabled socket object to self.socket before server_bind and server_activate, if necessary. """ options = Options() options.use_tls = False options.cgi_directories = [] options.is_executable_method = None options.dispatcher = dispatch.Dispatcher('.',None) options.dispatcher._handler_suite_map['/echo'] = dispatch._HandlerSuite(self.extra_handshake, self.web_socket_transfer_data, self.closing_handshake) # add an echo handler for testing purposes options.dispatcher._handler_suite_map['/'] = dispatch._HandlerSuite(self.extra_handshake, transfer_data, self.closing_handshake) # add the supplied transfer method as the default handler options.allow_draft75 = True options.strict = False self.request_queue_size = 128 self.__ws_is_shut_down = threading.Event() self.__ws_serving = False SocketServer.BaseServer.__init__( self, server_address, RequestHandlerClass) # Expose the options object to allow handler objects access it. We name # it with websocket_ prefix to avoid conflict. self.websocket_server_options = options self._create_sockets() self.server_bind() self.server_activate()
def test_transfer_data(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) request.ws_resource = '/origin_check' request.ws_protocol = 'p1' dispatcher.transfer_data(request) self.assertEqual( 'origin_check_wsh.py is called for /origin_check, p1' '\xff\x00', request.connection.written_data()) request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) request.ws_resource = '/sub/plain' request.ws_protocol = None dispatcher.transfer_data(request) self.assertEqual( 'sub/plain_wsh.py is called for /sub/plain, None' '\xff\x00', request.connection.written_data()) request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) request.ws_resource = '/sub/plain?' request.ws_protocol = None dispatcher.transfer_data(request) self.assertEqual( 'sub/plain_wsh.py is called for /sub/plain?, None' '\xff\x00', request.connection.written_data()) request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) request.ws_resource = '/sub/plain?q=v' request.ws_protocol = None dispatcher.transfer_data(request) self.assertEqual( 'sub/plain_wsh.py is called for /sub/plain?q=v, None' '\xff\x00', request.connection.written_data())
def test_scan_sub_dir_as_root(self): disp = dispatch.Dispatcher(_TEST_HANDLERS_SUB_DIR, _TEST_HANDLERS_SUB_DIR) self.assertEqual(2, len(disp._handlers)) self.failIf(disp._handlers.has_key('/origin_check')) self.failIf(disp._handlers.has_key('/sub/exception_in_transfer')) self.failIf(disp._handlers.has_key('/sub/plain')) self.failUnless(disp._handlers.has_key('/exception_in_transfer')) self.failUnless(disp._handlers.has_key('/plain'))
def test_do_extra_handshake(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) request = mock.MockRequest() request.ws_resource = '/origin_check' request.ws_origin = 'http://example.com' dispatcher.do_extra_handshake(request) # Must not raise exception. request.ws_origin = 'http://bad.example.com' self.assertRaises(Exception, dispatcher.do_extra_handshake, request)
def test_transfer_data_handler_exception(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) request = mock.MockRequest(connection=mock.MockConn('')) request.ws_resource = '/sub/exception_in_transfer' request.ws_protocol = 'p3' try: dispatcher.transfer_data(request) self.fail() except Exception, e: self.failUnless(str(e).find('Intentional') != -1)
def test_scan_sub_dir_as_root(self): disp = dispatch.Dispatcher(_TEST_HANDLERS_SUB_DIR, _TEST_HANDLERS_SUB_DIR) self.assertEqual(2, len(disp._handler_suite_map)) self.assertFalse('/origin_check' in disp._handler_suite_map) self.assertFalse( '/sub/exception_in_transfer' in disp._handler_suite_map) self.assertFalse('/sub/plain' in disp._handler_suite_map) self.assertTrue('/exception_in_transfer' in disp._handler_suite_map) self.assertTrue('/plain' in disp._handler_suite_map)
def test_resource_path_alias(self): disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) disp.add_resource_path_alias('/', '/origin_check') self.assertEqual(4, len(disp._handlers)) self.failUnless(disp._handlers.has_key('/origin_check')) self.failUnless(disp._handlers.has_key('/sub/exception_in_transfer')) self.failUnless(disp._handlers.has_key('/sub/plain')) self.failUnless(disp._handlers.has_key('/')) self.assertRaises(dispatch.DispatchError, disp.add_resource_path_alias, '/alias', '/not-exist')
def test_resource_path_alias(self): disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) disp.add_resource_path_alias('/', '/origin_check') self.assertEqual(5, len(disp._handler_suite_map)) self.failUnless('/origin_check' in disp._handler_suite_map) self.failUnless( '/sub/exception_in_transfer' in disp._handler_suite_map) self.failUnless('/sub/plain' in disp._handler_suite_map) self.failUnless('/' in disp._handler_suite_map) self.assertRaises(dispatch.DispatchException, disp.add_resource_path_alias, '/alias', '/not-exist')
def _create_dispatcher(): _HANDLER_ROOT = apache.main_server.get_options().get( _PYOPT_HANDLER_ROOT, None) if not _HANDLER_ROOT: raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT, apache.APLOG_ERR) _HANDLER_SCAN = apache.main_server.get_options().get( _PYOPT_HANDLER_SCAN, _HANDLER_ROOT) dispatcher = dispatch.Dispatcher(_HANDLER_ROOT, _HANDLER_SCAN) for warning in dispatcher.source_warnings(): apache.log_error('mod_pywebsocket: %s' % warning, apache.APLOG_WARNING) return dispatcher
def test_do_extra_handshake(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) request = mock.MockRequest() request.ws_resource = '/origin_check' request.ws_origin = 'http://example.com' dispatcher.do_extra_handshake(request) # Must not raise exception. request.ws_origin = 'http://bad.example.com' try: dispatcher.do_extra_handshake(request) self.fail('Could not catch HandshakeException with 403 status') except handshake.HandshakeException, e: self.assertEquals(403, e.status)
def test_transfer_data_no_handler(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) for resource in ['/blank', '/sub/non_callable', '/sub/no_wsh_at_the_end', '/does/not/exist']: request = mock.MockRequest(connection=mock.MockConn('')) request.ws_resource = resource request.ws_protocol = 'p2' try: dispatcher.transfer_data(request) self.fail() except dispatch.DispatchError, e: self.failUnless(str(e).find('No handler') != -1) except Exception: self.fail()
def test_source_warnings(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) warnings = dispatcher.source_warnings() warnings.sort() expected_warnings = [ (os.path.join(_TEST_HANDLERS_DIR, 'blank_wsh.py') + ': web_socket_do_extra_handshake is not defined.'), (os.path.join(_TEST_HANDLERS_DIR, 'sub', 'non_callable_wsh.py') + ': web_socket_do_extra_handshake is not callable.'), (os.path.join(_TEST_HANDLERS_DIR, 'sub', 'wrong_handshake_sig_wsh.py') + ': web_socket_do_extra_handshake is not defined.'), (os.path.join(_TEST_HANDLERS_DIR, 'sub', 'wrong_transfer_sig_wsh.py') + ': web_socket_transfer_data is not defined.'), ] self.assertEquals(4, len(warnings)) for expected, actual in zip(expected_warnings, warnings): self.assertEquals(expected, actual)
def __init__(self, options): """Override SocketServer.TCPServer.__init__ to set SSL enabled socket object to self.socket before server_bind and server_activate, if necessary. """ print "Starting Server..." # Share a Dispatcher among request handlers to save time for # instantiation. Dispatcher can be shared because it is thread-safe. options.dispatcher = dispatch.Dispatcher( options.websock_handlers, options.scan_dir, options.allow_handlers_outside_root_dir) if options.websock_handlers_map_file: _alias_handlers(options.dispatcher, options.websock_handlers_map_file) warnings = options.dispatcher.source_warnings() if warnings: for warning in warnings: logging.warning('Warning in source loading: %s' % warning) self._logger = util.get_class_logger(self) self.request_queue_size = options.request_queue_size self.__ws_is_shut_down = threading.Event() self.__ws_serving = False SocketServer.BaseServer.__init__(self, (options.server_host, options.port), WebSocketRequestHandler) # Expose the options object to allow handler objects access it. We name # it with websocket_ prefix to avoid conflict. self.websocket_server_options = options self._create_sockets() self.server_bind() self.server_activate()
def _main(args=None): options, args = _parse_args_and_config(args=args) os.chdir(options.document_root) _configure_logging(options) # TODO(tyoshino): Clean up initialization of CGI related values. Move some # of code here to WebSocketRequestHandler class if it's better. options.cgi_directories = [] options.is_executable_method = None if options.cgi_paths: options.cgi_directories = options.cgi_paths.split(',') if sys.platform in ('cygwin', 'win32'): cygwin_path = None # For Win32 Python, it is expected that CYGWIN_PATH # is set to a directory of cygwin binaries. # For example, websocket_server.py in Chromium sets CYGWIN_PATH to # full path of third_party/cygwin/bin. if 'CYGWIN_PATH' in os.environ: cygwin_path = os.environ['CYGWIN_PATH'] util.wrap_popen3_for_win(cygwin_path) def __check_script(scriptpath): return util.get_script_interp(scriptpath, cygwin_path) options.is_executable_method = __check_script if options.use_tls: if not (_HAS_SSL or _HAS_OPEN_SSL): logging.critical('TLS support requires ssl or pyOpenSSL module.') sys.exit(1) if not options.private_key or not options.certificate: logging.critical( 'To use TLS, specify private_key and certificate.') sys.exit(1) if options.tls_client_auth: if not options.use_tls: logging.critical('TLS must be enabled for client authentication.') sys.exit(1) if not _HAS_SSL: logging.critical('Client authentication requires ssl module.') if not options.scan_dir: options.scan_dir = options.websock_handlers if options.use_basic_auth: options.basic_auth_credential = 'Basic ' + base64.b64encode( options.basic_auth_credential) try: if options.thread_monitor_interval_in_sec > 0: # Run a thread monitor to show the status of server threads for # debugging. ThreadMonitor(options.thread_monitor_interval_in_sec).start() # Share a Dispatcher among request handlers to save time for # instantiation. Dispatcher can be shared because it is thread-safe. options.dispatcher = dispatch.Dispatcher( options.websock_handlers, options.scan_dir, options.allow_handlers_outside_root_dir) if options.websock_handlers_map_file: _alias_handlers(options.dispatcher, options.websock_handlers_map_file) warnings = options.dispatcher.source_warnings() if warnings: for warning in warnings: logging.warning('mod_pywebsocket: %s' % warning) server = WebSocketServer(options) server.serve_forever() except Exception, e: logging.critical('mod_pywebsocket: %s' % e) logging.critical('mod_pywebsocket: %s' % util.get_stack_trace()) sys.exit(1)
def _stream_ws_thread(self, stream_id, queue): frame = queue.get(True, None) if frame is None: return rfile, wfile = os.pipe() rfile, wfile = os.fdopen(rfile, 'rb'), os.fdopen( wfile, 'wb', 0) # needs to be unbuffer for websockets stream_handler = H2HandlerCopy(self, frame, rfile) h2request = H2Request(stream_handler) h2response = H2Response(stream_handler, h2request) dispatcher = dispatch.Dispatcher(self.server.ws_doc_root, None, False) if not dispatcher.get_handler_suite(stream_handler.path): h2response.set_error(404) h2response.write() return request_wrapper = _WebSocketRequest(stream_handler, h2response) handshaker = WsH2Handshaker(request_wrapper, dispatcher) try: handshaker.do_handshake() except HandshakeException as e: self.logger.info('Handshake failed for error: %s' % e) h2response.set_error(e.status) h2response.write() return except AbortedByUserException: h2response.write() return # h2 Handshaker prepares the headers but does not send them down the # wire. Flush the headers here. try: h2response.write_status_headers() except StreamClosedError: # work around https://github.com/web-platform-tests/wpt/issues/27786 # The stream was already closed. return request_wrapper._dispatcher = dispatcher # we need two threads: # - one to handle the frame queue # - one to handle the request (dispatcher.transfer_data is blocking) # the alternative is to have only one (blocking) thread. That thread # will call transfer_data. That would require a special case in # handle_one_request, to bypass the queue and write data to wfile # directly. t = threading.Thread( target=Http2WebTestRequestHandler._stream_ws_sub_thread, args=(self, request_wrapper, stream_handler, queue)) t.start() while not self.close_connection: try: frame = queue.get(True, 1) except Empty: continue if isinstance(frame, DataReceived): wfile.write(frame.data) if frame.stream_ended: raise NotImplementedError("frame.stream_ended") wfile.close() elif frame is None or isinstance( frame, (StreamReset, StreamEnded, ConnectionTerminated)): self.logger.debug('(%s - %s) Stream Reset, Thread Closing' % (self.uid, stream_id)) break t.join()
def test_scan_dir(self): disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) self.assertEqual(3, len(disp._handlers)) self.failUnless(disp._handlers.has_key('/origin_check')) self.failUnless(disp._handlers.has_key('/sub/exception_in_transfer')) self.failUnless(disp._handlers.has_key('/sub/plain'))
def test_scan_dir_must_under_root(self): dispatch.Dispatcher('a/b', 'a/b/c') # OK dispatch.Dispatcher('a/b///', 'a/b') # OK self.assertRaises(dispatch.DispatchException, dispatch.Dispatcher, 'a/b/c', 'a/b')
def _main(): parser = optparse.OptionParser() parser.add_option('-H', '--server-host', '--server_host', dest='server_host', default='', help='server hostname to listen to') parser.add_option('-p', '--port', dest='port', type='int', default=handshake.DEFAULT_WEB_SOCKET_PORT, help='port to listen to') parser.add_option('-w', '--websock-handlers', '--websock_handlers', dest='websock_handlers', default='.', help='Web Socket handlers root directory.') parser.add_option('-m', '--websock-handlers-map-file', '--websock_handlers_map_file', dest='websock_handlers_map_file', default=None, help=('Web Socket handlers map file. ' 'Each line consists of alias_resource_path and ' 'existing_resource_path, separated by spaces.')) parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir', default=None, help=('Web Socket handlers scan directory. ' 'Must be a directory under websock_handlers.')) parser.add_option('-d', '--document-root', '--document_root', dest='document_root', default='.', help='Document root directory.') parser.add_option('-x', '--cgi-paths', '--cgi_paths', dest='cgi_paths', default=None, help=('CGI paths relative to document_root.' 'Comma-separated. (e.g -x /cgi,/htbin) ' 'Files under document_root/cgi_path are handled ' 'as CGI programs. Must be executable.')) parser.add_option('-t', '--tls', dest='use_tls', action='store_true', default=False, help='use TLS (wss://)') parser.add_option('-k', '--private-key', '--private_key', dest='private_key', default='', help='TLS private key file.') parser.add_option('-c', '--certificate', dest='certificate', default='', help='TLS certificate file.') parser.add_option('-l', '--log-file', '--log_file', dest='log_file', default='', help='Log file.') parser.add_option('--log-level', '--log_level', type='choice', dest='log_level', default='warn', choices=['debug', 'info', 'warn', 'error', 'critical'], help='Log level.') parser.add_option('--log-max', '--log_max', dest='log_max', type='int', default=_DEFAULT_LOG_MAX_BYTES, help='Log maximum bytes') parser.add_option('--log-count', '--log_count', dest='log_count', type='int', default=_DEFAULT_LOG_BACKUP_COUNT, help='Log backup count') parser.add_option('--allow-draft75', dest='allow_draft75', action='store_true', default=False, help='Allow draft 75 handshake') parser.add_option('--strict', dest='strict', action='store_true', default=False, help='Strictly check handshake request') parser.add_option('-q', '--queue', dest='request_queue_size', type='int', default=_DEFAULT_REQUEST_QUEUE_SIZE, help='request queue size') options = parser.parse_args()[0] os.chdir(options.document_root) _configure_logging(options) SocketServer.TCPServer.request_queue_size = options.request_queue_size CGIHTTPServer.CGIHTTPRequestHandler.cgi_directories = [] if options.cgi_paths: CGIHTTPServer.CGIHTTPRequestHandler.cgi_directories = \ options.cgi_paths.split(',') if sys.platform in ('cygwin', 'win32'): cygwin_path = None # For Win32 Python, it is expected that CYGWIN_PATH # is set to a directory of cygwin binaries. # For example, websocket_server.py in Chromium sets CYGWIN_PATH to # full path of third_party/cygwin/bin. if 'CYGWIN_PATH' in os.environ: cygwin_path = os.environ['CYGWIN_PATH'] util.wrap_popen3_for_win(cygwin_path) def __check_script(scriptpath): return util.get_script_interp(scriptpath, cygwin_path) CGIHTTPServer.executable = __check_script if options.use_tls: if not _HAS_OPEN_SSL: logging.critical('To use TLS, install pyOpenSSL.') sys.exit(1) if not options.private_key or not options.certificate: logging.critical( 'To use TLS, specify private_key and certificate.') sys.exit(1) if not options.scan_dir: options.scan_dir = options.websock_handlers try: # Share a Dispatcher among request handlers to save time for # instantiation. Dispatcher can be shared because it is thread-safe. options.dispatcher = dispatch.Dispatcher(options.websock_handlers, options.scan_dir) if options.websock_handlers_map_file: _alias_handlers(options.dispatcher, options.websock_handlers_map_file) _print_warnings_if_any(options.dispatcher) WebSocketRequestHandler.options = options WebSocketServer.options = options server = WebSocketServer((options.server_host, options.port), WebSocketRequestHandler) server.serve_forever() except Exception, e: logging.critical(str(e)) sys.exit(1)
def test_abort_transfer_data(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) request = mock.MockRequest() request.ws_resource = '/abort_by_user' self.assertRaises(handshake.AbortedByUserException, dispatcher.transfer_data, request)