Merge branch 'develop'

This commit is contained in:
benoitc 2012-11-19 11:49:05 +01:00
commit 0b40e69e67
79 changed files with 1072 additions and 855 deletions

View File

@ -3,10 +3,15 @@ language: python
python:
- 2.6
- 2.7
- 3.2
- 3.3
- pypy
install: python setup.py install
script: nosetests
install:
- pip install -r requirements_dev.txt --use-mirrors
- python setup.py install
script: py.test tests/
branches:
only:

View File

@ -1,15 +1,13 @@
build:
virtualenv --no-site-packages .
bin/python setup.py develop
bin/pip install coverage
bin/pip install nose
bin/pip install -r requirements_dev.txt
test:
bin/nosetests
bin/python setup.py test
coverage:
bin/nosetests --with-coverage --cover-html --cover-html-dir=html \
--cover-package=gunicorn
bin/python setup.py test --cov
clean:
@rm -rf .Python bin lib include man build html

View File

@ -153,6 +153,28 @@ And then as per usual::
$ cd yourpasteproject
$ paster serve development.ini workers=2
**Gunicorn paster from script**
If you'd like to run Gunicorn paster from a script instead of the command line (for example: a runapp.py to start a Pyramid app),
you can use this example to help get you started::
import os
import multiprocessing
from paste.deploy import appconfig, loadapp
from gunicorn.app.pasterapp import paste_server
if __name__ == "__main__":
iniFile = 'config:development.ini'
port = int(os.environ.get("PORT", 5000))
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'gevent'
app = loadapp(iniFile, relative_to='.')
paste_server(app, host='0.0.0.0', port=port, workers=workers, worker_class=worker_class)
LICENSE
-------

4
THANKS
View File

@ -45,3 +45,7 @@ Caleb Brown <git@calebbrown.id.au>
Marc Abramowitz <marc@marc-abramowitz.com>
Vangelis Koukis <vkoukis@grnet.gr>
Prateek Singh Paudel <pratykschingh@gmail.com>
Andrew Gorcester <andrewsg@terra.(none)>
Kenneth Reitz <me@kennethreitz.com>
Eric Shull <eric@elevenbasetwo.com>
Christos Stavrakakis <cstavr@grnet.gr>

View File

