Permalink
Browse files

Avoid modifying the headers object passed in by the client.

This fixes a problem in which the header object was reused across requests
and behavior became inconsistent when following redirects.

Closes #459.
  • Loading branch information...
1 parent daeb7c4 commit 2f35821c235a5e747b57b8b2c3d84154dbcbaea1 @bdarnell bdarnell committed Mar 6, 2012
Showing with 20 additions and 3 deletions.
  1. +8 −1 tornado/httputil.py
  2. +4 −2 tornado/simple_httpclient.py
  3. +8 −0 tornado/test/simple_httpclient_test.py
View
@@ -58,7 +58,14 @@ def __init__(self, *args, **kwargs):
dict.__init__(self)
self._as_list = {}
self._last_key = None
- self.update(*args, **kwargs)
+ if (len(args) == 1 and len(kwargs) == 0 and
+ isinstance(args[0], HTTPHeaders)):
+ # Copy constructor
+ for k,v in args[0].get_all():
+ self.add(k,v)
+ else:
+ # Dict-style initialization
+ self.update(*args, **kwargs)
# new public methods
@@ -94,8 +94,10 @@ def initialize(self, io_loop=None, max_clients=10,
def fetch(self, request, callback, **kwargs):
if not isinstance(request, HTTPRequest):
request = HTTPRequest(url=request, **kwargs)
- if not isinstance(request.headers, HTTPHeaders):
- request.headers = HTTPHeaders(request.headers)
+ # We're going to modify this (to add Host, Accept-Encoding, etc),
+ # so make sure we don't modify the caller's object. This is also
+ # where normal dicts get converted to HTTPHeaders objects.
+ request.headers = HTTPHeaders(request.headers)
callback = stack_context.wrap(callback)
self.queue.append((request, callback))
self._process_queue()
@@ -6,6 +6,7 @@
import re
import socket
+from tornado.httputil import HTTPHeaders
from tornado.ioloop import IOLoop
from tornado.simple_httpclient import SimpleAsyncHTTPClient, _DEFAULT_CA_CERTS
from tornado.test.httpclient_test import HTTPClientCommonTestCase, ChunkHandler, CountdownHandler, HelloWorldHandler
@@ -179,6 +180,13 @@ def test_max_redirects(self):
self.assertTrue(response.effective_url.endswith("/countdown/2"))
self.assertTrue(response.headers["Location"].endswith("/countdown/1"))
+ def test_header_reuse(self):
+ # Apps may reuse a headers object if they are only passing in constant
+ # headers like user-agent. The header object should not be modified.
+ headers = HTTPHeaders({'User-Agent': 'Foo'})
+ self.fetch("/hello", headers=headers)
+ self.assertEqual(list(headers.get_all()), [('User-Agent', 'Foo')])
+
def test_303_redirect(self):
response = self.fetch("/303_post", method="POST", body="blah")
self.assertEqual(200, response.code)

0 comments on commit 2f35821

Please sign in to comment.