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.
		
		
		
		
		
			
		
			
				
					
					
						
							264 lines
						
					
					
						
							8.9 KiB
						
					
					
				
			
		
		
	
	
							264 lines
						
					
					
						
							8.9 KiB
						
					
					
				# -*- 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 "<Worker %s>" % 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 self.cfg.reload == 'off':
 | 
						|
                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.")
 | 
						|
 |