@ -60,6 +60,15 @@ a:hover {
border-bottom: 1px solid #2A8729;
}
.latest {
width: 150px;
top: 0;
display: block;
float: right;
font-weight: bold;
}
.logo-div {
width: 1000px;
margin: 0 auto;
@ -283,12 +292,14 @@ a:hover {
margin: 0 0 9px;
}
.tab-box a {
.tab-box a,
.latest a {
color: #3F3F27;
text-decoration: underline;
}
.tab-box a:hover {
.tab-box a:hover,
.latest a:hover {
color: #1D692D;
}
@ -389,4 +400,4 @@ pre {
.footer-wp a:hover {
color: #1D692D;
}
}

View File

@ -6,10 +6,19 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<link rel="stylesheet" type="text/css" href="css/style.css" />
<link rel="shortcut icon" href="images/favicon.png" type="image/x-icon">
<link rel="alternate"
href="https://github.com/benoitc/gunicorn/commits/master.atom"
type="application/atom+xml" title="Gunicorn commits">
</head>
<body>
<div class="logo-wrapper">
<div class="logo-div">
<div class="latest">
Latest version: <strong><a
href="http://docs.gunicorn.org/en/latest/news.html#id1">0.15.0</a></strong>
</div>
<div class="logo"><img src="images/logo.jpg" ></div>
</div>
</div>

View File

@ -20,6 +20,17 @@ Once again, in order of least to most authoritative:
2. Configuration File
3. Command Line
.. note::
To check your configuration when using the command line or the
configuration file you can run the following command::
$ gunicorn --check-config
It will also allows you to know if your applican can be launched.
Framework Settings
==================

View File

@ -1,6 +1,18 @@
Changelog
=========
0.16.0 / develop
----------------
- **Added support for Python 3.2 & 3.3**
- Expose --pythonpath command to all gunicorn commands
- Honor $PORT environment variable, useful for deployement on heroku
- Removed support for Python 2.5
- Make sure we reopen teh logs on the console
- Fix django settings module detection from path
- Reverted timeout for client socket. Fix issue on blocking issues.
- Fixed gevent worker
0.15.0 / 2012-10-18
-------------------

View File

@ -482,6 +482,9 @@ The Python path to a Django settings module.
e.g. 'myproject.settings.main'. If this isn't provided, the
DJANGO_SETTINGS_MODULE environment variable will be used.
Server Mechanics
----------------
pythonpath
~~~~~~~~~~

View File

@ -12,18 +12,18 @@ class MsgForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
f = forms.FileField()
def home(request):
from django.conf import settings
print settings.SOME_VALUE
print(settings.SOME_VALUE)
subject = None
message = None
size = 0
print request.META
print(request.META)
if request.POST:
form = MsgForm(request.POST, request.FILES)
print request.FILES
print(request.FILES)
if form.is_valid():
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
@ -31,29 +31,29 @@ def home(request):
size = int(os.fstat(f.fileno())[6])
else:
form = MsgForm()
return render_to_response('home.html', {
'form': form,
'subject': subject,
'message': message,
'size': size
}, RequestContext(request))
def acsv(request):
rows = [
{'a': 1, 'b': 2},
{'a': 3, 'b': 3}
]
response = HttpResponse(mimetype='text/csv')
response['Content-Disposition'] = 'attachment; filename=report.csv'
writer = csv.writer(response)
writer.writerow(['a', 'b'])
for r in rows:
writer.writerow([r['a'], r['b']])
return response

View File

@ -4,7 +4,7 @@ import gevent
def child_process(queue):
while True:
print queue.get()
print(queue.get())
requests.get('http://requestb.in/15s95oz1')
class GunicornSubProcessTestMiddleware(object):

View File

@ -1,6 +1,7 @@
# Create your views here.
import csv
import io
import os
from django import forms
from django.http import HttpResponse
@ -16,22 +17,27 @@ class MsgForm(forms.Form):
def home(request):
from django.conf import settings
print settings.SOME_VALUE
print(settings.SOME_VALUE)
subject = None
message = None
size = 0
print request.META
print(request.META)
if request.POST:
form = MsgForm(request.POST, request.FILES)
print request.FILES
print(request.FILES)
if form.is_valid():
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
f = request.FILES['f']
if not hasattr(f, "fileno"):
size = len(f.read())
else:
size = int(os.fstat(f.fileno())[6])
try:
size = int(os.fstat(f.fileno())[6])
except io.UnsupportedOperation:
size = len(f.read())
else:
form = MsgForm()

View File

@ -1,13 +1,14 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import sys
import time
class TestIter(object):
def __iter__(self):
lines = ['line 1\n', 'line 2\n']
for line in lines:
@ -16,12 +17,13 @@ class TestIter(object):
def app(environ, start_response):
"""Application which cooperatively pauses 20 seconds (needed to surpass normal timeouts) before responding"""
data = 'Hello, World!\n'
data = b'Hello, World!\n'
status = '200 OK'
response_headers = [
('Content-type','text/plain'),
('Transfer-Encoding', "chunked"),
]
print 'request received'
sys.stdout.write('request received')
sys.stdout.flush()
start_response(status, response_headers)
return TestIter()

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
#
# Run this application with:
@ -17,7 +17,7 @@
try:
from routes import Mapper
except:
print "This example requires Routes to be installed"
print("This example requires Routes to be installed")
# Obviously you'd import your app callables
# from different places...
@ -38,7 +38,7 @@ class Application(object):
return match[0]['app'](environ, start_response)
def error404(self, environ, start_response):
html = """\
html = b"""\
<html>
<head>
<title>404 - Not Found</title>
@ -55,4 +55,4 @@ class Application(object):
start_response('404 Not Found', headers)
return [html]
app = Application()
app = Application()

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import re
@ -20,15 +20,15 @@ class SubDomainApp:
return app(environ, start_response)
else:
start_response("404 Not Found", [])
return [""]
return [b""]
def hello(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return ["Hello, world\n"]
return [b"Hello, world\n"]
def bye(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return ["Goodbye!\n"]
return [b"Goodbye!\n"]
app = SubDomainApp([
("localhost", hello),

View File

@ -1,20 +1,21 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import sys
import time
def app(environ, start_response):
"""Application which cooperatively pauses 10 seconds before responding"""
data = 'Hello, World!\n'
data = b'Hello, World!\n'
status = '200 OK'
response_headers = [
('Content-type','text/plain'),
('Content-Length', str(len(data))) ]
print 'request received, pausing 10 seconds'
sys.stdout.write('request received, pausing 10 seconds')
sys.stdout.flush()
time.sleep(10)
start_response(status, response_headers)
return iter([data])
return iter([data])

View File

@ -12,14 +12,14 @@ from gunicorn import __version__
#@validator
def app(environ, start_response):
"""Simplest possible application object"""
data = 'Hello, World!\n'
data = b'Hello, World!\n'
status = '200 OK'
# print("print to stdout in test app")
# sys.stderr.write("stderr, print to stderr in test app\n")
response_headers = [
('Content-type','text/plain'),
('Content-Length', str(len(data))),
('X-Gunicorn-Version', __version__)
('X-Gunicorn-Version', __version__),
("Test", "test тест"),
]
start_response(status, response_headers)
return iter([data])

View File

@ -3,6 +3,6 @@
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
version_info = (0, 15, 0)
__version__ = ".".join(map(str, version_info))
version_info = (0, 16, 0)
__version__ = ".".join([str(v) for v in version_info])
SERVER_SOFTWARE = "gunicorn/%s" % __version__

View File

@ -3,17 +3,15 @@
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import errno
import os
import sys
import traceback
from gunicorn.glogging import Logger
from gunicorn import util
from gunicorn.arbiter import Arbiter
from gunicorn.config import Config
from gunicorn import debug
from gunicorn.six import execfile_
class Application(object):
"""\
@ -31,7 +29,7 @@ class Application(object):
def do_load_config(self):
try:
self.load_config()
except Exception, e:
except Exception as e:
sys.stderr.write("\nError: %s\n" % str(e))
sys.stderr.flush()
sys.exit(1)
@ -62,9 +60,9 @@ class Application(object):
"__package__": None
}
try:
execfile(opts.config, cfg, cfg)
execfile_(opts.config, cfg, cfg)
except Exception:
print "Failed to read config file: %s" % opts.config
print("Failed to read config file: %s" % opts.config)
traceback.print_exc()
sys.exit(1)
@ -104,15 +102,12 @@ class Application(object):
def run(self):
if self.cfg.check_config:
try:
self.load()
except:
sys.stderr.write("\nError while loading the application:\n\n")
traceback.print_exc()
sys.stderr.flush()
sys.exit(1)
sys.exit(0)
if self.cfg.spew:
@ -120,9 +115,17 @@ class Application(object):
if self.cfg.daemon:
util.daemonize()
# set python paths
if self.cfg.pythonpath and self.cfg.pythonpath is not None:
paths = self.cfg.pythonpath.split(",")
for path in paths:
pythonpath = os.path.abspath(self.cfg.pythonpath)
if pythonpath not in sys.path:
sys.path.insert(0, pythonpath)
try:
Arbiter(self).run()
except RuntimeError, e:
except RuntimeError as e:
sys.stderr.write("\nError: %s\n\n" % e)
sys.stderr.flush()
sys.exit(1)

View File

@ -10,10 +10,12 @@ import re
import sys
import time
try:
from cStringIO import StringIO
from io import StringIO
from imp import reload
except ImportError:
from StringIO import StringIO
from django.conf import settings
from django.core.management.validation import get_validation_errors
from django.utils import translation
@ -72,10 +74,10 @@ def reload_django_settings():
app_mod = util.import_module(app[:-2])
appdir = os.path.dirname(app_mod.__file__)
app_subdirs = os.listdir(appdir)
app_subdirs.sort()
name_pattern = re.compile(r'[a-zA-Z]\w*')
for d in app_subdirs:
if name_pattern.match(d) and os.path.isdir(os.path.join(appdir, d)):
for d in sorted(app_subdirs):
if (name_pattern.match(d) and
os.path.isdir(os.path.join(appdir, d))):
new_installed_apps.append('%s.%s' % (app[:-2], d))
else:
new_installed_apps.append(app)

View File

@ -44,12 +44,14 @@ def make_default_env(cfg):
os.environ['DJANGO_SETTINGS_MODULE'] = cfg.django_settings
if cfg.pythonpath and cfg.pythonpath is not None:
pythonpath = os.path.abspath(cfg.pythonpath)
if pythonpath not in sys.path:
sys.path.insert(0, pythonpath)
paths = cfg.pythonpath.split(",")
for path in paths:
pythonpath = os.path.abspath(cfg.pythonpath)
if pythonpath not in sys.path:
sys.path.insert(0, pythonpath)
try:
_ = os.environ['DJANGO_SETTINGS_MODULE']
os.environ['DJANGO_SETTINGS_MODULE']
except KeyError:
# not settings env set, try to build one.
project_path, settings_name = find_settings_module(os.getcwd())
@ -71,12 +73,15 @@ class DjangoApplication(Application):
def init(self, parser, opts, args):
if args:
if "." in args[0]:
if ("." in args[0] and not (os.path.isfile(args[0])
or os.path.isdir(args[0]))):
self.cfg.set("django_settings", args[0])
else:
# not settings env set, try to build one.
project_path, settings_name = find_settings_module(
os.path.abspath(args[0]))
if project_path not in sys.path:
sys.path.insert(0, project_path)
if not project_path:
raise RuntimeError("django project not found")

View File

@ -6,7 +6,11 @@
import os
import pkg_resources
import sys
import ConfigParser
try:
import configparser as ConfigParser
except ImportError:
import ConfigParser
from paste.deploy import loadapp, loadwsgi
SERVER = loadwsgi.SERVER
@ -118,7 +122,7 @@ class PasterServerApplication(PasterBaseApplication):
for k, v in cfg.items():
if k.lower() in self.cfg.settings and v is not None:
self.cfg.set(k.lower(), v)
except Exception, e:
except Exception as e:
sys.stderr.write("\nConfig error: %s\n" % str(e))
sys.stderr.flush()
sys.exit(1)

View File

@ -41,10 +41,8 @@ class Arbiter(object):
# I love dynamic languages
SIG_QUEUE = []
SIGNALS = map(
lambda x: getattr(signal, "SIG%s" % x),
"HUP QUIT INT TERM TTIN TTOU USR1 USR2 WINCH".split()
)
SIGNALS = [getattr(signal, "SIG%s" % x) \
for x in "HUP QUIT INT TERM TTIN TTOU USR1 USR2 WINCH".split()]
SIG_NAMES = dict(
(getattr(signal, name), name[3:].lower()) for name in dir(signal)
if name[:3] == "SIG" and name[3] != "_"
@ -99,7 +97,10 @@ class Arbiter(object):
if self.cfg.debug:
self.log.debug("Current configuration:")
for config, value in sorted(self.cfg.settings.iteritems()):
for config, value in sorted(self.cfg.settings.items(),
key=lambda setting: setting[1]):
self.log.debug(" %s: %s", config, value.value)
if self.cfg.preload_app:
@ -135,13 +136,20 @@ class Arbiter(object):
Initialize master signal handling. Most of the signals
are queued. Child signals only wake up the master.
"""
# close old PIPE
if self.PIPE:
map(os.close, self.PIPE)
[os.close(p) for p in self.PIPE]
# initialize the pipe
self.PIPE = pair = os.pipe()
map(util.set_non_blocking, pair)
map(util.close_on_exec, pair)
for p in pair:
util.set_non_blocking(p)
util.close_on_exec(p)
self.log.close_on_exec()
map(lambda s: signal.signal(s, self.signal), self.SIGNALS)
# intialiatze all signals
[signal.signal(s, self.signal) for s in self.SIGNALS]
signal.signal(signal.SIGCHLD, self.handle_chld)
def signal(self, sig, frame):
@ -181,7 +189,7 @@ class Arbiter(object):
self.halt()
except KeyboardInterrupt:
self.halt()
except HaltServer, inst:
except HaltServer as inst:
self.halt(reason=inst.reason, exit_status=inst.exit_status)
except SystemExit:
raise
@ -270,8 +278,8 @@ class Arbiter(object):
Wake up the arbiter by writing to the PIPE
"""
try:
os.write(self.PIPE[1], '.')
except IOError, e:
os.write(self.PIPE[1], b'.')
except IOError as e:
if e.errno not in [errno.EAGAIN, errno.EINTR]:
raise
@ -296,10 +304,10 @@ class Arbiter(object):
return
while os.read(self.PIPE[0], 1):
pass
except select.error, e:
if e[0] not in [errno.EAGAIN, errno.EINTR]:
except select.error as e:
if e.args[0] not in [errno.EAGAIN, errno.EINTR]:
raise
except OSError, e:
except OSError as e:
if e.errno not in [errno.EAGAIN, errno.EINTR]:
raise
except KeyboardInterrupt:
@ -423,7 +431,7 @@ class Arbiter(object):
if not worker:
continue
worker.tmp.close()
except OSError, e:
except OSError as e:
if e.errno == errno.ECHILD:
pass
@ -436,7 +444,7 @@ class Arbiter(object):
self.spawn_workers()
workers = self.WORKERS.items()
workers.sort(key=lambda w: w[1].age)
workers = sorted(workers, key=lambda w: w[1].age)
while len(workers) > self.num_workers:
(pid, _) = workers.pop(0)
self.kill_worker(pid, signal.SIGQUIT)
@ -504,7 +512,7 @@ class Arbiter(object):
"""
try:
os.kill(pid, sig)
except OSError, e:
except OSError as e:
if e.errno == errno.ESRCH:
try:
worker = self.WORKERS.pop(pid)

View File

@ -15,6 +15,7 @@ import types
from gunicorn import __version__
from gunicorn.errors import ConfigError
from gunicorn import util
from gunicorn.six import string_types, integer_types, bytes_to_str
KNOWN_SETTINGS = []
@ -61,10 +62,12 @@ class Config(object):
}
parser = optparse.OptionParser(**kwargs)
keys = self.settings.keys()
keys = list(self.settings)
def sorter(k):
return (self.settings[k].section, self.settings[k].order)
keys.sort(key=sorter)
keys = sorted(self.settings, key=self.settings.__getitem__)
for k in keys:
self.settings[k].add_option(parser)
return parser
@ -84,7 +87,7 @@ class Config(object):
@property
def address(self):
bind = self.settings['bind'].get()
return util.parse_address(util.to_bytestring(bind))
return util.parse_address(bytes_to_str(bind))
@property
def uid(self):
@ -134,8 +137,6 @@ class SettingMeta(type):
setattr(cls, "short", desc.splitlines()[0])
class Setting(object):
__metaclass__ = SettingMeta
name = None
value = None
section = None
@ -178,10 +179,17 @@ class Setting(object):
assert callable(self.validator), "Invalid validator: %s" % self.name
self.value = self.validator(val)
def __lt__(self, other):
return (self.section == other.section and
self.order < other.order)
__cmp__ = __lt__
Setting = SettingMeta('Setting', (Setting,), {})
def validate_bool(val):
if isinstance(val, types.BooleanType):
if isinstance(val, bool):
return val
if not isinstance(val, basestring):
if not isinstance(val, string_types):
raise TypeError("Invalid type for casting: %s" % val)
if val.lower().strip() == "true":
return True
@ -196,7 +204,7 @@ def validate_dict(val):
return val
def validate_pos_int(val):
if not isinstance(val, (types.IntType, types.LongType)):
if not isinstance(val, integer_types):
val = int(val, 0)
else:
# Booleans are ints!
@ -208,7 +216,7 @@ def validate_pos_int(val):
def validate_string(val):
if val is None:
return None
if not isinstance(val, basestring):
if not isinstance(val, string_types):
raise TypeError("Not a string: %s" % val)
return val.strip()
@ -229,7 +237,7 @@ def validate_class(val):
def validate_callable(arity):
def _validate_callable(val):
if isinstance(val, basestring):
if isinstance(val, string_types):
try:
mod_name, obj_name = val.rsplit(".", 1)
except ValueError:
@ -311,7 +319,12 @@ class Bind(Setting):
cli = ["-b", "--bind"]
meta = "ADDRESS"
validator = validate_string
default = "127.0.0.1:8000"
if 'PORT' in os.environ:
default = '0.0.0.0:{0}'.format(os.environ.get('PORT'))
else:
default = '127.0.0.1:8000'
desc = """\
The socket to bind.
@ -863,9 +876,9 @@ class DjangoSettings(Setting):
DJANGO_SETTINGS_MODULE environment variable will be used.
"""
class DjangoPythonPath(Setting):
class PythonPath(Setting):
name = "pythonpath"
section = "Django"
section = "Server Mechanics"
cli = ["--pythonpath"]
meta = "STRING"
validator = validate_string

View File

@ -43,7 +43,7 @@ class Spew(object):
line = 'Unknown code named [%s]. VM instruction #%d' % (
frame.f_code.co_name, frame.f_lasti)
if self.trace_names is None or name in self.trace_names:
print '%s:%s: %s' % (name, lineno, line.rstrip())
print('%s:%s: %s' % (name, lineno, line.rstrip()))
if not self.show_values:
return self
details = []
@ -54,7 +54,7 @@ class Spew(object):
if tok in frame.f_locals:
details.append('%s=%r' % (tok, frame.f_locals[tok]))
if details:
print "\t%s" % ' '.join(details)
print("\t%s" % ' '.join(details))
return self

View File

@ -4,7 +4,7 @@
# See the NOTICE for more information.
class HaltServer(Exception):
class HaltServer(BaseException):
def __init__(self, reason, exit_status=1):
self.reason = reason
self.exit_status = exit_status
@ -12,5 +12,5 @@ class HaltServer(Exception):
def __str__(self):
return "<HaltServer %r %d>" % (self.reason, self.exit_status)
class ConfigError(Exception):
class ConfigError(BaseException):
""" Exception raised on config error """

View File

@ -6,17 +6,15 @@
import datetime
import logging
logging.Logger.manager.emittedNoHandlerWarning = 1
from logging.config import fileConfig
import os
import sys
import traceback
import threading
try:
from logging.config import fileConfig
except ImportError:
from gunicorn.logging_config import fileConfig
from gunicorn import util
from gunicorn.six import string_types
CONFIG_DEFAULTS = dict(
version = 1,
@ -76,6 +74,16 @@ class LazyWriter(object):
self.lock.release()
return self.fileobj
def close(self):
if self.fileobj:
self.lock.acquire()
try:
if self.fileobj:
self.fileobj.close()
self.fileobj = None
finally:
self.lock.release()
def write(self, text):
fileobj = self.open()
fileobj.write(text)
@ -89,6 +97,9 @@ class LazyWriter(object):
def flush(self):
self.open().flush()
def isatty(self):
return bool(self.fileobj and self.fileobj.isatty())
class SafeAtoms(dict):
def __init__(self, atoms):
@ -179,7 +190,7 @@ class Logger(object):
self.error_log.exception(msg, *args)
def log(self, lvl, msg, *args, **kwargs):
if isinstance(lvl, basestring):
if isinstance(lvl, string_types):
lvl = self.LOG_LEVELS.get(lvl.lower(), logging.INFO)
self.error_log.log(lvl, msg, *args, **kwargs)
@ -238,6 +249,10 @@ class Logger(object):
def reopen_files(self):
if self.cfg.errorlog != "-":
# Close stderr & stdout if they are redirected to error log file
sys.stderr.close()
sys.stdout.close()
for log in loggers():
for handler in log.handlers:
if isinstance(handler, logging.FileHandler):

View File

@ -5,55 +5,51 @@
import sys
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from gunicorn.http.errors import NoMoreData, ChunkMissingTerminator, \
InvalidChunkSize
from gunicorn.http.errors import (NoMoreData, ChunkMissingTerminator,
InvalidChunkSize)
from gunicorn import six
class ChunkedReader(object):
def __init__(self, req, unreader):
self.req = req
self.parser = self.parse_chunked(unreader)
self.buf = StringIO()
self.buf = six.BytesIO()
def read(self, size):
if not isinstance(size, (int, long)):
if not isinstance(size, six.integer_types):
raise TypeError("size must be an integral type")
if size <= 0:
raise ValueError("Size must be positive.")
if size == 0:
return ""
return b""
if self.parser:
while self.buf.tell() < size:
try:
self.buf.write(self.parser.next())
self.buf.write(six.next(self.parser))
except StopIteration:
self.parser = None
break
data = self.buf.getvalue()
ret, rest = data[:size], data[size:]
self.buf.truncate(0)
self.buf = six.BytesIO()
self.buf.write(rest)
return ret
def parse_trailers(self, unreader, data):
buf = StringIO()
buf = six.BytesIO()
buf.write(data)
idx = buf.getvalue().find("\r\n\r\n")
done = buf.getvalue()[:2] == "\r\n"
idx = buf.getvalue().find(b"\r\n\r\n")
done = buf.getvalue()[:2] == b"\r\n"
while idx < 0 and not done:
self.get_data(unreader, buf)
idx = buf.getvalue().find("\r\n\r\n")
done = buf.getvalue()[:2] == "\r\n"
idx = buf.getvalue().find(b"\r\n\r\n")
done = buf.getvalue()[:2] == b"\r\n"
if done:
unreader.unread(buf.getvalue()[2:])
return ""
return b""
self.req.trailers = self.req.parse_headers(buf.getvalue()[:idx])
unreader.unread(buf.getvalue()[idx+4:])
@ -71,24 +67,24 @@ class ChunkedReader(object):
rest = rest[size:]
while len(rest) < 2:
rest += unreader.read()
if rest[:2] != '\r\n':
if rest[:2] != b'\r\n':
raise ChunkMissingTerminator(rest[:2])
(size, rest) = self.parse_chunk_size(unreader, data=rest[2:])
def parse_chunk_size(self, unreader, data=None):
buf = StringIO()
buf = six.BytesIO()
if data is not None:
buf.write(data)
idx = buf.getvalue().find("\r\n")
idx = buf.getvalue().find(b"\r\n")
while idx < 0:
self.get_data(unreader, buf)
idx = buf.getvalue().find("\r\n")
idx = buf.getvalue().find(b"\r\n")
data = buf.getvalue()
line, rest_chunk = data[:idx], data[idx+2:]
chunk_size = line.split(";", 1)[0].strip()
chunk_size = line.split(b";", 1)[0].strip()
try:
chunk_size = int(chunk_size, 16)
except ValueError:
@ -114,17 +110,17 @@ class LengthReader(object):
self.length = length
def read(self, size):
if not isinstance(size, (int, long)):
if not isinstance(size, six.integer_types):
raise TypeError("size must be an integral type")
size = min(self.length, size)
if size < 0:
raise ValueError("Size must be positive.")
if size == 0:
return ""
return b""
buf = StringIO()
buf = six.BytesIO()
data = self.unreader.read()
while data:
buf.write(data)
@ -141,21 +137,21 @@ class LengthReader(object):
class EOFReader(object):
def __init__(self, unreader):
self.unreader = unreader
self.buf = StringIO()
self.buf = six.BytesIO()
self.finished = False
def read(self, size):
if not isinstance(size, (int, long)):
if not isinstance(size, six.integer_types):
raise TypeError("size must be an integral type")
if size < 0:
raise ValueError("Size must be positive.")
if size == 0:
return ""
return b""
if self.finished:
data = self.buf.getvalue()
ret, rest = data[:size], data[size:]
self.buf.truncate(0)
self.buf = six.BytesIO()
self.buf.write(rest)
return ret
@ -171,42 +167,43 @@ class EOFReader(object):
data = self.buf.getvalue()
ret, rest = data[:size], data[size:]
self.buf.truncate(0)
self.buf = six.BytesIO()
self.buf.write(rest)
return ret
class Body(object):
def __init__(self, reader):
self.reader = reader
self.buf = StringIO()
self.buf = six.BytesIO()
def __iter__(self):
return self
def next(self):
def __next__(self):
ret = self.readline()
if not ret:
raise StopIteration()
return ret
next = __next__
def getsize(self, size):
if size is None:
return sys.maxint
elif not isinstance(size, (int, long)):
return six.MAXSIZE
elif not isinstance(size, six.integer_types):
raise TypeError("size must be an integral type")
elif size < 0:
return sys.maxint
return six.MAXSIZE
return size
def read(self, size=None):
size = self.getsize(size)
if size == 0:
return ""
return b""
if size < self.buf.tell():
data = self.buf.getvalue()
ret, rest = data[:size], data[size:]
self.buf.truncate(0)
self.buf = six.BytesIO()
self.buf.write(rest)
return ret
@ -218,23 +215,23 @@ class Body(object):
data = self.buf.getvalue()
ret, rest = data[:size], data[size:]
self.buf.truncate(0)
self.buf = six.BytesIO()
self.buf.write(rest)
return ret
def readline(self, size=None):
size = self.getsize(size)
if size == 0:
return ""
return b""
line = self.buf.getvalue()
self.buf.truncate(0)
self.buf = six.BytesIO()
if len(line) < size:
line += self.reader.read(size - len(line))
extra_buf_data = line[size:]
line = line[:size]
idx = line.find("\n")
idx = line.find(b"\n")
if idx >= 0:
ret = line[:idx+1]
self.buf.write(line[idx+1:])
@ -247,12 +244,11 @@ class Body(object):
ret = []
data = self.read()
while len(data):
pos = data.find("\n")
pos = data.find(b"\n")
if pos < 0:
ret.append(data)
data = ""
data = b""
else:
line, data = data[:pos+1], data[pos+1:]
ret.append(line)
return ret

View File

@ -9,6 +9,7 @@ class ParseException(Exception):
class NoMoreData(IOError):
def __init__(self, buf=None):
self.buf = buf
def __str__(self):
return "No more data after: %r" % self.buf

View File

@ -4,21 +4,16 @@
# See the NOTICE for more information.
import re
import urlparse
import socket
from errno import ENOTCONN
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from gunicorn.http.unreader import SocketUnreader
from gunicorn.http.body import ChunkedReader, LengthReader, EOFReader, Body
from gunicorn.http.errors import InvalidHeader, InvalidHeaderName, NoMoreData, \
InvalidRequestLine, InvalidRequestMethod, InvalidHTTPVersion, \
LimitRequestLine, LimitRequestHeaders
from gunicorn.http.errors import InvalidProxyLine, ForbiddenProxyRequest
from gunicorn.six import BytesIO, urlsplit, bytes_to_str
MAX_REQUEST_LINE = 8190
MAX_HEADERS = 32768
@ -61,7 +56,7 @@ class Message(object):
headers = []
# Split lines on \r\n keeping the \r\n on each line
lines = [line + "\r\n" for line in data.split("\r\n")]
lines = [bytes_to_str(line) + "\r\n" for line in data.split(b"\r\n")]
# Parse headers into key/value pairs paying attention
# to continuation lines.
@ -153,7 +148,6 @@ class Request(Message):
self.req_number = req_number
self.proxy_protocol_info = None
super(Request, self).__init__(cfg, unreader)
@ -166,31 +160,31 @@ class Request(Message):
buf.write(data)
def parse(self, unreader):
buf = StringIO()
buf = BytesIO()
self.get_data(unreader, buf, stop=True)
# get request line
line, rbuf = self.read_line(unreader, buf, self.limit_request_line)
# proxy protocol
if self.proxy_protocol(line):
if self.proxy_protocol(bytes_to_str(line)):
# get next request line
buf = StringIO()
buf = BytesIO()
buf.write(rbuf)
line, rbuf = self.read_line(unreader, buf, self.limit_request_line)
self.parse_request_line(line)
buf = StringIO()
self.parse_request_line(bytes_to_str(line))
buf = BytesIO()
buf.write(rbuf)
# Headers
data = buf.getvalue()
idx = data.find("\r\n\r\n")
idx = data.find(b"\r\n\r\n")
done = data[:2] == "\r\n"
done = data[:2] == b"\r\n"
while True:
idx = data.find("\r\n\r\n")
done = data[:2] == "\r\n"
idx = data.find(b"\r\n\r\n")
done = data[:2] == b"\r\n"
if idx < 0 and not done:
self.get_data(unreader, buf)
@ -202,19 +196,19 @@ class Request(Message):
if done:
self.unreader.unread(data[2:])
return ""
return b""
self.headers = self.parse_headers(data[:idx])
ret = data[idx+4:]
buf = StringIO()
buf = BytesIO()
return ret
def read_line(self, unreader, buf, limit=0):
data = buf.getvalue()
while True:
idx = data.find("\r\n")
idx = data.find(b"\r\n")
if idx >= 0:
# check if the request line is too large
if idx > limit > 0:
@ -256,7 +250,7 @@ class Request(Message):
try:
remote_host = self.unreader.sock.getpeername()[0]
except socket.error as e:
if e[0] == ENOTCONN:
if e.args[0] == ENOTCONN:
raise ForbiddenProxyRequest("UNKNOW")
raise
if remote_host not in self.cfg.proxy_allow_ips:
@ -328,7 +322,7 @@ class Request(Message):
else:
self.uri = bits[1]
parts = urlparse.urlsplit(self.uri)
parts = urlsplit(self.uri)
self.path = parts.path or ""
self.query = parts.query or ""
self.fragment = parts.fragment or ""

View File

@ -22,7 +22,7 @@ class Parser(object):
def __iter__(self):
return self
def next(self):
def __next__(self):
# Stop if HTTP dictates a stop.
if self.mesg and self.mesg.should_close():
raise StopIteration()
@ -33,6 +33,7 @@ class Parser(object):
while data:
data = self.mesg.body.read(8192)
# Parse the next request
self.req_count += 1
self.mesg = self.mesg_class(self.cfg, self.unreader, self.req_count)
@ -40,6 +41,8 @@ class Parser(object):
raise StopIteration()
return self.mesg
next = __next__
class RequestParser(Parser):
def __init__(self, *args, **kwargs):
super(RequestParser, self).__init__(Request, *args, **kwargs)

View File

@ -5,47 +5,47 @@
import os
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from gunicorn import six
# Classes that can undo reading data from
# a given type of data source.
class Unreader(object):
def __init__(self):
self.buf = StringIO()
self.buf = six.BytesIO()
def chunk(self):
raise NotImplementedError()
def read(self, size=None):
if size is not None and not isinstance(size, (int, long)):
if size is not None and not isinstance(size, six.integer_types):
raise TypeError("size parameter must be an int or long.")
if size == 0:
return ""
if size < 0:
size = None
if size is not None:
if size == 0:
return b""
if size < 0:
size = None
self.buf.seek(0, os.SEEK_END)
if size is None and self.buf.tell():
ret = self.buf.getvalue()
self.buf.truncate(0)
self.buf = six.BytesIO()
return ret
if size is None:
return self.chunk()
d = self.chunk()
return d
while self.buf.tell() < size:
chunk = self.chunk()
if not len(chunk):
ret = self.buf.getvalue()
self.buf.truncate(0)
self.buf = six.BytesIO()
return ret
self.buf.write(chunk)
data = self.buf.getvalue()
self.buf.truncate(0)
self.buf = six.BytesIO()
self.buf.write(data[size:])
return data[:size]
@ -69,9 +69,9 @@ class IterUnreader(Unreader):
def chunk(self):
if not self.iter:
return ""
return b""
try:
return self.iter.next()
return six.next(self.iter)
except StopIteration:
self.iter = None
return ""
return b""

View File

@ -7,8 +7,9 @@ import logging
import os
import re
import sys
from urllib import unquote
from gunicorn.six import (unquote, string_types, binary_type, reraise,
text_type)
from gunicorn import SERVER_SOFTWARE
import gunicorn.util as util
@ -54,7 +55,7 @@ def default_environ(req, sock, cfg):
"REQUEST_METHOD": req.method,
"QUERY_STRING": req.query,
"RAW_URI": req.uri,
"SERVER_PROTOCOL": "HTTP/%s" % ".".join(map(str, req.version))
"SERVER_PROTOCOL": "HTTP/%s" % ".".join([str(v) for v in req.version])
}
def proxy_environ(req):
@ -118,7 +119,7 @@ def create(req, sock, client, server, cfg):
environ['wsgi.url_scheme'] = url_scheme
if isinstance(forward, basestring):
if isinstance(forward, string_types):
# we only took the last one
# http://en.wikipedia.org/wiki/X-Forwarded-For
if forward.find(",") >= 0:
@ -145,7 +146,7 @@ def create(req, sock, client, server, cfg):
environ['REMOTE_ADDR'] = remote[0]
environ['REMOTE_PORT'] = str(remote[1])
if isinstance(server, basestring):
if isinstance(server, string_types):
server = server.split(":")
if len(server) == 1:
if url_scheme == "http":
@ -196,7 +197,7 @@ class Response(object):
if exc_info:
try:
if self.status and self.headers_sent:
raise exc_info[0], exc_info[1], exc_info[2]
reraise(exc_info[0], exc_info[1], exc_info[2])
finally:
exc_info = None
elif self.status is not None:
@ -209,7 +210,9 @@ class Response(object):
def process_headers(self, headers):
for name, value in headers:
assert isinstance(name, basestring), "%r is not a string" % name
assert isinstance(name, string_types), "%r is not a string" % name
value = str(value).strip()
lname = name.lower().strip()
if lname == "content-length":
self.response_length = int(value)
@ -220,11 +223,11 @@ class Response(object):
self.upgrade = True
elif lname == "upgrade":
if value.lower().strip() == "websocket":
self.headers.append((name.strip(), str(value).strip()))
self.headers.append((name.strip(), value))
# ignore hopbyhop headers
continue
self.headers.append((name.strip(), str(value).strip()))
self.headers.append((name.strip(), value))
def is_chunked(self):
@ -265,13 +268,19 @@ class Response(object):
if self.headers_sent:
return
tosend = self.default_headers()
tosend.extend(["%s: %s\r\n" % (n, v) for n, v in self.headers])
util.write(self.sock, "%s\r\n" % "".join(tosend))
tosend.extend(["%s: %s\r\n" % (k,v) for k, v in self.headers])
header_str = "%s\r\n" % "".join(tosend)
util.write(self.sock, util.to_bytestring(header_str))
self.headers_sent = True
def write(self, arg):
self.send_headers()
assert isinstance(arg, basestring), "%r is not a string." % arg
if isinstance(arg, text_type):
arg = arg.encode('utf-8')
assert isinstance(arg, binary_type), "%r is not a byte." % arg
arglen = len(arg)
tosend = arglen
@ -329,12 +338,13 @@ class Response(object):
self.send_headers()
if self.is_chunked():
self.sock.sendall("%X\r\n" % nbytes)
chunk_size = "%X\r\n" % nbytes
self.sock.sendall(chunk_size.encode('utf-8'))
self.sendfile_all(fileno, self.sock.fileno(), fo_offset, nbytes)
if self.is_chunked():
self.sock.sendall("\r\n")
self.sock.sendall(b"\r\n")
os.lseek(fileno, fd_offset, os.SEEK_SET)
else:
@ -345,4 +355,4 @@ class Response(object):
if not self.headers_sent:
self.send_headers()
if self.chunked:
util.write_chunk(self.sock, "")
util.write_chunk(self.sock, b"")

View File

@ -1,346 +0,0 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
#
# Copyright 2001-2005 by Vinay Sajip. All Rights Reserved.
#
"""
Configuration functions for the logging package for Python. The core package
is based on PEP 282 and comments thereto in comp.lang.python, and influenced
by Apache's log4j system.
Should work under Python versions >= 1.5.2, except that source line
information is not available unless 'sys._getframe()' is.
Copyright (C) 2001-2004 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging' and log away!
"""
import sys, logging, logging.handlers, string, socket, struct, os, traceback, types
try:
import thread
import threading
except ImportError:
thread = None
from SocketServer import ThreadingTCPServer, StreamRequestHandler
DEFAULT_LOGGING_CONFIG_PORT = 9030
if sys.platform == "win32":
RESET_ERROR = 10054 #WSAECONNRESET
else:
RESET_ERROR = 104 #ECONNRESET
#
# The following code implements a socket listener for on-the-fly
# reconfiguration of logging.
#
# _listener holds the server object doing the listening
_listener = None
def fileConfig(fname, defaults=None):
"""
Read the logging configuration from a ConfigParser-format file.
This can be called several times from an application, allowing an end user
the ability to select from various pre-canned configurations (if the
developer provides a mechanism to present the choices and load the chosen
configuration).
In versions of ConfigParser which have the readfp method [typically
shipped in 2.x versions of Python], you can pass in a file-like object
rather than a filename, in which case the file-like object will be read
using readfp.
"""
import ConfigParser
cp = ConfigParser.ConfigParser(defaults)
if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
cp.readfp(fname)
else:
cp.read(fname)
formatters = _create_formatters(cp)
# critical section
logging._acquireLock()
try:
logging._handlers.clear()
if hasattr(logging, '_handlerList'):
del logging._handlerList[:]
# Handlers add themselves to logging._handlers
handlers = _install_handlers(cp, formatters)
_install_loggers(cp, handlers)
finally:
logging._releaseLock()
def _resolve(name):
"""Resolve a dotted name to a global object."""
name = string.split(name, '.')
used = name.pop(0)
found = __import__(used)
for n in name:
used = used + '.' + n
try:
found = getattr(found, n)
except AttributeError:
__import__(used)
found = getattr(found, n)
return found
def _create_formatters(cp):
"""Create and return formatters"""
flist = cp.get("formatters", "keys")
if not len(flist):
return {}
flist = string.split(flist, ",")
formatters = {}
for form in flist:
form = string.strip(form)
sectname = "formatter_%s" % form
opts = cp.options(sectname)
if "format" in opts:
fs = cp.get(sectname, "format", 1)
else:
fs = None
if "datefmt" in opts:
dfs = cp.get(sectname, "datefmt", 1)
else:
dfs = None
c = logging.Formatter
if "class" in opts:
class_name = cp.get(sectname, "class")
if class_name:
c = _resolve(class_name)
f = c(fs, dfs)
formatters[form] = f
return formatters
def _install_handlers(cp, formatters):
"""Install and return handlers"""
hlist = cp.get("handlers", "keys")
if not len(hlist):
return {}
hlist = string.split(hlist, ",")
handlers = {}
fixups = [] #for inter-handler references
for hand in hlist:
hand = string.strip(hand)
sectname = "handler_%s" % hand
klass = cp.get(sectname, "class")
opts = cp.options(sectname)
if "formatter" in opts:
fmt = cp.get(sectname, "formatter")
else:
fmt = ""
try:
klass = eval(klass, vars(logging))
except (AttributeError, NameError):
klass = _resolve(klass)
args = cp.get(sectname, "args")
args = eval(args, vars(logging))
h = apply(klass, args)
if "level" in opts:
level = cp.get(sectname, "level")
h.setLevel(logging._levelNames[level])
if len(fmt):
h.setFormatter(formatters[fmt])
#temporary hack for FileHandler and MemoryHandler.
if klass == logging.handlers.MemoryHandler:
if "target" in opts:
target = cp.get(sectname,"target")
else:
target = ""
if len(target): #the target handler may not be loaded yet, so keep for later...
fixups.append((h, target))
handlers[hand] = h
#now all handlers are loaded, fixup inter-handler references...
for h, t in fixups:
h.setTarget(handlers[t])
return handlers
def _install_loggers(cp, handlers):
"""Create and install loggers"""
# configure the root first
llist = cp.get("loggers", "keys")
llist = string.split(llist, ",")
llist = map(lambda x: string.strip(x), llist)
llist.remove("root")
sectname = "logger_root"
root = logging.root
log = root
opts = cp.options(sectname)
if "level" in opts:
level = cp.get(sectname, "level")
log.setLevel(logging._levelNames[level])
for h in root.handlers[:]:
root.removeHandler(h)
hlist = cp.get(sectname, "handlers")
if len(hlist):
hlist = string.split(hlist, ",")
for hand in hlist:
log.addHandler(handlers[string.strip(hand)])
#and now the others...
#we don't want to lose the existing loggers,
#since other threads may have pointers to them.
#existing is set to contain all existing loggers,
#and as we go through the new configuration we
#remove any which are configured. At the end,
#what's left in existing is the set of loggers
#which were in the previous configuration but
#which are not in the new configuration.
existing = root.manager.loggerDict.keys()
#now set up the new ones...
for log in llist:
sectname = "logger_%s" % log
qn = cp.get(sectname, "qualname")
opts = cp.options(sectname)
if "propagate" in opts:
propagate = cp.getint(sectname, "propagate")
else:
propagate = 1
logger = logging.getLogger(qn)
if qn in existing:
existing.remove(qn)
if "level" in opts:
level = cp.get(sectname, "level")
logger.setLevel(logging._levelNames[level])
for h in logger.handlers[:]:
logger.removeHandler(h)
logger.propagate = propagate
logger.disabled = 0
hlist = cp.get(sectname, "handlers")
if len(hlist):
hlist = string.split(hlist, ",")
for hand in hlist:
logger.addHandler(handlers[string.strip(hand)])
#Disable any old loggers. There's no point deleting
#them as other threads may continue to hold references
#and by disabling them, you stop them doing any logging.
for log in existing:
root.manager.loggerDict[log].disabled = 1
def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
"""
Start up a socket server on the specified port, and listen for new
configurations.
These will be sent as a file suitable for processing by fileConfig().
Returns a Thread object on which you can call start() to start the server,
and which you can join() when appropriate. To stop the server, call
stopListening().
"""
if not thread:
raise NotImplementedError, "listen() needs threading to work"
class ConfigStreamHandler(StreamRequestHandler):
"""
Handler for a logging configuration request.
It expects a completely new logging configuration and uses fileConfig
to install it.
"""
def handle(self):
"""
Handle a request.
Each request is expected to be a 4-byte length, packed using
struct.pack(">L", n), followed by the config file.
Uses fileConfig() to do the grunt work.
"""
import tempfile
try:
conn = self.connection
chunk = conn.recv(4)
if len(chunk) == 4:
slen = struct.unpack(">L", chunk)[0]
chunk = self.connection.recv(slen)
while len(chunk) < slen:
chunk = chunk + conn.recv(slen - len(chunk))
#Apply new configuration. We'd like to be able to
#create a StringIO and pass that in, but unfortunately
#1.5.2 ConfigParser does not support reading file
#objects, only actual files. So we create a temporary
#file and remove it later.
file = tempfile.mktemp(".ini")
f = open(file, "w")
f.write(chunk)
f.close()
try:
fileConfig(file)
except (KeyboardInterrupt, SystemExit):
raise
except:
traceback.print_exc()
os.remove(file)
except socket.error, e:
if type(e.args) != types.TupleType:
raise
else:
errcode = e.args[0]
if errcode != RESET_ERROR:
raise
class ConfigSocketReceiver(ThreadingTCPServer):
"""
A simple TCP socket-based logging config receiver.
"""
allow_reuse_address = 1
def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
handler=None):
ThreadingTCPServer.__init__(self, (host, port), handler)
logging._acquireLock()
self.abort = 0
logging._releaseLock()
self.timeout = 1
def serve_until_stopped(self):
import select
abort = 0
while not abort:
rd, wr, ex = select.select([self.socket.fileno()],
[], [],
self.timeout)
if rd:
self.handle_request()
logging._acquireLock()
abort = self.abort
logging._releaseLock()
def serve(rcvr, hdlr, port):
server = rcvr(port=port, handler=hdlr)
global _listener
logging._acquireLock()
_listener = server
logging._releaseLock()
server.serve_until_stopped()
return threading.Thread(target=serve,
args=(ConfigSocketReceiver,
ConfigStreamHandler, port))
def stopListening():
"""
Stop the listening server which was created with a call to listen().
"""
global _listener
if _listener:
logging._acquireLock()
_listener.abort = 1
_listener = None
logging._releaseLock()

View File

@ -76,11 +76,11 @@ class Pidfile(object):
try:
os.kill(wpid, 0)
return wpid
except OSError, e:
if e[0] == errno.ESRCH:
except OSError as e:
if e.args[0] == errno.ESRCH:
return
raise
except IOError, e:
if e[0] == errno.ENOENT:
except IOError as e:
if e.args[0] == errno.ENOENT:
return
raise

399
gunicorn/six.py Normal file
View File

@ -0,0 +1,399 @@
"""Utilities for writing code that runs on Python 2 and 3"""
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.2.0"
# True if we are running on Python 3.
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform == "java":
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result)
# This is a bit ugly, but it avoids running this again.
delattr(tp, self.name)
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _MovedItems(types.ModuleType):
"""Lazy loading of moved objects"""
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("copyreg", "copy_reg"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
del attr
moves = sys.modules["gunicorn.six.moves"] = _MovedItems("moves")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_iterkeys = "keys"
_itervalues = "values"
_iteritems = "items"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_code = "func_code"
_func_defaults = "func_defaults"
_iterkeys = "iterkeys"
_itervalues = "itervalues"
_iteritems = "iteritems"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
if PY3:
def get_unbound_function(unbound):
return unbound
Iterator = object
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
else:
def get_unbound_function(unbound):
return unbound.im_func
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
def iterkeys(d):
"""Return an iterator over the keys of a dictionary."""
return iter(getattr(d, _iterkeys)())
def itervalues(d):
"""Return an iterator over the values of a dictionary."""
return iter(getattr(d, _itervalues)())
def iteritems(d):
"""Return an iterator over the (key, value) pairs of a dictionary."""
return iter(getattr(d, _iteritems)())
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s):
return s
def u(s):
return unicode(s, "unicode_escape")
int2byte = chr
import StringIO
StringIO = BytesIO = StringIO.StringIO
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
if PY3:
import builtins
exec_ = getattr(builtins, "exec")
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
print_ = getattr(builtins, "print")
def execfile_(fname, *args):
return exec_(compile(open(fname, 'rb').read(), fname, 'exec'), *args)
del builtins
else:
def exec_(code, globs=None, locs=None):
"""Execute code in a namespace."""
if globs is None:
frame = sys._getframe(1)
globs = frame.f_globals
if locs is None:
locs = frame.f_locals
del frame
elif locs is None:
locs = globs
exec("""exec code in globs, locs""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
execfile_ = execfile
def print_(*args, **kwargs):
"""The new-style print function."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
_add_doc(reraise, """Reraise an exception.""")
def with_metaclass(meta, base=object):
"""Create a base class with a metaclass."""
return meta("NewBase", (base,), {})
# specific to gunicorn
if PY3:
def bytes_to_str(b):
if isinstance(b, text_type):
return b
return str(b, 'latin1')
import urllib.parse
unquote = urllib.parse.unquote
urlsplit = urllib.parse.urlsplit
urlparse = urllib.parse.urlparse
else:
def bytes_to_str(s):
if isinstance(s, unicode):
return s.encode('utf-8')
return s
import urlparse as orig_urlparse
urlsplit = orig_urlparse.urlsplit
urlparse = orig_urlparse.urlparse
import urllib
unquote = urllib.unquote

View File

@ -10,7 +10,7 @@ import sys
import time
from gunicorn import util
from gunicorn.six import string_types
class BaseSocket(object):
@ -44,7 +44,7 @@ class BaseSocket(object):
def close(self):
try:
self.sock.close()
except socket.error, e:
except socket.error as e:
self.log.info("Error while closing socket %s", str(e))
time.sleep(0.3)
del self.sock
@ -108,7 +108,7 @@ def create_socket(conf, log):
sock_type = TCP6Socket
else:
sock_type = TCPSocket
elif isinstance(addr, basestring):
elif isinstance(addr, string_types):
sock_type = UnixSocket
else:
raise TypeError("Unable to create socket from: %r" % addr)
@ -117,8 +117,8 @@ def create_socket(conf, log):
fd = int(os.environ.pop('GUNICORN_FD'))
try:
return sock_type(conf, log, fd=fd)
except socket.error, e:
if e[0] == errno.ENOTCONN:
except socket.error as e:
if e.args[0] == errno.ENOTCONN:
log.error("GUNICORN_FD should refer to an open socket.")
else:
raise
@ -130,10 +130,10 @@ def create_socket(conf, log):
for i in range(5):
try:
return sock_type(conf, log)
except socket.error, e:
if e[0] == errno.EADDRINUSE:
except socket.error as e:
if e.args[0] == errno.EADDRINUSE:
log.error("Connection in use: %s", str(addr))
if e[0] == errno.EADDRNOTAVAIL:
if e.args[0] == errno.EADDRNOTAVAIL:
log.error("Invalid address: %s", str(addr))
sys.exit(1)
if i < 5:

View File

@ -25,6 +25,7 @@ import textwrap
import time
import inspect
from gunicorn.six import text_type, string_types
MAXFD = 1024
if (hasattr(os, "devnull")):
@ -74,7 +75,7 @@ except ImportError:
if not hasattr(package, 'rindex'):
raise ValueError("'package' not set to a string")
dot = len(package)
for x in xrange(level, 1, -1):
for x in range(level, 1, -1):
try:
dot = package.rindex('.', 0, dot)
except ValueError:
@ -125,7 +126,7 @@ def load_class(uri, default="sync", section="gunicorn.workers"):
return pkg_resources.load_entry_point("gunicorn",
section, uri)
except ImportError, e:
except ImportError as e:
raise RuntimeError("class uri invalid or not found: " +
"[%s]" % str(e))
klass = components.pop(-1)
@ -216,14 +217,17 @@ try:
except ImportError:
def closerange(fd_low, fd_high):
# Iterate through and close all file descriptors.
for fd in xrange(fd_low, fd_high):
for fd in range(fd_low, fd_high):
try:
os.close(fd)
except OSError: # ERROR, fd wasn't open to begin with (ignored)
pass
def write_chunk(sock, data):
chunk = "".join(("%X\r\n" % len(data), data, "\r\n"))
if isinstance(data, text_type):
data = data.encode('utf-8')
chunk_size = "%X\r\n" % len(data)
chunk = b"".join([chunk_size.encode('utf-8'), data, b"\r\n"])
sock.sendall(chunk)
def write(sock, data, chunked=False):
@ -267,7 +271,7 @@ def write_error(sock, status_int, reason, mesg):
\r
%s
""") % (str(status_int), reason, len(html), html)
write_nonblock(sock, http)
write_nonblock(sock, http.encode('latin1'))
def normalize_name(name):
return "-".join([w.lower().capitalize() for w in name.split("-")])
@ -307,15 +311,6 @@ def http_date(timestamp=None):
hh, mm, ss)
return s
def to_bytestring(s):
""" convert to bytestring an unicode """
if not isinstance(s, basestring):
return s
if isinstance(s, unicode):
return s.encode('utf-8')
else:
return s
def is_hoppish(header):
return header.lower().strip() in hop_headers
@ -350,6 +345,13 @@ def seed():
def check_is_writeable(path):
try:
f = open(path, 'a')
except IOError, e:
except IOError as e:
raise RuntimeError("Error: '%s' isn't writable [%r]" % (path, e))
f.close()
def to_bytestring(value):
"""Converts a string argument to a byte string"""
if isinstance(value, bytes):
return value
assert isinstance(value, text_type)
return value.encode("utf-8")

View File

@ -13,6 +13,7 @@ import gunicorn.http as http
import gunicorn.http.wsgi as wsgi
import gunicorn.util as util
import gunicorn.workers.base as base
from gunicorn import six
ALREADY_HANDLED = object()
@ -28,40 +29,37 @@ class AsyncWorker(base.Worker):
def handle(self, client, addr):
req = None
try:
client.settimeout(self.cfg.timeout)
parser = http.RequestParser(self.cfg, client)
try:
if not self.cfg.keepalive:
req = parser.next()
req = six.next(parser)
self.handle_request(req, client, addr)
else:
# keepalive loop
while True:
req = None
with self.timeout_ctx():
req = parser.next()
req = six.next(parser)
if not req:
break
self.handle_request(req, client, addr)
except http.errors.NoMoreData, e:
except http.errors.NoMoreData as e:
self.log.debug("Ignored premature client disconnection. %s", e)
except StopIteration, e:
except StopIteration as e:
self.log.debug("Closing connection. %s", e)
except socket.error:
raise # pass to next try-except level
except Exception, e:
except Exception as e:
self.handle_error(req, client, addr, e)
except socket.timeout as e:
self.handle_error(req, client, addr, e)
except socket.error, e:
if e[0] not in (errno.EPIPE, errno.ECONNRESET):
except socket.error as e:
if e.args[0] not in (errno.EPIPE, errno.ECONNRESET):
self.log.exception("Socket error processing request.")
else:
if e[0] == errno.ECONNRESET:
if e.args[0] == errno.ECONNRESET:
self.log.debug("Ignoring connection reset")
else:
self.log.debug("Ignoring EPIPE")
except Exception, e:
except Exception as e:
self.handle_error(req, client, addr, e)
finally:
util.close(client)

View File

@ -8,7 +8,6 @@ import os
import signal
import sys
import traceback
import socket
from gunicorn import util
@ -18,13 +17,12 @@ InvalidRequestLine, InvalidRequestMethod, InvalidHTTPVersion, \
LimitRequestLine, LimitRequestHeaders
from gunicorn.http.errors import InvalidProxyLine, ForbiddenProxyRequest
from gunicorn.http.wsgi import default_environ, Response
from gunicorn.six import MAXSIZE
class Worker(object):
SIGNALS = map(
lambda x: getattr(signal, "SIG%s" % x),
"HUP QUIT INT TERM USR1 USR2 WINCH CHLD".split()
)
SIGNALS = [getattr(signal, "SIG%s" % x) \
for x in "HUP QUIT INT TERM USR1 USR2 WINCH CHLD".split()]
PIPE = []
@ -43,7 +41,7 @@ class Worker(object):
self.booted = False
self.nr = 0
self.max_requests = cfg.max_requests or sys.maxint
self.max_requests = cfg.max_requests or MAXSIZE
self.alive = True
self.log = log
self.debug = cfg.debug
@ -87,8 +85,9 @@ class Worker(object):
# For waking ourselves up
self.PIPE = os.pipe()
map(util.set_non_blocking, self.PIPE)
map(util.close_on_exec, self.PIPE)
for p in self.PIPE:
util.set_non_blocking(p)
util.close_on_exec(p)
# Prevent fd inherientence
util.close_on_exec(self.socket)
@ -105,7 +104,9 @@ class Worker(object):
self.run()
def init_signals(self):
map(lambda s: signal.signal(s, signal.SIG_DFL), self.SIGNALS)
# reset signaling
[signal.signal(s, signal.SIG_DFL) for s in self.SIGNALS]
# init new signaling
signal.signal(signal.SIGQUIT, self.handle_quit)
signal.signal(signal.SIGTERM, self.handle_exit)
signal.signal(signal.SIGINT, self.handle_exit)
@ -164,10 +165,6 @@ class Worker(object):
error=str(exc),
)
)
elif isinstance(exc, socket.timeout):
status_int = 408
reason = "Request Timeout"
mesg = "<p>The server timed out handling for the request</p>"
else:
self.log.exception("Error handling request")

View File

@ -14,6 +14,7 @@ import gunicorn.http as http
import gunicorn.http.wsgi as wsgi
import gunicorn.util as util
import gunicorn.workers.base as base
from gunicorn import six
class SyncWorker(base.Worker):
@ -40,8 +41,8 @@ class SyncWorker(base.Worker):
# process.
continue
except socket.error, e:
if e[0] not in (errno.EAGAIN, errno.ECONNABORTED):
except socket.error as e:
if e.args[0] not in (errno.EAGAIN, errno.ECONNABORTED):
raise
# If our parent changed then we shut down.
@ -54,10 +55,10 @@ class SyncWorker(base.Worker):
ret = select.select([self.socket], [], self.PIPE, self.timeout)
if ret[0]:
continue
except select.error, e:
if e[0] == errno.EINTR:
except select.error as e:
if e.args[0] == errno.EINTR:
continue
if e[0] == errno.EBADF:
if e.args[0] == errno.EBADF:
if self.nr < 0:
continue
else:
@ -67,22 +68,19 @@ class SyncWorker(base.Worker):
def handle(self, client, addr):
req = None
try:
client.settimeout(self.cfg.timeout)
parser = http.RequestParser(self.cfg, client)
req = parser.next()
req = six.next(parser)
self.handle_request(req, client, addr)
except http.errors.NoMoreData, e:
except http.errors.NoMoreData as e:
self.log.debug("Ignored premature client disconnection. %s", e)
except StopIteration, e:
except StopIteration as e:
self.log.debug("Closing connection. %s", e)
except socket.timeout as e:
self.handle_error(req, client, addr, e)
except socket.error, e:
if e[0] != errno.EPIPE:
except socket.error as e:
if e.args[0] != errno.EPIPE:
self.log.exception("Error processing request.")
else:
self.log.debug("Ignoring EPIPE")
except Exception, e:
except Exception as e:
self.handle_error(req, client, addr, e)
finally:
util.close(client)
@ -117,7 +115,7 @@ class SyncWorker(base.Worker):
respiter.close()
except socket.error:
raise
except Exception, e:
except Exception as e:
# Only send back traceback in HTTP in debug mode.
self.handle_error(req, client, addr, e)
return

2
requirements_dev.txt Normal file
View File

@ -0,0 +1,2 @@
pytest
pytest-cov

View File

@ -2,3 +2,6 @@
build-requires = python2-devel python-setuptools
requires = python-setuptools >= 0.6c6 python-ctypes
install_script = rpm/install
[pytest]
norecursedirs = examples lib local src

View File

@ -5,47 +5,83 @@
import os
from setuptools import setup, find_packages
from setuptools import setup, find_packages, Command
import sys
from gunicorn import __version__
CLASSIFIERS = [
'Development Status :: 4 - Beta',
'Environment :: Other Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: MacOS :: MacOS X',
'Operating System :: POSIX',
'Programming Language :: Python',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Topic :: Internet',
'Topic :: Utilities',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: WSGI',
'Topic :: Internet :: WWW/HTTP :: WSGI :: Server',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content']
# read long description
with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f:
long_description = f.read()
# read dev requirements
fname = os.path.join(os.path.dirname(__file__), 'requirements_dev.txt')
with open(fname) as f:
tests_require = list(map(lambda l: l.strip(), f.readlines()))
class PyTest(Command):
user_options = [
("cov", None, "measure coverage")
]
def initialize_options(self):
self.cov = None
def finalize_options(self):
pass
def run(self):
import sys,subprocess
basecmd = [sys.executable, '-m', 'py.test']
if self.cov:
basecmd += ['--cov', 'gunicorn']
errno = subprocess.call(basecmd + ['tests'])
raise SystemExit(errno)
setup(
name = 'gunicorn',
version = __version__,
description = 'WSGI HTTP Server for UNIX',
long_description = file(
os.path.join(
os.path.dirname(__file__),
'README.rst'
)
).read(),
long_description = long_description,
author = 'Benoit Chesneau',
author_email = 'benoitc@e-engura.com',
license = 'MIT',
url = 'http://gunicorn.org',
classifiers = [
'Development Status :: 4 - Beta',
'Environment :: Other Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: MacOS :: MacOS X',
'Operating System :: POSIX',
'Programming Language :: Python',
'Topic :: Internet',
'Topic :: Utilities',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: WSGI',
'Topic :: Internet :: WWW/HTTP :: WSGI :: Server',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
classifiers = CLASSIFIERS,
zip_safe = False,
packages = find_packages(exclude=['examples', 'tests']),
include_package_data = True,
tests_require = tests_require,
cmdclass = {'test': PyTest},
entry_points="""
[console_scripts]
@ -66,6 +102,5 @@ setup(
[paste.server_runner]
main=gunicorn.app.pasterapp:paste_server
""",
test_suite = 'nose.collector',
"""
)

View File

@ -1,61 +0,0 @@
from StringIO import StringIO
import t
from gunicorn.http.body import Body
def assert_readline(payload, size, expected):
body = Body(StringIO(payload))
t.eq(body.readline(size), expected)
def test_readline_empty_body():
assert_readline("", None, "")
assert_readline("", 1, "")
def test_readline_zero_size():
assert_readline("abc", 0, "")
assert_readline("\n", 0, "")
def test_readline_new_line_before_size():
body = Body(StringIO("abc\ndef"))
t.eq(body.readline(4), "abc\n")
t.eq(body.readline(), "def")
def test_readline_new_line_after_size():
body = Body(StringIO("abc\ndef"))
t.eq(body.readline(2), "ab")
t.eq(body.readline(), "c\n")
def test_readline_no_new_line():
body = Body(StringIO("abcdef"))
t.eq(body.readline(), "abcdef")
body = Body(StringIO("abcdef"))
t.eq(body.readline(2), "ab")
t.eq(body.readline(2), "cd")
t.eq(body.readline(2), "ef")
def test_readline_buffer_loaded():
reader = StringIO("abc\ndef")
body = Body(reader)
body.read(1) # load internal buffer
reader.write("g\nhi")
reader.seek(7)
t.eq(body.readline(), "bc\n")
t.eq(body.readline(), "defg\n")
t.eq(body.readline(), "hi")
def test_readline_buffer_loaded_with_size():
body = Body(StringIO("abc\ndef"))
body.read(1) # load internal buffer
t.eq(body.readline(2), "bc")
t.eq(body.readline(2), "\n")
t.eq(body.readline(2), "de")
t.eq(body.readline(2), "f")

View File

@ -7,5 +7,5 @@ request = {
("CONTENT-TYPE", "application/json"),
("CONTENT-LENGTH", "14")
],
"body": '{"nom": "nom"}'
}
"body": b'{"nom": "nom"}'
}

View File

@ -7,5 +7,5 @@ request = {
("HOST", "0.0.0.0=5000"),
("ACCEPT", "*/*")
],
"body": ""
}
"body": b""
}

View File

@ -12,5 +12,5 @@ request = {
("KEEP-ALIVE", "300"),
("CONNECTION", "keep-alive")
],
"body": ""
}
"body": b""
}

View File

@ -5,5 +5,5 @@ request = {
"headers": [
("AAAAAAAAAAAAA", "++++++++++")
],
"body": ""
}
"body": b""
}

View File

@ -3,5 +3,5 @@ request = {
"uri": uri("/forums/1/topics/2375?page=1#posts-17408"),
"version": (1, 1),
"headers": [],
"body": ""
}
"body": b""
}

View File

@ -3,5 +3,5 @@ request = {
"uri": uri("/get_no_headers_no_body/world"),
"version": (1, 1),
"headers": [],
"body": ""
}
"body": b""
}

View File

@ -5,5 +5,5 @@ request = {
"headers": [
("ACCEPT", "*/*")
],
"body": ""
}
"body": b""
}

View File

@ -5,5 +5,5 @@ request = {
"headers": [
("CONTENT-LENGTH", "5")
],
"body": "HELLO"
}
"body": b"HELLO"
}

View File

@ -7,5 +7,5 @@ request = {
("TRANSFER-ENCODING", "identity"),
("CONTENT-LENGTH", "5")
],
"body": "World"
}
"body": b"World"
}

View File

@ -5,5 +5,5 @@ request = {
"headers": [
("TRANSFER-ENCODING", "chunked"),
],
"body": "all your base are belong to us"
}
"body": b"all your base are belong to us"
}

