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.
		
		
		
		
			
				
					265 lines
				
				8.9 KiB
			
		
		
			
		
	
	
					265 lines
				
				8.9 KiB
			| 
								 
											8 years ago
										 
									 | 
							
								# -*- 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.")
							 |