Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

base.py 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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. from datetime import datetime
  6. import os
  7. from random import randint
  8. import signal
  9. from ssl import SSLError
  10. import sys
  11. import time
  12. import traceback
  13. from gunicorn import util
  14. from gunicorn.workers.workertmp import WorkerTmp
  15. from gunicorn.reloader import Reloader
  16. from gunicorn.http.errors import (
  17. InvalidHeader, InvalidHeaderName, InvalidRequestLine, InvalidRequestMethod,
  18. InvalidHTTPVersion, LimitRequestLine, LimitRequestHeaders,
  19. )
  20. from gunicorn.http.errors import InvalidProxyLine, ForbiddenProxyRequest
  21. from gunicorn.http.wsgi import default_environ, Response
  22. from gunicorn.six import MAXSIZE
  23. class Worker(object):
  24. SIGNALS = [getattr(signal, "SIG%s" % x)
  25. for x in "ABRT HUP QUIT INT TERM USR1 USR2 WINCH CHLD".split()]
  26. PIPE = []
  27. def __init__(self, age, ppid, sockets, app, timeout, cfg, log):
  28. """\
  29. This is called pre-fork so it shouldn't do anything to the
  30. current process. If there's a need to make process wide
  31. changes you'll want to do that in ``self.init_process()``.
  32. """
  33. self.age = age
  34. self.ppid = ppid
  35. self.sockets = sockets
  36. self.app = app
  37. self.timeout = timeout
  38. self.cfg = cfg
  39. self.booted = False
  40. self.aborted = False
  41. self.reloader = None
  42. self.nr = 0
  43. jitter = randint(0, cfg.max_requests_jitter)
  44. self.max_requests = cfg.max_requests + jitter or MAXSIZE
  45. self.alive = True
  46. self.log = log
  47. self.tmp = WorkerTmp(cfg)
  48. def __str__(self):
  49. return "<Worker %s>" % self.pid
  50. @property
  51. def pid(self):
  52. return os.getpid()
  53. def notify(self):
  54. """\
  55. Your worker subclass must arrange to have this method called
  56. once every ``self.timeout`` seconds. If you fail in accomplishing
  57. this task, the master process will murder your workers.
  58. """
  59. self.tmp.notify()
  60. def run(self):
  61. """\
  62. This is the mainloop of a worker process. You should override
  63. this method in a subclass to provide the intended behaviour
  64. for your particular evil schemes.
  65. """
  66. raise NotImplementedError()
  67. def init_process(self):
  68. """\
  69. If you override this method in a subclass, the last statement
  70. in the function should be to call this method with
  71. super(MyWorkerClass, self).init_process() so that the ``run()``
  72. loop is initiated.
  73. """
  74. # start the reloader
  75. if self.cfg.reload:
  76. def changed(fname):
  77. self.log.info("Worker reloading: %s modified", fname)
  78. self.alive = False
  79. self.cfg.worker_int(self)
  80. time.sleep(0.1)
  81. sys.exit(0)
  82. self.reloader = Reloader(callback=changed)
  83. self.reloader.start()
  84. # set environment' variables
  85. if self.cfg.env:
  86. for k, v in self.cfg.env.items():
  87. os.environ[k] = v
  88. util.set_owner_process(self.cfg.uid, self.cfg.gid)
  89. # Reseed the random number generator
  90. util.seed()
  91. # For waking ourselves up
  92. self.PIPE = os.pipe()
  93. for p in self.PIPE:
  94. util.set_non_blocking(p)
  95. util.close_on_exec(p)
  96. # Prevent fd inheritance
  97. [util.close_on_exec(s) for s in self.sockets]
  98. util.close_on_exec(self.tmp.fileno())
  99. self.wait_fds = self.sockets + [self.PIPE[0]]
  100. self.log.close_on_exec()
  101. self.init_signals()
  102. self.load_wsgi()
  103. self.cfg.post_worker_init(self)
  104. # Enter main run loop
  105. self.booted = True
  106. self.run()
  107. def load_wsgi(self):
  108. try:
  109. self.wsgi = self.app.wsgi()
  110. except SyntaxError as e:
  111. if not self.cfg.reload:
  112. raise
  113. self.log.exception(e)
  114. # fix from PR #1228
  115. # storing the traceback into exc_tb will create a circular reference.
  116. # per https://docs.python.org/2/library/sys.html#sys.exc_info warning,
  117. # delete the traceback after use.
  118. try:
  119. exc_type, exc_val, exc_tb = sys.exc_info()
  120. self.reloader.add_extra_file(exc_val.filename)
  121. tb_string = traceback.format_tb(exc_tb)
  122. self.wsgi = util.make_fail_app(tb_string)
  123. finally:
  124. del exc_tb
  125. def init_signals(self):
  126. # reset signaling
  127. [signal.signal(s, signal.SIG_DFL) for s in self.SIGNALS]
  128. # init new signaling
  129. signal.signal(signal.SIGQUIT, self.handle_quit)
  130. signal.signal(signal.SIGTERM, self.handle_exit)
  131. signal.signal(signal.SIGINT, self.handle_quit)
  132. signal.signal(signal.SIGWINCH, self.handle_winch)
  133. signal.signal(signal.SIGUSR1, self.handle_usr1)
  134. signal.signal(signal.SIGABRT, self.handle_abort)
  135. # Don't let SIGTERM and SIGUSR1 disturb active requests
  136. # by interrupting system calls
  137. if hasattr(signal, 'siginterrupt'): # python >= 2.6
  138. signal.siginterrupt(signal.SIGTERM, False)
  139. signal.siginterrupt(signal.SIGUSR1, False)
  140. if hasattr(signal, 'set_wakeup_fd'):
  141. signal.set_wakeup_fd(self.PIPE[1])
  142. def handle_usr1(self, sig, frame):
  143. self.log.reopen_files()
  144. def handle_exit(self, sig, frame):
  145. self.alive = False
  146. def handle_quit(self, sig, frame):
  147. self.alive = False
  148. # worker_int callback
  149. self.cfg.worker_int(self)
  150. time.sleep(0.1)
  151. sys.exit(0)
  152. def handle_abort(self, sig, frame):
  153. self.alive = False
  154. self.cfg.worker_abort(self)
  155. sys.exit(1)
  156. def handle_error(self, req, client, addr, exc):
  157. request_start = datetime.now()
  158. addr = addr or ('', -1) # unix socket case
  159. if isinstance(exc, (InvalidRequestLine, InvalidRequestMethod,
  160. InvalidHTTPVersion, InvalidHeader, InvalidHeaderName,
  161. LimitRequestLine, LimitRequestHeaders,
  162. InvalidProxyLine, ForbiddenProxyRequest,
  163. SSLError)):
  164. status_int = 400
  165. reason = "Bad Request"
  166. if isinstance(exc, InvalidRequestLine):
  167. mesg = "Invalid Request Line '%s'" % str(exc)
  168. elif isinstance(exc, InvalidRequestMethod):
  169. mesg = "Invalid Method '%s'" % str(exc)
  170. elif isinstance(exc, InvalidHTTPVersion):
  171. mesg = "Invalid HTTP Version '%s'" % str(exc)
  172. elif isinstance(exc, (InvalidHeaderName, InvalidHeader,)):
  173. mesg = "%s" % str(exc)
  174. if not req and hasattr(exc, "req"):
  175. req = exc.req # for access log
  176. elif isinstance(exc, LimitRequestLine):
  177. mesg = "%s" % str(exc)
  178. elif isinstance(exc, LimitRequestHeaders):
  179. mesg = "Error parsing headers: '%s'" % str(exc)
  180. elif isinstance(exc, InvalidProxyLine):
  181. mesg = "'%s'" % str(exc)
  182. elif isinstance(exc, ForbiddenProxyRequest):
  183. reason = "Forbidden"
  184. mesg = "Request forbidden"
  185. status_int = 403
  186. elif isinstance(exc, SSLError):
  187. reason = "Forbidden"
  188. mesg = "'%s'" % str(exc)
  189. status_int = 403
  190. msg = "Invalid request from ip={ip}: {error}"
  191. self.log.debug(msg.format(ip=addr[0], error=str(exc)))
  192. else:
  193. if hasattr(req, "uri"):
  194. self.log.exception("Error handling request %s", req.uri)
  195. status_int = 500
  196. reason = "Internal Server Error"
  197. mesg = ""
  198. if req is not None:
  199. request_time = datetime.now() - request_start
  200. environ = default_environ(req, client, self.cfg)
  201. environ['REMOTE_ADDR'] = addr[0]
  202. environ['REMOTE_PORT'] = str(addr[1])
  203. resp = Response(req, client, self.cfg)
  204. resp.status = "%s %s" % (status_int, reason)
  205. resp.response_length = len(mesg)
  206. self.log.access(resp, req, environ, request_time)
  207. try:
  208. util.write_error(client, status_int, reason, mesg)
  209. except:
  210. self.log.debug("Failed to send error message.")
  211. def handle_winch(self, sig, fname):
  212. # Ignore SIGWINCH in worker. Fixes a crash on OpenBSD.
  213. self.log.debug("worker: SIGWINCH ignored.")