View File

@ -5,5 +5,5 @@ request = {
"headers": [
("TRANSFER-ENCODING", "chunked")
],
"body": "hello world"
}
"body": b"hello world"
}

View File

@ -5,9 +5,9 @@ request = {
"headers": [
("TRANSFER-ENCODING", "chunked")
],
"body": "hello world",
"body": b"hello world",
"trailers": [
("VARY", "*"),
("CONTENT-TYPE", "text/plain")
]
}
}

View File

@ -5,5 +5,5 @@ request = {
"headers": [
("TRANSFER-ENCODING", "chunked")
],
"body": "hello world"
}
"body": b"hello world"
}

View File

@ -3,5 +3,5 @@ request = {
"uri": uri('/with_"quotes"?foo="bar"'),
"version": (1, 1),
"headers": [],
"body": ""
}
"body": b""
}

View File

@ -7,5 +7,5 @@ request = {
("USER-AGENT", "ApacheBench/2.3"),
("ACCEPT", "*/*")
],
"body": ""
}
"body": b""
}

View File

@ -36,5 +36,5 @@ request = {
"uri": uri("/"),
"version": (1, 1),
"headers": [("X-SSL-CERT", certificate)],
"body": ""
}
"body": b""
}

View File

@ -6,5 +6,5 @@ request = {
("IF-MATCH", "bazinga!"),
("IF-MATCH", "large-sound")
],
"body": ""
"body": b""
}

