Robert Brewer avatar Robert Brewer committed 53b7cd7

gateways: integrated the new native gateway into the test suite.

Comments (0)

Files changed (9)

cherrypy/_cpnative_server.py

         for seg in body:
             req.write(seg)
 
+
+class CPHTTPServer(wsgiserver.HTTPServer):
+    """Wrapper for wsgiserver.HTTPServer.
+    
+    wsgiserver has been designed to not reference CherryPy in any way,
+    so that it can be used in other frameworks and applications.
+    Therefore, we wrap it here, so we can apply some attributes
+    from config -> cherrypy.server -> HTTPServer.
+    """
+    
+    def __init__(self, server_adapter=cherrypy.server):
+        self.server_adapter = server_adapter
+        
+        server_name = (self.server_adapter.socket_host or
+                       self.server_adapter.socket_file or
+                       None)
+        
+        wsgiserver.HTTPServer __init__(
+            self, server_adapter.bind_addr, NativeGateway,
+            minthreads=server_adapter.thread_pool,
+            maxthreads=server_adapter.thread_pool_max,
+            server_name=server_name):
+        
+        self.max_request_header_size = self.server_adapter.max_request_header_size or 0
+        self.max_request_body_size = self.server_adapter.max_request_body_size or 0
+        self.request_queue_size = self.server_adapter.socket_queue_size
+        self.timeout = self.server_adapter.socket_timeout
+        self.shutdown_timeout = self.server_adapter.shutdown_timeout
+        self.protocol = self.server_adapter.protocol_version
+        self.nodelay = self.server_adapter.nodelay
+        
+        ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
+        if self.server_adapter.ssl_context:
+            adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
+            self.ssl_adapter = adapter_class(
+                self.server_adapter.ssl_certificate,
+                self.server_adapter.ssl_private_key,
+                self.server_adapter.ssl_certificate_chain)
+            self.ssl_adapter.context = self.server_adapter.ssl_context
+        elif self.server_adapter.ssl_certificate:
+            adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
+            self.ssl_adapter = adapter_class(
+                self.server_adapter.ssl_certificate,
+                self.server_adapter.ssl_private_key,
+                self.server_adapter.ssl_certificate_chain)
+
+

cherrypy/_cpwsgi.py

     """Return a new environ dict for WSGI 1.0 from the given WSGI u.0 environ."""
     env10 = {}
     
-    enc = environ[u'wsgi.url_encoding']
-    for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
-        env10[str(key)] = environ[key].encode(enc)
-    
+    url_encoding = environ[u'wsgi.url_encoding']
     for k, v in environ.items():
         if k in [u'PATH_INFO', u'SCRIPT_NAME', u'QUERY_STRING']:
-            continue
-        if isinstance(v, unicode) and k not in [u'REQUEST_URI', u'wsgi.input']:
+            v = v.encode(url_encoding)
+        elif isinstance(v, unicode):
             v = v.encode('ISO-8859-1')
         env10[k.encode('ISO-8859-1')] = v
     
     def __init__(self, environ, start_response, cpapp, recursive=False):
         self.redirections = []
         self.recursive = recursive
-        if environ.get(u'wsgi.version') == ('u', 0):
+        if environ.get(u'wsgi.version') == (u'u', 0):
             environ = downgrade_wsgi_u0_to_10(environ)
         self.environ = environ
         self.start_response = start_response

cherrypy/_cpwsgi_server.py

         self.protocol = self.server_adapter.protocol_version
         self.nodelay = self.server_adapter.nodelay
         
+        ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
         if self.server_adapter.ssl_context:
-            adapter_class = self.get_ssl_adapter_class()
+            adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
             s.ssl_adapter = adapter_class(self.server_adapter.ssl_certificate,
                                           self.server_adapter.ssl_private_key,
                                           self.server_adapter.ssl_certificate_chain)
             s.ssl_adapter.context = self.server_adapter.ssl_context
         elif self.server_adapter.ssl_certificate:
