PageRenderTime 83ms CodeModel.GetById 40ms RepoModel.GetById 1ms app.codeStats 0ms

/gunicorn/http/wsgi.py

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