View File

@ -3,7 +3,7 @@ req1 = {
"uri": uri("/first"),
"version": (1, 1),
"headers": [],
"body": ""
"body": b""
}
req2 = {
@ -11,7 +11,7 @@ req2 = {
"uri": uri("/second"),
"version": (1, 1),
"headers": [],
"body": ""
"body": b""
}
request = [req1, req2]

View File

@ -3,5 +3,5 @@ request = {
"uri": uri("/first"),
"version": (1, 0),
"headers": [],
"body": ""
}
"body": b""
}

View File

@ -3,5 +3,5 @@ request = {
"uri": uri("/first"),
"version": (1, 0),
"headers": [('CONTENT-LENGTH', '24')],
"body": "GET /second HTTP/1.1\r\n\r\n"
}
"body": b"GET /second HTTP/1.1\r\n\r\n"
}

View File

@ -3,5 +3,5 @@ request = {
"uri": uri("/first"),
"version": (1, 1),
"headers": [("CONNECTION", "Close")],
"body": ""
}
"body": b""
}

View File

@ -3,7 +3,7 @@ req1 = {
"uri": uri("/first"),
"version": (1, 0),
"headers": [("CONNECTION", "Keep-Alive")],
"body": ""
"body": b""
}
req2 = {
@ -11,7 +11,7 @@ req2 = {
"uri": uri("/second"),
"version": (1, 1),
"headers": [],
"body": ""
"body": b""
}
request = [req1, req2]
request = [req1, req2]

View File

@ -5,7 +5,7 @@ req1 = {
"headers": [
("TRANSFER-ENCODING", "chunked")
],
"body": "hello world"
"body": b"hello world"
}
req2 = {
@ -13,7 +13,7 @@ req2 = {
"uri": uri("/second"),
"version": (1, 1),
"headers": [],
"body": ""
"body": b""
}
request = [req1, req2]
request = [req1, req2]

View File

@ -6,7 +6,7 @@ req1 = {
("CONTENT-LENGTH", "-1"),
("TRANSFER-ENCODING", "chunked")
],
"body": "hello world"
"body": b"hello world"
}
req2 = {
@ -17,7 +17,7 @@ req2 = {
("TRANSFER-ENCODING", "chunked"),
("CONTENT-LENGTH", "-1"),
],
"body": "hello world"
"body": b"hello world"
}
request = [req1, req2]

View File

@ -12,5 +12,5 @@ request = {
("CONTENT-TYPE", "application/json"),
("CONTENT-LENGTH", "14")
],
"body": '{"nom": "nom"}'
"body": b'{"nom": "nom"}'
}