-            adapter_class = self.get_ssl_adapter_class()
+            adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
             s.ssl_adapter = adapter_class(self.server_adapter.ssl_certificate,
                                           self.server_adapter.ssl_private_key,
                                           self.server_adapter.ssl_certificate_chain)
-    
-    def get_ssl_adapter_class(self):
-        adname = (self.server_adapter.ssl_module or 'pyopenssl').lower()
-        adapter = ssl_adapters[adname]
-        if isinstance(adapter, basestring):
-            last_dot = adapter.rfind(".")
-            attr_name = adapter[last_dot + 1:]
-            mod_path = adapter[:last_dot]
-            
-            try:
-                mod = sys.modules[mod_path]
-                if mod is None:
-                    raise KeyError()
-            except KeyError:
-                # The last [''] is important.
-                mod = __import__(mod_path, globals(), locals(), [''])
-            
-            # Let an AttributeError propagate outward.
-            try:
-                adapter = getattr(mod, attr_name)
-            except AttributeError:
-                raise AttributeError("'%s' object has no attribute '%s'"
-                                     % (mod_path, attr_name))
-        
-        return adapter
 
-# These may either be wsgiserver.SSLAdapter subclasses or the string names
-# of such classes (in which case they will be lazily loaded).
-ssl_adapters = {
-    'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
-    'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
-    }
 

cherrypy/test/helper.py

 CPTestRunner = webtest.TerseTestRunner(verbosity=2)
 
 
-def run_test_suite(moduleNames, conf, server):
-    """Run the given test modules using the given server and [global] conf.
+def run_test_suite(moduleNames, conf, supervisor):
+    """Run the given test modules using the given supervisor and [global] conf.
     
-    The 'server' arg should be an object with 'start' and 'stop' methods.
+    The 'supervisor' arg should be an object with 'start' and 'stop' methods.
     See test/test.py.
     """
     # The Pybots automatic testing system needs the suite to exit
     for testmod in moduleNames:
         cherrypy.config.reset()
         cherrypy.config.update(conf)
-        setup_client()
+        setup_client(supervisor)
         
         if '.' in testmod:
             package, test_name = testmod.rsplit('.', 1)
         suite = CPTestLoader.loadTestsFromName(testmod)
         
         setup = getattr(m, "setup_server", None)
-        if setup: server.start(testmod)
+        if setup: supervisor.start(testmod)
         try:
             result = CPTestRunner.run(suite)
             test_success &= result.wasSuccessful()
         finally:
-            if setup: server.stop()
+            if setup: supervisor.stop()
     
     if test_success:
         return 0
     else:
         return 1
 
-def setup_client():
+def setup_client(supervisor):
     """Set up the WebCase classes to match the server's socket settings."""
     webtest.WebCase.PORT = cherrypy.server.socket_port
     webtest.WebCase.HOST = cherrypy.server.socket_host
     """Run __main__ as a test module, with webtest debugging."""
     # Comment me out to see ENGINE messages etc. when running a test standalone.
     cherrypy.config.update({'environment': "test_suite"})
+    cherrypy.server.socket_host = '127.0.0.1'
     
-    cherrypy.server.socket_host = '127.0.0.1'
-    setup_client()
-    
-    from cherrypy.test.test import LocalServer
-    server = LocalServer(cherrypy.server.socket_host, cherrypy.server.socket_port,
-                         False, False, False)
-    server.start('__main__')
+    from cherrypy.test.test import LocalWSGISupervisor
+    supervisor = LocalWSGISupervisor(host=cherrypy.server.socket_host,
+                                     port=cherrypy.server.socket_port)
+    setup_client(supervisor)
+    supervisor.start('__main__')
     try:
         return webtest.main()
     finally:
-        server.stop()
+        supervisor.stop()
 
 
 

cherrypy/test/modfcgid.py

 FastCgiExternalServer "%(server)s" -host 127.0.0.1:4000
 """
 
-class ServerControl(test.LocalServer):
+class ModFCGISupervisor(test.LocalSupervisor):
     
+    using_apache = True
+    using_wsgi = True
     template = conf_fcgid
     
     def __str__(self):

cherrypy/test/modpy.py

 PythonDebug On
 """
 
