streaminghttp.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. """Streaming HTTP uploads module.
  2. This module extends the standard httplib and urllib2 objects so that
  3. iterable objects can be used in the body of HTTP requests.
  4. In most cases all one should have to do is call :func:`register_openers()`
  5. to register the new streaming http handlers which will take priority over
  6. the default handlers, and then you can use iterable objects in the body
  7. of HTTP requests.
  8. **N.B.** You must specify a Content-Length header if using an iterable object
  9. since there is no way to determine in advance the total size that will be
  10. yielded, and there is no way to reset an interator.
  11. Example usage:
  12. >>> from StringIO import StringIO
  13. >>> import urllib2, poster.streaminghttp
  14. >>> opener = poster.streaminghttp.register_openers()
  15. >>> s = "Test file data"
  16. >>> f = StringIO(s)
  17. >>> req = urllib2.Request("http://localhost:5000", f,
  18. ... {'Content-Length': str(len(s))})
  19. """
  20. import httplib, urllib2, socket
  21. from httplib import NotConnected
  22. __all__ = ['StreamingHTTPConnection', 'StreamingHTTPRedirectHandler',
  23. 'StreamingHTTPHandler', 'register_openers']
  24. if hasattr(httplib, 'HTTPS'):
  25. __all__.extend(['StreamingHTTPSHandler', 'StreamingHTTPSConnection'])
  26. class _StreamingHTTPMixin:
  27. """Mixin class for HTTP and HTTPS connections that implements a streaming
  28. send method."""
  29. def send(self, value):
  30. """Send ``value`` to the server.
  31. ``value`` can be a string object, a file-like object that supports
  32. a .read() method, or an iterable object that supports a .next()
  33. method.
  34. """
  35. # Based on python 2.6's httplib.HTTPConnection.send()
  36. if self.sock is None:
  37. if self.auto_open:
  38. self.connect()
  39. else:
  40. raise NotConnected()
  41. # send the data to the server. if we get a broken pipe, then close
  42. # the socket. we want to reconnect when somebody tries to send again.
  43. #
  44. # NOTE: we DO propagate the error, though, because we cannot simply
  45. # ignore the error... the caller will know if they can retry.
  46. if self.debuglevel > 0:
  47. print "send:", repr(value)
  48. try:
  49. blocksize = 8192
  50. if hasattr(value, 'read') :
  51. if hasattr(value, 'seek'):
  52. value.seek(0)
  53. if self.debuglevel > 0:
  54. print "sendIng a read()able"
  55. data = value.read(blocksize)
  56. while data:
  57. self.sock.sendall(data)
  58. data = value.read(blocksize)
  59. elif hasattr(value, 'next'):
  60. if hasattr(value, 'reset'):
  61. value.reset()
  62. if self.debuglevel > 0:
  63. print "sendIng an iterable"
  64. for data in value:
  65. self.sock.sendall(data)
  66. else:
  67. self.sock.sendall(value)
  68. except socket.error, v:
  69. if v[0] == 32: # Broken pipe
  70. self.close()
  71. raise
  72. class StreamingHTTPConnection(_StreamingHTTPMixin, httplib.HTTPConnection):
  73. """Subclass of `httplib.HTTPConnection` that overrides the `send()` method
  74. to support iterable body objects"""
  75. class StreamingHTTPRedirectHandler(urllib2.HTTPRedirectHandler):
  76. """Subclass of `urllib2.HTTPRedirectHandler` that overrides the
  77. `redirect_request` method to properly handle redirected POST requests
  78. This class is required because python 2.5's HTTPRedirectHandler does
  79. not remove the Content-Type or Content-Length headers when requesting
  80. the new resource, but the body of the original request is not preserved.
  81. """
  82. handler_order = urllib2.HTTPRedirectHandler.handler_order - 1
  83. # From python2.6 urllib2's HTTPRedirectHandler
  84. def redirect_request(self, req, fp, code, msg, headers, newurl):
  85. """Return a Request or None in response to a redirect.
  86. This is called by the http_error_30x methods when a
  87. redirection response is received. If a redirection should
  88. take place, return a new Request to allow http_error_30x to
  89. perform the redirect. Otherwise, raise HTTPError if no-one
  90. else should try to handle this url. Return None if you can't
  91. but another Handler might.
  92. """
  93. m = req.get_method()
  94. if (code in (301, 302, 303, 307) and m in ("GET", "HEAD")
  95. or code in (301, 302, 303) and m == "POST"):
  96. # Strictly (according to RFC 2616), 301 or 302 in response
  97. # to a POST MUST NOT cause a redirection without confirmation
  98. # from the user (of urllib2, in this case). In practice,
  99. # essentially all clients do redirect in this case, so we
  100. # do the same.
  101. # be conciliant with URIs containing a space
  102. newurl = newurl.replace(' ', '%20')
  103. newheaders = dict((k, v) for k, v in req.headers.items()
  104. if k.lower() not in (
  105. "content-length", "content-type")
  106. )
  107. return urllib2.Request(newurl,
  108. headers=newheaders,
  109. origin_req_host=req.get_origin_req_host(),
  110. unverifiable=True)
  111. else:
  112. raise urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp)
  113. class StreamingHTTPHandler(urllib2.HTTPHandler):
  114. """Subclass of `urllib2.HTTPHandler` that uses
  115. StreamingHTTPConnection as its http connection class."""
  116. handler_order = urllib2.HTTPHandler.handler_order - 1
  117. def http_open(self, req):
  118. """Open a StreamingHTTPConnection for the given request"""
  119. return self.do_open(StreamingHTTPConnection, req)
  120. def http_request(self, req):
  121. """Handle a HTTP request. Make sure that Content-Length is specified
  122. if we're using an interable value"""
  123. # Make sure that if we're using an iterable object as the request
  124. # body, that we've also specified Content-Length
  125. if req.has_data():
  126. data = req.get_data()
  127. if hasattr(data, 'read') or hasattr(data, 'next'):
  128. if not req.has_header('Content-length'):
  129. raise ValueError(
  130. "No Content-Length specified for iterable body")
  131. return urllib2.HTTPHandler.do_request_(self, req)
  132. if hasattr(httplib, 'HTTPS'):
  133. class StreamingHTTPSConnection(_StreamingHTTPMixin,
  134. httplib.HTTPSConnection):
  135. """Subclass of `httplib.HTTSConnection` that overrides the `send()`
  136. method to support iterable body objects"""
  137. class StreamingHTTPSHandler(urllib2.HTTPSHandler):
  138. """Subclass of `urllib2.HTTPSHandler` that uses
  139. StreamingHTTPSConnection as its http connection class."""
  140. handler_order = urllib2.HTTPSHandler.handler_order - 1
  141. def https_open(self, req):
  142. return self.do_open(StreamingHTTPSConnection, req)
  143. def https_request(self, req):
  144. # Make sure that if we're using an iterable object as the request
  145. # body, that we've also specified Content-Length
  146. if req.has_data():
  147. data = req.get_data()
  148. if hasattr(data, 'read') or hasattr(data, 'next'):
  149. if not req.has_header('Content-length'):
  150. raise ValueError(
  151. "No Content-Length specified for iterable body")
  152. return urllib2.HTTPSHandler.do_request_(self, req)
  153. def get_handlers():
  154. handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler]
  155. if hasattr(httplib, "HTTPS"):
  156. handlers.append(StreamingHTTPSHandler)
  157. return handlers
  158. def register_openers():
  159. """Register the streaming http handlers in the global urllib2 default
  160. opener object.
  161. Returns the created OpenerDirector object."""
  162. opener = urllib2.build_opener(*get_handlers())
  163. urllib2.install_opener(opener)
  164. return opener