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.")
|
|
|