-class ServerControl(object):
+class ModPythonSupervisor(test.Supervisor):
     
-    def __init__(self, host, port, template):
-        self.host = host
-        self.port = port
-        self.template = template
+    using_apache = True
+    using_wsgi = False
+    template = None
     
     def __str__(self):
         return "ModPython Server on %s:%s" % (self.host, self.port)

cherrypy/test/modwsgi.py

 """
 
 
-class ServerControl(object):
+class ModWSGISupervisor(test.Supervisor):
     """Server Controller for ModWSGI and CherryPy."""
     
-    def __init__(self, host, port, template=conf_modwsgi):
-        self.host = host
-        self.port = port
-        self.template = template
+    using_apache = True
+    using_wsgi = True
+    template=conf_modwsgi
     
     def __str__(self):
         return "ModWSGI Server on %s:%s" % (self.host, self.port)

cherrypy/test/test.py

 class TestHarness(object):
     """A test harness for the CherryPy framework and CherryPy applications."""
     
-    def __init__(self, tests=None, server=None, protocol="HTTP/1.1",
-                 port=8000, scheme="http", interactive=True, host='127.0.0.1'):
+    def __init__(self, supervisor, tests, interactive=True):
         """Constructor to populate the TestHarness instance.
         
         tests should be a list of module names (strings).
         """
-        self.tests = tests or []
-        self.server = server
-        self.protocol = protocol
-        self.port = port
-        self.host = host
-        self.scheme = scheme
+        self.supervisor = supervisor
+        self.tests = tests
         self.interactive = interactive
     
     def run(self, conf=None):
         v = sys.version.split()[0]
         print("Python version used to run this test script: %s" % v)
         print("CherryPy version: %s" % cherrypy.__version__)
-        if self.scheme == "https":
+        if self.supervisor.scheme == "https":
             ssl = " (ssl)"
         else:
             ssl = ""
-        print("HTTP server version: %s%s" % (self.protocol, ssl))
+        print("HTTP server version: %s%s" % (self.supervisor.protocol, ssl))
         print("PID: %s" % os.getpid())
         print("")
         
+        cherrypy.server.using_apache = self.supervisor.using_apache
+        cherrypy.server.using_wsgi = self.supervisor.using_wsgi
+        
         if isinstance(conf, basestring):
             parser = cherrypy.config._Parser()
             conf = parser.dict_from_file(conf).get('global', {})
         else:
             conf = conf or {}
         baseconf = conf.copy()
-        baseconf.update({'server.socket_host': self.host,
-                         'server.socket_port': self.port,
-                         'server.protocol_version': self.protocol,
+        baseconf.update({'server.socket_host': self.supervisor.host,
+                         'server.socket_port': self.supervisor.port,
+                         'server.protocol_version': self.supervisor.protocol,
                          'environment': "test_suite",
                          })
-        if self.scheme == "https":
+        if self.supervisor.scheme == "https":
             baseconf['server.ssl_certificate'] = serverpem
             baseconf['server.ssl_private_key'] = serverpem
         
         # and we wouldn't be able to globally override the port anymore.
         from cherrypy.test import helper, webtest
         webtest.WebCase.interactive = self.interactive
-        if self.scheme == "https":
+        if self.supervisor.scheme == "https":
             webtest.WebCase.HTTP_CONN = HTTPSConnection
         print("")
-        print("Running tests: %s" % self.server)
+        print("Running tests: %s" % self.supervisor)
         
-        return helper.run_test_suite(self.tests, baseconf, self.server)
+        return helper.run_test_suite(self.tests, baseconf, self.supervisor)
 
 
-class LocalServer(object):
-    """Server Controller for the builtin WSGI server."""
+class Supervisor(object):
+    """Base class for modeling and controlling servers during testing."""
     
-    def __init__(self, host, port, profile, validate, conquer):
-        self.host = host
-        self.port = port
-        self.profile = profile
-        self.validate = validate
-        self.conquer = conquer
+    def __init__(self, **kwargs):
+        for k, v in kwargs.iteritems():
+            setattr(self, k, v)
+
+
+class LocalSupervisor(Supervisor):
+    """Base class for modeling/controlling servers which run in the same process.
     
