From a19a7f385834ecdbe8fbf6c64b9379fa0e4a3be1 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 2 Jan 2010 22:51:34 -0800 Subject: [PATCH] Removed Green_fileobject and moved its contents to GreenPipe. Removed higher_order_send and higher_order_recv, integrating their contents with the appropriate methods on GreenSocket and GreenPipe. Fixed up callsites that were depending on various aspects of Green_fileobject's behavior. Added a backdoor unit test. --- eventlet/backdoor.py | 57 ++++---- eventlet/greenio.py | 287 ++++++++++++++--------------------------- tests/backdoor_test.py | 29 +++++ tests/greenio_test.py | 22 ++-- tests/wsgi_test.py | 130 +++++++++++++------ 5 files changed, 263 insertions(+), 262 deletions(-) create mode 100644 tests/backdoor_test.py diff --git a/eventlet/backdoor.py b/eventlet/backdoor.py index 30b96bb..82d308e 100644 --- a/eventlet/backdoor.py +++ b/eventlet/backdoor.py @@ -3,7 +3,7 @@ import sys import errno from code import InteractiveConsole -from eventlet import api +from eventlet import api, hubs from eventlet.support import greenlets try: @@ -16,25 +16,34 @@ except AttributeError: sys.ps2 = '... ' +class FileProxy(object): + def __init__(self, f): + self.f = f + def writeflush(*a, **kw): + f.write(*a, **kw) + f.flush() + self.fixups = { + 'softspace': 0, + 'isatty': lambda: True, + 'flush': lambda: None, + 'write': writeflush, + 'readline': lambda *a: f.readline(*a).replace('\r\n', '\n'), + } + + def __getattr__(self, attr): + fixups = object.__getattribute__(self, 'fixups') + if attr in fixups: + return fixups[attr] + f = object.__getattribute__(self, 'f') + return getattr(f, attr) + + class SocketConsole(greenlets.greenlet): def __init__(self, desc, hostport, locals): self.hostport = hostport self.locals = locals # mangle the socket - self.desc = desc - readline = desc.readline - self.old = {} - self.fixups = { - 'softspace': 0, - 'isatty': lambda: True, - 'flush': lambda: None, - 'readline': lambda *a: readline(*a).replace('\r\n', '\n'), - } - for key, value in self.fixups.iteritems(): - if hasattr(desc, key): - self.old[key] = getattr(desc, key) - setattr(desc, key, value) - + self.desc = FileProxy(desc) greenlets.greenlet.__init__(self) def run(self): @@ -55,15 +64,6 @@ class SocketConsole(greenlets.greenlet): def finalize(self): # restore the state of the socket - for key in self.fixups: - try: - value = self.old[key] - except KeyError: - delattr(self.desc, key) - else: - setattr(self.desc, key, value) - self.fixups.clear() - self.old.clear() self.desc = None print "backdoor closed to %s:%s" % self.hostport @@ -73,18 +73,18 @@ def backdoor_server(sock, locals=None): accepting connections and running backdoor consoles for each client that connects. """ - print "backdoor server listening on %s:%s" % server.getsockname() + print "backdoor server listening on %s:%s" % sock.getsockname() try: try: while True: - socketpair = server.accept() + socketpair = sock.accept() backdoor(socketpair, locals) except socket.error, e: # Broken pipe means it was shutdown if e[0] != errno.EPIPE: raise finally: - server.close() + sock.close() def backdoor((conn, addr), locals=None): @@ -95,8 +95,7 @@ def backdoor((conn, addr), locals=None): """ host, port = addr print "backdoor to %s:%s" % (host, port) - fl = conn.makeGreenFile("rw") - fl.newlines = '\n' + fl = conn.makefile("rw") console = SocketConsole(fl, (host, port), locals) hub = hubs.get_hub() hub.schedule_call_global(0, console.switch) diff --git a/eventlet/greenio.py b/eventlet/greenio.py index 0d6d1be..e4df071 100644 --- a/eventlet/greenio.py +++ b/eventlet/greenio.py @@ -15,56 +15,11 @@ import warnings from errno import EWOULDBLOCK, EAGAIN -__all__ = ['GreenSocket', 'GreenPipe'] +__all__ = ['GreenSocket', 'GreenPipe', 'shutdown_safe'] -def higher_order_recv(recv_func): - def recv(self, buflen, flags=0): - if self.act_non_blocking: - return self.fd.recv(buflen, flags) - buf = self.recvbuffer - if buf: - chunk, self.recvbuffer = buf[:buflen], buf[buflen:] - return chunk - fd = self.fd - bytes = recv_func(fd, buflen, flags) - if self.gettimeout(): - end = time.time()+self.gettimeout() - else: - end = None - timeout = None - while bytes is None: - try: - if end: - timeout = end - time.time() - trampoline(fd, read=True, timeout=timeout, timeout_exc=socket.timeout) - except socket.timeout: - raise - except socket.error, e: - if e[0] == errno.EPIPE: - bytes = '' - else: - raise - else: - bytes = recv_func(fd, buflen, flags) - self.recvcount += len(bytes) - return bytes - return recv +CONNECT_ERR = set((errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK)) +CONNECT_SUCCESS = set((0, errno.EISCONN)) - -def higher_order_send(send_func): - def send(self, data, flags=0): - if self.act_non_blocking: - return self.fd.send(data, flags) - count = send_func(self.fd, data, flags) - if not count: - return 0 - self.sendcount += count - return count - return send - - -CONNECT_ERR = (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK) -CONNECT_SUCCESS = (0, errno.EISCONN) def socket_connect(descriptor, address): err = descriptor.connect_ex(address) if err in CONNECT_ERR: @@ -83,60 +38,15 @@ def socket_accept(descriptor): raise -def socket_send(descriptor, data, flags=0): - try: - return descriptor.send(data, flags) - except socket.error, e: - if e[0] == errno.EWOULDBLOCK or e[0] == errno.ENOTCONN: - return 0 - raise - if sys.platform[:3]=="win": # winsock sometimes throws ENOTCONN - SOCKET_BLOCKING = (errno.EWOULDBLOCK,) - SOCKET_CLOSED = (errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN) + SOCKET_BLOCKING = set((errno.EWOULDBLOCK,)) + SOCKET_CLOSED = set((errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN)) else: # oddly, on linux/darwin, an unconnected socket is expected to block, # so we treat ENOTCONN the same as EWOULDBLOCK - SOCKET_BLOCKING = (errno.EWOULDBLOCK, errno.ENOTCONN) - SOCKET_CLOSED = (errno.ECONNRESET, errno.ESHUTDOWN) -def socket_recv(descriptor, buflen, flags=0): - try: - return descriptor.recv(buflen, flags) - except socket.error, e: - if e[0] in SOCKET_BLOCKING: - return None - if e[0] in SOCKET_CLOSED: - return '' - raise - - -def file_recv(fd, buflen, flags=0): - try: - return fd.read(buflen) - except IOError, e: - if e[0] == EAGAIN: - return None - return '' - except socket.error, e: - if e[0] == errno.EPIPE: - return '' - raise - - -def file_send(fd, data, flags=0): - try: - fd.write(data) - fd.flush() - return len(data) - except IOError, e: - if e[0] == EAGAIN: - return 0 - except ValueError, e: - written = 0 - except socket.error, e: - if e[0] == errno.EPIPE: - written = 0 + SOCKET_BLOCKING = set((errno.EWOULDBLOCK, errno.ENOTCONN)) + SOCKET_CLOSED = set((errno.ECONNRESET, errno.ESHUTDOWN, errno.EPIPE)) def set_nonblocking(fd): @@ -176,9 +86,6 @@ class GreenSocket(object): set_nonblocking(fd) self.fd = fd self._fileno = fd.fileno() - self.sendcount = 0 - self.recvcount = 0 - self.recvbuffer = '' self.closed = False self.timeout = socket.getdefaulttimeout() @@ -303,9 +210,28 @@ class GreenSocket(object): return socket._fileobject(self.dup(), mode, bufsize) def makeGreenFile(self, mode='r', bufsize=-1): - return Green_fileobject(self.dup()) + warnings.warn("makeGreenFile has been deprecated, please use " + "makefile instead", DeprecationWarning, stacklevel=2) + return self.makefile(mode, bufsize) - recv = higher_order_recv(socket_recv) + def recv(self, buflen, flags=0): + fd = self.fd + if self.act_non_blocking: + return fd.recv(buflen, flags) + while True: + try: + return fd.recv(buflen, flags) + except socket.error, e: + if e[0] in SOCKET_BLOCKING: + pass + elif e[0] in SOCKET_CLOSED: + return '' + else: + raise + trampoline(fd, + read=True, + timeout=self.gettimeout(), + timeout_exc=socket.timeout) def recvfrom(self, *args): if not self.act_non_blocking: @@ -322,13 +248,25 @@ class GreenSocket(object): trampoline(self.fd, read=True, timeout=self.gettimeout(), timeout_exc=socket.timeout) return self.fd.recv_into(*args) - send = higher_order_send(socket_send) + def send(self, data, flags=0): + fd = self.fd + if self.act_non_blocking: + return fd.send(data, flags) + try: + return fd.send(data, flags) + except socket.error, e: + if e[0] in SOCKET_BLOCKING: + return 0 + raise def sendall(self, data, flags=0): fd = self.fd tail = self.send(data, flags) while tail < len(data): - trampoline(self.fd, write=True, timeout_exc=socket.timeout) + trampoline(fd, + write=True, + timeout=self.gettimeout(), + timeout_exc=socket.timeout) tail += self.send(data[tail:], flags) def sendto(self, *args): @@ -371,47 +309,78 @@ class GreenSocket(object): return self.timeout -class Green_fileobject(object): - """Green version of socket._fileobject, for use only with regular - sockets.""" +class GreenPipe(object): newlines = '\r\n' - mode = 'wb+' - def __init__(self, fd): - if isinstance(fd, GreenSocket): - set_nonblocking(fd.fd) - else: - set_nonblocking(fd) - self.sock = fd + set_nonblocking(fd) + self.fd = fd self.closed = False - + self.recvbuffer = '' + def close(self): - self.sock.close() + self.fd.close() self.closed = True - + def fileno(self): - return self.sock.fileno() + return self.fd.fileno() - # TODO next + def read(self, buflen, flags=0): + fd = self.fd + if buflen is None: + buflen = BUFFER_SIZE + buf = self.recvbuffer + if buf: + chunk, self.recvbuffer = buf[:buflen], buf[buflen:] + return chunk + while True: + try: + return fd.read(buflen) + except IOError, e: + if e[0] != EAGAIN: + return '' + except socket.error, e: + if e[0] == errno.EPIPE: + return '' + raise + trampoline(fd, read=True) + + def write(self, data, flags=0): + fd = self.fd + tail = 0 + len_data = len(data) + while tail < len_data: + tosend = data[tail:] + try: + fd.write(tosend) + fd.flush() + tail += len(tosend) + if tail == len_data: + return len_data + except IOError, e: + if e[0] != EAGAIN: + raise + except ValueError, e: + pass + except socket.error, e: + if e[0] != errno.EPIPE: + raise + trampoline(fd, write=True) def flush(self): pass - - def write(self, data): - return self.sock.sendall(data) - + def readuntil(self, terminator, size=None): - buf, self.sock.recvbuffer = self.sock.recvbuffer, '' + buf, self.recvbuffer = self.recvbuffer, '' checked = 0 if size is None: while True: found = buf.find(terminator, checked) if found != -1: found += len(terminator) - chunk, self.sock.recvbuffer = buf[:found], buf[found:] + chunk, self.recvbuffer = buf[:found], buf[found:] return chunk checked = max(0, len(buf) - (len(terminator) - 1)) - d = self.sock.recv(BUFFER_SIZE) + d = self.fd.read(BUFFER_SIZE) if not d: break buf += d @@ -420,25 +389,22 @@ class Green_fileobject(object): found = buf.find(terminator, checked) if found != -1: found += len(terminator) - chunk, self.sock.recvbuffer = buf[:found], buf[found:] + chunk, self.recvbuffer = buf[:found], buf[found:] return chunk checked = len(buf) - d = self.sock.recv(BUFFER_SIZE) + d = self.fd.read(BUFFER_SIZE) if not d: break buf += d - chunk, self.sock.recvbuffer = buf[:size], buf[size:] + chunk, self.recvbuffer = buf[:size], buf[size:] return chunk - + def readline(self, size=None): return self.readuntil(self.newlines, size=size) def __iter__(self): return self.xreadlines() - def readlines(self, size=None): - return list(self.xreadlines(size=size)) - def xreadlines(self, size=None): if size is None: while True: @@ -458,61 +424,6 @@ class Green_fileobject(object): for line in lines: self.write(line) - def read(self, size=None): - if size is not None and not isinstance(size, (int, long)): - raise TypeError('Expecting an int or long for size, got %s: %s' % (type(size), repr(size))) - buf, self.sock.recvbuffer = self.sock.recvbuffer, '' - lst = [buf] - if size is None: - while True: - d = self.sock.recv(BUFFER_SIZE) - if not d: - break - lst.append(d) - else: - buflen = len(buf) - while buflen < size: - d = self.sock.recv(BUFFER_SIZE) - if not d: - break - buflen += len(d) - lst.append(d) - else: - d = lst[-1] - overbite = buflen - size - if overbite: - lst[-1], self.sock.recvbuffer = d[:-overbite], d[-overbite:] - else: - lst[-1], self.sock.recvbuffer = d, '' - return ''.join(lst) - - - -class GreenPipeSocket(GreenSocket): - """ This is a weird class that looks like a socket but expects a file descriptor as an argument instead of a socket. - """ - recv = higher_order_recv(file_recv) - - send = higher_order_send(file_send) - - -class GreenPipe(Green_fileobject): - def __init__(self, fd): - set_nonblocking(fd) - self.fd = GreenPipeSocket(fd) - super(GreenPipe, self).__init__(self.fd) - - def recv(self, *args, **kw): - fn = self.recv = self.fd.recv - return fn(*args, **kw) - - def send(self, *args, **kw): - fn = self.send = self.fd.send - return fn(*args, **kw) - - def flush(self): - self.fd.fd.flush() - # import SSL module here so we can refer to greenio.SSL.exceptionclass try: diff --git a/tests/backdoor_test.py b/tests/backdoor_test.py new file mode 100644 index 0000000..44a6c5a --- /dev/null +++ b/tests/backdoor_test.py @@ -0,0 +1,29 @@ +import eventlet +from eventlet import backdoor +from eventlet.green import socket + +from tests import LimitedTestCase, main + +class BackdoorTest(LimitedTestCase): + def test_server(self): + listener = socket.socket() + listener.bind(('localhost', 0)) + listener.listen(50) + serv = eventlet.spawn(backdoor.backdoor_server, listener) + client = socket.socket() + client.connect(('localhost', listener.getsockname()[1])) + f = client.makefile() + self.assert_('Python' in f.readline()) + f.readline() # build info + f.readline() # help info + self.assert_('InteractiveConsole' in f.readline()) + self.assertEquals('>>> ', f.read(4)) + f.write('print "hi"\n') + f.flush() + self.assertEquals('hi\n', f.readline()) + self.assertEquals('>>> ', f.read(4)) + + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/tests/greenio_test.py b/tests/greenio_test.py index 5b58343..e31c736 100644 --- a/tests/greenio_test.py +++ b/tests/greenio_test.py @@ -47,11 +47,15 @@ class TestGreenIo(LimitedTestCase): # by closing the socket prior to using the made file try: conn, addr = listener.accept() - fd = conn.makeGreenFile() + fd = conn.makefile() conn.close() fd.write('hello\n') fd.close() - self.assertRaises(socket.error, fd.write, 'a') + # socket._fileobjects are odd: writes don't check + # whether the socket is closed or not, and you get an + # AttributeError during flush if it is closed + fd.write('a') + self.assertRaises(Exception, fd.flush) self.assertRaises(socket.error, conn.send, 'b') finally: listener.close() @@ -61,19 +65,20 @@ class TestGreenIo(LimitedTestCase): # by closing the made file and then sending a character try: conn, addr = listener.accept() - fd = conn.makeGreenFile() + fd = conn.makefile() fd.write('hello') fd.close() conn.send('\n') conn.close() - self.assertRaises(socket.error, fd.write, 'a') + fd.write('a') + self.assertRaises(Exception, fd.flush) self.assertRaises(socket.error, conn.send, 'b') finally: listener.close() def did_it_work(server): client = api.connect_tcp(('127.0.0.1', server.getsockname()[1])) - fd = client.makeGreenFile() + fd = client.makefile() client.close() assert fd.readline() == 'hello\n' assert fd.read() == '' @@ -96,16 +101,17 @@ class TestGreenIo(LimitedTestCase): # closing the file object should close everything try: conn, addr = listener.accept() - conn = conn.makeGreenFile() + conn = conn.makefile() conn.write('hello\n') conn.close() - self.assertRaises(socket.error, conn.write, 'a') + conn.write('a') + self.assertRaises(Exception, conn.flush) finally: listener.close() server = api.tcp_listener(('0.0.0.0', 0)) killer = coros.execute(accept_once, server) client = api.connect_tcp(('127.0.0.1', server.getsockname()[1])) - fd = client.makeGreenFile() + fd = client.makefile() client.close() assert fd.read() == 'hello\n' assert fd.read() == '' diff --git a/tests/wsgi_test.py b/tests/wsgi_test.py index e72641c..024df3a 100644 --- a/tests/wsgi_test.py +++ b/tests/wsgi_test.py @@ -83,7 +83,7 @@ class ConnectionClosed(Exception): def read_http(sock): - fd = sock.makeGreenFile() + fd = sock.makefile() try: response_line = fd.readline() except socket.error, exc: @@ -92,11 +92,19 @@ def read_http(sock): raise if not response_line: raise ConnectionClosed - raw_headers = fd.readuntil('\r\n\r\n').strip() - #print "R", response_line, raw_headers + + header_lines = [] + while True: + line = fd.readline() + if line == '\r\n': + break + else: + header_lines.append(line) headers = dict() - for x in raw_headers.split('\r\n'): - #print "X", x + for x in header_lines: + x = x.strip() + if not x: + continue key, value = x.split(': ', 1) headers[key.lower()] = value @@ -134,8 +142,9 @@ class TestHttpd(LimitedTestCase): sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') + fd.flush() result = fd.read() fd.close() ## The server responds with the maximum version it supports @@ -146,10 +155,12 @@ class TestHttpd(LimitedTestCase): sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() read_http(sock) fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() read_http(sock) fd.close() @@ -158,8 +169,9 @@ class TestHttpd(LimitedTestCase): sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() cancel = api.exc_after(1, RuntimeError) self.assertRaises(TypeError, fd.read, "This shouldn't work") cancel.cancel() @@ -169,12 +181,15 @@ class TestHttpd(LimitedTestCase): sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() read_http(sock) fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') + fd.flush() read_http(sock) fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() self.assertRaises(ConnectionClosed, read_http, sock) fd.close() @@ -194,8 +209,9 @@ class TestHttpd(LimitedTestCase): path_parts.append('path') path = '/'.join(path_parts) request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write(request) + fd.flush() result = fd.readline() if result: # windows closes the socket before the data is flushed, @@ -220,8 +236,9 @@ class TestHttpd(LimitedTestCase): 'Content-Length: 3', '', 'a=a')) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write(request) + fd.flush() # send some junk after the actual request fd.write('01234567890123456789') @@ -233,12 +250,15 @@ class TestHttpd(LimitedTestCase): sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() response_line_200,_,_ = read_http(sock) fd.write('GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() response_line_404,_,_ = read_http(sock) fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() response_line_test,_,_ = read_http(sock) self.assertEqual(response_line_200,response_line_test) fd.close() @@ -248,8 +268,9 @@ class TestHttpd(LimitedTestCase): sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') + fd.flush() self.assert_('Transfer-Encoding: chunked' in fd.read()) def test_010_no_chunked_http_1_0(self): @@ -257,8 +278,9 @@ class TestHttpd(LimitedTestCase): sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n') + fd.flush() self.assert_('Transfer-Encoding: chunked' not in fd.read()) def test_011_multiple_chunks(self): @@ -266,9 +288,16 @@ class TestHttpd(LimitedTestCase): sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - headers = fd.readuntil('\r\n\r\n') + fd.flush() + headers = '' + while True: + line = fd.readline() + if line == '\r\n': + break + else: + headers += line self.assert_('Transfer-Encoding: chunked' in headers) chunks = 0 chunklen = int(fd.readline(), 16) @@ -316,43 +345,54 @@ class TestHttpd(LimitedTestCase): def test_014_chunked_post(self): self.site.application = chunked_post sock = api.connect_tcp(('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('PUT /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n' 'Transfer-Encoding: chunked\r\n\r\n' '2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n') - fd.readuntil('\r\n\r\n') + fd.flush() + while True: + if fd.readline() == '\r\n': + break response = fd.read() self.assert_(response == 'oh hai', 'invalid response %s' % response) sock = api.connect_tcp(('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('PUT /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n' 'Transfer-Encoding: chunked\r\n\r\n' '2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n') - fd.readuntil('\r\n\r\n') + fd.flush() + while True: + if fd.readline() == '\r\n': + break response = fd.read() self.assert_(response == 'oh hai', 'invalid response %s' % response) sock = api.connect_tcp(('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('PUT /c HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n' 'Transfer-Encoding: chunked\r\n\r\n' '2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n') - fd.readuntil('\r\n\r\n') + fd.flush() + while True: + if fd.readline() == '\r\n': + break response = fd.read(8192) self.assert_(response == 'oh hai', 'invalid response %s' % response) def test_015_write(self): self.site.application = use_write sock = api.connect_tcp(('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') + fd.flush() response_line, headers, body = read_http(sock) self.assert_('content-length' in headers) sock = api.connect_tcp(('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') + fd.flush() response_line, headers, body = read_http(sock) self.assert_('transfer-encoding' in headers) self.assert_(headers['transfer-encoding'] == 'chunked') @@ -367,10 +407,17 @@ class TestHttpd(LimitedTestCase): return ['testing'] self.site.application = wsgi_app sock = api.connect_tcp(('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - headerlines = fd.readuntil('\r\n\r\n').splitlines() - self.assertEquals(1, len([l for l in headerlines + fd.flush() + header_lines = [] + while True: + line = fd.readline() + if line == '\r\n': + break + else: + header_lines.append(line) + self.assertEquals(1, len([l for l in header_lines if l.lower().startswith('content-length')])) def test_017_ssl_zeroreturnerror(self): @@ -413,14 +460,19 @@ class TestHttpd(LimitedTestCase): sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n') - self.assert_('connection: keep-alive' in - fd.readuntil('\r\n\r\n').lower()) + fd.flush() + + response_line, headers, body = read_http(sock) + self.assert_('connection' in headers) + self.assertEqual('keep-alive', headers['connection']) # repeat request to verify connection is actually still open - fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n') - self.assert_('connection: keep-alive' in - fd.readuntil('\r\n\r\n').lower()) + fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n') + fd.flush() + response_line, headers, body = read_http(sock) + self.assert_('connection' in headers) + self.assertEqual('keep-alive', headers['connection']) def test_019_fieldstorage_compat(self): def use_fieldstorage(environ, start_response): @@ -434,13 +486,14 @@ class TestHttpd(LimitedTestCase): sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('POST / HTTP/1.1\r\n' 'Host: localhost\r\n' 'Connection: close\r\n' 'Transfer-Encoding: chunked\r\n\r\n' '2\r\noh\r\n' '4\r\n hai\r\n0\r\n\r\n') + fd.flush() self.assert_('hello!' in fd.read()) def test_020_x_forwarded_for(self): @@ -484,11 +537,12 @@ class TestHttpd(LimitedTestCase): return [] self.site.application = clobberin_time sock = api.connect_tcp(('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.1\r\n' 'Host: localhost\r\n' 'Connection: close\r\n' '\r\n\r\n') + fd.flush() self.assert_('200 OK' in fd.read()) def test_022_custom_pool(self): @@ -511,8 +565,9 @@ class TestHttpd(LimitedTestCase): # this stuff is copied from test_001_server, could be better factored sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') + fd.flush() result = fd.read() fd.close() self.assert_(result.startswith('HTTP'), result) @@ -521,8 +576,9 @@ class TestHttpd(LimitedTestCase): def test_023_bad_content_length(self): sock = api.connect_tcp( ('localhost', self.port)) - fd = sock.makeGreenFile() + fd = sock.makefile() fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nContent-length: argh\r\n\r\n') + fd.flush() result = fd.read() fd.close() self.assert_(result.startswith('HTTP'), result)