diff --git a/AUTHORS b/AUTHORS index 7d4a356..efe4a6f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -23,6 +23,7 @@ Linden Lab Contributors Thanks To --------- +* gholt, wsgi patches for accepting a custom pool, and returning 400 if content-length is invalid * Luke Tucker, bug report regarding wsgi + webob * Chuck Thier, reporting a bug in processes.py * Brantley Harris, reporting bug #4 diff --git a/eventlet/patcher.py b/eventlet/patcher.py index b43fd93..e10ac4f 100644 --- a/eventlet/patcher.py +++ b/eventlet/patcher.py @@ -33,6 +33,8 @@ def inject(module_name, new_globals, *additional_modules): for name, mod in additional_modules: if saved[name] is not None: sys.modules[name] = saved[name] + else: + del sys.modules[name] return module diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 1d5946c..cade800 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -167,6 +167,17 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): if not self.parse_request(): return + content_length = self.headers.getheader('content-length') + if content_length: + try: + int(content_length) + except ValueError: + self.wfile.write( + "HTTP/1.0 400 Bad Request\r\n" + "Connection: close\r\nContent-length: 0\r\n\r\n") + self.close_connection = 1 + return + self.environ = self.get_environ() self.application = self.server.app try: @@ -423,7 +434,8 @@ def server(sock, site, protocol=HttpProtocol, server_event=None, minimum_chunk_size=None, - log_x_forwarded_for=True): + log_x_forwarded_for=True, + custom_pool=None): """ Start up a `WSGI `_ server handling requests from the supplied server socket. This function loops forever. @@ -449,7 +461,10 @@ def server(sock, site, server_event.send(serv) if max_size is None: max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS - pool = Pool(max_size=max_size) + if custom_pool is not None: + pool = custom_pool + else: + pool = Pool(max_size=max_size) try: host, port = sock.getsockname() port = ':%s' % (port, ) diff --git a/tests/patcher_test.py b/tests/patcher_test.py new file mode 100644 index 0000000..401e11e --- /dev/null +++ b/tests/patcher_test.py @@ -0,0 +1,72 @@ +import os +import tempfile +import subprocess +import sys + +from tests import LimitedTestCase + +base_module_contents = """ +import socket +import urllib +print "base", socket, urllib +""" + +patching_module_contents = """ +from eventlet.green import socket +from eventlet.green import urllib +from eventlet import patcher +print 'patcher', socket, urllib +patcher.inject('%s', globals(), ('socket', socket), ('urllib', urllib)) +del patcher +""" + +import_module_contents = """ +import %(mod)s +import httplib +print "importing", %(mod)s, httplib, %(mod)s.socket, %(mod)s.urllib +""" + +class Patcher(LimitedTestCase): + TEST_TIMEOUT=3 # starting processes is time-consuming + def setUp(self): + self._saved_syspath = sys.path + self.tempfiles = [] + + def tearDown(self): + sys.path = self._saved_syspath + for tf in self.tempfiles: + os.remove(tf) + + def write_to_tempfile(self, contents): + fn, filename = tempfile.mkstemp('_patcher_test.py') + fd = os.fdopen(fn, 'w') + fd.write(contents) + fd.close() + self.tempfiles.append(filename) + return os.path.dirname(filename), os.path.basename(filename) + + def test_patch_a_module(self): + base = self.write_to_tempfile(base_module_contents) + base_modname = os.path.splitext(base[1])[0] + patching = self.write_to_tempfile(patching_module_contents % base_modname) + patching_modname = os.path.splitext(patching[1])[0] + importing = self.write_to_tempfile( + import_module_contents % dict(mod=patching_modname)) + + python_path = os.pathsep.join(sys.path) + python_path += os.pathsep.join((base[0], patching[0], importing[0])) + new_env = os.environ.copy() + new_env['PYTHONPATH'] = python_path + p = subprocess.Popen([sys.executable, + os.path.join(importing[0], importing[1])], + stdout=subprocess.PIPE, env=new_env) + output = p.communicate() + lines = output[0].split("\n") + self.assert_(lines[0].startswith('patcher')) + self.assert_(lines[1].startswith('base')) + self.assert_(lines[2].startswith('importing')) + self.assert_('eventlet.green.socket' in lines[1]) + self.assert_('eventlet.green.urllib' in lines[1]) + self.assert_('eventlet.green.socket' in lines[2]) + self.assert_('eventlet.green.urllib' in lines[2]) + self.assert_('eventlet.green.httplib' not in lines[2]) \ No newline at end of file diff --git a/tests/wsgi_test.py b/tests/wsgi_test.py index 88b84ac..e72641c 100644 --- a/tests/wsgi_test.py +++ b/tests/wsgi_test.py @@ -490,7 +490,45 @@ class TestHttpd(LimitedTestCase): 'Connection: close\r\n' '\r\n\r\n') self.assert_('200 OK' in fd.read()) - + + def test_022_custom_pool(self): + # just test that it accepts the parameter for now + # TODO: test that it uses the pool and that you can waitall() to + # ensure that all clients finished + from eventlet import pool + p = pool.Pool(max_size=5) + api.kill(self.killer) + listener = api.tcp_listener(('localhost', 0)) + self.port = listener.getsockname()[1] + self.killer = api.spawn( + wsgi.server, + listener, + self.site, + max_size=128, + log=self.logfile, + custom_pool=p) + + # this stuff is copied from test_001_server, could be better factored + sock = api.connect_tcp( + ('localhost', self.port)) + fd = sock.makeGreenFile() + fd.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') + result = fd.read() + fd.close() + self.assert_(result.startswith('HTTP'), result) + self.assert_(result.endswith('hello world')) + + def test_023_bad_content_length(self): + sock = api.connect_tcp( + ('localhost', self.port)) + fd = sock.makeGreenFile() + fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nContent-length: argh\r\n\r\n') + result = fd.read() + fd.close() + self.assert_(result.startswith('HTTP'), result) + self.assert_('400 Bad Request' in result) + self.assert_('500' not in result) + if __name__ == '__main__': main()