Robert Brewer avatar Robert Brewer committed 930f6b5

Initial broken foray into sendfile madness. The benchmark hangs for some odd reason.

Comments (0)

Files changed (5)

cherrypy/_cptools.py

         return handle_func
     
     def _wrapper(self, **kwargs):
+        """If self.callable returns True, turn off any other handler."""
         if self.callable(**kwargs):
             cherrypy.request.handler = None
     

cherrypy/_cpwsgi.py

             # may raise its own error at that point).
             s, h, b = _cperror.bare_error()
             self.iter_response = iter(b)
+        
+        if isinstance(b, self.environ['wsgi.file_wrapper']):
+            self.file_response = b
     
     def iredirect(self, path, query_string):
         """Doctor self.environ and perform an internal redirect.
         You probably shouldn't call this; call self.__call__ instead,
         so that any WSGI middleware in self.pipeline can run first.
         """
-        return self.response_class(environ, start_response, self.cpapp)
+        r_iter = self.response_class(environ, start_response, self.cpapp)
+        if getattr(r_iter, 'file_response', None) is not None:
+            # Return the file_wrapper object directly instead
+            return r_iter.file_response
+        return r_iter
     
     def __call__(self, environ, start_response):
         head = self.head

cherrypy/lib/static.py

         response.headers["Content-Disposition"] = cd
     
     # Set Content-Length and use an iterable (file object)
-    #   this way CP won't load the whole file in memory
+    # this way CP won't load the whole file in memory
+    # (assuming response.stream is True and no other
+    # code collapses the body).
     c_len = st.st_size
     bodyfile = open(path, 'rb')
+    fw = getattr(cherrypy.request, 'wsgi_environ', {}).get('wsgi.file_wrapper')
+    if fw is not None:
+        # TODO: support Content-Range
+        response.headers['Content-Length'] = c_len
+        response.body = fw(bodyfile)
+        print ".",
+        return response.body
     
     # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code
     if cherrypy.request.protocol >= (1, 1):

cherrypy/test/benchmark.py

     print_report(thread_report())
     
     print
-    print ("Client Thread Report (1000 requests, 14 bytes via staticdir, "
+    print ("Client Thread Report (1000 requests, 1500 bytes via staticdir, "
            "%s server threads):" % cherrypy.server.thread_pool)
-    print_report(thread_report("%s/static/index.html" % SCRIPT_NAME))
+    print_report(thread_report("%s/static/index2.html" % SCRIPT_NAME))
     
-    print
-    print ("Size Report (1000 requests, 50 client threads, "
-           "%s server threads):" % cherrypy.server.thread_pool)
-    print_report(size_report())
+#    print
+#    print ("Size Report (1000 requests, 50 client threads, "
+#           "%s server threads):" % cherrypy.server.thread_pool)
+#    print_report(size_report())
 
 
 #                         modpython and other WSGI                         #

cherrypy/wsgiserver/__init__.py

         
         response = self.wsgi_app(self.environ, self.start_response)
         try:
-            for chunk in response:
-                # "The start_response callable must not actually transmit
-                # the response headers. Instead, it must store them for the
-                # server or gateway to transmit only after the first
-                # iteration of the application return value that yields
-                # a NON-EMPTY string, or upon the application's first
-                # invocation of the write() callable." (PEP 333)
-                if chunk:
-                    self.write(chunk)
+            if isinstance(response, FileWrapper) and hasattr(response, '_transmit'):
+                if not self.sent_headers:
+                    self.sent_headers = True
+                    self.send_headers()
+                    # TODO: support chunked write
+                response._transmit(self.wfile._sock)
+            else:
+                for chunk in response:
+                    # "The start_response callable must not actually transmit
+                    # the response headers. Instead, it must store them for the
+                    # server or gateway to transmit only after the first
+                    # iteration of the application return value that yields
+                    # a NON-EMPTY string, or upon the application's first
+                    # invocation of the write() callable." (PEP 333)
+                    if chunk:
+                        self.write(chunk)
         finally:
             if hasattr(response, "close"):
                 response.close()
         
         self.environ.update(ssl_environ)
 
+class FileWrapper(object):
+
+    def __init__(self, filelike, blksize=8192):
+        self.filelike = filelike
+        self.blksize = blksize
+    
+    def close(self):
+        if hasattr(self.filelike, 'close'):
+            self.filelike.close()
+    
+    def __iter__(self):
+        return self
+    
+    def next(self):
+        data = self.filelike.read(self.blksize)
+        if data:
+            return data
+        raise StopIteration
+#HTTPConnection.environ['wsgi.file_wrapper'] = FileWrapper
+    
+try:
+    import sendfile
+except ImportError:
+    pass
+else:
+    class SendFileWrapper(FileWrapper):
+    
+        def _transmit(self, socket):
+            fd = self.filelike.fileno()
+            filelen = os.fstat(fd).st_size
+            offset = 0
+            while offset < filelen:
+                try:
+                    pos, sent = sendfile.sendfile(
+                        socket.fileno(), fd, offset, 
+                        self.blksize)
+                    offset += sent
+                except socket.error, e:
+                    if e.args[0] not in socket_errors_nonblocking:
+                        raise
+                    print format_exc()
+#    HTTPConnection.environ['wsgi.file_wrapper'] = SendFileWrapper
+
+
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.