blob: 29513778a3d8fc7d57f6d0c90e56c669787dfddc [file] [log] [blame]
"""Library to make sso_client wrapped HTTP requests to skylab service."""
from apiclient import discovery
import httplib2
import logging
import re
import subprocess
CODE_REGEX = '\d\d\d'
LINE_SEP = '\r\n' # The line separator used in http response.
DEFAULT_FETCH_DEADLINE_SEC = 60
class BadHttpResponseException(Exception):
"""Raise when fail to parse the HTTP response."""
pass
class BadHttpRequestException(Exception):
"""Raise when the sso_client based http request failed."""
pass
def retry_if_bad_request(exception):
"""Return True if we should retry, False otherwise"""
return isinstance(exception, BadHttpRequestException)
def sso_request(url, method='', body='', headers={}, max_redirects=None):
"""Create sso_client wrapped http request.
@param url: String of the URL to request.
@param method: The HTTP method to get use, e.g. GET or DELETE. Use POST if
there is data, GET otherwise. Default is "". If not specified,
GET is the default method for sso_client command.
@param body: The data used by POST/UPDATE method. String, default is "".
@param headers: Dict of the request headers, default is {}.
@param max_redirects: String format of integer representing the maximum
redirects. If not specified, sso_client uses 10 as
default.
@returns: The return value is a tuple of (response, content), the first
being and instance of the 'Response' class, the second being
a string that contains the response entity body.
"""
try:
cmd = ['sso_client', '--url', url, '-dump_header']
if method:
cmd.extend(['-method', method])
if body:
cmd.extend(['-data', body])
if headers:
# Remove the accept-encoding header to disable encoding in order to
# receive the raw text.
headers.pop('accept-encoding', None)
headers_str = ['%s:%s' % (k, v) for k, v in headers.iteritems()]
headers_str = ';'.join(headers_str)
if headers_str:
cmd.extend(['-headers', headers_str])
if max_redirects:
cmd.extend(['-max_redirects', max_redirects])
logging.debug('Sending SSO request: %s', cmd)
result = subprocess.check_output(cmd)
except subprocess.CalledProcessError as e:
error_msg = ('Fail to make the sso_client request to %s.\nError:\n%s' %
(url, e.output))
raise BadHttpRequestException(error_msg)
result = result.strip()
if result.find(LINE_SEP+LINE_SEP) == -1:
(headers, body) = (result, '')
else:
[headers, body] = [s.strip() for s in result.split(LINE_SEP+LINE_SEP)]
status = re.search(CODE_REGEX, headers.split(LINE_SEP)[0])
if not status:
raise BadHttpResponseException(
'Fail to parse the status return code from the HTTP response:\n%s' %
result)
status = status.group(0)
info = {'status': status, 'body': body, 'headers': headers}
return (httplib2.Response(info), body)
def _new_http(include_sso=True):
"""Creates httplib2 client that adds SSO cookie."""
http = httplib2.Http(timeout=DEFAULT_FETCH_DEADLINE_SEC)
if include_sso:
http.request = sso_request
return http
def build_service(service_name,
version,
discovery_service_url=discovery.DISCOVERY_URI,
include_sso=True):
"""Construct a service wrapped with SSO credentials as required."""
logging.debug('Requesting discovery service for url: %s',
discovery_service_url)
return discovery.build(
service_name,
version,
http=_new_http(include_sso),
discoveryServiceUrl=discovery_service_url)