View File

@ -13,7 +13,7 @@ req1 = {
("CONTENT-LENGTH", "14"),
("CONNECTION", "keep-alive")
],
"body": '{"nom": "nom"}'
"body": b'{"nom": "nom"}'
}
@ -24,7 +24,7 @@ req2 = {
"headers": [
("TRANSFER-ENCODING", "chunked"),
],
"body": "all your base are belong to us"
"body": b"all your base are belong to us"
}
request = [req1, req2]

View File

@ -1,43 +1,43 @@
# -*- coding: utf-8 -
# Copyright 2009 Paul J. Davis <paul.joseph.davis@gmail.com>
#
# This file is part of gunicorn released under the MIT license.
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
from __future__ import with_statement
import array
import os
from StringIO import StringIO
import tempfile
dirname = os.path.dirname(__file__)
from gunicorn.http.parser import RequestParser
from gunicorn.config import Config
from gunicorn.six import BytesIO
def data_source(fname):
buf = StringIO()
buf = BytesIO()
with open(fname) as handle:
for line in handle:
line = line.rstrip("\n").replace("\\r\\n", "\r\n")
buf.write(line)
buf.write(line.encode('latin1'))
return buf
class request(object):
def __init__(self, name):
self.fname = os.path.join(dirname, "requests", name)
def __call__(self, func):
def run():
src = data_source(self.fname)
func(src, RequestParser(src))
run.func_name = func.func_name
return run
class FakeSocket(object):
def __init__(self, data):
self.tmp = tempfile.TemporaryFile()
if data:
@ -47,32 +47,32 @@ class FakeSocket(object):
def fileno(self):
return self.tmp.fileno()
def len(self):
return self.tmp.len
def recv(self, length=None):
return self.tmp.read()
def recv_into(self, buf, length):
tmp_buffer = self.tmp.read(length)
v = len(tmp_buffer)
for i, c in enumerate(tmp_buffer):
buf[i] = c
return v
def send(self, data):
self.tmp.write(data)
self.tmp.flush()
def seek(self, offset, whence=0):
self.tmp.seek(offset, whence)
class http_request(object):
def __init__(self, name):
self.fname = os.path.join(dirname, "requests", name)
def __call__(self, func):
def run():
fsock = FakeSocket(data_source(self.fname))
@ -80,7 +80,7 @@ class http_request(object):
func(req)
run.func_name = func.func_name
return run
def eq(a, b):
assert a == b, "%r != %r" % (a, b)
@ -117,4 +117,3 @@ def raises(exctype, func, *args, **kwargs):
func_name = getattr(func, "func_name", "<builtin_function>")
raise AssertionError("Function %s did not raise %s" % (
func_name, exctype.__name__))

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import t
@ -9,6 +9,8 @@ import treq
import glob
import os
dirname = os.path.dirname(__file__)
from py.test import skip
reqdir = os.path.join(dirname, "requests", "valid")
def a_case(fname):

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import t
@ -8,7 +8,8 @@ import treq
import glob
import os
from nose.tools import raises
import pytest
dirname = os.path.dirname(__file__)
reqdir = os.path.join(dirname, "requests", "invalid")
@ -17,12 +18,12 @@ reqdir = os.path.join(dirname, "requests", "invalid")
def test_http_parser():
for fname in glob.glob(os.path.join(reqdir, "*.http")):
env = treq.load_py(os.path.splitext(fname)[0] + ".py")
expect = env['request']
cfg = env['cfg']
req = treq.badrequest(fname)
@raises(expect)
def check(fname):
return req.check(cfg)
yield check, fname # fname is pass so that we know which test failed
with pytest.raises(expect):
def f(fname):
return req.check(cfg)
f(fname)

