Skip to content

Commit df0bc00

Browse files
authored
wsgi: websocket ALREADY_HANDLED flag on corolocal
#543
1 parent 377b4fb commit df0bc00

4 files changed

Lines changed: 47 additions & 22 deletions

File tree

eventlet/websocket.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ def __call__(self, environ, start_response):
135135
ws._send_closing_frame(True)
136136
# use this undocumented feature of eventlet.wsgi to ensure that it
137137
# doesn't barf on the fact that we didn't call start_response
138-
return wsgi.ALREADY_HANDLED
138+
wsgi.WSGI_LOCAL.already_handled = True
139+
return []
139140

140141
def _handle_legacy_request(self, environ):
141142
if 'eventlet.input' in environ:

eventlet/wsgi.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import eventlet
1010
from eventlet import greenio
1111
from eventlet import support
12+
from eventlet.corolocal import local
1213
from eventlet.green import BaseHTTPServer
1314
from eventlet.green import socket
1415
import six
@@ -69,19 +70,7 @@ class ChunkReadError(ValueError):
6970
pass
7071

7172

72-
# special flag return value for apps
73-
class _AlreadyHandled(object):
74-
75-
def __iter__(self):
76-
return self
77-
78-
def next(self):
79-
raise StopIteration
80-
81-
__next__ = next
82-
83-
84-
ALREADY_HANDLED = _AlreadyHandled()
73+
WSGI_LOCAL = local()
8574

8675

8776
class Input(object):
@@ -570,14 +559,12 @@ def cap(x):
570559

571560
try:
572561
try:
562+
WSGI_LOCAL.already_handled = False
573563
result = self.application(self.environ, start_response)
574-
if (isinstance(result, _AlreadyHandled)
575-
or isinstance(getattr(result, '_obj', None), _AlreadyHandled)):
576-
self.close_connection = 1
577-
return
578564

579565
# Set content-length if possible
580-
if not headers_sent and hasattr(result, '__len__') and \
566+
if headers_set and \
567+
not headers_sent and hasattr(result, '__len__') and \
581568
'Content-Length' not in [h for h, _v in headers_set[1]]:
582569
headers_set[1].append(('Content-Length', str(sum(map(len, result)))))
583570

@@ -599,6 +586,9 @@ def cap(x):
599586
towrite = []
600587
just_written_size = towrite_size
601588
towrite_size = 0
589+
if WSGI_LOCAL.already_handled:
590+
self.close_connection = 1
591+
return
602592
if towrite:
603593
just_written_size = towrite_size
604594
write(b''.join(towrite))

tests/websocket_test.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,38 @@ def test_close_idle(self):
528528
with eventlet.Timeout(1):
529529
pool.waitall()
530530

531+
def test_wrapped_wsgi(self):
532+
site = self.site
533+
534+
def wrapper(environ, start_response):
535+
for chunk in site(environ, start_response):
536+
yield chunk
537+
538+
self.site = wrapper
539+
self.spawn_server()
540+
connect = [
541+
"GET /range HTTP/1.1",
542+
"Upgrade: WebSocket",
543+
"Connection: Upgrade",
544+
"Host: {}:{}".format(*self.server_addr),
545+
"Origin: http://{}:{}".format(*self.server_addr),
546+
"WebSocket-Protocol: ws",
547+
]
548+
sock = eventlet.connect(self.server_addr)
549+
550+
sock.sendall(six.b("\r\n".join(connect) + "\r\n\r\n"))
551+
resp = sock.recv(1024)
552+
headers, result = resp.split(b"\r\n\r\n")
553+
msgs = [result.strip(b"\x00\xff")]
554+
msgs.extend(sock.recv(20).strip(b"\x00\xff") for _ in range(10))
555+
expect = [six.b("msg {}".format(i)) for i in range(10)] + [b""]
556+
assert msgs == expect
557+
# In case of server error, server will write HTTP 500 response to the socket
558+
msg = sock.recv(20)
559+
assert not msg
560+
sock.close()
561+
eventlet.sleep(0.01)
562+
531563

532564
class TestWebSocketSSL(tests.wsgi_test._TestBase):
533565
def set_site(self):

tests/wsgi_test.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ def chunked_post(env, start_response):
103103

104104
def already_handled(env, start_response):
105105
start_response('200 OK', [('Content-type', 'text/plain')])
106-
return wsgi.ALREADY_HANDLED
106+
wsgi.WSGI_LOCAL.already_handled = True
107+
return []
107108

108109

109110
class Site(object):
@@ -115,8 +116,7 @@ def __call__(self, env, start_response):
115116

116117

117118
class IterableApp(object):
118-
119-
def __init__(self, send_start_response=False, return_val=wsgi.ALREADY_HANDLED):
119+
def __init__(self, send_start_response=False, return_val=()):
120120
self.send_start_response = send_start_response
121121
self.return_val = return_val
122122
self.env = {}
@@ -125,6 +125,8 @@ def __call__(self, env, start_response):
125125
self.env = env
126126
if self.send_start_response:
127127
start_response('200 OK', [('Content-type', 'text/plain')])
128+
else:
129+
wsgi.WSGI_LOCAL.already_handled = True
128130
return self.return_val
129131

130132

0 commit comments

Comments
 (0)