##// END OF EJS Templates
httpclient: update to c5abd358e543 of httpplus
Augie Fackler -
r16774:69af967b default
parent child Browse files
Show More
@@ -1,670 +1,674 b''
1 1 # Copyright 2010, Google Inc.
2 2 # All rights reserved.
3 3 #
4 4 # Redistribution and use in source and binary forms, with or without
5 5 # modification, are permitted provided that the following conditions are
6 6 # met:
7 7 #
8 8 # * Redistributions of source code must retain the above copyright
9 9 # notice, this list of conditions and the following disclaimer.
10 10 # * Redistributions in binary form must reproduce the above
11 11 # copyright notice, this list of conditions and the following disclaimer
12 12 # in the documentation and/or other materials provided with the
13 13 # distribution.
14 14 # * Neither the name of Google Inc. nor the names of its
15 15 # contributors may be used to endorse or promote products derived from
16 16 # this software without specific prior written permission.
17 17
18 18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 29 """Improved HTTP/1.1 client library
30 30
31 31 This library contains an HTTPConnection which is similar to the one in
32 32 httplib, but has several additional features:
33 33
34 34 * supports keepalives natively
35 35 * uses select() to block for incoming data
36 36 * notices when the server responds early to a request
37 37 * implements ssl inline instead of in a different class
38 38 """
39 39
40 40 import cStringIO
41 41 import errno
42 42 import httplib
43 43 import logging
44 44 import rfc822
45 45 import select
46 46 import socket
47 47
48 48 import _readers
49 49 import socketutil
50 50
51 51 logger = logging.getLogger(__name__)
52 52
53 53 __all__ = ['HTTPConnection', 'HTTPResponse']
54 54
55 55 HTTP_VER_1_0 = 'HTTP/1.0'
56 56 HTTP_VER_1_1 = 'HTTP/1.1'
57 57
58 58 OUTGOING_BUFFER_SIZE = 1 << 15
59 59 INCOMING_BUFFER_SIZE = 1 << 20
60 60
61 61 HDR_ACCEPT_ENCODING = 'accept-encoding'
62 62 HDR_CONNECTION_CTRL = 'connection'
63 63 HDR_CONTENT_LENGTH = 'content-length'
64 64 HDR_XFER_ENCODING = 'transfer-encoding'
65 65
66 66 XFER_ENCODING_CHUNKED = 'chunked'
67 67
68 68 CONNECTION_CLOSE = 'close'
69 69
70 70 EOL = '\r\n'
71 71 _END_HEADERS = EOL * 2
72 72
73 73 # Based on some searching around, 1 second seems like a reasonable
74 74 # default here.
75 75 TIMEOUT_ASSUME_CONTINUE = 1
76 76 TIMEOUT_DEFAULT = None
77 77
78 78
79 79 class HTTPResponse(object):
80 80 """Response from an HTTP server.
81 81
82 82 The response will continue to load as available. If you need the
83 83 complete response before continuing, check the .complete() method.
84 84 """
85 85 def __init__(self, sock, timeout, method):
86 86 self.sock = sock
87 87 self.method = method
88 88 self.raw_response = ''
89 89 self._headers_len = 0
90 90 self.headers = None
91 91 self.will_close = False
92 92 self.status_line = ''
93 93 self.status = None
94 94 self.continued = False
95 95 self.http_version = None
96 96 self.reason = None
97 97 self._reader = None
98 98
99 99 self._read_location = 0
100 100 self._eol = EOL
101 101
102 102 self._timeout = timeout
103 103
104 104 @property
105 105 def _end_headers(self):
106 106 return self._eol * 2
107 107
108 108 def complete(self):
109 109 """Returns true if this response is completely loaded.
110 110
111 111 Note that if this is a connection where complete means the
112 112 socket is closed, this will nearly always return False, even
113 113 in cases where all the data has actually been loaded.
114 114 """
115 115 if self._reader:
116 116 return self._reader.done()
117 117
118 118 def _close(self):
119 119 if self._reader is not None:
120 120 self._reader._close()
121 121
122 122 def readline(self):
123 123 """Read a single line from the response body.
124 124
125 125 This may block until either a line ending is found or the
126 126 response is complete.
127 127 """
128 128 # TODO: move this into the reader interface where it can be
129 129 # smarter (and probably avoid copies)
130 130 bytes = []
131 131 while not bytes:
132 132 try:
133 133 bytes = [self._reader.read(1)]
134 134 except _readers.ReadNotReady:
135 135 self._select()
136 136 while bytes[-1] != '\n' and not self.complete():
137 137 self._select()
138 138 bytes.append(self._reader.read(1))
139 139 if bytes[-1] != '\n':
140 140 next = self._reader.read(1)
141 141 while next and next != '\n':
142 142 bytes.append(next)
143 143 next = self._reader.read(1)
144 144 bytes.append(next)
145 145 return ''.join(bytes)
146 146
147 147 def read(self, length=None):
148 148 # if length is None, unbounded read
149 149 while (not self.complete() # never select on a finished read
150 150 and (not length # unbounded, so we wait for complete()
151 151 or length > self._reader.available_data)):
152 152 self._select()
153 153 if not length:
154 154 length = self._reader.available_data
155 155 r = self._reader.read(length)
156 156 if self.complete() and self.will_close:
157 157 self.sock.close()
158 158 return r
159 159
160 160 def _select(self):
161 161 r, _, _ = select.select([self.sock], [], [], self._timeout)
162 162 if not r:
163 163 # socket was not readable. If the response is not
164 164 # complete, raise a timeout.
165 165 if not self.complete():
166 166 logger.info('timed out with timeout of %s', self._timeout)
167 167 raise HTTPTimeoutException('timeout reading data')
168 168 try:
169 169 data = self.sock.recv(INCOMING_BUFFER_SIZE)
170 170 except socket.sslerror, e:
171 171 if e.args[0] != socket.SSL_ERROR_WANT_READ:
172 172 raise
173 173 logger.debug('SSL_WANT_READ in _select, should retry later')
174 174 return True
175 175 logger.debug('response read %d data during _select', len(data))
176 176 # If the socket was readable and no data was read, that means
177 177 # the socket was closed. Inform the reader (if any) so it can
178 178 # raise an exception if this is an invalid situation.
179 179 if not data:
180 180 if self._reader:
181 181 self._reader._close()
182 182 return False
183 183 else:
184 184 self._load_response(data)
185 185 return True
186 186
187 187 def _load_response(self, data):
188 188 # Being here implies we're not at the end of the headers yet,
189 189 # since at the end of this method if headers were completely
190 190 # loaded we replace this method with the load() method of the
191 191 # reader we created.
192 192 self.raw_response += data
193 193 # This is a bogus server with bad line endings
194 194 if self._eol not in self.raw_response:
195 195 for bad_eol in ('\n', '\r'):
196 196 if (bad_eol in self.raw_response
197 197 # verify that bad_eol is not the end of the incoming data
198 198 # as this could be a response line that just got
199 199 # split between \r and \n.
200 200 and (self.raw_response.index(bad_eol) <
201 201 (len(self.raw_response) - 1))):
202 202 logger.info('bogus line endings detected, '
203 203 'using %r for EOL', bad_eol)
204 204 self._eol = bad_eol
205 205 break
206 206 # exit early if not at end of headers
207 207 if self._end_headers not in self.raw_response or self.headers:
208 208 return
209 209
210 210 # handle 100-continue response
211 211 hdrs, body = self.raw_response.split(self._end_headers, 1)
212 212 http_ver, status = hdrs.split(' ', 1)
213 213 if status.startswith('100'):
214 214 self.raw_response = body
215 215 self.continued = True
216 216 logger.debug('continue seen, setting body to %r', body)
217 217 return
218 218
219 219 # arriving here means we should parse response headers
220 220 # as all headers have arrived completely
221 221 hdrs, body = self.raw_response.split(self._end_headers, 1)
222 222 del self.raw_response
223 223 if self._eol in hdrs:
224 224 self.status_line, hdrs = hdrs.split(self._eol, 1)
225 225 else:
226 226 self.status_line = hdrs
227 227 hdrs = ''
228 228 # TODO HTTP < 1.0 support
229 229 (self.http_version, self.status,
230 230 self.reason) = self.status_line.split(' ', 2)
231 231 self.status = int(self.status)
232 232 if self._eol != EOL:
233 233 hdrs = hdrs.replace(self._eol, '\r\n')
234 234 headers = rfc822.Message(cStringIO.StringIO(hdrs))
235 235 content_len = None
236 236 if HDR_CONTENT_LENGTH in headers:
237 237 content_len = int(headers[HDR_CONTENT_LENGTH])
238 238 if self.http_version == HTTP_VER_1_0:
239 239 self.will_close = True
240 240 elif HDR_CONNECTION_CTRL in headers:
241 241 self.will_close = (
242 242 headers[HDR_CONNECTION_CTRL].lower() == CONNECTION_CLOSE)
243 243 if (HDR_XFER_ENCODING in headers
244 244 and headers[HDR_XFER_ENCODING].lower() == XFER_ENCODING_CHUNKED):
245 245 self._reader = _readers.ChunkedReader(self._eol)
246 246 logger.debug('using a chunked reader')
247 247 else:
248 248 # HEAD responses are forbidden from returning a body, and
249 249 # it's implausible for a CONNECT response to use
250 250 # close-is-end logic for an OK response.
251 251 if (self.method == 'HEAD' or
252 252 (self.method == 'CONNECT' and content_len is None)):
253 253 content_len = 0
254 254 if content_len is not None:
255 255 logger.debug('using a content-length reader with length %d',
256 256 content_len)
257 257 self._reader = _readers.ContentLengthReader(content_len)
258 258 else:
259 259 # Response body had no length specified and is not
260 260 # chunked, so the end of the body will only be
261 261 # identifiable by the termination of the socket by the
262 262 # server. My interpretation of the spec means that we
263 263 # are correct in hitting this case if
264 264 # transfer-encoding, content-length, and
265 265 # connection-control were left unspecified.
266 266 self._reader = _readers.CloseIsEndReader()
267 267 logger.debug('using a close-is-end reader')
268 268 self.will_close = True
269 269
270 270 if body:
271 271 self._reader._load(body)
272 272 logger.debug('headers complete')
273 273 self.headers = headers
274 274 self._load_response = self._reader._load
275 275
276 276
277 277 class HTTPConnection(object):
278 278 """Connection to a single http server.
279 279
280 280 Supports 100-continue and keepalives natively. Uses select() for
281 281 non-blocking socket operations.
282 282 """
283 283 http_version = HTTP_VER_1_1
284 284 response_class = HTTPResponse
285 285
286 286 def __init__(self, host, port=None, use_ssl=None, ssl_validator=None,
287 287 timeout=TIMEOUT_DEFAULT,
288 288 continue_timeout=TIMEOUT_ASSUME_CONTINUE,
289 289 proxy_hostport=None, **ssl_opts):
290 290 """Create a new HTTPConnection.
291 291
292 292 Args:
293 293 host: The host to which we'll connect.
294 294 port: Optional. The port over which we'll connect. Default 80 for
295 295 non-ssl, 443 for ssl.
296 296 use_ssl: Optional. Wether to use ssl. Defaults to False if port is
297 297 not 443, true if port is 443.
298 298 ssl_validator: a function(socket) to validate the ssl cert
299 299 timeout: Optional. Connection timeout, default is TIMEOUT_DEFAULT.
300 300 continue_timeout: Optional. Timeout for waiting on an expected
301 301 "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE.
302 302 proxy_hostport: Optional. Tuple of (host, port) to use as an http
303 303 proxy for the connection. Default is to not use a proxy.
304 304 """
305 305 if port is None and host.count(':') == 1 or ']:' in host:
306 306 host, port = host.rsplit(':', 1)
307 307 port = int(port)
308 308 if '[' in host:
309 309 host = host[1:-1]
310 310 if use_ssl is None and port is None:
311 311 use_ssl = False
312 312 port = 80
313 313 elif use_ssl is None:
314 314 use_ssl = (port == 443)
315 315 elif port is None:
316 316 port = (use_ssl and 443 or 80)
317 317 self.port = port
318 318 if use_ssl and not socketutil.have_ssl:
319 319 raise Exception('ssl requested but unavailable on this Python')
320 320 self.ssl = use_ssl
321 321 self.ssl_opts = ssl_opts
322 322 self._ssl_validator = ssl_validator
323 323 self.host = host
324 324 self.sock = None
325 325 self._current_response = None
326 326 self._current_response_taken = False
327 327 if proxy_hostport is None:
328 328 self._proxy_host = self._proxy_port = None
329 329 else:
330 330 self._proxy_host, self._proxy_port = proxy_hostport
331 331
332 332 self.timeout = timeout
333 333 self.continue_timeout = continue_timeout
334 334
335 335 def _connect(self):
336 336 """Connect to the host and port specified in __init__."""
337 337 if self.sock:
338 338 return
339 339 if self._proxy_host is not None:
340 340 logger.info('Connecting to http proxy %s:%s',
341 341 self._proxy_host, self._proxy_port)
342 342 sock = socketutil.create_connection((self._proxy_host,
343 343 self._proxy_port))
344 344 if self.ssl:
345 345 # TODO proxy header support
346 346 data = self.buildheaders('CONNECT', '%s:%d' % (self.host,
347 347 self.port),
348 348 {}, HTTP_VER_1_0)
349 349 sock.send(data)
350 350 sock.setblocking(0)
351 351 r = self.response_class(sock, self.timeout, 'CONNECT')
352 352 timeout_exc = HTTPTimeoutException(
353 353 'Timed out waiting for CONNECT response from proxy')
354 354 while not r.complete():
355 355 try:
356 356 if not r._select():
357 357 if not r.complete():
358 358 raise timeout_exc
359 359 except HTTPTimeoutException:
360 360 # This raise/except pattern looks goofy, but
361 361 # _select can raise the timeout as well as the
362 362 # loop body. I wish it wasn't this convoluted,
363 363 # but I don't have a better solution
364 364 # immediately handy.
365 365 raise timeout_exc
366 366 if r.status != 200:
367 367 raise HTTPProxyConnectFailedException(
368 368 'Proxy connection failed: %d %s' % (r.status,
369 369 r.read()))
370 370 logger.info('CONNECT (for SSL) to %s:%s via proxy succeeded.',
371 371 self.host, self.port)
372 372 else:
373 373 sock = socketutil.create_connection((self.host, self.port))
374 374 if self.ssl:
375 # This is the default, but in the case of proxied SSL
376 # requests the proxy logic above will have cleared
377 # blocking mode, so reenable it just to be safe.
378 sock.setblocking(1)
375 379 logger.debug('wrapping socket for ssl with options %r',
376 380 self.ssl_opts)
377 381 sock = socketutil.wrap_socket(sock, **self.ssl_opts)
378 382 if self._ssl_validator:
379 383 self._ssl_validator(sock)
380 384 sock.setblocking(0)
381 385 self.sock = sock
382 386
383 387 def buildheaders(self, method, path, headers, http_ver):
384 388 if self.ssl and self.port == 443 or self.port == 80:
385 389 # default port for protocol, so leave it out
386 390 hdrhost = self.host
387 391 else:
388 392 # include nonstandard port in header
389 393 if ':' in self.host: # must be IPv6
390 394 hdrhost = '[%s]:%d' % (self.host, self.port)
391 395 else:
392 396 hdrhost = '%s:%d' % (self.host, self.port)
393 397 if self._proxy_host and not self.ssl:
394 398 # When talking to a regular http proxy we must send the
395 399 # full URI, but in all other cases we must not (although
396 400 # technically RFC 2616 says servers must accept our
397 401 # request if we screw up, experimentally few do that
398 402 # correctly.)
399 403 assert path[0] == '/', 'path must start with a /'
400 404 path = 'http://%s%s' % (hdrhost, path)
401 405 outgoing = ['%s %s %s%s' % (method, path, http_ver, EOL)]
402 406 headers['host'] = ('Host', hdrhost)
403 407 headers[HDR_ACCEPT_ENCODING] = (HDR_ACCEPT_ENCODING, 'identity')
404 408 for hdr, val in headers.itervalues():
405 409 outgoing.append('%s: %s%s' % (hdr, val, EOL))
406 410 outgoing.append(EOL)
407 411 return ''.join(outgoing)
408 412
409 413 def close(self):
410 414 """Close the connection to the server.
411 415
412 416 This is a no-op if the connection is already closed. The
413 417 connection may automatically close if requessted by the server
414 418 or required by the nature of a response.
415 419 """
416 420 if self.sock is None:
417 421 return
418 422 self.sock.close()
419 423 self.sock = None
420 424 logger.info('closed connection to %s on %s', self.host, self.port)
421 425
422 426 def busy(self):
423 427 """Returns True if this connection object is currently in use.
424 428
425 429 If a response is still pending, this will return True, even if
426 430 the request has finished sending. In the future,
427 431 HTTPConnection may transparently juggle multiple connections
428 432 to the server, in which case this will be useful to detect if
429 433 any of those connections is ready for use.
430 434 """
431 435 cr = self._current_response
432 436 if cr is not None:
433 437 if self._current_response_taken:
434 438 if cr.will_close:
435 439 self.sock = None
436 440 self._current_response = None
437 441 return False
438 442 elif cr.complete():
439 443 self._current_response = None
440 444 return False
441 445 return True
442 446 return False
443 447
444 448 def request(self, method, path, body=None, headers={},
445 449 expect_continue=False):
446 450 """Send a request to the server.
447 451
448 452 For increased flexibility, this does not return the response
449 453 object. Future versions of HTTPConnection that juggle multiple
450 454 sockets will be able to send (for example) 5 requests all at
451 455 once, and then let the requests arrive as data is
452 456 available. Use the `getresponse()` method to retrieve the
453 457 response.
454 458 """
455 459 if self.busy():
456 460 raise httplib.CannotSendRequest(
457 461 'Can not send another request before '
458 462 'current response is read!')
459 463 self._current_response_taken = False
460 464
461 465 logger.info('sending %s request for %s to %s on port %s',
462 466 method, path, self.host, self.port)
463 467 hdrs = dict((k.lower(), (k, v)) for k, v in headers.iteritems())
464 468 if hdrs.get('expect', ('', ''))[1].lower() == '100-continue':
465 469 expect_continue = True
466 470 elif expect_continue:
467 471 hdrs['expect'] = ('Expect', '100-Continue')
468 472
469 473 chunked = False
470 474 if body and HDR_CONTENT_LENGTH not in hdrs:
471 475 if getattr(body, '__len__', False):
472 476 hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, len(body))
473 477 elif getattr(body, 'read', False):
474 478 hdrs[HDR_XFER_ENCODING] = (HDR_XFER_ENCODING,
475 479 XFER_ENCODING_CHUNKED)
476 480 chunked = True
477 481 else:
478 482 raise BadRequestData('body has no __len__() nor read()')
479 483
480 484 self._connect()
481 485 outgoing_headers = self.buildheaders(
482 486 method, path, hdrs, self.http_version)
483 487 response = None
484 488 first = True
485 489
486 490 def reconnect(where):
487 491 logger.info('reconnecting during %s', where)
488 492 self.close()
489 493 self._connect()
490 494
491 495 while ((outgoing_headers or body)
492 496 and not (response and response.complete())):
493 497 select_timeout = self.timeout
494 498 out = outgoing_headers or body
495 499 blocking_on_continue = False
496 500 if expect_continue and not outgoing_headers and not (
497 501 response and (response.headers or response.continued)):
498 502 logger.info(
499 503 'waiting up to %s seconds for'
500 504 ' continue response from server',
501 505 self.continue_timeout)
502 506 select_timeout = self.continue_timeout
503 507 blocking_on_continue = True
504 508 out = False
505 509 if out:
506 510 w = [self.sock]
507 511 else:
508 512 w = []
509 513 r, w, x = select.select([self.sock], w, [], select_timeout)
510 514 # if we were expecting a 100 continue and it's been long
511 515 # enough, just go ahead and assume it's ok. This is the
512 516 # recommended behavior from the RFC.
513 517 if r == w == x == []:
514 518 if blocking_on_continue:
515 519 expect_continue = False
516 520 logger.info('no response to continue expectation from '
517 521 'server, optimistically sending request body')
518 522 else:
519 523 raise HTTPTimeoutException('timeout sending data')
520 524 was_first = first
521 525
522 526 # incoming data
523 527 if r:
524 528 try:
525 529 try:
526 530 data = r[0].recv(INCOMING_BUFFER_SIZE)
527 531 except socket.sslerror, e:
528 532 if e.args[0] != socket.SSL_ERROR_WANT_READ:
529 533 raise
530 534 logger.debug(
531 535 'SSL_WANT_READ while sending data, retrying...')
532 536 continue
533 537 if not data:
534 538 logger.info('socket appears closed in read')
535 539 self.sock = None
536 540 self._current_response = None
537 541 if response is not None:
538 542 response._close()
539 543 # This if/elif ladder is a bit subtle,
540 544 # comments in each branch should help.
541 545 if response is not None and response.complete():
542 546 # Server responded completely and then
543 547 # closed the socket. We should just shut
544 548 # things down and let the caller get their
545 549 # response.
546 550 logger.info('Got an early response, '
547 551 'aborting remaining request.')
548 552 break
549 553 elif was_first and response is None:
550 554 # Most likely a keepalive that got killed
551 555 # on the server's end. Commonly happens
552 556 # after getting a really large response
553 557 # from the server.
554 558 logger.info(
555 559 'Connection appeared closed in read on first'
556 560 ' request loop iteration, will retry.')
557 561 reconnect('read')
558 562 continue
559 563 else:
560 564 # We didn't just send the first data hunk,
561 565 # and either have a partial response or no
562 566 # response at all. There's really nothing
563 567 # meaningful we can do here.
564 568 raise HTTPStateError(
565 569 'Connection appears closed after '
566 570 'some request data was written, but the '
567 571 'response was missing or incomplete!')
568 572 logger.debug('read %d bytes in request()', len(data))
569 573 if response is None:
570 574 response = self.response_class(r[0], self.timeout, method)
571 575 response._load_response(data)
572 576 # Jump to the next select() call so we load more
573 577 # data if the server is still sending us content.
574 578 continue
575 579 except socket.error, e:
576 580 if e[0] != errno.EPIPE and not was_first:
577 581 raise
578 582
579 583 # outgoing data
580 584 if w and out:
581 585 try:
582 586 if getattr(out, 'read', False):
583 587 data = out.read(OUTGOING_BUFFER_SIZE)
584 588 if not data:
585 589 continue
586 590 if len(data) < OUTGOING_BUFFER_SIZE:
587 591 if chunked:
588 592 body = '0' + EOL + EOL
589 593 else:
590 594 body = None
591 595 if chunked:
592 596 out = hex(len(data))[2:] + EOL + data + EOL
593 597 else:
594 598 out = data
595 599 amt = w[0].send(out)
596 600 except socket.error, e:
597 601 if e[0] == socket.SSL_ERROR_WANT_WRITE and self.ssl:
598 602 # This means that SSL hasn't flushed its buffer into
599 603 # the socket yet.
600 604 # TODO: find a way to block on ssl flushing its buffer
601 605 # similar to selecting on a raw socket.
602 606 continue
603 607 elif (e[0] not in (errno.ECONNRESET, errno.EPIPE)
604 608 and not first):
605 609 raise
606 610 reconnect('write')
607 611 amt = self.sock.send(out)
608 612 logger.debug('sent %d', amt)
609 613 first = False
610 614 # stash data we think we sent in case the socket breaks
611 615 # when we read from it
612 616 if was_first:
613 617 sent_data = out[:amt]
614 618 if out is body:
615 619 body = out[amt:]
616 620 else:
617 621 outgoing_headers = out[amt:]
618 622
619 623 # close if the server response said to or responded before eating
620 624 # the whole request
621 625 if response is None:
622 626 response = self.response_class(self.sock, self.timeout, method)
623 627 complete = response.complete()
624 628 data_left = bool(outgoing_headers or body)
625 629 if data_left:
626 630 logger.info('stopped sending request early, '
627 631 'will close the socket to be safe.')
628 632 response.will_close = True
629 633 if response.will_close:
630 634 # The socket will be closed by the response, so we disown
631 635 # the socket
632 636 self.sock = None
633 637 self._current_response = response
634 638
635 639 def getresponse(self):
636 640 if self._current_response is None:
637 641 raise httplib.ResponseNotReady()
638 642 r = self._current_response
639 643 while r.headers is None:
640 644 if not r._select() and not r.complete():
641 645 raise _readers.HTTPRemoteClosedError()
642 646 if r.will_close:
643 647 self.sock = None
644 648 self._current_response = None
645 649 elif r.complete():
646 650 self._current_response = None
647 651 else:
648 652 self._current_response_taken = True
649 653 return r
650 654
651 655
652 656 class HTTPTimeoutException(httplib.HTTPException):
653 657 """A timeout occurred while waiting on the server."""
654 658
655 659
656 660 class BadRequestData(httplib.HTTPException):
657 661 """Request body object has neither __len__ nor read."""
658 662
659 663
660 664 class HTTPProxyConnectFailedException(httplib.HTTPException):
661 665 """Connecting to the HTTP proxy failed."""
662 666
663 667
664 668 class HTTPStateError(httplib.HTTPException):
665 669 """Invalid internal state encountered."""
666 670
667 671 # Forward this exception type from _readers since it needs to be part
668 672 # of the public API.
669 673 HTTPRemoteClosedError = _readers.HTTPRemoteClosedError
670 674 # no-check-code
@@ -1,195 +1,200 b''
1 1 # Copyright 2010, Google Inc.
2 2 # All rights reserved.
3 3 #
4 4 # Redistribution and use in source and binary forms, with or without
5 5 # modification, are permitted provided that the following conditions are
6 6 # met:
7 7 #
8 8 # * Redistributions of source code must retain the above copyright
9 9 # notice, this list of conditions and the following disclaimer.
10 10 # * Redistributions in binary form must reproduce the above
11 11 # copyright notice, this list of conditions and the following disclaimer
12 12 # in the documentation and/or other materials provided with the
13 13 # distribution.
14 14 # * Neither the name of Google Inc. nor the names of its
15 15 # contributors may be used to endorse or promote products derived from
16 16 # this software without specific prior written permission.
17 17
18 18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 29 import difflib
30 30 import socket
31 31
32 32 import httpplus
33 33
34 34
35 35 class MockSocket(object):
36 36 """Mock non-blocking socket object.
37 37
38 38 This is ONLY capable of mocking a nonblocking socket.
39 39
40 40 Attributes:
41 41 early_data: data to always send as soon as end of headers is seen
42 42 data: a list of strings to return on recv(), with the
43 43 assumption that the socket would block between each
44 44 string in the list.
45 45 read_wait_sentinel: data that must be written to the socket before
46 46 beginning the response.
47 47 close_on_empty: If true, close the socket when it runs out of data
48 48 for the client.
49 49 """
50 50 def __init__(self, af, socktype, proto):
51 51 self.af = af
52 52 self.socktype = socktype
53 53 self.proto = proto
54 54
55 55 self.early_data = []
56 56 self.data = []
57 57 self.remote_closed = self.closed = False
58 58 self.close_on_empty = False
59 59 self.sent = ''
60 60 self.read_wait_sentinel = httpplus._END_HEADERS
61 self.blocking = True
61 62
62 63 def close(self):
63 64 self.closed = True
64 65
65 66 def connect(self, sa):
66 67 self.sa = sa
67 68
68 69 def setblocking(self, timeout):
69 assert timeout == 0
70 self.blocking = bool(timeout)
70 71
71 72 def recv(self, amt=-1):
73 # we only properly emulate non-blocking sockets
74 assert not self.blocking
72 75 if self.early_data:
73 76 datalist = self.early_data
74 77 elif not self.data:
75 78 return ''
76 79 else:
77 80 datalist = self.data
78 81 if amt == -1:
79 82 return datalist.pop(0)
80 83 data = datalist.pop(0)
81 84 if len(data) > amt:
82 85 datalist.insert(0, data[amt:])
83 86 if not self.data and not self.early_data and self.close_on_empty:
84 87 self.remote_closed = True
85 88 return data[:amt]
86 89
87 90 @property
88 91 def ready_for_read(self):
89 92 return ((self.early_data and httpplus._END_HEADERS in self.sent)
90 93 or (self.read_wait_sentinel in self.sent and self.data)
91 94 or self.closed or self.remote_closed)
92 95
93 96 def send(self, data):
94 97 # this is a horrible mock, but nothing needs us to raise the
95 98 # correct exception yet
96 99 assert not self.closed, 'attempted to write to a closed socket'
97 100 assert not self.remote_closed, ('attempted to write to a'
98 101 ' socket closed by the server')
99 102 if len(data) > 8192:
100 103 data = data[:8192]
101 104 self.sent += data
102 105 return len(data)
103 106
104 107
105 108 def mockselect(r, w, x, timeout=0):
106 109 """Simple mock for select()
107 110 """
108 111 readable = filter(lambda s: s.ready_for_read, r)
109 112 return readable, w[:], []
110 113
111 114
112 115 class MockSSLSocket(object):
113 116 def __init__(self, sock):
114 117 self._sock = sock
115 118 self._fail_recv = True
116 119
117 120 def __getattr__(self, key):
118 121 return getattr(self._sock, key)
119 122
120 123 def __setattr__(self, key, value):
121 124 if key not in ('_sock', '_fail_recv'):
122 125 return setattr(self._sock, key, value)
123 126 return object.__setattr__(self, key, value)
124 127
125 128 def recv(self, amt=-1):
126 129 try:
127 130 if self._fail_recv:
128 131 raise socket.sslerror(socket.SSL_ERROR_WANT_READ)
129 132 return self._sock.recv(amt=amt)
130 133 finally:
131 134 self._fail_recv = not self._fail_recv
132 135
133 136
134 137 def mocksslwrap(sock, keyfile=None, certfile=None,
135 138 server_side=False, cert_reqs=httpplus.socketutil.CERT_NONE,
136 139 ssl_version=None, ca_certs=None,
137 140 do_handshake_on_connect=True,
138 141 suppress_ragged_eofs=True):
142 assert sock.blocking, ('wrapping a socket with ssl requires that '
143 'it be in blocking mode.')
139 144 return MockSSLSocket(sock)
140 145
141 146
142 147 def mockgetaddrinfo(host, port, unused, streamtype):
143 148 assert unused == 0
144 149 assert streamtype == socket.SOCK_STREAM
145 150 if host.count('.') != 3:
146 151 host = '127.0.0.42'
147 152 return [(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, '',
148 153 (host, port))]
149 154
150 155
151 156 class HttpTestBase(object):
152 157 def setUp(self):
153 158 self.orig_socket = socket.socket
154 159 socket.socket = MockSocket
155 160
156 161 self.orig_getaddrinfo = socket.getaddrinfo
157 162 socket.getaddrinfo = mockgetaddrinfo
158 163
159 164 self.orig_select = httpplus.select.select
160 165 httpplus.select.select = mockselect
161 166
162 167 self.orig_sslwrap = httpplus.socketutil.wrap_socket
163 168 httpplus.socketutil.wrap_socket = mocksslwrap
164 169
165 170 def tearDown(self):
166 171 socket.socket = self.orig_socket
167 172 httpplus.select.select = self.orig_select
168 173 httpplus.socketutil.wrap_socket = self.orig_sslwrap
169 174 socket.getaddrinfo = self.orig_getaddrinfo
170 175
171 176 def assertStringEqual(self, l, r):
172 177 try:
173 178 self.assertEqual(l, r, ('failed string equality check, '
174 179 'see stdout for details'))
175 180 except:
176 181 add_nl = lambda li: map(lambda x: x + '\n', li)
177 182 print 'failed expectation:'
178 183 print ''.join(difflib.unified_diff(
179 184 add_nl(l.splitlines()), add_nl(r.splitlines()),
180 185 fromfile='expected', tofile='got'))
181 186 raise
182 187
183 188 def doPost(self, con, expect_body, body_to_send='This is some POST data'):
184 189 con.request('POST', '/', body=body_to_send,
185 190 expect_continue=True)
186 191 expected_req = ('POST / HTTP/1.1\r\n'
187 192 'Host: 1.2.3.4\r\n'
188 193 'content-length: %d\r\n'
189 194 'Expect: 100-Continue\r\n'
190 195 'accept-encoding: identity\r\n\r\n' %
191 196 len(body_to_send))
192 197 if expect_body:
193 198 expected_req += body_to_send
194 199 return expected_req
195 200 # no-check-code
General Comments 0
You need to be logged in to leave comments. Login now