durin42 / async-http

Async http library intended for use in httplib2. Probably buggy, will become part of httplib2 when it is ready.

Clone this repository (size: 61.7 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/durin42/async-http/
commit 46: 0ba711e07bf9
parent 45: 50a458cf2774
branch: default
http: fix a bug with incoming chunked-transfer encoding We would fail to read the block properly if the read from the socket split the incoming buffer in the middle of the chunk length indicator
Augie Fackler / durin42
6 weeks ago

Changed (Δ4.4 KB):

raw changeset »

http.py (13 lines added, 1 lines removed)

test/simple_http_test.py (0 lines added, 26 lines removed)

test/test_chunked_transfer.py (75 lines added, 0 lines removed)

Up to file-list http.py:

@@ -64,6 +64,7 @@ class HTTPResponse(object):
64
64
        self._chunked_done = False
65
65
        self._chunked_until_next = 0
66
66
        self._chunked_skip_bytes = 0
67
        self._chunked_preloaded_block = None
67
68
        self.wait_for_continue = expect_continue
68
69
69
70
        self._read_location = 0
@@ -119,6 +120,10 @@ class HTTPResponse(object):
119
120
            self._load_response(data)
120
121
121
122
    def _chunked_parsedata(self, data):
123
        if self._chunked_preloaded_block:
124
            oldlen = len(data)
125
            data = self._chunked_preloaded_block + data
126
            self._chunked_preloaded_block = None
122
127
        while data:
123
128
            logger.debug('looping with %d data remaining', len(data))
124
129
            # Slice out anything we should skip
@@ -129,6 +134,7 @@ class HTTPResponse(object):
129
134
                    break
130
135
                else:
131
136
                    data = data[self._chunked_skip_bytes:]
137
                    self._chunked_skip_bytes = 0
132
138
133
139
            # determine how much is until the next chunk
134
140
            if self._chunked_until_next:
@@ -137,7 +143,13 @@ class HTTPResponse(object):
137
143
                self._chunked_until_next = 0
138
144
                body = data
139
145
            else:
140
                amt, body = data.split(EOL, 1)
146
                try:
147
                    amt, body = data.split(EOL, 1)
148
                except ValueError:
149
                    self._chunked_preloaded_block = data
150
                    logger.debug('saving %r as a preloaded block for chunked',
151
                                 self._chunked_preloaded_block)
152
                    return
141
153
                amt = int(amt, base=16)
142
154
                logger.debug('reading chunk of length %d', amt)
143
155
                if amt == 0:

Up to file-list test/simple_http_test.py:

@@ -109,29 +109,3 @@ class SimpleHttpTest(util.HttpTestBase,
109
109
        self.assertEqual(expected_req, sock.sent)
110
110
        self.assertEqual("You can do that.", con.getresponse().read())
111
111
        self.assertEqual(sock.closed, False)
112
113
    def testChunkedTransfer(self):
114
        con = http.HTTPConnection('1.2.3.4:80')
115
        con._connect()
116
        sock = con.sock
117
        sock.read_wait_sentinel = 'end-of-body'
118
        sock.data = ['HTTP/1.1 200 OK\r\n',
119
                     'Server: BogusServer 1.0\r\n',
120
                     'Content-Length: 6',
121
                     '\r\n\r\n',
122
                     "Thanks"]
123
124
        zz = 'zz\n'
125
        con.request('POST', '/', body=cStringIO.StringIO((zz * (0x8010/3)) + 'end-of-body'))
126
        expected_req = ('POST / HTTP/1.1\r\n'
127
                        'transfer-encoding: chunked\r\n'
128
                        'Host: 1.2.3.4\r\n'
129
                        'accept-encoding: identity\r\n\r\n')
130
        expected_req += '8000\r\n%szz\r\n' % ('zz\n' * (0x8000 / 3))
131
        expected_req += '1b\r\n\n%s\r\n' % ('zz\n' * ((0x1b - len('end-of-body')) / 3)
132
                                          + 'end-of-body')
133
        expected_req += '0\r\n\r\n'
134
        self.assertEqual(('1.2.3.4', 80), sock.sa)
135
        self.assertStringEqual(expected_req, sock.sent)
136
        self.assertEqual("Thanks", con.getresponse().read())
137
        self.assertEqual(sock.closed, False)

Up to file-list test/test_chunked_transfer.py:

1
import cStringIO
2
import doctest
3
import unittest
4
5
import http
6
import http_socket
7
from test import util
8
9
10
def chunkedblock(x):
11
    """Make a chunked transfer-encoding block.
12
13
    >>> chunkedblock('hi')
14
    '2\r\nhi\r\n'
15
    >>> chunkedblock('hi' * 10)
16
    '14\r\nhihihihihihihihihihi\r\n'
17
    """
18
    return '%s\r\n%s\r\n' % (hex(len(x))[2:], x)
19
20
21
class ChunkedTransferTest(util.HttpTestBase, unittest.TestCase):
22
    def testChunkedUpload(self):
23
        con = http.HTTPConnection('1.2.3.4:80')
24
        con._connect()
25
        sock = con.sock
26
        sock.read_wait_sentinel = 'end-of-body'
27
        sock.data = ['HTTP/1.1 200 OK\r\n',
28
                     'Server: BogusServer 1.0\r\n',
29
                     'Content-Length: 6',
30
                     '\r\n\r\n',
31
                     "Thanks"]
32
33
        zz = 'zz\n'
34
        con.request('POST', '/', body=cStringIO.StringIO((zz * (0x8010/3)) + 'end-of-body'))
35
        expected_req = ('POST / HTTP/1.1\r\n'
36
                        'transfer-encoding: chunked\r\n'
37
                        'Host: 1.2.3.4\r\n'
38
                        'accept-encoding: identity\r\n\r\n')
39
        expected_req += chunkedblock('zz\n' * (0x8000 / 3) + 'zz')
40
        expected_req += chunkedblock('\n' + 'zz\n' * ((0x1b - len('end-of-body')) / 3)
41
                                     + 'end-of-body')
42
        expected_req += '0\r\n\r\n'
43
        self.assertEqual(('1.2.3.4', 80), sock.sa)
44
        self.assertStringEqual(expected_req, sock.sent)
45
        self.assertEqual("Thanks", con.getresponse().read())
46
        self.assertEqual(sock.closed, False)
47
48
    def testChunkedDownload(self):
49
        con = http.HTTPConnection('1.2.3.4:80')
50
        con._connect()
51
        sock = con.sock
52
        sock.data = ['HTTP/1.1 200 OK\r\n',
53
                     'Server: BogusServer 1.0\r\n',
54
                     'transfer-encoding: chunked',
55
                     '\r\n\r\n',
56
                     chunkedblock('hi '),
57
                     chunkedblock('there'),
58
                     chunkedblock(''),
59
                     ]
60
        con.request('GET', '/')
61
        self.assertStringEqual('hi there', con.getresponse().read())
62
63
    def testChunkedDownloadPartialChunk(self):
64
        con = http.HTTPConnection('1.2.3.4:80')
65
        con._connect()
66
        sock = con.sock
67
        sock.data = ['HTTP/1.1 200 OK\r\n',
68
                     'Server: BogusServer 1.0\r\n',
69
                     'transfer-encoding: chunked',
70
                     '\r\n\r\n',
71
                     chunkedblock('hi '),
72
                     ] + list(chunkedblock('there\n' * 5)) + [chunkedblock('')]
73
        con.request('GET', '/')
74
        self.assertStringEqual('hi there\nthere\nthere\nthere\nthere\n',
75
                               con.getresponse().read())