Repository: benoitc/gunicorn

Issue:
Response silently fails if app_iter returns unicode
If you pass an iterator as your response, and the iterator `yield`s unicode, Gunicorn will silently abort the request.

I'm attaching a pull request to fix this. It may also be worth considering coercing iterator output to strings, to avoid the issue altogether, but I don't know enough to say whether that's appropriate.

Below is a minimal test case to demonstrate the problem. Here are the steps I witnessed, stepping through the response process with pdb. All line numbers are referenced according to [version 18 of gunicorn/workers/sync.py](https://github.com/benoitc/gunicorn/blob/18.0/gunicorn/workers/sync.py).
- In my view: `yield u"test\n"`
- In sync.py, line 137: `resp.write(item)`
- Exception raised: `AssertionError: u'test\n' is not a byte.`
- `Except` clause on line 146 is reached
- `if` condition on line 147 is triggered
- `raise StopIteration()` on line 155 is reached
- `finally` clause on line 159 is reached, bypassing the error handling clause on line 157.

Here's the sample view (written for Pyramid):

```
@view_config(route_name='test')                                                                                                                                                             
def test(request):                                                                                                                                                                          
    def test_():                                                                                                                                                                            
        while True:                                                                                                                                                                         
            yield u"test\n"                                                                                                                                                                 
            time.sleep(.5)                                                                                                                                                                  
    return Response(content_type="text/event-stream", app_iter=test_())
```

Response silently fails if app_iter returns unicode
If you pass an iterator as your response, and the iterator `yield`s unicode, Gunicorn will silently abort the request.

I'm attaching a pull request to fix this. It may also be worth considering coercing iterator output to strings, to avoid the issue altogether, but I don't know enough to say whether that's appropriate.

Below is a minimal test case to demonstrate the problem. Here are the steps I witnessed, stepping through the response process with pdb. All line numbers are referenced according to [version 18 of gunicorn/workers/sync.py](https://github.com/benoitc/gunicorn/blob/18.0/gunicorn/workers/sync.py).
- In my view: `yield u"test\n"`
- In sync.py, line 137: `resp.write(item)`
- Exception raised: `AssertionError: u'test\n' is not a byte.`
- `Except` clause on line 146 is reached
- `if` condition on line 147 is triggered
- `raise StopIteration()` on line 155 is reached
- `finally` clause on line 159 is reached, bypassing the error handling clause on line 157.

Here's the sample view (written for Pyramid):

```
@view_config(route_name='test')                                                                                                                                                             
def test(request):                                                                                                                                                                          
    def test_():                                                                                                                                                                            
        while True:                                                                                                                                                                         
            yield u"test\n"                                                                                                                                                                 
            time.sleep(.5)                                                                                                                                                                  
    return Response(content_type="text/event-stream", app_iter=test_())
```



Discussion:

Resolving patch:
diff --git a/gunicorn/workers/async.py b/gunicorn/workers/async.py
--- a/gunicorn/workers/async.py
+++ b/gunicorn/workers/async.py
@@ -112,6 +112,7 @@ def handle_request(self, listener, req, sock, addr):
             if resp and resp.headers_sent:
                 # If the requests have already been sent, we should close the
                 # connection to indicate the error.
+                self.log.exception("Error handling request")
                 try:
                     sock.shutdown(socket.SHUT_RDWR)
                     sock.close()
diff --git a/gunicorn/workers/sync.py b/gunicorn/workers/sync.py
--- a/gunicorn/workers/sync.py
+++ b/gunicorn/workers/sync.py
@@ -102,10 +102,13 @@ def handle(self, listener, client, addr):
                 self.log.debug("Error processing SSL request.")
                 self.handle_error(req, client, addr, e)
         except socket.error as e:
-            if e.args[0] != errno.EPIPE:
-                self.log.exception("Error processing request.")
+            if e.args[0] not in (errno.EPIPE, errno.ECONNRESET):
+                self.log.exception("Socket error processing request.")
             else:
-                self.log.debug("Ignoring EPIPE")
+                if e.args[0] == errno.ECONNRESET:
+                    self.log.debug("Ignoring connection reset")
+                else:
+                    self.log.debug("Ignoring EPIPE")
         except Exception as e:
             self.handle_error(req, client, addr, e)
         finally:
@@ -142,19 +145,18 @@ def handle_request(self, listener, req, client, addr):
                     respiter.close()
         except socket.error:
             raise
-        except Exception as e:
+        except Exception:
             if resp and resp.headers_sent:
                 # If the requests have already been sent, we should close the
                 # connection to indicate the error.
+                self.log.exception("Error handling request")
                 try:
                     client.shutdown(socket.SHUT_RDWR)
                     client.close()
                 except socket.error:
                     pass
                 raise StopIteration()
-            # Only send back traceback in HTTP in debug mode.
-            self.handle_error(req, client, addr, e)
-            return
+            raise
         finally:
             try:
                 self.cfg.post_request(self, req, environ, resp)


Test patch: