diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 4854fb5..23310ee 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -83,13 +83,33 @@ class Input(object): self.chunked_input = chunked_input self.chunk_length = -1 + # (optional) headers to send with a "100 Continue" response. Set by + # calling set_hundred_continue_respose_headers() on env['wsgi.input'] + self.hundred_continue_headers = None + + def _send_hundred_continue_response(self): + towrite = [] + + # 100 Continue status line + towrite.append(self.wfile_line) + + # Optional headers + if self.hundred_continue_headers is not None: + # 100 Continue headers + for header in self.hundred_continue_headers: + towrite.append('%s: %s\r\n' % header) + + # Blank line + towrite.append('\r\n') + + self.wfile.writelines(towrite) + self.wfile = None + self.wfile_line = None + def _do_read(self, reader, length=None): if self.wfile is not None: - # 100 Continue - self.wfile.write(self.wfile_line) - self.wfile = None - self.wfile_line = None - + # 100 Continue response + self._send_hundred_continue_response() if length is None and self.content_length is not None: length = self.content_length - self.position if length and length > self.content_length - self.position: @@ -105,10 +125,8 @@ class Input(object): def _chunked_read(self, rfile, length=None, use_readline=False): if self.wfile is not None: - # 100 Continue - self.wfile.write(self.wfile_line) - self.wfile = None - self.wfile_line = None + # 100 Continue response + self._send_hundred_continue_response() try: if length == 0: return "" @@ -175,6 +193,18 @@ class Input(object): def get_socket(self): return self.rfile._sock + def set_hundred_continue_response_headers(self, headers, + capitalize_response_headers=True): + # Response headers capitalization (default) + # CONTent-TYpe: TExt/PlaiN -> Content-Type: TExt/PlaiN + # Per HTTP RFC standard, header name is case-insensitive. + # Please, fix your client to ignore header case if possible. + if capitalize_response_headers: + headers = [ + ('-'.join([x.capitalize() for x in key.split('-')]), value) + for key, value in headers] + self.hundred_continue_headers = headers + class HeaderLineTooLong(Exception): pass @@ -526,7 +556,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): if env.get('HTTP_EXPECT') == '100-continue': wfile = self.wfile - wfile_line = 'HTTP/1.1 100 Continue\r\n\r\n' + wfile_line = 'HTTP/1.1 100 Continue\r\n' else: wfile = None wfile_line = None diff --git a/tests/wsgi_test.py b/tests/wsgi_test.py index 9c46a89..0d6ce39 100644 --- a/tests/wsgi_test.py +++ b/tests/wsgi_test.py @@ -774,6 +774,57 @@ class TestHttpd(_TestBase): fd.close() sock.close() + def test_024a_expect_100_continue_with_headers(self): + def wsgi_app(environ, start_response): + if int(environ['CONTENT_LENGTH']) > 1024: + start_response('417 Expectation Failed', [('Content-Length', '7')]) + return ['failure'] + else: + environ['wsgi.input'].set_hundred_continue_response_headers( + [('Hundred-Continue-Header-1', 'H1'), + ('Hundred-Continue-Header-2', 'H2'), + ('Hundred-Continue-Header-k', 'Hk')]) + text = environ['wsgi.input'].read() + start_response('200 OK', [('Content-Length', str(len(text)))]) + return [text] + self.site.application = wsgi_app + sock = eventlet.connect(('localhost', self.port)) + fd = sock.makefile('rw') + fd.write(b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 1025\r\nExpect: 100-continue\r\n\r\n') + fd.flush() + result = read_http(sock) + self.assertEqual(result.status, 'HTTP/1.1 417 Expectation Failed') + self.assertEqual(result.body, 'failure') + fd.write( + b'PUT / HTTP/1.1\r\nHost: localhost\r\nContent-length: 7\r\nExpect: 100-continue\r\n\r\ntesting') + fd.flush() + header_lines = [] + while True: + line = fd.readline() + if line == '\r\n': + break + else: + header_lines.append(line.strip()) + assert header_lines[0].startswith('HTTP/1.1 100 Continue') + headers = dict((k, v) for k, v in (h.split(': ', 1) for h in header_lines[1:])) + assert 'Hundred-Continue-Header-1' in headers + assert 'Hundred-Continue-Header-2' in headers + assert 'Hundred-Continue-Header-K' in headers + self.assertEqual('H1', headers['Hundred-Continue-Header-1']) + self.assertEqual('H2', headers['Hundred-Continue-Header-2']) + self.assertEqual('Hk', headers['Hundred-Continue-Header-K']) + header_lines = [] + while True: + line = fd.readline() + if line == '\r\n': + break + else: + header_lines.append(line) + assert header_lines[0].startswith('HTTP/1.1 200 OK') + self.assertEqual(fd.read(7), 'testing') + fd.close() + sock.close() + def test_025_accept_errors(self): debug.hub_exceptions(True) listener = greensocket.socket()