Repository: Pylons/webob

Issue:
Seems WebOb 1.7 has some glitches with WebTest when a 204 is responded
The recent WebOb 1.7 changes to initialisation function seem to had introduced a side effect when WebTest is used, as webtest will recreate a new `Response` object from the application response.

A simple testcase can be created to showcase the issue:

```
from webob import Response

def wsgi_app(environ, start_response):
    resp = Response(status='204 No Content', body=b'')
    resp.content_type = None
    return resp(environ, start_response)
```

The snippet is meant to work on both 1.6 and 1.7, so it adds some unnecessary values like `body` and `content_type` which are already provided in 1.7 (those values are in fact a nop on 1.7) while they are required on 1.6 to generate a response without content.

The response is as expected both on 1.6 and 1.7

```
Response: 204 No Content


```

Creating a plain webtest `TestApp` from the previous example:

```
from webtest import TestApp

app = TestApp(wsgi_app)
app.get('/')
```

will issue a validation error on WebOb 1.7:

```
Iterator garbage collected without being closedException AssertionError: AssertionError('Iterator garbage collected without being closed',) in <bound method IteratorWrapper.__del__ of <webtest.lint.IteratorWrapper object at 0x100ed0850>> ignored
```

while it will work without the error with WebOb 1.6.

This seems to be related to https://github.com/Pylons/webob/commit/35fd5854b4b706adafa4caab43b6bcdcf3e6a934 as https://github.com/Pylons/webob/blob/master/webob/response.py#L323 throws away the webtest `IteratorWrapper` injected by WebTest replacing it with `[b'']` as `code_has_body` is False and thus breaking `WebTest`

I wouldn't take for granted that if there is an `app_iter` it will be wrong and needs to be discarded as response has no content. It's probably a right assumption in the case of `body`, but discarding `app_iter` might in fact break some application by discarding an iterator which returns an empty response too but has other side effects.

Comments:

Patch:
diff --git a/.travis.yml b/.travis.yml
index 7037c57..895d219 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,7 +12,7 @@ matrix:
           env: TOXENV=py34
         - python: 3.5
           env: TOXENV=py35
-        - python: 3.6-dev
+        - python: 3.6
           env: TOXENV=py36
         - python: nightly
           env: TOXENV=py37
@@ -21,7 +21,6 @@ matrix:
         - python: 3.5
           env: TOXENV=py2-cover,py3-cover,coverage
     allow_failures:
-        - env: TOXENV=py36
         - env: TOXENV=py37
 
 install:
diff --git a/CHANGES.txt b/CHANGES.txt
index 393cd8d..2105fe8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,13 @@
 Unreleased
 ----------
 
+Bugfix
+~~~~~~
 
+- ``Response.__init__`` would discard ``app_iter`` when a ``Response`` had no
+  body, this would cause issues when ``app_iter`` was an object that was tied
+  to the life-cycle of a web application and had to be properly closed.
+  ``app_iter`` is more advanced API for ``Response`` and thus even if it
+  contains a body and is thus against the HTTP RFC's, we should let the users
+  shoot themselves by returning a body. See
+  https://github.com/Pylons/webob/issues/305
diff --git a/setup.py b/setup.py
index a1418c5..d74f25e 100644
--- a/setup.py
+++ b/setup.py
@@ -38,6 +38,7 @@ setup(
         "Programming Language :: Python :: 3.3",
         "Programming Language :: Python :: 3.4",
         "Programming Language :: Python :: 3.5",
+        "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: Implementation :: CPython",
         "Programming Language :: Python :: Implementation :: PyPy",
     ],
diff --git a/webob/response.py b/webob/response.py
index 899fc99..cd376b9 100644
--- a/webob/response.py
+++ b/webob/response.py
@@ -320,7 +320,7 @@ class Response(object):
                     if k.lower() != 'content-length'
                 ]
             self._headerlist.append(('Content-Length', str(len(body))))
-        elif app_iter is None or not code_has_body:
+        elif app_iter is None and not code_has_body:
             app_iter = [b'']
 
         self._app_iter = app_iter


Test patch:
diff --git a/tests/test_response.py b/tests/test_response.py
index 1d272c6..8e9b3ae 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -1316,7 +1316,9 @@ def test_204_has_no_body():
 
 def test_204_app_iter_set():
     res = Response(status='204', app_iter=[b'test'])
-    assert res.body == b''
+
+    # You are on your own in this case... you set app_iter you bought it
+    assert res.body == b'test'
     assert res.content_length is None
     assert res.headerlist == []
 
@@ -1353,3 +1355,13 @@ def test_content_type_has_charset():
     assert res.content_type == 'application/foo'
     assert res.charset == 'UTF-8'
     assert res.headers['Content-Type'] == 'application/foo; charset=UTF-8'
+
+def test_app_iter_is_same():
+    class app_iter(object):
+        pass
+
+    my_app_iter = app_iter()
+
+    res = Response(status=204, app_iter=my_app_iter)
+    assert res.app_iter == my_app_iter
+    assert isinstance(res.app_iter, app_iter)