wsgi.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  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 io
  6. import logging
  7. import os
  8. import re
  9. import sys
  10. from gunicorn._compat import unquote_to_wsgi_str
  11. from gunicorn.http.message import HEADER_RE
  12. from gunicorn.http.errors import InvalidHeader, InvalidHeaderName
  13. from gunicorn.six import string_types, binary_type, reraise
  14. from gunicorn import SERVER_SOFTWARE
  15. import gunicorn.util as util
  16. try:
  17. # Python 3.3 has os.sendfile().
  18. from os import sendfile
  19. except ImportError:
  20. try:
  21. from ._sendfile import sendfile
  22. except ImportError:
  23. sendfile = None
  24. # Send files in at most 1GB blocks as some operating systems can have problems
  25. # with sending files in blocks over 2GB.
  26. BLKSIZE = 0x3FFFFFFF
  27. HEADER_VALUE_RE = re.compile(r'[\x00-\x1F\x7F]')
  28. log = logging.getLogger(__name__)
  29. class FileWrapper(object):
  30. def __init__(self, filelike, blksize=8192):
  31. self.filelike = filelike
  32. self.blksize = blksize
  33. if hasattr(filelike, 'close'):
  34. self.close = filelike.close
  35. def __getitem__(self, key):
  36. data = self.filelike.read(self.blksize)
  37. if data:
  38. return data
  39. raise IndexError
  40. class WSGIErrorsWrapper(io.RawIOBase):
  41. def __init__(self, cfg):
  42. errorlog = logging.getLogger("gunicorn.error")
  43. handlers = errorlog.handlers
  44. self.streams = []
  45. if cfg.errorlog == "-":
  46. self.streams.append(sys.stderr)
  47. handlers = handlers[1:]
  48. for h in handlers:
  49. if hasattr(h, "stream"):
  50. self.streams.append(h.stream)
  51. def write(self, data):
  52. for stream in self.streams:
  53. try:
  54. stream.write(data)
  55. except UnicodeError:
  56. stream.write(data.encode("UTF-8"))
  57. stream.flush()
  58. def base_environ(cfg):
  59. return {
  60. "wsgi.errors": WSGIErrorsWrapper(cfg),
  61. "wsgi.version": (1, 0),
  62. "wsgi.multithread": False,
  63. "wsgi.multiprocess": (cfg.workers > 1),
  64. "wsgi.run_once": False,
  65. "wsgi.file_wrapper": FileWrapper,
  66. "SERVER_SOFTWARE": SERVER_SOFTWARE,
  67. }
  68. def default_environ(req, sock, cfg):
  69. env = base_environ(cfg)
  70. env.update({
  71. "wsgi.input": req.body,
  72. "gunicorn.socket": sock,
  73. "REQUEST_METHOD": req.method,
  74. "QUERY_STRING": req.query,
  75. "RAW_URI": req.uri,
  76. "SERVER_PROTOCOL": "HTTP/%s" % ".".join([str(v) for v in req.version])
  77. })
  78. return env
  79. def proxy_environ(req):
  80. info = req.proxy_protocol_info
  81. if not info:
  82. return {}
  83. return {
  84. "PROXY_PROTOCOL": info["proxy_protocol"],
  85. "REMOTE_ADDR": info["client_addr"],
  86. "REMOTE_PORT": str(info["client_port"]),
  87. "PROXY_ADDR": info["proxy_addr"],
  88. "PROXY_PORT": str(info["proxy_port"]),
  89. }
  90. def create(req, sock, client, server, cfg):
  91. resp = Response(req, sock, cfg)
  92. # set initial environ
  93. environ = default_environ(req, sock, cfg)
  94. # default variables
  95. host = None
  96. url_scheme = "https" if cfg.is_ssl else "http"
  97. script_name = os.environ.get("SCRIPT_NAME", "")
  98. # set secure_headers
  99. secure_headers = cfg.secure_scheme_headers
  100. if client and not isinstance(client, string_types):
  101. if ('*' not in cfg.forwarded_allow_ips
  102. and client[0] not in cfg.forwarded_allow_ips):
  103. secure_headers = {}
  104. # add the headers to the environ
  105. for hdr_name, hdr_value in req.headers:
  106. if hdr_name == "EXPECT":
  107. # handle expect
  108. if hdr_value.lower() == "100-continue":
  109. sock.send(b"HTTP/1.1 100 Continue\r\n\r\n")
  110. elif secure_headers and (hdr_name in secure_headers and
  111. hdr_value == secure_headers[hdr_name]):
  112. url_scheme = "https"
  113. elif hdr_name == 'HOST':
  114. host = hdr_value
  115. elif hdr_name == "SCRIPT_NAME":
  116. script_name = hdr_value
  117. elif hdr_name == "CONTENT-TYPE":
  118. environ['CONTENT_TYPE'] = hdr_value
  119. continue
  120. elif hdr_name == "CONTENT-LENGTH":
  121. environ['CONTENT_LENGTH'] = hdr_value
  122. continue
  123. key = 'HTTP_' + hdr_name.replace('-', '_')
  124. if key in environ:
  125. hdr_value = "%s,%s" % (environ[key], hdr_value)
  126. environ[key] = hdr_value
  127. # set the url scheme
  128. environ['wsgi.url_scheme'] = url_scheme
  129. # set the REMOTE_* keys in environ
  130. # authors should be aware that REMOTE_HOST and REMOTE_ADDR
  131. # may not qualify the remote addr:
  132. # http://www.ietf.org/rfc/rfc3875
  133. if isinstance(client, string_types):
  134. environ['REMOTE_ADDR'] = client
  135. elif isinstance(client, binary_type):
  136. environ['REMOTE_ADDR'] = str(client)
  137. else:
  138. environ['REMOTE_ADDR'] = client[0]
  139. environ['REMOTE_PORT'] = str(client[1])
  140. # handle the SERVER_*
  141. # Normally only the application should use the Host header but since the
  142. # WSGI spec doesn't support unix sockets, we are using it to create
  143. # viable SERVER_* if possible.
  144. if isinstance(server, string_types):
  145. server = server.split(":")
  146. if len(server) == 1:
  147. # unix socket
  148. if host and host is not None:
  149. server = host.split(':')
  150. if len(server) == 1:
  151. if url_scheme == "http":
  152. server.append(80),
  153. elif url_scheme == "https":
  154. server.append(443)
  155. else:
  156. server.append('')
  157. else:
  158. # no host header given which means that we are not behind a
  159. # proxy, so append an empty port.
  160. server.append('')
  161. environ['SERVER_NAME'] = server[0]
  162. environ['SERVER_PORT'] = str(server[1])
  163. # set the path and script name
  164. path_info = req.path
  165. if script_name:
  166. path_info = path_info.split(script_name, 1)[1]
  167. environ['PATH_INFO'] = unquote_to_wsgi_str(path_info)
  168. environ['SCRIPT_NAME'] = script_name
  169. # override the environ with the correct remote and server address if
  170. # we are behind a proxy using the proxy protocol.
  171. environ.update(proxy_environ(req))
  172. return resp, environ
  173. class Response(object):
  174. def __init__(self, req, sock, cfg):
  175. self.req = req
  176. self.sock = sock
  177. self.version = SERVER_SOFTWARE
  178. self.status = None
  179. self.chunked = False
  180. self.must_close = False
  181. self.headers = []
  182. self.headers_sent = False
  183. self.response_length = None
  184. self.sent = 0
  185. self.upgrade = False
  186. self.cfg = cfg
  187. def force_close(self):
  188. self.must_close = True
  189. def should_close(self):
  190. if self.must_close or self.req.should_close():
  191. return True
  192. if self.response_length is not None or self.chunked:
  193. return False
  194. if self.req.method == 'HEAD':
  195. return False
  196. if self.status_code < 200 or self.status_code in (204, 304):
  197. return False
  198. return True
  199. def start_response(self, status, headers, exc_info=None):
  200. if exc_info:
  201. try:
  202. if self.status and self.headers_sent:
  203. reraise(exc_info[0], exc_info[1], exc_info[2])
  204. finally:
  205. exc_info = None
  206. elif self.status is not None:
  207. raise AssertionError("Response headers already set!")
  208. self.status = status
  209. # get the status code from the response here so we can use it to check
  210. # the need for the connection header later without parsing the string
  211. # each time.
  212. try:
  213. self.status_code = int(self.status.split()[0])
  214. except ValueError:
  215. self.status_code = None
  216. self.process_headers(headers)
  217. self.chunked = self.is_chunked()
  218. return self.write
  219. def process_headers(self, headers):
  220. for name, value in headers:
  221. if not isinstance(name, string_types):
  222. raise TypeError('%r is not a string' % name)
  223. if HEADER_RE.search(name):
  224. raise InvalidHeaderName('%r' % name)
  225. if HEADER_VALUE_RE.search(value):
  226. raise InvalidHeader('%r' % value)
  227. value = str(value).strip()
  228. lname = name.lower().strip()
  229. if lname == "content-length":
  230. self.response_length = int(value)
  231. elif util.is_hoppish(name):
  232. if lname == "connection":
  233. # handle websocket
  234. if value.lower().strip() == "upgrade":
  235. self.upgrade = True
  236. elif lname == "upgrade":
  237. if value.lower().strip() == "websocket":
  238. self.headers.append((name.strip(), value))
  239. # ignore hopbyhop headers
  240. continue
  241. self.headers.append((name.strip(), value))
  242. def is_chunked(self):
  243. # Only use chunked responses when the client is
  244. # speaking HTTP/1.1 or newer and there was
  245. # no Content-Length header set.
  246. if self.response_length is not None:
  247. return False
  248. elif self.req.version <= (1, 0):
  249. return False
  250. elif self.req.method == 'HEAD':
  251. # Responses to a HEAD request MUST NOT contain a response body.
  252. return False
  253. elif self.status_code in (204, 304):
  254. # Do not use chunked responses when the response is guaranteed to
  255. # not have a response body.
  256. return False
  257. return True
  258. def default_headers(self):
  259. # set the connection header
  260. if self.upgrade:
  261. connection = "upgrade"
  262. elif self.should_close():
  263. connection = "close"
  264. else:
  265. connection = "keep-alive"
  266. headers = [
  267. "HTTP/%s.%s %s\r\n" % (self.req.version[0],
  268. self.req.version[1], self.status),
  269. "Server: %s\r\n" % self.version,
  270. "Date: %s\r\n" % util.http_date(),
  271. "Connection: %s\r\n" % connection
  272. ]
  273. if self.chunked:
  274. headers.append("Transfer-Encoding: chunked\r\n")
  275. return headers
  276. def send_headers(self):
  277. if self.headers_sent:
  278. return
  279. tosend = self.default_headers()
  280. tosend.extend(["%s: %s\r\n" % (k, v) for k, v in self.headers])
  281. header_str = "%s\r\n" % "".join(tosend)
  282. util.write(self.sock, util.to_bytestring(header_str, "ascii"))
  283. self.headers_sent = True
  284. def write(self, arg):
  285. self.send_headers()
  286. if not isinstance(arg, binary_type):
  287. raise TypeError('%r is not a byte' % arg)
  288. arglen = len(arg)
  289. tosend = arglen
  290. if self.response_length is not None:
  291. if self.sent >= self.response_length:
  292. # Never write more than self.response_length bytes
  293. return
  294. tosend = min(self.response_length - self.sent, tosend)
  295. if tosend < arglen:
  296. arg = arg[:tosend]
  297. # Sending an empty chunk signals the end of the
  298. # response and prematurely closes the response
  299. if self.chunked and tosend == 0:
  300. return
  301. self.sent += tosend
  302. util.write(self.sock, arg, self.chunked)
  303. def can_sendfile(self):
  304. return self.cfg.sendfile is not False and sendfile is not None
  305. def sendfile(self, respiter):
  306. if self.cfg.is_ssl or not self.can_sendfile():
  307. return False
  308. if not util.has_fileno(respiter.filelike):
  309. return False
  310. fileno = respiter.filelike.fileno()
  311. try:
  312. offset = os.lseek(fileno, 0, os.SEEK_CUR)
  313. if self.response_length is None:
  314. filesize = os.fstat(fileno).st_size
  315. # The file may be special and sendfile will fail.
  316. # It may also be zero-length, but that is okay.
  317. if filesize == 0:
  318. return False
  319. nbytes = filesize - offset
  320. else:
  321. nbytes = self.response_length
  322. except (OSError, io.UnsupportedOperation):
  323. return False
  324. self.send_headers()
  325. if self.is_chunked():
  326. chunk_size = "%X\r\n" % nbytes
  327. self.sock.sendall(chunk_size.encode('utf-8'))
  328. sockno = self.sock.fileno()
  329. sent = 0
  330. while sent != nbytes:
  331. count = min(nbytes - sent, BLKSIZE)
  332. sent += sendfile(sockno, fileno, offset + sent, count)
  333. if self.is_chunked():
  334. self.sock.sendall(b"\r\n")
  335. os.lseek(fileno, offset, os.SEEK_SET)
  336. return True
  337. def write_file(self, respiter):
  338. if not self.sendfile(respiter):
  339. for item in respiter:
  340. self.write(item)
  341. def close(self):
  342. if not self.headers_sent:
  343. self.send_headers()
  344. if self.chunked:
  345. util.write_chunk(self.sock, b"")