def prepare_discovery_request(self, response_body): self._response = test_utils.MockConnectionResponse(200, response_body) discovery = discovery_service.DiscoveryService( self.api_config_manager) discovery._discovery_proxy = self.mox.CreateMock( discovery_api_proxy.DiscoveryApiProxy) return discovery
def test_generate_discovery_doc_rest_unknown_api(self): request = test_utils.build_request('/_ah/api/foo', '{"api": "blah", "version": "v1"}') discovery_api = discovery_service.DiscoveryService( self.api_config_manager) discovery_api.handle_discovery_request( discovery_service.DiscoveryService._GET_REST_API, request, self.start_response) self.assertEquals(self.response_status, '404')
def call_spi(self, orig_request, start_response): """Generate SPI call (from earlier-saved request). This calls start_response and returns the response body. Args: orig_request: An ApiRequest, the original request from the user. start_response: A function with semantics defined in PEP-333. Returns: A string containing the response body. """ if orig_request.is_rpc(): method_config = self.lookup_rpc_method(orig_request) params = None else: method_config, params = self.lookup_rest_method(orig_request) if not method_config: cors_handler = EndpointsDispatcher.__CheckCorsHeaders(orig_request) return util.send_wsgi_not_found_response(start_response, cors_handler=cors_handler) # Prepare the request for the back end. spi_request = self.transform_request(orig_request, params, method_config) # Check if this SPI call is for the Discovery service. If so, route # it to our Discovery handler. discovery = discovery_service.DiscoveryService(self.config_manager) discovery_response = discovery.handle_discovery_request( spi_request.path, spi_request, start_response) if discovery_response: return discovery_response # Send the request to the user's SPI handlers. url = _SPI_ROOT_FORMAT % spi_request.path spi_request.headers['Content-Type'] = 'application/json' response = self._dispatcher.add_request('POST', url, spi_request.headers.items(), spi_request.body, spi_request.source_ip) return self.handle_spi_response(orig_request, spi_request, response, start_response)
class EndpointsDispatcher(object): """Dispatcher that handles requests to the built-in apiserver handlers.""" _API_EXPLORER_URL = 'https://developers.google.com/apis-explorer/?base=' def __init__(self, dispatcher, config_manager=None): """Constructor for EndpointsDispatcher. Args: dispatcher: A Dispatcher instance that can be used to make HTTP requests. config_manager: An ApiConfigManager instance that allows a caller to set up an existing configuration for testing. """ self._dispatcher = dispatcher if config_manager is None: config_manager = api_config_manager.ApiConfigManager() self.config_manager = config_manager self._dispatchers = [] self._add_dispatcher('/_ah/api/explorer/?$', self.handle_api_explorer_request) self._add_dispatcher('/_ah/api/static/.*$', self.handle_api_static_request) def _add_dispatcher(self, path_regex, dispatch_function): """Add a request path and dispatch handler. Args: path_regex: A string regex, the path to match against incoming requests. dispatch_function: The function to call for these requests. The function should take (request, start_response) as arguments and return the contents of the response body. """ self._dispatchers.append((re.compile(path_regex), dispatch_function)) def __call__(self, environ, start_response): """Handle an incoming request. Args: environ: An environ dict for the request as defined in PEP-333. start_response: A function used to begin the response to the caller. This follows the semantics defined in PEP-333. In particular, it's called with (status, response_headers, exc_info=None), and it returns an object with a write(body_data) function that can be used to write the body of the response. Yields: An iterable over strings containing the body of the HTTP response. """ request = api_request.ApiRequest(environ) # PEP-333 requires that we return an iterator that iterates over the # response body. Yielding the returned body accomplishes this. yield self.dispatch(request, start_response) def dispatch(self, request, start_response): """Handles dispatch to apiserver handlers. This typically ends up calling start_response and returning the entire body of the response. Args: request: An ApiRequest, the request from the user. start_response: A function with semantics defined in PEP-333. Returns: A string, the body of the response. """ # Check if this matches any of our special handlers. dispatched_response = self.dispatch_non_api_requests(request, start_response) if dispatched_response is not None: return dispatched_response # Get API configuration first. We need this so we know how to # call the back end. api_config_response = self.get_api_configs() if not self.handle_get_api_configs_response(api_config_response): return self.fail_request(request, 'BackendService.getApiConfigs Error', start_response) # Call the service. return self.call_spi(request, start_response) def dispatch_non_api_requests(self, request, start_response): """Dispatch this request if this is a request to a reserved URL. If the request matches one of our reserved URLs, this calls start_response and returns the response body. Args: request: An ApiRequest, the request from the user. start_response: A function with semantics defined in PEP-333. Returns: None if the request doesn't match one of the reserved URLs this handles. Otherwise, returns the response body. """ for path_regex, dispatch_function in self._dispatchers: if path_regex.match(request.relative_url): return dispatch_function(request, start_response) return None def handle_api_explorer_request(self, request, start_response): """Handler for requests to _ah/api/explorer. This calls start_response and returns the response body. Args: request: An ApiRequest, the request from the user. start_response: A function with semantics defined in PEP-333. Returns: A string containing the response body (which is empty, in this case). """ base_url = 'http://%s:%s/_ah/api' % (request.server, request.port) redirect_url = self._API_EXPLORER_URL + base_url return util.send_wsgi_redirect_response(redirect_url, start_response) def handle_api_static_request(self, request, start_response): """Handler for requests to _ah/api/static/.*. This calls start_response and returns the response body. Args: request: An ApiRequest, the request from the user. start_response: A function with semantics defined in PEP-333. Returns: A string containing the response body. """ discovery_api = discovery_api_proxy.DiscoveryApiProxy() response, body = discovery_api.get_static_file(request.relative_url) status_string = '%d %s' % (response.status, response.reason) if response.status == 200: # Some of the headers that come back from the server can't be passed # along in our response. Specifically, the response from the server has # transfer-encoding: chunked, which doesn't apply to the response that # we're forwarding. There may be other problematic headers, so we strip # off everything but Content-Type. return util.send_wsgi_response(status_string, [('Content-Type', response.getheader('Content-Type'))], body, start_response) else: logging.error('Discovery API proxy failed on %s with %d. Details: %s', request.relative_url, response.status, body) return util.send_wsgi_response(status_string, response.getheaders(), body, start_response) def get_api_configs(self): """Makes a call to the BackendService.getApiConfigs endpoint. Returns: A ResponseTuple containing the response information from the HTTP request. """ headers = [('Content-Type', 'application/json')] request_body = '{}' response = self._dispatcher.add_request( 'POST', '/_ah/spi/BackendService.getApiConfigs', headers, request_body, _SERVER_SOURCE_IP) return response @staticmethod def verify_response(response, status_code, content_type=None): """Verifies that a response has the expected status and content type. Args: response: The ResponseTuple to be checked. status_code: An int, the HTTP status code to be compared with response status. content_type: A string with the acceptable Content-Type header value. None allows any content type. Returns: True if both status_code and content_type match, else False. """ status = int(response.status.split(' ', 1)[0]) if status != status_code: return False if content_type is None: return True for header, value in response.headers: if header.lower() == 'content-type': return value == content_type else: return False def handle_get_api_configs_response(self, api_config_response): """Parses the result of GetApiConfigs and stores its information. Args: api_config_response: The ResponseTuple from the GetApiConfigs call. Returns: True on success, False on failure """ if self.verify_response(api_config_response, 200, 'application/json'): self.config_manager.parse_api_config_response( api_config_response.content) return True else: return False def call_spi(self, orig_request, start_response): """Generate SPI call (from earlier-saved request). This calls start_response and returns the response body. Args: orig_request: An ApiRequest, the original request from the user. start_response: A function with semantics defined in PEP-333. Returns: A string containing the response body. """ if orig_request.is_rpc(): method_config = self.lookup_rpc_method(orig_request) params = None else: method_config, params = self.lookup_rest_method(orig_request) if not method_config: cors_handler = EndpointsDispatcher.__CheckCorsHeaders(orig_request) return util.send_wsgi_not_found_response(start_response, cors_handler=cors_handler) # Prepare the request for the back end. try: spi_request = self.transform_request(orig_request, params, method_config) except errors.RequestRejectionError, rejection_error: cors_handler = EndpointsDispatcher.__CheckCorsHeaders(orig_request) return util.send_wsgi_rejected_response(rejection_error, start_response, cors_handler=cors_handler) # Check if this SPI call is for the Discovery service. If so, route # it to our Discovery handler. discovery = discovery_service.DiscoveryService(self.config_manager) discovery_response = discovery.handle_discovery_request( spi_request.path, spi_request, start_response) if discovery_response: return discovery_response # Send the request to the user's SPI handlers. url = _SPI_ROOT_FORMAT % spi_request.path spi_request.headers['Content-Type'] = 'application/json' response = self._dispatcher.add_request('POST', url, spi_request.headers.items(), spi_request.body, spi_request.source_ip) return self.handle_spi_response(orig_request, spi_request, response, start_response)