-    def __str__(self):
-        return "Builtin WSGI Server on %s:%s" % (self.host, self.port)
+    When the server side runs in a different process, start/stop can dump all
+    state between each test module easily. When the server side runs in the
+    same process as the client, however, we have to do a bit more work to ensure
+    config and mounted apps are reset between tests.
+    """
+    
+    using_apache = False
+    using_wsgi = False
+    
+    def __init__(self, **kwargs):
+        for k, v in kwargs.iteritems():
+            setattr(self, k, v)
+        
+        import cherrypy
+        cherrypy.server.httpserver = self.httpserver_class
+        
+        engine = cherrypy.engine
+        if hasattr(engine, "signal_handler"):
+            engine.signal_handler.subscribe()
+        if hasattr(engine, "console_control_handler"):
+            engine.console_control_handler.subscribe()
+        #engine.subscribe('log', lambda msg, level: sys.stderr.write(msg + os.linesep))
     
     def start(self, modulename=None):
         """Load and start the HTTP server."""
         
         cherrypy.engine.start()
         
-        # The setup functions probably mounted new apps.
-        # Tell our server about them.
         self.sync_apps()
     
+    def sync_apps(self):
+        """Tell the server about any apps which the setup functions mounted."""
+        pass
+    
     def stop(self):
         if self.teardown:
             self.teardown()
         import cherrypy
         cherrypy.engine.exit()
+
+
+class NativeServerSupervisor(LocalSupervisor):
+    """Server supervisor for the builtin HTTP server."""
+    
+    httpserver_class = "cherrypy._cpnative_server.CPHTTPServer"
+    using_apache = False
+    using_wsgi = False
+    
+    def __str__(self):
+        return "Builtin HTTP Server on %s:%s" % (self.host, self.port)
+
+
+class LocalWSGISupervisor(LocalSupervisor):
+    """Server supervisor for the builtin WSGI server."""
+    
+    httpserver_class = "cherrypy._cpwsgi_server.CPWSGIServer"
+    using_apache = False
+    using_wsgi = True
+    
+    def __str__(self):
+        return "Builtin WSGI Server on %s:%s" % (self.host, self.port)
     
     def sync_apps(self):
         """Hook a new WSGI app into the origin server."""
         return app
 
 
+def get_cpmodpy_supervisor(**options):
+    from cherrypy.test import modpy
+    sup = modpy.ModPythonSupervisor(**options)
+    sup.template = modpy.conf_cpmodpy
+    return sup
+
+def get_modpygw_supervisor(**options):
+    from cherrypy.test import modpy
+    sup = modpy.ModPythonSupervisor(**options)
+    sup.template = modpy.conf_modpython_gateway
+    sup.using_wsgi = True
+    return sup
+
+def get_modwsgi_supervisor(**options):
+    from cherrypy.test import modwsgi
+    return modwsgi.ModWSGISupervisor(**options)
+
+def get_modfcgid_supervisor(**options):
+    from cherrypy.test import modfcgid
+    return modfcgid.ModFCGISupervisor(**options)
+
+def get_wsgi_u_supervisor(**options):
+    import cherrypy
+    cherrypy.server.wsgi_version = ('u', 0)
+    return LocalWSGISupervisor(**options)
+
+
 class CommandLineParser(object):
-    available_servers = {'wsgi': "cherrypy._cpwsgi.CPWSGIServer",
-                         'cpmodpy': "cpmodpy",
-                         'modpygw': "modpygw",
-                         'modwsgi': "modwsgi",
-                         'modfcgid': "modfcgid",
+    available_servers = {'wsgi': LocalWSGISupervisor,
+                         'wsgi_u': get_wsgi_u_supervisor,
+                         'native': NativeServerSupervisor,
+                         'cpmodpy': get_cpmodpy_supervisor,
+                         'modpygw': get_modpygw_supervisor,
+                         'modwsgi': get_modwsgi_supervisor,
+                         'modfcgid': get_modfcgid_supervisor,
                          }
     default_server = "wsgi"
-    scheme = "http"
-    protocol = "HTTP/1.1"
-    port = 8080
-    host = '127.0.0.1'
+    
+    supervisor_factory = None
+    supervisor_options = {
+        'scheme': 'http',
+        'protocol': "HTTP/1.1",
+        'port': 8080,
+        'host': '127.0.0.1',
+        'profile': False,
+        'validate': False,
+        'conquer': False,
+        }
+    
     cover = False
-    profile = False
-    validate = False
-    conquer = False
-    server = None
     basedir = None
     interactive = True
     
             set of args if you like.
         """
         self.available_tests = available_tests
