Repository: pallets/werkzeug

Issue:
ClosingIterator does not close wrapped iterable
`ClosingIterator` does not respect [wsgi specification](https://www.python.org/dev/peps/pep-3333) namely this piece:

> If the iterable returned by the application has a close() method, the server or gateway must call that method upon completion of the current request

The problem is that `ClosingIterator` tries to invoke `close` on the _iterator_ returned by the iterable  but it should invoke `close` on the _iterable_ itself. 

Here's relevant part of the [example server](https://www.python.org/dev/peps/pep-3333/#the-server-gateway-side) implementation from the wsgi specification:

```
    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result, 'close'):
            result.close()
```

Note that close is invoked on the object returned by the `application`.

This causes the problem as close is not invoked at all. I face this problem in a django application that runs in gunicorn which is setup to use [gevent](http://docs.gunicorn.org/en/stable/settings.html#worker-class) async workers. In order to isolate greenlets werkzeug LocalManager is used to wrap django wsgi application. As `close` is not invoked django does not do necessary cleanup and does not close database connections. The problem that is [described](https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/#applying-wsgi-middleware) in django documentation happens:

<img width="836" alt="screen shot 2018-03-01 at 14 44 09" src="https://user-images.githubusercontent.com/1078674/36892180-3febd2f0-1e04-11e8-8bbf-b1133388ca33.png">

Here's the smallest example that demonstrates the problem.
I used `AppClass` from the [wsgi specification](https://www.python.org/dev/peps/pep-3333/#the-application-framework-side) with a slight modification that adds a `close` method to it. 

1. install two requirements into virtual env:
```
uwsgi==2.0.17
Werkzeug==0.14.1
```
2. create `app.py` file with content below
3. run server `bin/uwsgi --http :9090 --wsgi-file app.py`
4. execute request `curl "http://localhost:9090"`

If you set wrap to `False` wsgi application is not wrapped and `closed` is printed to stdout. If the application is wrapped with `ClosingIterator` then `closed` is not invoked and nothing is printed.

`app.py` file:

```
class AppClass:
    def __init__(self, environ, start_response):
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield b"Hello World"

    def close(self):
        print "closed"

wrap = True

if wrap:
    def application(environ, start_response):
        from werkzeug.wsgi import ClosingIterator
        return ClosingIterator(AppClass(environ, start_response))
else:
    application = AppClass
```


Discussion:

Resolving patch:
diff --git a/werkzeug/wsgi.py b/werkzeug/wsgi.py
--- a/werkzeug/wsgi.py
+++ b/werkzeug/wsgi.py
@@ -841,9 +841,10 @@ def __call__(self, environ, start_response):
 class ClosingIterator(object):
 
     """The WSGI specification requires that all middlewares and gateways
-    respect the `close` callback of an iterator.  Because it is useful to add
-    another close action to a returned iterator and adding a custom iterator
-    is a boring task this class can be used for that::
+    respect the `close` callback of the iterable returned by the application.
+    Because it is useful to add another close action to a returned iterable
+    and adding a custom iterable is a boring task this class can be used for
+    that::
 
         return ClosingIterator(app(environ, start_response), [cleanup_session,
                                                               cleanup_locals])
@@ -869,7 +870,7 @@ def __init__(self, iterable, callbacks=None):
             callbacks = [callbacks]
         else:
             callbacks = list(callbacks)
-        iterable_close = getattr(iterator, 'close', None)
+        iterable_close = getattr(iterable, 'close', None)
         if iterable_close:
             callbacks.insert(0, iterable_close)
         self._callbacks = callbacks


Test patch:
diff --git a/tests/test_wsgi.py b/tests/test_wsgi.py
--- a/tests/test_wsgi.py
+++ b/tests/test_wsgi.py
@@ -19,12 +19,12 @@
 from tests import strict_eq
 from werkzeug import wsgi
 from werkzeug._compat import BytesIO, NativeStringIO, StringIO, to_bytes, \
-    to_native
+        to_native
 from werkzeug.exceptions import BadRequest, ClientDisconnected
 from werkzeug.test import Client, create_environ, run_wsgi_app
 from werkzeug.wrappers import BaseResponse
 from werkzeug.urls import url_parse
-from werkzeug.wsgi import _RangeWrapper, wrap_file
+from werkzeug.wsgi import _RangeWrapper, ClosingIterator, wrap_file
 
 
 def test_shareddatamiddleware_get_file_loader():
@@ -555,3 +555,36 @@ def app(request):
     # test query string
     rv = client.get('/bar/baz?a=a&b=b')
     assert rv.data.decode('ascii') == 'bar|localhost|/baz?a=a&b=b'
+
+
+def test_closing_iterator():
+    class Namespace(object):
+        got_close = False
+        got_additional = False
+
+    class Response(object):
+        def __init__(self, environ, start_response):
+            self.start = start_response
+
+        # Return a generator instead of making the object its own
+        # iterator. This ensures that ClosingIterator calls close on
+        # the iterable (the object), not the iterator.
+        def __iter__(self):
+            self.start('200 OK', [('Content-Type', 'text/plain')])
+            yield 'some content'
+
+        def close(self):
+            Namespace.got_close = True
+
+    def additional():
+        Namespace.got_additional = True
+
+    def app(environ, start_response):
+        return ClosingIterator(Response(environ, start_response), additional)
+
+    app_iter, status, headers = run_wsgi_app(
+        app, create_environ(), buffered=True)
+
+    assert ''.join(app_iter) == 'some content'
+    assert Namespace.got_close
+    assert Namespace.got_additional