Changeset 1915
- Timestamp:
- 03/12/08 01:39:38
- Files:
-
- trunk/cherrypy/test/test_http.py (modified) (4 diffs)
- trunk/cherrypy/test/test_session.py (modified) (2 diffs)
- trunk/cherrypy/wsgiserver/__init__.py (modified) (12 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/test/test_http.py
r1905 r1915 6 6 import httplib 7 7 import cherrypy 8 import m d5, mimetypes8 import mimetypes 9 9 10 10 … … 39 39 40 40 def post_multipart(self, file): 41 # compute and return md5 of posted file41 """Return a summary ("a * 65536\nb * 65536") of the uploaded file.""" 42 42 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) 44 57 post_multipart.exposed = True 45 58 … … 57 70 # will hang. Verify that CP times out the socket and responds 58 71 # 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) 60 76 c.request("POST", "/") 61 77 self.assertEqual(c.getresponse().status, 411) 62 78 63 79 def test_post_multipart(self): 80 alphabet = "abcdefghijklmnopqrstuvwxyz" 64 81 # 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]) 67 83 68 84 # encode as multipart form data … … 85 101 86 102 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) 88 105 89 106 trunk/cherrypy/test/test_session.py
r1868 r1915 2 2 test.prefer_parent_path() 3 3 4 import httplib 4 5 import os 5 6 localDir = os.path.dirname(__file__) … … 190 191 191 192 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) 192 197 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()) 194 206 # Uncomment the following line to prove threads overlap. 195 207 ## print index, 196 data_dict[index] = v = int(self.body)208 data_dict[index] = max(data_dict[index], int(body)) 197 209 198 210 # Start <request_count> requests from each of trunk/cherrypy/wsgiserver/__init__.py
r1902 r1915 37 37 let the name "CherryPyWSGIServer" throw you; the name merely reflects 38 38 its origin, not its coupling. 39 40 For those of you wanting to understand internals of this module, here's the 41 basic call flow. The server's listening thread runs a very tight loop, 42 sticking 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 53 Worker threads are kept in a pool and poll the Queue, popping off and then 54 handling each connection in turn. Each connection can consist of an arbitrary 55 number 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 39 77 """ 40 78 41 79 42 80 import base64 81 import os 43 82 import Queue 44 import os45 83 import re 46 84 quoted_slash = re.compile("(?i)%2F") … … 203 241 A single HTTP connection may consist of multiple request/response pairs. 204 242 205 send all: the 'sendall' method from the connection's fileobject.243 send: the 'send' method from the connection's socket object. 206 244 wsgi_app: the WSGI application to call. 207 245 environ: a partial WSGI environ (server and connection entries). … … 236 274 max_request_body_size = 0 237 275 238 def __init__(self, send all, environ, wsgi_app):276 def __init__(self, send, environ, wsgi_app): 239 277 self.rfile = environ['wsgi.input'] 240 self.send all = sendall278 self.send = send 241 279 self.environ = environ.copy() 242 280 self.wsgi_app = wsgi_app … … 570 608 self.sendall(chunk) 571 609 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 572 620 def send_headers(self): 573 621 """Assert, process, and send the HTTP response message-headers.""" … … 643 691 644 692 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) 693 class 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 833 class 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 """ 653 845 start = time.time() 654 846 while True: 655 847 try: 656 return method(self,*args, **kwargs)848 return call(*args, **kwargs) 657 849 except (SSL.WantReadError, SSL.WantWriteError): 658 850 # Sleep and try again. This is dangerous, because it means … … 685 877 raise NoSSLError() 686 878 raise 879 except: 880 raise 687 881 if time.time() - start > self.ssl_timeout: 688 882 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) 704 892 705 893 … … 712 900 713 901 rfile: a fileobject for reading from the socket. 714 send all: a function for writing (+ flush) to the socket.902 send: a function for writing (+ flush) to the socket. 715 903 """ 716 904 … … 737 925 self.rfile = SSL_fileobject(sock, "r", self.rbufsize) 738 926 self.rfile.ssl_timeout = timeout 739 self.send all = _ssl_wrap_method(sock.sendall)927 self.send = self.rfile.send 740 928 else: 741 self.rfile = sock.makefile("rb", self.rbufsize)742 self.send all = sock.sendall929 self.rfile = CP_fileobject(sock, "rb", self.rbufsize) 930 self.send = sock.send 743 931 744 932 # Wrap wsgi.input but not HTTPConnection.rfile itself. … … 756 944 # get written to the previous request. 757 945 req = None 758 req = self.RequestHandlerClass(self.send all, self.environ,946 req = self.RequestHandlerClass(self.send, self.environ, 759 947 self.wsgi_app) 760 948 … … 772 960 if errnum not in socket_errors_to_ignore: 773 961 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()) 774 967 req.simple_response("500 Internal Server Error", 775 968 format_exc()) … … 778 971 raise 779 972 except NoSSLError: 780 # Unwrap our send all781 req.send all = self.socket._sock.sendall973 # Unwrap our send 974 req.send = self.socket._sock.send 782 975 req.simple_response("400 Bad Request", 783 976 "The client sent a plain HTTP request, but " 784 977 "this server only speaks HTTPS on this port.") 785 except :978 except Exception, e: 786 979 if req: 980 fd = open("ssl_errors.txt", "a") 981 fd.write("2" * 80) 982 fd.write("\n") 983 fd.write(format_exc()) 787 984 req.simple_response("500 Internal Server Error", format_exc()) 788 985 … … 790 987 """Close the socket underlying this connection.""" 791 988 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 792 997 self.socket.close() 793 998

