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
368
369
370
371
372 environ["PATH_INFO"] = path
373
374
375
376 environ["QUERY_STRING"] = qs
377
378
379
380
381
382
383
384
385
386
387
388
389
390 rp = int(req_protocol[5]), int(req_protocol[7])
391 server_protocol = environ["ACTUAL_SERVER_PROTOCOL"]
392 sp = int(server_protocol[5]), int(server_protocol[7])
393 if sp[0] != rp[0]:
394 self.simple_response("505 HTTP Version Not Supported")
395 return
396
397 environ["SERVER_PROTOCOL"] = req_protocol
398 self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
399
400
401 if location:
402 environ["SERVER_NAME"] = location
403
404
405 try:
406 self.read_headers()
407 except ValueError, ex:
408 self.simple_response("400 Bad Request", repr(ex.args))
409 return
410
411 mrbs = self.max_request_body_size
412 if mrbs and int(environ.get("CONTENT_LENGTH", 0)) > mrbs:
413 self.simple_response("413 Request Entity Too Large")
414 return
415
416
417 if self.response_protocol == "HTTP/1.1":
418
419 if environ.get("HTTP_CONNECTION", "") == "close":
420 self.close_connection = True
421 else:
422
423 if environ.get("HTTP_CONNECTION", "") != "Keep-Alive":
424 self.close_connection = True
425
426
427 te = None
428 if self.response_protocol == "HTTP/1.1":
429 te = environ.get("HTTP_TRANSFER_ENCODING")
430 if te:
431 te = [x.strip().lower() for x in te.split(",") if x.strip()]
432
433 self.chunked_read = False
434
435 if te:
436 for enc in te:
437 if enc == "chunked":
438 self.chunked_read = True
439 else:
440
441
442 self.simple_response("501 Unimplemented")
443 self.close_connection = True
444 return
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463 if environ.get("HTTP_EXPECT", "") == "100-continue":
464 self.simple_response(100)
465
466 self.ready = True
467
469 """Read header lines from the incoming stream."""
470 environ = self.environ
471
472 while True:
473 line = self.rfile.readline()
474 if not line:
475
476 raise ValueError("Illegal end of headers.")
477
478 if line == '\r\n':
479
480 break
481
482 if line[0] in ' \t':
483
484 v = line.strip()
485 else:
486 k, v = line.split(":", 1)
487 k, v = k.strip().upper(), v.strip()
488 envname = "HTTP_" + k.replace("-", "_")
489
490 if k in comma_separated_headers:
491 existing = environ.get(envname)
492 if existing:
493 v = ", ".join((existing, v))
494 environ[envname] = v
495
496 ct = environ.pop("HTTP_CONTENT_TYPE", None)
497 if ct is not None:
498 environ["CONTENT_TYPE"] = ct
499 cl = environ.pop("HTTP_CONTENT_LENGTH", None)
500 if cl is not None:
501 environ["CONTENT_LENGTH"] = cl
502
504 """Decode the 'chunked' transfer coding."""
505 cl = 0
506 data = StringIO.StringIO()
507 while True:
508 line = self.rfile.readline().strip().split(";", 1)
509 chunk_size = int(line.pop(0), 16)
510 if chunk_size <= 0:
511 break
512
513 cl += chunk_size
514 data.write(self.rfile.read(chunk_size))
515 crlf = self.rfile.read(2)
516 if crlf != "\r\n":
517 self.simple_response("400 Bad Request",
518 "Bad chunked transfer coding "
519 "(expected '\\r\\n', got %r)" % crlf)
520 return
521
522
523 self.read_headers()
524
525 data.seek(0)
526 self.environ["wsgi.input"] = data
527 self.environ["CONTENT_LENGTH"] = str(cl) or ""
528 return True
529
531 """Call the appropriate WSGI app and write its iterable output."""
532
533
534
535 if self.chunked_read:
536
537 self.rfile.maxlen = self.max_request_body_size
538 else:
539 cl = int(self.environ.get("CONTENT_LENGTH", 0))
540 if self.max_request_body_size:
541 self.rfile.maxlen = min(cl, self.max_request_body_size)
542 else:
543 self.rfile.maxlen = cl
544 self.rfile.bytes_read = 0
545
546 try:
547 self._respond()
548 except MaxSizeExceeded:
549 if not self.sent_headers:
550 self.simple_response("413 Request Entity Too Large")
551 return
552
554 if self.chunked_read:
555 if not self.decode_chunked():
556 self.close_connection = True
557 return
558
559 response = self.wsgi_app(self.environ, self.start_response)
560 try:
561 for chunk in response:
562
563
564
565
566
567
568 if chunk:
569 self.write(chunk)
570 finally:
571 if hasattr(response, "close"):
572 response.close()
573
574 if (self.ready and not self.sent_headers):
575 self.sent_headers = True
576 self.send_headers()
577 if self.chunked_write:
578 self.wfile.sendall("0\r\n\r\n")
579
581 """Write a simple response back to the client."""
582 status = str(status)
583 buf = ["%s %s\r\n" % (self.environ['ACTUAL_SERVER_PROTOCOL'], status),
584 "Content-Length: %s\r\n" % len(msg),
585 "Content-Type: text/plain\r\n"]
586
587 if status[:3] == "413" and self.response_protocol == 'HTTP/1.1':
588
589 self.close_connection = True
590 buf.append("Connection: close\r\n")
591
592 buf.append("\r\n")
593 if msg:
594 buf.append(msg)
595
596 try:
597 self.wfile.sendall("".join(buf))
598 except socket.error, x:
599 if x.args[0] not in socket_errors_to_ignore:
600 raise
601
603 """WSGI callable to begin the HTTP response."""
604
605
606 if self.started_response and not exc_info:
607 raise AssertionError("WSGI start_response called a second "
608 "time with no exc_info.")
609
610
611
612
613 if self.sent_headers:
614 try:
615 raise exc_info[0], exc_info[1], exc_info[2]
616 finally:
617 exc_info = None
618
619 self.started_response = True
620 self.status = status
621 self.outheaders.extend(headers)
622 return self.write
623
625 """WSGI callable to write unbuffered data to the client.
626
627 This method is also used internally by start_response (to write
628 data from the iterable returned by the WSGI application).
629 """
630 if not self.started_response:
631 raise AssertionError("WSGI write called before start_response.")
632
633 if not self.sent_headers:
634 self.sent_headers = True
635 self.send_headers()
636
637 if self.chunked_write and chunk:
638 buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"]
639 self.wfile.sendall("".join(buf))
640 else:
641 self.wfile.sendall(chunk)
642
644 """Assert, process, and send the HTTP response message-headers."""
645 hkeys = [key.lower() for key, value in self.outheaders]
646 status = int(self.status[:3])
647
648 if status == 413:
649
650 self.close_connection = True
651 elif "content-length" not in hkeys:
652
653
654
655 if status < 200 or status in (204, 205, 304):
656 pass
657 else:
658 if (self.response_protocol == 'HTTP/1.1'
659 and self.environ["REQUEST_METHOD"] != 'HEAD'):
660
661 self.chunked_write = True
662 self.outheaders.append(("Transfer-Encoding", "chunked"))
663 else:
664
665 self.close_connection = True
666
667 if "connection" not in hkeys:
668 if self.response_protocol == 'HTTP/1.1':
669
670 if self.close_connection:
671 self.outheaders.append(("Connection", "close"))
672 else:
673
674 if not self.close_connection:
675 self.outheaders.append(("Connection", "Keep-Alive"))
676
677 if (not self.close_connection) and (not self.chunked_read):
678
679
680
681
682
683
684
685
686
687
688
689
690 size = self.rfile.maxlen - self.rfile.bytes_read
691 if size > 0:
692 self.rfile.read(size)
693
694 if "date" not in hkeys:
695 self.outheaders.append(("Date", rfc822.formatdate()))
696
697 if "server" not in hkeys:
698 self.outheaders.append(("Server", self.environ['SERVER_SOFTWARE']))
699
700 buf = [self.environ['ACTUAL_SERVER_PROTOCOL'], " ", self.status, "\r\n"]
701 try:
702 buf += [k + ": " + v + "\r\n" for k, v in self.outheaders]
703 except TypeError:
704 if not isinstance(k, str):
705 raise TypeError("WSGI response header key %r is not a string.")
706 if not isinstance(v, str):
707 raise TypeError("WSGI response header value %r is not a string.")
708 else:
709 raise
710 buf.append("\r\n")
711 self.wfile.sendall("".join(buf))
712
713
715 """Exception raised when a client speaks HTTP to an HTTPS socket."""
716 pass
717
718
720 """Exception raised when the SSL implementation signals a fatal alert."""
721 pass
722
723
724 if not _fileobject_uses_str_type:
726 """Faux file object attached to a socket object."""
727
729 """Sendall for non-blocking sockets."""
730 while data:
731 try:
732 bytes_sent = self.send(data)
733 data = data[bytes_sent:]
734 except socket.error, e:
735 if e.args[0] not in socket_errors_nonblocking:
736 raise
737
738 - def send(self, data):
739 return self._sock.send(data)
740
742 if self._wbuf:
743 buffer = "".join(self._wbuf)
744 self._wbuf = []
745 self.sendall(buffer)
746
747 - def recv(self, size):
755
756 - def read(self, size=-1):
757
758
759
760 rbufsize = max(self._rbufsize, self.default_bufsize)
761
762
763
764 buf = self._rbuf
765 buf.seek(0, 2)
766 if size < 0:
767
768 self._rbuf = StringIO.StringIO()
769 while True:
770 data = self.recv(rbufsize)
771 if not data:
772 break
773 buf.write(data)
774 return buf.getvalue()
775 else:
776
777 buf_len = buf.tell()
778 if buf_len >= size:
779
780 buf.seek(0)
781 rv = buf.read(size)
782 self._rbuf = StringIO.StringIO()
783 self._rbuf.write(buf.read())
784 return rv
785
786 self._rbuf = StringIO.StringIO()
787 while True:
788 left = size - buf_len
789
790
791
792
793
794 data = self.recv(left)
795 if not data:
796 break
797 n = len(data)
798 if n == size and not buf_len:
799
800
801
802
803
804 return data
805 if n == left:
806 buf.write(data)
807 del data
808 break
809 assert n <= left, "recv(%d) returned %d bytes" % (left, n)
810 buf.write(data)
811 buf_len += n
812 del data
813
814 return buf.getvalue()
815
817 buf = self._rbuf
818 buf.seek(0, 2)
819 if buf.tell() > 0:
820
821 buf.seek(0)
822 bline = buf.readline(size)
823 if bline.endswith('\n') or len(bline) == size:
824 self._rbuf = StringIO.StringIO()
825 self._rbuf.write(buf.read())
826 return bline
827 del bline
828 if size < 0:
829
830 if self._rbufsize <= 1:
831
832 buf.seek(0)
833 buffers = [buf.read()]
834 self._rbuf = StringIO.StringIO()
835 data = None
836 recv = self.recv
837 while data != "\n":
838 data = recv(1)
839 if not data:
840 break
841 buffers.append(data)
842 return "".join(buffers)
843
844 buf.seek(0, 2)
845 self._rbuf = StringIO.StringIO()
846 while True:
847 data = self.recv(self._rbufsize)
848 if not data:
849 break
850 nl = data.find('\n')
851 if nl >= 0:
852 nl += 1
853 buf.write(data[:nl])
854 self._rbuf.write(data[nl:])
855 del data
856 break
857 buf.write(data)
858 return buf.getvalue()
859 else:
860
861 buf.seek(0, 2)
862 buf_len = buf.tell()
863 if buf_len >= size:
864 buf.seek(0)
865 rv = buf.read(size)
866 self._rbuf = StringIO.StringIO()
867 self._rbuf.write(buf.read())
868 return rv
869 self._rbuf = StringIO.StringIO()
870 while True:
871 data = self.recv(self._rbufsize)
872 if not data:
873 break
874 left = size - buf_len
875
876 nl = data.find('\n', 0, left)
877 if nl >= 0:
878 nl += 1
879
880 self._rbuf.write(data[nl:])
881 if buf_len:
882 buf.write(data[:nl])
883 break
884 else:
885
886
887 return data[:nl]
888 n = len(data)
889 if n == size and not buf_len:
890
891
892 return data
893 if n >= left:
894 buf.write(data[:left])
895 self._rbuf.write(data[left:])
896 break
897 buf.write(data)
898 buf_len += n
899
900 return buf.getvalue()
901
902 else:
904 """Faux file object attached to a socket object."""
905
907 """Sendall for non-blocking sockets."""
908 while data:
909 try:
910 bytes_sent = self.send(data)
911 data = data[bytes_sent:]
912 except socket.error, e:
913 if e.args[0] not in socket_errors_nonblocking:
914 raise
915
916 - def send(self, data):
917 return self._sock.send(data)
918
920 if self._wbuf:
921 buffer = "".join(self._wbuf)
922 self._wbuf = []
923 self.sendall(buffer)
924
925 - def recv(self, size):
933
934 - def read(self, size=-1):
935 if size < 0:
936
937 buffers = [self._rbuf]
938 self._rbuf = ""
939 if self._rbufsize <= 1:
940 recv_size = self.default_bufsize
941 else:
942 recv_size = self._rbufsize
943
944 while True:
945 data = self.recv(recv_size)
946 if not data:
947 break
948 buffers.append(data)
949 return "".join(buffers)
950 else:
951
952 data = self._rbuf
953 buf_len = len(data)
954 if buf_len >= size:
955 self._rbuf = data[size:]
956 return data[:size]
957 buffers = []
958 if data:
959 buffers.append(data)
960 self._rbuf = ""
961 while True:
962 left = size - buf_len
963 recv_size = max(self._rbufsize, left)
964 data = self.recv(recv_size)
965 if not data:
966 break
967 buffers.append(data)
968 n = len(data)
969 if n >= left:
970 self._rbuf = data[left:]
971 buffers[-1] = data[:left]
972 break
973 buf_len += n
974 return "".join(buffers)
975
977 data = self._rbuf
978 if size < 0:
979
980 if self._rbufsize <= 1:
981
982 assert data == ""
983 buffers = []
984 while data != "\n":
985 data = self.recv(1)
986 if not data:
987 break
988 buffers.append(data)
989 return "".join(buffers)
990 nl = data.find('\n')
991 if nl >= 0:
992 nl += 1
993 self._rbuf = data[nl:]
994 return data[:nl]
995 buffers = []
996 if data:
997 buffers.append(data)
998 self._rbuf = ""
999 while True:
1000 data = self.recv(self._rbufsize)
1001 if not data:
1002 break
1003 buffers.append(data)
1004 nl = data.find('\n')
1005 if nl >= 0:
1006 nl += 1
1007 self._rbuf = data[nl:]
1008 buffers[-1] = data[:nl]
1009 break
1010 return "".join(buffers)
1011 else:
1012
1013 nl = data.find('\n', 0, size)
1014 if nl >= 0:
1015 nl += 1
1016 self._rbuf = data[nl:]
1017 return data[:nl]
1018 buf_len = len(data)
1019 if buf_len >= size:
1020 self._rbuf = data[size:]
1021 return data[:size]
1022 buffers = []
1023 if data:
1024 buffers.append(data)
1025 self._rbuf = ""
1026 while True:
1027 data = self.recv(self._rbufsize)
1028 if not data:
1029 break
1030 buffers.append(data)
1031 left = size - buf_len
1032 nl = data.find('\n', 0, left)
1033 if nl >= 0:
1034 nl += 1
1035 self._rbuf = data[nl:]
1036 buffers[-1] = data[:nl]
1037 break
1038 n = len(data)
1039 if n >= left:
1040 self._rbuf = data[left:]
1041 buffers[-1] = data[:left]
1042 break
1043 buf_len += n
1044 return "".join(buffers)
1045
1046
1048 """SSL file object attached to a socket object."""
1049
1050 ssl_timeout = 3
1051 ssl_retry = .01
1052
1053 - def _safe_call(self, is_reader, call, *args, **kwargs):
1054 """Wrap the given call with SSL error-trapping.
1055
1056 is_reader: if False EOF errors will be raised. If True, EOF errors
1057 will return "" (to emulate normal sockets).
1058 """
1059 start = time.time()
1060 while True:
1061 try:
1062 return call(*args, **kwargs)
1063 except SSL.WantReadError:
1064
1065
1066
1067
1068 time.sleep(self.ssl_retry)
1069 except SSL.WantWriteError:
1070 time.sleep(self.ssl_retry)
1071 except SSL.SysCallError, e:
1072 if is_reader and e.args == (-1, 'Unexpected EOF'):
1073 return ""
1074
1075 errnum = e.args[0]
1076 if is_reader and errnum in socket_errors_to_ignore:
1077 return ""
1078 raise socket.error(errnum)
1079 except SSL.Error, e:
1080 if is_reader and e.args == (-1, 'Unexpected EOF'):
1081 return ""
1082
1083 thirdarg = None
1084 try:
1085 thirdarg = e.args[0][0][2]
1086 except IndexError:
1087 pass
1088
1089 if thirdarg == 'http request':
1090
1091 raise NoSSLError()
1092 raise FatalSSLAlert(*e.args)
1093 except:
1094 raise
1095
1096 if time.time() - start > self.ssl_timeout:
1097 raise socket.timeout("timed out")
1098
1099 - def recv(self, *args, **kwargs):
1100 buf = []
1101 r = super(SSL_fileobject, self).recv
1102 while True:
1103 data = self._safe_call(True, r, *args, **kwargs)
1104 buf.append(data)
1105 p = self._sock.pending()
1106 if not p:
1107 return "".join(buf)
1108
1109 - def sendall(self, *args, **kwargs):
1111
1112 - def send(self, *args, **kwargs):
1114
1115
1117 """An HTTP connection (active socket).
1118
1119 socket: the raw socket object (usually TCP) for this connection.
1120 wsgi_app: the WSGI application for this server/connection.
1121 environ: a WSGI environ template. This will be copied for each request.
1122
1123 rfile: a fileobject for reading from the socket.
1124 send: a function for writing (+ flush) to the socket.
1125 """
1126
1127 rbufsize = -1
1128 RequestHandlerClass = HTTPRequest
1129 environ = {"wsgi.version": (1, 0),
1130 "wsgi.url_scheme": "http",
1131 "wsgi.multithread": True,
1132 "wsgi.multiprocess": False,
1133 "wsgi.run_once": False,
1134 "wsgi.errors": sys.stderr,
1135 }
1136
1137 - def __init__(self, sock, wsgi_app, environ):
1138 self.socket = sock
1139 self.wsgi_app = wsgi_app
1140
1141
1142 self.environ = self.environ.copy()
1143 self.environ.update(environ)
1144
1145 if SSL and isinstance(sock, SSL.ConnectionType):
1146 timeout = sock.gettimeout()
1147 self.rfile = SSL_fileobject(sock, "rb", self.rbufsize)
1148 self.rfile.ssl_timeout = timeout
1149 self.wfile = SSL_fileobject(sock, "wb", -1)
1150 self.wfile.ssl_timeout = timeout
1151 else:
1152 self.rfile = CP_fileobject(sock, "rb", self.rbufsize)
1153 self.wfile = CP_fileobject(sock, "wb", -1)
1154
1155
1156
1157
1158
1159 self.environ["wsgi.input"] = SizeCheckWrapper(self.rfile, 0)
1160
1162 """Read each request and respond appropriately."""
1163 try:
1164 while True:
1165
1166
1167
1168 req = None
1169 req = self.RequestHandlerClass(self.wfile, self.environ,
1170 self.wsgi_app)
1171
1172
1173 req.parse_request()
1174 if not req.ready:
1175 return
1176
1177 req.respond()
1178 if req.close_connection:
1179 return
1180
1181 except socket.error, e:
1182 errnum = e.args[0]
1183 if errnum == 'timed out':
1184 if req and not req.sent_headers:
1185 req.simple_response("408 Request Timeout")
1186 elif errnum not in socket_errors_to_ignore:
1187 if req and not req.sent_headers:
1188 req.simple_response("500 Internal Server Error",
1189 format_exc())
1190 return
1191 except (KeyboardInterrupt, SystemExit):
1192 raise
1193 except FatalSSLAlert, e:
1194
1195 return
1196 except NoSSLError:
1197
1198 req.wfile = CP_fileobject(self.socket, "wb", -1)
1199 if req and not req.sent_headers:
1200 req.simple_response("400 Bad Request",
1201 "The client sent a plain HTTP request, but "
1202 "this server only speaks HTTPS on this port.")
1203 except Exception, e:
1204 if req and not req.sent_headers:
1205 req.simple_response("500 Internal Server Error", format_exc())
1206
1208 """Close the socket underlying this connection."""
1209 self.rfile.close()
1210
1211
1212
1213
1214
1215
1216 self.socket._sock.close()
1217
1218 self.socket.close()
1219
1220
1228
1229
1230 _SHUTDOWNREQUEST = None
1231
1233 """Thread which continuously polls a Queue for Connection objects.
1234
1235 server: the HTTP Server which spawned this thread, and which owns the
1236 Queue and is placing active connections into it.
1237 ready: a simple flag for the calling server to know when this thread
1238 has begun polling the Queue.
1239
1240 Due to the timing issues of polling a Queue, a WorkerThread does not
1241 check its own 'ready' flag after it has started. To stop the thread,
1242 it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
1243 (one for each running WorkerThread).
1244 """
1245
1246 conn = None
1247
1249 self.ready = False
1250 self.server = server
1251 threading.Thread.__init__(self)
1252
1269
1270
1272 """A Request Queue for the CherryPyWSGIServer which pools threads.
1273
1274 ThreadPool objects must provide min, get(), put(obj), start()
1275 and stop(timeout) attributes.
1276 """
1277
1278 - def __init__(self, server, min=10, max=-1):
1279 self.server = server
1280 self.min = min
1281 self.max = max
1282 self._threads = []
1283 self._queue = Queue.Queue()
1284 self.get = self._queue.get
1285
1287 """Start the pool of threads."""
1288 for i in xrange(self.min):
1289 self._threads.append(WorkerThread(self.server))
1290 for worker in self._threads:
1291 worker.setName("CP WSGIServer " + worker.getName())
1292 worker.start()
1293 for worker in self._threads:
1294 while not worker.ready:
1295 time.sleep(.1)
1296
1298 """Number of worker threads which are idle. Read-only."""
1299 return len([t for t in self._threads if t.conn is None])
1300 idle = property(_get_idle, doc=_get_idle.__doc__)
1301
1302 - def put(self, obj):
1306
1307 - def grow(self, amount):
1308 """Spawn new worker threads (not above self.max)."""
1309 for i in xrange(amount):
1310 if self.max > 0 and len(self._threads) >= self.max:
1311 break
1312 worker = WorkerThread(self.server)
1313 worker.setName("CP WSGIServer " + worker.getName())
1314 self._threads.append(worker)
1315 worker.start()
1316
1318 """Kill off worker threads (not below self.min)."""
1319
1320
1321 for t in self._threads:
1322 if not t.isAlive():
1323 self._threads.remove(t)
1324 amount -= 1
1325
1326 if amount > 0:
1327 for i in xrange(min(amount, len(self._threads) - self.min)):
1328
1329
1330
1331
1332 self._queue.put(_SHUTDOWNREQUEST)
1333
1334 - def stop(self, timeout=5):
1335
1336
1337 for worker in self._threads:
1338 self._queue.put(_SHUTDOWNREQUEST)
1339
1340
1341 current = threading.currentThread()
1342 while self._threads:
1343 worker = self._threads.pop()
1344 if worker is not current and worker.isAlive():
1345 try:
1346 if timeout is None or timeout < 0:
1347 worker.join()
1348 else:
1349 worker.join(timeout)
1350 if worker.isAlive():
1351
1352
1353 c = worker.conn
1354 if c and not c.rfile.closed:
1355 if SSL and isinstance(c.socket, SSL.ConnectionType):
1356
1357 c.socket.shutdown()
1358 else:
1359 c.socket.shutdown(socket.SHUT_RD)
1360 worker.join()
1361 except (AssertionError,
1362
1363
1364 KeyboardInterrupt), exc1:
1365 pass
1366
1367
1368
1370 """A thread-safe wrapper for an SSL.Connection.
1371
1372 *args: the arguments to create the wrapped SSL.Connection(*args).
1373 """
1374
1376 self._ssl_conn = SSL.Connection(*args)
1377 self._lock = threading.RLock()
1378
1379 for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
1380 'renegotiate', 'bind', 'listen', 'connect', 'accept',
1381 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list',
1382 'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
1383 'makefile', 'get_app_data', 'set_app_data', 'state_string',
1384 'sock_shutdown', 'get_peer_certificate', 'want_read',
1385 'want_write', 'set_connect_state', 'set_accept_state',
1386 'connect_ex', 'sendall', 'settimeout'):
1387 exec """def %s(self, *args):
1388 self._lock.acquire()
1389 try:
1390 return self._ssl_conn.%s(*args)
1391 finally:
1392 self._lock.release()
1393 """ % (f, f)
1394
1395
1396 try:
1397 import fcntl
1398 except ImportError:
1399 try:
1400 from ctypes import windll, WinError
1401 except ImportError:
1403 """Dummy function, since neither fcntl nor ctypes are available."""
1404 pass
1405 else:
1407 """Mark the given socket fd as non-inheritable (Windows)."""
1408 if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
1409 raise WinError()
1410 else:
1412 """Mark the given socket fd as non-inheritable (POSIX)."""
1413 fd = sock.fileno()
1414 old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
1415 fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
1416
1417
1419 """An HTTP server for WSGI.
1420
1421 bind_addr: The interface on which to listen for connections.
1422 For TCP sockets, a (host, port) tuple. Host values may be any IPv4
1423 or IPv6 address, or any valid hostname. The string 'localhost' is a
1424 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
1425 The string '0.0.0.0' is a special IPv4 entry meaning "any active
1426 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
1427 IPv6. The empty string or None are not allowed.
1428
1429 For UNIX sockets, supply the filename as a string.
1430 wsgi_app: the WSGI 'application callable'; multiple WSGI applications
1431 may be passed as (path_prefix, app) pairs.
1432 numthreads: the number of worker threads to create (default 10).
1433 server_name: the string to set for WSGI's SERVER_NAME environ entry.
1434 Defaults to socket.gethostname().
1435 max: the maximum number of queued requests (defaults to -1 = no limit).
1436 request_queue_size: the 'backlog' argument to socket.listen();
1437 specifies the maximum number of queued connections (default 5).
1438 timeout: the timeout in seconds for accepted connections (default 10).
1439
1440 nodelay: if True (the default since 3.1), sets the TCP_NODELAY socket
1441 option.
1442
1443 protocol: the version string to write in the Status-Line of all
1444 HTTP responses. For example, "HTTP/1.1" (the default). This
1445 also limits the supported features used in the response.
1446
1447
1448 SSL/HTTPS
1449 ---------
1450 The OpenSSL module must be importable for SSL functionality.
1451 You can obtain it from http://pyopenssl.sourceforge.net/
1452
1453 ssl_certificate: the filename of the server SSL certificate.
1454 ssl_privatekey: the filename of the server's private key file.
1455
1456 If either of these is None (both are None by default), this server
1457 will not use SSL. If both are given and are valid, they will be read
1458 on server start and used in the SSL context for the listening socket.
1459 """
1460
1461 protocol = "HTTP/1.1"
1462 _bind_addr = "127.0.0.1"
1463 version = "CherryPy/3.1.0"
1464 ready = False
1465 _interrupt = None
1466
1467 nodelay = True
1468
1469 ConnectionClass = HTTPConnection
1470 environ = {}
1471
1472
1473 ssl_certificate = None
1474 ssl_private_key = None
1475
1476 - def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
1477 max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
1478 self.requests = ThreadPool(self, min=numthreads or 1, max=max)
1479
1480 if callable(wsgi_app):
1481
1482
1483 self.wsgi_app = wsgi_app
1484 else:
1485
1486
1487
1488 warnings.warn("The ability to pass multiple apps is deprecated "
1489 "and will be removed in 3.2. You should explicitly "
1490 "include a WSGIPathInfoDispatcher instead.",
1491 DeprecationWarning)
1492 self.wsgi_app = WSGIPathInfoDispatcher(wsgi_app)
1493
1494 self.bind_addr = bind_addr
1495 if not server_name:
1496 server_name = socket.gethostname()
1497 self.server_name = server_name
1498 self.request_queue_size = request_queue_size
1499
1500 self.timeout = timeout
1501 self.shutdown_timeout = shutdown_timeout
1502
1504 return self.requests.min
1506 self.requests.min = value
1507 numthreads = property(_get_numthreads, _set_numthreads)
1508
1510 return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
1511 self.bind_addr)
1512
1516 if isinstance(value, tuple) and value[0] in ('', None):
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527 raise ValueError("Host values of '' or None are not allowed. "
1528 "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
1529 "to listen on all active interfaces.")
1530 self._bind_addr = value
1531 bind_addr = property(_get_bind_addr, _set_bind_addr,
1532 doc="""The interface on which to listen for connections.
1533
1534 For TCP sockets, a (host, port) tuple. Host values may be any IPv4
1535 or IPv6 address, or any valid hostname. The string 'localhost' is a
1536 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
1537 The string '0.0.0.0' is a special IPv4 entry meaning "any active
1538 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
1539 IPv6. The empty string or None are not allowed.
1540
1541 For UNIX sockets, supply the filename as a string.""")
1542
1544 """Run the server forever."""
1545
1546
1547
1548
1549 self._interrupt = None
1550
1551
1552 if isinstance(self.bind_addr, basestring):
1553
1554
1555
1556 try: os.unlink(self.bind_addr)
1557 except: pass
1558
1559
1560 try: os.chmod(self.bind_addr, 0777)
1561 except: pass
1562
1563 info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1564 else:
1565
1566
1567 host, port = self.bind_addr
1568 try:
1569 info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1570 socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
1571 except socket.gaierror:
1572
1573 info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1574
1575 self.socket = None
1576 msg = "No socket could be created"
1577 for res in info:
1578 af, socktype, proto, canonname, sa = res
1579 try:
1580 self.bind(af, socktype, proto)
1581 except socket.error, msg:
1582 if self.socket:
1583 self.socket.close()
1584 self.socket = None
1585 continue
1586 break
1587 if not self.socket:
1588 raise socket.error, msg
1589
1590
1591 self.socket.settimeout(1)
1592 self.socket.listen(self.request_queue_size)
1593
1594
1595 self.requests.start()
1596
1597 self.ready = True
1598 while self.ready:
1599 self.tick()
1600 if self.interrupt:
1601 while self.interrupt is True:
1602
1603 time.sleep(0.1)
1604 if self.interrupt:
1605 raise self.interrupt
1606
1607 - def bind(self, family, type, proto=0):
1608 """Create (or recreate) the actual socket object."""
1609 self.socket = socket.socket(family, type, proto)
1610 prevent_socket_inheritance(self.socket)
1611 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1612 if self.nodelay:
1613 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1614 if self.ssl_certificate and self.ssl_private_key:
1615 if SSL is None:
1616 raise ImportError("You must install pyOpenSSL to use HTTPS.")
1617
1618
1619 ctx = SSL.Context(SSL.SSLv23_METHOD)
1620 ctx.use_privatekey_file(self.ssl_private_key)
1621 ctx.use_certificate_file(self.ssl_certificate)
1622 self.socket = SSLConnection(ctx, self.socket)
1623 self.populate_ssl_environ()
1624
1625
1626
1627 if (not isinstance(self.bind_addr, basestring)
1628 and self.bind_addr[0] == '::' and family == socket.AF_INET6):
1629 try:
1630 self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
1631 except (AttributeError, socket.error):
1632
1633
1634 pass
1635
1636 self.socket.bind(self.bind_addr)
1637
1639 """Accept a new connection and put it on the Queue."""
1640 try:
1641 s, addr = self.socket.accept()
1642 prevent_socket_inheritance(s)
1643 if not self.ready:
1644 return
1645 if hasattr(s, 'settimeout'):
1646 s.settimeout(self.timeout)
1647
1648 environ = self.environ.copy()
1649
1650
1651 if environ.get("SERVER_SOFTWARE") is None:
1652 environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version
1653
1654
1655
1656 environ["ACTUAL_SERVER_PROTOCOL"] = self.protocol
1657 environ["SERVER_NAME"] = self.server_name
1658
1659 if isinstance(self.bind_addr, basestring):
1660
1661
1662 environ["SERVER_PORT"] = ""
1663 else:
1664 environ["SERVER_PORT"] = str(self.bind_addr[1])
1665
1666
1667 environ["REMOTE_ADDR"] = addr[0]
1668 environ["REMOTE_PORT"] = str(addr[1])
1669
1670 conn = self.ConnectionClass(s, self.wsgi_app, environ)
1671 self.requests.put(conn)
1672 except socket.timeout:
1673
1674
1675
1676 return
1677 except socket.error, x:
1678 if x.args[0] in socket_error_eintr:
1679
1680
1681
1682
1683
1684 return
1685 if x.args[0] in socket_errors_nonblocking:
1686
1687 return
1688 if x.args[0] in socket_errors_to_ignore:
1689
1690
1691 return
1692 raise
1693
1700 interrupt = property(_get_interrupt, _set_interrupt,
1701 doc="Set this to an Exception instance to "
1702 "interrupt the server.")
1703
1705 """Gracefully shutdown a server that is serving forever."""
1706 self.ready = False
1707
1708 sock = getattr(self, "socket", None)
1709 if sock:
1710 if not isinstance(self.bind_addr, basestring):
1711
1712 try:
1713 host, port = sock.getsockname()[:2]
1714 except socket.error, x:
1715 if x.args[1] != "Bad file descriptor":
1716 raise
1717 else:
1718
1719
1720
1721
1722 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1723 socket.SOCK_STREAM):
1724 af, socktype, proto, canonname, sa = res
1725 s = None
1726 try:
1727 s = socket.socket(af, socktype, proto)
1728
1729
1730 s.settimeout(1.0)
1731 s.connect((host, port))
1732 s.close()
1733 except socket.error:
1734 if s:
1735 s.close()
1736 if hasattr(sock, "close"):
1737 sock.close()
1738 self.socket = None
1739
1740 self.requests.stop(self.shutdown_timeout)
1741
1743 """Create WSGI environ entries to be merged into each request."""
1744 cert = open(self.ssl_certificate, 'rb').read()
1745 cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
1746 ssl_environ = {
1747 "wsgi.url_scheme": "https",
1748 "HTTPS": "on",
1749
1750
1751
1752
1753
1754 }
1755
1756
1757 ssl_environ.update({
1758 'SSL_SERVER_M_VERSION': cert.get_version(),
1759 'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
1760
1761
1762 })
1763
1764 for prefix, dn in [("I", cert.get_issuer()),
1765 ("S", cert.get_subject())]:
1766
1767
1768
1769 dnstr = str(dn)[18:-2]
1770
1771 wsgikey = 'SSL_SERVER_%s_DN' % prefix
1772 ssl_environ[wsgikey] = dnstr
1773
1774
1775
1776 while dnstr:
1777 pos = dnstr.rfind("=")
1778 dnstr, value = dnstr[:pos], dnstr[pos + 1:]
1779 pos = dnstr.rfind("/")
1780 dnstr, key = dnstr[:pos], dnstr[pos + 1:]
1781 if key and value:
1782 wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
1783 ssl_environ[wsgikey] = value
1784
1785 self.environ.update(ssl_environ)
1786