Commit 12e5338a authored by Jason Madden's avatar Jason Madden

pywsgi mostly works, there are a few timeout issues lurking somewhere in the...

pywsgi mostly works, there are a few timeout issues lurking somewhere in the test suite. But it's enough for the example servers to run.
parent 953ebfad
...@@ -105,8 +105,10 @@ def fix_links(data, proxy_url, host_url): ...@@ -105,8 +105,10 @@ def fix_links(data, proxy_url, host_url):
result = m.group('before') + '"' + join(proxy_url, host_url, url) + '"' result = m.group('before') + '"' + join(proxy_url, host_url, url) + '"'
#print('replaced %r -> %r' % (m.group(0), result)) #print('replaced %r -> %r' % (m.group(0), result))
return result return result
data = data.decode('latin-1') # XXX Assuming charset. Can regexes work with bytes data?
data = _link_re_1.sub(fix_link_cb, data) data = _link_re_1.sub(fix_link_cb, data)
data = _link_re_2.sub(fix_link_cb, data) data = _link_re_2.sub(fix_link_cb, data)
data = data.encode('latin-1')
return data return data
_link_re_1 = re.compile('''(?P<before>(href|src|action)\s*=\s*)(?P<quote>['"])(?P<url>[^#].*?)(?P=quote)''') _link_re_1 = re.compile('''(?P<before>(href|src|action)\s*=\s*)(?P<quote>['"])(?P<url>[^#].*?)(?P=quote)''')
...@@ -114,7 +116,7 @@ _link_re_2 = re.compile('''(?P<before>(href|src|action)\s*=\s*)(?P<url>[^'"#>][^ ...@@ -114,7 +116,7 @@ _link_re_2 = re.compile('''(?P<before>(href|src|action)\s*=\s*)(?P<url>[^'"#>][^
drop_headers = ['transfer-encoding', 'set-cookie'] drop_headers = ['transfer-encoding', 'set-cookie']
FORM = """<html><head> FORM = b"""<html><head>
<title>Web Proxy - gevent example</title></head><body> <title>Web Proxy - gevent example</title></head><body>
<table width=60% height=100% align=center> <table width=60% height=100% align=center>
<tr height=30%><td align=center valign=bottom>Type in URL you want to visit and press Enter</td></tr> <tr height=30%><td align=center valign=bottom>Type in URL you want to visit and press Enter</td></tr>
......
...@@ -7,10 +7,10 @@ from gevent.pywsgi import WSGIServer ...@@ -7,10 +7,10 @@ from gevent.pywsgi import WSGIServer
def application(env, start_response): def application(env, start_response):
if env['PATH_INFO'] == '/': if env['PATH_INFO'] == '/':
start_response('200 OK', [('Content-Type', 'text/html')]) start_response('200 OK', [('Content-Type', 'text/html')])
return ["<b>hello world</b>"] return [b"<b>hello world</b>"]
else: else:
start_response('404 Not Found', [('Content-Type', 'text/html')]) start_response('404 Not Found', [('Content-Type', 'text/html')])
return ['<h1>Not Found</h1>'] return [b'<h1>Not Found</h1>']
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -8,10 +8,10 @@ from gevent import pywsgi ...@@ -8,10 +8,10 @@ from gevent import pywsgi
def hello_world(env, start_response): def hello_world(env, start_response):
if env['PATH_INFO'] == '/': if env['PATH_INFO'] == '/':
start_response('200 OK', [('Content-Type', 'text/html')]) start_response('200 OK', [('Content-Type', 'text/html')])
return ["<b>hello world</b>"] return [b"<b>hello world</b>"]
else: else:
start_response('404 Not Found', [('Content-Type', 'text/html')]) start_response('404 Not Found', [('Content-Type', 'text/html')])
return ['<h1>Not Found</h1>'] return [b'<h1>Not Found</h1>']
print('Serving on https://127.0.0.1:8443') print('Serving on https://127.0.0.1:8443')
server = pywsgi.WSGIServer(('0.0.0.0', 8443), hello_world, keyfile='server.key', certfile='server.crt') server = pywsgi.WSGIServer(('0.0.0.0', 8443), hello_world, keyfile='server.key', certfile='server.crt')
......
...@@ -25,7 +25,6 @@ SocketIO = __socket__.SocketIO ...@@ -25,7 +25,6 @@ SocketIO = __socket__.SocketIO
def _get_memory(string, offset): def _get_memory(string, offset):
return memoryview(string)[offset:] return memoryview(string)[offset:]
timeout_default = object() timeout_default = object()
...@@ -535,10 +534,8 @@ class _fileobject(object): ...@@ -535,10 +534,8 @@ class _fileobject(object):
while True: while True:
try: try:
data = self._sock.recv(rbufsize) data = self._sock.recv(rbufsize)
except error as e: except InterruptedError:
if e.args[0] == EINTR: continue
continue
raise
if not data: if not data:
break break
buf.write(data) buf.write(data)
...@@ -564,10 +561,9 @@ class _fileobject(object): ...@@ -564,10 +561,9 @@ class _fileobject(object):
# fragmentation issues on many platforms. # fragmentation issues on many platforms.
try: try:
data = self._sock.recv(left) data = self._sock.recv(left)
except error as e: except InterruptedError:
if e.args[0] == EINTR: continue
continue
raise
if not data: if not data:
break break
n = len(data) n = len(data)
...@@ -596,7 +592,7 @@ class _fileobject(object): ...@@ -596,7 +592,7 @@ class _fileobject(object):
# check if we already have it in our buffer # check if we already have it in our buffer
buf.seek(0) buf.seek(0)
bline = buf.readline(size) bline = buf.readline(size)
if bline.endswith('\n') or len(bline) == size: if bline.endswith(b'\n') or len(bline) == size:
self._rbuf = BytesIO() self._rbuf = BytesIO()
self._rbuf.write(buf.read()) self._rbuf.write(buf.read())
return bline return bline
...@@ -612,17 +608,16 @@ class _fileobject(object): ...@@ -612,17 +608,16 @@ class _fileobject(object):
recv = self._sock.recv recv = self._sock.recv
while True: while True:
try: try:
while data != "\n": while data != b"\n":
data = recv(1) data = recv(1)
if not data: if not data:
break break
buffers.append(data) buffers.append(data)
except error as e: except InterruptedError:
# The try..except to catch EINTR was moved outside the # The try..except to catch EINTR was moved outside the
# recv loop to avoid the per byte overhead. # recv loop to avoid the per byte overhead.
if e.args[0] == EINTR: continue
continue
raise
break break
return "".join(buffers) return "".join(buffers)
...@@ -631,10 +626,9 @@ class _fileobject(object): ...@@ -631,10 +626,9 @@ class _fileobject(object):
while True: while True:
try: try:
data = self._sock.recv(self._rbufsize) data = self._sock.recv(self._rbufsize)
except error as e: except InterruptedError:
if e.args[0] == EINTR: continue
continue
raise
if not data: if not data:
break break
nl = data.find(b'\n') nl = data.find(b'\n')
...@@ -660,10 +654,9 @@ class _fileobject(object): ...@@ -660,10 +654,9 @@ class _fileobject(object):
while True: while True:
try: try:
data = self._sock.recv(self._rbufsize) data = self._sock.recv(self._rbufsize)
except error as e: except InterruptedError:
if e.args[0] == EINTR: continue
continue
raise
if not data: if not data:
break break
left = size - buf_len left = size - buf_len
......
...@@ -27,13 +27,13 @@ _MONTHNAME = [None, # Dummy so we can use 1-based month numbers ...@@ -27,13 +27,13 @@ _MONTHNAME = [None, # Dummy so we can use 1-based month numbers
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
_INTERNAL_ERROR_STATUS = '500 Internal Server Error' _INTERNAL_ERROR_STATUS = '500 Internal Server Error'
_INTERNAL_ERROR_BODY = 'Internal Server Error' _INTERNAL_ERROR_BODY = b'Internal Server Error'
_INTERNAL_ERROR_HEADERS = [('Content-Type', 'text/plain'), _INTERNAL_ERROR_HEADERS = [('Content-Type', 'text/plain'),
('Connection', 'close'), ('Connection', 'close'),
('Content-Length', str(len(_INTERNAL_ERROR_BODY)))] ('Content-Length', str(len(_INTERNAL_ERROR_BODY)))]
_REQUEST_TOO_LONG_RESPONSE = "HTTP/1.1 414 Request URI Too Long\r\nConnection: close\r\nContent-length: 0\r\n\r\n" _REQUEST_TOO_LONG_RESPONSE = b"HTTP/1.1 414 Request URI Too Long\r\nConnection: close\r\nContent-length: 0\r\n\r\n"
_BAD_REQUEST_RESPONSE = "HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-length: 0\r\n\r\n" _BAD_REQUEST_RESPONSE = b"HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-length: 0\r\n\r\n"
_CONTINUE_RESPONSE = "HTTP/1.1 100 Continue\r\n\r\n" _CONTINUE_RESPONSE = b"HTTP/1.1 100 Continue\r\n\r\n"
def format_date_time(timestamp): def format_date_time(timestamp):
...@@ -73,7 +73,7 @@ class Input(object): ...@@ -73,7 +73,7 @@ class Input(object):
if content_length is None: if content_length is None:
# Either Content-Length or "Transfer-Encoding: chunked" must be present in a request with a body # Either Content-Length or "Transfer-Encoding: chunked" must be present in a request with a body
# if it was chunked, then this function would have not been called # if it was chunked, then this function would have not been called
return '' return b''
self._send_100_continue() self._send_100_continue()
left = content_length - self.position left = content_length - self.position
if length is None: if length is None:
...@@ -81,11 +81,11 @@ class Input(object): ...@@ -81,11 +81,11 @@ class Input(object):
elif length > left: elif length > left:
length = left length = left
if not length: if not length:
return '' return b''
read = reader(length) read = reader(length)
self.position += len(read) self.position += len(read)
if len(read) < length: if len(read) < length:
if (use_readline and not read.endswith("\n")) or not use_readline: if (use_readline and not read.endswith(b"\n")) or not use_readline:
raise IOError("unexpected end of file while reading request at position %s" % (self.position,)) raise IOError("unexpected end of file while reading request at position %s" % (self.position,))
return read return read
...@@ -95,9 +95,9 @@ class Input(object): ...@@ -95,9 +95,9 @@ class Input(object):
self._send_100_continue() self._send_100_continue()
if length == 0: if length == 0:
return "" return b""
if length < 0: if length is not None and length < 0:
length = None length = None
if use_readline: if use_readline:
...@@ -128,18 +128,19 @@ class Input(object): ...@@ -128,18 +128,19 @@ class Input(object):
length -= datalen length -= datalen
if length == 0: if length == 0:
break break
if use_readline and data[-1] == "\n": if use_readline and data[-1] == b"\n":
break break
else: else:
line = rfile.readline() line = rfile.readline()
if not line.endswith("\n"): if not line.endswith(b"\n"):
self.chunk_length = 0 self.chunk_length = 0
raise IOError("unexpected end of file while reading chunked data header") raise IOError("unexpected end of file while reading chunked data header")
self.chunk_length = int(line.split(";", 1)[0], 16)
self.chunk_length = int(line.split(b";", 1)[0], 16)
self.position = 0 self.position = 0
if self.chunk_length == 0: if self.chunk_length == 0:
rfile.readline() rfile.readline()
return ''.join(response) return b''.join(response)
def read(self, length=None): def read(self, length=None):
if self.chunked_input: if self.chunked_input:
...@@ -163,6 +164,7 @@ class Input(object): ...@@ -163,6 +164,7 @@ class Input(object):
if not line: if not line:
raise StopIteration raise StopIteration
return line return line
__next__ = next
try: try:
...@@ -200,7 +202,13 @@ except ImportError: ...@@ -200,7 +202,13 @@ except ImportError:
class WSGIHandler(object): class WSGIHandler(object):
protocol_version = 'HTTP/1.1' protocol_version = 'HTTP/1.1'
MessageClass = headers_factory if PY3:
# if we do like Py2, then headers_factory unconditionally
# becomes a bound method, meaning the fp argument becomes WSGIHandler
def MessageClass(self, *args):
return headers_factory(*args)
else:
MessageClass = headers_factory
def __init__(self, socket, address, server, rfile=None): def __init__(self, socket, address, server, rfile=None):
self.socket = socket self.socket = socket
...@@ -229,13 +237,16 @@ class WSGIHandler(object): ...@@ -229,13 +237,16 @@ class WSGIHandler(object):
break break
finally: finally:
if self.socket is not None: if self.socket is not None:
_sock = getattr(self.socket, '_sock', None) # Python 3
try: try:
# read out request data to prevent error: [Errno 104] Connection reset by peer # read out request data to prevent error: [Errno 104] Connection reset by peer
try: if _sock:
self.socket._sock.recv(16384) try:
finally: # socket.recv would hang
self.socket._sock.close() # do not rely on garbage collection _sock.recv(16384)
self.socket.close() finally:
_sock.close()
self.socket.close()
except socket.error: except socket.error:
pass pass
self.__dict__.pop('socket', None) self.__dict__.pop('socket', None)
...@@ -270,6 +281,7 @@ class WSGIHandler(object): ...@@ -270,6 +281,7 @@ class WSGIHandler(object):
return return
self.headers = self.MessageClass(self.rfile, 0) self.headers = self.MessageClass(self.rfile, 0)
if self.headers.status: if self.headers.status:
self.log_error('Invalid headers status: %r', self.headers.status) self.log_error('Invalid headers status: %r', self.headers.status)
return return
...@@ -319,7 +331,10 @@ class WSGIHandler(object): ...@@ -319,7 +331,10 @@ class WSGIHandler(object):
traceback.print_exc() traceback.print_exc()
def read_requestline(self): def read_requestline(self):
return self.rfile.readline(MAX_REQUEST_LINE) line = self.rfile.readline(MAX_REQUEST_LINE)
if PY3:
line = line.decode('latin-1')
return line
def handle_one_request(self): def handle_one_request(self):
if self.rfile.closed: if self.rfile.closed:
...@@ -399,7 +414,7 @@ class WSGIHandler(object): ...@@ -399,7 +414,7 @@ class WSGIHandler(object):
return return
if self.response_use_chunked: if self.response_use_chunked:
## Write the chunked encoding ## Write the chunked encoding
data = "%x\r\n%s\r\n" % (len(data), data) data = ("%x\r\n" % len(data)).encode('ascii') + data + b'\r\n'
self._sendall(data) self._sendall(data)
def write(self, data): def write(self, data):
...@@ -418,17 +433,22 @@ class WSGIHandler(object): ...@@ -418,17 +433,22 @@ class WSGIHandler(object):
self.headers_sent = True self.headers_sent = True
self.finalize_headers() self.finalize_headers()
towrite.extend('HTTP/1.1 %s\r\n' % self.status) towrite.extend(('HTTP/1.1 %s\r\n' % self.status).encode('latin-1'))
for header in self.response_headers: for header in self.response_headers:
towrite.extend('%s: %s\r\n' % header) towrite.extend(('%s: %s\r\n' % header).encode('latin-1'))
towrite.extend('\r\n') towrite.extend(b'\r\n')
if data: if data:
if self.response_use_chunked: if self.response_use_chunked:
## Write the chunked encoding ## Write the chunked encoding
towrite.extend("%x\r\n%s\r\n" % (len(data), data)) towrite.extend(("%x\r\n" % len(data)).encode('latin-1'))
else:
towrite.extend(data) towrite.extend(data)
towrite.extend(b"\r\n")
else:
try:
towrite.extend(data)
except TypeError:
raise TypeError("Not an bytestring", data)
self._sendall(towrite) self._sendall(towrite)
def start_response(self, status, headers, exc_info=None): def start_response(self, status, headers, exc_info=None):
...@@ -466,6 +486,8 @@ class WSGIHandler(object): ...@@ -466,6 +486,8 @@ class WSGIHandler(object):
if self.code in (304, 204): if self.code in (304, 204):
if self.provided_content_length is not None and self.provided_content_length != '0': if self.provided_content_length is not None and self.provided_content_length != '0':
msg = 'Invalid Content-Length for %s response: %r (must be absent or zero)' % (self.code, self.provided_content_length) msg = 'Invalid Content-Length for %s response: %r (must be absent or zero)' % (self.code, self.provided_content_length)
if PY3:
msg = msg.encode('latin-1')
raise AssertionError(msg) raise AssertionError(msg)
return self.write return self.write
...@@ -496,9 +518,9 @@ class WSGIHandler(object): ...@@ -496,9 +518,9 @@ class WSGIHandler(object):
if data: if data:
self.write(data) self.write(data)
if self.status and not self.headers_sent: if self.status and not self.headers_sent:
self.write('') self.write(b'')
if self.response_use_chunked: if self.response_use_chunked:
self.socket.sendall('0\r\n\r\n') self.socket.sendall(b'0\r\n\r\n')
self.response_length += 5 self.response_length += 5
def run_application(self): def run_application(self):
......
...@@ -14,7 +14,7 @@ import ssl ...@@ -14,7 +14,7 @@ import ssl
class Test_wsgiserver(util.TestServer): class Test_wsgiserver(util.TestServer):
server = 'wsgiserver.py' server = 'wsgiserver.py'
URL = 'http://127.0.0.1:8088' URL = 'http://127.0.0.1:8088'
not_found_message = '<h1>Not Found</h1>' not_found_message = b'<h1>Not Found</h1>'
ssl_ctx = None ssl_ctx = None
def read(self, path='/'): def read(self, path='/'):
...@@ -35,7 +35,7 @@ class Test_wsgiserver(util.TestServer): ...@@ -35,7 +35,7 @@ class Test_wsgiserver(util.TestServer):
def _test_hello(self): def _test_hello(self):
status, data = self.read('/') status, data = self.read('/')
self.assertEqual(status, '200 OK') self.assertEqual(status, '200 OK')
self.assertEqual(data, "<b>hello world</b>") self.assertEqual(data, b"<b>hello world</b>")
def _test_not_found(self): def _test_not_found(self):
status, data = self.read('/xxx') status, data = self.read('/xxx')
...@@ -59,10 +59,10 @@ class Test_webproxy(Test_wsgiserver): ...@@ -59,10 +59,10 @@ class Test_webproxy(Test_wsgiserver):
def _run_all_tests(self): def _run_all_tests(self):
status, data = self.read('/') status, data = self.read('/')
self.assertEqual(status, '200 OK') self.assertEqual(status, '200 OK')
assert "gevent example" in data, repr(data) assert b"gevent example" in data, repr(data)
status, data = self.read('/http://www.google.com') status, data = self.read('/http://www.google.com')
self.assertEqual(status, '200 OK') self.assertEqual(status, '200 OK')
assert 'google' in data.lower(), repr(data) assert b'google' in data.lower(), repr(data)
# class Test_webpy(Test_wsgiserver): # class Test_webpy(Test_wsgiserver):
......
This diff is collapsed.
...@@ -9,10 +9,10 @@ from test__server import Settings as server_Settings ...@@ -9,10 +9,10 @@ from test__server import Settings as server_Settings
def application(self, environ, start_response): def application(self, environ, start_response):
if environ['PATH_INFO'] == '/': if environ['PATH_INFO'] == '/':
start_response("200 OK", []) start_response("200 OK", [])
return ["PONG"] return [b"PONG"]
if environ['PATH_INFO'] == '/ping': if environ['PATH_INFO'] == '/ping':
start_response("200 OK", []) start_response("200 OK", [])
return ["PONG"] return [b"PONG"]
elif environ['PATH_INFO'] == '/short': elif environ['PATH_INFO'] == '/short':
gevent.sleep(0.5) gevent.sleep(0.5)
start_response("200 OK", []) start_response("200 OK", [])
......
...@@ -85,7 +85,6 @@ if PY3: ...@@ -85,7 +85,6 @@ if PY3:
FAILING_TESTS += ''' FAILING_TESTS += '''
test__example_udp_server.py test__example_udp_server.py
test__pool.py test__pool.py
FLAKY test___example_servers.py
test_threading_2.py test_threading_2.py
test__refcount.py test__refcount.py
test__subprocess.py test__subprocess.py
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment