wsgi.py 12 KB

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