mirror of https://github.com/ansible/ansible.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
4.8 KiB
Python
182 lines
4.8 KiB
Python
8 years ago
|
"""
|
||
|
Primitive replacement for requests to avoid extra dependency.
|
||
|
Avoids use of urllib2 due to lack of SNI support.
|
||
|
"""
|
||
5 years ago
|
from __future__ import (absolute_import, division, print_function)
|
||
|
__metaclass__ = type
|
||
8 years ago
|
|
||
|
import json
|
||
7 years ago
|
import time
|
||
8 years ago
|
|
||
|
try:
|
||
|
from urllib import urlencode
|
||
|
except ImportError:
|
||
8 years ago
|
# noinspection PyCompatibility, PyUnresolvedReferences
|
||
8 years ago
|
from urllib.parse import urlencode # pylint: disable=locally-disabled, import-error, no-name-in-module
|
||
|
|
||
8 years ago
|
try:
|
||
|
# noinspection PyCompatibility
|
||
7 years ago
|
from urlparse import urlparse, urlunparse, parse_qs
|
||
8 years ago
|
except ImportError:
|
||
|
# noinspection PyCompatibility, PyUnresolvedReferences
|
||
7 years ago
|
from urllib.parse import urlparse, urlunparse, parse_qs # pylint: disable=locally-disabled, ungrouped-imports
|
||
8 years ago
|
|
||
5 years ago
|
from .util import (
|
||
8 years ago
|
ApplicationError,
|
||
7 years ago
|
SubprocessError,
|
||
|
display,
|
||
8 years ago
|
)
|
||
|
|
||
5 years ago
|
from .util_common import (
|
||
5 years ago
|
CommonConfig,
|
||
|
run_command,
|
||
|
)
|
||
|
|
||
8 years ago
|
|
||
5 years ago
|
class HttpClient:
|
||
8 years ago
|
"""Make HTTP requests via curl."""
|
||
6 years ago
|
def __init__(self, args, always=False, insecure=False, proxy=None):
|
||
8 years ago
|
"""
|
||
|
:type args: CommonConfig
|
||
|
:type always: bool
|
||
7 years ago
|
:type insecure: bool
|
||
8 years ago
|
"""
|
||
|
self.args = args
|
||
|
self.always = always
|
||
7 years ago
|
self.insecure = insecure
|
||
6 years ago
|
self.proxy = proxy
|
||
8 years ago
|
|
||
7 years ago
|
self.username = None
|
||
|
self.password = None
|
||
|
|
||
8 years ago
|
def get(self, url):
|
||
|
"""
|
||
|
:type url: str
|
||
|
:rtype: HttpResponse
|
||
|
"""
|
||
|
return self.request('GET', url)
|
||
|
|
||
|
def delete(self, url):
|
||
|
"""
|
||
|
:type url: str
|
||
|
:rtype: HttpResponse
|
||
|
"""
|
||
|
return self.request('DELETE', url)
|
||
|
|
||
|
def put(self, url, data=None, headers=None):
|
||
|
"""
|
||
|
:type url: str
|
||
|
:type data: str | None
|
||
|
:type headers: dict[str, str] | None
|
||
|
:rtype: HttpResponse
|
||
|
"""
|
||
|
return self.request('PUT', url, data, headers)
|
||
|
|
||
|
def request(self, method, url, data=None, headers=None):
|
||
|
"""
|
||
|
:type method: str
|
||
|
:type url: str
|
||
|
:type data: str | None
|
||
|
:type headers: dict[str, str] | None
|
||
|
:rtype: HttpResponse
|
||
|
"""
|
||
|
cmd = ['curl', '-s', '-S', '-i', '-X', method]
|
||
|
|
||
7 years ago
|
if self.insecure:
|
||
|
cmd += ['--insecure']
|
||
|
|
||
8 years ago
|
if headers is None:
|
||
|
headers = {}
|
||
|
|
||
|
headers['Expect'] = '' # don't send expect continue header
|
||
|
|
||
7 years ago
|
if self.username:
|
||
|
if self.password:
|
||
|
display.sensitive.add(self.password)
|
||
|
cmd += ['-u', '%s:%s' % (self.username, self.password)]
|
||
|
else:
|
||
|
cmd += ['-u', self.username]
|
||
|
|
||
8 years ago
|
for header in headers.keys():
|
||
|
cmd += ['-H', '%s: %s' % (header, headers[header])]
|
||
|
|
||
|
if data is not None:
|
||
|
cmd += ['-d', data]
|
||
|
|
||
6 years ago
|
if self.proxy:
|
||
|
cmd += ['-x', self.proxy]
|
||
|
|
||
8 years ago
|
cmd += [url]
|
||
|
|
||
7 years ago
|
attempts = 0
|
||
|
max_attempts = 3
|
||
|
sleep_seconds = 3
|
||
|
|
||
|
# curl error codes which are safe to retry (request never sent to server)
|
||
|
retry_on_status = (
|
||
|
6, # CURLE_COULDNT_RESOLVE_HOST
|
||
|
)
|
||
|
|
||
5 years ago
|
stdout = ''
|
||
|
|
||
7 years ago
|
while True:
|
||
|
attempts += 1
|
||
|
|
||
|
try:
|
||
5 years ago
|
stdout = run_command(self.args, cmd, capture=True, always=self.always, cmd_verbosity=2)[0]
|
||
7 years ago
|
break
|
||
|
except SubprocessError as ex:
|
||
|
if ex.status in retry_on_status and attempts < max_attempts:
|
||
|
display.warning(u'%s' % ex)
|
||
|
time.sleep(sleep_seconds)
|
||
|
continue
|
||
|
|
||
|
raise
|
||
8 years ago
|
|
||
|
if self.args.explain and not self.always:
|
||
7 years ago
|
return HttpResponse(method, url, 200, '')
|
||
8 years ago
|
|
||
|
header, body = stdout.split('\r\n\r\n', 1)
|
||
|
|
||
|
response_headers = header.split('\r\n')
|
||
|
first_line = response_headers[0]
|
||
|
http_response = first_line.split(' ')
|
||
|
status_code = int(http_response[1])
|
||
|
|
||
7 years ago
|
return HttpResponse(method, url, status_code, body)
|
||
8 years ago
|
|
||
|
|
||
5 years ago
|
class HttpResponse:
|
||
8 years ago
|
"""HTTP response from curl."""
|
||
7 years ago
|
def __init__(self, method, url, status_code, response):
|
||
8 years ago
|
"""
|
||
7 years ago
|
:type method: str
|
||
|
:type url: str
|
||
8 years ago
|
:type status_code: int
|
||
|
:type response: str
|
||
|
"""
|
||
7 years ago
|
self.method = method
|
||
|
self.url = url
|
||
8 years ago
|
self.status_code = status_code
|
||
|
self.response = response
|
||
|
|
||
|
def json(self):
|
||
|
"""
|
||
|
:rtype: any
|
||
|
"""
|
||
|
try:
|
||
|
return json.loads(self.response)
|
||
|
except ValueError:
|
||
7 years ago
|
raise HttpError(self.status_code, 'Cannot parse response to %s %s as JSON:\n%s' % (self.method, self.url, self.response))
|
||
8 years ago
|
|
||
|
|
||
|
class HttpError(ApplicationError):
|
||
|
"""HTTP response as an error."""
|
||
|
def __init__(self, status, message):
|
||
|
"""
|
||
|
:type status: int
|
||
|
:type message: str
|
||
|
"""
|
||
|
super(HttpError, self).__init__('%s: %s' % (status, message))
|
||
|
self.status = status
|