Last active
December 16, 2015 15:49
-
-
Save mouadino/5458786 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""This script was created to test which server support closing connection | |
as soon as a response is available when the client has set Connection header | |
to close, assuming that we are using HTTP 1.1 here. | |
Results: | |
+-----------+----------------+---------------+ | |
|Framework | Connection: | No Connection | | |
| | close | header | | |
+-----------+----------------+---------------+ | |
|cherrypy | YES | YES | | |
+-----------+----------------+---------------+ | |
|tornado | YES | YES | | |
+-----------+----------------+---------------+ | |
|eventlet | NO | YES | | |
+-----------+----------------+---------------+ | |
|gevent | NO | YES | | |
+-----------+----------------+---------------+ | |
|werkzeug | NO | YES | | |
+-----------+----------------+---------------+ | |
|twisted | NO | YES | | |
+-----------+----------------+---------------+ | |
|paste | YES | NO | | |
+-----------+----------------+---------------+ | |
|wsgiref | YES | NO | | |
+-----------+----------------+---------------+ | |
YES: Work as expect | |
NO: Doesn't work as expected | |
Conclusion: | |
- The following servers work as expected: tornado, cherrypy | |
- The following servers close connection when a response is avaialable no | |
matter if we set Connection to close or no: paste, wgriref. | |
- Other servers (eventlet, gevent, werkzeug) will continue | |
fetching all body before closing the connection no matter what. | |
- Apache/2.2.22 with mod_wsgi 3.3 failed also in this test. | |
More detail: https://github.com/eventlet/eventlet/issues/27 | |
""" | |
import os | |
import sys | |
import time | |
import signal | |
import socket | |
import httplib | |
import StringIO | |
import multiprocessing | |
PORT = 8080 | |
CHUNKSIZE = 1024 * 64 | |
HEADERS = { | |
'Transfer-Encoding': 'chunked', | |
'Connection': 'close', # Try to comment this and run again. | |
} | |
def _run_test(server_serve, args=()): | |
"Return True if success else False." | |
print "Starting server" | |
p = multiprocessing.Process( | |
target=server_serve, args=args | |
) | |
p.start() | |
time.sleep(3) # Wait for the server to become listening. | |
print "Uploading ..." | |
success = upload() | |
os.kill(p.pid, signal.SIGKILL) | |
if not success: | |
print "NOTE: WSGI server didn't stop the upload !!" | |
return success | |
def dummy_app(env, start_response): | |
start_response('200 OK', [('Content-Type', 'text/plain')]) | |
return ["I don't care about your upload i just want to return a response" | |
"so please dont't waste your time sending more! \r\n"] | |
def upload(): | |
"""Return True if server didn't ask for all file and instead closed | |
connection else return False. | |
""" | |
conn = httplib.HTTPConnection("127.0.0.1", PORT) | |
conn.putrequest("GET", "/") | |
for header, value in HEADERS.items(): | |
conn.putheader(header, value) | |
conn.endheaders() | |
file_obj = StringIO.StringIO('X' * (1024 * 1024 * 4)) | |
chunk = file_obj.read(CHUNKSIZE) | |
loop = 0 | |
try: | |
while chunk: | |
conn.send('%x\r\n%s\r\n' % (len(chunk), chunk)) | |
chunk = file_obj.read(CHUNKSIZE) | |
loop += 1 | |
conn.send('0\r\n\r\n') | |
except socket.error: | |
# If server close connection as expected this where we will end up. | |
resp = conn.getresponse() | |
return loop == 1 and resp.status == 200 | |
return False | |
def test_eventlet(): | |
try: | |
from eventlet import wsgi | |
import eventlet | |
except ImportError: | |
print "Skip Eventlet: Not installed" | |
return | |
return _run_test(wsgi.server, args=(eventlet.listen(('', PORT)), dummy_app)) | |
def test_cherrypy(): | |
try: | |
from cherrypy import wsgiserver | |
except ImportError: | |
print "Skip CherryPy: Not installed" | |
return | |
server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', PORT), dummy_app) | |
return _run_test(server.start) | |
def test_gevent(): | |
try: | |
from gevent import pywsgi | |
except ImportError: | |
print "Skip Gevent: Not installed" | |
return | |
server = pywsgi.WSGIServer(('', PORT), dummy_app) | |
return _run_test(server.serve_forever) | |
def test_wsgiref(): | |
from wsgiref.simple_server import make_server | |
server = make_server('', PORT, dummy_app) | |
return _run_test(server.serve_forever) | |
def test_werkzeug(): | |
try: | |
from werkzeug.serving import run_simple | |
except ImportError: | |
print "Skip Werkzeug: Not installed" | |
return | |
return _run_test(run_simple, args=('', PORT, dummy_app)) | |
def test_paste(): | |
try: | |
from paste import httpserver | |
except ImportError: | |
print "Skip Paste: Not installed" | |
return | |
return _run_test(httpserver.serve, args=(dummy_app, '0.0.0.0', PORT)) | |
def test_tornado(): | |
try: | |
import tornado.httpserver | |
import tornado.ioloop | |
import tornado.wsgi | |
except ImportError: | |
print "Skip Tornado: Not installed" | |
return | |
container = tornado.wsgi.WSGIContainer(dummy_app) | |
http_server = tornado.httpserver.HTTPServer(container) | |
http_server.listen(PORT) | |
return _run_test(tornado.ioloop.IOLoop.instance().start) | |
def test_twisted(): | |
try: | |
from twisted.web.server import Site | |
from twisted.web.wsgi import WSGIResource | |
from twisted.internet import reactor | |
except ImportError: | |
print "Skip Twisted: Not installed" | |
return | |
resource = WSGIResource(reactor, reactor.getThreadPool(), dummy_app) | |
reactor.listenTCP(PORT, Site(resource)) | |
return _run_test(reactor.run) | |
def main(argv=sys.argv[1:]): | |
"""This script can be called to limit the tests to run like this: | |
$ script.py eventlet gevent | |
""" | |
global PORT | |
worked = [] | |
for func_name in globals(): | |
if func_name.startswith('test'): | |
framework = func_name.split('_')[1] | |
if argv and framework not in argv: | |
continue | |
print "********* %s **********" % framework.upper() | |
success = globals()[func_name]() | |
if not (success ^ (HEADERS.get('Connection') == 'close')): | |
worked.append(framework) | |
# Increment port so there will be no socket already in use errors. | |
PORT += 1 | |
print '\nSuccess: %s' % ', '.join(worked or ['None']) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment