Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(12)

Side by Side Diff: third_party/cherrypy/wsgiserver/wsgiserver3.py

Issue 9368042: Add CherryPy to third_party. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build/
Patch Set: '' Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/cherrypy/wsgiserver/wsgiserver2.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
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 # In Python 3, the native string type is unicode
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 # In Python 2, the native string type is bytes. Assume it's already
120 # in the given encoding, which for ISO-8859-1 is almost always what
121 # was intended.
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
139 def plat_specific_errors(*errnames):
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 # de-dupe the list
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
183 def read_headers(rfile, hdict=None):
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 # No more data--illegal end of headers
202 raise ValueError("Illegal end of headers.")
203
204 if line == CRLF:
205 # Normal end of headers
206 break
207 if not line.endswith(CRLF):
208 raise ValueError("HTTP requires CRLF terminators")
209
210 if line[0] in (SPACE, TAB):
211 # It's a continuation line.
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 # TODO: what about TE and WWW-Authenticate?
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
232 class MaxSizeExceeded(Exception):
233 pass
234
235 class SizeCheckWrapper(object):
236 """Wraps a file-like object, raising MaxSizeExceeded if too large."""
237
238 def __init__(self, rfile, maxlen):
239 self.rfile = rfile
240 self.maxlen = maxlen
241 self.bytes_read = 0
242
243 def _check_length(self):
244 if self.maxlen and self.bytes_read > self.maxlen:
245 raise MaxSizeExceeded()
246
247 def read(self, size=None):
248 data = self.rfile.read(size)
249 self.bytes_read += len(data)
250 self._check_length()
251 return data
252
253 def readline(self, size=None):
254 if size is not None:
255 data = self.rfile.readline(size)
256 self.bytes_read += len(data)
257 self._check_length()
258 return data
259
260 # User didn't specify a size ...
261 # We read the line in chunks to make sure it's not a 100MB line !
262 res = []
263 while True:
264 data = self.rfile.readline(256)
265 self.bytes_read += len(data)
266 self._check_length()
267 res.append(data)
268 # See http://www.cherrypy.org/ticket/421
269 if len(data) < 256 or data[-1:] == "\n":
270 return EMPTY.join(res)
271
272 def readlines(self, sizehint=0):
273 # Shamelessly stolen from StringIO
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
285 def close(self):
286 self.rfile.close()
287
288 def __iter__(self):
289 return self
290
291 def __next__(self):
292 data = next(self.rfile)
293 self.bytes_read += len(data)
294 self._check_length()
295 return data
296
297 def next(self):
298 data = self.rfile.next()
299 self.bytes_read += len(data)
300 self._check_length()
301 return data
302
303
304 class KnownLengthRFile(object):
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
323 def readline(self, size=None):
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
335 def readlines(self, sizehint=0):
336 # Shamelessly stolen from StringIO
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
348 def close(self):
349 self.rfile.close()
350
351 def __iter__(self):
352 return self
353
354 def __next__(self):
355 data = next(self.rfile)
356 self.remaining -= len(data)
357 return data
358
359
360 class ChunkedRFile(object):
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):
369 self.rfile = rfile
370 self.maxlen = maxlen
371 self.bytes_read = 0
372 self.buffer = EMPTY
373 self.bufsize = bufsize
374 self.closed = False
375
376 def _fetch(self):
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 ## if line: chunk_extension = line[0]
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 # EOF
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
432 def readline(self, size=None):
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 # EOF
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
461 def readlines(self, sizehint=0):
462 # Shamelessly stolen from StringIO
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
474 def read_trailer_lines(self):
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 # No more data--illegal end of headers
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 # Normal end of headers
491 break
492 if not line.endswith(CRLF):
493 raise ValueError("HTTP requires CRLF terminators")
494
495 yield line
496
497 def close(self):
498 self.rfile.close()
499
500 def __iter__(self):
501 # Shamelessly stolen from StringIO
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
512 class HTTPRequest(object):
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
545 def __init__(self, server, conn):
546 self.server= server
547 self.conn = conn
548
549 self.ready = False
550 self.started_request = False
551 self.scheme = ntob("http")
552 if self.server.ssl_adapter is not None:
553 self.scheme = ntob("https")
554 # Use the lowest-common protocol in case read_request_line errors.
555 self.response_protocol = 'HTTP/1.0'
556 self.inheaders = {}
557
558 self.status = ""
559 self.outheaders = []
560 self.sent_headers = False
561 self.close_connection = self.__class__.close_connection
562 self.chunked_read = False
563 self.chunked_write = self.__class__.chunked_write
564
565 def parse_request(self):
566 """Parse the next HTTP request start-line and message-headers."""
567 self.rfile = SizeCheckWrapper(self.conn.rfile,
568 self.server.max_request_header_size)
569 try:
570 success = self.read_request_line()
571 except MaxSizeExceeded:
572 self.simple_response("414 Request-URI Too Long",
573 "The Request-URI sent with the request exceeds the maximum "
574 "allowed bytes.")
575 return
576 else:
577 if not success:
578 return
579
580 try:
581 success = self.read_request_headers()
582 except MaxSizeExceeded:
583 self.simple_response("413 Request Entity Too Large",
584 "The headers sent with the request exceed the maximum "
585 "allowed bytes.")
586 return
587 else:
588 if not success:
589 return
590
591 self.ready = True
592
593 def read_request_line(self):
594 # HTTP/1.1 connections are persistent by default. If a client
595 # requests a page, then idles (leaves the connection open),
596 # then rfile.readline() will raise socket.error("timed out").
597 # Note that it does this based on the value given to settimeout(),
598 # and doesn't need the client to request or acknowledge the close
599 # (although your TCP stack might suffer for it: cf Apache's history
600 # with FIN_WAIT_2).
601 request_line = self.rfile.readline()
602
603 # Set started_request to True so communicate() knows to send 408
604 # from here on out.
605 self.started_request = True
606 if not request_line:
607 return False
608
609 if request_line == CRLF:
610 # RFC 2616 sec 4.1: "...if the server is reading the protocol
611 # stream at the beginning of a message and receives a CRLF
612 # first, it should ignore the CRLF."
613 # But only ignore one leading line! else we enable a DoS.
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 terminat ors")
620 return False
621
622 try:
623 method, uri, req_protocol = request_line.strip().split(SPACE, 2)
624 # The [x:y] slicing is necessary for byte strings to avoid getting o rd's
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 # uri may be an abs_path (including "http://host.domain.tld");
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 # Unquote the path+params (e.g. "/this%20path" -> "/this path").
648 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
649 #
650 # But note that "...a URI must be separated into its components
651 # before the escaped characters within those components can be
652 # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
653 # Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path".
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 # Note that, like wsgiref and most other HTTP servers,
664 # we "% HEX HEX"-unquote the path but not the query string.
665 self.qs = qs
666
667 # Compare request and server HTTP protocol versions, in case our
668 # server does not support the requested protocol. Limit our output
669 # to min(req, server). We want the following output:
670 # request server actual written supported response
671 # protocol protocol response protocol feature set
672 # a 1.0 1.0 1.0 1.0
673 # b 1.0 1.1 1.1 1.0
674 # c 1.1 1.0 1.0 1.0
675 # d 1.1 1.1 1.1 1.1
676 # Notice that, in (b), the response will be "HTTP/1.1" even though
677 # the client only understands 1.0. RFC 2616 10.5.6 says we should
678 # only return 505 if the _major_ version is different.
679 # The [x:y] slicing is necessary for byte strings to avoid getting ord's
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
690 def read_request_headers(self):
691 """Read self.rfile into self.inheaders. Return success."""
692
693 # then all the http headers
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 # Persistent connection support
709 if self.response_protocol == "HTTP/1.1":
710 # Both server and client are HTTP/1.1
711 if self.inheaders.get(b"Connection", b"") == b"close":
712 self.close_connection = True
713 else:
714 # Either the server or client (or both) are HTTP/1.0
715 if self.inheaders.get(b"Connection", b"") != b"Keep-Alive":
716 self.close_connection = True
717
718 # Transfer-Encoding support
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 # Note that, even if we see "chunked", we must reject
733 # if there is an extension we don't recognize.
734 self.simple_response("501 Unimplemented")
735 self.close_connection = True
736 return False
737
738 # From PEP 333:
739 # "Servers and gateways that implement HTTP 1.1 must provide
740 # transparent support for HTTP 1.1's "expect/continue" mechanism.
741 # This may be done in any of several ways:
742 # 1. Respond to requests containing an Expect: 100-continue request
743 # with an immediate "100 Continue" response, and proceed normally.
744 # 2. Proceed with the request normally, but provide the application
745 # with a wsgi.input stream that will send the "100 Continue"
746 # response if/when the application first attempts to read from
747 # the input stream. The read request must then remain blocked
748 # until the client responds.
749 # 3. Wait until the client decides that the server does not support
750 # expect/continue, and sends the request body on its own.
751 # (This is suboptimal, and is not recommended.)
752 #
753 # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
754 # but it seems like it would be a big slowdown for such a rare case.
755 if self.inheaders.get(b"Expect", b"") == b"100-continue":
756 # Don't use simple_response here, because it emits headers
757 # we don't want. See http://www.cherrypy.org/ticket/951
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
767 def parse_request_uri(self, uri):
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 # An absoluteURI.
793 # If there's a scheme (and it must be http or https), then:
794 # http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ] ]
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 # An abs_path.
800 return None, None, uri
801 else:
802 # An authority.
803 return None, uri, None
804
805 def unquote_bytes(self, path):
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
817 def respond(self):
818 """Call the gateway and write its iterable output."""
819 mrbs = self.server.max_request_body_size
820 if self.chunked_read:
821 self.rfile = ChunkedRFile(self.conn.rfile, mrbs)
822 else:
823 cl = int(self.inheaders.get(b"Content-Length", 0))
824 if mrbs and mrbs < cl:
825 if not self.sent_headers:
826 self.simple_response("413 Request Entity Too Large",
827 "The entity sent with the request exceeds the maximum "
828 "allowed bytes.")
829 return
830 self.rfile = KnownLengthRFile(self.conn.rfile, cl)
831
832 self.server.gateway(self).respond()
833
834 if (self.ready and not self.sent_headers):
835 self.sent_headers = True
836 self.send_headers()
837 if self.chunked_write:
838 self.conn.wfile.write(b"0\r\n\r\n")
839
840 def simple_response(self, status, msg=""):
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 # Request Entity Too Large / Request-URI Too Long
850 self.close_connection = True
851 if self.response_protocol == 'HTTP/1.1':
852 # This will not be true for 414, since read_request_line
853 # usually raises 414 before reading the whole line, and we
854 # therefore cannot know the proper response_protocol.
855 buf.append(b"Connection: close\r\n")
856 else:
857 # HTTP/1.0 had no 413/414 status nor Connection header.
858 # Emit 400 instead and trust the message body is enough.
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
874 def write(self, chunk):
875 """Write unbuffered data to the client."""
876 if self.chunked_write and chunk:
877 buf = [bytes(hex(len(chunk)), 'ASCII')[2:], CRLF, chunk, CRLF]
878 self.conn.wfile.write(EMPTY.join(buf))
879 else:
880 self.conn.wfile.write(chunk)
881
882 def send_headers(self):
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 # Request Entity Too Large. Close conn to avoid garbage.
892 self.close_connection = True
893 elif b"content-length" not in hkeys:
894 # "All 1xx (informational), 204 (no content),
895 # and 304 (not modified) responses MUST NOT
896 # include a message-body." So no point chunking.
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 # Use the chunked transfer-coding
903 self.chunked_write = True
904 self.outheaders.append((b"Transfer-Encoding", b"chunked"))
905 else:
906 # Closing the conn is the only way to determine len.
907 self.close_connection = True
908
909 if b"connection" not in hkeys:
910 if self.response_protocol == 'HTTP/1.1':
911 # Both server and client are HTTP/1.1 or better
912 if self.close_connection:
913 self.outheaders.append((b"Connection", b"close"))
914 else:
915 # Server and/or client are HTTP/1.0
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 # Read any remaining request body data on the socket.
921 # "If an origin server receives a request that does not include an
922 # Expect request-header field with the "100-continue" expectation,
923 # the request includes a request body, and the server responds
924 # with a final status code before reading the entire request body
925 # from the transport connection, then the server SHOULD NOT close
926 # the transport connection until it has read the entire request,
927 # or until the client closes the connection. Otherwise, the client
928 # might not reliably receive the response message. However, this
929 # requirement is not be construed as preventing a server from
930 # defending itself against denial-of-service attacks, or from
931 # badly broken client implementations."
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
951 class NoSSLError(Exception):
952 """Exception raised when a client speaks HTTP to an HTTPS socket."""
953 pass
954
955
956 class FatalSSLAlert(Exception):
957 """Exception raised when the SSL implementation signals a fatal alert."""
958 pass
959
960
961 class CP_BufferedWriter(io.BufferedWriter):
962 """Faux file object attached to a socket object."""
963
964 def write(self, b):
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
974 def _flush_unlocked(self):
975 self._checkClosed("flush of closed file")
976 while self._write_buf:
977 try:
978 # ssl sockets only except 'bytes', not bytearrays
979 # so perhaps we should conditionally wrap this for perf?
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
986 def CP_makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
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
992 class HTTPConnection(object):
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
1007 def __init__(self, server, sock, makefile=CP_makefile):
1008 self.server = server
1009 self.socket = sock
1010 self.rfile = makefile(sock, "rb", self.rbufsize)
1011 self.wfile = makefile(sock, "wb", self.wbufsize)
1012 self.requests_seen = 0
1013
1014 def communicate(self):
1015 """Read each request and respond appropriately."""
1016 request_seen = False
1017 try:
1018 while True:
1019 # (re)set req to None so that if something goes wrong in
1020 # the RequestHandlerClass constructor, the error doesn't
1021 # get written to the previous request.
1022 req = None
1023 req = self.RequestHandlerClass(self.server, self)
1024
1025 # This order of operations should guarantee correct pipelining.
1026 req.parse_request()
1027 if self.server.stats['Enabled']:
1028 self.requests_seen += 1
1029 if not req.ready:
1030 # Something went wrong in the parsing (and the server has
1031 # probably already made a simple_response). Return and
1032 # let the conn close.
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 # sadly SSL sockets return a different (longer) time out string
1043 if errnum == 'timed out' or errnum == 'The read operation timed out' :
1044 # Don't error if we're between requests; only error
1045 # if 1) no request has been started at all, or 2) we're
1046 # in the middle of a request.
1047 # See http://www.cherrypy.org/ticket/853
1048 if (not request_seen) or (req and req.started_request):
1049 # Don't bother writing the 408 if the response
1050 # has already started being written.
1051 if req and not req.sent_headers:
1052 try:
1053 req.simple_response("408 Request Timeout")
1054 except FatalSSLAlert:
1055 # Close the connection.
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 # Close the connection.
1065 return
1066 return
1067 except (KeyboardInterrupt, SystemExit):
1068 raise
1069 except FatalSSLAlert:
1070 # Close the connection.
1071 return
1072 except NoSSLError:
1073 if req and not req.sent_headers:
1074 # Unwrap our wfile
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 # Close the connection.
1088 return
1089
1090 linger = False
1091
1092 def close(self):
1093 """Close the socket underlying this connection."""
1094 self.rfile.close()
1095
1096 if not self.linger:
1097 # Python's socket module does NOT call close on the kernel socket
1098 # when you call socket.close(). We do so manually here because we
1099 # want this server to send a FIN TCP segment immediately. Note this
1100 # must be called *before* calling socket.close(), because the latter
1101 # drops its reference to the kernel socket.
1102 # Python 3 *probably* fixed this with socket._real_close; hard to te ll.
1103 ## self.socket._sock.close()
1104 self.socket.close()
1105 else:
1106 # On the other hand, sometimes we want to hang around for a bit
1107 # to make sure the client has a chance to read our entire
1108 # response. Skipping the close() calls here delays the FIN
1109 # packet until the socket object is garbage-collected later.
1110 # Someday, perhaps, we'll do the full lingering_close that
1111 # Apache does, but not today.
1112 pass
1113
1114
1115 class TrueyZero(object):
1116 """An object which equals and does math like the integer '0' but evals True. """
1117 def __add__(self, other):
1118 return other
1119 def __radd__(self, other):
1120 return other
1121 trueyzero = TrueyZero()
1122
1123
1124 _SHUTDOWNREQUEST = None
1125
1126 class WorkerThread(threading.Thread):
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
1147 def __init__(self, server):
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) a nd 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
1166 def run(self):
1167 self.server.stats['Worker Threads'][self.getName()] = self.stats
1168 try:
1169 self.ready = True
1170 while True:
1171 conn = self.server.requests.get()
1172 if conn is _SHUTDOWNREQUEST:
1173 return
1174
1175 self.conn = conn
1176 if self.server.stats['Enabled']:
1177 self.start_time = time.time()
1178 try:
1179 conn.communicate()
1180 finally:
1181 conn.close()
1182 if self.server.stats['Enabled']:
1183 self.requests_seen += self.conn.requests_seen
1184 self.bytes_read += self.conn.rfile.bytes_read
1185 self.bytes_written += self.conn.wfile.bytes_written
1186 self.work_time += time.time() - self.start_time
1187 self.start_time = None
1188 self.conn = None
1189 except (KeyboardInterrupt, SystemExit):
1190 exc = sys.exc_info()[1]
1191 self.server.interrupt = exc
1192
1193
1194 class ThreadPool(object):
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
1209 def start(self):
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
1220 def _get_idle(self):
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):
1226 self._queue.put(obj)
1227 if obj is _SHUTDOWNREQUEST:
1228 return
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
1240 def shrink(self, amount):
1241 """Kill off worker threads (not below self.min)."""
1242 # Grow/shrink the pool if necessary.
1243 # Remove any dead threads from our list
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 # Put a number of shutdown requests on the queue equal
1252 # to 'amount'. Once each of those is processed by a worker,
1253 # that worker will terminate and be culled from our list
1254 # in self.put.
1255 self._queue.put(_SHUTDOWNREQUEST)
1256
1257 def stop(self, timeout=5):
1258 # Must shut down threads here so the code that calls
1259 # this method can know when all threads are stopped.
1260 for worker in self._threads:
1261 self._queue.put(_SHUTDOWNREQUEST)
1262
1263 # Don't join currentThread (when stop is called inside a request).
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 # We exhausted the timeout.
1279 # Forcibly shut down the socket.
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 # pyOpenSSL sockets don't take an arg
1286 c.socket.shutdown()
1287 worker.join()
1288 except (AssertionError,
1289 # Ignore repeated Ctrl-C.
1290 # See http://www.cherrypy.org/ticket/691.
1291 KeyboardInterrupt):
1292 pass
1293
1294 def _get_qsize(self):
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:
1306 def prevent_socket_inheritance(sock):
1307 """Dummy function, since neither fcntl nor ctypes are available."""
1308 pass
1309 else:
1310 def prevent_socket_inheritance(sock):
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:
1315 def prevent_socket_inheritance(sock):
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
1322 class SSLAdapter(object):
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):
1332 self.certificate = certificate
1333 self.private_key = private_key
1334 self.certificate_chain = certificate_chain
1335
1336 def wrap(self, sock):
1337 raise NotImplemented
1338
1339 def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
1340 raise NotImplemented
1341
1342
1343 class HTTPServer(object):
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):
1406 self.bind_addr = bind_addr
1407 self.gateway = gateway
1408
1409 self.requests = ThreadPool(self, min=minthreads or 1, max=maxthreads)
1410
1411 if not server_name:
1412 server_name = socket.gethostname()
1413 self.server_name = server_name
1414 self.clear_stats()
1415
1416 def clear_stats(self):
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 R ead'](w) for w
1432 in s['Worker Threads'].values()], 0),
1433 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum([w['Byte s 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 Tim e'](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
1447 def runtime(self):
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
1453 def __str__(self):
1454 return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
1455 self.bind_addr)
1456
1457 def _get_bind_addr(self):
1458 return self._bind_addr
1459 def _set_bind_addr(self, value):
1460 if isinstance(value, tuple) and value[0] in ('', None):
1461 # Despite the socket module docs, using '' does not
1462 # allow AI_PASSIVE to work. Passing None instead
1463 # returns '0.0.0.0' like we want. In other words:
1464 # host AI_PASSIVE result
1465 # '' Y 192.168.x.y
1466 # '' N 192.168.x.y
1467 # None Y 0.0.0.0
1468 # None N 127.0.0.1
1469 # But since you can get the same effect with an explicit
1470 # '0.0.0.0', we deny both the empty string and None as values.
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
1487 def start(self):
1488 """Run the server forever."""
1489 # We don't have to trap KeyboardInterrupt or SystemExit here,
1490 # because cherrpy.server already does so, calling self.stop() for us.
1491 # If you're using this server with another framework, you should
1492 # trap those exceptions in whatever code block calls start().
1493 self._interrupt = None
1494
1495 if self.software is None:
1496 self.software = "%s Server" % self.version
1497
1498 # Select the appropriate socket
1499 if isinstance(self.bind_addr, basestring):
1500 # AF_UNIX socket
1501
1502 # So we can reuse the socket...
1503 try: os.unlink(self.bind_addr)
1504 except: pass
1505
1506 # So everyone can access the socket...
1507 try: os.chmod(self.bind_addr, 511) # 0777
1508 except: pass
1509
1510 info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1511 else:
1512 # AF_INET or AF_INET6 socket
1513 # Get the correct address family for our host (allows IPv6 addresses )
1514 host, port = self.bind_addr
1515 try:
1516 info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1517 socket.SOCK_STREAM, 0, socket.AI_PASSI VE)
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 # Timeout so KeyboardInterrupt can be caught on Win32
1542 self.socket.settimeout(1)
1543 self.socket.listen(self.request_queue_size)
1544
1545 # Create worker threads
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 # Wait for self.stop() to complete. See _set_interrupt.
1561 time.sleep(0.1)
1562 if self.interrupt:
1563 raise self.interrupt
1564
1565 def error_log(self, msg="", level=20, traceback=False):
1566 # Override this in subclasses as desired
1567 sys.stderr.write(msg + '\n')
1568 sys.stderr.flush()
1569 if traceback:
1570 tblines = format_exc()
1571 sys.stderr.write(tblines)
1572 sys.stderr.flush()
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 # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
1586 # activate dual-stack. See http://www.cherrypy.org/ticket/871.
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 # Apparently, the socket option is not available in
1593 # this machine's TCP stack
1594 pass
1595
1596 self.socket.bind(self.bind_addr)
1597
1598 def tick(self):
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 # if ssl cert and key are set, we try to be a secure HTTP server
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 # Re-apply our timeout since we may have a new socket object
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 # optional values
1644 # Until we do DNS lookups, omit REMOTE_HOST
1645 if addr is None: # sometimes this can happen
1646 # figure out if AF_INET or AF_INET6.
1647 if len(s.getsockname()) == 2:
1648 # AF_INET
1649 addr = ('0.0.0.0', 0)
1650 else:
1651 # AF_INET6
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 # The only reason for the timeout in start() is so we can
1661 # notice keyboard interrupts on Win32, which don't interrupt
1662 # accept() by default
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 # I *think* this is right. EINTR should occur when a signal
1670 # is received during the accept() call; all docs say retry
1671 # the call, and I *think* I'm reading it right that Python
1672 # will then go ahead and poll for and handle the signal
1673 # elsewhere. See http://www.cherrypy.org/ticket/707.
1674 return
1675 if x.args[0] in socket_errors_nonblocking:
1676 # Just try again. See http://www.cherrypy.org/ticket/479.
1677 return
1678 if x.args[0] in socket_errors_to_ignore:
1679 # Our socket was closed.
1680 # See http://www.cherrypy.org/ticket/686.
1681 return
1682 raise
1683
1684 def _get_interrupt(self):
1685 return self._interrupt
1686 def _set_interrupt(self, interrupt):
1687 self._interrupt = True
1688 self.stop()
1689 self._interrupt = interrupt
1690 interrupt = property(_get_interrupt, _set_interrupt,
1691 doc="Set this to an Exception instance to "
1692 "interrupt the server.")
1693
1694 def stop(self):
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 # Touch our own socket to make accept() return immediately.
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 # Changed to use error code and not message
1711 # See http://www.cherrypy.org/ticket/860.
1712 raise
1713 else:
1714 # Note that we're explicitly NOT using AI_PASSIVE,
1715 # here, because we want an actual IP to touch.
1716 # localhost won't work if we've bound to a public IP,
1717 # but it will if we bound to '0.0.0.0' (INADDR_ANY).
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 # See http://groups.google.com/group/cherrypy-users/
1725 # browse_frm/thread/bbfe5eb39c904fe0
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
1739 class Gateway(object):
1740 """A base class to interface HTTPServer with other systems, such as WSGI."""
1741
1742 def __init__(self, req):
1743 self.req = req
1744
1745 def respond(self):
1746 """Process the current request. Must be overridden in a subclass."""
1747 raise NotImplemented
1748
1749
1750 # These may either be wsgiserver.SSLAdapter subclasses or the string names
1751 # of such classes (in which case they will be lazily loaded).
1752 ssl_adapters = {
1753 'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
1754 }
1755
1756 def get_ssl_adapter_class(name='builtin'):
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 # The last [''] is important.
1770 mod = __import__(mod_path, globals(), locals(), [''])
1771
1772 # Let an AttributeError propagate outward.
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 # -------------------------------- WSGI Stuff -------------------------------- #
1782
1783
1784 class CherryPyWSGIServer(HTTPServer):
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):
1792 self.requests = ThreadPool(self, min=numthreads or 1, max=max)
1793 self.wsgi_app = wsgi_app
1794 self.gateway = wsgi_gateways[self.wsgi_version]
1795
1796 self.bind_addr = bind_addr
1797 if not server_name:
1798 server_name = socket.gethostname()
1799 self.server_name = server_name
1800 self.request_queue_size = request_queue_size
1801
1802 self.timeout = timeout
1803 self.shutdown_timeout = shutdown_timeout
1804 self.clear_stats()
1805
1806 def _get_numthreads(self):
1807 return self.requests.min
1808 def _set_numthreads(self, value):
1809 self.requests.min = value
1810 numthreads = property(_get_numthreads, _set_numthreads)
1811
1812
1813 class WSGIGateway(Gateway):
1814 """A base class to interface HTTPServer with WSGI."""
1815
1816 def __init__(self, req):
1817 self.req = req
1818 self.started_response = False
1819 self.env = self.get_environ()
1820 self.remaining_bytes_out = None
1821
1822 def get_environ(self):
1823 """Return a new environ dict targeting the given wsgi.version"""
1824 raise NotImplemented
1825
1826 def respond(self):
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 # "The start_response callable must not actually transmit
1832 # the response headers. Instead, it must store them for the
1833 # server or gateway to transmit only after the first
1834 # iteration of the application return value that yields
1835 # a NON-EMPTY string, or upon the application's first
1836 # invocation of the write() callable." (PEP 333)
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
1845 def start_response(self, status, headers, exc_info = None):
1846 """WSGI callable to begin the HTTP response."""
1847 # "The application may call start_response more than once,
1848 # if and only if the exc_info argument is provided."
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 # "if exc_info is provided, and the HTTP headers have already been
1855 # sent, start_response must raise an error, and should raise the
1856 # exc_info tuple."
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 # According to PEP 3333, when using Python 3, the response status
1864 # and headers must be bytes masquerading as unicode; that is, they
1865 # must be of type "str" but are restricted to code points in the
1866 # "latin-1" set.
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 st r." % 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-88 59-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 # Whew. We can send a 500 to the client.
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 # Dang. We have probably already sent data. Truncate the chunk
1901 # to fit (so the client doesn't hang) and raise an error later.
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
1917 class WSGIGateway_10(WSGIGateway):
1918 """A Gateway class to interface HTTPServer with WSGI 1.0.x."""
1919
1920 def get_environ(self):
1921 """Return a new environ dict targeting the given wsgi.version"""
1922 req = self.req
1923 env = {
1924 # set a non-standard environ entry so the WSGI app can know what
1925 # the *real* server protocol is (and what features to support).
1926 # See http://www.faqs.org/rfcs/rfc2145.html.
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 # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
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 # AF_UNIX. This isn't really allowed by WSGI, which doesn't
1950 # address unix domain sockets. But it's better than nothing.
1951 env["SERVER_PORT"] = ""
1952 else:
1953 env["SERVER_PORT"] = str(req.server.bind_addr[1])
1954
1955 # Request headers
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 # CONTENT_TYPE/CONTENT_LENGTH
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
1974 class WSGIGateway_u0(WSGIGateway_10):
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
1981 def get_environ(self):
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 # Request-URI
1989 env.setdefault('wsgi.url_encoding', 'utf-8')
1990 try:
1991 # SCRIPT_NAME is the empty string, who cares what encoding it is?
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 # Fall back to latin 1 so apps can transcode if needed.
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
2007 class WSGIPathInfoDispatcher(object):
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
2013 def __init__(self, apps):
2014 try:
2015 apps = list(apps.items())
2016 except AttributeError:
2017 pass
2018
2019 # Sort the apps by len(path), descending
2020 apps.sort()
2021 apps.reverse()
2022
2023 # The path_prefix strings must start, but not end, with a slash.
2024 # Use "" instead of "/".
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 # The apps list should be sorted by length, descending.
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
OLDNEW
« no previous file with comments | « third_party/cherrypy/wsgiserver/wsgiserver2.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698