PageRenderTime 63ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/python/gunicorn/http/wsgi.py

http://github.com/mozilla/zamboni-lib
Python | 233 lines | 223 code | 6 blank | 4 comment | 0 complexity | 676697224a04b9e532313db8f545c1e9 MD5 | raw file
  1. # -*- coding: utf-8 -
  2. #
  3. # This file is part of gunicorn released under the MIT license.
  4. # See the NOTICE for more information.
  5. import logging
  6. import os
  7. import re
  8. import sys
  9. from urllib import unquote
  10. from gunicorn import SERVER_SOFTWARE
  11. import gunicorn.util as util
  12. NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
  13. log = logging.getLogger(__name__)
  14. def create(req, sock, client, server, cfg):
  15. resp = Response(req, sock)
  16. environ = {
  17. "wsgi.input": req.body,
  18. "wsgi.errors": sys.stderr,
  19. "wsgi.version": (1, 0),
  20. "wsgi.multithread": False,
  21. "wsgi.multiprocess": (cfg.workers > 1),
  22. "wsgi.run_once": False,
  23. "gunicorn.socket": sock,
  24. "SERVER_SOFTWARE": SERVER_SOFTWARE,
  25. "REQUEST_METHOD": req.method,
  26. "QUERY_STRING": req.query,
  27. "RAW_URI": req.uri,
  28. "SERVER_PROTOCOL": "HTTP/%s" % ".".join(map(str, req.version)),
  29. "CONTENT_TYPE": "",
  30. "CONTENT_LENGTH": ""
  31. }
  32. # authors should be aware that REMOTE_HOST and REMOTE_ADDR
  33. # may not qualify the remote addr:
  34. # http://www.ietf.org/rfc/rfc3875
  35. client = client or "127.0.0.1"
  36. forward = client
  37. url_scheme = "http"
  38. script_name = os.environ.get("SCRIPT_NAME", "")
  39. for hdr_name, hdr_value in req.headers:
  40. if hdr_name == "EXPECT":
  41. # handle expect
  42. if hdr_value.lower() == "100-continue":
  43. sock.send("HTTP/1.1 100 Continue\r\n\r\n")
  44. elif hdr_name == "X-FORWARDED-FOR":
  45. forward = hdr_value
  46. elif hdr_name == "X-FORWARDED-PROTOCOL" and hdr_value.lower() == "ssl":
  47. url_scheme = "https"
  48. elif hdr_name == "X-FORWARDED-SSL" and hdr_value.lower() == "on":
  49. url_scheme = "https"
  50. elif hdr_name == "HOST":
  51. server = hdr_value
  52. elif hdr_name == "SCRIPT_NAME":
  53. script_name = hdr_value
  54. elif hdr_name == "CONTENT-TYPE":
  55. environ['CONTENT_TYPE'] = hdr_value
  56. continue
  57. elif hdr_name == "CONTENT-LENGTH":
  58. environ['CONTENT_LENGTH'] = hdr_value
  59. continue
  60. key = 'HTTP_' + hdr_name.replace('-', '_')
  61. environ[key] = hdr_value
  62. environ['wsgi.url_scheme'] = url_scheme
  63. if isinstance(forward, basestring):
  64. # we only took the last one
  65. # http://en.wikipedia.org/wiki/X-Forwarded-For
  66. if forward.find(",") >= 0:
  67. forward = forward.rsplit(",", 1)[1].strip()
  68. # find host and port on ipv6 address
  69. if '[' in forward and ']' in forward:
  70. host = forward.split(']')[0][1:].lower()
  71. elif ":" in forward and forward.count(":") == 1:
  72. host = forward.split(":")[0].lower()
  73. else:
  74. host = forward
  75. forward = forward.split(']')[-1]
  76. if ":" in forward and forward.count(":") == 1:
  77. port = forward.split(':', 1)[1]
  78. else:
  79. port = 80
  80. remote = (host, port)
  81. else:
  82. remote = forward
  83. environ['REMOTE_ADDR'] = remote[0]
  84. environ['REMOTE_PORT'] = str(remote[1])
  85. if isinstance(server, basestring):
  86. server = server.split(":")
  87. if len(server) == 1:
  88. if url_scheme == "http":
  89. server.append("80")
  90. elif url_scheme == "https":
  91. server.append("443")
  92. else:
  93. server.append('')
  94. environ['SERVER_NAME'] = server[0]
  95. environ['SERVER_PORT'] = server[1]
  96. path_info = req.path
  97. if script_name:
  98. path_info = path_info.split(script_name, 1)[1]
  99. environ['PATH_INFO'] = unquote(path_info)
  100. environ['SCRIPT_NAME'] = script_name
  101. return resp, environ
  102. class Response(object):
  103. def __init__(self, req, sock):
  104. self.req = req
  105. self.sock = sock
  106. self.version = SERVER_SOFTWARE
  107. self.status = None
  108. self.chunked = False
  109. self.must_close = False
  110. self.headers = []
  111. self.headers_sent = False
  112. self.clength = None
  113. self.sent = 0
  114. def force_close(self):
  115. self.must_close = True
  116. def should_close(self):
  117. if self.must_close or self.req.should_close():
  118. return True
  119. if self.clength is not None or self.chunked:
  120. return False
  121. return True
  122. def start_response(self, status, headers, exc_info=None):
  123. if exc_info:
  124. try:
  125. if self.status and self.headers_sent:
  126. raise exc_info[0], exc_info[1], exc_info[2]
  127. finally:
  128. exc_info = None
  129. elif self.status is not None:
  130. raise AssertionError("Response headers already set!")
  131. self.status = status
  132. self.process_headers(headers)
  133. self.chunked = self.is_chunked()
  134. return self.write
  135. def process_headers(self, headers):
  136. for name, value in headers:
  137. assert isinstance(name, basestring), "%r is not a string" % name
  138. lname = name.lower().strip()
  139. if lname == "content-length":
  140. self.clength = int(value)
  141. elif util.is_hoppish(name):
  142. if lname == "connection":
  143. # handle websocket
  144. if value.lower().strip() != "upgrade":
  145. continue
  146. else:
  147. # ignore hopbyhop headers
  148. continue
  149. self.headers.append((name.strip(), str(value).strip()))
  150. def is_chunked(self):
  151. # Only use chunked responses when the client is
  152. # speaking HTTP/1.1 or newer and there was
  153. # no Content-Length header set.
  154. if self.clength is not None:
  155. return False
  156. elif self.req.version <= (1,0):
  157. return False
  158. return True
  159. def default_headers(self):
  160. connection = "keep-alive"
  161. if self.should_close():
  162. connection = "close"
  163. headers = [
  164. "HTTP/%s.%s %s\r\n" % (self.req.version[0],
  165. self.req.version[1], self.status),
  166. "Server: %s\r\n" % self.version,
  167. "Date: %s\r\n" % util.http_date(),
  168. "Connection: %s\r\n" % connection
  169. ]
  170. if self.chunked:
  171. headers.append("Transfer-Encoding: chunked\r\n")
  172. return headers
  173. def send_headers(self):
  174. if self.headers_sent:
  175. return
  176. tosend = self.default_headers()
  177. tosend.extend(["%s: %s\r\n" % (n, v) for n, v in self.headers])
  178. util.write(self.sock, "%s\r\n" % "".join(tosend))
  179. self.headers_sent = True
  180. def write(self, arg):
  181. self.send_headers()
  182. assert isinstance(arg, basestring), "%r is not a string." % arg
  183. arglen = len(arg)
  184. tosend = arglen
  185. if self.clength is not None:
  186. if self.sent >= self.clength:
  187. # Never write more than self.clength bytes
  188. return
  189. tosend = min(self.clength - self.sent, tosend)
  190. if tosend < arglen:
  191. arg = arg[:tosend]
  192. self.sent += tosend
  193. util.write(self.sock, arg, self.chunked)
  194. def close(self):
  195. if not self.headers_sent:
  196. self.send_headers()
  197. if self.chunked:
  198. util.write_chunk(self.sock, "")