| Index: third_party/google-endpoints/endpoints/util.py
|
| diff --git a/third_party/google-endpoints/endpoints/util.py b/third_party/google-endpoints/endpoints/util.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..bd1e0ba4cf450d06cfd038e2e9f1c8013f1cbe16
|
| --- /dev/null
|
| +++ b/third_party/google-endpoints/endpoints/util.py
|
| @@ -0,0 +1,272 @@
|
| +# Copyright 2016 Google Inc. All Rights Reserved.
|
| +#
|
| +# Licensed under the Apache License, Version 2.0 (the "License");
|
| +# you may not use this file except in compliance with the License.
|
| +# You may obtain a copy of the License at
|
| +#
|
| +# http://www.apache.org/licenses/LICENSE-2.0
|
| +#
|
| +# Unless required by applicable law or agreed to in writing, software
|
| +# distributed under the License is distributed on an "AS IS" BASIS,
|
| +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| +# See the License for the specific language governing permissions and
|
| +# limitations under the License.
|
| +
|
| +"""Helper utilities for the endpoints package."""
|
| +
|
| +# pylint: disable=g-bad-name
|
| +
|
| +import cStringIO
|
| +import json
|
| +import os
|
| +import wsgiref.headers
|
| +
|
| +from google.appengine.api import app_identity
|
| +
|
| +from google.appengine.api.modules import modules
|
| +
|
| +
|
| +class StartResponseProxy(object):
|
| + """Proxy for the typical WSGI start_response object."""
|
| +
|
| + def __init__(self):
|
| + self.call_context = {}
|
| + self.body_buffer = cStringIO.StringIO()
|
| +
|
| + def __enter__(self):
|
| + return self
|
| +
|
| + def __exit__(self, exc_type, exc_value, traceback):
|
| + # Close out the cStringIO.StringIO buffer to prevent memory leakage.
|
| + if self.body_buffer:
|
| + self.body_buffer.close()
|
| +
|
| + def Proxy(self, status, headers, exc_info=None):
|
| + """Save args, defer start_response until response body is parsed.
|
| +
|
| + Create output buffer for body to be written into.
|
| + Note: this is not quite WSGI compliant: The body should come back as an
|
| + iterator returned from calling service_app() but instead, StartResponse
|
| + returns a writer that will be later called to output the body.
|
| + See google/appengine/ext/webapp/__init__.py::Response.wsgi_write()
|
| + write = start_response('%d %s' % self.__status, self.__wsgi_headers)
|
| + write(body)
|
| +
|
| + Args:
|
| + status: Http status to be sent with this response
|
| + headers: Http headers to be sent with this response
|
| + exc_info: Exception info to be displayed for this response
|
| + Returns:
|
| + callable that takes as an argument the body content
|
| + """
|
| + self.call_context['status'] = status
|
| + self.call_context['headers'] = headers
|
| + self.call_context['exc_info'] = exc_info
|
| +
|
| + return self.body_buffer.write
|
| +
|
| + @property
|
| + def response_body(self):
|
| + return self.body_buffer.getvalue()
|
| +
|
| + @property
|
| + def response_headers(self):
|
| + return self.call_context.get('headers')
|
| +
|
| + @property
|
| + def response_status(self):
|
| + return self.call_context.get('status')
|
| +
|
| + @property
|
| + def response_exc_info(self):
|
| + return self.call_context.get('exc_info')
|
| +
|
| +
|
| +def send_wsgi_not_found_response(start_response, cors_handler=None):
|
| + return send_wsgi_response('404 Not Found', [('Content-Type', 'text/plain')],
|
| + 'Not Found', start_response,
|
| + cors_handler=cors_handler)
|
| +
|
| +
|
| +def send_wsgi_error_response(message, start_response, cors_handler=None):
|
| + body = json.dumps({'error': {'message': message}})
|
| + return send_wsgi_response('500', [('Content-Type', 'application/json')], body,
|
| + start_response, cors_handler=cors_handler)
|
| +
|
| +
|
| +def send_wsgi_rejected_response(rejection_error, start_response,
|
| + cors_handler=None):
|
| + body = rejection_error.to_json()
|
| + return send_wsgi_response('400', [('Content-Type', 'application/json')], body,
|
| + start_response, cors_handler=cors_handler)
|
| +
|
| +
|
| +def send_wsgi_redirect_response(redirect_location, start_response,
|
| + cors_handler=None):
|
| + return send_wsgi_response('302', [('Location', redirect_location)], '',
|
| + start_response, cors_handler=cors_handler)
|
| +
|
| +
|
| +def send_wsgi_no_content_response(start_response, cors_handler=None):
|
| + return send_wsgi_response('204 No Content', [], '', start_response,
|
| + cors_handler)
|
| +
|
| +
|
| +def send_wsgi_response(status, headers, content, start_response,
|
| + cors_handler=None):
|
| + """Dump reformatted response to CGI start_response.
|
| +
|
| + This calls start_response and returns the response body.
|
| +
|
| + Args:
|
| + status: A string containing the HTTP status code to send.
|
| + headers: A list of (header, value) tuples, the headers to send in the
|
| + response.
|
| + content: A string containing the body content to write.
|
| + start_response: A function with semantics defined in PEP-333.
|
| + cors_handler: A handler to process CORS request headers and update the
|
| + headers in the response. Or this can be None, to bypass CORS checks.
|
| +
|
| + Returns:
|
| + A string containing the response body.
|
| + """
|
| + if cors_handler:
|
| + cors_handler.update_headers(headers)
|
| +
|
| + # Update content length.
|
| + content_len = len(content) if content else 0
|
| + headers = [(header, value) for header, value in headers
|
| + if header.lower() != 'content-length']
|
| + headers.append(('Content-Length', '%s' % content_len))
|
| +
|
| + start_response(status, headers)
|
| + return content
|
| +
|
| +
|
| +def get_headers_from_environ(environ):
|
| + """Get a wsgiref.headers.Headers object with headers from the environment.
|
| +
|
| + Headers in environ are prefixed with 'HTTP_', are all uppercase, and have
|
| + had dashes replaced with underscores. This strips the HTTP_ prefix and
|
| + changes underscores back to dashes before adding them to the returned set
|
| + of headers.
|
| +
|
| + Args:
|
| + environ: An environ dict for the request as defined in PEP-333.
|
| +
|
| + Returns:
|
| + A wsgiref.headers.Headers object that's been filled in with any HTTP
|
| + headers found in environ.
|
| + """
|
| + headers = wsgiref.headers.Headers([])
|
| + for header, value in environ.iteritems():
|
| + if header.startswith('HTTP_'):
|
| + headers[header[5:].replace('_', '-')] = value
|
| + # Content-Type is special; it does not start with 'HTTP_'.
|
| + if 'CONTENT_TYPE' in environ:
|
| + headers['CONTENT-TYPE'] = environ['CONTENT_TYPE']
|
| + return headers
|
| +
|
| +
|
| +def put_headers_in_environ(headers, environ):
|
| + """Given a list of headers, put them into environ based on PEP-333.
|
| +
|
| + This converts headers to uppercase, prefixes them with 'HTTP_', and
|
| + converts dashes to underscores before adding them to the environ dict.
|
| +
|
| + Args:
|
| + headers: A list of (header, value) tuples. The HTTP headers to add to the
|
| + environment.
|
| + environ: An environ dict for the request as defined in PEP-333.
|
| + """
|
| + for key, value in headers:
|
| + environ['HTTP_%s' % key.upper().replace('-', '_')] = value
|
| +
|
| +
|
| +def is_running_on_app_engine():
|
| + return os.environ.get('GAE_MODULE_NAME') is not None
|
| +
|
| +
|
| +def is_running_on_devserver():
|
| + return os.environ.get('SERVER_SOFTWARE', '').startswith('Development/')
|
| +
|
| +
|
| +def is_running_on_localhost():
|
| + return os.environ.get('SERVER_NAME') == 'localhost'
|
| +
|
| +
|
| +def get_app_hostname():
|
| + """Return hostname of a running Endpoints service.
|
| +
|
| + Returns hostname of an running Endpoints API. It can be 1) "localhost:PORT"
|
| + if running on development server, or 2) "app_id.appspot.com" if running on
|
| + external app engine prod, or "app_id.googleplex.com" if running as Google
|
| + first-party Endpoints API, or 4) None if not running on App Engine
|
| + (e.g. Tornado Endpoints API).
|
| +
|
| + Returns:
|
| + A string representing the hostname of the service.
|
| + """
|
| + if not is_running_on_app_engine() or is_running_on_localhost():
|
| + return None
|
| +
|
| + version = modules.get_current_version_name()
|
| + app_id = app_identity.get_application_id()
|
| +
|
| + suffix = 'appspot.com'
|
| +
|
| + if ':' in app_id:
|
| + tokens = app_id.split(':')
|
| + api_name = tokens[1]
|
| + if tokens[0] == 'google.com':
|
| + suffix = 'googleplex.com'
|
| + else:
|
| + api_name = app_id
|
| +
|
| + # Check if this is the default version
|
| + default_version = modules.get_default_version()
|
| + if version == default_version:
|
| + return '{0}.{1}'.format(app_id, suffix)
|
| + else:
|
| + return '{0}-dot-{1}.{2}'.format(version, api_name, suffix)
|
| +
|
| +
|
| +def check_list_type(objects, allowed_type, name, allow_none=True):
|
| + """Verify that objects in list are of the allowed type or raise TypeError.
|
| +
|
| + Args:
|
| + objects: The list of objects to check.
|
| + allowed_type: The allowed type of items in 'settings'.
|
| + name: Name of the list of objects, added to the exception.
|
| + allow_none: If set, None is also allowed.
|
| +
|
| + Raises:
|
| + TypeError: if object is not of the allowed type.
|
| +
|
| + Returns:
|
| + The list of objects, for convenient use in assignment.
|
| + """
|
| + if objects is None:
|
| + if not allow_none:
|
| + raise TypeError('%s is None, which is not allowed.' % name)
|
| + return objects
|
| + if not isinstance(objects, (tuple, list)):
|
| + raise TypeError('%s is not a list.' % name)
|
| + if not all(isinstance(i, allowed_type) for i in objects):
|
| + type_list = sorted(list(set(type(obj) for obj in objects)))
|
| + raise TypeError('%s contains types that don\'t match %s: %s' %
|
| + (name, allowed_type.__name__, type_list))
|
| + return objects
|
| +
|
| +
|
| +def snake_case_to_headless_camel_case(snake_string):
|
| + """Convert snake_case to headlessCamelCase.
|
| +
|
| + Args:
|
| + snake_string: The string to be converted.
|
| + Returns:
|
| + The input string converted to headlessCamelCase.
|
| + """
|
| + return ''.join([snake_string.split('_')[0]] +
|
| + list(sub_string.capitalize()
|
| + for sub_string in snake_string.split('_')[1:]))
|
|
|