mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-01-21 10:22:58 -08:00
210 lines
8.0 KiB
Python
210 lines
8.0 KiB
Python
# MIT licensed code copied from https://bitbucket.org/chrisatlee/poster
|
|
"""Streaming HTTP uploads module.
|
|
|
|
This module extends the standard httplib and urllib2 objects so that
|
|
iterable objects can be used in the body of HTTP requests.
|
|
|
|
In most cases all one should have to do is call :func:`register_openers()`
|
|
to register the new streaming http handlers which will take priority over
|
|
the default handlers, and then you can use iterable objects in the body
|
|
of HTTP requests.
|
|
|
|
**N.B.** You must specify a Content-Length header if using an iterable object
|
|
since there is no way to determine in advance the total size that will be
|
|
yielded, and there is no way to reset an interator.
|
|
|
|
Example usage:
|
|
|
|
>>> from StringIO import StringIO
|
|
>>> import urllib2, poster.streaminghttp
|
|
|
|
>>> opener = poster.streaminghttp.register_openers()
|
|
|
|
>>> s = "Test file data"
|
|
>>> f = StringIO(s)
|
|
|
|
>>> req = urllib2.Request("http://localhost:5000", f,
|
|
... {'Content-Length': str(len(s))})
|
|
"""
|
|
|
|
import socket
|
|
import sys
|
|
|
|
from cloudinary.compat import NotConnected, httplib, urllib2
|
|
|
|
__all__ = ['StreamingHTTPConnection', 'StreamingHTTPRedirectHandler',
|
|
'StreamingHTTPHandler', 'register_openers']
|
|
|
|
if hasattr(httplib, 'HTTPS'):
|
|
__all__.extend(['StreamingHTTPSHandler', 'StreamingHTTPSConnection'])
|
|
|
|
|
|
class _StreamingHTTPMixin:
|
|
"""Mixin class for HTTP and HTTPS connections that implements a streaming
|
|
send method."""
|
|
def send(self, value):
|
|
"""Send ``value`` to the server.
|
|
|
|
``value`` can be a string object, a file-like object that supports
|
|
a .read() method, or an iterable object that supports a .next()
|
|
method.
|
|
"""
|
|
# Based on python 2.6's httplib.HTTPConnection.send()
|
|
if self.sock is None:
|
|
if self.auto_open:
|
|
self.connect()
|
|
else:
|
|
raise NotConnected()
|
|
|
|
# send the data to the server. if we get a broken pipe, then close
|
|
# the socket. we want to reconnect when somebody tries to send again.
|
|
#
|
|
# NOTE: we DO propagate the error, though, because we cannot simply
|
|
# ignore the error... the caller will know if they can retry.
|
|
if self.debuglevel > 0:
|
|
print("send:", repr(value))
|
|
try:
|
|
blocksize = 8192
|
|
if hasattr(value, 'read'):
|
|
if hasattr(value, 'seek'):
|
|
value.seek(0)
|
|
if self.debuglevel > 0:
|
|
print("sendIng a read()able")
|
|
data = value.read(blocksize)
|
|
while data:
|
|
self.sock.sendall(data)
|
|
data = value.read(blocksize)
|
|
elif hasattr(value, 'next'):
|
|
if hasattr(value, 'reset'):
|
|
value.reset()
|
|
if self.debuglevel > 0:
|
|
print("sendIng an iterable")
|
|
for data in value:
|
|
self.sock.sendall(data)
|
|
else:
|
|
self.sock.sendall(value)
|
|
except socket.error:
|
|
e = sys.exc_info()[1]
|
|
if e[0] == 32: # Broken pipe
|
|
self.close()
|
|
raise
|
|
|
|
|
|
class StreamingHTTPConnection(_StreamingHTTPMixin, httplib.HTTPConnection):
|
|
"""Subclass of `httplib.HTTPConnection` that overrides the `send()` method
|
|
to support iterable body objects"""
|
|
|
|
|
|
class StreamingHTTPRedirectHandler(urllib2.HTTPRedirectHandler):
|
|
"""Subclass of `urllib2.HTTPRedirectHandler` that overrides the
|
|
`redirect_request` method to properly handle redirected POST requests
|
|
|
|
This class is required because python 2.5's HTTPRedirectHandler does
|
|
not remove the Content-Type or Content-Length headers when requesting
|
|
the new resource, but the body of the original request is not preserved.
|
|
"""
|
|
|
|
handler_order = urllib2.HTTPRedirectHandler.handler_order - 1
|
|
|
|
# From python2.6 urllib2's HTTPRedirectHandler
|
|
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
|
"""Return a Request or None in response to a redirect.
|
|
|
|
This is called by the http_error_30x methods when a
|
|
redirection response is received. If a redirection should
|
|
take place, return a new Request to allow http_error_30x to
|
|
perform the redirect. Otherwise, raise HTTPError if no-one
|
|
else should try to handle this url. Return None if you can't
|
|
but another Handler might.
|
|
"""
|
|
m = req.get_method()
|
|
if (code in (301, 302, 303, 307) and m in ("GET", "HEAD")
|
|
or code in (301, 302, 303) and m == "POST"):
|
|
# Strictly (according to RFC 2616), 301 or 302 in response
|
|
# to a POST MUST NOT cause a redirection without confirmation
|
|
# from the user (of urllib2, in this case). In practice,
|
|
# essentially all clients do redirect in this case, so we
|
|
# do the same.
|
|
# be conciliant with URIs containing a space
|
|
newurl = newurl.replace(' ', '%20')
|
|
newheaders = dict((k, v) for k, v in req.headers.items()
|
|
if k.lower() not in (
|
|
"content-length", "content-type")
|
|
)
|
|
return urllib2.Request(
|
|
newurl,
|
|
headers=newheaders,
|
|
origin_req_host=req.get_origin_req_host(),
|
|
unverifiable=True)
|
|
else:
|
|
raise urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp)
|
|
|
|
|
|
class StreamingHTTPHandler(urllib2.HTTPHandler):
|
|
"""Subclass of `urllib2.HTTPHandler` that uses
|
|
StreamingHTTPConnection as its http connection class."""
|
|
|
|
handler_order = urllib2.HTTPHandler.handler_order - 1
|
|
|
|
def http_open(self, req):
|
|
"""Open a StreamingHTTPConnection for the given request"""
|
|
return self.do_open(StreamingHTTPConnection, req)
|
|
|
|
def http_request(self, req):
|
|
"""Handle a HTTP request. Make sure that Content-Length is specified
|
|
if we're using an interable value"""
|
|
# Make sure that if we're using an iterable object as the request
|
|
# body, that we've also specified Content-Length
|
|
if req.has_data():
|
|
data = req.get_data()
|
|
if hasattr(data, 'read') or hasattr(data, 'next'):
|
|
if not req.has_header('Content-length'):
|
|
raise ValueError(
|
|
"No Content-Length specified for iterable body")
|
|
return urllib2.HTTPHandler.do_request_(self, req)
|
|
|
|
|
|
if hasattr(httplib, 'HTTPS'):
|
|
class StreamingHTTPSConnection(_StreamingHTTPMixin, httplib.HTTPSConnection):
|
|
"""Subclass of `httplib.HTTSConnection` that overrides the `send()`
|
|
method to support iterable body objects"""
|
|
|
|
class StreamingHTTPSHandler(urllib2.HTTPSHandler):
|
|
"""Subclass of `urllib2.HTTPSHandler` that uses
|
|
StreamingHTTPSConnection as its http connection class."""
|
|
|
|
handler_order = urllib2.HTTPSHandler.handler_order - 1
|
|
|
|
def https_open(self, req):
|
|
return self.do_open(StreamingHTTPSConnection, req)
|
|
|
|
def https_request(self, req):
|
|
# Make sure that if we're using an iterable object as the request
|
|
# body, that we've also specified Content-Length
|
|
if req.has_data():
|
|
data = req.get_data()
|
|
if hasattr(data, 'read') or hasattr(data, 'next'):
|
|
if not req.has_header('Content-length'):
|
|
raise ValueError(
|
|
"No Content-Length specified for iterable body")
|
|
return urllib2.HTTPSHandler.do_request_(self, req)
|
|
|
|
|
|
def get_handlers():
|
|
handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler]
|
|
if hasattr(httplib, "HTTPS"):
|
|
handlers.append(StreamingHTTPSHandler)
|
|
return handlers
|
|
|
|
|
|
def register_openers():
|
|
"""Register the streaming http handlers in the global urllib2 default
|
|
opener object.
|
|
|
|
Returns the created OpenerDirector object."""
|
|
opener = urllib2.build_opener(*get_handlers())
|
|
|
|
urllib2.install_opener(opener)
|
|
|
|
return opener
|