Skip to content

Instantly share code, notes, and snippets.

@mouadino
Last active December 16, 2015 15:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mouadino/5458786 to your computer and use it in GitHub Desktop.
Save mouadino/5458786 to your computer and use it in GitHub Desktop.
"""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