[PATCH] Update httpclient to revision da98a10c4f74 of httpplus
Augie Fackler
raf at durin42.com
Fri May 4 13:49:11 CDT 2012
# HG changeset patch
# User Augie Fackler <raf at durin42.com>
# Date 1336155592 18000
# Node ID 25f2bcfb3629dc312f7feca2e604bc806ee6ee09
# Parent 20cde586ae5aa776b632be47dd9db6ee66063e2f
Update httpclient to revision da98a10c4f74 of httpplus.
diff --git a/mercurial/httpclient/__init__.py b/mercurial/httpclient/__init__.py
--- a/mercurial/httpclient/__init__.py
+++ b/mercurial/httpclient/__init__.py
@@ -45,6 +45,7 @@
import select
import socket
+import _readers
import socketutil
logger = logging.getLogger(__name__)
@@ -54,8 +55,6 @@
HTTP_VER_1_0 = 'HTTP/1.0'
HTTP_VER_1_1 = 'HTTP/1.1'
-_LEN_CLOSE_IS_END = -1
-
OUTGOING_BUFFER_SIZE = 1 << 15
INCOMING_BUFFER_SIZE = 1 << 20
@@ -83,23 +82,19 @@
The response will continue to load as available. If you need the
complete response before continuing, check the .complete() method.
"""
- def __init__(self, sock, timeout):
+ def __init__(self, sock, timeout, method):
self.sock = sock
+ self.method = method
self.raw_response = ''
- self._body = None
self._headers_len = 0
- self._content_len = 0
self.headers = None
self.will_close = False
self.status_line = ''
self.status = None
+ self.continued = False
self.http_version = None
self.reason = None
- self._chunked = False
- self._chunked_done = False
- self._chunked_until_next = 0
- self._chunked_skip_bytes = 0
- self._chunked_preloaded_block = None
+ self._reader = None
self._read_location = 0
self._eol = EOL
@@ -117,11 +112,12 @@
socket is closed, this will nearly always return False, even
in cases where all the data has actually been loaded.
"""
- if self._chunked:
- return self._chunked_done
- if self._content_len == _LEN_CLOSE_IS_END:
- return False
- return self._body is not None and len(self._body) >= self._content_len
+ if self._reader:
+ return self._reader.done()
+
+ def _close(self):
+ if self._reader is not None:
+ self._reader._close()
def readline(self):
"""Read a single line from the response body.
@@ -129,30 +125,34 @@
This may block until either a line ending is found or the
response is complete.
"""
- eol = self._body.find('\n', self._read_location)
- while eol == -1 and not self.complete():
+ # TODO: move this into the reader interface where it can be
+ # smarter (and probably avoid copies)
+ bytes = []
+ while not bytes:
+ try:
+ bytes = [self._reader.read(1)]
+ except _readers.ReadNotReady:
+ self._select()
+ while bytes[-1] != '\n' and not self.complete():
self._select()
- eol = self._body.find('\n', self._read_location)
- if eol != -1:
- eol += 1
- else:
- eol = len(self._body)
- data = self._body[self._read_location:eol]
- self._read_location = eol
- return data
+ bytes.append(self._reader.read(1))
+ if bytes[-1] != '\n':
+ next = self._reader.read(1)
+ while next and next != '\n':
+ bytes.append(next)
+ next = self._reader.read(1)
+ bytes.append(next)
+ return ''.join(bytes)
def read(self, length=None):
# if length is None, unbounded read
while (not self.complete() # never select on a finished read
and (not length # unbounded, so we wait for complete()
- or (self._read_location + length) > len(self._body))):
+ or length > self._reader.available_data)):
self._select()
if not length:
- length = len(self._body) - self._read_location
- elif len(self._body) < (self._read_location + length):
- length = len(self._body) - self._read_location
- r = self._body[self._read_location:self._read_location + length]
- self._read_location += len(r)
+ length = self._reader.available_data
+ r = self._reader.read(length)
if self.complete() and self.will_close:
self.sock.close()
return r
@@ -160,93 +160,34 @@
def _select(self):
r, _, _ = select.select([self.sock], [], [], self._timeout)
if not r:
- # socket was not readable. If the response is not complete
- # and we're not a _LEN_CLOSE_IS_END response, raise a timeout.
- # If we are a _LEN_CLOSE_IS_END response and we have no data,
- # raise a timeout.
- if not (self.complete() or
- (self._content_len == _LEN_CLOSE_IS_END and self._body)):
+ # socket was not readable. If the response is not
+ # complete, raise a timeout.
+ if not self.complete():
logger.info('timed out with timeout of %s', self._timeout)
raise HTTPTimeoutException('timeout reading data')
- logger.info('cl: %r body: %r', self._content_len, self._body)
try:
data = self.sock.recv(INCOMING_BUFFER_SIZE)
- # If the socket was readable and no data was read, that
- # means the socket was closed. If this isn't a
- # _CLOSE_IS_END socket, then something is wrong if we're
- # here (we shouldn't enter _select() if the response is
- # complete), so abort.
- if not data and self._content_len != _LEN_CLOSE_IS_END:
- raise HTTPRemoteClosedError(
- 'server appears to have closed the socket mid-response')
except socket.sslerror, e:
if e.args[0] != socket.SSL_ERROR_WANT_READ:
raise
logger.debug('SSL_WANT_READ in _select, should retry later')
return True
logger.debug('response read %d data during _select', len(data))
+ # If the socket was readable and no data was read, that
+ # means the socket was closed. Inform the reader so it can
+ # raise an exception if this is an invalid situation.
if not data:
- if self.headers and self._content_len == _LEN_CLOSE_IS_END:
- self._content_len = len(self._body)
+ self._reader._close()
return False
else:
self._load_response(data)
return True
- def _chunked_parsedata(self, data):
- if self._chunked_preloaded_block:
- data = self._chunked_preloaded_block + data
- self._chunked_preloaded_block = None
- while data:
- logger.debug('looping with %d data remaining', len(data))
- # Slice out anything we should skip
- if self._chunked_skip_bytes:
- if len(data) <= self._chunked_skip_bytes:
- self._chunked_skip_bytes -= len(data)
- data = ''
- break
- else:
- data = data[self._chunked_skip_bytes:]
- self._chunked_skip_bytes = 0
-
- # determine how much is until the next chunk
- if self._chunked_until_next:
- amt = self._chunked_until_next
- logger.debug('reading remaining %d of existing chunk', amt)
- self._chunked_until_next = 0
- body = data
- else:
- try:
- amt, body = data.split(self._eol, 1)
- except ValueError:
- self._chunked_preloaded_block = data
- logger.debug('saving %r as a preloaded block for chunked',
- self._chunked_preloaded_block)
- return
- amt = int(amt, base=16)
- logger.debug('reading chunk of length %d', amt)
- if amt == 0:
- self._chunked_done = True
-
- # read through end of what we have or the chunk
- self._body += body[:amt]
- if len(body) >= amt:
- data = body[amt:]
- self._chunked_skip_bytes = len(self._eol)
- else:
- self._chunked_until_next = amt - len(body)
- self._chunked_skip_bytes = 0
- data = ''
-
def _load_response(self, data):
- if self._chunked:
- self._chunked_parsedata(data)
- return
- elif self._body is not None:
- self._body += data
- return
-
- # We haven't seen end of headers yet
+ # Being here implies we're not at the end of the headers yet,
+ # since at the end of this method if headers were completely
+ # loaded we replace this method with the load() method of the
+ # reader we created.
self.raw_response += data
# This is a bogus server with bad line endings
if self._eol not in self.raw_response:
@@ -270,6 +211,7 @@
http_ver, status = hdrs.split(' ', 1)
if status.startswith('100'):
self.raw_response = body
+ self.continued = True
logger.debug('continue seen, setting body to %r', body)
return
@@ -289,23 +231,46 @@
if self._eol != EOL:
hdrs = hdrs.replace(self._eol, '\r\n')
headers = rfc822.Message(cStringIO.StringIO(hdrs))
+ content_len = None
if HDR_CONTENT_LENGTH in headers:
- self._content_len = int(headers[HDR_CONTENT_LENGTH])
+ content_len = int(headers[HDR_CONTENT_LENGTH])
if self.http_version == HTTP_VER_1_0:
self.will_close = True
elif HDR_CONNECTION_CTRL in headers:
self.will_close = (
headers[HDR_CONNECTION_CTRL].lower() == CONNECTION_CLOSE)
- if self._content_len == 0:
- self._content_len = _LEN_CLOSE_IS_END
if (HDR_XFER_ENCODING in headers
and headers[HDR_XFER_ENCODING].lower() == XFER_ENCODING_CHUNKED):
- self._body = ''
- self._chunked_parsedata(body)
- self._chunked = True
- if self._body is None:
- self._body = body
+ self._reader = _readers.ChunkedReader(self._eol)
+ logger.debug('using a chunked reader')
+ else:
+ # HEAD responses are forbidden from returning a body, and
+ # it's implausible for a CONNECT response to use
+ # close-is-end logic for an OK response.
+ if (self.method == 'HEAD' or
+ (self.method == 'CONNECT' and content_len is None)):
+ content_len = 0
+ if content_len is not None:
+ logger.debug('using a content-length reader with length %d',
+ content_len)
+ self._reader = _readers.ContentLengthReader(content_len)
+ else:
+ # Response body had no length specified and is not
+ # chunked, so the end of the body will only be
+ # identifiable by the termination of the socket by the
+ # server. My interpretation of the spec means that we
+ # are correct in hitting this case if
+ # transfer-encoding, content-length, and
+ # connection-control were left unspecified.
+ self._reader = _readers.CloseIsEndReader()
+ logger.debug('using a close-is-end reader')
+ self.will_close = True
+
+ if body:
+ self._reader._load(body)
+ logger.debug('headers complete')
self.headers = headers
+ self._load_response = self._reader._load
class HTTPConnection(object):
@@ -382,13 +347,14 @@
{}, HTTP_VER_1_0)
sock.send(data)
sock.setblocking(0)
- r = self.response_class(sock, self.timeout)
+ r = self.response_class(sock, self.timeout, 'CONNECT')
timeout_exc = HTTPTimeoutException(
'Timed out waiting for CONNECT response from proxy')
while not r.complete():
try:
if not r._select():
- raise timeout_exc
+ if not r.complete():
+ raise timeout_exc
except HTTPTimeoutException:
# This raise/except pattern looks goofy, but
# _select can raise the timeout as well as the
@@ -527,7 +493,7 @@
out = outgoing_headers or body
blocking_on_continue = False
if expect_continue and not outgoing_headers and not (
- response and response.headers):
+ response and (response.headers or response.continued)):
logger.info(
'waiting up to %s seconds for'
' continue response from server',
@@ -550,11 +516,6 @@
'server, optimistically sending request body')
else:
raise HTTPTimeoutException('timeout sending data')
- # TODO exceptional conditions with select? (what are those be?)
- # TODO if the response is loading, must we finish sending at all?
- #
- # Certainly not if it's going to close the connection and/or
- # the response is already done...I think.
was_first = first
# incoming data
@@ -572,11 +533,11 @@
logger.info('socket appears closed in read')
self.sock = None
self._current_response = None
+ if response is not None:
+ response._close()
# This if/elif ladder is a bit subtle,
# comments in each branch should help.
- if response is not None and (
- response.complete() or
- response._content_len == _LEN_CLOSE_IS_END):
+ if response is not None and response.complete():
# Server responded completely and then
# closed the socket. We should just shut
# things down and let the caller get their
@@ -605,7 +566,7 @@
'response was missing or incomplete!')
logger.debug('read %d bytes in request()', len(data))
if response is None:
- response = self.response_class(r[0], self.timeout)
+ response = self.response_class(r[0], self.timeout, method)
response._load_response(data)
# Jump to the next select() call so we load more
# data if the server is still sending us content.
@@ -613,10 +574,6 @@
except socket.error, e:
if e[0] != errno.EPIPE and not was_first:
raise
- if (response._content_len
- and response._content_len != _LEN_CLOSE_IS_END):
- outgoing_headers = sent_data + outgoing_headers
- reconnect('read')
# outgoing data
if w and out:
@@ -661,7 +618,7 @@
# close if the server response said to or responded before eating
# the whole request
if response is None:
- response = self.response_class(self.sock, self.timeout)
+ response = self.response_class(self.sock, self.timeout, method)
complete = response.complete()
data_left = bool(outgoing_headers or body)
if data_left:
@@ -705,7 +662,7 @@
class HTTPStateError(httplib.HTTPException):
"""Invalid internal state encountered."""
-
-class HTTPRemoteClosedError(httplib.HTTPException):
- """The server closed the remote socket in the middle of a response."""
+# Forward this exception type from _readers since it needs to be part
+# of the public API.
+HTTPRemoteClosedError = _readers.HTTPRemoteClosedError
# no-check-code
diff --git a/mercurial/httpclient/tests/simple_http_test.py b/mercurial/httpclient/tests/simple_http_test.py
--- a/mercurial/httpclient/tests/simple_http_test.py
+++ b/mercurial/httpclient/tests/simple_http_test.py
@@ -29,7 +29,7 @@
import socket
import unittest
-import http
+import httpplus
# relative import to ease embedding the library
import util
@@ -38,7 +38,7 @@
class SimpleHttpTest(util.HttpTestBase, unittest.TestCase):
def _run_simple_test(self, host, server_data, expected_req, expected_data):
- con = http.HTTPConnection(host)
+ con = httpplus.HTTPConnection(host)
con._connect()
con.sock.data = server_data
con.request('GET', '/')
@@ -47,9 +47,9 @@
self.assertEqual(expected_data, con.getresponse().read())
def test_broken_data_obj(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
- self.assertRaises(http.BadRequestData,
+ self.assertRaises(httpplus.BadRequestData,
con.request, 'POST', '/', body=1)
def test_no_keepalive_http_1_0(self):
@@ -74,7 +74,7 @@
fncache
dotencode
"""
- con = http.HTTPConnection('localhost:9999')
+ con = httpplus.HTTPConnection('localhost:9999')
con._connect()
con.sock.data = [expected_response_headers, expected_response_body]
con.request('GET', '/remote/.hg/requires',
@@ -95,7 +95,7 @@
self.assert_(resp.sock.closed)
def test_multiline_header(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
con.sock.data = ['HTTP/1.1 200 OK\r\n',
'Server: BogusServer 1.0\r\n',
@@ -122,7 +122,7 @@
self.assertEqual(con.sock.closed, False)
def testSimpleRequest(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
con.sock.data = ['HTTP/1.1 200 OK\r\n',
'Server: BogusServer 1.0\r\n',
@@ -149,12 +149,13 @@
resp.headers.getheaders('server'))
def testHeaderlessResponse(self):
- con = http.HTTPConnection('1.2.3.4', use_ssl=False)
+ con = httpplus.HTTPConnection('1.2.3.4', use_ssl=False)
con._connect()
con.sock.data = ['HTTP/1.1 200 OK\r\n',
'\r\n'
'1234567890'
]
+ con.sock.close_on_empty = True
con.request('GET', '/')
expected_req = ('GET / HTTP/1.1\r\n'
@@ -169,16 +170,14 @@
self.assertEqual(resp.status, 200)
def testReadline(self):
- con = http.HTTPConnection('1.2.3.4')
+ con = httpplus.HTTPConnection('1.2.3.4')
con._connect()
- # make sure it trickles in one byte at a time
- # so that we touch all the cases in readline
- con.sock.data = list(''.join(
- ['HTTP/1.1 200 OK\r\n',
- 'Server: BogusServer 1.0\r\n',
- 'Connection: Close\r\n',
- '\r\n'
- '1\n2\nabcdefg\n4\n5']))
+ con.sock.data = ['HTTP/1.1 200 OK\r\n',
+ 'Server: BogusServer 1.0\r\n',
+ 'Connection: Close\r\n',
+ '\r\n'
+ '1\n2\nabcdefg\n4\n5']
+ con.sock.close_on_empty = True
expected_req = ('GET / HTTP/1.1\r\n'
'Host: 1.2.3.4\r\n'
@@ -193,6 +192,85 @@
self.assertEqual(expected, actual,
'Expected %r, got %r' % (expected, actual))
+ def testReadlineTrickle(self):
+ con = httpplus.HTTPConnection('1.2.3.4')
+ con._connect()
+ # make sure it trickles in one byte at a time
+ # so that we touch all the cases in readline
+ con.sock.data = list(''.join(
+ ['HTTP/1.1 200 OK\r\n',
+ 'Server: BogusServer 1.0\r\n',
+ 'Connection: Close\r\n',
+ '\r\n'
+ '1\n2\nabcdefg\n4\n5']))
+ con.sock.close_on_empty = True
+
+ expected_req = ('GET / HTTP/1.1\r\n'
+ 'Host: 1.2.3.4\r\n'
+ 'accept-encoding: identity\r\n\r\n')
+
+ con.request('GET', '/')
+ self.assertEqual(('1.2.3.4', 80), con.sock.sa)
+ self.assertEqual(expected_req, con.sock.sent)
+ r = con.getresponse()
+ for expected in ['1\n', '2\n', 'abcdefg\n', '4\n', '5']:
+ actual = r.readline()
+ self.assertEqual(expected, actual,
+ 'Expected %r, got %r' % (expected, actual))
+
+ def testVariousReads(self):
+ con = httpplus.HTTPConnection('1.2.3.4')
+ con._connect()
+ # make sure it trickles in one byte at a time
+ # so that we touch all the cases in readline
+ con.sock.data = list(''.join(
+ ['HTTP/1.1 200 OK\r\n',
+ 'Server: BogusServer 1.0\r\n',
+ 'Connection: Close\r\n',
+ '\r\n'
+ '1\n2',
+ '\na', 'bc',
+ 'defg\n4\n5']))
+ con.sock.close_on_empty = True
+
+ expected_req = ('GET / HTTP/1.1\r\n'
+ 'Host: 1.2.3.4\r\n'
+ 'accept-encoding: identity\r\n\r\n')
+
+ con.request('GET', '/')
+ self.assertEqual(('1.2.3.4', 80), con.sock.sa)
+ self.assertEqual(expected_req, con.sock.sent)
+ r = con.getresponse()
+ for read_amt, expect in [(1, '1'), (1, '\n'),
+ (4, '2\nab'),
+ ('line', 'cdefg\n'),
+ (None, '4\n5')]:
+ if read_amt == 'line':
+ self.assertEqual(expect, r.readline())
+ else:
+ self.assertEqual(expect, r.read(read_amt))
+
+ def testZeroLengthBody(self):
+ con = httpplus.HTTPConnection('1.2.3.4')
+ con._connect()
+ # make sure it trickles in one byte at a time
+ # so that we touch all the cases in readline
+ con.sock.data = list(''.join(
+ ['HTTP/1.1 200 OK\r\n',
+ 'Server: BogusServer 1.0\r\n',
+ 'Content-length: 0\r\n',
+ '\r\n']))
+
+ expected_req = ('GET / HTTP/1.1\r\n'
+ 'Host: 1.2.3.4\r\n'
+ 'accept-encoding: identity\r\n\r\n')
+
+ con.request('GET', '/')
+ self.assertEqual(('1.2.3.4', 80), con.sock.sa)
+ self.assertEqual(expected_req, con.sock.sent)
+ r = con.getresponse()
+ self.assertEqual('', r.read())
+
def testIPv6(self):
self._run_simple_test('[::1]:8221',
['HTTP/1.1 200 OK\r\n',
@@ -226,7 +304,7 @@
'1234567890')
def testEarlyContinueResponse(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
sock = con.sock
sock.data = ['HTTP/1.1 403 Forbidden\r\n',
@@ -240,8 +318,23 @@
self.assertEqual("You can't do that.", con.getresponse().read())
self.assertEqual(sock.closed, True)
+ def testEarlyContinueResponseNoContentLength(self):
+ con = httpplus.HTTPConnection('1.2.3.4:80')
+ con._connect()
+ sock = con.sock
+ sock.data = ['HTTP/1.1 403 Forbidden\r\n',
+ 'Server: BogusServer 1.0\r\n',
+ '\r\n'
+ "You can't do that."]
+ sock.close_on_empty = True
+ expected_req = self.doPost(con, expect_body=False)
+ self.assertEqual(('1.2.3.4', 80), sock.sa)
+ self.assertStringEqual(expected_req, sock.sent)
+ self.assertEqual("You can't do that.", con.getresponse().read())
+ self.assertEqual(sock.closed, True)
+
def testDeniedAfterContinueTimeoutExpires(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
sock = con.sock
sock.data = ['HTTP/1.1 403 Forbidden\r\n',
@@ -269,7 +362,7 @@
self.assertEqual(sock.closed, True)
def testPostData(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
sock = con.sock
sock.read_wait_sentinel = 'POST data'
@@ -286,7 +379,7 @@
self.assertEqual(sock.closed, False)
def testServerWithoutContinue(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
sock = con.sock
sock.read_wait_sentinel = 'POST data'
@@ -302,7 +395,7 @@
self.assertEqual(sock.closed, False)
def testServerWithSlowContinue(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
sock = con.sock
sock.read_wait_sentinel = 'POST data'
@@ -321,7 +414,7 @@
self.assertEqual(sock.closed, False)
def testSlowConnection(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
# simulate one byte arriving at a time, to check for various
# corner cases
@@ -341,11 +434,11 @@
self.assertEqual('1234567890', con.getresponse().read())
def testTimeout(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
con.sock.data = []
con.request('GET', '/')
- self.assertRaises(http.HTTPTimeoutException,
+ self.assertRaises(httpplus.HTTPTimeoutException,
con.getresponse)
expected_req = ('GET / HTTP/1.1\r\n'
@@ -370,7 +463,7 @@
return s
socket.socket = closingsocket
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
con.request('GET', '/')
r1 = con.getresponse()
@@ -381,7 +474,7 @@
self.assertEqual(2, len(sockets))
def test_server_closes_before_end_of_body(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
s = con.sock
s.data = ['HTTP/1.1 200 OK\r\n',
@@ -393,9 +486,9 @@
s.close_on_empty = True
con.request('GET', '/')
r1 = con.getresponse()
- self.assertRaises(http.HTTPRemoteClosedError, r1.read)
+ self.assertRaises(httpplus.HTTPRemoteClosedError, r1.read)
def test_no_response_raises_response_not_ready(self):
- con = http.HTTPConnection('foo')
- self.assertRaises(http.httplib.ResponseNotReady, con.getresponse)
+ con = httpplus.HTTPConnection('foo')
+ self.assertRaises(httpplus.httplib.ResponseNotReady, con.getresponse)
# no-check-code
diff --git a/mercurial/httpclient/tests/test_bogus_responses.py b/mercurial/httpclient/tests/test_bogus_responses.py
--- a/mercurial/httpclient/tests/test_bogus_responses.py
+++ b/mercurial/httpclient/tests/test_bogus_responses.py
@@ -34,7 +34,7 @@
"""
import unittest
-import http
+import httpplus
# relative import to ease embedding the library
import util
@@ -43,7 +43,7 @@
class SimpleHttpTest(util.HttpTestBase, unittest.TestCase):
def bogusEOL(self, eol):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
con.sock.data = ['HTTP/1.1 200 OK%s' % eol,
'Server: BogusServer 1.0%s' % eol,
diff --git a/mercurial/httpclient/tests/test_chunked_transfer.py b/mercurial/httpclient/tests/test_chunked_transfer.py
--- a/mercurial/httpclient/tests/test_chunked_transfer.py
+++ b/mercurial/httpclient/tests/test_chunked_transfer.py
@@ -29,7 +29,7 @@
import cStringIO
import unittest
-import http
+import httpplus
# relative import to ease embedding the library
import util
@@ -50,7 +50,7 @@
class ChunkedTransferTest(util.HttpTestBase, unittest.TestCase):
def testChunkedUpload(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
sock = con.sock
sock.read_wait_sentinel = '0\r\n\r\n'
@@ -77,7 +77,7 @@
self.assertEqual(sock.closed, False)
def testChunkedDownload(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
sock = con.sock
sock.data = ['HTTP/1.1 200 OK\r\n',
@@ -85,14 +85,31 @@
'transfer-encoding: chunked',
'\r\n\r\n',
chunkedblock('hi '),
- chunkedblock('there'),
+ ] + list(chunkedblock('there')) + [
chunkedblock(''),
]
con.request('GET', '/')
self.assertStringEqual('hi there', con.getresponse().read())
+ def testChunkedDownloadOddReadBoundaries(self):
+ con = httpplus.HTTPConnection('1.2.3.4:80')
+ con._connect()
+ sock = con.sock
+ sock.data = ['HTTP/1.1 200 OK\r\n',
+ 'Server: BogusServer 1.0\r\n',
+ 'transfer-encoding: chunked',
+ '\r\n\r\n',
+ chunkedblock('hi '),
+ ] + list(chunkedblock('there')) + [
+ chunkedblock(''),
+ ]
+ con.request('GET', '/')
+ resp = con.getresponse()
+ for amt, expect in [(1, 'h'), (5, 'i the'), (100, 're')]:
+ self.assertEqual(expect, resp.read(amt))
+
def testChunkedDownloadBadEOL(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
sock = con.sock
sock.data = ['HTTP/1.1 200 OK\n',
@@ -107,7 +124,7 @@
self.assertStringEqual('hi there', con.getresponse().read())
def testChunkedDownloadPartialChunkBadEOL(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
sock = con.sock
sock.data = ['HTTP/1.1 200 OK\n',
@@ -122,7 +139,7 @@
con.getresponse().read())
def testChunkedDownloadPartialChunk(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
sock = con.sock
sock.data = ['HTTP/1.1 200 OK\r\n',
@@ -136,7 +153,7 @@
con.getresponse().read())
def testChunkedDownloadEarlyHangup(self):
- con = http.HTTPConnection('1.2.3.4:80')
+ con = httpplus.HTTPConnection('1.2.3.4:80')
con._connect()
sock = con.sock
broken = chunkedblock('hi'*20)[:-1]
@@ -149,5 +166,5 @@
sock.close_on_empty = True
con.request('GET', '/')
resp = con.getresponse()
- self.assertRaises(http.HTTPRemoteClosedError, resp.read)
+ self.assertRaises(httpplus.HTTPRemoteClosedError, resp.read)
# no-check-code
diff --git a/mercurial/httpclient/tests/test_proxy_support.py b/mercurial/httpclient/tests/test_proxy_support.py
--- a/mercurial/httpclient/tests/test_proxy_support.py
+++ b/mercurial/httpclient/tests/test_proxy_support.py
@@ -29,13 +29,13 @@
import unittest
import socket
-import http
+import httpplus
# relative import to ease embedding the library
import util
-def make_preloaded_socket(data):
+def make_preloaded_socket(data, close=False):
"""Make a socket pre-loaded with data so it can be read during connect.
Useful for https proxy tests because we have to read from the
@@ -44,6 +44,7 @@
def s(*args, **kwargs):
sock = util.MockSocket(*args, **kwargs)
sock.early_data = data[:]
+ sock.close_on_empty = close
return sock
return s
@@ -51,7 +52,7 @@
class ProxyHttpTest(util.HttpTestBase, unittest.TestCase):
def _run_simple_test(self, host, server_data, expected_req, expected_data):
- con = http.HTTPConnection(host)
+ con = httpplus.HTTPConnection(host)
con._connect()
con.sock.data = server_data
con.request('GET', '/')
@@ -60,7 +61,7 @@
self.assertEqual(expected_data, con.getresponse().read())
def testSimpleRequest(self):
- con = http.HTTPConnection('1.2.3.4:80',
+ con = httpplus.HTTPConnection('1.2.3.4:80',
proxy_hostport=('magicproxy', 4242))
con._connect()
con.sock.data = ['HTTP/1.1 200 OK\r\n',
@@ -88,7 +89,7 @@
resp.headers.getheaders('server'))
def testSSLRequest(self):
- con = http.HTTPConnection('1.2.3.4:443',
+ con = httpplus.HTTPConnection('1.2.3.4:443',
proxy_hostport=('magicproxy', 4242))
socket.socket = make_preloaded_socket(
['HTTP/1.1 200 OK\r\n',
@@ -124,12 +125,47 @@
self.assertEqual(['BogusServer 1.0'],
resp.headers.getheaders('server'))
- def testSSLProxyFailure(self):
- con = http.HTTPConnection('1.2.3.4:443',
+ def testSSLRequestNoConnectBody(self):
+ con = httpplus.HTTPConnection('1.2.3.4:443',
proxy_hostport=('magicproxy', 4242))
socket.socket = make_preloaded_socket(
- ['HTTP/1.1 407 Proxy Authentication Required\r\n\r\n'])
- self.assertRaises(http.HTTPProxyConnectFailedException, con._connect)
- self.assertRaises(http.HTTPProxyConnectFailedException,
+ ['HTTP/1.1 200 OK\r\n',
+ 'Server: BogusServer 1.0\r\n',
+ '\r\n'])
+ con._connect()
+ con.sock.data = ['HTTP/1.1 200 OK\r\n',
+ 'Server: BogusServer 1.0\r\n',
+ 'Content-Length: 10\r\n',
+ '\r\n'
+ '1234567890'
+ ]
+ connect_sent = con.sock.sent
+ con.sock.sent = ''
+ con.request('GET', '/')
+
+ expected_connect = ('CONNECT 1.2.3.4:443 HTTP/1.0\r\n'
+ 'Host: 1.2.3.4\r\n'
+ 'accept-encoding: identity\r\n'
+ '\r\n')
+ expected_request = ('GET / HTTP/1.1\r\n'
+ 'Host: 1.2.3.4\r\n'
+ 'accept-encoding: identity\r\n\r\n')
+
+ self.assertEqual(('127.0.0.42', 4242), con.sock.sa)
+ self.assertStringEqual(expected_connect, connect_sent)
+ self.assertStringEqual(expected_request, con.sock.sent)
+ resp = con.getresponse()
+ self.assertEqual(resp.status, 200)
+ self.assertEqual('1234567890', resp.read())
+ self.assertEqual(['BogusServer 1.0'],
+ resp.headers.getheaders('server'))
+
+ def testSSLProxyFailure(self):
+ con = httpplus.HTTPConnection('1.2.3.4:443',
+ proxy_hostport=('magicproxy', 4242))
+ socket.socket = make_preloaded_socket(
+ ['HTTP/1.1 407 Proxy Authentication Required\r\n\r\n'], close=True)
+ self.assertRaises(httpplus.HTTPProxyConnectFailedException, con._connect)
+ self.assertRaises(httpplus.HTTPProxyConnectFailedException,
con.request, 'GET', '/')
# no-check-code
diff --git a/mercurial/httpclient/tests/test_ssl.py b/mercurial/httpclient/tests/test_ssl.py
--- a/mercurial/httpclient/tests/test_ssl.py
+++ b/mercurial/httpclient/tests/test_ssl.py
@@ -28,7 +28,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest
-import http
+import httpplus
# relative import to ease embedding the library
import util
@@ -37,7 +37,7 @@
class HttpSslTest(util.HttpTestBase, unittest.TestCase):
def testSslRereadRequired(self):
- con = http.HTTPConnection('1.2.3.4:443')
+ con = httpplus.HTTPConnection('1.2.3.4:443')
con._connect()
# extend the list instead of assign because of how
# MockSSLSocket works.
@@ -66,7 +66,7 @@
resp.headers.getheaders('server'))
def testSslRereadInEarlyResponse(self):
- con = http.HTTPConnection('1.2.3.4:443')
+ con = httpplus.HTTPConnection('1.2.3.4:443')
con._connect()
con.sock.early_data = ['HTTP/1.1 200 OK\r\n',
'Server: BogusServer 1.0\r\n',
diff --git a/mercurial/httpclient/tests/util.py b/mercurial/httpclient/tests/util.py
--- a/mercurial/httpclient/tests/util.py
+++ b/mercurial/httpclient/tests/util.py
@@ -29,7 +29,7 @@
import difflib
import socket
-import http
+import httpplus
class MockSocket(object):
@@ -57,7 +57,7 @@
self.remote_closed = self.closed = False
self.close_on_empty = False
self.sent = ''
- self.read_wait_sentinel = http._END_HEADERS
+ self.read_wait_sentinel = httpplus._END_HEADERS
def close(self):
self.closed = True
@@ -86,7 +86,7 @@
@property
def ready_for_read(self):
- return ((self.early_data and http._END_HEADERS in self.sent)
+ return ((self.early_data and httpplus._END_HEADERS in self.sent)
or (self.read_wait_sentinel in self.sent and self.data)
or self.closed or self.remote_closed)
@@ -132,7 +132,7 @@
def mocksslwrap(sock, keyfile=None, certfile=None,
- server_side=False, cert_reqs=http.socketutil.CERT_NONE,
+ server_side=False, cert_reqs=httpplus.socketutil.CERT_NONE,
ssl_version=None, ca_certs=None,
do_handshake_on_connect=True,
suppress_ragged_eofs=True):
@@ -156,16 +156,16 @@
self.orig_getaddrinfo = socket.getaddrinfo
socket.getaddrinfo = mockgetaddrinfo
- self.orig_select = http.select.select
- http.select.select = mockselect
+ self.orig_select = httpplus.select.select
+ httpplus.select.select = mockselect
- self.orig_sslwrap = http.socketutil.wrap_socket
- http.socketutil.wrap_socket = mocksslwrap
+ self.orig_sslwrap = httpplus.socketutil.wrap_socket
+ httpplus.socketutil.wrap_socket = mocksslwrap
def tearDown(self):
socket.socket = self.orig_socket
- http.select.select = self.orig_select
- http.socketutil.wrap_socket = self.orig_sslwrap
+ httpplus.select.select = self.orig_select
+ httpplus.socketutil.wrap_socket = self.orig_sslwrap
socket.getaddrinfo = self.orig_getaddrinfo
def assertStringEqual(self, l, r):
More information about the Mercurial-devel
mailing list