+        self.supervisor_options = self.supervisor_options.copy()
         
         longopts = self.longopts[:]
         longopts.extend(self.available_tests)
             elif o == "--cover":
                 self.cover = True
             elif o == "--profile":
-                self.profile = True
+                self.supervisor_options['profile'] = True
             elif o == "--validate":
-                self.validate = True
+                self.supervisor_options['validate'] = True
             elif o == "--conquer":
-                self.conquer = True
+                self.supervisor_options['conquer'] = True
             elif o == "--dumb":
                 self.interactive = False
             elif o == "--1.0":
-                self.protocol = "HTTP/1.0"
+                self.supervisor_options['protocol'] = "HTTP/1.0"
             elif o == "--ssl":
-                self.scheme = "https"
+                self.supervisor_options['scheme'] = "https"
             elif o == "--basedir":
                 self.basedir = a
             elif o == "--port":
-                self.port = int(a)
+                self.supervisor_options['port'] = int(a)
             elif o == "--host":
-                self.host = a
+                self.supervisor_options['host'] = a
             elif o == "--server":
-                if a in self.available_servers:
-                    a = self.available_servers[a]
-                self.server = a
+                if a not in self.available_servers:
+                    print('Error: The --server argument must be one of %s.' %
+                          '|'.join(self.available_servers.keys()))
+                    sys.exit(2)
+                self.supervisor_factory = self.available_servers[a]
             else:
                 o = o[2:]
                 if o in self.available_tests and o not in self.tests:
                     self.tests.append(o)
         
         import cherrypy
-        if self.cover and self.profile:
+        if self.cover and self.supervisor_options['profile']:
             # Print error message and exit
             print('Error: you cannot run the profiler and the '
                    'coverage tool at the same time.')
             sys.exit(2)
         
-        if not self.server:
-            self.server = self.available_servers[self.default_server]
+        if not self.supervisor_factory:
+            self.supervisor_factory = self.available_servers[self.default_server]
         
         if not self.tests:
             self.tests = self.available_tests[:]
         
     """ % (self.__class__.host, self.__class__.port))
         print('    * servers:')
-        for name, val in self.available_servers.items():
+        for name in self.available_servers.items():
             if name == self.default_server:
-                print('        --server=%s: %s (default)' % (name, val))
+                print('        --server=%s (default)' % name)
             else:
-                print('        --server=%s: %s' % (name, val))
+                print('        --server=%s' % name)
         
         print("""
     
     --cover: turn on code-coverage tool.
     --basedir=path: display coverage stats for some path other than cherrypy.
     
-    --profile: turn on profiling tool.
+    --profile: turn on WSGI profiling tool.
     --validate: use wsgiref.validate (builtin in Python 2.5).
     --conquer: use wsgiconq (which uses pyconquer) to trace calls.
     --dumb: turn off the interactive output features.
         if self.cover:
             self.start_coverage()
         
