• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Library to make sso_client wrapped HTTP requests to skylab service."""
2
3from apiclient import discovery
4import httplib2
5import logging
6import re
7import subprocess
8
9
10CODE_REGEX = '\d\d\d'
11LINE_SEP = '\r\n' # The line separator used in http response.
12DEFAULT_FETCH_DEADLINE_SEC = 60
13
14
15class BadHttpResponseException(Exception):
16  """Raise when fail to parse the HTTP response."""
17  pass
18
19
20class BadHttpRequestException(Exception):
21  """Raise when the sso_client based http request failed."""
22  pass
23
24
25def retry_if_bad_request(exception):
26  """Return True if we should retry, False otherwise"""
27  return isinstance(exception, BadHttpRequestException)
28
29def sso_request(url, method='', body='', headers={}, max_redirects=None):
30  """Create sso_client wrapped http request.
31
32  @param url: String of the URL to request.
33  @param method: The HTTP method to get use, e.g. GET or DELETE. Use POST if
34                 there is data, GET otherwise. Default is "". If not specified,
35                 GET is the default method for sso_client command.
36  @param body: The data used by POST/UPDATE method. String, default is "".
37  @param headers: Dict of the request headers, default is {}.
38  @param max_redirects: String format of integer representing the maximum
39                        redirects. If not specified, sso_client uses 10 as
40                        default.
41
42  @returns: The return value is a tuple of (response, content), the first
43            being and instance of the 'Response' class, the second being
44            a string that contains the response entity body.
45  """
46  try:
47    cmd = ['sso_client', '--url', url, '-dump_header']
48    if method:
49      cmd.extend(['-method', method])
50    if body:
51      cmd.extend(['-data', body])
52    if headers:
53      # Remove the accept-encoding header to disable encoding in order to
54      # receive the raw text.
55      headers.pop('accept-encoding', None)
56      headers_str = ['%s:%s' % (k, v) for k, v in headers.iteritems()]
57      headers_str = ';'.join(headers_str)
58      if headers_str:
59        cmd.extend(['-headers', headers_str])
60    if max_redirects:
61      cmd.extend(['-max_redirects', max_redirects])
62
63    logging.debug('Sending SSO request: %s', cmd)
64    result = subprocess.check_output(cmd)
65  except subprocess.CalledProcessError as e:
66    error_msg = ('Fail to make the sso_client request to %s.\nError:\n%s' %
67                 (url, e.output))
68    raise BadHttpRequestException(error_msg)
69
70  result = result.strip()
71  if result.find(LINE_SEP+LINE_SEP) == -1:
72    (headers, body) = (result, '')
73  else:
74    [headers, body] = [s.strip() for s in result.split(LINE_SEP+LINE_SEP)]
75  status = re.search(CODE_REGEX, headers.split(LINE_SEP)[0])
76  if not status:
77    raise BadHttpResponseException(
78        'Fail to parse the status return code from the HTTP response:\n%s' %
79        result)
80  status = status.group(0)
81  info = {'status': status, 'body': body, 'headers': headers}
82  return (httplib2.Response(info), body)
83
84
85def _new_http(include_sso=True):
86  """Creates httplib2 client that adds SSO cookie."""
87  http = httplib2.Http(timeout=DEFAULT_FETCH_DEADLINE_SEC)
88  if include_sso:
89    http.request = sso_request
90
91  return http
92
93
94def build_service(service_name,
95                  version,
96                  discovery_service_url=discovery.DISCOVERY_URI,
97                  include_sso=True):
98  """Construct a service wrapped with SSO credentials as required."""
99  logging.debug('Requesting discovery service for url: %s',
100               discovery_service_url)
101  return discovery.build(
102      service_name,
103      version,
104      http=_new_http(include_sso),
105      discoveryServiceUrl=discovery_service_url)
106
107