1 """A high-speed, production ready, thread pooled, generic WSGI server.
2
3 Simplest example on how to use this module directly
4 (without using CherryPy's application machinery):
5
6 from cherrypy import wsgiserver
7
8 def my_crazy_app(environ, start_response):
9 status = '200 OK'
10 response_headers = [('Content-type','text/plain')]
11 start_response(status, response_headers)
12 return ['Hello world!\n']
13
14 server = wsgiserver.CherryPyWSGIServer(
15 ('0.0.0.0', 8070), my_crazy_app,
16 server_name='www.cherrypy.example')
17
18 The CherryPy WSGI server can serve as many WSGI applications
19 as you want in one instance by using a WSGIPathInfoDispatcher:
20
21 d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app})
22 server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d)
23
24 Want SSL support? Just set these attributes:
25
26 server.ssl_certificate = <filename>
27 server.ssl_private_key = <filename>
28
29 if __name__ == '__main__':
30 try:
31 server.start()
32 except KeyboardInterrupt:
33 server.stop()
34
35 This won't call the CherryPy engine (application side) at all, only the
36 WSGI server, which is independant from the rest of CherryPy. Don't
37 let the name "CherryPyWSGIServer" throw you; the name merely reflects
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
77 """
78
79
80 import base64
81 import os
82 import Queue
83 import re
84 quoted_slash = re.compile("(?i)%2F")
85 import rfc822
86 import socket
87 try:
88 import cStringIO as StringIO
89 except ImportError:
90 import StringIO
91
92 _fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring)
93
94 import sys
95 import threading
96 import time
97 import traceback
98 from urllib import unquote
99 from urlparse import urlparse
100 import warnings
101
102 try:
103 from OpenSSL import SSL
104 from OpenSSL import crypto
105 except ImportError:
106 SSL = None
107
108 import errno
109
111 """Return error numbers for all errors in errnames on this platform.
112
113 The 'errno' module contains different global constants depending on
114 the specific platform (OS). This function will return the list of
115 numeric values for a given list of potential names.
116 """
117 errno_names = dir(errno)
118 nums = [getattr(errno, k) for k in errnames if k in errno_names]
119
120 return dict.fromkeys(nums).keys()
121
122 socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
123
124 socket_errors_to_ignore = plat_specific_errors(
125 "EPIPE",
126 "EBADF", "WSAEBADF",
127 "ENOTSOCK", "WSAENOTSOCK",
128 "ETIMEDOUT", "WSAETIMEDOUT",
129 "ECONNREFUSED", "WSAECONNREFUSED",
130 "ECONNRESET", "WSAECONNRESET",
131 "ECONNABORTED", "WSAECONNABORTED",
132 "ENETRESET", "WSAENETRESET",
133 "EHOSTDOWN", "EHOSTUNREACH",
134 )
135 socket_errors_to_ignore.append("timed out")
136
137 socket_errors_nonblocking = plat_specific_errors(
138 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
139
140 comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING',
141 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL',
142 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT',
143 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE',
144 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING',
145 'WWW-AUTHENTICATE']
146
147
149 """A WSGI dispatcher for dispatch based on the PATH_INFO.
150
151 apps: a dict or list of (path_prefix, app) pairs.
152 """
153
155 try:
156 apps = apps.items()
157 except AttributeError:
158 pass
159
160
161 apps.sort()
162 apps.reverse()
163
164
165
166 self.apps = [(p.rstrip("/"), a) for p, a in apps]
167
168 - def __call__(self, environ, start_response):
169 path = environ["PATH_INFO"] or "/"
170 for p, app in self.apps:
171
172 if path.startswith(p + "/") or path == p:
173 environ = environ.copy()
174 environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
175 environ["PATH_INFO"] = path[len(p):]
176 return app(environ, start_response)
177
178 start_response('404 Not Found', [('Content-Type', 'text/plain'),
179 ('Content-Length', '0')])
180 return ['']
181
182
185
187 """Wraps a file-like object, raising MaxSizeExceeded if too large."""
188
190 self.rfile = rfile
191 self.maxlen = maxlen
192 self.bytes_read = 0
193
195 if self.maxlen and self.bytes_read > self.maxlen:
196 raise MaxSizeExceeded()
197
198 - def read(self, size=None):
199 data = self.rfile.read(size)
200 self.bytes_read += len(data)
201 self._check_length()
202 return data
203
205 if size is not None:
206 data = self.rfile.readline(size)
207 self.bytes_read += len(data)
208 self._check_length()
209 return data
210
211
212
213 res = []
214 while True:
215 data = self.rfile.readline(256)
216 self.bytes_read += len(data)
217 self._check_length()
218 res.append(data)
219
220 if len(data) < 256 or data[-1:] == "\n":
221 return ''.join(res)
222
224
225 total = 0
226 lines = []
227 line = self.readline()
228 while line:
229 lines.append(line)
230 total += len(line)
231 if 0 < sizehint <= total:
232 break
233 line = self.readline()
234 return lines
235
238
241
243 data = self.rfile.next()
244 self.bytes_read += len(data)
245 self._check_length()
246 return data
247
248
250 """An HTTP Request (and response).
251
252 A single HTTP connection may consist of multiple request/response pairs.
253
254 send: the 'send' method from the connection's socket object.
255 wsgi_app: the WSGI application to call.
256 environ: a partial WSGI environ (server and connection entries).
257 The caller MUST set the following entries:
258 * All wsgi.* entries, including .input
259 * SERVER_NAME and SERVER_PORT
260 * Any SSL_* entries
261 * Any custom entries like REMOTE_ADDR and REMOTE_PORT
262 * SERVER_SOFTWARE: the value to write in the "Server" response header.
263 * ACTUAL_SERVER_PROTOCOL: the value to write in the Status-Line of
264 the response. From RFC 2145: "An HTTP server SHOULD send a
265 response version equal to the highest version for which the
266 server is at least conditionally compliant, and whose major
267 version is less than or equal to the one received in the
268 request. An HTTP server MUST NOT send a version for which
269 it is not at least conditionally compliant."
270
271 outheaders: a list of header tuples to write in the response.
272 ready: when True, the request has been parsed and is ready to begin
273 generating the response. When False, signals the calling Connection
274 that the response should not be generated and the connection should
275 close.
276 close_connection: signals the calling Connection that the request
277 should close. This does not imply an error! The client and/or
278 server may each request that the connection be closed.
279 chunked_write: if True, output will be encoded with the "chunked"
280 transfer-coding. This value is set automatically inside
281 send_headers.
282 """
283
284 max_request_header_size = 0
285 max_request_body_size = 0
286
287 - def __init__(self, wfile, environ, wsgi_app):
288 self.rfile = environ['wsgi.input']
289 self.wfile = wfile
290 self.environ = environ.copy()
291 self.wsgi_app = wsgi_app
292
293 self.ready = False
294 self.started_response = False
295 self.status = ""
296 self.outheaders = []
297 self.sent_headers = False
298 self.close_connection = False
299 self.chunked_write = False
300
302 """Parse the next HTTP request start-line and message-headers."""
303 self.rfile.maxlen = self.max_request_header_size
304 self.rfile.bytes_read = 0
305
306 try:
307 self._parse_request()
308 except MaxSizeExceeded:
309 self.simple_response("413 Request Entity Too Large")
310 return
311
313
314
315
316
317
318
319
320 request_line = self.rfile.readline()
321 if not request_line:
322
323 self.ready = False
324 return
325
326 if request_line == "\r\n":
327
328
329
330
331 request_line = self.rfile.readline()
332 if not request_line:
333 self.ready = False
334 return
335
336 environ = self.environ
337
338 try:
339 method, path, req_protocol = request_line.strip().split(" ", 2)
340 except ValueError:
341 self.simple_response(400, "Malformed Request-Line")
342 return
343
344 environ["REQUEST_METHOD"] = method
345
346
347 scheme, location, path, params, qs, frag = urlparse(path)
348
349 if frag:
350 self.simple_response("400 Bad Request",
351 "Illegal #fragment in Request-URI.")
352 return
353
354 if scheme:
355 environ["wsgi.url_scheme"] = scheme
356 if params:
357 path = path + ";" + params
358
359 environ["SCRIPT_NAME"] = ""
360
361
362
363
364
365
366
367 atoms = [unquote(x) for x in quoted_slash.split(path)]
368 path = "%2F".join(atoms)
369 environ["PATH_INFO"] = path
370
371
372
373 environ["QUERY_STRING"] = qs
374
375
376
377
378
379
380
381
382
383
384
385
386
387 rp = int(req_protocol[5]), int(req_protocol[7])
388 server_protocol = environ["ACTUAL_SERVER_PROTOCOL"]
389 sp = int(server_protocol[5]), int(server_protocol[7])
390 if sp[0] != rp[0]:
391 self.simple_response("505 HTTP Version Not Supported")
392 return
393
394 environ["SERVER_PROTOCOL"] = req_protocol
395 self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
396
397
398 if location:
399 environ["SERVER_NAME"] = location
400
401
402 try:
403 self.read_headers()
404 except ValueError, ex:
405 self.simple_response("400 Bad Request", repr(ex.args))
406 return
407
408 mrbs = self.max_request_body_size
409 if mrbs and int(environ.get("CONTENT_LENGTH", 0)) > mrbs:
410 self.simple_response("413 Request Entity Too Large")
411 return
412
413
414 if self.response_protocol == "HTTP/1.1":
415
416 if environ.get("HTTP_CONNECTION", "") == "close":
417 self.close_connection = True
418 else:
419
420 if environ.get("HTTP_CONNECTION", "") != "Keep-Alive":
421 self.close_connection = True
422
423
424 te = None
425 if self.response_protocol == "HTTP/1.1":
426 te = environ.get("HTTP_TRANSFER_ENCODING")
427 if te:
428 te = [x.strip().lower() for x in te.split(",") if x.strip()]
429
430 self.chunked_read = False
431
432 if te:
433 for enc in te:
434 if enc == "chunked":
435 self.chunked_read = True
436 else:
437
438
439 self.simple_response("501 Unimplemented")
440 self.close_connection = True
441 return
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460 if environ.get("HTTP_EXPECT", "") == "100-continue":
461 self.simple_response(100)
462
463 self.ready = True
464
466 """Read header lines from the incoming stream."""
467 environ = self.environ
468
469 while True:
470 line = self.rfile.readline()
471 if not line:
472
473 raise ValueError("Illegal end of headers.")
474
475 if line == '\r\n':
476
477 break
478
479 if line[0] in ' \t':
480
481 v = line.strip()
482 else:
483 k, v = line.split(":", 1)
484 k, v = k.strip().upper(), v.strip()
485 envname = "HTTP_" + k.replace("-", "_")
486
487 if k in comma_separated_headers:
488 existing = environ.get(envname)
489 if existing:
490 v = ", ".join((existing, v))
491 environ[envname] = v
492
493 ct = environ.pop("HTTP_CONTENT_TYPE", None)
494 if ct is not None:
495 environ["CONTENT_TYPE"] = ct
496 cl = environ.pop("HTTP_CONTENT_LENGTH", None)
497 if cl is not None:
498 environ["CONTENT_LENGTH"] = cl
499
501 """Decode the 'chunked' transfer coding."""
502 cl = 0
503 data = StringIO.StringIO()
504 while True:
505 line = self.rfile.readline().strip().split(";", 1)
506 chunk_size = int(line.pop(0), 16)
507 if chunk_size <= 0:
508 break
509
510 cl += chunk_size
511 data.write(self.rfile.read(chunk_size))
512 crlf = self.rfile.read(2)
513 if crlf != "\r\n":
514 self.simple_response("400 Bad Request",
515 "Bad chunked transfer coding "
516 "(expected '\\r\\n', got %r)" % crlf)
517 return
518
519
520 self.read_headers()
521
522 data.seek(0)
523 self.environ["wsgi.input"] = data
524 self.environ["CONTENT_LENGTH"] = str(cl) or ""
525 return True
526
528 """Call the appropriate WSGI app and write its iterable output."""
529
530
531
532 if self.chunked_read:
533
534 self.rfile.maxlen = self.max_request_body_size
535 else:
536 cl = int(self.environ.get("CONTENT_LENGTH", 0))
537 if self.max_request_body_size:
538 self.rfile.maxlen = min(cl, self.max_request_body_size)
539 else:
540 self.rfile.maxlen = cl
541 self.rfile.bytes_read = 0
542
543 try:
544 self._respond()
545 except MaxSizeExceeded:
546 if not self.sent_headers:
547 self.simple_response("413 Request Entity Too Large")
548 return
549
551 if self.chunked_read:
552 if not self.decode_chunked():
553 self.close_connection = True
554 return
555
556 response = self.wsgi_app(self.environ, self.start_response)
557 try:
558 for chunk in response:
559
560
561
562
563
564
565 if chunk:
566 self.write(chunk)
567 finally:
568 if hasattr(response, "close"):
569 response.close()
570
571 if (self.ready and not self.sent_headers):
572 self.sent_headers = True
573 self.send_headers()
574 if self.chunked_write:
575 self.wfile.sendall("0\r\n\r\n")
576
578 """Write a simple response back to the client."""
579 status = str(status)
580 buf = ["%s %s\r\n" % (self.environ['ACTUAL_SERVER_PROTOCOL'], status),
581 "Content-Length: %s\r\n" % len(msg),
582 "Content-Type: text/plain\r\n"]
583
584 if status[:3] == "413" and self.response_protocol == 'HTTP/1.1':
585
586 self.close_connection = True
587 buf.append("Connection: close\r\n")
588
589 buf.append("\r\n")
590 if msg:
591 buf.append(msg)
592
593 try:
594 self.wfile.sendall("".join(buf))
595 except socket.error, x:
596 if x.args[0] not in socket_errors_to_ignore:
597 raise
598
600 """WSGI callable to begin the HTTP response."""
601
602
603 if self.started_response and not exc_info:
604 raise AssertionError("WSGI start_response called a second "
605 "time with no exc_info.")
606
607
608
609
610 if self.sent_headers:
611 try:
612 raise exc_info[0], exc_info[1], exc_info[2]
613 finally:
614 exc_info = None
615
616 self.started_response = True
617 self.status = status
618 self.outheaders.extend(headers)
619 return self.write
620
622 """WSGI callable to write unbuffered data to the client.
623
624 This method is also used internally by start_response (to write
625 data from the iterable returned by the WSGI application).
626 """
627 if not self.started_response:
628 raise AssertionError("WSGI write called before start_response.")
629
630 if not self.sent_headers:
631 self.sent_headers = True
632 self.send_headers()
633
634 if self.chunked_write and chunk:
635 buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"]
636 self.wfile.sendall("".join(buf))
637 else:
638 self.wfile.sendall(chunk)
639
641 """Assert, process, and send the HTTP response message-headers."""
642 hkeys = [key.lower() for key, value in self.outheaders]
643 status = int(self.status[:3])
644
645 if status == 413:
646
647 self.close_connection = True
648 elif "content-length" not in hkeys:
649
650
651
652 if status < 200 or status in (204, 205, 304):
653 pass
654 else:
655 if (self.response_protocol == 'HTTP/1.1'
656 and self.environ["REQUEST_METHOD"] != 'HEAD'):
657
658 self.chunked_write = True
659 self.outheaders.append(("Transfer-Encoding", "chunked"))
660 else:
661
662 self.close_connection = True
663
664 if "connection" not in hkeys:
665 if self.response_protocol == 'HTTP/1.1':
666
667 if self.close_connection:
668 self.outheaders.append(("Connection", "close"))
669 else:
670
671 if not self.close_connection:
672 self.outheaders.append(("Connection", "Keep-Alive"))
673
674 if (not self.close_connection) and (not self.chunked_read):
675
676
677
678
679
680
681
682
683
684
685
686
687 size = self.rfile.maxlen - self.rfile.bytes_read
688 if size > 0:
689 self.rfile.read(size)
690
691 if "date" not in hkeys:
692 self.outheaders.append(("Date", rfc822.formatdate()))
693
694 if "server" not in hkeys:
695 self.outheaders.append(("Server", self.environ['SERVER_SOFTWARE']))
696
697 buf = [self.environ['ACTUAL_SERVER_PROTOCOL'], " ", self.status, "\r\n"]
698 try:
699 buf += [k + ": " + v + "\r\n" for k, v in self.outheaders]
700 except TypeError:
701 if not isinstance(k, str):
702 raise TypeError("WSGI response header key %r is not a string.")
703 if not isinstance(v, str):
704 raise TypeError("WSGI response header value %r is not a string.")
705 else:
706 raise
707 buf.append("\r\n")
708 self.wfile.sendall("".join(buf))
709
710
712 """Exception raised when a client speaks HTTP to an HTTPS socket."""
713 pass
714
715
717 """Exception raised when the SSL implementation signals a fatal alert."""
718 pass
719
720
721 if not _fileobject_uses_str_type:
723 """Faux file object attached to a socket object."""
724
726 """Sendall for non-blocking sockets."""
727 while data:
728 try:
729 bytes_sent = self.send(data)
730 data = data[bytes_sent:]
731 except socket.error, e:
732 if e.args[0] not in socket_errors_nonblocking:
733 raise
734
735 - def send(self, data):
736 return self._sock.send(data)
737
739 if self._wbuf:
740 buffer = "".join(self._wbuf)
741 self._wbuf = []
742 self.sendall(buffer)
743
744 - def recv(self, size):
752
753 - def read(self, size=-1):
754
755
756
757 rbufsize = max(self._rbufsize, self.default_bufsize)
758
759
760
761 buf = self._rbuf
762 buf.seek(0, 2)
763 if size < 0:
764
765 self._rbuf = StringIO.StringIO()
766 while True:
767 data = self.recv(rbufsize)
768 if not data:
769 break
770 buf.write(data)
771 return buf.getvalue()
772 else:
773
774 buf_len = buf.tell()
775 if buf_len >= size:
776
777 buf.seek(0)
778 rv = buf.read(size)
779 self._rbuf = StringIO.StringIO()
780 self._rbuf.write(buf.read())
781 return rv
782
783 self._rbuf = StringIO.StringIO()
784 while True:
785 left = size - buf_len
786
787
788
789
790
791 data = self.recv(left)
792 if not data:
793 break
794 n = len(data)
795 if n == size and not buf_len:
796
797
798
799
800
801 return data
802 if n == left:
803 buf.write(data)
804 del data
805 break
806 assert n <= left, "recv(%d) returned %d bytes" % (left, n)
807 buf.write(data)
808 buf_len += n
809 del data
810
811 return buf.getvalue()
812
814 buf = self._rbuf
815 buf.seek(0, 2)
816 if buf.tell() > 0:
817
818 buf.seek(0)
819 bline = buf.readline(size)
820 if bline.endswith('\n') or len(bline) == size:
821 self._rbuf = StringIO.StringIO()
822 self._rbuf.write(buf.read())
823 return bline
824 del bline
825 if size < 0:
826
827 if self._rbufsize <= 1:
828
829 buf.seek(0)
830 buffers = [buf.read()]
831 self._rbuf = StringIO.StringIO()
832 data = None
833 recv = self.recv
834 while data != "\n":
835 data = recv(1)
836 if not data:
837 break
838 buffers.append(data)
839 return "".join(buffers)
840
841 buf.seek(0, 2)
842 self._rbuf = StringIO.StringIO()
843 while True:
844 data = self.recv(self._rbufsize)
845 if not data:
846 break
847 nl = data.find('\n')
848 if nl >= 0:
849 nl += 1
850 buf.write(data[:nl])
851 self._rbuf.write(data[nl:])
852 del data
853 break
854 buf.write(data)
855 return buf.getvalue()
856 else:
857
858 buf.seek(0, 2)
859 buf_len = buf.tell()
860 if buf_len >= size:
861 buf.seek(0)
862 rv = buf.read(size)
863 self._rbuf = StringIO.StringIO()
864 self._rbuf.write(buf.read())
865 return rv
866 self._rbuf = StringIO.StringIO()
867 while True:
868 data = self.recv(self._rbufsize)
869 if not data:
870 break
871 left = size - buf_len
872
873 nl = data.find('\n', 0, left)
874 if nl >= 0:
875 nl += 1
876
877 self._rbuf.write(data[nl:])
878 if buf_len:
879 buf.write(data[:nl])
880 break
881 else:
882
883
884 return data[:nl]
885 n = len(data)
886 if n == size and not buf_len:
887
888
889 return data
890 if n >= left:
891 buf.write(data[:left])
892 self._rbuf.write(data[left:])
893 break
894 buf.write(data)
895 buf_len += n
896
897 return buf.getvalue()
898
899 else:
901 """Faux file object attached to a socket object."""
902
904 """Sendall for non-blocking sockets."""
905 while data:
906 try:
907 bytes_sent = self.send(data)
908 data = data[bytes_sent:]
909 except socket.error, e:
910 if e.args[0] not in socket_errors_nonblocking:
911 raise
912
913 - def send(self, data):
914 return self._sock.send(data)
915
917 if self._wbuf:
918 buffer = "".join(self._wbuf)
919 self._wbuf = []
920 self.sendall(buffer)
921
922 - def recv(self, size):
930
931 - def read(self, size=-1):
932 if size < 0:
933
934 buffers = [self._rbuf]
935 self._rbuf = ""
936 if self._rbufsize <= 1:
937 recv_size = self.default_bufsize
938 else:
939 recv_size = self._rbufsize
940
941 while True:
942 data = self.recv(recv_size)
943 if not data:
944 break
945 buffers.append(data)
946 return "".join(buffers)
947 else:
948
949 data = self._rbuf
950 buf_len = len(data)
951 if buf_len >= size:
952 self._rbuf = data[size:]
953 return data[:size]
954 buffers = []
955 if data:
956 buffers.append(data)
957 self._rbuf = ""
958 while True:
959 left = size - buf_len
960 recv_size = max(self._rbufsize, left)
961 data = self.recv(recv_size)
962 if not data:
963 break
964 buffers.append(data)
965 n = len(data)
966 if n >= left:
967 self._rbuf = data[left:]
968 buffers[-1] = data[:left]
969 break
970 buf_len += n
971 return "".join(buffers)
972
974 data = self._rbuf
975 if size < 0:
976
977 if self._rbufsize <= 1:
978
979 assert data == ""
980 buffers = []
981 while data != "\n":
982 data = self.recv(1)
983 if not data:
984 break
985 buffers.append(data)
986 return "".join(buffers)
987 nl = data.find('\n')
988 if nl >= 0:
989 nl += 1
990 self._rbuf = data[nl:]
991 return data[:nl]
992 buffers = []
993 if data:
994 buffers.append(data)
995 self._rbuf = ""
996 while True:
997 data = self.recv(self._rbufsize)
998 if not data:
999 break
1000 buffers.append(data)
1001 nl = data.find('\n')
1002 if nl >= 0:
1003 nl += 1
1004 self._rbuf = data[nl:]
1005 buffers[-1] = data[:nl]
1006 break
1007 return "".join(buffers)
1008 else:
1009
1010 nl = data.find('\n', 0, size)
1011 if nl >= 0:
1012 nl += 1
1013 self._rbuf = data[nl:]
1014 return data[:nl]
1015 buf_len = len(data)
1016 if buf_len >= size:
1017 self._rbuf = data[size:]
1018 return data[:size]
1019 buffers = []
1020 if data:
1021 buffers.append(data)
1022 self._rbuf = ""
1023 while True:
1024 data = self.recv(self._rbufsize)
1025 if not data:
1026 break
1027 buffers.append(data)
1028 left = size - buf_len
1029 nl = data.find('\n', 0, left)
1030 if nl >= 0:
1031 nl += 1
1032 self._rbuf = data[nl:]
1033 buffers[-1] = data[:nl]
1034 break
1035 n = len(data)
1036 if n >= left:
1037 self._rbuf = data[left:]
1038 buffers[-1] = data[:left]
1039 break
1040 buf_len += n
1041 return "".join(buffers)
1042
1043
1045 """SSL file object attached to a socket object."""
1046
1047 ssl_timeout = 3
1048 ssl_retry = .01
1049
1050 - def _safe_call(self, is_reader, call, *args, **kwargs):
1051 """Wrap the given call with SSL error-trapping.
1052
1053 is_reader: if False EOF errors will be raised. If True, EOF errors
1054 will return "" (to emulate normal sockets).
1055 """
1056 start = time.time()
1057 while True:
1058 try:
1059 return call(*args, **kwargs)
1060 except SSL.WantReadError:
1061
1062
1063
1064
1065 time.sleep(self.ssl_retry)
1066 except SSL.WantWriteError:
1067 time.sleep(self.ssl_retry)
1068 except SSL.SysCallError, e:
1069 if is_reader and e.args == (-1, 'Unexpected EOF'):
1070 return ""
1071
1072 errnum = e.args[0]
1073 if is_reader and errnum in socket_errors_to_ignore:
1074 return ""
1075 raise socket.error(errnum)
1076 except SSL.Error, e:
1077 if is_reader and e.args == (-1, 'Unexpected EOF'):
1078 return ""
1079
1080 thirdarg = None
1081 try:
1082 thirdarg = e.args[0][0][2]
1083 except IndexError:
1084 pass
1085
1086 if thirdarg == 'http request':
1087
1088 raise NoSSLError()
1089 raise FatalSSLAlert(*e.args)
1090 except:
1091 raise
1092
1093 if time.time() - start > self.ssl_timeout:
1094 raise socket.timeout("timed out")
1095
1096 - def recv(self, *args, **kwargs):
1097 buf = []
1098 r = super(SSL_fileobject, self).recv
1099 while True:
1100 data = self._safe_call(True, r, *args, **kwargs)
1101 buf.append(data)
1102 p = self._sock.pending()
1103 if not p:
1104 return "".join(buf)
1105
1106 - def sendall(self, *args, **kwargs):
1108
1109 - def send(self, *args, **kwargs):
1111
1112
1114 """An HTTP connection (active socket).
1115
1116 socket: the raw socket object (usually TCP) for this connection.
1117 wsgi_app: the WSGI application for this server/connection.
1118 environ: a WSGI environ template. This will be copied for each request.
1119
1120 rfile: a fileobject for reading from the socket.
1121 send: a function for writing (+ flush) to the socket.
1122 """
1123
1124 rbufsize = -1
1125 RequestHandlerClass = HTTPRequest
1126 environ = {"wsgi.version": (1, 0),
1127 "wsgi.url_scheme": "http",
1128 "wsgi.multithread": True,
1129 "wsgi.multiprocess": False,
1130 "wsgi.run_once": False,
1131 "wsgi.errors": sys.stderr,
1132 }
1133
1134 - def __init__(self, sock, wsgi_app, environ):
1135 self.socket = sock
1136 self.wsgi_app = wsgi_app
1137
1138
1139 self.environ = self.environ.copy()
1140 self.environ.update(environ)
1141
1142 if SSL and isinstance(sock, SSL.ConnectionType):
1143 timeout = sock.gettimeout()
1144 self.rfile = SSL_fileobject(sock, "rb", self.rbufsize)
1145 self.rfile.ssl_timeout = timeout
1146 self.wfile = SSL_fileobject(sock, "wb", -1)
1147 self.wfile.ssl_timeout = timeout
1148 else:
1149 self.rfile = CP_fileobject(sock, "rb", self.rbufsize)
1150 self.wfile = CP_fileobject(sock, "wb", -1)
1151
1152
1153
1154
1155
1156 self.environ["wsgi.input"] = SizeCheckWrapper(self.rfile, 0)
1157
1159 """Read each request and respond appropriately."""
1160 try:
1161 while True:
1162
1163
1164
1165 req = None
1166 req = self.RequestHandlerClass(self.wfile, self.environ,
1167 self.wsgi_app)
1168
1169
1170 req.parse_request()
1171 if not req.ready:
1172 return
1173
1174 req.respond()
1175 if req.close_connection:
1176 return
1177
1178 except socket.error, e:
1179 errnum = e.args[0]
1180 if errnum == 'timed out':
1181 if req and not req.sent_headers:
1182 req.simple_response("408 Request Timeout")
1183 elif errnum not in socket_errors_to_ignore:
1184 if req and not req.sent_headers:
1185 req.simple_response("500 Internal Server Error",
1186 format_exc())
1187 return
1188 except (KeyboardInterrupt, SystemExit):
1189 raise
1190 except FatalSSLAlert, e:
1191
1192 return
1193 except NoSSLError:
1194
1195 req.wfile = CP_fileobject(self.socket, "wb", -1)
1196 if req and not req.sent_headers:
1197 req.simple_response("400 Bad Request",
1198 "The client sent a plain HTTP request, but "
1199 "this server only speaks HTTPS on this port.")
1200 except Exception, e:
1201 if req and not req.sent_headers:
1202 req.simple_response("500 Internal Server Error", format_exc())
1203
1205 """Close the socket underlying this connection."""
1206 self.rfile.close()
1207
1208
1209
1210
1211
1212
1213 self.socket._sock.close()
1214
1215 self.socket.close()
1216
1217
1225
1226
1227 _SHUTDOWNREQUEST = None
1228
1230 """Thread which continuously polls a Queue for Connection objects.
1231
1232 server: the HTTP Server which spawned this thread, and which owns the
1233 Queue and is placing active connections into it.
1234 ready: a simple flag for the calling server to know when this thread
1235 has begun polling the Queue.
1236
1237 Due to the timing issues of polling a Queue, a WorkerThread does not
1238 check its own 'ready' flag after it has started. To stop the thread,
1239 it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
1240 (one for each running WorkerThread).
1241 """
1242
1243 conn = None
1244
1246 self.ready = False
1247 self.server = server
1248 threading.Thread.__init__(self)
1249
1266
1267
1269 """A Request Queue for the CherryPyWSGIServer which pools threads.
1270
1271 ThreadPool objects must provide min, get(), put(obj), start()
1272 and stop(timeout) attributes.
1273 """
1274
1275 - def __init__(self, server, min=10, max=-1):
1276 self.server = server
1277 self.min = min
1278 self.max = max
1279 self._threads = []
1280 self._queue = Queue.Queue()
1281 self.get = self._queue.get
1282
1284 """Start the pool of threads."""
1285 for i in xrange(self.min):
1286 self._threads.append(WorkerThread(self.server))
1287 for worker in self._threads:
1288 worker.setName("CP WSGIServer " + worker.getName())
1289 worker.start()
1290 for worker in self._threads:
1291 while not worker.ready:
1292 time.sleep(.1)
1293
1295 """Number of worker threads which are idle. Read-only."""
1296 return len([t for t in self._threads if t.conn is None])
1297 idle = property(_get_idle, doc=_get_idle.__doc__)
1298
1299 - def put(self, obj):
1303
1304 - def grow(self, amount):
1305 """Spawn new worker threads (not above self.max)."""
1306 for i in xrange(amount):
1307 if self.max > 0 and len(self._threads) >= self.max:
1308 break
1309 worker = WorkerThread(self.server)
1310 worker.setName("CP WSGIServer " + worker.getName())
1311 self._threads.append(worker)
1312 worker.start()
1313
1315 """Kill off worker threads (not below self.min)."""
1316
1317
1318 for t in self._threads:
1319 if not t.isAlive():
1320 self._threads.remove(t)
1321 amount -= 1
1322
1323 if amount > 0:
1324 for i in xrange(min(amount, len(self._threads) - self.min)):
1325
1326
1327
1328
1329 self._queue.put(_SHUTDOWNREQUEST)
1330
1331 - def stop(self, timeout=5):
1332
1333
1334 for worker in self._threads:
1335 self._queue.put(_SHUTDOWNREQUEST)
1336
1337
1338 current = threading.currentThread()
1339 while self._threads:
1340 worker = self._threads.pop()
1341 if worker is not current and worker.isAlive():
1342 try:
1343 if timeout is None or timeout < 0:
1344 worker.join()
1345 else:
1346 worker.join(timeout)
1347 if worker.isAlive():
1348
1349
1350 c = worker.conn
1351 if c and not c.rfile.closed:
1352 if SSL and isinstance(c.socket, SSL.ConnectionType):
1353
1354 c.socket.shutdown()
1355 else:
1356 c.socket.shutdown(socket.SHUT_RD)
1357 worker.join()
1358 except (AssertionError,
1359
1360
1361 KeyboardInterrupt), exc1:
1362 pass
1363
1364
1365
1367 """A thread-safe wrapper for an SSL.Connection.
1368
1369 *args: the arguments to create the wrapped SSL.Connection(*args).
1370 """
1371
1373 self._ssl_conn = SSL.Connection(*args)
1374 self._lock = threading.RLock()
1375
1376 for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
1377 'renegotiate', 'bind', 'listen', 'connect', 'accept',
1378 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list',
1379 'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
1380 'makefile', 'get_app_data', 'set_app_data', 'state_string',
1381 'sock_shutdown', 'get_peer_certificate', 'want_read',
1382 'want_write', 'set_connect_state', 'set_accept_state',
1383 'connect_ex', 'sendall', 'settimeout'):
1384 exec """def %s(self, *args):
1385 self._lock.acquire()
1386 try:
1387 return self._ssl_conn.%s(*args)
1388 finally:
1389 self._lock.release()
1390 """ % (f, f)
1391
1392
1393 try:
1394 import fcntl
1395 except ImportError:
1396 try:
1397 from ctypes import windll, WinError
1398 except ImportError:
1400 """Dummy function, since neither fcntl nor ctypes are available."""
1401 pass
1402 else:
1404 """Mark the given socket fd as non-inheritable (Windows)."""
1405 if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
1406 raise WinError()
1407 else:
1409 """Mark the given socket fd as non-inheritable (POSIX)."""
1410 fd = sock.fileno()
1411 old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
1412 fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
1413
1414
1416 """An HTTP server for WSGI.
1417
1418 bind_addr: The interface on which to listen for connections.
1419 For TCP sockets, a (host, port) tuple. Host values may be any IPv4
1420 or IPv6 address, or any valid hostname. The string 'localhost' is a
1421 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
1422 The string '0.0.0.0' is a special IPv4 entry meaning "any active
1423 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
1424 IPv6. The empty string or None are not allowed.
1425
1426 For UNIX sockets, supply the filename as a string.
1427 wsgi_app: the WSGI 'application callable'; multiple WSGI applications
1428 may be passed as (path_prefix, app) pairs.
1429 numthreads: the number of worker threads to create (default 10).
1430 server_name: the string to set for WSGI's SERVER_NAME environ entry.
1431 Defaults to socket.gethostname().
1432 max: the maximum number of queued requests (defaults to -1 = no limit).
1433 request_queue_size: the 'backlog' argument to socket.listen();
1434 specifies the maximum number of queued connections (default 5).
1435 timeout: the timeout in seconds for accepted connections (default 10).
1436
1437 nodelay: if True (the default since 3.1), sets the TCP_NODELAY socket
1438 option.
1439
1440 protocol: the version string to write in the Status-Line of all
1441 HTTP responses. For example, "HTTP/1.1" (the default). This
1442 also limits the supported features used in the response.
1443
1444
1445 SSL/HTTPS
1446 ---------
1447 The OpenSSL module must be importable for SSL functionality.
1448 You can obtain it from http://pyopenssl.sourceforge.net/
1449
1450 ssl_certificate: the filename of the server SSL certificate.
1451 ssl_privatekey: the filename of the server's private key file.
1452
1453 If either of these is None (both are None by default), this server
1454 will not use SSL. If both are given and are valid, they will be read
1455 on server start and used in the SSL context for the listening socket.
1456 """
1457
1458 protocol = "HTTP/1.1"
1459 _bind_addr = "127.0.0.1"
1460 version = "CherryPy/3.1.0"
1461 ready = False
1462 _interrupt = None
1463
1464 nodelay = True
1465
1466 ConnectionClass = HTTPConnection
1467 environ = {}
1468
1469
1470 ssl_certificate = None
1471 ssl_private_key = None
1472
1473 - def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
1474 max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
1475 self.requests = ThreadPool(self, min=numthreads or 1, max=max)
1476
1477 if callable(wsgi_app):
1478
1479
1480 self.wsgi_app = wsgi_app
1481 else:
1482
1483
1484
1485 warnings.warn("The ability to pass multiple apps is deprecated "
1486 "and will be removed in 3.2. You should explicitly "
1487 "include a WSGIPathInfoDispatcher instead.",
1488 DeprecationWarning)
1489 self.wsgi_app = WSGIPathInfoDispatcher(wsgi_app)
1490
1491 self.bind_addr = bind_addr
1492 if not server_name:
1493 server_name = socket.gethostname()
1494 self.server_name = server_name
1495 self.request_queue_size = request_queue_size
1496
1497 self.timeout = timeout
1498 self.shutdown_timeout = shutdown_timeout
1499
1501 return self.requests.min
1503 self.requests.min = value
1504 numthreads = property(_get_numthreads, _set_numthreads)
1505
1507 return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
1508 self.bind_addr)
1509
1513 if isinstance(value, tuple) and value[0] in ('', None):
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524 raise ValueError("Host values of '' or None are not allowed. "
1525 "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
1526 "to listen on all active interfaces.")
1527 self._bind_addr = value
1528 bind_addr = property(_get_bind_addr, _set_bind_addr,
1529 doc="""The interface on which to listen for connections.
1530
1531 For TCP sockets, a (host, port) tuple. Host values may be any IPv4
1532 or IPv6 address, or any valid hostname. The string 'localhost' is a
1533 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
1534 The string '0.0.0.0' is a special IPv4 entry meaning "any active
1535 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
1536 IPv6. The empty string or None are not allowed.
1537
1538 For UNIX sockets, supply the filename as a string.""")
1539
1541 """Run the server forever."""
1542
1543
1544
1545
1546 self._interrupt = None
1547
1548
1549 if isinstance(self.bind_addr, basestring):
1550
1551
1552
1553 try: os.unlink(self.bind_addr)
1554 except: pass
1555
1556
1557 try: os.chmod(self.bind_addr, 0777)
1558 except: pass
1559
1560 info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1561 else:
1562
1563
1564 host, port = self.bind_addr
1565 try:
1566 info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1567 socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
1568 except socket.gaierror:
1569
1570 info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1571
1572 self.socket = None
1573 msg = "No socket could be created"
1574 for res in info:
1575 af, socktype, proto, canonname, sa = res
1576 try:
1577 self.bind(af, socktype, proto)
1578 except socket.error, msg:
1579 if self.socket:
1580 self.socket.close()
1581 self.socket = None
1582 continue
1583 break
1584 if not self.socket:
1585 raise socket.error, msg
1586
1587
1588 self.socket.settimeout(1)
1589 self.socket.listen(self.request_queue_size)
1590
1591
1592 self.requests.start()
1593
1594 self.ready = True
1595 while self.ready:
1596 self.tick()
1597 if self.interrupt:
1598 while self.interrupt is True:
1599
1600 time.sleep(0.1)
1601 if self.interrupt:
1602 raise self.interrupt
1603
1604 - def bind(self, family, type, proto=0):
1605 """Create (or recreate) the actual socket object."""
1606 self.socket = socket.socket(family, type, proto)
1607 prevent_socket_inheritance(self.socket)
1608 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1609 if self.nodelay:
1610 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1611 if self.ssl_certificate and self.ssl_private_key:
1612 if SSL is None:
1613 raise ImportError("You must install pyOpenSSL to use HTTPS.")
1614
1615
1616 ctx = SSL.Context(SSL.SSLv23_METHOD)
1617 ctx.use_privatekey_file(self.ssl_private_key)
1618 ctx.use_certificate_file(self.ssl_certificate)
1619 self.socket = SSLConnection(ctx, self.socket)
1620 self.populate_ssl_environ()
1621
1622
1623
1624 if (not isinstance(self.bind_addr, basestring)
1625 and self.bind_addr[0] == '::' and family == socket.AF_INET6):
1626 try:
1627 self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
1628 except (AttributeError, socket.error):
1629
1630
1631 pass
1632
1633 self.socket.bind(self.bind_addr)
1634
1636 """Accept a new connection and put it on the Queue."""
1637 try:
1638 s, addr = self.socket.accept()
1639 prevent_socket_inheritance(s)
1640 if not self.ready:
1641 return
1642 if hasattr(s, 'settimeout'):
1643 s.settimeout(self.timeout)
1644
1645 environ = self.environ.copy()
1646
1647
1648 if environ.get("SERVER_SOFTWARE") is None:
1649 environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version
1650
1651
1652
1653 environ["ACTUAL_SERVER_PROTOCOL"] = self.protocol
1654 environ["SERVER_NAME"] = self.server_name
1655
1656 if isinstance(self.bind_addr, basestring):
1657
1658
1659 environ["SERVER_PORT"] = ""
1660 else:
1661 environ["SERVER_PORT"] = str(self.bind_addr[1])
1662
1663
1664 environ["REMOTE_ADDR"] = addr[0]
1665 environ["REMOTE_PORT"] = str(addr[1])
1666
1667 conn = self.ConnectionClass(s, self.wsgi_app, environ)
1668 self.requests.put(conn)
1669 except socket.timeout:
1670
1671
1672
1673 return
1674 except socket.error, x:
1675 if x.args[0] in socket_error_eintr:
1676
1677
1678
1679
1680
1681 return
1682 if x.args[0] in socket_errors_nonblocking:
1683
1684 return
1685 if x.args[0] in socket_errors_to_ignore:
1686
1687
1688 return
1689 raise
1690
1697 interrupt = property(_get_interrupt, _set_interrupt,
1698 doc="Set this to an Exception instance to "
1699 "interrupt the server.")
1700
1702 """Gracefully shutdown a server that is serving forever."""
1703 self.ready = False
1704
1705 sock = getattr(self, "socket", None)
1706 if sock:
1707 if not isinstance(self.bind_addr, basestring):
1708
1709 try:
1710 host, port = sock.getsockname()[:2]
1711 except socket.error, x:
1712 if x.args[1] != "Bad file descriptor":
1713 raise
1714 else:
1715
1716
1717
1718
1719 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1720 socket.SOCK_STREAM):
1721 af, socktype, proto, canonname, sa = res
1722 s = None
1723 try:
1724 s = socket.socket(af, socktype, proto)
1725
1726
1727 s.settimeout(1.0)
1728 s.connect((host, port))
1729 s.close()
1730 except socket.error:
1731 if s:
1732 s.close()
1733 if hasattr(sock, "close"):
1734 sock.close()
1735 self.socket = None
1736
1737 self.requests.stop(self.shutdown_timeout)
1738
1740 """Create WSGI environ entries to be merged into each request."""
1741 cert = open(self.ssl_certificate, 'rb').read()
1742 cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
1743 ssl_environ = {
1744 "wsgi.url_scheme": "https",
1745 "HTTPS": "on",
1746
1747
1748
1749
1750
1751 }
1752
1753
1754 ssl_environ.update({
1755 'SSL_SERVER_M_VERSION': cert.get_version(),
1756 'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
1757
1758
1759 })
1760
1761 for prefix, dn in [("I", cert.get_issuer()),
1762 ("S", cert.get_subject())]:
1763
1764
1765
1766 dnstr = str(dn)[18:-2]
1767
1768 wsgikey = 'SSL_SERVER_%s_DN' % prefix
1769 ssl_environ[wsgikey] = dnstr
1770
1771
1772
1773 while dnstr:
1774 pos = dnstr.rfind("=")
1775 dnstr, value = dnstr[:pos], dnstr[pos + 1:]
1776 pos = dnstr.rfind("/")
1777 dnstr, key = dnstr[:pos], dnstr[pos + 1:]
1778 if key and value:
1779 wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
1780 ssl_environ[wsgikey] = value
1781
1782 self.environ.update(ssl_environ)
1783