[Python-checkins] r85125 - in python/branches/py3k: Doc/library/http.server.rst Lib/http/server.py Lib/test/test_httpservers.py Misc/NEWS

senthil.kumaran python-checkins at python.org
Thu Sep 30 08:09:18 CEST 2010


Author: senthil.kumaran
Date: Thu Sep 30 08:09:18 2010
New Revision: 85125

Log:
Issue1491 - BaseHTTPServer incorrectly implements response code 100



Modified:
   python/branches/py3k/Doc/library/http.server.rst
   python/branches/py3k/Lib/http/server.py
   python/branches/py3k/Lib/test/test_httpservers.py
   python/branches/py3k/Misc/NEWS

Modified: python/branches/py3k/Doc/library/http.server.rst
==============================================================================
--- python/branches/py3k/Doc/library/http.server.rst	(original)
+++ python/branches/py3k/Doc/library/http.server.rst	Thu Sep 30 08:09:18 2010
@@ -155,6 +155,17 @@
       This method will parse and dispatch the request to the appropriate
       :meth:`do_\*` method.  You should never need to override it.
 
+   .. method:: handle_expect_100()
+
+      When a HTTP/1.1 compliant server receives a ``Expect: 100-continue``
+      request header it responds back with a ``100 Continue`` followed by ``200
+      OK`` headers.
+      This method can be overridden to raise an error if the server does not
+      want the client to continue.  For e.g. server can chose to send ``417
+      Expectation Failed`` as a response header and ``return False``.
+
+      .. versionadded:: 3.2
+
    .. method:: send_error(code, message=None)
 
       Sends and logs a complete error reply to the client. The numeric *code*
@@ -174,6 +185,15 @@
       Writes a specific HTTP header to the output stream. *keyword* should
       specify the header keyword, with *value* specifying its value.
 
+   .. method:: send_response_only(code, message=None)
+
+      Sends the reponse header only, used for the purposes when ``100
+      Continue`` response is sent by the server to the client. If the *message*
+      is not specified, the HTTP message corresponding the response *code*  is
+      sent.
+
+      .. versionadded:: 3.2
+
    .. method:: end_headers()
 
       Sends a blank line, indicating the end of the HTTP headers in the

Modified: python/branches/py3k/Lib/http/server.py
==============================================================================
--- python/branches/py3k/Lib/http/server.py	(original)
+++ python/branches/py3k/Lib/http/server.py	Thu Sep 30 08:09:18 2010
@@ -322,6 +322,30 @@
         elif (conntype.lower() == 'keep-alive' and
               self.protocol_version >= "HTTP/1.1"):
             self.close_connection = 0
+        # Examine the headers and look for an Expect directive
+        expect = self.headers.get('Expect', "")
+        if (expect.lower() == "100-continue" and
+                self.protocol_version >= "HTTP/1.1" and
+                self.request_version >= "HTTP/1.1"):
+            if not self.handle_expect_100():
+                return False
+        return True
+
+    def handle_expect_100(self):
+        """Decide what to do with an "Expect: 100-continue" header.
+
+        If the client is expecting a 100 Continue response, we must
+        respond with either a 100 Continue or a final response before
+        waiting for the request body. The default is to always respond
+        with a 100 Continue. You can behave differently (for example,
+        reject unauthorized requests) by overriding this method.
+
+        This method should either return True (possibly after sending
+        a 100 Continue response) or send an error response and return
+        False.
+
+        """
+        self.send_response_only(100)
         return True
 
     def handle_one_request(self):