View File

@ -1,12 +1,10 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
from __future__ import with_statement
from nose.plugins.skip import SkipTest
import t
import functools
@ -23,14 +21,6 @@ def cfg_file():
def paster_ini():
return os.path.join(dirname, "..", "examples", "frameworks", "pylonstest", "nose.ini")
def PasterApp():
try:
from paste.deploy import loadapp, loadwsgi
except ImportError:
raise SkipTest()
from gunicorn.app.pasterapp import PasterApplication
return PasterApplication("no_usage")
class AltArgs(object):
def __init__(self, args=None):
self.args = args or []
@ -38,17 +28,17 @@ class AltArgs(object):
def __enter__(self):
sys.argv = self.args
def __exit__(self, exc_type, exc_inst, traceback):
sys.argv = self.orig
class NoConfigApp(Application):
def __init__(self):
super(NoConfigApp, self).__init__("no_usage")
def init(self, parser, opts, args):
pass
def load(self):
pass
@ -63,25 +53,25 @@ def test_property_access():
c = config.Config()
for s in config.KNOWN_SETTINGS:
getattr(c, s.name)
# Class was loaded
t.eq(c.worker_class, SyncWorker)
# Debug affects workers
t.eq(c.workers, 1)
c.set("workers", 3)
t.eq(c.workers, 3)
# Address is parsed
t.eq(c.address, ("127.0.0.1", 8000))
# User and group defaults
t.eq(os.geteuid(), c.uid)
t.eq(os.getegid(), c.gid)
# Proc name
t.eq("gunicorn", c.proc_name)
# Not a config property
t.raises(AttributeError, getattr, c, "foo")
# Force to be not an error
@ -93,10 +83,10 @@ def test_property_access():
# Attempt to set a cfg not via c.set
t.raises(AttributeError, setattr, c, "proc_name", "baz")
# No setting for name
t.raises(AttributeError, c.set, "baz", "bar")
def test_bool_validation():
c = config.Config()
t.eq(c.debug, False)
@ -196,30 +186,9 @@ def test_load_config():
t.eq(app.cfg.bind, "unix:/tmp/bar/baz")
t.eq(app.cfg.workers, 3)
t.eq(app.cfg.proc_name, "fooey")
def test_cli_overrides_config():
with AltArgs(["prog_name", "-c", cfg_file(), "-b", "blarney"]):
app = NoConfigApp()
t.eq(app.cfg.bind, "blarney")
t.eq(app.cfg.proc_name, "fooey")
def test_paster_config():
with AltArgs(["prog_name", paster_ini()]):
app = PasterApp()
t.eq(app.cfg.bind, "192.168.0.1:80")
t.eq(app.cfg.proc_name, "brim")
t.eq("ignore_me" in app.cfg.settings, False)
def test_cfg_over_paster():
with AltArgs(["prog_name", "-c", cfg_file(), paster_ini()]):
app = PasterApp()
t.eq(app.cfg.bind, "unix:/tmp/bar/baz")
t.eq(app.cfg.proc_name, "fooey")
t.eq(app.cfg.default_proc_name, "blurgh")
def test_cli_cfg_paster():
with AltArgs(["prog_name", "-c", cfg_file(), "-b", "whee", paster_ini()]):
app = PasterApp()
t.eq(app.cfg.bind, "whee")
t.eq(app.cfg.proc_name, "fooey")
t.eq(app.cfg.default_proc_name, "blurgh")

