Permalink
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
Cannot retrieve contributors at this time.
Cannot retrieve contributors at this time
| from __future__ import absolute_import, division, print_function | |
| import mimetypes | |
| import uuid | |
| from io import BytesIO | |
| from twisted.internet.interfaces import IProtocol | |
| from twisted.internet.defer import Deferred | |
| from twisted.python.components import proxyForInterface | |
| from twisted.python.compat import _PY3, unicode | |
| from twisted.python.filepath import FilePath | |
| from twisted.python.url import URL | |
| from twisted.web.http import urlparse | |
| from twisted.web.http_headers import Headers | |
| from twisted.web.iweb import IBodyProducer, IResponse | |
| from twisted.web.client import ( | |
| FileBodyProducer, | |
| RedirectAgent, | |
| BrowserLikeRedirectAgent, | |
| ContentDecoderAgent, | |
| GzipDecoder, | |
| CookieAgent | |
| ) | |
| from twisted.python.components import registerAdapter | |
| from json import dumps as json_dumps | |
| from treq._utils import default_reactor | |
| from treq.auth import add_auth | |
| from treq import multipart | |
| from treq.response import _Response | |
| from requests.cookies import cookiejar_from_dict, merge_cookies | |
| if _PY3: | |
| from urllib.parse import urlunparse, urlencode as _urlencode | |
| def urlencode(query, doseq): | |
| return _urlencode(query, doseq).encode('ascii') | |
| from http.cookiejar import CookieJar | |
| else: | |
| from cookielib import CookieJar | |
| from urlparse import urlunparse | |
| from urllib import urlencode | |
| class _BodyBufferingProtocol(proxyForInterface(IProtocol)): | |
| def __init__(self, original, buffer, finished): | |
| self.original = original | |
| self.buffer = buffer | |
| self.finished = finished | |
| def dataReceived(self, data): | |
| self.buffer.append(data) | |
| self.original.dataReceived(data) | |
| def connectionLost(self, reason): | |
| self.original.connectionLost(reason) | |
| self.finished.errback(reason) | |
| class _BufferedResponse(proxyForInterface(IResponse)): | |
| def __init__(self, original): | |
| self.original = original | |
| self._buffer = [] | |
| self._waiters = [] | |
| self._waiting = None | |
| self._finished = False | |
| self._reason = None | |
| def _deliverWaiting(self, reason): | |
| self._reason = reason | |
| self._finished = True | |
| for waiter in self._waiters: | |
| for segment in self._buffer: | |
| waiter.dataReceived(segment) | |
| waiter.connectionLost(reason) | |
| def deliverBody(self, protocol): | |
| if self._waiting is None and not self._finished: | |
| self._waiting = Deferred() | |
| self._waiting.addBoth(self._deliverWaiting) | |
| self.original.deliverBody( | |
| _BodyBufferingProtocol( | |
| protocol, | |
| self._buffer, | |
| self._waiting | |
| ) | |
| ) | |
| elif self._finished: | |
| for segment in self._buffer: | |
| protocol.dataReceived(segment) | |
| protocol.connectionLost(self._reason) | |
| else: | |
| self._waiters.append(protocol) | |
| class HTTPClient(object): | |
| def __init__(self, agent, cookiejar=None, | |
| data_to_body_producer=IBodyProducer): | |
| self._agent = agent | |
| self._cookiejar = cookiejar or cookiejar_from_dict({}) | |
| self._data_to_body_producer = data_to_body_producer | |
| def get(self, url, **kwargs): | |
| """ | |
| See :func:`treq.get()`. | |
| """ | |
| return self.request('GET', url, **kwargs) | |
| def put(self, url, data=None, **kwargs): | |
| """ | |
| See :func:`treq.put()`. | |
| """ | |
| return self.request('PUT', url, data=data, **kwargs) | |
| def patch(self, url, data=None, **kwargs): | |
| """ | |
| See :func:`treq.patch()`. | |
| """ | |
| return self.request('PATCH', url, data=data, **kwargs) | |
| def post(self, url, data=None, **kwargs): | |
| """ | |
| See :func:`treq.post()`. | |
| """ | |
| return self.request('POST', url, data=data, **kwargs) | |
| def head(self, url, **kwargs): | |
| """ | |
| See :func:`treq.head()`. | |
| """ | |
| return self.request('HEAD', url, **kwargs) | |
| def delete(self, url, **kwargs): | |
| """ | |
| See :func:`treq.delete()`. | |
| """ | |
| return self.request('DELETE', url, **kwargs) | |
| def request(self, method, url, **kwargs): | |
| """ | |
| See :func:`treq.request()`. | |
| """ | |
| method = method.encode('ascii').upper() | |
| # Join parameters provided in the URL | |
| # and the ones passed as argument. | |
| params = kwargs.get('params') | |
| if params: | |
| url = _combine_query_params(url, params) | |
| if isinstance(url, unicode): | |
| url = URL.fromText(url).asURI().asText().encode('ascii') | |
| # Convert headers dictionary to | |
| # twisted raw headers format. | |
| headers = kwargs.get('headers') | |
| if headers: | |
| if isinstance(headers, dict): | |
| h = Headers({}) | |
| for k, v in headers.items(): | |
| if isinstance(v, (bytes, unicode)): | |
| h.addRawHeader(k, v) | |
| elif isinstance(v, list): | |
| h.setRawHeaders(k, v) | |
| headers = h | |
| else: | |
| headers = Headers({}) | |
| # Here we choose a right producer | |
| # based on the parameters passed in. | |
| bodyProducer = None | |
| data = kwargs.get('data') | |
| files = kwargs.get('files') | |
| # since json=None needs to be serialized as 'null', we need to | |
| # explicitly check kwargs for this key | |
| has_json = 'json' in kwargs | |
| if files: | |
| # If the files keyword is present we will issue a | |
| # multipart/form-data request as it suits better for cases | |
| # with files and/or large objects. | |
| files = list(_convert_files(files)) | |
| boundary = str(uuid.uuid4()).encode('ascii') | |
| headers.setRawHeaders( | |
| b'content-type', [ | |
| b'multipart/form-data; boundary=' + boundary]) | |
| if data: | |
| data = _convert_params(data) | |
| else: | |
| data = [] | |
| bodyProducer = multipart.MultiPartProducer( | |
| data + files, boundary=boundary) | |
| elif data: | |
| # Otherwise stick to x-www-form-urlencoded format | |
| # as it's generally faster for smaller requests. | |
| if isinstance(data, (dict, list, tuple)): | |
| headers.setRawHeaders( | |
| b'content-type', [b'application/x-www-form-urlencoded']) | |
| data = urlencode(data, doseq=True) | |
| bodyProducer = self._data_to_body_producer(data) | |
| elif has_json: | |
| # If data is sent as json, set Content-Type as 'application/json' | |
| headers.setRawHeaders( | |
| b'content-type', [b'application/json; charset=UTF-8']) | |
| content = kwargs['json'] | |
| json = json_dumps(content, separators=(u',', u':')).encode('utf-8') | |
| bodyProducer = self._data_to_body_producer(json) | |
| cookies = kwargs.get('cookies', {}) | |
| if not isinstance(cookies, CookieJar): | |
| cookies = cookiejar_from_dict(cookies) | |
| cookies = merge_cookies(self._cookiejar, cookies) | |
| wrapped_agent = CookieAgent(self._agent, cookies) | |
| if kwargs.get('allow_redirects', True): | |
| if kwargs.get('browser_like_redirects', False): | |
| wrapped_agent = BrowserLikeRedirectAgent(wrapped_agent) | |
| else: | |
| wrapped_agent = RedirectAgent(wrapped_agent) | |
| wrapped_agent = ContentDecoderAgent(wrapped_agent, | |
| [(b'gzip', GzipDecoder)]) | |
| auth = kwargs.get('auth') | |
| if auth: | |
| wrapped_agent = add_auth(wrapped_agent, auth) | |
| d = wrapped_agent.request( | |
| method, url, headers=headers, | |
| bodyProducer=bodyProducer) | |
| timeout = kwargs.get('timeout') | |
| if timeout: | |
| delayedCall = default_reactor(kwargs.get('reactor')).callLater( | |
| timeout, d.cancel) | |
| def gotResult(result): | |
| if delayedCall.active(): | |
| delayedCall.cancel() | |
| return result | |
| d.addBoth(gotResult) | |
| if not kwargs.get('unbuffered', False): | |
| d.addCallback(_BufferedResponse) | |
| return d.addCallback(_Response, cookies) | |
| def _convert_params(params): | |
| if hasattr(params, "iteritems"): | |
| return list(sorted(params.iteritems())) | |
| elif hasattr(params, "items"): | |
| return list(sorted(params.items())) | |
| elif isinstance(params, (tuple, list)): | |
| return list(params) | |
| else: | |
| raise ValueError("Unsupported format") | |
| def _convert_files(files): | |
| """Files can be passed in a variety of formats: | |
| * {'file': open("bla.f")} | |
| * {'file': (name, open("bla.f"))} | |
| * {'file': (name, content-type, open("bla.f"))} | |
| * Anything that has iteritems method, e.g. MultiDict: | |
| MultiDict([(name, open()), (name, open())] | |
| Our goal is to standardize it to unified form of: | |
| * [(param, (file name, content type, producer))] | |
| """ | |
| if hasattr(files, "iteritems"): | |
| files = files.iteritems() | |
| elif hasattr(files, "items"): | |
| files = files.items() | |
| for param, val in files: | |
| file_name, content_type, fobj = (None, None, None) | |
| if isinstance(val, tuple): | |
| if len(val) == 2: | |
| file_name, fobj = val | |
| elif len(val) == 3: | |
| file_name, content_type, fobj = val | |
| else: | |
| fobj = val | |
| if hasattr(fobj, "name"): | |
| file_name = FilePath(fobj.name).basename() | |
| if not content_type: | |
| content_type = _guess_content_type(file_name) | |
| yield (param, (file_name, content_type, IBodyProducer(fobj))) | |
| def _combine_query_params(url, params): | |
| parsed_url = urlparse(url.encode('ascii')) | |
| qs = [] | |
| if parsed_url.query: | |
| qs.extend([parsed_url.query, b'&']) | |
| qs.append(urlencode(params, doseq=True)) | |
| return urlunparse((parsed_url[0], parsed_url[1], | |
| parsed_url[2], parsed_url[3], | |
| b''.join(qs), parsed_url[5])) | |
| def _from_bytes(orig_bytes): | |
| return FileBodyProducer(BytesIO(orig_bytes)) | |
| def _from_file(orig_file): | |
| return FileBodyProducer(orig_file) | |
| def _guess_content_type(filename): | |
| if filename: | |
| guessed = mimetypes.guess_type(filename)[0] | |
| else: | |
| guessed = None | |
| return guessed or 'application/octet-stream' | |
| registerAdapter(_from_bytes, bytes, IBodyProducer) | |
| registerAdapter(_from_file, BytesIO, IBodyProducer) | |
| if not _PY3: | |
| from StringIO import StringIO | |
| registerAdapter(_from_file, StringIO, IBodyProducer) | |
| registerAdapter(_from_file, file, IBodyProducer) | |
| else: | |
| import io | |
| # file()/open() equiv on Py3 | |
| registerAdapter(_from_file, io.BufferedReader, IBodyProducer) |