Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 1915

Show
Ignore:
Timestamp:
03/12/08 01:39:38
Author:
fumanchu
Message:

Merging the 598-sendall branch. This fixes #598, #783, and perhaps #589.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/test/test_http.py

    r1905 r1915  
    66import httplib 
    77import cherrypy 
    8 import md5, mimetypes 
     8import mimetypes 
    99 
    1010 
     
    3939         
    4040        def post_multipart(self, file): 
    41             # compute and return md5 of posted file 
     41            """Return a summary ("a * 65536\nb * 65536") of the uploaded file.""" 
    4242            contents = file.file.read() 
    43             return md5.md5(contents).hexdigest() 
     43            summary = [] 
     44            curchar = "" 
     45            count = 0 
     46            for c in contents: 
     47                if c == curchar: 
     48                    count += 1 
     49                else: 
     50                    if count: 
     51                        summary.append("%s * %d" % (curchar, count)) 
     52                    count = 1 
     53                    curchar = c 
     54            if count: 
     55                summary.append("%s * %d" % (curchar, count)) 
     56            return ", ".join(summary) 
    4457        post_multipart.exposed = True 
    4558     
     
    5770        # will hang. Verify that CP times out the socket and responds 
    5871        # with 411 Length Required. 
    59         c = httplib.HTTPConnection("127.0.0.1:%s" % self.PORT) 
     72        if self.scheme == "https": 
     73            c = httplib.HTTPSConnection("127.0.0.1:%s" % self.PORT) 
     74        else: 
     75            c = httplib.HTTPConnection("127.0.0.1:%s" % self.PORT) 
    6076        c.request("POST", "/") 
    6177        self.assertEqual(c.getresponse().status, 411) 
    6278     
    6379    def test_post_multipart(self): 
     80        alphabet = "abcdefghijklmnopqrstuvwxyz" 
    6481        # generate file contents for a large post 
    65         contents = "abcdefghijklmnopqrstuvwxyz" * 1000000 
    66         post_md5 = md5.md5(contents).hexdigest() 
     82        contents = "".join([c * 65536 for c in alphabet]) 
    6783         
    6884        # encode as multipart form data 
     
    85101         
    86102        response_body = c.file.read() 
    87         self.assertEquals(post_md5, response_body) 
     103        self.assertEquals(", ".join(["%s * 65536" % c for c in alphabet]), 
     104                          response_body) 
    88105 
    89106 
  • trunk/cherrypy/test/test_session.py

    r1868 r1915  
    22test.prefer_parent_path() 
    33 
     4import httplib 
    45import os 
    56localDir = os.path.dirname(__file__) 
     
    190191         
    191192        def request(index): 
     193            if self.scheme == 'https': 
     194                c = httplib.HTTPSConnection('127.0.0.1:%s' % self.PORT) 
     195            else: 
     196                c = httplib.HTTPConnection('127.0.0.1:%s' % self.PORT) 
    192197            for i in xrange(request_count): 
    193                 self.getPage("/", cookies) 
     198                c.putrequest('GET', '/') 
     199                for k, v in cookies: 
     200                    c.putheader(k, v) 
     201                c.endheaders() 
     202                response = c.getresponse() 
     203                self.assertEqual(response.status, 200) 
     204                body = response.read() 
     205                self.failUnless(body.isdigit()) 
    194206                # Uncomment the following line to prove threads overlap. 
    195207##                print index, 
    196             data_dict[index] = v = int(self.body
     208            data_dict[index] = max(data_dict[index], int(body)
    197209         
    198210        # Start <request_count> requests from each of 
  • trunk/cherrypy/wsgiserver/__init__.py

    r1902 r1915  
    3737let the name "CherryPyWSGIServer" throw you; the name merely reflects 
    3838its origin, not its coupling. 
     39 
     40For those of you wanting to understand internals of this module, here's the 
     41basic call flow. The server's listening thread runs a very tight loop, 
     42sticking incoming connections onto a Queue: 
     43 
     44    server = CherryPyWSGIServer(...) 
     45    server.start() 
     46    while True: 
     47        tick() 
     48        # This blocks until a request comes in: 
     49        child = socket.accept() 
     50        conn = HTTPConnection(child, ...) 
     51        server.requests.put(conn) 
     52 
     53Worker threads are kept in a pool and poll the Queue, popping off and then 
     54handling each connection in turn. Each connection can consist of an arbitrary 
     55number of requests and their responses, so we run a nested loop: 
     56 
     57    while True: 
     58        conn = server.requests.get() 
     59        conn.communicate() 
     60        ->  while True: 
     61                req = HTTPRequest(...) 
     62                req.parse_request() 
     63                ->  # Read the Request-Line, e.g. "GET /page HTTP/1.1" 
     64                    req.rfile.readline() 
     65                    req.read_headers() 
     66                req.respond() 
     67                ->  response = wsgi_app(...) 
     68                    try: 
     69                        for chunk in response: 
     70                            if chunk: 
     71                                req.write(chunk) 
     72                    finally: 
     73                        if hasattr(response, "close"): 
     74                            response.close() 
     75                if req.close_connection: 
     76                    return 
    3977""" 
    4078 
    4179 
    4280import base64 
     81import os 
    4382import Queue 
    44 import os 
    4583import re 
    4684quoted_slash = re.compile("(?i)%2F") 
     
    203241    A single HTTP connection may consist of multiple request/response pairs. 
    204242     
    205     sendall: the 'sendall' method from the connection's fileobject. 
     243    send: the 'send' method from the connection's socket object. 
    206244    wsgi_app: the WSGI application to call. 
    207245    environ: a partial WSGI environ (server and connection entries). 
     
    236274    max_request_body_size = 0 
    237275     
    238     def __init__(self, sendall, environ, wsgi_app): 
     276    def __init__(self, send, environ, wsgi_app): 
    239277        self.rfile = environ['wsgi.input'] 
    240         self.sendall = sendall 
     278        self.send = send 
    241279        self.environ = environ.copy() 
    242280        self.wsgi_app = wsgi_app 
     
    570608            self.sendall(chunk) 
    571609     
     610    def sendall(self, data): 
     611        """Sendall for non-blocking sockets.""" 
     612        while data: 
     613            try: 
     614                bytes_sent = self.send(data) 
     615                data = data[bytes_sent:] 
     616            except socket.error, e: 
     617                if e.args[0] not in socket_errors_nonblocking: 
     618                    raise 
     619     
    572620    def send_headers(self): 
    573621        """Assert, process, and send the HTTP response message-headers.""" 
     
    643691 
    644692 
    645 def _ssl_wrap_method(method, is_reader=False): 
    646     """Wrap the given method with SSL error-trapping. 
    647      
    648     is_reader: if False (the default), EOF errors will be raised. 
    649         If True, EOF errors will return "" (to emulate normal sockets). 
    650     """ 
    651     def ssl_method_wrapper(self, *args, **kwargs): 
    652 ##        print (id(self), method, args, kwargs) 
     693class CP_fileobject(socket._fileobject): 
     694    """Faux file object attached to a socket object.""" 
     695 
     696    def recv(self, size): 
     697        return self._sock.recv(size) 
     698 
     699    def sendall(self, data): 
     700        """Sendall for non-blocking sockets.""" 
     701        while data: 
     702            try: 
     703                bytes_sent = self._sock.send(data) 
     704                data = data[bytes_sent:] 
     705            except socket.error, e: 
     706                if e.args[0] not in socket_errors_nonblocking: 
     707                    raise 
     708 
     709    def send(self, data): 
     710        return self._sock.send(data) 
     711     
     712    def flush(self): 
     713        if self._wbuf: 
     714            buffer = "".join(self._wbuf) 
     715            self._wbuf = [] 
     716            self.sendall(buffer) 
     717     
     718    def read(self, size=-1): 
     719        data = self._rbuf 
     720        if size < 0: 
     721            # Read until EOF 
     722            buffers = [] 
     723            if data: 
     724                buffers.append(data) 
     725            self._rbuf = "" 
     726            if self._rbufsize <= 1: 
     727                recv_size = self.default_bufsize 
     728            else: 
     729                recv_size = self._rbufsize 
     730             
     731            while True: 
     732                data = self.recv(recv_size) 
     733                if not data: 
     734                    break 
     735                buffers.append(data) 
     736            return "".join(buffers) 
     737        else: 
     738            # Read until size bytes or EOF seen, whichever comes first 
     739            buf_len = len(data) 
     740            if buf_len >= size: 
     741                self._rbuf = data[size:] 
     742                return data[:size] 
     743            buffers = [] 
     744            if data: 
     745                buffers.append(data) 
     746            self._rbuf = "" 
     747            while True: 
     748                left = size - buf_len 
     749                recv_size = max(self._rbufsize, left) 
     750                data = self.recv(recv_size) 
     751                if not data: 
     752                    break 
     753                buffers.append(data) 
     754                n = len(data) 
     755                if n >= left: 
     756                    self._rbuf = data[left:] 
     757                    buffers[-1] = data[:left] 
     758                    break 
     759                buf_len += n 
     760            return "".join(buffers) 
     761 
     762    def readline(self, size=-1): 
     763        data = self._rbuf 
     764        if size < 0: 
     765            # Read until \n or EOF, whichever comes first 
     766            if self._rbufsize <= 1: 
     767                # Speed up unbuffered case 
     768                assert data == "" 
     769                buffers = [] 
     770                while data != "\n": 
     771                    data = self.recv(1) 
     772                    if not data: 
     773                        break 
     774                    buffers.append(data) 
     775                return "".join(buffers) 
     776            nl = data.find('\n') 
     777            if nl >= 0: 
     778                nl += 1 
     779                self._rbuf = data[nl:] 
     780                return data[:nl] 
     781            buffers = [] 
     782            if data: 
     783                buffers.append(data) 
     784            self._rbuf = "" 
     785            while True: 
     786                data = self.recv(self._rbufsize) 
     787                if not data: 
     788                    break 
     789                buffers.append(data) 
     790                nl = data.find('\n') 
     791                if nl >= 0: 
     792                    nl += 1 
     793                    self._rbuf = data[nl:] 
     794                    buffers[-1] = data[:nl] 
     795                    break 
     796            return "".join(buffers) 
     797        else: 
     798            # Read until size bytes or \n or EOF seen, whichever comes first 
     799            nl = data.find('\n', 0, size) 
     800            if nl >= 0: 
     801                nl += 1 
     802                self._rbuf = data[nl:] 
     803                return data[:nl] 
     804            buf_len = len(data) 
     805            if buf_len >= size: 
     806                self._rbuf = data[size:] 
     807                return data[:size] 
     808            buffers = [] 
     809            if data: 
     810                buffers.append(data) 
     811            self._rbuf = "" 
     812            while True: 
     813                data = self.recv(self._rbufsize) 
     814                if not data: 
     815                    break 
     816                buffers.append(data) 
     817                left = size - buf_len 
     818                nl = data.find('\n', 0, left) 
     819                if nl >= 0: 
     820                    nl += 1 
     821                    self._rbuf = data[nl:] 
     822                    buffers[-1] = data[:nl] 
     823                    break 
     824                n = len(data) 
     825                if n >= left: 
     826                    self._rbuf = data[left:] 
     827                    buffers[-1] = data[:left] 
     828                    break 
     829                buf_len += n 
     830            return "".join(buffers) 
     831     
     832 
     833class SSL_fileobject(CP_fileobject): 
     834    """SSL file object attached to a socket object.""" 
     835     
     836    ssl_timeout = 3 
     837    ssl_retry = .01 
     838     
     839    def _safe_call(self, is_reader, call, *args, **kwargs): 
     840        """Wrap the given call with SSL error-trapping. 
     841         
     842        is_reader: if False EOF errors will be raised. If True, EOF errors 
     843            will return "" (to emulate normal sockets). 
     844        """ 
    653845        start = time.time() 
    654846        while True: 
    655847            try: 
    656                 return method(self, *args, **kwargs) 
     848                return call(*args, **kwargs) 
    657849            except (SSL.WantReadError, SSL.WantWriteError): 
    658850                # Sleep and try again. This is dangerous, because it means 
     
    685877                    raise NoSSLError() 
    686878                raise 
     879            except: 
     880                raise 
    687881            if time.time() - start > self.ssl_timeout: 
    688882                raise socket.timeout("timed out") 
    689     return ssl_method_wrapper 
    690  
    691 class SSL_fileobject(socket._fileobject): 
    692     """Faux file object attached to a socket object.""" 
    693      
    694     ssl_timeout = 3 
    695     ssl_retry = .01 
    696      
    697     close = _ssl_wrap_method(socket._fileobject.close) 
    698     flush = _ssl_wrap_method(socket._fileobject.flush) 
    699     write = _ssl_wrap_method(socket._fileobject.write) 
    700     writelines = _ssl_wrap_method(socket._fileobject.writelines) 
    701     read = _ssl_wrap_method(socket._fileobject.read, is_reader=True) 
    702     readline = _ssl_wrap_method(socket._fileobject.readline, is_reader=True) 
    703     readlines = _ssl_wrap_method(socket._fileobject.readlines, is_reader=True) 
     883 
     884    def recv(self, *args, **kwargs): 
     885        return self._safe_call(True, super(SSL_fileobject, self).recv, *args, **kwargs) 
     886 
     887    def sendall(self, *args, **kwargs): 
     888        return self._safe_call(False, super(SSL_fileobject, self).sendall, *args, **kwargs) 
     889 
     890    def send(self, *args, **kwargs): 
     891        return self._safe_call(False, super(SSL_fileobject, self).send, *args, **kwargs) 
    704892 
    705893 
     
    712900     
    713901    rfile: a fileobject for reading from the socket. 
    714     sendall: a function for writing (+ flush) to the socket. 
     902    send: a function for writing (+ flush) to the socket. 
    715903    """ 
    716904     
     
    737925            self.rfile = SSL_fileobject(sock, "r", self.rbufsize) 
    738926            self.rfile.ssl_timeout = timeout 
    739             self.sendall = _ssl_wrap_method(sock.sendall) 
     927            self.send = self.rfile.send 
    740928        else: 
    741             self.rfile = sock.makefile("rb", self.rbufsize) 
    742             self.sendall = sock.sendall 
     929            self.rfile = CP_fileobject(sock, "rb", self.rbufsize) 
     930            self.send = sock.send 
    743931         
    744932        # Wrap wsgi.input but not HTTPConnection.rfile itself. 
     
    756944                # get written to the previous request. 
    757945                req = None 
    758                 req = self.RequestHandlerClass(self.sendall, self.environ, 
     946                req = self.RequestHandlerClass(self.send, self.environ, 
    759947                                               self.wsgi_app) 
    760948                 
     
    772960            if errnum not in socket_errors_to_ignore: 
    773961                if req: 
     962                    fd = open("ssl_errors.txt", "a") 
     963                    fd.write("1" * 80) 
     964                    fd.write("\n") 
     965                    fd.write(str(type(e))) 
     966                    fd.write(format_exc()) 
    774967                    req.simple_response("500 Internal Server Error", 
    775968                                        format_exc()) 
     
    778971            raise 
    779972        except NoSSLError: 
    780             # Unwrap our sendall 
    781             req.sendall = self.socket._sock.sendall 
     973            # Unwrap our send 
     974            req.send = self.socket._sock.send 
    782975            req.simple_response("400 Bad Request", 
    783976                                "The client sent a plain HTTP request, but " 
    784977                                "this server only speaks HTTPS on this port.") 
    785         except
     978        except Exception, e
    786979            if req: 
     980                fd = open("ssl_errors.txt", "a") 
     981                fd.write("2" * 80) 
     982                fd.write("\n") 
     983                fd.write(format_exc()) 
    787984                req.simple_response("500 Internal Server Error", format_exc()) 
    788985     
     
    790987        """Close the socket underlying this connection.""" 
    791988        self.rfile.close() 
     989         
     990        # Python's socket module does NOT call close on the kernel socket 
     991        # when you call socket.close(). We do so manually here because we 
     992        # want this server to send a FIN TCP segment immediately. Note this 
     993        # must be called *before* calling socket.close(), because the latter 
     994        # drops its reference to the kernel socket. 
     995        self.socket._sock.close() 
     996         
    792997        self.socket.close() 
    793998 

Hosted by WebFaction

Log in as guest/cpguest to create tickets