# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. from datetime import datetime import os from random import randint import signal from ssl import SSLError import sys import time import traceback from gunicorn import six from gunicorn import util from gunicorn.workers.workertmp import WorkerTmp from gunicorn.reloader import reloader_engines from gunicorn.http.errors import ( InvalidHeader, InvalidHeaderName, InvalidRequestLine, InvalidRequestMethod, InvalidHTTPVersion, LimitRequestLine, LimitRequestHeaders, ) from gunicorn.http.errors import InvalidProxyLine, ForbiddenProxyRequest from gunicorn.http.errors import InvalidSchemeHeaders from gunicorn.http.wsgi import default_environ, Response from gunicorn.six import MAXSIZE class Worker(object): SIGNALS = [getattr(signal, "SIG%s" % x) for x in "ABRT HUP QUIT INT TERM USR1 USR2 WINCH CHLD".split()] PIPE = [] def __init__(self, age, ppid, sockets, app, timeout, cfg, log): """\ This is called pre-fork so it shouldn't do anything to the current process. If there's a need to make process wide changes you'll want to do that in ``self.init_process()``. """ self.age = age self.pid = "[booting]" self.ppid = ppid self.sockets = sockets self.app = app self.timeout = timeout self.cfg = cfg self.booted = False self.aborted = False self.reloader = None self.nr = 0 jitter = randint(0, cfg.max_requests_jitter) self.max_requests = cfg.max_requests + jitter or MAXSIZE self.alive = True self.log = log self.tmp = WorkerTmp(cfg) def __str__(self): return "" % self.pid def notify(self): """\ Your worker subclass must arrange to have this method called once every ``self.timeout`` seconds. If you fail in accomplishing this task, the master process will murder your workers. """ self.tmp.notify() def run(self): """\ This is the mainloop of a worker process. You should override this method in a subclass to provide the intended behaviour for your particular evil schemes. """ raise NotImplementedError() def init_process(self): """\ If you override this method in a subclass, the last statement in the function should be to call this method with super(MyWorkerClass, self).init_process() so that the ``run()`` loop is initiated. """ # set environment' variables if self.cfg.env: for k, v in self.cfg.env.items(): os.environ[k] = v util.set_owner_process(self.cfg.uid, self.cfg.gid, initgroups=self.cfg.initgroups) # Reseed the random number generator util.seed() # For waking ourselves up self.PIPE = os.pipe() for p in self.PIPE: util.set_non_blocking(p) util.close_on_exec(p) # Prevent fd inheritance for s in self.sockets: util.close_on_exec(s) util.close_on_exec(self.tmp.fileno()) self.wait_fds = self.sockets + [self.PIPE[0]] self.log.close_on_exec() self.init_signals() # start the reloader if self.cfg.reload: def changed(fname): self.log.info("Worker reloading: %s modified", fname) self.alive = False self.cfg.worker_int(self) time.sleep(0.1) sys.exit(0) reloader_cls = reloader_engines[self.cfg.reload_engine] self.reloader = reloader_cls(extra_files=self.cfg.reload_extra_files, callback=changed) self.reloader.start() self.load_wsgi() self.cfg.post_worker_init(self) # Enter main run loop self.booted = True self.run() def load_wsgi(self): try: self.wsgi = self.app.wsgi() except SyntaxError as e: if not self.cfg.reload: raise self.log.exception(e) # fix from PR #1228 # storing the traceback into exc_tb will create a circular reference. # per https://docs.python.org/2/library/sys.html#sys.exc_info warning, # delete the traceback after use. try: _, exc_val, exc_tb = sys.exc_info() self.reloader.add_extra_file(exc_val.filename) tb_string = six.StringIO() traceback.print_tb(exc_tb, file=tb_string) self.wsgi = util.make_fail_app(tb_string.getvalue()) finally: del exc_tb def init_signals(self): # reset signaling for s in self.SIGNALS: signal.signal(s, signal.SIG_DFL) # init new signaling signal.signal(signal.SIGQUIT, self.handle_quit) signal.signal(signal.SIGTERM, self.handle_exit) signal.signal(signal.SIGINT, self.handle_quit) signal.signal(signal.SIGWINCH, self.handle_winch) signal.signal(signal.SIGUSR1, self.handle_usr1) signal.signal(signal.SIGABRT, self.handle_abort) # Don't let SIGTERM and SIGUSR1 disturb active requests # by interrupting system calls if hasattr(signal, 'siginterrupt'): # python >= 2.6 signal.siginterrupt(signal.SIGTERM, False) signal.siginterrupt(signal.SIGUSR1, False) if hasattr(signal, 'set_wakeup_fd'): signal.set_wakeup_fd(self.PIPE[1]) def handle_usr1(self, sig, frame): self.log.reopen_files() def handle_exit(self, sig, frame): self.alive = False def handle_quit(self, sig, frame): self.alive = False # worker_int callback self.cfg.worker_int(self) time.sleep(0.1) sys.exit(0) def handle_abort(self, sig, frame): self.alive = False self.cfg.worker_abort(self) sys.exit(1) def handle_error(self, req, client, addr, exc): request_start = datetime.now() addr = addr or ('', -1) # unix socket case if isinstance(exc, (InvalidRequestLine, InvalidRequestMethod, InvalidHTTPVersion, InvalidHeader, InvalidHeaderName, LimitRequestLine, LimitRequestHeaders, InvalidProxyLine, ForbiddenProxyRequest, InvalidSchemeHeaders, SSLError)): status_int = 400 reason = "Bad Request" if isinstance(exc, InvalidRequestLine): mesg = "Invalid Request Line '%s'" % str(exc) elif isinstance(exc, InvalidRequestMethod): mesg = "Invalid Method '%s'" % str(exc) elif isinstance(exc, InvalidHTTPVersion): mesg = "Invalid HTTP Version '%s'" % str(exc) elif isinstance(exc, (InvalidHeaderName, InvalidHeader,)): mesg = "%s" % str(exc) if not req and hasattr(exc, "req"): req = exc.req # for access log elif isinstance(exc, LimitRequestLine): mesg = "%s" % str(exc) elif isinstance(exc, LimitRequestHeaders): mesg = "Error parsing headers: '%s'" % str(exc) elif isinstance(exc, InvalidProxyLine): mesg = "'%s'" % str(exc) elif isinstance(exc, ForbiddenProxyRequest): reason = "Forbidden" mesg = "Request forbidden" status_int = 403 elif isinstance(exc, InvalidSchemeHeaders): mesg = "%s" % str(exc) elif isinstance(exc, SSLError): reason = "Forbidden" mesg = "'%s'" % str(exc) status_int = 403 msg = "Invalid request from ip={ip}: {error}" self.log.debug(msg.format(ip=addr[0], error=str(exc))) else: if hasattr(req, "uri"): self.log.exception("Error handling request %s", req.uri) status_int = 500 reason = "Internal Server Error" mesg = "" if req is not None: request_time = datetime.now() - request_start environ = default_environ(req, client, self.cfg) environ['REMOTE_ADDR'] = addr[0] environ['REMOTE_PORT'] = str(addr[1]) resp = Response(req, client, self.cfg) resp.status = "%s %s" % (status_int, reason) resp.response_length = len(mesg) self.log.access(resp, req, environ, request_time) try: util.write_error(client, status_int, reason, mesg) except: self.log.debug("Failed to send error message.") def handle_winch(self, sig, fname): # Ignore SIGWINCH in worker. Fixes a crash on OpenBSD. self.log.debug("worker: SIGWINCH ignored.")