59 uname = os_module.uname() |
60 uname = os_module.uname() |
60 return "%s/%s" % (uname[0], uname[2]) |
61 return "%s/%s" % (uname[0], uname[2]) |
61 else: |
62 else: |
62 return "unknown" |
63 return "unknown" |
63 |
64 |
|
65 def HttpRequestToString(req, include_data=True): |
|
66 """Converts a urllib2.Request to a string. |
|
67 |
|
68 Args: |
|
69 req: urllib2.Request |
|
70 Returns: |
|
71 Multi-line string representing the request. |
|
72 """ |
|
73 |
|
74 headers = "" |
|
75 for header in req.header_items(): |
|
76 headers += "%s: %s\n" % (header[0], header[1]) |
|
77 |
|
78 template = ("%(method)s %(selector)s %(type)s/1.1\n" |
|
79 "Host: %(host)s\n" |
|
80 "%(headers)s") |
|
81 if include_data: |
|
82 template = template + "\n%(data)s" |
|
83 |
|
84 return template % { |
|
85 'method' : req.get_method(), |
|
86 'selector' : req.get_selector(), |
|
87 'type' : req.get_type().upper(), |
|
88 'host' : req.get_host(), |
|
89 'headers': headers, |
|
90 'data': req.get_data(), |
|
91 } |
64 |
92 |
65 class ClientLoginError(urllib2.HTTPError): |
93 class ClientLoginError(urllib2.HTTPError): |
66 """Raised to indicate there was an error authenticating with ClientLogin.""" |
94 """Raised to indicate there was an error authenticating with ClientLogin.""" |
67 |
95 |
68 def __init__(self, url, code, msg, headers, args): |
96 def __init__(self, url, code, msg, headers, args): |
69 urllib2.HTTPError.__init__(self, url, code, msg, headers, None) |
97 urllib2.HTTPError.__init__(self, url, code, msg, headers, None) |
70 self.args = args |
98 self.args = args |
71 self.reason = args["Error"] |
99 self.reason = args["Error"] |
72 |
100 |
|
101 def read(self): |
|
102 return '%d %s: %s' % (self.code, self.msg, self.reason) |
|
103 |
73 |
104 |
74 class AbstractRpcServer(object): |
105 class AbstractRpcServer(object): |
75 """Provides a common interface for a simple RPC server.""" |
106 """Provides a common interface for a simple RPC server.""" |
76 |
107 |
77 def __init__(self, host, auth_function, user_agent, source, |
108 def __init__(self, host, auth_function, user_agent, source, |
78 host_override=None, extra_headers=None, save_cookies=False, |
109 host_override=None, extra_headers=None, save_cookies=False, |
79 auth_tries=3, account_type=None): |
110 auth_tries=3, account_type=None, debug_data=True, secure=False): |
80 """Creates a new HttpRpcServer. |
111 """Creates a new HttpRpcServer. |
81 |
112 |
82 Args: |
113 Args: |
83 host: The host to send requests to. |
114 host: The host to send requests to. |
84 auth_function: A function that takes no arguments and returns an |
115 auth_function: A function that takes no arguments and returns an |
93 save_cookies: If True, save the authentication cookies to local disk. |
124 save_cookies: If True, save the authentication cookies to local disk. |
94 If False, use an in-memory cookiejar instead. Subclasses must |
125 If False, use an in-memory cookiejar instead. Subclasses must |
95 implement this functionality. Defaults to False. |
126 implement this functionality. Defaults to False. |
96 auth_tries: The number of times to attempt auth_function before failing. |
127 auth_tries: The number of times to attempt auth_function before failing. |
97 account_type: One of GOOGLE, HOSTED_OR_GOOGLE, or None for automatic. |
128 account_type: One of GOOGLE, HOSTED_OR_GOOGLE, or None for automatic. |
98 """ |
129 debug_data: Whether debugging output should include data contents. |
|
130 """ |
|
131 if secure: |
|
132 self.scheme = "https" |
|
133 else: |
|
134 self.scheme = "http" |
99 self.host = host |
135 self.host = host |
100 self.host_override = host_override |
136 self.host_override = host_override |
101 self.auth_function = auth_function |
137 self.auth_function = auth_function |
102 self.source = source |
138 self.source = source |
103 self.authenticated = False |
139 self.authenticated = False |
104 self.auth_tries = auth_tries |
140 self.auth_tries = auth_tries |
|
141 self.debug_data = debug_data |
105 |
142 |
106 self.account_type = account_type |
143 self.account_type = account_type |
107 |
144 |
108 self.extra_headers = {} |
145 self.extra_headers = {} |
109 if user_agent: |
146 if user_agent: |
113 |
150 |
114 self.save_cookies = save_cookies |
151 self.save_cookies = save_cookies |
115 self.cookie_jar = cookielib.MozillaCookieJar() |
152 self.cookie_jar = cookielib.MozillaCookieJar() |
116 self.opener = self._GetOpener() |
153 self.opener = self._GetOpener() |
117 if self.host_override: |
154 if self.host_override: |
118 logging.info("Server: %s; Host: %s", self.host, self.host_override) |
155 logger.info("Server: %s; Host: %s", self.host, self.host_override) |
119 else: |
156 else: |
120 logging.info("Server: %s", self.host) |
157 logger.info("Server: %s", self.host) |
121 |
158 |
122 if ((self.host_override and self.host_override == "localhost") or |
159 if ((self.host_override and self.host_override == "localhost") or |
123 self.host == "localhost" or self.host.startswith("localhost:")): |
160 self.host == "localhost" or self.host.startswith("localhost:")): |
124 self._DevAppServerAuthenticate() |
161 self._DevAppServerAuthenticate() |
125 |
162 |
198 HTTPError: If there was an error fetching the authentication cookies. |
235 HTTPError: If there was an error fetching the authentication cookies. |
199 """ |
236 """ |
200 continue_location = "http://localhost/" |
237 continue_location = "http://localhost/" |
201 args = {"continue": continue_location, "auth": auth_token} |
238 args = {"continue": continue_location, "auth": auth_token} |
202 login_path = os.environ.get("APPCFG_LOGIN_PATH", "/_ah") |
239 login_path = os.environ.get("APPCFG_LOGIN_PATH", "/_ah") |
203 req = self._CreateRequest("http://%s%s/login?%s" % |
240 req = self._CreateRequest("%s://%s%s/login?%s" % |
204 (self.host, login_path, urllib.urlencode(args))) |
241 (self.scheme, self.host, login_path, |
|
242 urllib.urlencode(args))) |
205 try: |
243 try: |
206 response = self.opener.open(req) |
244 response = self.opener.open(req) |
207 except urllib2.HTTPError, e: |
245 except urllib2.HTTPError, e: |
208 response = e |
246 response = e |
209 if (response.code != 302 or |
247 if (response.code != 302 or |
289 """ |
327 """ |
290 old_timeout = socket.getdefaulttimeout() |
328 old_timeout = socket.getdefaulttimeout() |
291 socket.setdefaulttimeout(timeout) |
329 socket.setdefaulttimeout(timeout) |
292 try: |
330 try: |
293 tries = 0 |
331 tries = 0 |
|
332 auth_tried = False |
294 while True: |
333 while True: |
295 tries += 1 |
334 tries += 1 |
296 args = dict(kwargs) |
335 args = dict(kwargs) |
297 url = "http://%s%s?%s" % (self.host, request_path, |
336 url = "%s://%s%s?%s" % (self.scheme, self.host, request_path, |
298 urllib.urlencode(args)) |
337 urllib.urlencode(args)) |
299 req = self._CreateRequest(url=url, data=payload) |
338 req = self._CreateRequest(url=url, data=payload) |
300 req.add_header("Content-Type", content_type) |
339 req.add_header("Content-Type", content_type) |
301 req.add_header("X-appcfg-api-version", "1") |
340 req.add_header("X-appcfg-api-version", "1") |
302 try: |
341 try: |
|
342 logger.debug('Sending HTTP request:\n%s' % |
|
343 HttpRequestToString(req, include_data=self.debug_data)) |
303 f = self.opener.open(req) |
344 f = self.opener.open(req) |
304 response = f.read() |
345 response = f.read() |
305 f.close() |
346 f.close() |
306 return response |
347 return response |
307 except urllib2.HTTPError, e: |
348 except urllib2.HTTPError, e: |
308 logging.debug("Got http error, this is try #%s" % tries) |
349 logger.debug("Got http error, this is try #%s" % tries) |
309 if tries > self.auth_tries: |
350 if tries > self.auth_tries: |
310 raise |
351 raise |
311 elif e.code == 401: |
352 elif e.code == 401: |
|
353 if auth_tried: |
|
354 raise |
|
355 auth_tried = True |
312 self._Authenticate() |
356 self._Authenticate() |
313 elif e.code >= 500 and e.code < 600: |
357 elif e.code >= 500 and e.code < 600: |
314 continue |
358 continue |
315 elif e.code == 302: |
359 elif e.code == 302: |
|
360 if auth_tried: |
|
361 raise |
|
362 auth_tried = True |
316 loc = e.info()["location"] |
363 loc = e.info()["location"] |
317 logging.debug("Got 302 redirect. Location: %s" % loc) |
364 logger.debug("Got 302 redirect. Location: %s" % loc) |
318 if loc.startswith("https://www.google.com/accounts/ServiceLogin"): |
365 if loc.startswith("https://www.google.com/accounts/ServiceLogin"): |
319 self._Authenticate() |
366 self._Authenticate() |
320 elif re.match(r"https://www.google.com/a/[a-z0-9.-]+/ServiceLogin", |
367 elif re.match(r"https://www.google.com/a/[a-z0-9.-]+/ServiceLogin", |
321 loc): |
368 loc): |
322 self.account_type = "HOSTED" |
369 self.account_type = "HOSTED" |
335 DEFAULT_COOKIE_FILE_PATH = "~/.appcfg_cookies" |
382 DEFAULT_COOKIE_FILE_PATH = "~/.appcfg_cookies" |
336 |
383 |
337 def _Authenticate(self): |
384 def _Authenticate(self): |
338 """Save the cookie jar after authentication.""" |
385 """Save the cookie jar after authentication.""" |
339 if cert_file_available and not uses_cert_verification: |
386 if cert_file_available and not uses_cert_verification: |
340 logging.warn("ssl module not found. Without this the identity of the " |
387 logger.warn("ssl module not found. Without this the identity of the " |
341 "remote host cannot be verified, and connections are NOT " |
388 "remote host cannot be verified, and connections are NOT " |
342 "secure. To fix this, please install the ssl module from " |
389 "secure. To fix this, please install the ssl module from " |
343 "http://pypi.python.org/pypi/ssl") |
390 "http://pypi.python.org/pypi/ssl") |
344 super(HttpRpcServer, self)._Authenticate() |
391 super(HttpRpcServer, self)._Authenticate() |
345 if self.cookie_jar.filename is not None and self.save_cookies: |
392 if self.cookie_jar.filename is not None and self.save_cookies: |
346 logging.info("Saving authentication cookies to %s" % |
393 logger.info("Saving authentication cookies to %s" % |
347 self.cookie_jar.filename) |
394 self.cookie_jar.filename) |
348 self.cookie_jar.save() |
395 self.cookie_jar.save() |
349 |
396 |
350 def _GetOpener(self): |
397 def _GetOpener(self): |
351 """Returns an OpenerDirector that supports cookies and ignores redirects. |
398 """Returns an OpenerDirector that supports cookies and ignores redirects. |
352 |
399 |
367 |
414 |
368 if os.path.exists(self.cookie_jar.filename): |
415 if os.path.exists(self.cookie_jar.filename): |
369 try: |
416 try: |
370 self.cookie_jar.load() |
417 self.cookie_jar.load() |
371 self.authenticated = True |
418 self.authenticated = True |
372 logging.info("Loaded authentication cookies from %s" % |
419 logger.info("Loaded authentication cookies from %s" % |
373 self.cookie_jar.filename) |
420 self.cookie_jar.filename) |
374 except (OSError, IOError, cookielib.LoadError), e: |
421 except (OSError, IOError, cookielib.LoadError), e: |
375 logging.debug("Could not load authentication cookies; %s: %s", |
422 logger.debug("Could not load authentication cookies; %s: %s", |
376 e.__class__.__name__, e) |
423 e.__class__.__name__, e) |
377 self.cookie_jar.filename = None |
424 self.cookie_jar.filename = None |
378 else: |
425 else: |
379 try: |
426 try: |
380 fd = os.open(self.cookie_jar.filename, os.O_CREAT, 0600) |
427 fd = os.open(self.cookie_jar.filename, os.O_CREAT, 0600) |
381 os.close(fd) |
428 os.close(fd) |
382 except (OSError, IOError), e: |
429 except (OSError, IOError), e: |
383 logging.debug("Could not create authentication cookies file; %s: %s", |
430 logger.debug("Could not create authentication cookies file; %s: %s", |
384 e.__class__.__name__, e) |
431 e.__class__.__name__, e) |
385 self.cookie_jar.filename = None |
432 self.cookie_jar.filename = None |
386 |
433 |
387 opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar)) |
434 opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar)) |
388 return opener |
435 return opener |