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.

This commit is contained in:
Ryan Williams
2010-01-02 22:51:34 -08:00
parent 9791fefac6
commit a19a7f3858
5 changed files with 263 additions and 262 deletions

View File

@@ -3,7 +3,7 @@ import sys
import errno import errno
from code import InteractiveConsole from code import InteractiveConsole
from eventlet import api from eventlet import api, hubs
from eventlet.support import greenlets from eventlet.support import greenlets
try: try:
@@ -16,25 +16,34 @@ except AttributeError:
sys.ps2 = '... ' 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): class SocketConsole(greenlets.greenlet):
def __init__(self, desc, hostport, locals): def __init__(self, desc, hostport, locals):
self.hostport = hostport self.hostport = hostport
self.locals = locals self.locals = locals
# mangle the socket # mangle the socket
self.desc = desc self.desc = FileProxy(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)
greenlets.greenlet.__init__(self) greenlets.greenlet.__init__(self)
def run(self): def run(self):
@@ -55,15 +64,6 @@ class SocketConsole(greenlets.greenlet):
def finalize(self): def finalize(self):
# restore the state of the socket # 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 self.desc = None
print "backdoor closed to %s:%s" % self.hostport 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 accepting connections and running backdoor consoles for each client that
connects. connects.
""" """
print "backdoor server listening on %s:%s" % server.getsockname() print "backdoor server listening on %s:%s" % sock.getsockname()
try: try:
try: try:
while True: while True:
socketpair = server.accept() socketpair = sock.accept()
backdoor(socketpair, locals) backdoor(socketpair, locals)
except socket.error, e: except socket.error, e:
# Broken pipe means it was shutdown # Broken pipe means it was shutdown
if e[0] != errno.EPIPE: if e[0] != errno.EPIPE:
raise raise
finally: finally:
server.close() sock.close()
def backdoor((conn, addr), locals=None): def backdoor((conn, addr), locals=None):
@@ -95,8 +95,7 @@ def backdoor((conn, addr), locals=None):
""" """
host, port = addr host, port = addr
print "backdoor to %s:%s" % (host, port) print "backdoor to %s:%s" % (host, port)
fl = conn.makeGreenFile("rw") fl = conn.makefile("rw")
fl.newlines = '\n'
console = SocketConsole(fl, (host, port), locals) console = SocketConsole(fl, (host, port), locals)
hub = hubs.get_hub() hub = hubs.get_hub()
hub.schedule_call_global(0, console.switch) hub.schedule_call_global(0, console.switch)

View File

@@ -15,56 +15,11 @@ import warnings
from errno import EWOULDBLOCK, EAGAIN from errno import EWOULDBLOCK, EAGAIN
__all__ = ['GreenSocket', 'GreenPipe'] __all__ = ['GreenSocket', 'GreenPipe', 'shutdown_safe']
def higher_order_recv(recv_func): CONNECT_ERR = set((errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK))
def recv(self, buflen, flags=0): CONNECT_SUCCESS = set((0, errno.EISCONN))
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
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): def socket_connect(descriptor, address):
err = descriptor.connect_ex(address) err = descriptor.connect_ex(address)
if err in CONNECT_ERR: if err in CONNECT_ERR:
@@ -83,60 +38,15 @@ def socket_accept(descriptor):
raise 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": if sys.platform[:3]=="win":
# winsock sometimes throws ENOTCONN # winsock sometimes throws ENOTCONN
SOCKET_BLOCKING = (errno.EWOULDBLOCK,) SOCKET_BLOCKING = set((errno.EWOULDBLOCK,))
SOCKET_CLOSED = (errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN) SOCKET_CLOSED = set((errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN))
else: else:
# oddly, on linux/darwin, an unconnected socket is expected to block, # oddly, on linux/darwin, an unconnected socket is expected to block,
# so we treat ENOTCONN the same as EWOULDBLOCK # so we treat ENOTCONN the same as EWOULDBLOCK
SOCKET_BLOCKING = (errno.EWOULDBLOCK, errno.ENOTCONN) SOCKET_BLOCKING = set((errno.EWOULDBLOCK, errno.ENOTCONN))
SOCKET_CLOSED = (errno.ECONNRESET, errno.ESHUTDOWN) SOCKET_CLOSED = set((errno.ECONNRESET, errno.ESHUTDOWN, errno.EPIPE))
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
def set_nonblocking(fd): def set_nonblocking(fd):
@@ -176,9 +86,6 @@ class GreenSocket(object):
set_nonblocking(fd) set_nonblocking(fd)
self.fd = fd self.fd = fd
self._fileno = fd.fileno() self._fileno = fd.fileno()
self.sendcount = 0
self.recvcount = 0
self.recvbuffer = ''
self.closed = False self.closed = False
self.timeout = socket.getdefaulttimeout() self.timeout = socket.getdefaulttimeout()
@@ -303,9 +210,28 @@ class GreenSocket(object):
return socket._fileobject(self.dup(), mode, bufsize) return socket._fileobject(self.dup(), mode, bufsize)
def makeGreenFile(self, mode='r', bufsize=-1): 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): def recvfrom(self, *args):
if not self.act_non_blocking: 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) trampoline(self.fd, read=True, timeout=self.gettimeout(), timeout_exc=socket.timeout)
return self.fd.recv_into(*args) 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): def sendall(self, data, flags=0):
fd = self.fd fd = self.fd
tail = self.send(data, flags) tail = self.send(data, flags)
while tail < len(data): 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) tail += self.send(data[tail:], flags)
def sendto(self, *args): def sendto(self, *args):
@@ -371,47 +309,78 @@ class GreenSocket(object):
return self.timeout return self.timeout
class Green_fileobject(object): class GreenPipe(object):
"""Green version of socket._fileobject, for use only with regular
sockets."""
newlines = '\r\n' newlines = '\r\n'
mode = 'wb+'
def __init__(self, fd): def __init__(self, fd):
if isinstance(fd, GreenSocket): set_nonblocking(fd)
set_nonblocking(fd.fd) self.fd = fd
else:
set_nonblocking(fd)
self.sock = fd
self.closed = False self.closed = False
self.recvbuffer = ''
def close(self): def close(self):
self.sock.close() self.fd.close()
self.closed = True self.closed = True
def fileno(self): 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): def flush(self):
pass pass
def write(self, data):
return self.sock.sendall(data)
def readuntil(self, terminator, size=None): def readuntil(self, terminator, size=None):
buf, self.sock.recvbuffer = self.sock.recvbuffer, '' buf, self.recvbuffer = self.recvbuffer, ''
checked = 0 checked = 0
if size is None: if size is None:
while True: while True:
found = buf.find(terminator, checked) found = buf.find(terminator, checked)
if found != -1: if found != -1:
found += len(terminator) found += len(terminator)
chunk, self.sock.recvbuffer = buf[:found], buf[found:] chunk, self.recvbuffer = buf[:found], buf[found:]
return chunk return chunk
checked = max(0, len(buf) - (len(terminator) - 1)) checked = max(0, len(buf) - (len(terminator) - 1))
d = self.sock.recv(BUFFER_SIZE) d = self.fd.read(BUFFER_SIZE)
if not d: if not d:
break break
buf += d buf += d
@@ -420,14 +389,14 @@ class Green_fileobject(object):
found = buf.find(terminator, checked) found = buf.find(terminator, checked)
if found != -1: if found != -1:
found += len(terminator) found += len(terminator)
chunk, self.sock.recvbuffer = buf[:found], buf[found:] chunk, self.recvbuffer = buf[:found], buf[found:]
return chunk return chunk
checked = len(buf) checked = len(buf)
d = self.sock.recv(BUFFER_SIZE) d = self.fd.read(BUFFER_SIZE)
if not d: if not d:
break break
buf += d buf += d
chunk, self.sock.recvbuffer = buf[:size], buf[size:] chunk, self.recvbuffer = buf[:size], buf[size:]
return chunk return chunk
def readline(self, size=None): def readline(self, size=None):
@@ -436,9 +405,6 @@ class Green_fileobject(object):
def __iter__(self): def __iter__(self):
return self.xreadlines() return self.xreadlines()
def readlines(self, size=None):
return list(self.xreadlines(size=size))
def xreadlines(self, size=None): def xreadlines(self, size=None):
if size is None: if size is None:
while True: while True:
@@ -458,61 +424,6 @@ class Green_fileobject(object):
for line in lines: for line in lines:
self.write(line) 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 # import SSL module here so we can refer to greenio.SSL.exceptionclass
try: try:

29
tests/backdoor_test.py Normal file
View File

@@ -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()

View File

@@ -47,11 +47,15 @@ class TestGreenIo(LimitedTestCase):
# by closing the socket prior to using the made file # by closing the socket prior to using the made file
try: try:
conn, addr = listener.accept() conn, addr = listener.accept()
fd = conn.makeGreenFile() fd = conn.makefile()
conn.close() conn.close()
fd.write('hello\n') fd.write('hello\n')
fd.close() 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') self.assertRaises(socket.error, conn.send, 'b')
finally: finally:
listener.close() listener.close()
@@ -61,19 +65,20 @@ class TestGreenIo(LimitedTestCase):
# by closing the made file and then sending a character # by closing the made file and then sending a character
try: try:
conn, addr = listener.accept() conn, addr = listener.accept()
fd = conn.makeGreenFile() fd = conn.makefile()
fd.write('hello') fd.write('hello')
fd.close() fd.close()
conn.send('\n') conn.send('\n')
conn.close() conn.close()
self.assertRaises(socket.error, fd.write, 'a') fd.write('a')
self.assertRaises(Exception, fd.flush)
self.assertRaises(socket.error, conn.send, 'b') self.assertRaises(socket.error, conn.send, 'b')
finally: finally:
listener.close() listener.close()
def did_it_work(server): def did_it_work(server):
client = api.connect_tcp(('127.0.0.1', server.getsockname()[1])) client = api.connect_tcp(('127.0.0.1', server.getsockname()[1]))
fd = client.makeGreenFile() fd = client.makefile()
client.close() client.close()
assert fd.readline() == 'hello\n' assert fd.readline() == 'hello\n'
assert fd.read() == '' assert fd.read() == ''
@@ -96,16 +101,17 @@ class TestGreenIo(LimitedTestCase):
# closing the file object should close everything # closing the file object should close everything
try: try:
conn, addr = listener.accept() conn, addr = listener.accept()
conn = conn.makeGreenFile() conn = conn.makefile()
conn.write('hello\n') conn.write('hello\n')
conn.close() conn.close()
self.assertRaises(socket.error, conn.write, 'a') conn.write('a')
self.assertRaises(Exception, conn.flush)
finally: finally:
listener.close() listener.close()
server = api.tcp_listener(('0.0.0.0', 0)) server = api.tcp_listener(('0.0.0.0', 0))
killer = coros.execute(accept_once, server) killer = coros.execute(accept_once, server)
client = api.connect_tcp(('127.0.0.1', server.getsockname()[1])) client = api.connect_tcp(('127.0.0.1', server.getsockname()[1]))
fd = client.makeGreenFile() fd = client.makefile()
client.close() client.close()
assert fd.read() == 'hello\n' assert fd.read() == 'hello\n'
assert fd.read() == '' assert fd.read() == ''

View File

@@ -83,7 +83,7 @@ class ConnectionClosed(Exception):
def read_http(sock): def read_http(sock):
fd = sock.makeGreenFile() fd = sock.makefile()
try: try:
response_line = fd.readline() response_line = fd.readline()
except socket.error, exc: except socket.error, exc:
@@ -92,11 +92,19 @@ def read_http(sock):
raise raise
if not response_line: if not response_line:
raise ConnectionClosed 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() headers = dict()
for x in raw_headers.split('\r\n'): for x in header_lines:
#print "X", x x = x.strip()
if not x:
continue
key, value = x.split(': ', 1) key, value = x.split(': ', 1)
headers[key.lower()] = value headers[key.lower()] = value
@@ -134,8 +142,9 @@ class TestHttpd(LimitedTestCase):
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('localhost', self.port))
fd = sock.makeGreenFile() fd = sock.makefile()
fd.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') fd.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
fd.flush()
result = fd.read() result = fd.read()
fd.close() fd.close()
## The server responds with the maximum version it supports ## The server responds with the maximum version it supports
@@ -146,10 +155,12 @@ class TestHttpd(LimitedTestCase):
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('localhost', self.port))
fd = sock.makeGreenFile() fd = sock.makefile()
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
fd.flush()
read_http(sock) read_http(sock)
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
fd.flush()
read_http(sock) read_http(sock)
fd.close() fd.close()
@@ -158,8 +169,9 @@ class TestHttpd(LimitedTestCase):
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('localhost', self.port))
fd = sock.makeGreenFile() fd = sock.makefile()
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
fd.flush()
cancel = api.exc_after(1, RuntimeError) cancel = api.exc_after(1, RuntimeError)
self.assertRaises(TypeError, fd.read, "This shouldn't work") self.assertRaises(TypeError, fd.read, "This shouldn't work")
cancel.cancel() cancel.cancel()
@@ -169,12 +181,15 @@ class TestHttpd(LimitedTestCase):
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('localhost', self.port))
fd = sock.makeGreenFile() fd = sock.makefile()
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
fd.flush()
read_http(sock) read_http(sock)
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
fd.flush()
read_http(sock) read_http(sock)
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
fd.flush()
self.assertRaises(ConnectionClosed, read_http, sock) self.assertRaises(ConnectionClosed, read_http, sock)
fd.close() fd.close()
@@ -194,8 +209,9 @@ class TestHttpd(LimitedTestCase):
path_parts.append('path') path_parts.append('path')
path = '/'.join(path_parts) path = '/'.join(path_parts)
request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path
fd = sock.makeGreenFile() fd = sock.makefile()
fd.write(request) fd.write(request)
fd.flush()
result = fd.readline() result = fd.readline()
if result: if result:
# windows closes the socket before the data is flushed, # windows closes the socket before the data is flushed,
@@ -220,8 +236,9 @@ class TestHttpd(LimitedTestCase):
'Content-Length: 3', 'Content-Length: 3',
'', '',
'a=a')) 'a=a'))
fd = sock.makeGreenFile() fd = sock.makefile()
fd.write(request) fd.write(request)
fd.flush()
# send some junk after the actual request # send some junk after the actual request
fd.write('01234567890123456789') fd.write('01234567890123456789')
@@ -233,12 +250,15 @@ class TestHttpd(LimitedTestCase):
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('localhost', self.port))
fd = sock.makeGreenFile() fd = sock.makefile()
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
fd.flush()
response_line_200,_,_ = read_http(sock) response_line_200,_,_ = read_http(sock)
fd.write('GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n') fd.write('GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n')
fd.flush()
response_line_404,_,_ = read_http(sock) response_line_404,_,_ = read_http(sock)
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
fd.flush()
response_line_test,_,_ = read_http(sock) response_line_test,_,_ = read_http(sock)
self.assertEqual(response_line_200,response_line_test) self.assertEqual(response_line_200,response_line_test)
fd.close() fd.close()
@@ -248,8 +268,9 @@ class TestHttpd(LimitedTestCase):
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('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.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()) self.assert_('Transfer-Encoding: chunked' in fd.read())
def test_010_no_chunked_http_1_0(self): def test_010_no_chunked_http_1_0(self):
@@ -257,8 +278,9 @@ class TestHttpd(LimitedTestCase):
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('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.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()) self.assert_('Transfer-Encoding: chunked' not in fd.read())
def test_011_multiple_chunks(self): def test_011_multiple_chunks(self):
@@ -266,9 +288,16 @@ class TestHttpd(LimitedTestCase):
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('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.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) self.assert_('Transfer-Encoding: chunked' in headers)
chunks = 0 chunks = 0
chunklen = int(fd.readline(), 16) chunklen = int(fd.readline(), 16)
@@ -316,43 +345,54 @@ class TestHttpd(LimitedTestCase):
def test_014_chunked_post(self): def test_014_chunked_post(self):
self.site.application = chunked_post self.site.application = chunked_post
sock = api.connect_tcp(('localhost', self.port)) 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' fd.write('PUT /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
'Transfer-Encoding: chunked\r\n\r\n' 'Transfer-Encoding: chunked\r\n\r\n'
'2\r\noh\r\n4\r\n hai\r\n0\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() response = fd.read()
self.assert_(response == 'oh hai', 'invalid response %s' % response) self.assert_(response == 'oh hai', 'invalid response %s' % response)
sock = api.connect_tcp(('localhost', self.port)) 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' fd.write('PUT /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
'Transfer-Encoding: chunked\r\n\r\n' 'Transfer-Encoding: chunked\r\n\r\n'
'2\r\noh\r\n4\r\n hai\r\n0\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() response = fd.read()
self.assert_(response == 'oh hai', 'invalid response %s' % response) self.assert_(response == 'oh hai', 'invalid response %s' % response)
sock = api.connect_tcp(('localhost', self.port)) 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' fd.write('PUT /c HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
'Transfer-Encoding: chunked\r\n\r\n' 'Transfer-Encoding: chunked\r\n\r\n'
'2\r\noh\r\n4\r\n hai\r\n0\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) response = fd.read(8192)
self.assert_(response == 'oh hai', 'invalid response %s' % response) self.assert_(response == 'oh hai', 'invalid response %s' % response)
def test_015_write(self): def test_015_write(self):
self.site.application = use_write self.site.application = use_write
sock = api.connect_tcp(('localhost', self.port)) 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.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) response_line, headers, body = read_http(sock)
self.assert_('content-length' in headers) self.assert_('content-length' in headers)
sock = api.connect_tcp(('localhost', self.port)) 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.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) response_line, headers, body = read_http(sock)
self.assert_('transfer-encoding' in headers) self.assert_('transfer-encoding' in headers)
self.assert_(headers['transfer-encoding'] == 'chunked') self.assert_(headers['transfer-encoding'] == 'chunked')
@@ -367,10 +407,17 @@ class TestHttpd(LimitedTestCase):
return ['testing'] return ['testing']
self.site.application = wsgi_app self.site.application = wsgi_app
sock = api.connect_tcp(('localhost', self.port)) 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.write('GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
headerlines = fd.readuntil('\r\n\r\n').splitlines() fd.flush()
self.assertEquals(1, len([l for l in headerlines 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')])) if l.lower().startswith('content-length')]))
def test_017_ssl_zeroreturnerror(self): def test_017_ssl_zeroreturnerror(self):
@@ -413,14 +460,19 @@ class TestHttpd(LimitedTestCase):
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('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') fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
self.assert_('connection: keep-alive' in fd.flush()
fd.readuntil('\r\n\r\n').lower())
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 # 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') fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
self.assert_('connection: keep-alive' in fd.flush()
fd.readuntil('\r\n\r\n').lower()) 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 test_019_fieldstorage_compat(self):
def use_fieldstorage(environ, start_response): def use_fieldstorage(environ, start_response):
@@ -434,13 +486,14 @@ class TestHttpd(LimitedTestCase):
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('localhost', self.port))
fd = sock.makeGreenFile() fd = sock.makefile()
fd.write('POST / HTTP/1.1\r\n' fd.write('POST / HTTP/1.1\r\n'
'Host: localhost\r\n' 'Host: localhost\r\n'
'Connection: close\r\n' 'Connection: close\r\n'
'Transfer-Encoding: chunked\r\n\r\n' 'Transfer-Encoding: chunked\r\n\r\n'
'2\r\noh\r\n' '2\r\noh\r\n'
'4\r\n hai\r\n0\r\n\r\n') '4\r\n hai\r\n0\r\n\r\n')
fd.flush()
self.assert_('hello!' in fd.read()) self.assert_('hello!' in fd.read())
def test_020_x_forwarded_for(self): def test_020_x_forwarded_for(self):
@@ -484,11 +537,12 @@ class TestHttpd(LimitedTestCase):
return [] return []
self.site.application = clobberin_time self.site.application = clobberin_time
sock = api.connect_tcp(('localhost', self.port)) sock = api.connect_tcp(('localhost', self.port))
fd = sock.makeGreenFile() fd = sock.makefile()
fd.write('GET / HTTP/1.1\r\n' fd.write('GET / HTTP/1.1\r\n'
'Host: localhost\r\n' 'Host: localhost\r\n'
'Connection: close\r\n' 'Connection: close\r\n'
'\r\n\r\n') '\r\n\r\n')
fd.flush()
self.assert_('200 OK' in fd.read()) self.assert_('200 OK' in fd.read())
def test_022_custom_pool(self): 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 # this stuff is copied from test_001_server, could be better factored
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('localhost', self.port))
fd = sock.makeGreenFile() fd = sock.makefile()
fd.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') fd.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
fd.flush()
result = fd.read() result = fd.read()
fd.close() fd.close()
self.assert_(result.startswith('HTTP'), result) self.assert_(result.startswith('HTTP'), result)
@@ -521,8 +576,9 @@ class TestHttpd(LimitedTestCase):
def test_023_bad_content_length(self): def test_023_bad_content_length(self):
sock = api.connect_tcp( sock = api.connect_tcp(
('localhost', self.port)) ('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.write('GET / HTTP/1.0\r\nHost: localhost\r\nContent-length: argh\r\n\r\n')
fd.flush()
result = fd.read() result = fd.read()
fd.close() fd.close()
self.assert_(result.startswith('HTTP'), result) self.assert_(result.startswith('HTTP'), result)