Skip to content

Commit 7a793f2

Browse files
committed
gh-135056: Support multiple headers of the same name.
1 parent 6f88c13 commit 7a793f2

3 files changed

Lines changed: 23 additions & 21 deletions

File tree

Doc/library/http.server.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,8 @@ instantiation, of which this module provides three different variants:
375375
The *directory* parameter accepts a :term:`path-like object`.
376376

377377
.. versionchanged:: next
378-
Added *response_headers*, which accepts an optional dictionary of
379-
additional HTTP headers to add to each successful HTTP status 200
378+
Added *response_headers*, which accepts an optional iterable of
379+
name/value pairs of HTTP headers to add to each successful HTTP status 200
380380
response. All other status code responses will not include these headers.
381381

382382
A lot of the work, such as parsing the request, is done by the base class
@@ -433,8 +433,8 @@ instantiation, of which this module provides three different variants:
433433
followed by a ``'Content-Length:'`` header with the file's size and a
434434
``'Last-Modified:'`` header with the file's modification time.
435435

436-
The headers specified in the dictionary instance argument
437-
``response_headers`` are each individually sent in the response.
436+
The instance attribute ``response_headers`` is used as an iterable of
437+
name/value pairs to set user specified custom response headers.
438438

439439
Then follows a blank line signifying the end of the headers, and then the
440440
contents of the file are output.

Lib/http/server.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ def send_custom_response_headers(self):
718718
"""Send the headers stored in self.response_headers"""
719719
# User specified response_headers
720720
if self.response_headers is not None:
721-
for header, value in self.response_headers.items():
721+
for header, value in self.response_headers:
722722
self.send_header(header, value)
723723

724724
def send_head(self):
@@ -1056,9 +1056,9 @@ def _main(args=None):
10561056
except OSError as e:
10571057
parser.error(f"Failed to read TLS password file: {e}")
10581058

1059-
response_headers = {}
1059+
response_headers = []
10601060
for header, value in args.header or []:
1061-
response_headers[header] = value
1061+
response_headers.append((header, value))
10621062

10631063
# ensure dual-stack is not disabled; ref #38907
10641064
class DualStackServerMixin:

Lib/test/test_httpservers.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -830,25 +830,27 @@ def test_path_without_leading_slash(self):
830830
self.tempdir_name + "/?hi=1")
831831

832832
def test_custom_headers_list_dir(self):
833-
with mock.patch.object(self.request_handler, 'custom_headers', new={
834-
'X-Test1': 'test1',
835-
'X-Test2': 'test2',
836-
}):
833+
with mock.patch.object(self.request_handler, 'custom_headers', new=[
834+
('X-Test1', 'test1'),
835+
('X-Test2', 'test2'),
836+
]):
837837
response = self.request(self.base_url + '/')
838838
self.assertEqual(response.getheader("X-Test1"), 'test1')
839839
self.assertEqual(response.getheader("X-Test2"), 'test2')
840840

841841
def test_custom_headers_get_file(self):
842-
with mock.patch.object(self.request_handler, 'custom_headers', new={
843-
'X-Test1': 'test1',
844-
'X-Test2': 'test2',
845-
}):
842+
with mock.patch.object(self.request_handler, 'custom_headers', new=[
843+
('Set-Cookie', 'test1=value1'),
844+
('Set-Cookie', 'test2=value2'),
845+
('X-Test1', 'value3'),
846+
]):
846847
data = b"Dummy index file\r\n"
847848
with open(os.path.join(self.tempdir_name, 'index.html'), 'wb') as f:
848849
f.write(data)
849850
response = self.request(self.base_url + '/')
850-
self.assertEqual(response.getheader("X-Test1"), 'test1')
851-
self.assertEqual(response.getheader("X-Test2"), 'test2')
851+
self.assertEqual(response.getheader("Set-Cookie"),
852+
'test1=value1, test2=value2')
853+
self.assertEqual(response.getheader("X-Test1"), 'value3')
852854

853855
class SocketlessRequestHandler(SimpleHTTPRequestHandler):
854856
def __init__(self, directory=None):
@@ -1498,7 +1500,7 @@ def test_response_headers_arg(self):
14981500
HTTPServer, 'serve_forever'
14991501
) as mock_serve_forever:
15001502
httpd = server._main(
1501-
['-H', 'X-Test1', 'Test1', '-H', 'X-Test2', 'Test2', '8080']
1503+
['-H', 'Set-Cookie', 'k=v', '-H', 'Set-Cookie', 'k2=v2', '8080']
15021504
)
15031505
request_handler_class = httpd.RequestHandlerClass
15041506
with mock.patch.object(
@@ -1510,9 +1512,9 @@ def test_response_headers_arg(self):
15101512
httpd.finish_request(mock.Mock(), '127.0.0.1')
15111513
mock_handler_init.assert_called_once_with(
15121514
mock.ANY, mock.ANY, mock.ANY, directory=mock.ANY,
1513-
response_headers={
1514-
'X-Test1': 'Test1', 'X-Test2': 'Test2'
1515-
}
1515+
response_headers=[
1516+
('Set-Cookie', 'k=v'), ('Set-Cookie', 'k2=v2')
1517+
]
15161518
)
15171519

15181520

0 commit comments

Comments
 (0)