Skip to content

Commit

Permalink
Improve adherence to the WSGI spec.
Browse files Browse the repository at this point in the history
- Call close method if present on iterables returned by start_response.
- Don't include non-string values in the CGI environment.
- Always include QUERY_STRING to avoid the cgi module falling back to
  sys.argv.
- Add tests based on paste.lint middleware.
  • Loading branch information
kilink committed Mar 13, 2013
1 parent c679f60 commit 4df1b40
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 8 deletions.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def read(*rnames):
'zope.testing',
'zope.i18n',
'zope.component',
'Paste',
]


Expand Down
10 changes: 4 additions & 6 deletions src/zope/server/http/httptask.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,10 @@ def getCGIEnvironment(self):
env['SERVER_NAME'] = server.server_name
env['SERVER_SOFTWARE'] = server.SERVER_IDENT
env['SERVER_PROTOCOL'] = "HTTP/%s" % self.version
env['CHANNEL_CREATION_TIME'] = channel.creation_time
env['SCRIPT_NAME']=''
env['PATH_INFO']='/' + path
query = request_data.query
if query:
env['QUERY_STRING'] = query
env['CHANNEL_CREATION_TIME'] = str(channel.creation_time)
env['SCRIPT_NAME']= ''
env['PATH_INFO']= '/' + path
env['QUERY_STRING'] = request_data.query or ''
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
addr = channel.addr[0]
env['REMOTE_ADDR'] = addr
Expand Down
56 changes: 56 additions & 0 deletions src/zope/server/http/tests/test_wsgiserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
"""Test Publisher-based HTTP Server
"""
import StringIO
import paste.lint
import sys
import unittest
import warnings

from asyncore import socket_map, poll
from threading import Thread
from time import sleep
Expand Down Expand Up @@ -412,6 +415,59 @@ def test_start_response_with_headers_sent(self):

self.server.application = orig_app


def test_closes_iterator(self):
"""PEP-0333 specifies that if an iterable returned by
a WSGI application has a 'close' method, it must
be called.
paste.lint has this check as well, but it writes a
message to stderr instead of raising an error or
issuing a warning.
"""
orig_app = self.server.application
app, _ = self._getFakeAppAndTask()

class CloseableIterator(object):

closed = False

def __init__(self, value):
self._iter = iter(value)
self.value = value

def __iter__(self):
return self

def next(self):
return self._iter.next()

def close(self):
self.closed = True

iterator = CloseableIterator(["Klaatu", "barada", "nikto"])
def app(environ, start_response):
start_response("200 Ok", [], None)
return iterator

self.server.application = app
self.invokeRequest("/")
self.assertTrue(iterator.closed,
"close method wasn't called on iterable")

self.server.application = orig_app

def test_wsgi_compliance(self):
orig_app = self.server.application
self.server.application = paste.lint.middleware(orig_app)

with warnings.catch_warnings(record=True) as w:
self.invokeRequest("/foo")
self.assertTrue(len(w) == 0, w)
self.server.application = orig_app



class PMDBTests(Tests):

def _getServerClass(self):
Expand Down
12 changes: 10 additions & 2 deletions src/zope/server/http/wsgihttpserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,12 @@ def executeRequest(self, task):

# By iterating manually at this point, we execute task.write()
# multiple times, allowing partial data to be sent.
for value in result:
task.write(value)
try:
for value in result:
task.write(value)
finally:
if hasattr(result, "close"):
result.close()


class PMDBWSGIHTTPServer(WSGIHTTPServer):
Expand All @@ -123,6 +127,7 @@ def executeRequest(self, task):
env['wsgi.handleErrors'] = False

# Call the application to handle the request and write a response
result = None
try:
result = self.application(env, curriedStartResponse(task))
# By iterating manually at this point, we execute task.write()
Expand All @@ -139,6 +144,9 @@ def executeRequest(self, task):
raise
finally:
zope.security.management.endInteraction()
finally:
if hasattr(result, "close"):
result.close()


def run_paste(wsgi_app, global_conf, name='zope.server.http',
Expand Down

0 comments on commit 4df1b40

Please sign in to comment.