@@ -400,6 +424,12 @@
 
         """
         self.log_request(code)
+        self.send_response_only(code, message)
+        self.send_header('Server', self.version_string())
+        self.send_header('Date', self.date_time_string())
+
+    def send_response_only(self, code, message=None):
+        """Send the response header only."""
         if message is None:
             if code in self.responses:
                 message = self.responses[code][0]
@@ -408,9 +438,6 @@
         if self.request_version != 'HTTP/0.9':
             self.wfile.write(("%s %d %s\r\n" %
                               (self.protocol_version, code, message)).encode('ASCII', 'strict'))
-            # print (self.protocol_version, code, message)
-        self.send_header('Server', self.version_string())
-        self.send_header('Date', self.date_time_string())
 
     def send_header(self, keyword, value):
         """Send a MIME header."""

Modified: python/branches/py3k/Lib/test/test_httpservers.py
==============================================================================
--- python/branches/py3k/Lib/test/test_httpservers.py	(original)
+++ python/branches/py3k/Lib/test/test_httpservers.py	Thu Sep 30 08:09:18 2010
@@ -10,11 +10,13 @@
 
 import os
 import sys
+import re
 import base64
 import shutil
 import urllib.parse
 import http.client
 import tempfile
+from io import BytesIO
 
 import unittest
 from test import support
@@ -403,8 +405,103 @@
 
 class SocketlessRequestHandler(SimpleHTTPRequestHandler):
     def __init__(self):
+        self.get_called = False
+        self.protocol_version = "HTTP/1.1"
+
+    def do_GET(self):
+        self.get_called = True
+        self.send_response(200)
+        self.send_header('Content-Type', 'text/html')
+        self.end_headers()
+        self.wfile.write(b'<html><body>Data</body></html>\r\n')
+
+    def log_message(self, format, *args):
         pass
 
+class RejectingSocketlessRequestHandler(SocketlessRequestHandler):
+    def handle_expect_100(self):
+        self.send_error(417)
+        return False
+
+class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
+    """Test the functionaility of the BaseHTTPServer.
+
+       Test the support for the Expect 100-continue header.
+       """
+
+    HTTPResponseMatch = re.compile(b'HTTP/1.[0-9]+ 200 OK')
+
+    def setUp (self):
+        self.handler = SocketlessRequestHandler()
+
+    def send_typical_request(self, message):
+        input = BytesIO(message)
+        output = BytesIO()
+        self.handler.rfile = input
+        self.handler.wfile = output
+        self.handler.handle_one_request()
+        output.seek(0)
+        return output.readlines()
+
+    def verify_get_called(self):
+        self.assertTrue(self.handler.get_called)
+
+    def verify_expected_headers(self, headers):
+        for fieldName in b'Server: ', b'Date: ', b'Content-Type: ':
+            self.assertEqual(sum(h.startswith(fieldName) for h in headers), 1)
+
+    def verify_http_server_response(self, response):
+        match = self.HTTPResponseMatch.search(response)
+        self.assertTrue(match is not None)
+
+    def test_http_1_1(self):
+        result = self.send_typical_request(b'GET / HTTP/1.1\r\n\r\n')
+        self.verify_http_server_response(result[0])
+        self.verify_expected_headers(result[1:-1])
+        self.verify_get_called()
+        self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
+
+    def test_http_1_0(self):
+        result = self.send_typical_request(b'GET / HTTP/1.0\r\n\r\n')
+        self.verify_http_server_response(result[0])
+        self.verify_expected_headers(result[1:-1])
+        self.verify_get_called()
+        self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
+
+    def test_http_0_9(self):
+        result = self.send_typical_request(b'GET / HTTP/0.9\r\n\r\n')
+        self.assertEqual(len(result), 1)
+        self.assertEqual(result[0], b'<html><body>Data</body></html>\r\n')
+        self.verify_get_called()
+
+    def test_with_continue_1_0(self):
+        result = self.send_typical_request(b'GET / HTTP/1.0\r\nExpect: 100-continue\r\n\r\n')
+        self.verify_http_server_response(result[0])
+        self.verify_expected_headers(result[1:-1])
+        self.verify_get_called()
+        self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
+
+    def test_with_continue_1_1(self):
+        result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
+        self.assertEqual(result[0], b'HTTP/1.1 100 Continue\r\n')
+        self.assertEqual(result[1], b'HTTP/1.1 200 OK\r\n')
+        self.verify_expected_headers(result[2:-1])
+        self.verify_get_called()
+        self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
+
+    def test_with_continue_rejected(self):
+        usual_handler = self.handler        # Save to avoid breaking any subsequent tests.
+        self.handler = RejectingSocketlessRequestHandler()
+        result = self.send_typical_request(b'GET / HTTP/1.1\r\nExpect: 100-continue\r\n\r\n')
+        self.assertEqual(result[0], b'HTTP/1.1 417 Expectation Failed\r\n')
+        self.verify_expected_headers(result[1:-1])
+        # The expect handler should short circuit the usual get method by
+        # returning false here, so get_called should be false
+        self.assertFalse(self.handler.get_called)
+        self.assertEqual(sum(r == b'Connection: close\r\n' for r in result[1:-1]), 1)
+        self.handler = usual_handler        # Restore to avoid breaking any subsequent tests.
+
+
 class SimpleHTTPRequestHandlerTestCase(unittest.TestCase):
     """ Test url parsing """
     def setUp(self):
@@ -431,6 +528,7 @@
     cwd = os.getcwd()
     try:
         support.run_unittest(
+            BaseHTTPRequestHandlerTestCase,
             BaseHTTPServerTestCase,
             SimpleHTTPServerTestCase,
             CGIHTTPServerTestCase,

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Thu Sep 30 08:09:18 2010
@@ -76,6 +76,9 @@
 Library
 -------
 
+- Issue #1491: BaseHTTPServer nows send a 100 Continue response before sending
+  a 200 OK for the Expect: 100-continue request header.
+
 - Issue #9360: Cleanup and improvements to the nntplib module.  The API
   now conforms to the philosophy of bytes and unicode separation in Python 3.
   A test suite has also been added.


More information about the Python-checkins mailing list