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):
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):
203
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
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
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
549
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
734
735 - def send(self, 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
912
913 - def send(self, 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):
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 if req and not req.sent_headers:
1195
1196 req.wfile = CP_fileobject(self.socket._sock, "wb", -1)
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 self.linger = True
1201 except Exception, e:
1202 if req and not req.sent_headers:
1203 req.simple_response("500 Internal Server Error", format_exc())
1204
1205 linger = False
1206
1208 """Close the socket underlying this connection."""
1209 self.rfile.close()
1210
1211 if not self.linger:
1212
1213
1214
1215
1216
1217 self.socket._sock.close()
1218 self.socket.close()
1219 else:
1220
1221
1222
1223
1224
1225
1226 pass
1227
1228
1236
1237
1238 _SHUTDOWNREQUEST = None
1239
1241 """Thread which continuously polls a Queue for Connection objects.
1242
1243 server: the HTTP Server which spawned this thread, and which owns the
1244 Queue and is placing active connections into it.
1245 ready: a simple flag for the calling server to know when this thread
1246 has begun polling the Queue.
1247
1248 Due to the timing issues of polling a Queue, a WorkerThread does not
1249 check its own 'ready' flag after it has started. To stop the thread,
1250 it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
1251 (one for each running WorkerThread).
1252 """
1253
1254 conn = None
1255
1260
1277
1278
1280 """A Request Queue for the CherryPyWSGIServer which pools threads.
1281
1282 ThreadPool objects must provide min, get(), put(obj), start()
1283 and stop(timeout) attributes.
1284 """
1285
1286 - def __init__(self, server, min=10, max=-1):
1287 self.server = server
1288 self.min = min
1289 self.max = max
1290 self._threads = []
1291 self._queue = Queue.Queue()
1292 self.get = self._queue.get
1293
1295 """Start the pool of threads."""
1296 for i in xrange(self.min):
1297 self._threads.append(WorkerThread(self.server))
1298 for worker in self._threads:
1299 worker.setName("CP WSGIServer " + worker.getName())
1300 worker.start()
1301 for worker in self._threads:
1302 while not worker.ready:
1303 time.sleep(.1)
1304
1306 """Number of worker threads which are idle. Read-only."""
1307 return len([t for t in self._threads if t.conn is None])
1308 idle = property(_get_idle, doc=_get_idle.__doc__)
1309
1310 - def put(self, obj):
1314
1315 - def grow(self, amount):
1316 """Spawn new worker threads (not above self.max)."""
1317 for i in xrange(amount):
1318 if self.max > 0 and len(self._threads) >= self.max:
1319 break
1320 worker = WorkerThread(self.server)
1321 worker.setName("CP WSGIServer " + worker.getName())
1322 self._threads.append(worker)
1323 worker.start()
1324
1326 """Kill off worker threads (not below self.min)."""
1327
1328
1329 for t in self._threads:
1330 if not t.isAlive():
1331 self._threads.remove(t)
1332 amount -= 1
1333
1334 if amount > 0:
1335 for i in xrange(min(amount, len(self._threads) - self.min)):
1336
1337
1338
1339
1340 self._queue.put(_SHUTDOWNREQUEST)
1341
1342 - def stop(self, timeout=5):
1343
1344
1345 for worker in self._threads:
1346 self._queue.put(_SHUTDOWNREQUEST)
1347
1348
1349 current = threading.currentThread()
1350 while self._threads:
1351 worker = self._threads.pop()
1352 if worker is not current and worker.isAlive():
1353 try:
1354 if timeout is None or timeout < 0:
1355 worker.join()
1356 else:
1357 worker.join(timeout)
1358 if worker.isAlive():
1359
1360
1361 c = worker.conn
1362 if c and not c.rfile.closed:
1363 if SSL and isinstance(c.socket, SSL.ConnectionType):
1364
1365 c.socket.shutdown()
1366 else:
1367 c.socket.shutdown(socket.SHUT_RD)
1368 worker.join()
1369 except (AssertionError,
1370
1371
1372 KeyboardInterrupt), exc1:
1373 pass
1374
1375
1376
1378 """A thread-safe wrapper for an SSL.Connection.
1379
1380 *args: the arguments to create the wrapped SSL.Connection(*args).
1381 """
1382
1384 self._ssl_conn = SSL.Connection(*args)
1385 self._lock = threading.RLock()
1386
1387 for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
1388 'renegotiate', 'bind', 'listen', 'connect', 'accept',
1389 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list',
1390 'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
1391 'makefile', 'get_app_data', 'set_app_data', 'state_string',
1392 'sock_shutdown', 'get_peer_certificate', 'want_read',
1393 'want_write', 'set_connect_state', 'set_accept_state',
1394 'connect_ex', 'sendall', 'settimeout'):
1395 exec """def %s(self, *args):
1396 self._lock.acquire()
1397 try:
1398 return self._ssl_conn.%s(*args)
1399 finally:
1400 self._lock.release()
1401 """ % (f, f)
1402
1403
1404 try:
1405 import fcntl
1406 except ImportError:
1407 try:
1408 from ctypes import windll, WinError
1409 except ImportError:
1411 """Dummy function, since neither fcntl nor ctypes are available."""
1412 pass
1413 else:
1415 """Mark the given socket fd as non-inheritable (Windows)."""
1416 if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
1417 raise WinError()
1418 else:
1420 """Mark the given socket fd as non-inheritable (POSIX)."""
1421 fd = sock.fileno()
1422 old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
1423 fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
1424
1425
1427 """An HTTP server for WSGI.
1428
1429 bind_addr: The interface on which to listen for connections.
1430 For TCP sockets, a (host, port) tuple. Host values may be any IPv4
1431 or IPv6 address, or any valid hostname. The string 'localhost' is a
1432 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
1433 The string '0.0.0.0' is a special IPv4 entry meaning "any active
1434 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
1435 IPv6. The empty string or None are not allowed.
1436
1437 For UNIX sockets, supply the filename as a string.
1438 wsgi_app: the WSGI 'application callable'; multiple WSGI applications
1439 may be passed as (path_prefix, app) pairs.
1440 numthreads: the number of worker threads to create (default 10).
1441 server_name: the string to set for WSGI's SERVER_NAME environ entry.
1442 Defaults to socket.gethostname().
1443 max: the maximum number of queued requests (defaults to -1 = no limit).
1444 request_queue_size: the 'backlog' argument to socket.listen();
1445 specifies the maximum number of queued connections (default 5).
1446 timeout: the timeout in seconds for accepted connections (default 10).
1447
1448 nodelay: if True (the default since 3.1), sets the TCP_NODELAY socket
1449 option.
1450
1451 protocol: the version string to write in the Status-Line of all
1452 HTTP responses. For example, "HTTP/1.1" (the default). This
1453 also limits the supported features used in the response.
1454
1455
1456 SSL/HTTPS
1457 ---------
1458 The OpenSSL module must be importable for SSL functionality.
1459 You can obtain it from http://pyopenssl.sourceforge.net/
1460
1461 ssl_certificate: the filename of the server SSL certificate.
1462 ssl_privatekey: the filename of the server's private key file.
1463
1464 If either of these is None (both are None by default), this server
1465 will not use SSL. If both are given and are valid, they will be read
1466 on server start and used in the SSL context for the listening socket.
1467 """
1468
1469 protocol = "HTTP/1.1"
1470 _bind_addr = "127.0.0.1"
1471 version = "CherryPy/3.1.2"
1472 ready = False
1473 _interrupt = None
1474
1475 nodelay = True
1476
1477 ConnectionClass = HTTPConnection
1478 environ = {}
1479
1480
1481 ssl_certificate = None
1482 ssl_private_key = None
1483
1484 - def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
1485 max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
1486 self.requests = ThreadPool(self, min=numthreads or 1, max=max)
1487
1488 if callable(wsgi_app):
1489
1490
1491 self.wsgi_app = wsgi_app
1492 else:
1493
1494
1495
1496 warnings.warn("The ability to pass multiple apps is deprecated "
1497 "and will be removed in 3.2. You should explicitly "
1498 "include a WSGIPathInfoDispatcher instead.",
1499 DeprecationWarning)
1500 self.wsgi_app = WSGIPathInfoDispatcher(wsgi_app)
1501
1502 self.bind_addr = bind_addr
1503 if not server_name:
1504 server_name = socket.gethostname()
1505 self.server_name = server_name
1506 self.request_queue_size = request_queue_size
1507
1508 self.timeout = timeout
1509 self.shutdown_timeout = shutdown_timeout
1510
1512 return self.requests.min
1514 self.requests.min = value
1515 numthreads = property(_get_numthreads, _set_numthreads)
1516
1518 return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
1519 self.bind_addr)
1520
1524 if isinstance(value, tuple) and value[0] in ('', None):
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535 raise ValueError("Host values of '' or None are not allowed. "
1536 "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
1537 "to listen on all active interfaces.")
1538 self._bind_addr = value
1539 bind_addr = property(_get_bind_addr, _set_bind_addr,
1540 doc="""The interface on which to listen for connections.
1541
1542 For TCP sockets, a (host, port) tuple. Host values may be any IPv4
1543 or IPv6 address, or any valid hostname. The string 'localhost' is a
1544 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
1545 The string '0.0.0.0' is a special IPv4 entry meaning "any active
1546 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
1547 IPv6. The empty string or None are not allowed.
1548
1549 For UNIX sockets, supply the filename as a string.""")
1550
1552 """Run the server forever."""
1553
1554
1555
1556
1557 self._interrupt = None
1558
1559
1560 if isinstance(self.bind_addr, basestring):
1561
1562
1563
1564 try: os.unlink(self.bind_addr)
1565 except: pass
1566
1567
1568 try: os.chmod(self.bind_addr, 0777)
1569 except: pass
1570
1571 info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1572 else:
1573
1574
1575 host, port = self.bind_addr
1576 try:
1577 info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1578 socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
1579 except socket.gaierror:
1580
1581 info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1582
1583 self.socket = None
1584 msg = "No socket could be created"
1585 for res in info:
1586 af, socktype, proto, canonname, sa = res
1587 try:
1588 self.bind(af, socktype, proto)
1589 except socket.error, msg:
1590 if self.socket:
1591 self.socket.close()
1592 self.socket = None
1593 continue
1594 break
1595 if not self.socket:
1596 raise socket.error, msg
1597
1598
1599 self.socket.settimeout(1)
1600 self.socket.listen(self.request_queue_size)
1601
1602
1603 self.requests.start()
1604
1605 self.ready = True
1606 while self.ready:
1607 self.tick()
1608 if self.interrupt:
1609 while self.interrupt is True:
1610
1611 time.sleep(0.1)
1612 if self.interrupt:
1613 raise self.interrupt
1614
1615 - def bind(self, family, type, proto=0):
1616 """Create (or recreate) the actual socket object."""
1617 self.socket = socket.socket(family, type, proto)
1618 prevent_socket_inheritance(self.socket)
1619 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1620 if self.nodelay:
1621 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1622 if self.ssl_certificate and self.ssl_private_key:
1623 if SSL is None:
1624 raise ImportError("You must install pyOpenSSL to use HTTPS.")
1625
1626
1627 ctx = SSL.Context(SSL.SSLv23_METHOD)
1628 ctx.use_privatekey_file(self.ssl_private_key)
1629 ctx.use_certificate_file(self.ssl_certificate)
1630 self.socket = SSLConnection(ctx, self.socket)
1631 self.populate_ssl_environ()
1632
1633
1634
1635 if (not isinstance(self.bind_addr, basestring)
1636 and self.bind_addr[0] == '::' and family == socket.AF_INET6):
1637 try:
1638 self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
1639 except (AttributeError, socket.error):
1640
1641
1642 pass
1643
1644 self.socket.bind(self.bind_addr)
1645
1701
1708 interrupt = property(_get_interrupt, _set_interrupt,
1709 doc="Set this to an Exception instance to "
1710 "interrupt the server.")
1711
1713 """Gracefully shutdown a server that is serving forever."""
1714 self.ready = False
1715
1716 sock = getattr(self, "socket", None)
1717 if sock:
1718 if not isinstance(self.bind_addr, basestring):
1719
1720 try:
1721 host, port = sock.getsockname()[:2]
1722 except socket.error, x:
1723 if x.args[0] not in socket_errors_to_ignore:
1724 raise
1725 else:
1726
1727
1728
1729
1730 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1731 socket.SOCK_STREAM):
1732 af, socktype, proto, canonname, sa = res
1733 s = None
1734 try:
1735 s = socket.socket(af, socktype, proto)
1736
1737
1738 s.settimeout(1.0)
1739 s.connect((host, port))
1740 s.close()
1741 except socket.error:
1742 if s:
1743 s.close()
1744 if hasattr(sock, "close"):
1745 sock.close()
1746 self.socket = None
1747
1748 self.requests.stop(self.shutdown_timeout)
1749
1751 """Create WSGI environ entries to be merged into each request."""
1752 cert = open(self.ssl_certificate, 'rb').read()
1753 cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
1754 ssl_environ = {
1755 "wsgi.url_scheme": "https",
1756 "HTTPS": "on",
1757
1758
1759
1760
1761
1762 }
1763
1764
1765 ssl_environ.update({
1766 'SSL_SERVER_M_VERSION': cert.get_version(),
1767 'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
1768
1769
1770 })
1771
1772 for prefix, dn in [("I", cert.get_issuer()),
1773 ("S", cert.get_subject())]:
1774
1775
1776
1777 dnstr = str(dn)[18:-2]
1778
1779 wsgikey = 'SSL_SERVER_%s_DN' % prefix
1780 ssl_environ[wsgikey] = dnstr
1781
1782
1783
1784 while dnstr:
1785 pos = dnstr.rfind("=")
1786 dnstr, value = dnstr[:pos], dnstr[pos + 1:]
1787 pos = dnstr.rfind("/")
1788 dnstr, key = dnstr[:pos], dnstr[pos + 1:]
1789 if key and value:
1790 wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
1791 ssl_environ[wsgikey] = value
1792
1793 self.environ.update(ssl_environ)
1794