summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/http.server.rst20
-rw-r--r--Lib/http/server.py33
-rw-r--r--Lib/test/test_httpservers.py98
-rw-r--r--Misc/NEWS3
4 files changed, 151 insertions, 3 deletions
diff --git a/Doc/library/http.server.rst b/Doc/library/http.server.rst
index 1ca1620..2f28012 100644
--- a/Doc/library/http.server.rst
+++ b/Doc/library/http.server.rst
@@ -155,6 +155,17 @@ of which this module provides three different variants:
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 @@ of which this module provides three different variants:
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
diff --git a/Lib/http/server.py b/Lib/http/server.py
index 098ad25..4fa58a2 100644
--- a/Lib/http/server.py
+++ b/Lib/http/server.py
@@ -322,6 +322,30 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
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 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
"""
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 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
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."""
diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py
index b276c73..23fc7e4 100644
--- a/Lib/test/test_httpservers.py
+++ b/Lib/test/test_httpservers.py
@@ -10,11 +10,13 @@ from http import server
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 CGIHTTPServerTestCase(BaseTestCase):
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 @@ def test_main(verbose=None):
cwd = os.getcwd()
try:
support.run_unittest(
+ BaseHTTPRequestHandlerTestCase,
BaseHTTPServerTestCase,
SimpleHTTPServerTestCase,
CGIHTTPServerTestCase,
diff --git a/Misc/NEWS b/Misc/NEWS
index 79c710c..9144e25 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -76,6 +76,9 @@ Core and Builtins
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.