1 """A high-speed, production ready, thread pooled, generic HTTP 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!']
13
14 server = wsgiserver.CherryPyWSGIServer(
15 ('0.0.0.0', 8070), my_crazy_app,
16 server_name='www.cherrypy.example')
17 server.start()
18
19 The CherryPy WSGI server can serve as many WSGI applications
20 as you want in one instance by using a WSGIPathInfoDispatcher::
21
22 d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app})
23 server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d)
24
25 Want SSL support? Just set server.ssl_adapter to an SSLAdapter instance.
26
27 This won't call the CherryPy engine (application side) at all, only the
28 HTTP server, which is independent from the rest of CherryPy. Don't
29 let the name "CherryPyWSGIServer" throw you; the name merely reflects
30 its origin, not its coupling.
31
32 For those of you wanting to understand internals of this module, here's the
33 basic call flow. The server's listening thread runs a very tight loop,
34 sticking incoming connections onto a Queue::
35
36 server = CherryPyWSGIServer(...)
37 server.start()
38 while True:
39 tick()
40 # This blocks until a request comes in:
41 child = socket.accept()
42 conn = HTTPConnection(child, ...)
43 server.requests.put(conn)
44
45 Worker threads are kept in a pool and poll the Queue, popping off and then
46 handling each connection in turn. Each connection can consist of an arbitrary
47 number of requests and their responses, so we run a nested loop::
48
49 while True:
50 conn = server.requests.get()
51 conn.communicate()
52 -> while True:
53 req = HTTPRequest(...)
54 req.parse_request()
55 -> # Read the Request-Line, e.g. "GET /page HTTP/1.1"
56 req.rfile.readline()
57 read_headers(req.rfile, req.inheaders)
58 req.respond()
59 -> response = app(...)
60 try:
61 for chunk in response:
62 if chunk:
63 req.write(chunk)
64 finally:
65 if hasattr(response, "close"):
66 response.close()
67 if req.close_connection:
68 return
69 """
70
71 __all__ = ['HTTPRequest', 'HTTPConnection', 'HTTPServer',
72 'SizeCheckWrapper', 'KnownLengthRFile', 'ChunkedRFile',
73 'CP_fileobject',
74 'MaxSizeExceeded', 'NoSSLError', 'FatalSSLAlert',
75 'WorkerThread', 'ThreadPool', 'SSLAdapter',
76 'CherryPyWSGIServer',
77 'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
78 'WSGIPathInfoDispatcher', 'get_ssl_adapter_class']
79
80 import os
81 try:
82 import queue
83 except:
84 import Queue as queue
85 import re
86 import rfc822
87 import socket
88 import sys
89 if 'win' in sys.platform and hasattr(socket, "AF_INET6"):
90 if not hasattr(socket, 'IPPROTO_IPV6'):
91 socket.IPPROTO_IPV6 = 41
92 if not hasattr(socket, 'IPV6_V6ONLY'):
93 socket.IPV6_V6ONLY = 27
94 try:
95 import cStringIO as StringIO
96 except ImportError:
97 import StringIO
98 DEFAULT_BUFFER_SIZE = -1
99
100
102
103 """Faux socket with the minimal interface required by pypy"""
104
107
108 _fileobject_uses_str_type = isinstance(
109 socket._fileobject(FauxSocket())._rbuf, basestring)
110 del FauxSocket
111
112 import threading
113 import time
114 import traceback
115
116
124
125 import operator
126
127 from urllib import unquote
128 import warnings
129
130 if sys.version_info >= (3, 0):
131 bytestr = bytes
132 unicodestr = str
133 basestring = (bytes, str)
134
135 - def ntob(n, encoding='ISO-8859-1'):
136 """Return the given native string as a byte string in the given
137 encoding.
138 """
139
140 return n.encode(encoding)
141 else:
142 bytestr = str
143 unicodestr = unicode
144 basestring = basestring
145
146 - def ntob(n, encoding='ISO-8859-1'):
147 """Return the given native string as a byte string in the given
148 encoding.
149 """
150
151
152
153 return n
154
155 LF = ntob('\n')
156 CRLF = ntob('\r\n')
157 TAB = ntob('\t')
158 SPACE = ntob(' ')
159 COLON = ntob(':')
160 SEMICOLON = ntob(';')
161 EMPTY = ntob('')
162 NUMBER_SIGN = ntob('#')
163 QUESTION_MARK = ntob('?')
164 ASTERISK = ntob('*')
165 FORWARD_SLASH = ntob('/')
166 quoted_slash = re.compile(ntob("(?i)%2F"))
167
168 import errno
169
170
172 """Return error numbers for all errors in errnames on this platform.
173
174 The 'errno' module contains different global constants depending on
175 the specific platform (OS). This function will return the list of
176 numeric values for a given list of potential names.
177 """
178 errno_names = dir(errno)
179 nums = [getattr(errno, k) for k in errnames if k in errno_names]
180
181 return list(dict.fromkeys(nums).keys())
182
183 socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
184
185 socket_errors_to_ignore = plat_specific_errors(
186 "EPIPE",
187 "EBADF", "WSAEBADF",
188 "ENOTSOCK", "WSAENOTSOCK",
189 "ETIMEDOUT", "WSAETIMEDOUT",
190 "ECONNREFUSED", "WSAECONNREFUSED",
191 "ECONNRESET", "WSAECONNRESET",
192 "ECONNABORTED", "WSAECONNABORTED",
193 "ENETRESET", "WSAENETRESET",
194 "EHOSTDOWN", "EHOSTUNREACH",
195 )
196 socket_errors_to_ignore.append("timed out")
197 socket_errors_to_ignore.append("The read operation timed out")
198
199 socket_errors_nonblocking = plat_specific_errors(
200 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
201
202 comma_separated_headers = [
203 ntob(h) for h in
204 ['Accept', 'Accept-Charset', 'Accept-Encoding',
205 'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control',
206 'Connection', 'Content-Encoding', 'Content-Language', 'Expect',
207 'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE',
208 'Trailer', 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning',
209 'WWW-Authenticate']
210 ]
211
212
213 import logging
214 if not hasattr(logging, 'statistics'):
215 logging.statistics = {}
216
217
219 """Read headers from the given stream into the given header dict.
220
221 If hdict is None, a new header dict is created. Returns the populated
222 header dict.
223
224 Headers which are repeated are folded together using a comma if their
225 specification so dictates.
226
227 This function raises ValueError when the read bytes violate the HTTP spec.
228 You should probably return "400 Bad Request" if this happens.
229 """
230 if hdict is None:
231 hdict = {}
232
233 while True:
234 line = rfile.readline()
235 if not line:
236
237 raise ValueError("Illegal end of headers.")
238
239 if line == CRLF:
240
241 break
242 if not line.endswith(CRLF):
243 raise ValueError("HTTP requires CRLF terminators")
244
245 if line[0] in (SPACE, TAB):
246
247 v = line.strip()
248 else:
249 try:
250 k, v = line.split(COLON, 1)
251 except ValueError:
252 raise ValueError("Illegal header line.")
253
254 k = k.strip().title()
255 v = v.strip()
256 hname = k
257
258 if k in comma_separated_headers:
259 existing = hdict.get(hname)
260 if existing:
261 v = ", ".join((existing, v))
262 hdict[hname] = v
263
264 return hdict
265
266
269
270
272
273 """Wraps a file-like object, raising MaxSizeExceeded if too large."""
274
276 self.rfile = rfile
277 self.maxlen = maxlen
278 self.bytes_read = 0
279
281 if self.maxlen and self.bytes_read > self.maxlen:
282 raise MaxSizeExceeded()
283
284 - def read(self, size=None):
289
308
310
311 total = 0
312 lines = []
313 line = self.readline()
314 while line:
315 lines.append(line)
316 total += len(line)
317 if 0 < sizehint <= total:
318 break
319 line = self.readline()
320 return lines
321
324
327
333
339
340
342
343 """Wraps a file-like object, returning an empty string when exhausted."""
344
345 - def __init__(self, rfile, content_length):
346 self.rfile = rfile
347 self.remaining = content_length
348
349 - def read(self, size=None):
350 if self.remaining == 0:
351 return ''
352 if size is None:
353 size = self.remaining
354 else:
355 size = min(size, self.remaining)
356
357 data = self.rfile.read(size)
358 self.remaining -= len(data)
359 return data
360
362 if self.remaining == 0:
363 return ''
364 if size is None:
365 size = self.remaining
366 else:
367 size = min(size, self.remaining)
368
369 data = self.rfile.readline(size)
370 self.remaining -= len(data)
371 return data
372
374
375 total = 0
376 lines = []
377 line = self.readline(sizehint)
378 while line:
379 lines.append(line)
380 total += len(line)
381 if 0 < sizehint <= total:
382 break
383 line = self.readline(sizehint)
384 return lines
385
388
391
396
397
399
400 """Wraps a file-like object, returning an empty string when exhausted.
401
402 This class is intended to provide a conforming wsgi.input value for
403 request entities that have been encoded with the 'chunked' transfer
404 encoding.
405 """
406
407 - def __init__(self, rfile, maxlen, bufsize=8192):
414
416 if self.closed:
417 return
418
419 line = self.rfile.readline()
420 self.bytes_read += len(line)
421
422 if self.maxlen and self.bytes_read > self.maxlen:
423 raise MaxSizeExceeded("Request Entity Too Large", self.maxlen)
424
425 line = line.strip().split(SEMICOLON, 1)
426
427 try:
428 chunk_size = line.pop(0)
429 chunk_size = int(chunk_size, 16)
430 except ValueError:
431 raise ValueError("Bad chunked transfer size: " + repr(chunk_size))
432
433 if chunk_size <= 0:
434 self.closed = True
435 return
436
437
438
439 if self.maxlen and self.bytes_read + chunk_size > self.maxlen:
440 raise IOError("Request Entity Too Large")
441
442 chunk = self.rfile.read(chunk_size)
443 self.bytes_read += len(chunk)
444 self.buffer += chunk
445
446 crlf = self.rfile.read(2)
447 if crlf != CRLF:
448 raise ValueError(
449 "Bad chunked transfer coding (expected '\\r\\n', "
450 "got " + repr(crlf) + ")")
451
452 - def read(self, size=None):
453 data = EMPTY
454 while True:
455 if size and len(data) >= size:
456 return data
457
458 if not self.buffer:
459 self._fetch()
460 if not self.buffer:
461
462 return data
463
464 if size:
465 remaining = size - len(data)
466 data += self.buffer[:remaining]
467 self.buffer = self.buffer[remaining:]
468 else:
469 data += self.buffer
470
472 data = EMPTY
473 while True:
474 if size and len(data) >= size:
475 return data
476
477 if not self.buffer:
478 self._fetch()
479 if not self.buffer:
480
481 return data
482
483 newline_pos = self.buffer.find(LF)
484 if size:
485 if newline_pos == -1:
486 remaining = size - len(data)
487 data += self.buffer[:remaining]
488 self.buffer = self.buffer[remaining:]
489 else:
490 remaining = min(size - len(data), newline_pos)
491 data += self.buffer[:remaining]
492 self.buffer = self.buffer[remaining:]
493 else:
494 if newline_pos == -1:
495 data += self.buffer
496 else:
497 data += self.buffer[:newline_pos]
498 self.buffer = self.buffer[newline_pos:]
499
501
502 total = 0
503 lines = []
504 line = self.readline(sizehint)
505 while line:
506 lines.append(line)
507 total += len(line)
508 if 0 < sizehint <= total:
509 break
510 line = self.readline(sizehint)
511 return lines
512
514 if not self.closed:
515 raise ValueError(
516 "Cannot read trailers until the request body has been read.")
517
518 while True:
519 line = self.rfile.readline()
520 if not line:
521
522 raise ValueError("Illegal end of headers.")
523
524 self.bytes_read += len(line)
525 if self.maxlen and self.bytes_read > self.maxlen:
526 raise IOError("Request Entity Too Large")
527
528 if line == CRLF:
529
530 break
531 if not line.endswith(CRLF):
532 raise ValueError("HTTP requires CRLF terminators")
533
534 yield line
535
538
540
541 total = 0
542 line = self.readline(sizehint)
543 while line:
544 yield line
545 total += len(line)
546 if 0 < sizehint <= total:
547 break
548 line = self.readline(sizehint)
549
550
552
553 """An HTTP Request (and response).
554
555 A single HTTP connection may consist of multiple request/response pairs.
556 """
557
558 server = None
559 """The HTTPServer object which is receiving this request."""
560
561 conn = None
562 """The HTTPConnection object on which this request connected."""
563
564 inheaders = {}
565 """A dict of request headers."""
566
567 outheaders = []
568 """A list of header tuples to write in the response."""
569
570 ready = False
571 """When True, the request has been parsed and is ready to begin generating
572 the response. When False, signals the calling Connection that the response
573 should not be generated and the connection should close."""
574
575 close_connection = False
576 """Signals the calling Connection that the request should close. This does
577 not imply an error! The client and/or server may each request that the
578 connection be closed."""
579
580 chunked_write = False
581 """If True, output will be encoded with the "chunked" transfer-coding.
582
583 This value is set automatically inside send_headers."""
584
604
634
636
637
638
639
640
641
642
643 request_line = self.rfile.readline()
644
645
646
647 self.started_request = True
648 if not request_line:
649 return False
650
651 if request_line == CRLF:
652
653
654
655
656 request_line = self.rfile.readline()
657 if not request_line:
658 return False
659
660 if not request_line.endswith(CRLF):
661 self.simple_response(
662 "400 Bad Request", "HTTP requires CRLF terminators")
663 return False
664
665 try:
666 method, uri, req_protocol = request_line.strip().split(SPACE, 2)
667 rp = int(req_protocol[5]), int(req_protocol[7])
668 except (ValueError, IndexError):
669 self.simple_response("400 Bad Request", "Malformed Request-Line")
670 return False
671
672 self.uri = uri
673 self.method = method
674
675
676 scheme, authority, path = self.parse_request_uri(uri)
677 if NUMBER_SIGN in path:
678 self.simple_response("400 Bad Request",
679 "Illegal #fragment in Request-URI.")
680 return False
681
682 if scheme:
683 self.scheme = scheme
684
685 qs = EMPTY
686 if QUESTION_MARK in path:
687 path, qs = path.split(QUESTION_MARK, 1)
688
689
690
691
692
693
694
695
696 try:
697 atoms = [unquote(x) for x in quoted_slash.split(path)]
698 except ValueError:
699 ex = sys.exc_info()[1]
700 self.simple_response("400 Bad Request", ex.args[0])
701 return False
702 path = "%2F".join(atoms)
703 self.path = path
704
705
706
707 self.qs = qs
708
709
710
711
712
713
714
715
716
717
718
719
720
721 sp = int(self.server.protocol[5]), int(self.server.protocol[7])
722
723 if sp[0] != rp[0]:
724 self.simple_response("505 HTTP Version Not Supported")
725 return False
726
727 self.request_protocol = req_protocol
728 self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
729
730 return True
731
733 """Read self.rfile into self.inheaders. Return success."""
734
735
736 try:
737 read_headers(self.rfile, self.inheaders)
738 except ValueError:
739 ex = sys.exc_info()[1]
740 self.simple_response("400 Bad Request", ex.args[0])
741 return False
742
743 mrbs = self.server.max_request_body_size
744 if mrbs and int(self.inheaders.get("Content-Length", 0)) > mrbs:
745 self.simple_response(
746 "413 Request Entity Too Large",
747 "The entity sent with the request exceeds the maximum "
748 "allowed bytes.")
749 return False
750
751
752 if self.response_protocol == "HTTP/1.1":
753
754 if self.inheaders.get("Connection", "") == "close":
755 self.close_connection = True
756 else:
757
758 if self.inheaders.get("Connection", "") != "Keep-Alive":
759 self.close_connection = True
760
761
762 te = None
763 if self.response_protocol == "HTTP/1.1":
764 te = self.inheaders.get("Transfer-Encoding")
765 if te:
766 te = [x.strip().lower() for x in te.split(",") if x.strip()]
767
768 self.chunked_read = False
769
770 if te:
771 for enc in te:
772 if enc == "chunked":
773 self.chunked_read = True
774 else:
775
776
777 self.simple_response("501 Unimplemented")
778 self.close_connection = True
779 return False
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798 if self.inheaders.get("Expect", "") == "100-continue":
799
800
801
802 msg = self.server.protocol + " 100 Continue\r\n\r\n"
803 try:
804 self.conn.wfile.sendall(msg)
805 except socket.error:
806 x = sys.exc_info()[1]
807 if x.args[0] not in socket_errors_to_ignore:
808 raise
809 return True
810
812 """Parse a Request-URI into (scheme, authority, path).
813
814 Note that Request-URI's must be one of::
815
816 Request-URI = "*" | absoluteURI | abs_path | authority
817
818 Therefore, a Request-URI which starts with a double forward-slash
819 cannot be a "net_path"::
820
821 net_path = "//" authority [ abs_path ]
822
823 Instead, it must be interpreted as an "abs_path" with an empty first
824 path segment::
825
826 abs_path = "/" path_segments
827 path_segments = segment *( "/" segment )
828 segment = *pchar *( ";" param )
829 param = *pchar
830 """
831 if uri == ASTERISK:
832 return None, None, uri
833
834 i = uri.find('://')
835 if i > 0 and QUESTION_MARK not in uri[:i]:
836
837
838
839
840 scheme, remainder = uri[:i].lower(), uri[i + 3:]
841 authority, path = remainder.split(FORWARD_SLASH, 1)
842 path = FORWARD_SLASH + path
843 return scheme, authority, path
844
845 if uri.startswith(FORWARD_SLASH):
846
847 return None, None, uri
848 else:
849
850 return None, uri, None
851
875
909
917
919 """Assert, process, and send the HTTP response message-headers.
920
921 You must set self.status, and self.outheaders before calling this.
922 """
923 hkeys = [key.lower() for key, value in self.outheaders]
924 status = int(self.status[:3])
925
926 if status == 413:
927
928 self.close_connection = True
929 elif "content-length" not in hkeys:
930
931
932
933 if status < 200 or status in (204, 205, 304):
934 pass
935 else:
936 if (self.response_protocol == 'HTTP/1.1'
937 and self.method != 'HEAD'):
938
939 self.chunked_write = True
940 self.outheaders.append(("Transfer-Encoding", "chunked"))
941 else:
942
943 self.close_connection = True
944
945 if "connection" not in hkeys:
946 if self.response_protocol == 'HTTP/1.1':
947
948 if self.close_connection:
949 self.outheaders.append(("Connection", "close"))
950 else:
951
952 if not self.close_connection:
953 self.outheaders.append(("Connection", "Keep-Alive"))
954
955 if (not self.close_connection) and (not self.chunked_read):
956
957
958
959
960
961
962
963
964
965
966
967
968 remaining = getattr(self.rfile, 'remaining', 0)
969 if remaining > 0:
970 self.rfile.read(remaining)
971
972 if "date" not in hkeys:
973 self.outheaders.append(("Date", rfc822.formatdate()))
974
975 if "server" not in hkeys:
976 self.outheaders.append(("Server", self.server.server_name))
977
978 buf = [self.server.protocol + SPACE + self.status + CRLF]
979 for k, v in self.outheaders:
980 buf.append(k + COLON + SPACE + v + CRLF)
981 buf.append(CRLF)
982 self.conn.wfile.sendall(EMPTY.join(buf))
983
984
986
987 """Exception raised when a client speaks HTTP to an HTTPS socket."""
988 pass
989
990
992
993 """Exception raised when the SSL implementation signals a fatal alert."""
994 pass
995
996
998
999 """Faux file object attached to a socket object."""
1000
1002 self.bytes_read = 0
1003 self.bytes_written = 0
1004 socket._fileobject.__init__(self, *args, **kwargs)
1005
1015
1016 - def send(self, data):
1017 bytes_sent = self._sock.send(data)
1018 self.bytes_written += bytes_sent
1019 return bytes_sent
1020
1022 if self._wbuf:
1023 buffer = "".join(self._wbuf)
1024 self._wbuf = []
1025 self.sendall(buffer)
1026
1027 - def recv(self, size):
1037
1038 if not _fileobject_uses_str_type:
1039 - def read(self, size=-1):
1040
1041
1042
1043
1044 rbufsize = max(self._rbufsize, self.default_bufsize)
1045
1046
1047
1048
1049 buf = self._rbuf
1050 buf.seek(0, 2)
1051 if size < 0:
1052
1053
1054 self._rbuf = StringIO.StringIO()
1055 while True:
1056 data = self.recv(rbufsize)
1057 if not data:
1058 break
1059 buf.write(data)
1060 return buf.getvalue()
1061 else:
1062
1063 buf_len = buf.tell()
1064 if buf_len >= size:
1065
1066
1067 buf.seek(0)
1068 rv = buf.read(size)
1069 self._rbuf = StringIO.StringIO()
1070 self._rbuf.write(buf.read())
1071 return rv
1072
1073
1074 self._rbuf = StringIO.StringIO()
1075 while True:
1076 left = size - buf_len
1077
1078
1079
1080
1081
1082 data = self.recv(left)
1083 if not data:
1084 break
1085 n = len(data)
1086 if n == size and not buf_len:
1087
1088
1089
1090
1091
1092 return data
1093 if n == left:
1094 buf.write(data)
1095 del data
1096 break
1097 assert n <= left, "recv(%d) returned %d bytes" % (left, n)
1098 buf.write(data)
1099 buf_len += n
1100 del data
1101
1102 return buf.getvalue()
1103
1105 buf = self._rbuf
1106 buf.seek(0, 2)
1107 if buf.tell() > 0:
1108
1109 buf.seek(0)
1110 bline = buf.readline(size)
1111 if bline.endswith('\n') or len(bline) == size:
1112 self._rbuf = StringIO.StringIO()
1113 self._rbuf.write(buf.read())
1114 return bline
1115 del bline
1116 if size < 0:
1117
1118 if self._rbufsize <= 1:
1119
1120 buf.seek(0)
1121 buffers = [buf.read()]
1122
1123 self._rbuf = StringIO.StringIO()
1124 data = None
1125 recv = self.recv
1126 while data != "\n":
1127 data = recv(1)
1128 if not data:
1129 break
1130 buffers.append(data)
1131 return "".join(buffers)
1132
1133 buf.seek(0, 2)
1134
1135 self._rbuf = StringIO.StringIO()
1136 while True:
1137 data = self.recv(self._rbufsize)
1138 if not data:
1139 break
1140 nl = data.find('\n')
1141 if nl >= 0:
1142 nl += 1
1143 buf.write(data[:nl])
1144 self._rbuf.write(data[nl:])
1145 del data
1146 break
1147 buf.write(data)
1148 return buf.getvalue()
1149 else:
1150
1151
1152 buf.seek(0, 2)
1153 buf_len = buf.tell()
1154 if buf_len >= size:
1155 buf.seek(0)
1156 rv = buf.read(size)
1157 self._rbuf = StringIO.StringIO()
1158 self._rbuf.write(buf.read())
1159 return rv
1160
1161 self._rbuf = StringIO.StringIO()
1162 while True:
1163 data = self.recv(self._rbufsize)
1164 if not data:
1165 break
1166 left = size - buf_len
1167
1168 nl = data.find('\n', 0, left)
1169 if nl >= 0:
1170 nl += 1
1171
1172 self._rbuf.write(data[nl:])
1173 if buf_len:
1174 buf.write(data[:nl])
1175 break
1176 else:
1177
1178
1179 return data[:nl]
1180 n = len(data)
1181 if n == size and not buf_len:
1182
1183
1184 return data
1185 if n >= left:
1186 buf.write(data[:left])
1187 self._rbuf.write(data[left:])
1188 break
1189 buf.write(data)
1190 buf_len += n
1191
1192 return buf.getvalue()
1193 else:
1194 - def read(self, size=-1):
1195 if size < 0:
1196
1197 buffers = [self._rbuf]
1198 self._rbuf = ""
1199 if self._rbufsize <= 1:
1200 recv_size = self.default_bufsize
1201 else:
1202 recv_size = self._rbufsize
1203
1204 while True:
1205 data = self.recv(recv_size)
1206 if not data:
1207 break
1208 buffers.append(data)
1209 return "".join(buffers)
1210 else:
1211
1212 data = self._rbuf
1213 buf_len = len(data)
1214 if buf_len >= size:
1215 self._rbuf = data[size:]
1216 return data[:size]
1217 buffers = []
1218 if data:
1219 buffers.append(data)
1220 self._rbuf = ""
1221 while True:
1222 left = size - buf_len
1223 recv_size = max(self._rbufsize, left)
1224 data = self.recv(recv_size)
1225 if not data:
1226 break
1227 buffers.append(data)
1228 n = len(data)
1229 if n >= left:
1230 self._rbuf = data[left:]
1231 buffers[-1] = data[:left]
1232 break
1233 buf_len += n
1234 return "".join(buffers)
1235
1237 data = self._rbuf
1238 if size < 0:
1239
1240 if self._rbufsize <= 1:
1241
1242 assert data == ""
1243 buffers = []
1244 while data != "\n":
1245 data = self.recv(1)
1246 if not data:
1247 break
1248 buffers.append(data)
1249 return "".join(buffers)
1250 nl = data.find('\n')
1251 if nl >= 0:
1252 nl += 1
1253 self._rbuf = data[nl:]
1254 return data[:nl]
1255 buffers = []
1256 if data:
1257 buffers.append(data)
1258 self._rbuf = ""
1259 while True:
1260 data = self.recv(self._rbufsize)
1261 if not data:
1262 break
1263 buffers.append(data)
1264 nl = data.find('\n')
1265 if nl >= 0:
1266 nl += 1
1267 self._rbuf = data[nl:]
1268 buffers[-1] = data[:nl]
1269 break
1270 return "".join(buffers)
1271 else:
1272
1273
1274 nl = data.find('\n', 0, size)
1275 if nl >= 0:
1276 nl += 1
1277 self._rbuf = data[nl:]
1278 return data[:nl]
1279 buf_len = len(data)
1280 if buf_len >= size:
1281 self._rbuf = data[size:]
1282 return data[:size]
1283 buffers = []
1284 if data:
1285 buffers.append(data)
1286 self._rbuf = ""
1287 while True:
1288 data = self.recv(self._rbufsize)
1289 if not data:
1290 break
1291 buffers.append(data)
1292 left = size - buf_len
1293 nl = data.find('\n', 0, left)
1294 if nl >= 0:
1295 nl += 1
1296 self._rbuf = data[nl:]
1297 buffers[-1] = data[:nl]
1298 break
1299 n = len(data)
1300 if n >= left:
1301 self._rbuf = data[left:]
1302 buffers[-1] = data[:left]
1303 break
1304 buf_len += n
1305 return "".join(buffers)
1306
1307
1309
1310 """An HTTP connection (active socket).
1311
1312 server: the Server object which received this connection.
1313 socket: the raw socket object (usually TCP) for this connection.
1314 makefile: a fileobject class for reading from the socket.
1315 """
1316
1317 remote_addr = None
1318 remote_port = None
1319 ssl_env = None
1320 rbufsize = DEFAULT_BUFFER_SIZE
1321 wbufsize = DEFAULT_BUFFER_SIZE
1322 RequestHandlerClass = HTTPRequest
1323
1330
1332 """Read each request and respond appropriately."""
1333 request_seen = False
1334 try:
1335 while True:
1336
1337
1338
1339 req = None
1340 req = self.RequestHandlerClass(self.server, self)
1341
1342
1343 req.parse_request()
1344 if self.server.stats['Enabled']:
1345 self.requests_seen += 1
1346 if not req.ready:
1347
1348
1349
1350 return
1351
1352 request_seen = True
1353 req.respond()
1354 if req.close_connection:
1355 return
1356 except socket.error:
1357 e = sys.exc_info()[1]
1358 errnum = e.args[0]
1359
1360 if (
1361 errnum == 'timed out' or
1362 errnum == 'The read operation timed out'
1363 ):
1364
1365
1366
1367
1368 if (not request_seen) or (req and req.started_request):
1369
1370
1371 if req and not req.sent_headers:
1372 try:
1373 req.simple_response("408 Request Timeout")
1374 except FatalSSLAlert:
1375
1376 return
1377 elif errnum not in socket_errors_to_ignore:
1378 self.server.error_log("socket.error %s" % repr(errnum),
1379 level=logging.WARNING, traceback=True)
1380 if req and not req.sent_headers:
1381 try:
1382 req.simple_response("500 Internal Server Error")
1383 except FatalSSLAlert:
1384
1385 return
1386 return
1387 except (KeyboardInterrupt, SystemExit):
1388 raise
1389 except FatalSSLAlert:
1390
1391 return
1392 except NoSSLError:
1393 if req and not req.sent_headers:
1394
1395 self.wfile = CP_fileobject(
1396 self.socket._sock, "wb", self.wbufsize)
1397 req.simple_response(
1398 "400 Bad Request",
1399 "The client sent a plain HTTP request, but "
1400 "this server only speaks HTTPS on this port.")
1401 self.linger = True
1402 except Exception:
1403 e = sys.exc_info()[1]
1404 self.server.error_log(repr(e), level=logging.ERROR, traceback=True)
1405 if req and not req.sent_headers:
1406 try:
1407 req.simple_response("500 Internal Server Error")
1408 except FatalSSLAlert:
1409
1410 return
1411
1412 linger = False
1413
1415 """Close the socket underlying this connection."""
1416 self.rfile.close()
1417
1418 if not self.linger:
1419
1420
1421
1422
1423
1424
1425 if hasattr(self.socket, '_sock'):
1426 self.socket._sock.close()
1427 self.socket.close()
1428 else:
1429
1430
1431
1432
1433
1434
1435 pass
1436
1437
1439
1440 """An object which equals and does math like the integer 0 but evals True.
1441 """
1442
1445
1448 trueyzero = TrueyZero()
1449
1450
1451 _SHUTDOWNREQUEST = None
1452
1453
1455
1456 """Thread which continuously polls a Queue for Connection objects.
1457
1458 Due to the timing issues of polling a Queue, a WorkerThread does not
1459 check its own 'ready' flag after it has started. To stop the thread,
1460 it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
1461 (one for each running WorkerThread).
1462 """
1463
1464 conn = None
1465 """The current connection pulled off the Queue, or None."""
1466
1467 server = None
1468 """The HTTP Server which spawned this thread, and which owns the
1469 Queue and is placing active connections into it."""
1470
1471 ready = False
1472 """A simple flag for the calling server to know when this thread
1473 has begun polling the Queue."""
1474
1476 self.ready = False
1477 self.server = server
1478
1479 self.requests_seen = 0
1480 self.bytes_read = 0
1481 self.bytes_written = 0
1482 self.start_time = None
1483 self.work_time = 0
1484 self.stats = {
1485 'Requests': lambda s: self.requests_seen + (
1486 (self.start_time is None) and
1487 trueyzero or
1488 self.conn.requests_seen
1489 ),
1490 'Bytes Read': lambda s: self.bytes_read + (
1491 (self.start_time is None) and
1492 trueyzero or
1493 self.conn.rfile.bytes_read
1494 ),
1495 'Bytes Written': lambda s: self.bytes_written + (
1496 (self.start_time is None) and
1497 trueyzero or
1498 self.conn.wfile.bytes_written
1499 ),
1500 'Work Time': lambda s: self.work_time + (
1501 (self.start_time is None) and
1502 trueyzero or
1503 time.time() - self.start_time
1504 ),
1505 'Read Throughput': lambda s: s['Bytes Read'](s) / (
1506 s['Work Time'](s) or 1e-6),
1507 'Write Throughput': lambda s: s['Bytes Written'](s) / (
1508 s['Work Time'](s) or 1e-6),
1509 }
1510 threading.Thread.__init__(self)
1511
1538
1539
1541
1542 """A Request Queue for an HTTPServer which pools threads.
1543
1544 ThreadPool objects must provide min, get(), put(obj), start()
1545 and stop(timeout) attributes.
1546 """
1547
1548 - def __init__(self, server, min=10, max=-1):
1549 self.server = server
1550 self.min = min
1551 self.max = max
1552 self._threads = []
1553 self._queue = queue.Queue()
1554 self.get = self._queue.get
1555
1557 """Start the pool of threads."""
1558 for i in range(self.min):
1559 self._threads.append(WorkerThread(self.server))
1560 for worker in self._threads:
1561 worker.setName("CP Server " + worker.getName())
1562 worker.start()
1563 for worker in self._threads:
1564 while not worker.ready:
1565 time.sleep(.1)
1566
1568 """Number of worker threads which are idle. Read-only."""
1569 return len([t for t in self._threads if t.conn is None])
1570 idle = property(_get_idle, doc=_get_idle.__doc__)
1571
1572 - def put(self, obj):
1576
1577 - def grow(self, amount):
1578 """Spawn new worker threads (not above self.max)."""
1579 if self.max > 0:
1580 budget = max(self.max - len(self._threads), 0)
1581 else:
1582
1583 budget = float('inf')
1584
1585 n_new = min(amount, budget)
1586
1587 workers = [self._spawn_worker() for i in range(n_new)]
1588 while not self._all(operator.attrgetter('ready'), workers):
1589 time.sleep(.1)
1590 self._threads.extend(workers)
1591
1593 worker = WorkerThread(self.server)
1594 worker.setName("CP Server " + worker.getName())
1595 worker.start()
1596 return worker
1597
1598 - def _all(func, items):
1599 results = [func(item) for item in items]
1600 return reduce(operator.and_, results, True)
1601 _all = staticmethod(_all)
1602
1604 """Kill off worker threads (not below self.min)."""
1605
1606
1607 for t in self._threads:
1608 if not t.isAlive():
1609 self._threads.remove(t)
1610 amount -= 1
1611
1612
1613 n_extra = max(len(self._threads) - self.min, 0)
1614
1615
1616 n_to_remove = min(amount, n_extra)
1617
1618
1619
1620
1621 for n in range(n_to_remove):
1622 self._queue.put(_SHUTDOWNREQUEST)
1623
1624 - def stop(self, timeout=5):
1625
1626
1627 for worker in self._threads:
1628 self._queue.put(_SHUTDOWNREQUEST)
1629
1630
1631 current = threading.currentThread()
1632 if timeout and timeout >= 0:
1633 endtime = time.time() + timeout
1634 while self._threads:
1635 worker = self._threads.pop()
1636 if worker is not current and worker.isAlive():
1637 try:
1638 if timeout is None or timeout < 0:
1639 worker.join()
1640 else:
1641 remaining_time = endtime - time.time()
1642 if remaining_time > 0:
1643 worker.join(remaining_time)
1644 if worker.isAlive():
1645
1646
1647 c = worker.conn
1648 if c and not c.rfile.closed:
1649 try:
1650 c.socket.shutdown(socket.SHUT_RD)
1651 except TypeError:
1652
1653 c.socket.shutdown()
1654 worker.join()
1655 except (AssertionError,
1656
1657
1658
1659 KeyboardInterrupt):
1660 pass
1661
1663 return self._queue.qsize()
1664 qsize = property(_get_qsize)
1665
1666
1667 try:
1668 import fcntl
1669 except ImportError:
1670 try:
1671 from ctypes import windll, WinError
1672 import ctypes.wintypes
1673 _SetHandleInformation = windll.kernel32.SetHandleInformation
1674 _SetHandleInformation.argtypes = [
1675 ctypes.wintypes.HANDLE,
1676 ctypes.wintypes.DWORD,
1677 ctypes.wintypes.DWORD,
1678 ]
1679 _SetHandleInformation.restype = ctypes.wintypes.BOOL
1680 except ImportError:
1682 """Dummy function, since neither fcntl nor ctypes are available."""
1683 pass
1684 else:
1686 """Mark the given socket fd as non-inheritable (Windows)."""
1687 if not _SetHandleInformation(sock.fileno(), 1, 0):
1688 raise WinError()
1689 else:
1691 """Mark the given socket fd as non-inheritable (POSIX)."""
1692 fd = sock.fileno()
1693 old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
1694 fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
1695
1696
1698
1699 """Base class for SSL driver library adapters.
1700
1701 Required methods:
1702
1703 * ``wrap(sock) -> (wrapped socket, ssl environ dict)``
1704 * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) ->
1705 socket file object``
1706 """
1707
1708 - def __init__(self, certificate, private_key, certificate_chain=None):
1712
1713 - def wrap(self, sock):
1714 raise NotImplemented
1715
1717 raise NotImplemented
1718
1719
1721
1722 """An HTTP server."""
1723
1724 _bind_addr = "127.0.0.1"
1725 _interrupt = None
1726
1727 gateway = None
1728 """A Gateway instance."""
1729
1730 minthreads = None
1731 """The minimum number of worker threads to create (default 10)."""
1732
1733 maxthreads = None
1734 """The maximum number of worker threads to create (default -1 = no limit).
1735 """
1736
1737 server_name = None
1738 """The name of the server; defaults to socket.gethostname()."""
1739
1740 protocol = "HTTP/1.1"
1741 """The version string to write in the Status-Line of all HTTP responses.
1742
1743 For example, "HTTP/1.1" is the default. This also limits the supported
1744 features used in the response."""
1745
1746 request_queue_size = 5
1747 """The 'backlog' arg to socket.listen(); max queued connections
1748 (default 5).
1749 """
1750
1751 shutdown_timeout = 5
1752 """The total time, in seconds, to wait for worker threads to cleanly exit.
1753 """
1754
1755 timeout = 10
1756 """The timeout in seconds for accepted connections (default 10)."""
1757
1758 version = "CherryPy/3.3.0"
1759 """A version string for the HTTPServer."""
1760
1761 software = None
1762 """The value to set for the SERVER_SOFTWARE entry in the WSGI environ.
1763
1764 If None, this defaults to ``'%s Server' % self.version``."""
1765
1766 ready = False
1767 """An internal flag which marks whether the socket is accepting connections
1768 """
1769
1770 max_request_header_size = 0
1771 """The maximum size, in bytes, for request headers, or 0 for no limit."""
1772
1773 max_request_body_size = 0
1774 """The maximum size, in bytes, for request bodies, or 0 for no limit."""
1775
1776 nodelay = True
1777 """If True (the default since 3.1), sets the TCP_NODELAY socket option."""
1778
1779 ConnectionClass = HTTPConnection
1780 """The class to use for handling HTTP connections."""
1781
1782 ssl_adapter = None
1783 """An instance of SSLAdapter (or a subclass).
1784
1785 You must have the corresponding SSL driver library installed."""
1786
1787 - def __init__(self, bind_addr, gateway, minthreads=10, maxthreads=-1,
1788 server_name=None):
1798
1800 self._start_time = None
1801 self._run_time = 0
1802 self.stats = {
1803 'Enabled': False,
1804 'Bind Address': lambda s: repr(self.bind_addr),
1805 'Run time': lambda s: (not s['Enabled']) and -1 or self.runtime(),
1806 'Accepts': 0,
1807 'Accepts/sec': lambda s: s['Accepts'] / self.runtime(),
1808 'Queue': lambda s: getattr(self.requests, "qsize", None),
1809 'Threads': lambda s: len(getattr(self.requests, "_threads", [])),
1810 'Threads Idle': lambda s: getattr(self.requests, "idle", None),
1811 'Socket Errors': 0,
1812 'Requests': lambda s: (not s['Enabled']) and -1 or sum(
1813 [w['Requests'](w) for w in s['Worker Threads'].values()], 0),
1814 'Bytes Read': lambda s: (not s['Enabled']) and -1 or sum(
1815 [w['Bytes Read'](w) for w in s['Worker Threads'].values()], 0),
1816 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum(
1817 [w['Bytes Written'](w) for w in s['Worker Threads'].values()],
1818 0),
1819 'Work Time': lambda s: (not s['Enabled']) and -1 or sum(
1820 [w['Work Time'](w) for w in s['Worker Threads'].values()], 0),
1821 'Read Throughput': lambda s: (not s['Enabled']) and -1 or sum(
1822 [w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6)
1823 for w in s['Worker Threads'].values()], 0),
1824 'Write Throughput': lambda s: (not s['Enabled']) and -1 or sum(
1825 [w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6)
1826 for w in s['Worker Threads'].values()], 0),
1827 'Worker Threads': {},
1828 }
1829 logging.statistics["CherryPy HTTPServer %d" % id(self)] = self.stats
1830
1832 if self._start_time is None:
1833 return self._run_time
1834 else:
1835 return self._run_time + (time.time() - self._start_time)
1836
1838 return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
1839 self.bind_addr)
1840
1843
1845 if isinstance(value, tuple) and value[0] in ('', None):
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856 raise ValueError("Host values of '' or None are not allowed. "
1857 "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
1858 "to listen on all active interfaces.")
1859 self._bind_addr = value
1860 bind_addr = property(
1861 _get_bind_addr,
1862 _set_bind_addr,
1863 doc="""The interface on which to listen for connections.
1864
1865 For TCP sockets, a (host, port) tuple. Host values may be any IPv4
1866 or IPv6 address, or any valid hostname. The string 'localhost' is a
1867 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
1868 The string '0.0.0.0' is a special IPv4 entry meaning "any active
1869 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
1870 IPv6. The empty string or None are not allowed.
1871
1872 For UNIX sockets, supply the filename as a string.""")
1873
1875 """Run the server forever."""
1876
1877
1878
1879
1880 self._interrupt = None
1881
1882 if self.software is None:
1883 self.software = "%s Server" % self.version
1884
1885
1886 if (self.ssl_adapter is None and
1887 getattr(self, 'ssl_certificate', None) and
1888 getattr(self, 'ssl_private_key', None)):
1889 warnings.warn(
1890 "SSL attributes are deprecated in CherryPy 3.2, and will "
1891 "be removed in CherryPy 3.3. Use an ssl_adapter attribute "
1892 "instead.",
1893 DeprecationWarning
1894 )
1895 try:
1896 from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
1897 except ImportError:
1898 pass
1899 else:
1900 self.ssl_adapter = pyOpenSSLAdapter(
1901 self.ssl_certificate, self.ssl_private_key,
1902 getattr(self, 'ssl_certificate_chain', None))
1903
1904
1905 if isinstance(self.bind_addr, basestring):
1906
1907
1908
1909 try:
1910 os.unlink(self.bind_addr)
1911 except:
1912 pass
1913
1914
1915 try:
1916 os.chmod(self.bind_addr, 511)
1917 except:
1918 pass
1919
1920 info = [
1921 (socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1922 else:
1923
1924
1925
1926 host, port = self.bind_addr
1927 try:
1928 info = socket.getaddrinfo(
1929 host, port, socket.AF_UNSPEC,
1930 socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
1931 except socket.gaierror:
1932 if ':' in self.bind_addr[0]:
1933 info = [(socket.AF_INET6, socket.SOCK_STREAM,
1934 0, "", self.bind_addr + (0, 0))]
1935 else:
1936 info = [(socket.AF_INET, socket.SOCK_STREAM,
1937 0, "", self.bind_addr)]
1938
1939 self.socket = None
1940 msg = "No socket could be created"
1941 for res in info:
1942 af, socktype, proto, canonname, sa = res
1943 try:
1944 self.bind(af, socktype, proto)
1945 except socket.error, serr:
1946 msg = "%s -- (%s: %s)" % (msg, sa, serr)
1947 if self.socket:
1948 self.socket.close()
1949 self.socket = None
1950 continue
1951 break
1952 if not self.socket:
1953 raise socket.error(msg)
1954
1955
1956 self.socket.settimeout(1)
1957 self.socket.listen(self.request_queue_size)
1958
1959
1960 self.requests.start()
1961
1962 self.ready = True
1963 self._start_time = time.time()
1964 while self.ready:
1965 try:
1966 self.tick()
1967 except (KeyboardInterrupt, SystemExit):
1968 raise
1969 except:
1970 self.error_log("Error in HTTPServer.tick", level=logging.ERROR,
1971 traceback=True)
1972
1973 if self.interrupt:
1974 while self.interrupt is True:
1975
1976 time.sleep(0.1)
1977 if self.interrupt:
1978 raise self.interrupt
1979
1980 - def error_log(self, msg="", level=20, traceback=False):
1988
1989 - def bind(self, family, type, proto=0):
1990 """Create (or recreate) the actual socket object."""
1991 self.socket = socket.socket(family, type, proto)
1992 prevent_socket_inheritance(self.socket)
1993 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1994 if self.nodelay and not isinstance(self.bind_addr, str):
1995 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1996
1997 if self.ssl_adapter is not None:
1998 self.socket = self.ssl_adapter.bind(self.socket)
1999
2000
2001
2002
2003 if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
2004 and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
2005 try:
2006 self.socket.setsockopt(
2007 socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
2008 except (AttributeError, socket.error):
2009
2010
2011 pass
2012
2013 self.socket.bind(self.bind_addr)
2014
2016 """Accept a new connection and put it on the Queue."""
2017 try:
2018 s, addr = self.socket.accept()
2019 if self.stats['Enabled']:
2020 self.stats['Accepts'] += 1
2021 if not self.ready:
2022 return
2023
2024 prevent_socket_inheritance(s)
2025 if hasattr(s, 'settimeout'):
2026 s.settimeout(self.timeout)
2027
2028 makefile = CP_fileobject
2029 ssl_env = {}
2030
2031 if self.ssl_adapter is not None:
2032 try:
2033 s, ssl_env = self.ssl_adapter.wrap(s)
2034 except NoSSLError:
2035 msg = ("The client sent a plain HTTP request, but "
2036 "this server only speaks HTTPS on this port.")
2037 buf = ["%s 400 Bad Request\r\n" % self.protocol,
2038 "Content-Length: %s\r\n" % len(msg),
2039 "Content-Type: text/plain\r\n\r\n",
2040 msg]
2041
2042 wfile = makefile(s._sock, "wb", DEFAULT_BUFFER_SIZE)
2043 try:
2044 wfile.sendall("".join(buf))
2045 except socket.error:
2046 x = sys.exc_info()[1]
2047 if x.args[0] not in socket_errors_to_ignore:
2048 raise
2049 return
2050 if not s:
2051 return
2052 makefile = self.ssl_adapter.makefile
2053
2054 if hasattr(s, 'settimeout'):
2055 s.settimeout(self.timeout)
2056
2057 conn = self.ConnectionClass(self, s, makefile)
2058
2059 if not isinstance(self.bind_addr, basestring):
2060
2061
2062 if addr is None:
2063
2064 if len(s.getsockname()) == 2:
2065
2066 addr = ('0.0.0.0', 0)
2067 else:
2068
2069 addr = ('::', 0)
2070 conn.remote_addr = addr[0]
2071 conn.remote_port = addr[1]
2072
2073 conn.ssl_env = ssl_env
2074
2075 self.requests.put(conn)
2076 except socket.timeout:
2077
2078
2079
2080 return
2081 except socket.error:
2082 x = sys.exc_info()[1]
2083 if self.stats['Enabled']:
2084 self.stats['Socket Errors'] += 1
2085 if x.args[0] in socket_error_eintr:
2086
2087
2088
2089
2090
2091
2092 return
2093 if x.args[0] in socket_errors_nonblocking:
2094
2095
2096 return
2097 if x.args[0] in socket_errors_to_ignore:
2098
2099
2100 return
2101 raise
2102
2105
2110 interrupt = property(_get_interrupt, _set_interrupt,
2111 doc="Set this to an Exception instance to "
2112 "interrupt the server.")
2113
2115 """Gracefully shutdown a server that is serving forever."""
2116 self.ready = False
2117 if self._start_time is not None:
2118 self._run_time += (time.time() - self._start_time)
2119 self._start_time = None
2120
2121 sock = getattr(self, "socket", None)
2122 if sock:
2123 if not isinstance(self.bind_addr, basestring):
2124
2125 try:
2126 host, port = sock.getsockname()[:2]
2127 except socket.error:
2128 x = sys.exc_info()[1]
2129 if x.args[0] not in socket_errors_to_ignore:
2130
2131
2132
2133 raise
2134 else:
2135
2136
2137
2138
2139 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
2140 socket.SOCK_STREAM):
2141 af, socktype, proto, canonname, sa = res
2142 s = None
2143 try:
2144 s = socket.socket(af, socktype, proto)
2145
2146
2147
2148 s.settimeout(1.0)
2149 s.connect((host, port))
2150 s.close()
2151 except socket.error:
2152 if s:
2153 s.close()
2154 if hasattr(sock, "close"):
2155 sock.close()
2156 self.socket = None
2157
2158 self.requests.stop(self.shutdown_timeout)
2159
2160
2162
2163 """A base class to interface HTTPServer with other systems, such as WSGI.
2164 """
2165
2168
2170 """Process the current request. Must be overridden in a subclass."""
2171 raise NotImplemented
2172
2173
2174
2175
2176 ssl_adapters = {
2177 'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
2178 'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
2179 }
2180
2181
2183 """Return an SSL adapter class for the given name."""
2184 adapter = ssl_adapters[name.lower()]
2185 if isinstance(adapter, basestring):
2186 last_dot = adapter.rfind(".")
2187 attr_name = adapter[last_dot + 1:]
2188 mod_path = adapter[:last_dot]
2189
2190 try:
2191 mod = sys.modules[mod_path]
2192 if mod is None:
2193 raise KeyError()
2194 except KeyError:
2195
2196 mod = __import__(mod_path, globals(), locals(), [''])
2197
2198
2199 try:
2200 adapter = getattr(mod, attr_name)
2201 except AttributeError:
2202 raise AttributeError("'%s' object has no attribute '%s'"
2203 % (mod_path, attr_name))
2204
2205 return adapter
2206
2207
2208
2209
2211
2212 """A subclass of HTTPServer which calls a WSGI application."""
2213
2214 wsgi_version = (1, 0)
2215 """The version of WSGI to produce."""
2216
2217 - def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
2218 max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
2232
2234 return self.requests.min
2235
2237 self.requests.min = value
2238 numthreads = property(_get_numthreads, _set_numthreads)
2239
2240
2242
2243 """A base class to interface HTTPServer with WSGI."""
2244
2246 self.req = req
2247 self.started_response = False
2248 self.env = self.get_environ()
2249 self.remaining_bytes_out = None
2250
2252 """Return a new environ dict targeting the given wsgi.version"""
2253 raise NotImplemented
2254
2256 """Process the current request."""
2257 response = self.req.server.wsgi_app(self.env, self.start_response)
2258 try:
2259 for chunk in response:
2260
2261
2262
2263
2264
2265
2266 if chunk:
2267 if isinstance(chunk, unicodestr):
2268 chunk = chunk.encode('ISO-8859-1')
2269 self.write(chunk)
2270 finally:
2271 if hasattr(response, "close"):
2272 response.close()
2273
2275 """WSGI callable to begin the HTTP response."""
2276
2277
2278 if self.started_response and not exc_info:
2279 raise AssertionError("WSGI start_response called a second "
2280 "time with no exc_info.")
2281 self.started_response = True
2282
2283
2284
2285
2286 if self.req.sent_headers:
2287 try:
2288 raise exc_info[0], exc_info[1], exc_info[2]
2289 finally:
2290 exc_info = None
2291
2292 self.req.status = status
2293 for k, v in headers:
2294 if not isinstance(k, str):
2295 raise TypeError(
2296 "WSGI response header key %r is not of type str." % k)
2297 if not isinstance(v, str):
2298 raise TypeError(
2299 "WSGI response header value %r is not of type str." % v)
2300 if k.lower() == 'content-length':
2301 self.remaining_bytes_out = int(v)
2302 self.req.outheaders.extend(headers)
2303
2304 return self.write
2305
2306 - def write(self, chunk):
2307 """WSGI callable to write unbuffered data to the client.
2308
2309 This method is also used internally by start_response (to write
2310 data from the iterable returned by the WSGI application).
2311 """
2312 if not self.started_response:
2313 raise AssertionError("WSGI write called before start_response.")
2314
2315 chunklen = len(chunk)
2316 rbo = self.remaining_bytes_out
2317 if rbo is not None and chunklen > rbo:
2318 if not self.req.sent_headers:
2319
2320 self.req.simple_response(
2321 "500 Internal Server Error",
2322 "The requested resource returned more bytes than the "
2323 "declared Content-Length.")
2324 else:
2325
2326
2327 chunk = chunk[:rbo]
2328
2329 if not self.req.sent_headers:
2330 self.req.sent_headers = True
2331 self.req.send_headers()
2332
2333 self.req.write(chunk)
2334
2335 if rbo is not None:
2336 rbo -= chunklen
2337 if rbo < 0:
2338 raise ValueError(
2339 "Response body exceeds the declared Content-Length.")
2340
2341
2343
2344 """A Gateway class to interface HTTPServer with WSGI 1.0.x."""
2345
2347 """Return a new environ dict targeting the given wsgi.version"""
2348 req = self.req
2349 env = {
2350
2351
2352
2353 'ACTUAL_SERVER_PROTOCOL': req.server.protocol,
2354 'PATH_INFO': req.path,
2355 'QUERY_STRING': req.qs,
2356 'REMOTE_ADDR': req.conn.remote_addr or '',
2357 'REMOTE_PORT': str(req.conn.remote_port or ''),
2358 'REQUEST_METHOD': req.method,
2359 'REQUEST_URI': req.uri,
2360 'SCRIPT_NAME': '',
2361 'SERVER_NAME': req.server.server_name,
2362
2363 'SERVER_PROTOCOL': req.request_protocol,
2364 'SERVER_SOFTWARE': req.server.software,
2365 'wsgi.errors': sys.stderr,
2366 'wsgi.input': req.rfile,
2367 'wsgi.multiprocess': False,
2368 'wsgi.multithread': True,
2369 'wsgi.run_once': False,
2370 'wsgi.url_scheme': req.scheme,
2371 'wsgi.version': (1, 0),
2372 }
2373
2374 if isinstance(req.server.bind_addr, basestring):
2375
2376
2377 env["SERVER_PORT"] = ""
2378 else:
2379 env["SERVER_PORT"] = str(req.server.bind_addr[1])
2380
2381
2382 for k, v in req.inheaders.iteritems():
2383 env["HTTP_" + k.upper().replace("-", "_")] = v
2384
2385
2386 ct = env.pop("HTTP_CONTENT_TYPE", None)
2387 if ct is not None:
2388 env["CONTENT_TYPE"] = ct
2389 cl = env.pop("HTTP_CONTENT_LENGTH", None)
2390 if cl is not None:
2391 env["CONTENT_LENGTH"] = cl
2392
2393 if req.conn.ssl_env:
2394 env.update(req.conn.ssl_env)
2395
2396 return env
2397
2398
2400
2401 """A Gateway class to interface HTTPServer with WSGI u.0.
2402
2403 WSGI u.0 is an experimental protocol, which uses unicode for keys and
2404 values in both Python 2 and Python 3.
2405 """
2406
2408 """Return a new environ dict targeting the given wsgi.version"""
2409 req = self.req
2410 env_10 = WSGIGateway_10.get_environ(self)
2411 env = dict([(k.decode('ISO-8859-1'), v)
2412 for k, v in env_10.iteritems()])
2413 env[u'wsgi.version'] = ('u', 0)
2414
2415
2416 env.setdefault(u'wsgi.url_encoding', u'utf-8')
2417 try:
2418 for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
2419 env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
2420 except UnicodeDecodeError:
2421
2422 env[u'wsgi.url_encoding'] = u'ISO-8859-1'
2423 for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
2424 env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
2425
2426 for k, v in sorted(env.items()):
2427 if isinstance(v, str) and k not in ('REQUEST_URI', 'wsgi.input'):
2428 env[k] = v.decode('ISO-8859-1')
2429
2430 return env
2431
2432 wsgi_gateways = {
2433 (1, 0): WSGIGateway_10,
2434 ('u', 0): WSGIGateway_u0,
2435 }
2436
2437
2439
2440 """A WSGI dispatcher for dispatch based on the PATH_INFO.
2441
2442 apps: a dict or list of (path_prefix, app) pairs.
2443 """
2444
2446 try:
2447 apps = list(apps.items())
2448 except AttributeError:
2449 pass
2450
2451
2452 apps.sort(cmp=lambda x, y: cmp(len(x[0]), len(y[0])))
2453 apps.reverse()
2454
2455
2456
2457 self.apps = [(p.rstrip("/"), a) for p, a in apps]
2458
2459 - def __call__(self, environ, start_response):
2460 path = environ["PATH_INFO"] or "/"
2461 for p, app in self.apps:
2462
2463 if path.startswith(p + "/") or path == p:
2464 environ = environ.copy()
2465 environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
2466 environ["PATH_INFO"] = path[len(p):]
2467 return app(environ, start_response)
2468
2469 start_response('404 Not Found', [('Content-Type', 'text/plain'),
2470 ('Content-Length', '0')])
2471 return ['']
2472