View File

@ -0,0 +1,61 @@
import t
from gunicorn.http.body import Body
from gunicorn.six import BytesIO
def assert_readline(payload, size, expected):
body = Body(BytesIO(payload))
t.eq(body.readline(size), expected)
def test_readline_empty_body():
assert_readline(b"", None, b"")
assert_readline(b"", 1, b"")
def test_readline_zero_size():
assert_readline(b"abc", 0, b"")
assert_readline(b"\n", 0, b"")
def test_readline_new_line_before_size():
body = Body(BytesIO(b"abc\ndef"))
t.eq(body.readline(4), b"abc\n")
t.eq(body.readline(), b"def")
def test_readline_new_line_after_size():
body = Body(BytesIO(b"abc\ndef"))
t.eq(body.readline(2), b"ab")
t.eq(body.readline(), b"c\n")
def test_readline_no_new_line():
body = Body(BytesIO(b"abcdef"))
t.eq(body.readline(), b"abcdef")
body = Body(BytesIO(b"abcdef"))
t.eq(body.readline(2), b"ab")
t.eq(body.readline(2), b"cd")
t.eq(body.readline(2), b"ef")
def test_readline_buffer_loaded():
reader = BytesIO(b"abc\ndef")
body = Body(reader)
body.read(1) # load internal buffer
reader.write(b"g\nhi")
reader.seek(7)
print(reader.getvalue())
t.eq(body.readline(), b"bc\n")
t.eq(body.readline(), b"defg\n")
t.eq(body.readline(), b"hi")
def test_readline_buffer_loaded_with_size():
body = Body(BytesIO(b"abc\ndef"))
body.read(1) # load internal buffer
t.eq(body.readline(2), b"bc")
t.eq(body.readline(2), b"\n")
t.eq(body.readline(2), b"de")
t.eq(body.readline(2), b"f")

View File

@ -0,0 +1,13 @@
import sys
from gunicorn.glogging import LazyWriter
def test_lazywriter_isatty():
orig = sys.stdout
sys.stdout = LazyWriter('test.log')
try:
sys.stdout.isatty()
except AttributeError:
raise AssertionError("LazyWriter has no attribute 'isatty'")
sys.stdout = orig

View File

@ -10,18 +10,19 @@ import t
import inspect
import os
import random
import urlparse
from gunicorn.config import Config
from gunicorn.http.errors import ParseException
from gunicorn.http.parser import RequestParser
from gunicorn.six import urlparse, execfile_
from gunicorn import six
dirname = os.path.dirname(__file__)
random.seed()
def uri(data):
ret = {"raw": data}
parts = urlparse.urlparse(data)
parts = urlparse(data)
ret["scheme"] = parts.scheme or ''
ret["host"] = parts.netloc.rsplit(":", 1)[0] or None
ret["port"] = parts.port or 80
@ -42,7 +43,7 @@ def load_py(fname):
config = globals().copy()
config["uri"] = uri
config["cfg"] = Config()
execfile(fname, config)
execfile_(fname, config)
return config
class request(object):
@ -54,10 +55,10 @@ class request(object):
if not isinstance(self.expect, list):
self.expect = [self.expect]
with open(self.fname) as handle:
with open(self.fname, 'rb') as handle:
self.data = handle.read()
self.data = self.data.replace("\n", "").replace("\\r\\n", "\r\n")
self.data = self.data.replace("\\0", "\000")
self.data = self.data.replace(b"\n", b"").replace(b"\\r\\n", b"\r\n")
self.data = self.data.replace(b"\\0", b"\000")
# Functions for sending data to the parser.
# These functions mock out reading from a
@ -69,20 +70,20 @@ class request(object):
def send_lines(self):
lines = self.data
pos = lines.find("\r\n")
pos = lines.find(b"\r\n")
while pos > 0:
yield lines[:pos+2]
lines = lines[pos+2:]
pos = lines.find("\r\n")
pos = lines.find(b"\r\n")
if len(lines):
yield lines
def send_bytes(self):
for d in self.data:
yield d
for d in str(self.data.decode("latin1")):
yield bytes(d.encode("latin1"))
def send_random(self):
maxs = len(self.data) / 10
maxs = round(len(self.data) / 10)
read = 0
while read < len(self.data):
chunk = random.randint(1, maxs)
@ -143,7 +144,7 @@ class request(object):
while len(body):
if body[:len(data)] != data:
raise AssertionError("Invalid data read: %r" % data)
if '\n' in data[:-1]:
if b'\n' in data[:-1]:
raise AssertionError("Embedded new line: %r" % data)
body = body[len(data):]
data = self.szread(req.body.readline, sizes)
@ -165,7 +166,7 @@ class request(object):
"""
data = req.body.readlines()
for line in data:
if '\n' in line[:-1]:
if b'\n' in line[:-1]:
raise AssertionError("Embedded new line: %r" % line)
if line != body[:len(line)]:
raise AssertionError("Invalid body data read: %r != %r" % (
@ -182,7 +183,7 @@ class request(object):
This skips sizes because there's its not part of the iter api.
"""
for line in req.body:
if '\n' in line[:-1]:
if b'\n' in line[:-1]:
raise AssertionError("Embedded new line: %r" % line)
if line != body[:len(line)]:
raise AssertionError("Invalid body data read: %r != %r" % (
@ -191,7 +192,7 @@ class request(object):
if len(body):
raise AssertionError("Failed to read entire body: %r" % body)
try:
data = iter(req.body).next()
data = six.next(iter(req.body))
raise AssertionError("Read data after body finished: %r" % data)
except StopIteration:
pass
@ -214,9 +215,15 @@ class request(object):
ret = []
for (mt, sz, sn) in cfgs:
mtn = mt.func_name[6:]
szn = sz.func_name[5:]
snn = sn.func_name[5:]
if hasattr(mt, 'funcname'):
mtn = mt.func_name[6:]
szn = sz.func_name[5:]
snn = sn.func_name[5:]
else:
mtn = mt.__name__[6:]
szn = sz.__name__[5:]
snn = sn.__name__[5:]
def test_req(sn, sz, mt):
self.check(cfg, sn, sz, mt)
desc = "%s: MT: %s SZ: %s SN: %s" % (self.name, mtn, szn, snn)
@ -251,9 +258,10 @@ class badrequest(object):
self.data = handle.read()
self.data = self.data.replace("\n", "").replace("\\r\\n", "\r\n")
self.data = self.data.replace("\\0", "\000")
self.data = self.data.encode('latin1')
def send(self):
maxs = len(self.data) / 10
maxs = round(len(self.data) / 10)
read = 0
while read < len(self.data):
chunk = random.randint(1, maxs)
@ -262,5 +270,4 @@ class badrequest(object):
def check(self, cfg):
p = RequestParser(cfg, self.send())
[req for req in p]
six.next(p)

View File

@ -4,8 +4,8 @@
# and then run "tox" from this directory.
[tox]
envlist = py26, py27, pypy
envlist = py26, py27, py33, pypy
[testenv]
commands = nosetests
deps = nose
commands = py.test tests/
deps = pytest