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_makefile',
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 email.utils
87 import socket
88 import sys
89 if 'win' in sys.platform and not hasattr(socket, 'IPPROTO_IPV6'):
90 socket.IPPROTO_IPV6 = 41
91 if sys.version_info < (3,1):
92 import io
93 else:
94 import _pyio as io
95 DEFAULT_BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE
96
97 import threading
98 import time
99 from traceback import format_exc
100 from urllib.parse import unquote
101 from urllib.parse import urlparse
102 from urllib.parse import scheme_chars
103 import warnings
104
105 if sys.version_info >= (3, 0):
106 bytestr = bytes
107 unicodestr = str
108 basestring = (bytes, str)
109 - def ntob(n, encoding='ISO-8859-1'):
110 """Return the given native string as a byte string in the given encoding."""
111
112 return n.encode(encoding)
113 else:
114 bytestr = str
115 unicodestr = unicode
116 basestring = basestring
117 - def ntob(n, encoding='ISO-8859-1'):
118 """Return the given native string as a byte string in the given encoding."""
119
120
121
122 return n
123
124 LF = ntob('\n')
125 CRLF = ntob('\r\n')
126 TAB = ntob('\t')
127 SPACE = ntob(' ')
128 COLON = ntob(':')
129 SEMICOLON = ntob(';')
130 EMPTY = ntob('')
131 NUMBER_SIGN = ntob('#')
132 QUESTION_MARK = ntob('?')
133 ASTERISK = ntob('*')
134 FORWARD_SLASH = ntob('/')
135 quoted_slash = re.compile(ntob("(?i)%2F"))
136
137 import errno
138
140 """Return error numbers for all errors in errnames on this platform.
141
142 The 'errno' module contains different global constants depending on
143 the specific platform (OS). This function will return the list of
144 numeric values for a given list of potential names.
145 """
146 errno_names = dir(errno)
147 nums = [getattr(errno, k) for k in errnames if k in errno_names]
148
149 return list(dict.fromkeys(nums).keys())
150
151 socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
152
153 socket_errors_to_ignore = plat_specific_errors(
154 "EPIPE",
155 "EBADF", "WSAEBADF",
156 "ENOTSOCK", "WSAENOTSOCK",
157 "ETIMEDOUT", "WSAETIMEDOUT",
158 "ECONNREFUSED", "WSAECONNREFUSED",
159 "ECONNRESET", "WSAECONNRESET",
160 "ECONNABORTED", "WSAECONNABORTED",
161 "ENETRESET", "WSAENETRESET",
162 "EHOSTDOWN", "EHOSTUNREACH",
163 )
164 socket_errors_to_ignore.append("timed out")
165 socket_errors_to_ignore.append("The read operation timed out")
166
167 socket_errors_nonblocking = plat_specific_errors(
168 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
169
170 comma_separated_headers = [ntob(h) for h in
171 ['Accept', 'Accept-Charset', 'Accept-Encoding',
172 'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control',
173 'Connection', 'Content-Encoding', 'Content-Language', 'Expect',
174 'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE',
175 'Trailer', 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning',
176 'WWW-Authenticate']]
177
178
179 import logging
180 if not hasattr(logging, 'statistics'): logging.statistics = {}
181
182
184 """Read headers from the given stream into the given header dict.
185
186 If hdict is None, a new header dict is created. Returns the populated
187 header dict.
188
189 Headers which are repeated are folded together using a comma if their
190 specification so dictates.
191
192 This function raises ValueError when the read bytes violate the HTTP spec.
193 You should probably return "400 Bad Request" if this happens.
194 """
195 if hdict is None:
196 hdict = {}
197
198 while True:
199 line = rfile.readline()
200 if not line:
201
202 raise ValueError("Illegal end of headers.")
203
204 if line == CRLF:
205
206 break
207 if not line.endswith(CRLF):
208 raise ValueError("HTTP requires CRLF terminators")
209
210 if line[0] in (SPACE, TAB):
211
212 v = line.strip()
213 else:
214 try:
215 k, v = line.split(COLON, 1)
216 except ValueError:
217 raise ValueError("Illegal header line.")
218
219 k = k.strip().title()
220 v = v.strip()
221 hname = k
222
223 if k in comma_separated_headers:
224 existing = hdict.get(hname)
225 if existing:
226 v = b", ".join((existing, v))
227 hdict[hname] = v
228
229 return hdict
230
231
234
236 """Wraps a file-like object, raising MaxSizeExceeded if too large."""
237
239 self.rfile = rfile
240 self.maxlen = maxlen
241 self.bytes_read = 0
242
244 if self.maxlen and self.bytes_read > self.maxlen:
245 raise MaxSizeExceeded()
246
247 - def read(self, size=None):
252
271
273
274 total = 0
275 lines = []
276 line = self.readline()
277 while line:
278 lines.append(line)
279 total += len(line)
280 if 0 < sizehint <= total:
281 break
282 line = self.readline()
283 return lines
284
287
290
296
302
303
305 """Wraps a file-like object, returning an empty string when exhausted."""
306
307 - def __init__(self, rfile, content_length):
308 self.rfile = rfile
309 self.remaining = content_length
310
311 - def read(self, size=None):
312 if self.remaining == 0:
313 return b''
314 if size is None:
315 size = self.remaining
316 else:
317 size = min(size, self.remaining)
318
319 data = self.rfile.read(size)
320 self.remaining -= len(data)
321 return data
322
324 if self.remaining == 0:
325 return b''
326 if size is None:
327 size = self.remaining
328 else:
329 size = min(size, self.remaining)
330
331 data = self.rfile.readline(size)
332 self.remaining -= len(data)
333 return data
334
336
337 total = 0
338 lines = []
339 line = self.readline(sizehint)
340 while line:
341 lines.append(line)
342 total += len(line)
343 if 0 < sizehint <= total:
344 break
345 line = self.readline(sizehint)
346 return lines
347
350
353
358
359
361 """Wraps a file-like object, returning an empty string when exhausted.
362
363 This class is intended to provide a conforming wsgi.input value for
364 request entities that have been encoded with the 'chunked' transfer
365 encoding.
366 """
367
368 - def __init__(self, rfile, maxlen, bufsize=8192):
375
377 if self.closed:
378 return
379
380 line = self.rfile.readline()
381 self.bytes_read += len(line)
382
383 if self.maxlen and self.bytes_read > self.maxlen:
384 raise MaxSizeExceeded("Request Entity Too Large", self.maxlen)
385
386 line = line.strip().split(SEMICOLON, 1)
387
388 try:
389 chunk_size = line.pop(0)
390 chunk_size = int(chunk_size, 16)
391 except ValueError:
392 raise ValueError("Bad chunked transfer size: " + repr(chunk_size))
393
394 if chunk_size <= 0:
395 self.closed = True
396 return
397
398
399
400 if self.maxlen and self.bytes_read + chunk_size > self.maxlen:
401 raise IOError("Request Entity Too Large")
402
403 chunk = self.rfile.read(chunk_size)
404 self.bytes_read += len(chunk)
405 self.buffer += chunk
406
407 crlf = self.rfile.read(2)
408 if crlf != CRLF:
409 raise ValueError(
410 "Bad chunked transfer coding (expected '\\r\\n', "
411 "got " + repr(crlf) + ")")
412
413 - def read(self, size=None):
414 data = EMPTY
415 while True:
416 if size and len(data) >= size:
417 return data
418
419 if not self.buffer:
420 self._fetch()
421 if not self.buffer:
422
423 return data
424
425 if size:
426 remaining = size - len(data)
427 data += self.buffer[:remaining]
428 self.buffer = self.buffer[remaining:]
429 else:
430 data += self.buffer
431
433 data = EMPTY
434 while True:
435 if size and len(data) >= size:
436 return data
437
438 if not self.buffer:
439 self._fetch()
440 if not self.buffer:
441
442 return data
443
444 newline_pos = self.buffer.find(LF)
445 if size:
446 if newline_pos == -1:
447 remaining = size - len(data)
448 data += self.buffer[:remaining]
449 self.buffer = self.buffer[remaining:]
450 else:
451 remaining = min(size - len(data), newline_pos)
452 data += self.buffer[:remaining]
453 self.buffer = self.buffer[remaining:]
454 else:
455 if newline_pos == -1:
456 data += self.buffer
457 else:
458 data += self.buffer[:newline_pos]
459 self.buffer = self.buffer[newline_pos:]
460
462
463 total = 0
464 lines = []
465 line = self.readline(sizehint)
466 while line:
467 lines.append(line)
468 total += len(line)
469 if 0 < sizehint <= total:
470 break
471 line = self.readline(sizehint)
472 return lines
473
475 if not self.closed:
476 raise ValueError(
477 "Cannot read trailers until the request body has been read.")
478
479 while True:
480 line = self.rfile.readline()
481 if not line:
482
483 raise ValueError("Illegal end of headers.")
484
485 self.bytes_read += len(line)
486 if self.maxlen and self.bytes_read > self.maxlen:
487 raise IOError("Request Entity Too Large")
488
489 if line == CRLF:
490
491 break
492 if not line.endswith(CRLF):
493 raise ValueError("HTTP requires CRLF terminators")
494
495 yield line
496
499
501
502 total = 0
503 line = self.readline(sizehint)
504 while line:
505 yield line
506 total += len(line)
507 if 0 < sizehint <= total:
508 break
509 line = self.readline(sizehint)
510
511
513 """An HTTP Request (and response).
514
515 A single HTTP connection may consist of multiple request/response pairs.
516 """
517
518 server = None
519 """The HTTPServer object which is receiving this request."""
520
521 conn = None
522 """The HTTPConnection object on which this request connected."""
523
524 inheaders = {}
525 """A dict of request headers."""
526
527 outheaders = []
528 """A list of header tuples to write in the response."""
529
530 ready = False
531 """When True, the request has been parsed and is ready to begin generating
532 the response. When False, signals the calling Connection that the response
533 should not be generated and the connection should close."""
534
535 close_connection = False
536 """Signals the calling Connection that the request should close. This does
537 not imply an error! The client and/or server may each request that the
538 connection be closed."""
539
540 chunked_write = False
541 """If True, output will be encoded with the "chunked" transfer-coding.
542
543 This value is set automatically inside send_headers."""
544
564
592
594
595
596
597
598
599
600
601 request_line = self.rfile.readline()
602
603
604
605 self.started_request = True
606 if not request_line:
607 return False
608
609 if request_line == CRLF:
610
611
612
613
614 request_line = self.rfile.readline()
615 if not request_line:
616 return False
617
618 if not request_line.endswith(CRLF):
619 self.simple_response("400 Bad Request", "HTTP requires CRLF terminators")
620 return False
621
622 try:
623 method, uri, req_protocol = request_line.strip().split(SPACE, 2)
624
625 rp = int(req_protocol[5:6]), int(req_protocol[7:8])
626 except ValueError:
627 self.simple_response("400 Bad Request", "Malformed Request-Line")
628 return False
629
630 self.uri = uri
631 self.method = method
632
633
634 scheme, authority, path = self.parse_request_uri(uri)
635 if NUMBER_SIGN in path:
636 self.simple_response("400 Bad Request",
637 "Illegal #fragment in Request-URI.")
638 return False
639
640 if scheme:
641 self.scheme = scheme
642
643 qs = EMPTY
644 if QUESTION_MARK in path:
645 path, qs = path.split(QUESTION_MARK, 1)
646
647
648
649
650
651
652
653
654 try:
655 atoms = [self.unquote_bytes(x) for x in quoted_slash.split(path)]
656 except ValueError:
657 ex = sys.exc_info()[1]
658 self.simple_response("400 Bad Request", ex.args[0])
659 return False
660 path = b"%2F".join(atoms)
661 self.path = path
662
663
664
665 self.qs = qs
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680 sp = int(self.server.protocol[5:6]), int(self.server.protocol[7:8])
681
682 if sp[0] != rp[0]:
683 self.simple_response("505 HTTP Version Not Supported")
684 return False
685
686 self.request_protocol = req_protocol
687 self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
688 return True
689
691 """Read self.rfile into self.inheaders. Return success."""
692
693
694 try:
695 read_headers(self.rfile, self.inheaders)
696 except ValueError:
697 ex = sys.exc_info()[1]
698 self.simple_response("400 Bad Request", ex.args[0])
699 return False
700
701 mrbs = self.server.max_request_body_size
702 if mrbs and int(self.inheaders.get(b"Content-Length", 0)) > mrbs:
703 self.simple_response("413 Request Entity Too Large",
704 "The entity sent with the request exceeds the maximum "
705 "allowed bytes.")
706 return False
707
708
709 if self.response_protocol == "HTTP/1.1":
710
711 if self.inheaders.get(b"Connection", b"") == b"close":
712 self.close_connection = True
713 else:
714
715 if self.inheaders.get(b"Connection", b"") != b"Keep-Alive":
716 self.close_connection = True
717
718
719 te = None
720 if self.response_protocol == "HTTP/1.1":
721 te = self.inheaders.get(b"Transfer-Encoding")
722 if te:
723 te = [x.strip().lower() for x in te.split(b",") if x.strip()]
724
725 self.chunked_read = False
726
727 if te:
728 for enc in te:
729 if enc == b"chunked":
730 self.chunked_read = True
731 else:
732
733
734 self.simple_response("501 Unimplemented")
735 self.close_connection = True
736 return False
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755 if self.inheaders.get(b"Expect", b"") == b"100-continue":
756
757
758 msg = self.server.protocol.encode('ascii') + b" 100 Continue\r\n\r\n"
759 try:
760 self.conn.wfile.write(msg)
761 except socket.error:
762 x = sys.exc_info()[1]
763 if x.args[0] not in socket_errors_to_ignore:
764 raise
765 return True
766
768 """Parse a Request-URI into (scheme, authority, path).
769
770 Note that Request-URI's must be one of::
771
772 Request-URI = "*" | absoluteURI | abs_path | authority
773
774 Therefore, a Request-URI which starts with a double forward-slash
775 cannot be a "net_path"::
776
777 net_path = "//" authority [ abs_path ]
778
779 Instead, it must be interpreted as an "abs_path" with an empty first
780 path segment::
781
782 abs_path = "/" path_segments
783 path_segments = segment *( "/" segment )
784 segment = *pchar *( ";" param )
785 param = *pchar
786 """
787 if uri == ASTERISK:
788 return None, None, uri
789
790 scheme, sep, remainder = uri.partition(b'://')
791 if sep and QUESTION_MARK not in scheme:
792
793
794
795 authority, path_a, path_b = remainder.partition(FORWARD_SLASH)
796 return scheme.lower(), authority, path_a+path_b
797
798 if uri.startswith(FORWARD_SLASH):
799
800 return None, None, uri
801 else:
802
803 return None, uri, None
804
806 """takes quoted string and unquotes % encoded values"""
807 res = path.split(b'%')
808
809 for i in range(1, len(res)):
810 item = res[i]
811 try:
812 res[i] = bytes([int(item[:2], 16)]) + item[2:]
813 except ValueError:
814 raise
815 return b''.join(res)
816
839
841 """Write a simple response back to the client."""
842 status = str(status)
843 buf = [bytes(self.server.protocol, "ascii") + SPACE +
844 bytes(status, "ISO-8859-1") + CRLF,
845 bytes("Content-Length: %s\r\n" % len(msg), "ISO-8859-1"),
846 b"Content-Type: text/plain\r\n"]
847
848 if status[:3] in ("413", "414"):
849
850 self.close_connection = True
851 if self.response_protocol == 'HTTP/1.1':
852
853
854
855 buf.append(b"Connection: close\r\n")
856 else:
857
858
859 status = "400 Bad Request"
860
861 buf.append(CRLF)
862 if msg:
863 if isinstance(msg, unicodestr):
864 msg = msg.encode("ISO-8859-1")
865 buf.append(msg)
866
867 try:
868 self.conn.wfile.write(b"".join(buf))
869 except socket.error:
870 x = sys.exc_info()[1]
871 if x.args[0] not in socket_errors_to_ignore:
872 raise
873
881
883 """Assert, process, and send the HTTP response message-headers.
884
885 You must set self.status, and self.outheaders before calling this.
886 """
887 hkeys = [key.lower() for key, value in self.outheaders]
888 status = int(self.status[:3])
889
890 if status == 413:
891
892 self.close_connection = True
893 elif b"content-length" not in hkeys:
894
895
896
897 if status < 200 or status in (204, 205, 304):
898 pass
899 else:
900 if (self.response_protocol == 'HTTP/1.1'
901 and self.method != b'HEAD'):
902
903 self.chunked_write = True
904 self.outheaders.append((b"Transfer-Encoding", b"chunked"))
905 else:
906
907 self.close_connection = True
908
909 if b"connection" not in hkeys:
910 if self.response_protocol == 'HTTP/1.1':
911
912 if self.close_connection:
913 self.outheaders.append((b"Connection", b"close"))
914 else:
915
916 if not self.close_connection:
917 self.outheaders.append((b"Connection", b"Keep-Alive"))
918
919 if (not self.close_connection) and (not self.chunked_read):
920
921
922
923
924
925
926
927
928
929
930
931
932 remaining = getattr(self.rfile, 'remaining', 0)
933 if remaining > 0:
934 self.rfile.read(remaining)
935
936 if b"date" not in hkeys:
937 self.outheaders.append(
938 (b"Date", email.utils.formatdate(usegmt=True).encode('ISO-8859-1')))
939
940 if b"server" not in hkeys:
941 self.outheaders.append(
942 (b"Server", self.server.server_name.encode('ISO-8859-1')))
943
944 buf = [self.server.protocol.encode('ascii') + SPACE + self.status + CRLF]
945 for k, v in self.outheaders:
946 buf.append(k + COLON + SPACE + v + CRLF)
947 buf.append(CRLF)
948 self.conn.wfile.write(EMPTY.join(buf))
949
950
952 """Exception raised when a client speaks HTTP to an HTTPS socket."""
953 pass
954
955
957 """Exception raised when the SSL implementation signals a fatal alert."""
958 pass
959
960
962 """Faux file object attached to a socket object."""
963
965 self._checkClosed()
966 if isinstance(b, str):
967 raise TypeError("can't write str to binary stream")
968
969 with self._write_lock:
970 self._write_buf.extend(b)
971 self._flush_unlocked()
972 return len(b)
973
975 self._checkClosed("flush of closed file")
976 while self._write_buf:
977 try:
978
979
980 n = self.raw.write(bytes(self._write_buf))
981 except io.BlockingIOError as e:
982 n = e.characters_written
983 del self._write_buf[:n]
984
985
987 if 'r' in mode:
988 return io.BufferedReader(socket.SocketIO(sock, mode), bufsize)
989 else:
990 return CP_BufferedWriter(socket.SocketIO(sock, mode), bufsize)
991
993 """An HTTP connection (active socket).
994
995 server: the Server object which received this connection.
996 socket: the raw socket object (usually TCP) for this connection.
997 makefile: a fileobject class for reading from the socket.
998 """
999
1000 remote_addr = None
1001 remote_port = None
1002 ssl_env = None
1003 rbufsize = DEFAULT_BUFFER_SIZE
1004 wbufsize = DEFAULT_BUFFER_SIZE
1005 RequestHandlerClass = HTTPRequest
1006
1013
1015 """Read each request and respond appropriately."""
1016 request_seen = False
1017 try:
1018 while True:
1019
1020
1021
1022 req = None
1023 req = self.RequestHandlerClass(self.server, self)
1024
1025
1026 req.parse_request()
1027 if self.server.stats['Enabled']:
1028 self.requests_seen += 1
1029 if not req.ready:
1030
1031
1032
1033 return
1034
1035 request_seen = True
1036 req.respond()
1037 if req.close_connection:
1038 return
1039 except socket.error:
1040 e = sys.exc_info()[1]
1041 errnum = e.args[0]
1042
1043 if errnum == 'timed out' or errnum == 'The read operation timed out':
1044
1045
1046
1047
1048 if (not request_seen) or (req and req.started_request):
1049
1050
1051 if req and not req.sent_headers:
1052 try:
1053 req.simple_response("408 Request Timeout")
1054 except FatalSSLAlert:
1055
1056 return
1057 elif errnum not in socket_errors_to_ignore:
1058 self.server.error_log("socket.error %s" % repr(errnum),
1059 level=logging.WARNING, traceback=True)
1060 if req and not req.sent_headers:
1061 try:
1062 req.simple_response("500 Internal Server Error")
1063 except FatalSSLAlert:
1064
1065 return
1066 return
1067 except (KeyboardInterrupt, SystemExit):
1068 raise
1069 except FatalSSLAlert:
1070
1071 return
1072 except NoSSLError:
1073 if req and not req.sent_headers:
1074
1075 self.wfile = CP_makefile(self.socket._sock, "wb", self.wbufsize)
1076 req.simple_response("400 Bad Request",
1077 "The client sent a plain HTTP request, but "
1078 "this server only speaks HTTPS on this port.")
1079 self.linger = True
1080 except Exception:
1081 e = sys.exc_info()[1]
1082 self.server.error_log(repr(e), level=logging.ERROR, traceback=True)
1083 if req and not req.sent_headers:
1084 try:
1085 req.simple_response("500 Internal Server Error")
1086 except FatalSSLAlert:
1087
1088 return
1089
1090 linger = False
1091
1093 """Close the socket underlying this connection."""
1094 self.rfile.close()
1095
1096 if not self.linger:
1097
1098
1099
1100
1101
1102
1103
1104 self.socket.close()
1105 else:
1106
1107
1108
1109
1110
1111
1112 pass
1113
1114
1116 """An object which equals and does math like the integer '0' but evals True."""
1121 trueyzero = TrueyZero()
1122
1123
1124 _SHUTDOWNREQUEST = None
1125
1127 """Thread which continuously polls a Queue for Connection objects.
1128
1129 Due to the timing issues of polling a Queue, a WorkerThread does not
1130 check its own 'ready' flag after it has started. To stop the thread,
1131 it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
1132 (one for each running WorkerThread).
1133 """
1134
1135 conn = None
1136 """The current connection pulled off the Queue, or None."""
1137
1138 server = None
1139 """The HTTP Server which spawned this thread, and which owns the
1140 Queue and is placing active connections into it."""
1141
1142 ready = False
1143 """A simple flag for the calling server to know when this thread
1144 has begun polling the Queue."""
1145
1146
1148 self.ready = False
1149 self.server = server
1150
1151 self.requests_seen = 0
1152 self.bytes_read = 0
1153 self.bytes_written = 0
1154 self.start_time = None
1155 self.work_time = 0
1156 self.stats = {
1157 'Requests': lambda s: self.requests_seen + ((self.start_time is None) and trueyzero or self.conn.requests_seen),
1158 'Bytes Read': lambda s: self.bytes_read + ((self.start_time is None) and trueyzero or self.conn.rfile.bytes_read),
1159 'Bytes Written': lambda s: self.bytes_written + ((self.start_time is None) and trueyzero or self.conn.wfile.bytes_written),
1160 'Work Time': lambda s: self.work_time + ((self.start_time is None) and trueyzero or time.time() - self.start_time),
1161 'Read Throughput': lambda s: s['Bytes Read'](s) / (s['Work Time'](s) or 1e-6),
1162 'Write Throughput': lambda s: s['Bytes Written'](s) / (s['Work Time'](s) or 1e-6),
1163 }
1164 threading.Thread.__init__(self)
1165
1192
1193
1195 """A Request Queue for an HTTPServer which pools threads.
1196
1197 ThreadPool objects must provide min, get(), put(obj), start()
1198 and stop(timeout) attributes.
1199 """
1200
1201 - def __init__(self, server, min=10, max=-1):
1202 self.server = server
1203 self.min = min
1204 self.max = max
1205 self._threads = []
1206 self._queue = queue.Queue()
1207 self.get = self._queue.get
1208
1210 """Start the pool of threads."""
1211 for i in range(self.min):
1212 self._threads.append(WorkerThread(self.server))
1213 for worker in self._threads:
1214 worker.setName("CP Server " + worker.getName())
1215 worker.start()
1216 for worker in self._threads:
1217 while not worker.ready:
1218 time.sleep(.1)
1219
1221 """Number of worker threads which are idle. Read-only."""
1222 return len([t for t in self._threads if t.conn is None])
1223 idle = property(_get_idle, doc=_get_idle.__doc__)
1224
1225 - def put(self, obj):
1229
1230 - def grow(self, amount):
1231 """Spawn new worker threads (not above self.max)."""
1232 for i in range(amount):
1233 if self.max > 0 and len(self._threads) >= self.max:
1234 break
1235 worker = WorkerThread(self.server)
1236 worker.setName("CP Server " + worker.getName())
1237 self._threads.append(worker)
1238 worker.start()
1239
1241 """Kill off worker threads (not below self.min)."""
1242
1243
1244 for t in self._threads:
1245 if not t.isAlive():
1246 self._threads.remove(t)
1247 amount -= 1
1248
1249 if amount > 0:
1250 for i in range(min(amount, len(self._threads) - self.min)):
1251
1252
1253
1254
1255 self._queue.put(_SHUTDOWNREQUEST)
1256
1257 - def stop(self, timeout=5):
1258
1259
1260 for worker in self._threads:
1261 self._queue.put(_SHUTDOWNREQUEST)
1262
1263
1264 current = threading.currentThread()
1265 if timeout and timeout >= 0:
1266 endtime = time.time() + timeout
1267 while self._threads:
1268 worker = self._threads.pop()
1269 if worker is not current and worker.isAlive():
1270 try:
1271 if timeout is None or timeout < 0:
1272 worker.join()
1273 else:
1274 remaining_time = endtime - time.time()
1275 if remaining_time > 0:
1276 worker.join(remaining_time)
1277 if worker.isAlive():
1278
1279
1280 c = worker.conn
1281 if c and not c.rfile.closed:
1282 try:
1283 c.socket.shutdown(socket.SHUT_RD)
1284 except TypeError:
1285
1286 c.socket.shutdown()
1287 worker.join()
1288 except (AssertionError,
1289
1290
1291 KeyboardInterrupt):
1292 pass
1293
1295 return self._queue.qsize()
1296 qsize = property(_get_qsize)
1297
1298
1299
1300 try:
1301 import fcntl
1302 except ImportError:
1303 try:
1304 from ctypes import windll, WinError
1305 except ImportError:
1307 """Dummy function, since neither fcntl nor ctypes are available."""
1308 pass
1309 else:
1311 """Mark the given socket fd as non-inheritable (Windows)."""
1312 if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
1313 raise WinError()
1314 else:
1316 """Mark the given socket fd as non-inheritable (POSIX)."""
1317 fd = sock.fileno()
1318 old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
1319 fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
1320
1321
1323 """Base class for SSL driver library adapters.
1324
1325 Required methods:
1326
1327 * ``wrap(sock) -> (wrapped socket, ssl environ dict)``
1328 * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> socket file object``
1329 """
1330
1331 - def __init__(self, certificate, private_key, certificate_chain=None):
1335
1336 - def wrap(self, sock):
1337 raise NotImplemented
1338
1340 raise NotImplemented
1341
1342
1344 """An HTTP server."""
1345
1346 _bind_addr = "127.0.0.1"
1347 _interrupt = None
1348
1349 gateway = None
1350 """A Gateway instance."""
1351
1352 minthreads = None
1353 """The minimum number of worker threads to create (default 10)."""
1354
1355 maxthreads = None
1356 """The maximum number of worker threads to create (default -1 = no limit)."""
1357
1358 server_name = None
1359 """The name of the server; defaults to socket.gethostname()."""
1360
1361 protocol = "HTTP/1.1"
1362 """The version string to write in the Status-Line of all HTTP responses.
1363
1364 For example, "HTTP/1.1" is the default. This also limits the supported
1365 features used in the response."""
1366
1367 request_queue_size = 5
1368 """The 'backlog' arg to socket.listen(); max queued connections (default 5)."""
1369
1370 shutdown_timeout = 5
1371 """The total time, in seconds, to wait for worker threads to cleanly exit."""
1372
1373 timeout = 10
1374 """The timeout in seconds for accepted connections (default 10)."""
1375
1376 version = "CherryPy/3.2.2"
1377 """A version string for the HTTPServer."""
1378
1379 software = None
1380 """The value to set for the SERVER_SOFTWARE entry in the WSGI environ.
1381
1382 If None, this defaults to ``'%s Server' % self.version``."""
1383
1384 ready = False
1385 """An internal flag which marks whether the socket is accepting connections."""
1386
1387 max_request_header_size = 0
1388 """The maximum size, in bytes, for request headers, or 0 for no limit."""
1389
1390 max_request_body_size = 0
1391 """The maximum size, in bytes, for request bodies, or 0 for no limit."""
1392
1393 nodelay = True
1394 """If True (the default since 3.1), sets the TCP_NODELAY socket option."""
1395
1396 ConnectionClass = HTTPConnection
1397 """The class to use for handling HTTP connections."""
1398
1399 ssl_adapter = None
1400 """An instance of SSLAdapter (or a subclass).
1401
1402 You must have the corresponding SSL driver library installed."""
1403
1404 - def __init__(self, bind_addr, gateway, minthreads=10, maxthreads=-1,
1405 server_name=None):
1415
1417 self._start_time = None
1418 self._run_time = 0
1419 self.stats = {
1420 'Enabled': False,
1421 'Bind Address': lambda s: repr(self.bind_addr),
1422 'Run time': lambda s: (not s['Enabled']) and -1 or self.runtime(),
1423 'Accepts': 0,
1424 'Accepts/sec': lambda s: s['Accepts'] / self.runtime(),
1425 'Queue': lambda s: getattr(self.requests, "qsize", None),
1426 'Threads': lambda s: len(getattr(self.requests, "_threads", [])),
1427 'Threads Idle': lambda s: getattr(self.requests, "idle", None),
1428 'Socket Errors': 0,
1429 'Requests': lambda s: (not s['Enabled']) and -1 or sum([w['Requests'](w) for w
1430 in s['Worker Threads'].values()], 0),
1431 'Bytes Read': lambda s: (not s['Enabled']) and -1 or sum([w['Bytes Read'](w) for w
1432 in s['Worker Threads'].values()], 0),
1433 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum([w['Bytes Written'](w) for w
1434 in s['Worker Threads'].values()], 0),
1435 'Work Time': lambda s: (not s['Enabled']) and -1 or sum([w['Work Time'](w) for w
1436 in s['Worker Threads'].values()], 0),
1437 'Read Throughput': lambda s: (not s['Enabled']) and -1 or sum(
1438 [w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6)
1439 for w in s['Worker Threads'].values()], 0),
1440 'Write Throughput': lambda s: (not s['Enabled']) and -1 or sum(
1441 [w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6)
1442 for w in s['Worker Threads'].values()], 0),
1443 'Worker Threads': {},
1444 }
1445 logging.statistics["CherryPy HTTPServer %d" % id(self)] = self.stats
1446
1448 if self._start_time is None:
1449 return self._run_time
1450 else:
1451 return self._run_time + (time.time() - self._start_time)
1452
1454 return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
1455 self.bind_addr)
1456
1460 if isinstance(value, tuple) and value[0] in ('', None):
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471 raise ValueError("Host values of '' or None are not allowed. "
1472 "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
1473 "to listen on all active interfaces.")
1474 self._bind_addr = value
1475 bind_addr = property(_get_bind_addr, _set_bind_addr,
1476 doc="""The interface on which to listen for connections.
1477
1478 For TCP sockets, a (host, port) tuple. Host values may be any IPv4
1479 or IPv6 address, or any valid hostname. The string 'localhost' is a
1480 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
1481 The string '0.0.0.0' is a special IPv4 entry meaning "any active
1482 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
1483 IPv6. The empty string or None are not allowed.
1484
1485 For UNIX sockets, supply the filename as a string.""")
1486
1488 """Run the server forever."""
1489
1490
1491
1492
1493 self._interrupt = None
1494
1495 if self.software is None:
1496 self.software = "%s Server" % self.version
1497
1498
1499 if isinstance(self.bind_addr, basestring):
1500
1501
1502
1503 try: os.unlink(self.bind_addr)
1504 except: pass
1505
1506
1507 try: os.chmod(self.bind_addr, 511)
1508 except: pass
1509
1510 info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1511 else:
1512
1513
1514 host, port = self.bind_addr
1515 try:
1516 info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1517 socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
1518 except socket.gaierror:
1519 if ':' in self.bind_addr[0]:
1520 info = [(socket.AF_INET6, socket.SOCK_STREAM,
1521 0, "", self.bind_addr + (0, 0))]
1522 else:
1523 info = [(socket.AF_INET, socket.SOCK_STREAM,
1524 0, "", self.bind_addr)]
1525
1526 self.socket = None
1527 msg = "No socket could be created"
1528 for res in info:
1529 af, socktype, proto, canonname, sa = res
1530 try:
1531 self.bind(af, socktype, proto)
1532 except socket.error:
1533 if self.socket:
1534 self.socket.close()
1535 self.socket = None
1536 continue
1537 break
1538 if not self.socket:
1539 raise socket.error(msg)
1540
1541
1542 self.socket.settimeout(1)
1543 self.socket.listen(self.request_queue_size)
1544
1545
1546 self.requests.start()
1547
1548 self.ready = True
1549 self._start_time = time.time()
1550 while self.ready:
1551 try:
1552 self.tick()
1553 except (KeyboardInterrupt, SystemExit):
1554 raise
1555 except:
1556 self.error_log("Error in HTTPServer.tick", level=logging.ERROR,
1557 traceback=True)
1558 if self.interrupt:
1559 while self.interrupt is True:
1560
1561 time.sleep(0.1)
1562 if self.interrupt:
1563 raise self.interrupt
1564
1565 - def error_log(self, msg="", level=20, traceback=False):
1573
1574 - def bind(self, family, type, proto=0):
1575 """Create (or recreate) the actual socket object."""
1576 self.socket = socket.socket(family, type, proto)
1577 prevent_socket_inheritance(self.socket)
1578 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1579 if self.nodelay and not isinstance(self.bind_addr, str):
1580 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1581
1582 if self.ssl_adapter is not None:
1583 self.socket = self.ssl_adapter.bind(self.socket)
1584
1585
1586
1587 if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
1588 and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
1589 try:
1590 self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
1591 except (AttributeError, socket.error):
1592
1593
1594 pass
1595
1596 self.socket.bind(self.bind_addr)
1597
1599 """Accept a new connection and put it on the Queue."""
1600 try:
1601 s, addr = self.socket.accept()
1602 if self.stats['Enabled']:
1603 self.stats['Accepts'] += 1
1604 if not self.ready:
1605 return
1606
1607 prevent_socket_inheritance(s)
1608 if hasattr(s, 'settimeout'):
1609 s.settimeout(self.timeout)
1610
1611 makefile = CP_makefile
1612 ssl_env = {}
1613
1614 if self.ssl_adapter is not None:
1615 try:
1616 s, ssl_env = self.ssl_adapter.wrap(s)
1617 except NoSSLError:
1618 msg = ("The client sent a plain HTTP request, but "
1619 "this server only speaks HTTPS on this port.")
1620 buf = ["%s 400 Bad Request\r\n" % self.protocol,
1621 "Content-Length: %s\r\n" % len(msg),
1622 "Content-Type: text/plain\r\n\r\n",
1623 msg]
1624
1625 wfile = makefile(s, "wb", DEFAULT_BUFFER_SIZE)
1626 try:
1627 wfile.write("".join(buf).encode('ISO-8859-1'))
1628 except socket.error:
1629 x = sys.exc_info()[1]
1630 if x.args[0] not in socket_errors_to_ignore:
1631 raise
1632 return
1633 if not s:
1634 return
1635 makefile = self.ssl_adapter.makefile
1636
1637 if hasattr(s, 'settimeout'):
1638 s.settimeout(self.timeout)
1639
1640 conn = self.ConnectionClass(self, s, makefile)
1641
1642 if not isinstance(self.bind_addr, basestring):
1643
1644
1645 if addr is None:
1646
1647 if len(s.getsockname()) == 2:
1648
1649 addr = ('0.0.0.0', 0)
1650 else:
1651
1652 addr = ('::', 0)
1653 conn.remote_addr = addr[0]
1654 conn.remote_port = addr[1]
1655
1656 conn.ssl_env = ssl_env
1657
1658 self.requests.put(conn)
1659 except socket.timeout:
1660
1661
1662
1663 return
1664 except socket.error:
1665 x = sys.exc_info()[1]
1666 if self.stats['Enabled']:
1667 self.stats['Socket Errors'] += 1
1668 if x.args[0] in socket_error_eintr:
1669
1670
1671
1672
1673
1674 return
1675 if x.args[0] in socket_errors_nonblocking:
1676
1677 return
1678 if x.args[0] in socket_errors_to_ignore:
1679
1680
1681 return
1682 raise
1683
1690 interrupt = property(_get_interrupt, _set_interrupt,
1691 doc="Set this to an Exception instance to "
1692 "interrupt the server.")
1693
1695 """Gracefully shutdown a server that is serving forever."""
1696 self.ready = False
1697 if self._start_time is not None:
1698 self._run_time += (time.time() - self._start_time)
1699 self._start_time = None
1700
1701 sock = getattr(self, "socket", None)
1702 if sock:
1703 if not isinstance(self.bind_addr, basestring):
1704
1705 try:
1706 host, port = sock.getsockname()[:2]
1707 except socket.error:
1708 x = sys.exc_info()[1]
1709 if x.args[0] not in socket_errors_to_ignore:
1710
1711
1712 raise
1713 else:
1714
1715
1716
1717
1718 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1719 socket.SOCK_STREAM):
1720 af, socktype, proto, canonname, sa = res
1721 s = None
1722 try:
1723 s = socket.socket(af, socktype, proto)
1724
1725
1726 s.settimeout(1.0)
1727 s.connect((host, port))
1728 s.close()
1729 except socket.error:
1730 if s:
1731 s.close()
1732 if hasattr(sock, "close"):
1733 sock.close()
1734 self.socket = None
1735
1736 self.requests.stop(self.shutdown_timeout)
1737
1738
1740 """A base class to interface HTTPServer with other systems, such as WSGI."""
1741
1744
1746 """Process the current request. Must be overridden in a subclass."""
1747 raise NotImplemented
1748
1749
1750
1751
1752 ssl_adapters = {
1753 'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
1754 }
1755
1757 """Return an SSL adapter class for the given name."""
1758 adapter = ssl_adapters[name.lower()]
1759 if isinstance(adapter, basestring):
1760 last_dot = adapter.rfind(".")
1761 attr_name = adapter[last_dot + 1:]
1762 mod_path = adapter[:last_dot]
1763
1764 try:
1765 mod = sys.modules[mod_path]
1766 if mod is None:
1767 raise KeyError()
1768 except KeyError:
1769
1770 mod = __import__(mod_path, globals(), locals(), [''])
1771
1772
1773 try:
1774 adapter = getattr(mod, attr_name)
1775 except AttributeError:
1776 raise AttributeError("'%s' object has no attribute '%s'"
1777 % (mod_path, attr_name))
1778
1779 return adapter
1780
1781
1782
1783
1785 """A subclass of HTTPServer which calls a WSGI application."""
1786
1787 wsgi_version = (1, 0)
1788 """The version of WSGI to produce."""
1789
1790 - def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
1791 max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
1805
1807 return self.requests.min
1809 self.requests.min = value
1810 numthreads = property(_get_numthreads, _set_numthreads)
1811
1812
1814 """A base class to interface HTTPServer with WSGI."""
1815
1817 self.req = req
1818 self.started_response = False
1819 self.env = self.get_environ()
1820 self.remaining_bytes_out = None
1821
1823 """Return a new environ dict targeting the given wsgi.version"""
1824 raise NotImplemented
1825
1827 """Process the current request."""
1828 response = self.req.server.wsgi_app(self.env, self.start_response)
1829 try:
1830 for chunk in response:
1831
1832
1833
1834
1835
1836
1837 if chunk:
1838 if isinstance(chunk, unicodestr):
1839 chunk = chunk.encode('ISO-8859-1')
1840 self.write(chunk)
1841 finally:
1842 if hasattr(response, "close"):
1843 response.close()
1844
1846 """WSGI callable to begin the HTTP response."""
1847
1848
1849 if self.started_response and not exc_info:
1850 raise AssertionError("WSGI start_response called a second "
1851 "time with no exc_info.")
1852 self.started_response = True
1853
1854
1855
1856
1857 if self.req.sent_headers:
1858 try:
1859 raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
1860 finally:
1861 exc_info = None
1862
1863
1864
1865
1866
1867 if not isinstance(status, str):
1868 raise TypeError("WSGI response status is not of type str.")
1869 self.req.status = status.encode('ISO-8859-1')
1870
1871 for k, v in headers:
1872 if not isinstance(k, str):
1873 raise TypeError("WSGI response header key %r is not of type str." % k)
1874 if not isinstance(v, str):
1875 raise TypeError("WSGI response header value %r is not of type str." % v)
1876 if k.lower() == 'content-length':
1877 self.remaining_bytes_out = int(v)
1878 self.req.outheaders.append((k.encode('ISO-8859-1'), v.encode('ISO-8859-1')))
1879
1880 return self.write
1881
1882 - def write(self, chunk):
1883 """WSGI callable to write unbuffered data to the client.
1884
1885 This method is also used internally by start_response (to write
1886 data from the iterable returned by the WSGI application).
1887 """
1888 if not self.started_response:
1889 raise AssertionError("WSGI write called before start_response.")
1890
1891 chunklen = len(chunk)
1892 rbo = self.remaining_bytes_out
1893 if rbo is not None and chunklen > rbo:
1894 if not self.req.sent_headers:
1895
1896 self.req.simple_response("500 Internal Server Error",
1897 "The requested resource returned more bytes than the "
1898 "declared Content-Length.")
1899 else:
1900
1901
1902 chunk = chunk[:rbo]
1903
1904 if not self.req.sent_headers:
1905 self.req.sent_headers = True
1906 self.req.send_headers()
1907
1908 self.req.write(chunk)
1909
1910 if rbo is not None:
1911 rbo -= chunklen
1912 if rbo < 0:
1913 raise ValueError(
1914 "Response body exceeds the declared Content-Length.")
1915
1916
1918 """A Gateway class to interface HTTPServer with WSGI 1.0.x."""
1919
1921 """Return a new environ dict targeting the given wsgi.version"""
1922 req = self.req
1923 env = {
1924
1925
1926
1927 'ACTUAL_SERVER_PROTOCOL': req.server.protocol,
1928 'PATH_INFO': req.path.decode('ISO-8859-1'),
1929 'QUERY_STRING': req.qs.decode('ISO-8859-1'),
1930 'REMOTE_ADDR': req.conn.remote_addr or '',
1931 'REMOTE_PORT': str(req.conn.remote_port or ''),
1932 'REQUEST_METHOD': req.method.decode('ISO-8859-1'),
1933 'REQUEST_URI': req.uri,
1934 'SCRIPT_NAME': '',
1935 'SERVER_NAME': req.server.server_name,
1936
1937 'SERVER_PROTOCOL': req.request_protocol.decode('ISO-8859-1'),
1938 'SERVER_SOFTWARE': req.server.software,
1939 'wsgi.errors': sys.stderr,
1940 'wsgi.input': req.rfile,
1941 'wsgi.multiprocess': False,
1942 'wsgi.multithread': True,
1943 'wsgi.run_once': False,
1944 'wsgi.url_scheme': req.scheme.decode('ISO-8859-1'),
1945 'wsgi.version': (1, 0),
1946 }
1947
1948 if isinstance(req.server.bind_addr, basestring):
1949
1950
1951 env["SERVER_PORT"] = ""
1952 else:
1953 env["SERVER_PORT"] = str(req.server.bind_addr[1])
1954
1955
1956 for k, v in req.inheaders.items():
1957 k = k.decode('ISO-8859-1').upper().replace("-", "_")
1958 env["HTTP_" + k] = v.decode('ISO-8859-1')
1959
1960
1961 ct = env.pop("HTTP_CONTENT_TYPE", None)
1962 if ct is not None:
1963 env["CONTENT_TYPE"] = ct
1964 cl = env.pop("HTTP_CONTENT_LENGTH", None)
1965 if cl is not None:
1966 env["CONTENT_LENGTH"] = cl
1967
1968 if req.conn.ssl_env:
1969 env.update(req.conn.ssl_env)
1970
1971 return env
1972
1973
1975 """A Gateway class to interface HTTPServer with WSGI u.0.
1976
1977 WSGI u.0 is an experimental protocol, which uses unicode for keys and values
1978 in both Python 2 and Python 3.
1979 """
1980
1982 """Return a new environ dict targeting the given wsgi.version"""
1983 req = self.req
1984 env_10 = WSGIGateway_10.get_environ(self)
1985 env = env_10.copy()
1986 env['wsgi.version'] = ('u', 0)
1987
1988
1989 env.setdefault('wsgi.url_encoding', 'utf-8')
1990 try:
1991
1992 env["PATH_INFO"] = req.path.decode(env['wsgi.url_encoding'])
1993 env["QUERY_STRING"] = req.qs.decode(env['wsgi.url_encoding'])
1994 except UnicodeDecodeError:
1995
1996 env['wsgi.url_encoding'] = 'ISO-8859-1'
1997 env["PATH_INFO"] = env_10["PATH_INFO"]
1998 env["QUERY_STRING"] = env_10["QUERY_STRING"]
1999
2000 return env
2001
2002 wsgi_gateways = {
2003 (1, 0): WSGIGateway_10,
2004 ('u', 0): WSGIGateway_u0,
2005 }
2006
2008 """A WSGI dispatcher for dispatch based on the PATH_INFO.
2009
2010 apps: a dict or list of (path_prefix, app) pairs.
2011 """
2012
2014 try:
2015 apps = list(apps.items())
2016 except AttributeError:
2017 pass
2018
2019
2020 apps.sort()
2021 apps.reverse()
2022
2023
2024
2025 self.apps = [(p.rstrip("/"), a) for p, a in apps]
2026
2027 - def __call__(self, environ, start_response):
2028 path = environ["PATH_INFO"] or "/"
2029 for p, app in self.apps:
2030
2031 if path.startswith(p + "/") or path == p:
2032 environ = environ.copy()
2033 environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
2034 environ["PATH_INFO"] = path[len(p):]
2035 return app(environ, start_response)
2036
2037 start_response('404 Not Found', [('Content-Type', 'text/plain'),
2038 ('Content-Length', '0')])
2039 return ['']
2040