PageRenderTime 59ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/python/lib/Lib/site-packages/django/core/servers/basehttp.py

http://github.com/JetBrains/intellij-community
Python | 696 lines | 639 code | 19 blank | 38 comment | 18 complexity | 1b1c0419a5b575acaeba30c007050151 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MPL-2.0-no-copyleft-exception, MIT, EPL-1.0, AGPL-1.0
  1. """
  2. BaseHTTPServer that implements the Python WSGI protocol (PEP 333, rev 1.21).
  3. Adapted from wsgiref.simple_server: http://svn.eby-sarna.com/wsgiref/
  4. This is a simple server for use in testing or debugging Django apps. It hasn't
  5. been reviewed for security issues. Don't use it for production use.
  6. """
  7. from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
  8. import os
  9. import re
  10. import socket
  11. import sys
  12. import urllib
  13. import warnings
  14. from django.core.management.color import color_style
  15. from django.utils.http import http_date
  16. from django.utils._os import safe_join
  17. from django.contrib.staticfiles import handlers, views as static
  18. __version__ = "0.1"
  19. __all__ = ['WSGIServer','WSGIRequestHandler']
  20. server_version = "WSGIServer/" + __version__
  21. sys_version = "Python/" + sys.version.split()[0]
  22. software_version = server_version + ' ' + sys_version
  23. class WSGIServerException(Exception):
  24. pass
  25. class FileWrapper(object):
  26. """Wrapper to convert file-like objects to iterables"""
  27. def __init__(self, filelike, blksize=8192):
  28. self.filelike = filelike
  29. self.blksize = blksize
  30. if hasattr(filelike,'close'):
  31. self.close = filelike.close
  32. def __getitem__(self,key):
  33. data = self.filelike.read(self.blksize)
  34. if data:
  35. return data
  36. raise IndexError
  37. def __iter__(self):
  38. return self
  39. def next(self):
  40. data = self.filelike.read(self.blksize)
  41. if data:
  42. return data
  43. raise StopIteration
  44. # Regular expression that matches `special' characters in parameters, the
  45. # existence of which force quoting of the parameter value.
  46. tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
  47. def _formatparam(param, value=None, quote=1):
  48. """Convenience function to format and return a key=value pair.
  49. This will quote the value if needed or if quote is true.
  50. """
  51. if value is not None and len(value) > 0:
  52. if quote or tspecials.search(value):
  53. value = value.replace('\\', '\\\\').replace('"', r'\"')
  54. return '%s="%s"' % (param, value)
  55. else:
  56. return '%s=%s' % (param, value)
  57. else:
  58. return param
  59. class Headers(object):
  60. """Manage a collection of HTTP response headers"""
  61. def __init__(self,headers):
  62. if not isinstance(headers, list):
  63. raise TypeError("Headers must be a list of name/value tuples")
  64. self._headers = headers
  65. def __len__(self):
  66. """Return the total number of headers, including duplicates."""
  67. return len(self._headers)
  68. def __setitem__(self, name, val):
  69. """Set the value of a header."""
  70. del self[name]
  71. self._headers.append((name, val))
  72. def __delitem__(self,name):
  73. """Delete all occurrences of a header, if present.
  74. Does *not* raise an exception if the header is missing.
  75. """
  76. name = name.lower()
  77. self._headers[:] = [kv for kv in self._headers if kv[0].lower()<>name]
  78. def __getitem__(self,name):
  79. """Get the first header value for 'name'
  80. Return None if the header is missing instead of raising an exception.
  81. Note that if the header appeared multiple times, the first exactly which
  82. occurrance gets returned is undefined. Use getall() to get all
  83. the values matching a header field name.
  84. """
  85. return self.get(name)
  86. def has_key(self, name):
  87. """Return true if the message contains the header."""
  88. return self.get(name) is not None
  89. __contains__ = has_key
  90. def get_all(self, name):
  91. """Return a list of all the values for the named field.
  92. These will be sorted in the order they appeared in the original header
  93. list or were added to this instance, and may contain duplicates. Any
  94. fields deleted and re-inserted are always appended to the header list.
  95. If no fields exist with the given name, returns an empty list.
  96. """
  97. name = name.lower()
  98. return [kv[1] for kv in self._headers if kv[0].lower()==name]
  99. def get(self,name,default=None):
  100. """Get the first header value for 'name', or return 'default'"""
  101. name = name.lower()
  102. for k,v in self._headers:
  103. if k.lower()==name:
  104. return v
  105. return default
  106. def keys(self):
  107. """Return a list of all the header field names.
  108. These will be sorted in the order they appeared in the original header
  109. list, or were added to this instance, and may contain duplicates.
  110. Any fields deleted and re-inserted are always appended to the header
  111. list.
  112. """
  113. return [k for k, v in self._headers]
  114. def values(self):
  115. """Return a list of all header values.
  116. These will be sorted in the order they appeared in the original header
  117. list, or were added to this instance, and may contain duplicates.
  118. Any fields deleted and re-inserted are always appended to the header
  119. list.
  120. """
  121. return [v for k, v in self._headers]
  122. def items(self):
  123. """Get all the header fields and values.
  124. These will be sorted in the order they were in the original header
  125. list, or were added to this instance, and may contain duplicates.
  126. Any fields deleted and re-inserted are always appended to the header
  127. list.
  128. """
  129. return self._headers[:]
  130. def __repr__(self):
  131. return "Headers(%s)" % `self._headers`
  132. def __str__(self):
  133. """str() returns the formatted headers, complete with end line,
  134. suitable for direct HTTP transmission."""
  135. return '\r\n'.join(["%s: %s" % kv for kv in self._headers]+['',''])
  136. def setdefault(self,name,value):
  137. """Return first matching header value for 'name', or 'value'
  138. If there is no header named 'name', add a new header with name 'name'
  139. and value 'value'."""
  140. result = self.get(name)
  141. if result is None:
  142. self._headers.append((name,value))
  143. return value
  144. else:
  145. return result
  146. def add_header(self, _name, _value, **_params):
  147. """Extended header setting.
  148. _name is the header field to add. keyword arguments can be used to set
  149. additional parameters for the header field, with underscores converted
  150. to dashes. Normally the parameter will be added as key="value" unless
  151. value is None, in which case only the key will be added.
  152. Example:
  153. h.add_header('content-disposition', 'attachment', filename='bud.gif')
  154. Note that unlike the corresponding 'email.Message' method, this does
  155. *not* handle '(charset, language, value)' tuples: all values must be
  156. strings or None.
  157. """
  158. parts = []
  159. if _value is not None:
  160. parts.append(_value)
  161. for k, v in _params.items():
  162. if v is None:
  163. parts.append(k.replace('_', '-'))
  164. else:
  165. parts.append(_formatparam(k.replace('_', '-'), v))
  166. self._headers.append((_name, "; ".join(parts)))
  167. def guess_scheme(environ):
  168. """Return a guess for whether 'wsgi.url_scheme' should be 'http' or 'https'
  169. """
  170. if environ.get("HTTPS") in ('yes','on','1'):
  171. return 'https'
  172. else:
  173. return 'http'
  174. _hop_headers = {
  175. 'connection':1, 'keep-alive':1, 'proxy-authenticate':1,
  176. 'proxy-authorization':1, 'te':1, 'trailers':1, 'transfer-encoding':1,
  177. 'upgrade':1
  178. }
  179. def is_hop_by_hop(header_name):
  180. """Return true if 'header_name' is an HTTP/1.1 "Hop-by-Hop" header"""
  181. return header_name.lower() in _hop_headers
  182. class ServerHandler(object):
  183. """Manage the invocation of a WSGI application"""
  184. # Configuration parameters; can override per-subclass or per-instance
  185. wsgi_version = (1,0)
  186. wsgi_multithread = True
  187. wsgi_multiprocess = True
  188. wsgi_run_once = False
  189. origin_server = True # We are transmitting direct to client
  190. http_version = "1.0" # Version that should be used for response
  191. server_software = software_version
  192. # os_environ is used to supply configuration from the OS environment:
  193. # by default it's a copy of 'os.environ' as of import time, but you can
  194. # override this in e.g. your __init__ method.
  195. os_environ = dict(os.environ.items())
  196. # Collaborator classes
  197. wsgi_file_wrapper = FileWrapper # set to None to disable
  198. headers_class = Headers # must be a Headers-like class
  199. # Error handling (also per-subclass or per-instance)
  200. traceback_limit = None # Print entire traceback to self.get_stderr()
  201. error_status = "500 INTERNAL SERVER ERROR"
  202. error_headers = [('Content-Type','text/plain')]
  203. # State variables (don't mess with these)
  204. status = result = None
  205. headers_sent = False
  206. headers = None
  207. bytes_sent = 0
  208. def __init__(self, stdin, stdout, stderr, environ, multithread=True,
  209. multiprocess=False):
  210. self.stdin = stdin
  211. self.stdout = stdout
  212. self.stderr = stderr
  213. self.base_env = environ
  214. self.wsgi_multithread = multithread
  215. self.wsgi_multiprocess = multiprocess
  216. def run(self, application):
  217. """Invoke the application"""
  218. # Note to self: don't move the close()! Asynchronous servers shouldn't
  219. # call close() from finish_response(), so if you close() anywhere but
  220. # the double-error branch here, you'll break asynchronous servers by
  221. # prematurely closing. Async servers must return from 'run()' without
  222. # closing if there might still be output to iterate over.
  223. try:
  224. self.setup_environ()
  225. self.result = application(self.environ, self.start_response)
  226. self.finish_response()
  227. except:
  228. try:
  229. self.handle_error()
  230. except:
  231. # If we get an error handling an error, just give up already!
  232. self.close()
  233. raise # ...and let the actual server figure it out.
  234. def setup_environ(self):
  235. """Set up the environment for one request"""
  236. env = self.environ = self.os_environ.copy()
  237. self.add_cgi_vars()
  238. env['wsgi.input'] = self.get_stdin()
  239. env['wsgi.errors'] = self.get_stderr()
  240. env['wsgi.version'] = self.wsgi_version
  241. env['wsgi.run_once'] = self.wsgi_run_once
  242. env['wsgi.url_scheme'] = self.get_scheme()
  243. env['wsgi.multithread'] = self.wsgi_multithread
  244. env['wsgi.multiprocess'] = self.wsgi_multiprocess
  245. if self.wsgi_file_wrapper is not None:
  246. env['wsgi.file_wrapper'] = self.wsgi_file_wrapper
  247. if self.origin_server and self.server_software:
  248. env.setdefault('SERVER_SOFTWARE',self.server_software)
  249. def finish_response(self):
  250. """
  251. Send any iterable data, then close self and the iterable
  252. Subclasses intended for use in asynchronous servers will want to
  253. redefine this method, such that it sets up callbacks in the event loop
  254. to iterate over the data, and to call 'self.close()' once the response
  255. is finished.
  256. """
  257. if not self.result_is_file() or not self.sendfile():
  258. for data in self.result:
  259. self.write(data)
  260. self.finish_content()
  261. self.close()
  262. def get_scheme(self):
  263. """Return the URL scheme being used"""
  264. return guess_scheme(self.environ)
  265. def set_content_length(self):
  266. """Compute Content-Length or switch to chunked encoding if possible"""
  267. try:
  268. blocks = len(self.result)
  269. except (TypeError, AttributeError, NotImplementedError):
  270. pass
  271. else:
  272. if blocks==1:
  273. self.headers['Content-Length'] = str(self.bytes_sent)
  274. return
  275. # XXX Try for chunked encoding if origin server and client is 1.1
  276. def cleanup_headers(self):
  277. """Make any necessary header changes or defaults
  278. Subclasses can extend this to add other defaults.
  279. """
  280. if 'Content-Length' not in self.headers:
  281. self.set_content_length()
  282. def start_response(self, status, headers,exc_info=None):
  283. """'start_response()' callable as specified by PEP 333"""
  284. if exc_info:
  285. try:
  286. if self.headers_sent:
  287. # Re-raise original exception if headers sent
  288. raise exc_info[0], exc_info[1], exc_info[2]
  289. finally:
  290. exc_info = None # avoid dangling circular ref
  291. elif self.headers is not None:
  292. raise AssertionError("Headers already set!")
  293. assert isinstance(status, str),"Status must be a string"
  294. assert len(status)>=4,"Status must be at least 4 characters"
  295. assert int(status[:3]),"Status message must begin w/3-digit code"
  296. assert status[3]==" ", "Status message must have a space after code"
  297. if __debug__:
  298. for name,val in headers:
  299. assert isinstance(name, str),"Header names must be strings"
  300. assert isinstance(val, str),"Header values must be strings"
  301. assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
  302. self.status = status
  303. self.headers = self.headers_class(headers)
  304. return self.write
  305. def send_preamble(self):
  306. """Transmit version/status/date/server, via self._write()"""
  307. if self.origin_server:
  308. if self.client_is_modern():
  309. self._write('HTTP/%s %s\r\n' % (self.http_version,self.status))
  310. if 'Date' not in self.headers:
  311. self._write(
  312. 'Date: %s\r\n' % http_date()
  313. )
  314. if self.server_software and 'Server' not in self.headers:
  315. self._write('Server: %s\r\n' % self.server_software)
  316. else:
  317. self._write('Status: %s\r\n' % self.status)
  318. def write(self, data):
  319. """'write()' callable as specified by PEP 333"""
  320. assert isinstance(data, str), "write() argument must be string"
  321. if not self.status:
  322. raise AssertionError("write() before start_response()")
  323. elif not self.headers_sent:
  324. # Before the first output, send the stored headers
  325. self.bytes_sent = len(data) # make sure we know content-length
  326. self.send_headers()
  327. else:
  328. self.bytes_sent += len(data)
  329. # XXX check Content-Length and truncate if too many bytes written?
  330. # If data is too large, socket will choke, so write chunks no larger
  331. # than 32MB at a time.
  332. length = len(data)
  333. if length > 33554432:
  334. offset = 0
  335. while offset < length:
  336. chunk_size = min(33554432, length)
  337. self._write(data[offset:offset+chunk_size])
  338. self._flush()
  339. offset += chunk_size
  340. else:
  341. self._write(data)
  342. self._flush()
  343. def sendfile(self):
  344. """Platform-specific file transmission
  345. Override this method in subclasses to support platform-specific
  346. file transmission. It is only called if the application's
  347. return iterable ('self.result') is an instance of
  348. 'self.wsgi_file_wrapper'.
  349. This method should return a true value if it was able to actually
  350. transmit the wrapped file-like object using a platform-specific
  351. approach. It should return a false value if normal iteration
  352. should be used instead. An exception can be raised to indicate
  353. that transmission was attempted, but failed.
  354. NOTE: this method should call 'self.send_headers()' if
  355. 'self.headers_sent' is false and it is going to attempt direct
  356. transmission of the file1.
  357. """
  358. return False # No platform-specific transmission by default
  359. def finish_content(self):
  360. """Ensure headers and content have both been sent"""
  361. if not self.headers_sent:
  362. self.headers['Content-Length'] = "0"
  363. self.send_headers()
  364. else:
  365. pass # XXX check if content-length was too short?
  366. def close(self):
  367. try:
  368. self.request_handler.log_request(self.status.split(' ',1)[0], self.bytes_sent)
  369. finally:
  370. try:
  371. if hasattr(self.result,'close'):
  372. self.result.close()
  373. finally:
  374. self.result = self.headers = self.status = self.environ = None
  375. self.bytes_sent = 0; self.headers_sent = False
  376. def send_headers(self):
  377. """Transmit headers to the client, via self._write()"""
  378. self.cleanup_headers()
  379. self.headers_sent = True
  380. if not self.origin_server or self.client_is_modern():
  381. self.send_preamble()
  382. self._write(str(self.headers))
  383. def result_is_file(self):
  384. """True if 'self.result' is an instance of 'self.wsgi_file_wrapper'"""
  385. wrapper = self.wsgi_file_wrapper
  386. return wrapper is not None and isinstance(self.result,wrapper)
  387. def client_is_modern(self):
  388. """True if client can accept status and headers"""
  389. return self.environ['SERVER_PROTOCOL'].upper() != 'HTTP/0.9'
  390. def log_exception(self,exc_info):
  391. """Log the 'exc_info' tuple in the server log
  392. Subclasses may override to retarget the output or change its format.
  393. """
  394. try:
  395. from traceback import print_exception
  396. stderr = self.get_stderr()
  397. print_exception(
  398. exc_info[0], exc_info[1], exc_info[2],
  399. self.traceback_limit, stderr
  400. )
  401. stderr.flush()
  402. finally:
  403. exc_info = None
  404. def handle_error(self):
  405. """Log current error, and send error output to client if possible"""
  406. self.log_exception(sys.exc_info())
  407. if not self.headers_sent:
  408. self.result = self.error_output(self.environ, self.start_response)
  409. self.finish_response()
  410. # XXX else: attempt advanced recovery techniques for HTML or text?
  411. def error_output(self, environ, start_response):
  412. import traceback
  413. start_response(self.error_status, self.error_headers[:], sys.exc_info())
  414. return ['\n'.join(traceback.format_exception(*sys.exc_info()))]
  415. # Pure abstract methods; *must* be overridden in subclasses
  416. def _write(self,data):
  417. self.stdout.write(data)
  418. self._write = self.stdout.write
  419. def _flush(self):
  420. self.stdout.flush()
  421. self._flush = self.stdout.flush
  422. def get_stdin(self):
  423. return self.stdin
  424. def get_stderr(self):
  425. return self.stderr
  426. def add_cgi_vars(self):
  427. self.environ.update(self.base_env)
  428. class WSGIServer(HTTPServer):
  429. """BaseHTTPServer that implements the Python WSGI protocol"""
  430. application = None
  431. def __init__(self, *args, **kwargs):
  432. if kwargs.pop('ipv6', False):
  433. self.address_family = socket.AF_INET6
  434. HTTPServer.__init__(self, *args, **kwargs)
  435. def server_bind(self):
  436. """Override server_bind to store the server name."""
  437. try:
  438. HTTPServer.server_bind(self)
  439. except Exception, e:
  440. raise WSGIServerException(e)
  441. self.setup_environ()
  442. def setup_environ(self):
  443. # Set up base environment
  444. env = self.base_environ = {}
  445. env['SERVER_NAME'] = self.server_name
  446. env['GATEWAY_INTERFACE'] = 'CGI/1.1'
  447. env['SERVER_PORT'] = str(self.server_port)
  448. env['REMOTE_HOST']=''
  449. env['CONTENT_LENGTH']=''
  450. env['SCRIPT_NAME'] = ''
  451. def get_app(self):
  452. return self.application
  453. def set_app(self,application):
  454. self.application = application
  455. class WSGIRequestHandler(BaseHTTPRequestHandler):
  456. server_version = "WSGIServer/" + __version__
  457. def __init__(self, *args, **kwargs):
  458. from django.conf import settings
  459. self.admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
  460. # We set self.path to avoid crashes in log_message() on unsupported
  461. # requests (like "OPTIONS").
  462. self.path = ''
  463. self.style = color_style()
  464. BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
  465. def get_environ(self):
  466. env = self.server.base_environ.copy()
  467. env['SERVER_PROTOCOL'] = self.request_version
  468. env['REQUEST_METHOD'] = self.command
  469. if '?' in self.path:
  470. path,query = self.path.split('?',1)
  471. else:
  472. path,query = self.path,''
  473. env['PATH_INFO'] = urllib.unquote(path)
  474. env['QUERY_STRING'] = query
  475. env['REMOTE_ADDR'] = self.client_address[0]
  476. if self.headers.typeheader is None:
  477. env['CONTENT_TYPE'] = self.headers.type
  478. else:
  479. env['CONTENT_TYPE'] = self.headers.typeheader
  480. length = self.headers.getheader('content-length')
  481. if length:
  482. env['CONTENT_LENGTH'] = length
  483. for h in self.headers.headers:
  484. k,v = h.split(':',1)
  485. k=k.replace('-','_').upper(); v=v.strip()
  486. if k in env:
  487. continue # skip content length, type,etc.
  488. if 'HTTP_'+k in env:
  489. env['HTTP_'+k] += ','+v # comma-separate multiple headers
  490. else:
  491. env['HTTP_'+k] = v
  492. return env
  493. def get_stderr(self):
  494. return sys.stderr
  495. def handle(self):
  496. """Handle a single HTTP request"""
  497. self.raw_requestline = self.rfile.readline()
  498. if not self.parse_request(): # An error code has been sent, just exit
  499. return
  500. handler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())
  501. handler.request_handler = self # backpointer for logging
  502. handler.run(self.server.get_app())
  503. def log_message(self, format, *args):
  504. # Don't bother logging requests for admin images or the favicon.
  505. if self.path.startswith(self.admin_media_prefix) or self.path == '/favicon.ico':
  506. return
  507. msg = "[%s] %s\n" % (self.log_date_time_string(), format % args)
  508. # Utilize terminal colors, if available
  509. if args[1][0] == '2':
  510. # Put 2XX first, since it should be the common case
  511. msg = self.style.HTTP_SUCCESS(msg)
  512. elif args[1][0] == '1':
  513. msg = self.style.HTTP_INFO(msg)
  514. elif args[1] == '304':
  515. msg = self.style.HTTP_NOT_MODIFIED(msg)
  516. elif args[1][0] == '3':
  517. msg = self.style.HTTP_REDIRECT(msg)
  518. elif args[1] == '404':
  519. msg = self.style.HTTP_NOT_FOUND(msg)
  520. elif args[1][0] == '4':
  521. msg = self.style.HTTP_BAD_REQUEST(msg)
  522. else:
  523. # Any 5XX, or any other response
  524. msg = self.style.HTTP_SERVER_ERROR(msg)
  525. sys.stderr.write(msg)
  526. class AdminMediaHandler(handlers.StaticFilesHandler):
  527. """
  528. WSGI middleware that intercepts calls to the admin media directory, as
  529. defined by the ADMIN_MEDIA_PREFIX setting, and serves those images.
  530. Use this ONLY LOCALLY, for development! This hasn't been tested for
  531. security and is not super efficient.
  532. This is pending for deprecation since 1.3.
  533. """
  534. def get_base_dir(self):
  535. import django
  536. return os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
  537. def get_base_url(self):
  538. from django.conf import settings
  539. from django.core.exceptions import ImproperlyConfigured
  540. if not settings.ADMIN_MEDIA_PREFIX:
  541. raise ImproperlyConfigured(
  542. "The ADMIN_MEDIA_PREFIX setting can't be empty "
  543. "when using the AdminMediaHandler, e.g. with runserver.")
  544. return settings.ADMIN_MEDIA_PREFIX
  545. def file_path(self, url):
  546. """
  547. Returns the path to the media file on disk for the given URL.
  548. The passed URL is assumed to begin with ``self.base_url``. If the
  549. resulting file path is outside the media directory, then a ValueError
  550. is raised.
  551. """
  552. relative_url = url[len(self.base_url[2]):]
  553. relative_path = urllib.url2pathname(relative_url)
  554. return safe_join(self.base_dir, relative_path)
  555. def serve(self, request):
  556. document_root, path = os.path.split(self.file_path(request.path))
  557. return static.serve(request, path,
  558. document_root=document_root, insecure=True)
  559. def _should_handle(self, path):
  560. """
  561. Checks if the path should be handled. Ignores the path if:
  562. * the host is provided as part of the base_url
  563. * the request's path isn't under the base path
  564. """
  565. return path.startswith(self.base_url[2]) and not self.base_url[1]
  566. def run(addr, port, wsgi_handler, ipv6=False):
  567. server_address = (addr, port)
  568. httpd = WSGIServer(server_address, WSGIRequestHandler, ipv6=ipv6)
  569. httpd.set_app(wsgi_handler)
  570. httpd.serve_forever()