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 accepted_queue_size=-1, accepted_queue_timeout=10):
1557
1559 """Start the pool of threads."""
1560 for i in range(self.min):
1561 self._threads.append(WorkerThread(self.server))
1562 for worker in self._threads:
1563 worker.setName("CP Server " + worker.getName())
1564 worker.start()
1565 for worker in self._threads:
1566 while not worker.ready:
1567 time.sleep(.1)
1568
1570 """Number of worker threads which are idle. Read-only."""
1571 return len([t for t in self._threads if t.conn is None])
1572 idle = property(_get_idle, doc=_get_idle.__doc__)
1573
1574 - def put(self, obj):
1578
1579 - def grow(self, amount):
1580 """Spawn new worker threads (not above self.max)."""
1581 if self.max > 0:
1582 budget = max(self.max - len(self._threads), 0)
1583 else:
1584
1585 budget = float('inf')
1586
1587 n_new = min(amount, budget)
1588
1589 workers = [self._spawn_worker() for i in range(n_new)]
1590 while not self._all(operator.attrgetter('ready'), workers):
1591 time.sleep(.1)
1592 self._threads.extend(workers)
1593
1595 worker = WorkerThread(self.server)
1596 worker.setName("CP Server " + worker.getName())
1597 worker.start()
1598 return worker
1599
1600 - def _all(func, items):
1601 results = [func(item) for item in items]
1602 return reduce(operator.and_, results, True)
1603 _all = staticmethod(_all)
1604
1606 """Kill off worker threads (not below self.min)."""
1607
1608
1609 for t in self._threads:
1610 if not t.isAlive():
1611 self._threads.remove(t)
1612 amount -= 1
1613
1614
1615 n_extra = max(len(self._threads) - self.min, 0)
1616
1617
1618 n_to_remove = min(amount, n_extra)
1619
1620
1621
1622
1623 for n in range(n_to_remove):
1624 self._queue.put(_SHUTDOWNREQUEST)
1625
1626 - def stop(self, timeout=5):
1627
1628
1629 for worker in self._threads:
1630 self._queue.put(_SHUTDOWNREQUEST)
1631
1632
1633 current = threading.currentThread()
1634 if timeout and timeout >= 0:
1635 endtime = time.time() + timeout
1636 while self._threads:
1637 worker = self._threads.pop()
1638 if worker is not current and worker.isAlive():
1639 try:
1640 if timeout is None or timeout < 0:
1641 worker.join()
1642 else:
1643 remaining_time = endtime - time.time()
1644 if remaining_time > 0:
1645 worker.join(remaining_time)
1646 if worker.isAlive():
1647
1648
1649 c = worker.conn
1650 if c and not c.rfile.closed:
1651 try:
1652 c.socket.shutdown(socket.SHUT_RD)
1653 except TypeError:
1654
1655 c.socket.shutdown()
1656 worker.join()
1657 except (AssertionError,
1658
1659
1660
1661 KeyboardInterrupt):
1662 pass
1663
1665 return self._queue.qsize()
1666 qsize = property(_get_qsize)
1667
1668
1669 try:
1670 import fcntl
1671 except ImportError:
1672 try:
1673 from ctypes import windll, WinError
1674 import ctypes.wintypes
1675 _SetHandleInformation = windll.kernel32.SetHandleInformation
1676 _SetHandleInformation.argtypes = [
1677 ctypes.wintypes.HANDLE,
1678 ctypes.wintypes.DWORD,
1679 ctypes.wintypes.DWORD,
1680 ]
1681 _SetHandleInformation.restype = ctypes.wintypes.BOOL
1682 except ImportError:
1684 """Dummy function, since neither fcntl nor ctypes are available."""
1685 pass
1686 else:
1688 """Mark the given socket fd as non-inheritable (Windows)."""
1689 if not _SetHandleInformation(sock.fileno(), 1, 0):
1690 raise WinError()
1691 else:
1693 """Mark the given socket fd as non-inheritable (POSIX)."""
1694 fd = sock.fileno()
1695 old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
1696 fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
1697
1698
1700
1701 """Base class for SSL driver library adapters.
1702
1703 Required methods:
1704
1705 * ``wrap(sock) -> (wrapped socket, ssl environ dict)``
1706 * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) ->
1707 socket file object``
1708 """
1709
1710 - def __init__(self, certificate, private_key, certificate_chain=None):
1714
1715 - def wrap(self, sock):
1716 raise NotImplemented
1717
1719 raise NotImplemented
1720
1721
1723
1724 """An HTTP server."""
1725
1726 _bind_addr = "127.0.0.1"
1727 _interrupt = None
1728
1729 gateway = None
1730 """A Gateway instance."""
1731
1732 minthreads = None
1733 """The minimum number of worker threads to create (default 10)."""
1734
1735 maxthreads = None
1736 """The maximum number of worker threads to create (default -1 = no limit).
1737 """
1738
1739 server_name = None
1740 """The name of the server; defaults to socket.gethostname()."""
1741
1742 protocol = "HTTP/1.1"
1743 """The version string to write in the Status-Line of all HTTP responses.
1744
1745 For example, "HTTP/1.1" is the default. This also limits the supported
1746 features used in the response."""
1747
1748 request_queue_size = 5
1749 """The 'backlog' arg to socket.listen(); max queued connections
1750 (default 5).
1751 """
1752
1753 shutdown_timeout = 5
1754 """The total time, in seconds, to wait for worker threads to cleanly exit.
1755 """
1756
1757 timeout = 10
1758 """The timeout in seconds for accepted connections (default 10)."""
1759
1760 version = "CherryPy/3.5.0"
1761 """A version string for the HTTPServer."""
1762
1763 software = None
1764 """The value to set for the SERVER_SOFTWARE entry in the WSGI environ.
1765
1766 If None, this defaults to ``'%s Server' % self.version``."""
1767
1768 ready = False
1769 """An internal flag which marks whether the socket is accepting connections
1770 """
1771
1772 max_request_header_size = 0
1773 """The maximum size, in bytes, for request headers, or 0 for no limit."""
1774
1775 max_request_body_size = 0
1776 """The maximum size, in bytes, for request bodies, or 0 for no limit."""
1777
1778 nodelay = True
1779 """If True (the default since 3.1), sets the TCP_NODELAY socket option."""
1780
1781 ConnectionClass = HTTPConnection
1782 """The class to use for handling HTTP connections."""
1783
1784 ssl_adapter = None
1785 """An instance of SSLAdapter (or a subclass).
1786
1787 You must have the corresponding SSL driver library installed."""
1788
1789 - def __init__(self, bind_addr, gateway, minthreads=10, maxthreads=-1,
1790 server_name=None):
1800
1802 self._start_time = None
1803 self._run_time = 0
1804 self.stats = {
1805 'Enabled': False,
1806 'Bind Address': lambda s: repr(self.bind_addr),
1807 'Run time': lambda s: (not s['Enabled']) and -1 or self.runtime(),
1808 'Accepts': 0,
1809 'Accepts/sec': lambda s: s['Accepts'] / self.runtime(),
1810 'Queue': lambda s: getattr(self.requests, "qsize", None),
1811 'Threads': lambda s: len(getattr(self.requests, "_threads", [])),
1812 'Threads Idle': lambda s: getattr(self.requests, "idle", None),
1813 'Socket Errors': 0,
1814 'Requests': lambda s: (not s['Enabled']) and -1 or sum(
1815 [w['Requests'](w) for w in s['Worker Threads'].values()], 0),
1816 'Bytes Read': lambda s: (not s['Enabled']) and -1 or sum(
1817 [w['Bytes Read'](w) for w in s['Worker Threads'].values()], 0),
1818 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum(
1819 [w['Bytes Written'](w) for w in s['Worker Threads'].values()],
1820 0),
1821 'Work Time': lambda s: (not s['Enabled']) and -1 or sum(
1822 [w['Work Time'](w) for w in s['Worker Threads'].values()], 0),
1823 'Read Throughput': lambda s: (not s['Enabled']) and -1 or sum(
1824 [w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6)
1825 for w in s['Worker Threads'].values()], 0),
1826 'Write Throughput': lambda s: (not s['Enabled']) and -1 or sum(
1827 [w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6)
1828 for w in s['Worker Threads'].values()], 0),
1829 'Worker Threads': {},
1830 }
1831 logging.statistics["CherryPy HTTPServer %d" % id(self)] = self.stats
1832
1834 if self._start_time is None:
1835 return self._run_time
1836 else:
1837 return self._run_time + (time.time() - self._start_time)
1838
1840 return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
1841 self.bind_addr)
1842
1845
1847 if isinstance(value, tuple) and value[0] in ('', None):
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858 raise ValueError("Host values of '' or None are not allowed. "
1859 "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
1860 "to listen on all active interfaces.")
1861 self._bind_addr = value
1862 bind_addr = property(
1863 _get_bind_addr,
1864 _set_bind_addr,
1865 doc="""The interface on which to listen for connections.
1866
1867 For TCP sockets, a (host, port) tuple. Host values may be any IPv4
1868 or IPv6 address, or any valid hostname. The string 'localhost' is a
1869 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
1870 The string '0.0.0.0' is a special IPv4 entry meaning "any active
1871 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
1872 IPv6. The empty string or None are not allowed.
1873
1874 For UNIX sockets, supply the filename as a string.""")
1875
1877 """Run the server forever."""
1878
1879
1880
1881
1882 self._interrupt = None
1883
1884 if self.software is None:
1885 self.software = "%s Server" % self.version
1886
1887
1888 if (self.ssl_adapter is None and
1889 getattr(self, 'ssl_certificate', None) and
1890 getattr(self, 'ssl_private_key', None)):
1891 warnings.warn(
1892 "SSL attributes are deprecated in CherryPy 3.2, and will "
1893 "be removed in CherryPy 3.3. Use an ssl_adapter attribute "
1894 "instead.",
1895 DeprecationWarning
1896 )
1897 try:
1898 from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
1899 except ImportError:
1900 pass
1901 else:
1902 self.ssl_adapter = pyOpenSSLAdapter(
1903 self.ssl_certificate, self.ssl_private_key,
1904 getattr(self, 'ssl_certificate_chain', None))
1905
1906
1907 if isinstance(self.bind_addr, basestring):
1908
1909
1910
1911 try:
1912 os.unlink(self.bind_addr)
1913 except:
1914 pass
1915
1916
1917 try:
1918 os.chmod(self.bind_addr, 511)
1919 except:
1920 pass
1921
1922 info = [
1923 (socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1924 else:
1925
1926
1927
1928 host, port = self.bind_addr
1929 try:
1930 info = socket.getaddrinfo(
1931 host, port, socket.AF_UNSPEC,
1932 socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
1933 except socket.gaierror:
1934 if ':' in self.bind_addr[0]:
1935 info = [(socket.AF_INET6, socket.SOCK_STREAM,
1936 0, "", self.bind_addr + (0, 0))]
1937 else:
1938 info = [(socket.AF_INET, socket.SOCK_STREAM,
1939 0, "", self.bind_addr)]
1940
1941 self.socket = None
1942 msg = "No socket could be created"
1943 for res in info:
1944 af, socktype, proto, canonname, sa = res
1945 try:
1946 self.bind(af, socktype, proto)
1947 except socket.error, serr:
1948 msg = "%s -- (%s: %s)" % (msg, sa, serr)
1949 if self.socket:
1950 self.socket.close()
1951 self.socket = None
1952 continue
1953 break
1954 if not self.socket:
1955 raise socket.error(msg)
1956
1957
1958 self.socket.settimeout(1)
1959 self.socket.listen(self.request_queue_size)
1960
1961
1962 self.requests.start()
1963
1964 self.ready = True
1965 self._start_time = time.time()
1966 while self.ready:
1967 try:
1968 self.tick()
1969 except (KeyboardInterrupt, SystemExit):
1970 raise
1971 except:
1972 self.error_log("Error in HTTPServer.tick", level=logging.ERROR,
1973 traceback=True)
1974
1975 if self.interrupt:
1976 while self.interrupt is True:
1977
1978 time.sleep(0.1)
1979 if self.interrupt:
1980 raise self.interrupt
1981
1982 - def error_log(self, msg="", level=20, traceback=False):
1990
1991 - def bind(self, family, type, proto=0):
1992 """Create (or recreate) the actual socket object."""
1993 self.socket = socket.socket(family, type, proto)
1994 prevent_socket_inheritance(self.socket)
1995 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1996 if self.nodelay and not isinstance(self.bind_addr, str):
1997 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1998
1999 if self.ssl_adapter is not None:
2000 self.socket = self.ssl_adapter.bind(self.socket)
2001
2002
2003
2004
2005 if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
2006 and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
2007 try:
2008 self.socket.setsockopt(
2009 socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
2010 except (AttributeError, socket.error):
2011
2012
2013 pass
2014
2015 self.socket.bind(self.bind_addr)
2016
2018 """Accept a new connection and put it on the Queue."""
2019 try:
2020 s, addr = self.socket.accept()
2021 if self.stats['Enabled']:
2022 self.stats['Accepts'] += 1
2023 if not self.ready:
2024 return
2025
2026 prevent_socket_inheritance(s)
2027 if hasattr(s, 'settimeout'):
2028 s.settimeout(self.timeout)
2029
2030 makefile = CP_fileobject
2031 ssl_env = {}
2032
2033 if self.ssl_adapter is not None:
2034 try:
2035 s, ssl_env = self.ssl_adapter.wrap(s)
2036 except NoSSLError:
2037 msg = ("The client sent a plain HTTP request, but "
2038 "this server only speaks HTTPS on this port.")
2039 buf = ["%s 400 Bad Request\r\n" % self.protocol,
2040 "Content-Length: %s\r\n" % len(msg),
2041 "Content-Type: text/plain\r\n\r\n",
2042 msg]
2043
2044 wfile = makefile(s._sock, "wb", DEFAULT_BUFFER_SIZE)
2045 try:
2046 wfile.sendall("".join(buf))
2047 except socket.error:
2048 x = sys.exc_info()[1]
2049 if x.args[0] not in socket_errors_to_ignore:
2050 raise
2051 return
2052 if not s:
2053 return
2054 makefile = self.ssl_adapter.makefile
2055
2056 if hasattr(s, 'settimeout'):
2057 s.settimeout(self.timeout)
2058
2059 conn = self.ConnectionClass(self, s, makefile)
2060
2061 if not isinstance(self.bind_addr, basestring):
2062
2063
2064 if addr is None:
2065
2066 if len(s.getsockname()) == 2:
2067
2068 addr = ('0.0.0.0', 0)
2069 else:
2070
2071 addr = ('::', 0)
2072 conn.remote_addr = addr[0]
2073 conn.remote_port = addr[1]
2074
2075 conn.ssl_env = ssl_env
2076
2077 try:
2078 self.requests.put(conn)
2079 except queue.Full:
2080
2081 conn.close()
2082 return
2083 except socket.timeout:
2084
2085
2086
2087 return
2088 except socket.error:
2089 x = sys.exc_info()[1]
2090 if self.stats['Enabled']:
2091 self.stats['Socket Errors'] += 1
2092 if x.args[0] in socket_error_eintr:
2093
2094
2095
2096
2097
2098
2099 return
2100 if x.args[0] in socket_errors_nonblocking:
2101
2102
2103 return
2104 if x.args[0] in socket_errors_to_ignore:
2105
2106
2107 return
2108 raise
2109
2112
2117 interrupt = property(_get_interrupt, _set_interrupt,
2118 doc="Set this to an Exception instance to "
2119 "interrupt the server.")
2120
2122 """Gracefully shutdown a server that is serving forever."""
2123 self.ready = False
2124 if self._start_time is not None:
2125 self._run_time += (time.time() - self._start_time)
2126 self._start_time = None
2127
2128 sock = getattr(self, "socket", None)
2129 if sock:
2130 if not isinstance(self.bind_addr, basestring):
2131
2132 try:
2133 host, port = sock.getsockname()[:2]
2134 except socket.error:
2135 x = sys.exc_info()[1]
2136 if x.args[0] not in socket_errors_to_ignore:
2137
2138
2139
2140 raise
2141 else:
2142
2143
2144
2145
2146 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
2147 socket.SOCK_STREAM):
2148 af, socktype, proto, canonname, sa = res
2149 s = None
2150 try:
2151 s = socket.socket(af, socktype, proto)
2152
2153
2154
2155 s.settimeout(1.0)
2156 s.connect((host, port))
2157 s.close()
2158 except socket.error:
2159 if s:
2160 s.close()
2161 if hasattr(sock, "close"):
2162 sock.close()
2163 self.socket = None
2164
2165 self.requests.stop(self.shutdown_timeout)
2166
2167
2169
2170 """A base class to interface HTTPServer with other systems, such as WSGI.
2171 """
2172
2175
2177 """Process the current request. Must be overridden in a subclass."""
2178 raise NotImplemented
2179
2180
2181
2182
2183 ssl_adapters = {
2184 'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
2185 'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
2186 }
2187
2188
2190 """Return an SSL adapter class for the given name."""
2191 adapter = ssl_adapters[name.lower()]
2192 if isinstance(adapter, basestring):
2193 last_dot = adapter.rfind(".")
2194 attr_name = adapter[last_dot + 1:]
2195 mod_path = adapter[:last_dot]
2196
2197 try:
2198 mod = sys.modules[mod_path]
2199 if mod is None:
2200 raise KeyError()
2201 except KeyError:
2202
2203 mod = __import__(mod_path, globals(), locals(), [''])
2204
2205
2206 try:
2207 adapter = getattr(mod, attr_name)
2208 except AttributeError:
2209 raise AttributeError("'%s' object has no attribute '%s'"
2210 % (mod_path, attr_name))
2211
2212 return adapter
2213
2214
2215
2216
2218
2219 """A subclass of HTTPServer which calls a WSGI application."""
2220
2221 wsgi_version = (1, 0)
2222 """The version of WSGI to produce."""
2223
2224 - def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
2225 max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5,
2226 accepted_queue_size=-1, accepted_queue_timeout=10):
2242
2244 return self.requests.min
2245
2247 self.requests.min = value
2248 numthreads = property(_get_numthreads, _set_numthreads)
2249
2250
2252
2253 """A base class to interface HTTPServer with WSGI."""
2254
2256 self.req = req
2257 self.started_response = False
2258 self.env = self.get_environ()
2259 self.remaining_bytes_out = None
2260
2262 """Return a new environ dict targeting the given wsgi.version"""
2263 raise NotImplemented
2264
2266 """Process the current request."""
2267 response = self.req.server.wsgi_app(self.env, self.start_response)
2268 try:
2269 for chunk in response:
2270
2271
2272
2273
2274
2275
2276 if chunk:
2277 if isinstance(chunk, unicodestr):
2278 chunk = chunk.encode('ISO-8859-1')
2279 self.write(chunk)
2280 finally:
2281 if hasattr(response, "close"):
2282 response.close()
2283
2285 """WSGI callable to begin the HTTP response."""
2286
2287
2288 if self.started_response and not exc_info:
2289 raise AssertionError("WSGI start_response called a second "
2290 "time with no exc_info.")
2291 self.started_response = True
2292
2293
2294
2295
2296 if self.req.sent_headers:
2297 try:
2298 raise exc_info[0], exc_info[1], exc_info[2]
2299 finally:
2300 exc_info = None
2301
2302 self.req.status = status
2303 for k, v in headers:
2304 if not isinstance(k, str):
2305 raise TypeError(
2306 "WSGI response header key %r is not of type str." % k)
2307 if not isinstance(v, str):
2308 raise TypeError(
2309 "WSGI response header value %r is not of type str." % v)
2310 if k.lower() == 'content-length':
2311 self.remaining_bytes_out = int(v)
2312 self.req.outheaders.extend(headers)
2313
2314 return self.write
2315
2316 - def write(self, chunk):
2317 """WSGI callable to write unbuffered data to the client.
2318
2319 This method is also used internally by start_response (to write
2320 data from the iterable returned by the WSGI application).
2321 """
2322 if not self.started_response:
2323 raise AssertionError("WSGI write called before start_response.")
2324
2325 chunklen = len(chunk)
2326 rbo = self.remaining_bytes_out
2327 if rbo is not None and chunklen > rbo:
2328 if not self.req.sent_headers:
2329
2330 self.req.simple_response(
2331 "500 Internal Server Error",
2332 "The requested resource returned more bytes than the "
2333 "declared Content-Length.")
2334 else:
2335
2336
2337 chunk = chunk[:rbo]
2338
2339 if not self.req.sent_headers:
2340 self.req.sent_headers = True
2341 self.req.send_headers()
2342
2343 self.req.write(chunk)
2344
2345 if rbo is not None:
2346 rbo -= chunklen
2347 if rbo < 0:
2348 raise ValueError(
2349 "Response body exceeds the declared Content-Length.")
2350
2351
2353
2354 """A Gateway class to interface HTTPServer with WSGI 1.0.x."""
2355
2357 """Return a new environ dict targeting the given wsgi.version"""
2358 req = self.req
2359 env = {
2360
2361
2362
2363 'ACTUAL_SERVER_PROTOCOL': req.server.protocol,
2364 'PATH_INFO': req.path,
2365 'QUERY_STRING': req.qs,
2366 'REMOTE_ADDR': req.conn.remote_addr or '',
2367 'REMOTE_PORT': str(req.conn.remote_port or ''),
2368 'REQUEST_METHOD': req.method,
2369 'REQUEST_URI': req.uri,
2370 'SCRIPT_NAME': '',
2371 'SERVER_NAME': req.server.server_name,
2372
2373 'SERVER_PROTOCOL': req.request_protocol,
2374 'SERVER_SOFTWARE': req.server.software,
2375 'wsgi.errors': sys.stderr,
2376 'wsgi.input': req.rfile,
2377 'wsgi.multiprocess': False,
2378 'wsgi.multithread': True,
2379 'wsgi.run_once': False,
2380 'wsgi.url_scheme': req.scheme,
2381 'wsgi.version': (1, 0),
2382 }
2383
2384 if isinstance(req.server.bind_addr, basestring):
2385
2386
2387 env["SERVER_PORT"] = ""
2388 else:
2389 env["SERVER_PORT"] = str(req.server.bind_addr[1])
2390
2391
2392 for k, v in req.inheaders.iteritems():
2393 env["HTTP_" + k.upper().replace("-", "_")] = v
2394
2395
2396 ct = env.pop("HTTP_CONTENT_TYPE", None)
2397 if ct is not None:
2398 env["CONTENT_TYPE"] = ct
2399 cl = env.pop("HTTP_CONTENT_LENGTH", None)
2400 if cl is not None:
2401 env["CONTENT_LENGTH"] = cl
2402
2403 if req.conn.ssl_env:
2404 env.update(req.conn.ssl_env)
2405
2406 return env
2407
2408
2410
2411 """A Gateway class to interface HTTPServer with WSGI u.0.
2412
2413 WSGI u.0 is an experimental protocol, which uses unicode for keys and
2414 values in both Python 2 and Python 3.
2415 """
2416
2418 """Return a new environ dict targeting the given wsgi.version"""
2419 req = self.req
2420 env_10 = WSGIGateway_10.get_environ(self)
2421 env = dict([(k.decode('ISO-8859-1'), v)
2422 for k, v in env_10.iteritems()])
2423 env[u'wsgi.version'] = ('u', 0)
2424
2425
2426 env.setdefault(u'wsgi.url_encoding', u'utf-8')
2427 try:
2428 for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
2429 env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
2430 except UnicodeDecodeError:
2431
2432 env[u'wsgi.url_encoding'] = u'ISO-8859-1'
2433 for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
2434 env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
2435
2436 for k, v in sorted(env.items()):
2437 if isinstance(v, str) and k not in ('REQUEST_URI', 'wsgi.input'):
2438 env[k] = v.decode('ISO-8859-1')
2439
2440 return env
2441
2442 wsgi_gateways = {
2443 (1, 0): WSGIGateway_10,
2444 ('u', 0): WSGIGateway_u0,
2445 }
2446
2447
2449
2450 """A WSGI dispatcher for dispatch based on the PATH_INFO.
2451
2452 apps: a dict or list of (path_prefix, app) pairs.
2453 """
2454
2456 try:
2457 apps = list(apps.items())
2458 except AttributeError:
2459 pass
2460
2461
2462 apps.sort(cmp=lambda x, y: cmp(len(x[0]), len(y[0])))
2463 apps.reverse()
2464
2465
2466
2467 self.apps = [(p.rstrip("/"), a) for p, a in apps]
2468
2469 - def __call__(self, environ, start_response):
2470 path = environ["PATH_INFO"] or "/"
2471 for p, app in self.apps:
2472
2473 if path.startswith(p + "/") or path == p:
2474 environ = environ.copy()
2475 environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
2476 environ["PATH_INFO"] = path[len(p):]
2477 return app(environ, start_response)
2478
2479 start_response('404 Not Found', [('Content-Type', 'text/plain'),
2480 ('Content-Length', '0')])
2481 return ['']
2482