-        import cherrypy
-        if self.server == 'cpmodpy':
-            from cherrypy.test import modpy
-            server = modpy.ServerControl(self.host, self.port,
-                                         modpy.conf_cpmodpy)
-            cherrypy.server.using_apache = True
-            cherrypy.server.using_wsgi = False
-        elif self.server == 'modpygw':
-            from cherrypy.test import modpy
-            server = modpy.ServerControl(self.host, self.port,
-                                         modpy.conf_modpython_gateway)
-            cherrypy.server.using_apache = True
-            cherrypy.server.using_wsgi = True
-        elif self.server == 'modwsgi':
-            from cherrypy.test import modwsgi
-            server = modwsgi.ServerControl(self.host, self.port)
-            cherrypy.server.using_apache = True
-            cherrypy.server.using_wsgi = True
-        elif self.server == 'modfcgid':
-            from cherrypy.test import modfcgid
-            server = modfcgid.ServerControl(self.host, self.port, self.profile,
-                                            self.validate, self.conquer)
-            cherrypy.server.using_apache = True
-            cherrypy.server.using_wsgi = True
-        else:
-            server = LocalServer(self.host, self.port, self.profile,
-                                 self.validate, self.conquer)
-            cherrypy.server.using_apache = False
-            cherrypy.server.using_wsgi = True
-            engine = cherrypy.engine
-            if hasattr(engine, "signal_handler"):
-                engine.signal_handler.subscribe()
-            if hasattr(engine, "console_control_handler"):
-                engine.console_control_handler.subscribe()
-            #engine.subscribe('log', lambda msg, level: sys.stderr.write(msg + os.linesep))
+        supervisor = self.supervisor_factory(**self.supervisor_options)
         
-        if cherrypy.server.using_apache and 'test_conn' in self.tests:
+        if supervisor.using_apache and 'test_conn' in self.tests:
             self.tests.remove('test_conn')
         
-        h = TestHarness(self.tests, server, self.protocol, self.port,
-                        self.scheme, self.interactive, self.host)
+        h = TestHarness(supervisor, self.tests, self.interactive)
         success = h.run(conf)
         
-        if self.profile:
+        if self.supervisor_options['profile']:
             print("")
             print("run /cherrypy/lib/profiler.py as a script to serve "
                    "profiling results on port 8080")

cherrypy/wsgiserver/__init__.py

         raise NotImplemented
 
 
-class CherryPyHTTPServer(object):
+class HTTPServer(object):
     """An HTTP server.
     
     bind_addr: The interface on which to listen for connections.
     def respond(self):
         raise NotImplemented
 
+
+# These may either be wsgiserver.SSLAdapter subclasses or the string names
+# of such classes (in which case they will be lazily loaded).
+ssl_adapters = {
+    'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
+    'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
+    }
+
+def get_ssl_adapter_class(name='pyopenssl'):
+    adapter = ssl_adapters[name.lower()]
+    if isinstance(adapter, basestring):
+        last_dot = adapter.rfind(".")
+        attr_name = adapter[last_dot + 1:]
+        mod_path = adapter[:last_dot]
+        
+        try:
+            mod = sys.modules[mod_path]
+            if mod is None:
+                raise KeyError()
+        except KeyError:
+            # The last [''] is important.
+            mod = __import__(mod_path, globals(), locals(), [''])
+        
+        # Let an AttributeError propagate outward.
+        try:
+            adapter = getattr(mod, attr_name)
+        except AttributeError:
+            raise AttributeError("'%s' object has no attribute '%s'"
+                                 % (mod_path, attr_name))
+    
+    return adapter
+
 # -------------------------------- WSGI Stuff -------------------------------- #
 
 
-class CherryPyWSGIServer(CherryPyHTTPServer):
+class CherryPyWSGIServer(HTTPServer):
     
     wsgi_version = (1, 0)
     
         return env
 
 
-class WSGIGateway_u0(WSGIGateway):
+class WSGIGateway_u0(WSGIGateway_10):
     
     def get_environ(self):
         """Return a new environ dict targeting the given wsgi.version"""
         req = self.req
         env_10 = WSGIGateway_10.get_environ(self)
         env = dict([(k.decode('ISO-8859-1'), v) for k, v in env_10.iteritems()])
-        env[u'wsgi.version'] = ('u', 0),
+        env[u'wsgi.version'] = ('u', 0)
         
         # Request-URI
         env.setdefault(u'wsgi.url_encoding', u'utf-8')
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.