def CreateApiserverDispatcher(config_manager=None):
  """Function to create Apiserver dispatcher.

  Args:
    config_manager: Allow setting of ApiConfigManager for testing.

  Returns:
    New dispatcher capable of handling requests to the built-in apiserver
    handlers.
  """



  from google.appengine.tools import dev_appserver

  class ApiserverDispatcher(dev_appserver.URLDispatcher):
    """Dispatcher that handles requests to the built-in apiserver handlers."""

    _API_EXPLORER_URL = 'https://developers.google.com/apis-explorer/?base='

    class RequestState(object):
      """Enum tracking request state."""
      INIT = 0
      GET_API_CONFIGS = 1
      SPI_CALL = 2
      END = 3



    def __init__(self, config_manager=None, *args, **kwargs):
      self._is_rpc = None
      self.request = None
      self._request_stage = self.RequestState.INIT
      self._is_batch = False
      if config_manager is None:
        config_manager = ApiConfigManager()
      self.config_manager = config_manager
      self._dispatchers = []
      self._AddDispatcher('/_ah/api/explorer/?$',
                          self.HandleApiExplorerRequest)
      self._AddDispatcher('/_ah/api/static/.*$',
                          self.HandleApiStaticRequest)
      dev_appserver.URLDispatcher.__init__(self, *args, **kwargs)

    def _AddDispatcher(self, path_regex, dispatch_function):
      """Add a request path and dispatch handler.

      Args:
        path_regex: Regex path to match against incoming requests.
        dispatch_function: Function to call for these requests.  The function
          should take (request, outfile, base_env_dict) as arguments and
          return True.
      """
      self._dispatchers.append((re.compile(path_regex), dispatch_function))

    def _EndRequest(self):
      """End the request and clean up.

      Sets the request state to END and cleans up any variables that
      need it.
      """
      self._request_stage = self.RequestState.END
      self._is_batch = False

    def IsRpc(self):
      """Check if the current request is an RPC request.

      This should only be used after Dispatch, where this info is cached.

      Returns:
        True if the current request is an RPC.  False if not.
      """
      assert self._is_rpc is not None
      return self._is_rpc

    def DispatchNonApiRequests(self, request, outfile, base_env_dict):
      """Dispatch this request if this is a request to a reserved URL.

      Args:
        request: AppServerRequest.
        outfile: The response file.
        base_env_dict: Dictionary of CGI environment parameters if available.
          Defaults to None.

      Returns:
        False if the request doesn't match one of the reserved URLs this
        handles.  True if it is handled.
      """
      for path_regex, dispatch_function in self._dispatchers:
        if path_regex.match(request.relative_url):
          return dispatch_function(request, outfile, base_env_dict)
      return False

    def Dispatch(self,
                 request,
                 outfile,
                 base_env_dict=None):
      """Handles dispatch to apiserver handlers.

      base_env_dict should contain at least:
      REQUEST_METHOD, REMOTE_ADDR, SERVER_SOFTWARE, SERVER_NAME,
      SERVER_PROTOCOL, SERVER_PORT

      Args:
        request: AppServerRequest.
        outfile: The response file.
        base_env_dict: Dictionary of CGI environment parameters if available.
          Defaults to None.

      Returns:
        AppServerRequest internal redirect for normal API calls or
        None for error conditions (e.g. method not found -> 404) and
        other calls not requiring the GetApiConfigs redirect.
      """
      if self._request_stage != self.RequestState.INIT:
        return self.FailRequest('Dispatch in unexpected state', outfile)

      if not base_env_dict:
        return self.FailRequest('CGI Environment Not Available', outfile)

      if self.DispatchNonApiRequests(request, outfile, base_env_dict):
        return None


      self.request = ApiRequest(base_env_dict, dev_appserver, request)





      self._is_rpc = self.request._IsRpc()


      self._request_stage = self.RequestState.GET_API_CONFIGS
      return self.GetApiConfigs(base_env_dict, dev_appserver)

    def HandleApiExplorerRequest(self, unused_request, outfile, base_env_dict):
      """Handler for requests to _ah/api/explorer.

      Args:
        unused_request: AppServerRequest.
        outfile: The response file.
        base_env_dict: Dictionary of CGI environment parameters
          if available. Defaults to None.

      Returns:
        True
        We will redirect these requests to the google apis explorer.
      """
      base_url = 'http://%s:%s/_ah/api' % (base_env_dict['SERVER_NAME'],
                                           base_env_dict['SERVER_PORT'])
      redirect_url = self._API_EXPLORER_URL + base_url
      SendCGIRedirectResponse(redirect_url, outfile)
      return True

    def HandleApiStaticRequest(self, request, outfile, unused_base_env_dict):
      """Handler for requests to _ah/api/static/.*.

      Args:
        request: AppServerRequest.
        outfile: The response file.
        unused_base_env_dict: Dictionary of CGI environment parameters
          if available. Defaults to None.

      Returns:
        True
        We will redirect these requests to an endpoint proxy.
      """
      discovery_api_proxy = DiscoveryApiProxy()
      response, body = discovery_api_proxy.GetStaticFile(request.relative_url)
      if response.status == 200:
        SendCGIResponse('200',
                        {'Content-Type': response.getheader('Content-Type')},
                        body, outfile)
      else:
        logging.error('Discovery API proxy failed on %s with %d. Details: %s',
                      request.relative_url, response.status, body)
        SendCGIResponse(response.status, dict(response.getheaders()), body,
                        outfile)
      return True

    def EndRedirect(self, dispatched_output, outfile):
      """Handle the end of getApiConfigs and SPI complete notification.

      This EndRedirect is called twice.

      The first time is upon completion of the BackendService.getApiConfigs()
      call.  After this call, the set of all available methods and their
      parameters / paths / config is contained in dispatched_output.  This is
      parsed and used to dispatch the request to the SPI backend itself.

      In order to cause a second dispatch and EndRedirect, this EndRedirect
      will return an AppServerRequest filled out with the SPI backend request.

      The second time it is called is upon completion of the call to the SPI
      backend. After this call, if the initial request (sent in Dispatch, prior
      to getApiConfigs) is used to reformat the response as needed.  This
      currently only results in changes for JsonRPC requests, where the response
      body is moved into {'result': response_body_goes_here} and the request id
      is copied back into the response.

      Args:
        dispatched_output: resulting output from the SPI
        outfile: final output file for this handler

      Returns:
        An AppServerRequest for redirect or None for an immediate response.
      """
      if self._request_stage == self.RequestState.GET_API_CONFIGS:
        if self.HandleGetApiConfigsResponse(dispatched_output, outfile):
          return self.CallSpi(outfile)
      elif self._request_stage == self.RequestState.SPI_CALL:
        return self.HandleSpiResponse(dispatched_output, outfile)
      else:
        return self.FailRequest('EndRedirect in unexpected state', outfile)

    def GetApiConfigs(self, cgi_env, dev_appserver):
      """Makes a call to BackendService.getApiConfigs and parses result.

      Args:
        cgi_env: CGI environment dictionary as passed in by the framework
        dev_appserver: dev_appserver instance used to generate AppServerRequest.

      Returns:
        AppServerRequest to be returned as an internal redirect to getApiConfigs
      """
      request = ApiRequest(cgi_env, dev_appserver)
      request.path = 'BackendService.getApiConfigs'
      request.body = '{}'
      return BuildCGIRequest(cgi_env, request, dev_appserver)

    @staticmethod
    def VerifyResponse(response, status_code, content_type=None):
      """Verifies that a response has the expected status and content type.

      Args:
        response: Response to be checked.
        status_code: HTTP status code to be compared with response status.
        content_type: acceptable Content-Type: header value, None allows any.

      Returns:
        True if both status_code and content_type match, else False.
      """
      if response.status_code != status_code:
        return False
      if content_type is None:
        return True
      for header in response.headers:
        if header.lower() == 'content-type':
          return response.headers[header].lower() == content_type
      else:
        return False

    @staticmethod
    def ParseCgiResponse(response):
      """Parses a CGI response, returning a headers dict and body.

      Args:
        response: a CGI response

      Returns:
        tuple of ({header: header_value, ...}, body)
      """
      header_dict = {}
      for header in response.headers.headers:
        header_name, header_value = header.split(':', 1)
        header_dict[header_name.strip()] = header_value.strip()

      if response.body:
        body = response.body.read()
      else:
        body = ''
      return header_dict, body

    def HandleGetApiConfigsResponse(self, dispatched_output, outfile):
      """Parses the result of getApiConfigs, returning True on success.

      Args:
        dispatched_output: Output from the getApiConfigs call handler.
        outfile: CGI output handle, used for error conditions.

      Returns:
        True on success, False on failure
      """
      response = dev_appserver.RewriteResponse(dispatched_output)
      if self.VerifyResponse(response, 200, 'application/json'):
        self.config_manager.ParseApiConfigResponse(response.body.read())
        return True
      else:
        self.FailRequest('BackendService.getApiConfigs Error', outfile)
        return False

    def CallSpi(self, outfile):
      """Generate SPI call (from earlier-saved request).

      Side effects:
        self.request is modified from Rest/JsonRPC format to apiserving format.

      Args:
        outfile: File to write out CGI-style response in case of error.

      Returns:
        AppServerRequest for redirect or None to send immediate CGI response.
      """
      if self.IsRpc():
        method_config = self.LookupRpcMethod()
        params = None
      else:
        method_config, params = self.LookupRestMethod()
      if method_config:
        try:
          self.TransformRequest(params, method_config)
          discovery_service = DiscoveryService(self.config_manager,
                                               self.request, outfile)

          if not discovery_service.HandleDiscoveryRequest(self.request.path):
            self._request_stage = self.RequestState.SPI_CALL
            return BuildCGIRequest(self.request.cgi_env, self.request,
                                   dev_appserver)
        except RequestRejectionError, rejection_error:
          self._EndRequest()
          return SendCGIRejectedResponse(rejection_error, outfile)
      else:
        self._EndRequest()
        cors_handler = ApiserverDispatcher.__CheckCorsHeaders(self.request)
        return SendCGINotFoundResponse(outfile, cors_handler=cors_handler)

    class __CheckCorsHeaders(object):
      """Track information about CORS headers and our response to them."""

      def __init__(self, request):
        self.allow_cors_request = False
        self.origin = None
        self.cors_request_method = None
        self.cors_request_headers = None

        self.__CheckCorsRequest(request)

      def __CheckCorsRequest(self, request):
        """Check for a CORS request, and see if it gets a CORS response."""

        for orig_header, orig_value in request.headers.iteritems():
          if orig_header.lower() == _CORS_HEADER_ORIGIN:
            self.origin = orig_value
          if orig_header.lower() == _CORS_HEADER_REQUEST_METHOD:
            self.cors_request_method = orig_value
          if orig_header.lower() == _CORS_HEADER_REQUEST_HEADERS:
            self.cors_request_headers = orig_value


        if (self.origin and
            ((self.cors_request_method is None) or
             (self.cors_request_method.upper() in _CORS_ALLOWED_METHODS))):
          self.allow_cors_request = True

      def UpdateHeaders(self, headers):
        """Add CORS headers to the response, if needed."""
        if not self.allow_cors_request:
          return


        headers[_CORS_HEADER_ALLOW_ORIGIN] = self.origin
        headers[_CORS_HEADER_ALLOW_METHODS] = ','.join(
            tuple(_CORS_ALLOWED_METHODS))
        if self.cors_request_headers is not None:
          headers[_CORS_HEADER_ALLOW_HEADERS] = self.cors_request_headers

    def HandleSpiResponse(self, dispatched_output, outfile):
      """Handle SPI response, transforming output as needed.

      Args:
        dispatched_output: Response returned by SPI backend.
        outfile: File-like object to write transformed result.

      Returns:
        None
      """

      response = dev_appserver.AppServerResponse(
          response_file=dispatched_output)
      response_headers, body = self.ParseCgiResponse(response)



      headers = {}
      for header, value in response_headers.items():
        if (header.lower() == 'content-type' and
            not value.lower().startswith('application/json')):
          return self.FailRequest('Non-JSON reply: %s' % body, outfile)
        elif header.lower() not in ('content-length', 'content-type'):
          headers[header] = value

      if self.IsRpc():
        body = self.TransformJsonrpcResponse(body)
      self._EndRequest()

      cors_handler = ApiserverDispatcher.__CheckCorsHeaders(self.request)
      return SendCGIResponse(response.status_code, headers, body, outfile,
                             cors_handler=cors_handler)

    def FailRequest(self, message, outfile):
      """Write an immediate failure response to outfile, no redirect.

      Args:
        message: Error message to be displayed to user (plain text).
        outfile: File-like object to write CGI response to.

      Returns:
        None
      """
      self._EndRequest()
      if self.request:
        cors_handler = ApiserverDispatcher.__CheckCorsHeaders(self.request)
      else:
        cors_handler = None
      return SendCGIErrorResponse(message, outfile, cors_handler=cors_handler)

    def LookupRestMethod(self):
      """Looks up and returns rest method for the currently-pending request.

      This method uses self.request as the currently-pending request.

      Returns:
        tuple of (method, parameters)
      """
      method_name, method, params = self.config_manager.LookupRestMethod(
          self.request.path, self.request.http_method)
      self.request.method_name = method_name
      return method, params

    def LookupRpcMethod(self):
      """Looks up and returns RPC method for the currently-pending request.

      This method uses self.request as the currently-pending request.

      Returns:
        RPC method that was found for the current request.
      """
      if not self.request.body_obj:
        return None
      try:
        method_name = self.request.body_obj.get('method', '')
      except AttributeError:




        if len(self.request.body_obj) != 1:
          raise NotImplementedError('Batch requests with more than 1 element '
                                    'not supported in dev_appserver.  Found '
                                    '%d elements.' % len(self.request.body_obj))
        logging.info('Converting batch request to single request.')
        self.request.body_obj = self.request.body_obj[0]
        method_name = self.request.body_obj.get('method', '')
        self._is_batch = True

      version = self.request.body_obj.get('apiVersion', '')
      self.request.method_name = method_name
      return self.config_manager.LookupRpcMethod(method_name, version)

    def TransformRequest(self, params, method_config):
      """Transforms self.request to apiserving request.

      This method uses self.request to determine the currently-pending request.
      This method accepts a rest-style or RPC-style request.

      Side effects:
        Updates self.request to apiserving format. (e.g. updating path to be the
        method name, and moving request parameters to the body.)

      Args:
        params: Path parameters dictionary for rest request
        method_config: API config of the method to be called
      """
      if self.IsRpc():
        self.TransformJsonrpcRequest()
      else:
        method_params = method_config.get('request', {}).get('parameters', {})
        self.TransformRestRequest(params, method_params)
      self.request.path = method_config.get('rosyMethod', '')

    def _CheckEnum(self, parameter_name, value, field_parameter):
      """Checks if the parameter value is valid if an enum.

      If the parameter is not an enum, does nothing. If it is, verifies that
      its value is valid.

      Args:
        parameter_name: String; The name of the parameter, which is either just
          a variable name or the name with the index appended. For example 'var'
          or 'var[2]'.
        value: String or list of Strings; The value(s) to be used as enum(s) for
          the parameter.
        field_parameter: The dictionary containing information specific to the
          field in question. This is retrieved from request.parameters in the
          method config.

      Raises:
        EnumRejectionError: If the given value is not among the accepted
          enum values in the field parameter.
      """
      if 'enum' not in field_parameter:
        return

      enum_values = [enum['backendValue']
                     for enum in field_parameter['enum'].values()
                     if 'backendValue' in enum]
      if value not in enum_values:
        raise EnumRejectionError(parameter_name, value, enum_values)

    def _CheckParameter(self, parameter_name, value, field_parameter):
      """Checks if the parameter value is valid against all parameter rules.

      First checks if the value is a list and recursively calls _CheckParameter
      on the values in the list. Otherwise, checks all parameter rules for the
      the current value.

      In the list case, '[index-of-value]' is appended to the parameter name for
      error reporting purposes.

      Currently only checks if value adheres to enum rule, but more can be
      added.

      Args:
        parameter_name: String; The name of the parameter, which is either just
          a variable name or the name with the index appended in the recursive
          case. For example 'var' or 'var[2]'.
        value: String or List of values; The value(s) to be used for the
          parameter.
        field_parameter: The dictionary containing information specific to the
          field in question. This is retrieved from request.parameters in the
          method config.
      """
      if isinstance(value, list):
        for index, element in enumerate(value):
          parameter_name_index = '%s[%d]' % (parameter_name, index)
          self._CheckParameter(parameter_name_index, element, field_parameter)
        return

      self._CheckEnum(parameter_name, value, field_parameter)

    def _AddMessageField(self, field_name, value, params):
      """Converts a . delimitied field name to a message field in parameters.

      For example:
        {'a.b.c': ['foo']}
      becomes:
        {'a': {'b': {'c': ['foo']}}}

      Args:
        field_name: String; the . delimitied name to be converted into a
          dictionary.
        value: The value to be set.
        params: The dictionary holding all the parameters, where the value is
          eventually set.
      """
      if '.' not in field_name:
        params[field_name] = value
        return

      root, remaining = field_name.split('.', 1)
      sub_params = params.setdefault(root, {})
      self._AddMessageField(remaining, value, sub_params)

    def _UpdateFromBody(self, destination, source):
      """Updates the dictionary for an API payload with the request body.

      The values from the body should override those already in the payload, but
      for nested fields (message objects), the values can be combined
      recursively.

      Args:
        destination: A dictionary containing an API payload parsed from the
          path and query parameters in a request.
        source: The object parsed from the body of the request.
      """
      for key, value in source.iteritems():
        destination_value = destination.get(key)
        if isinstance(value, dict) and isinstance(destination_value, dict):
          self._UpdateFromBody(destination_value, value)
        else:
          destination[key] = value

    def TransformRestRequest(self, params, method_parameters):
      """Translates a Rest request/response into an apiserving request/response.

      The request can receive values from the path, query and body and combine
      them before sending them along to the SPI server. In cases of collision,
      objects from the body take precedence over those from the query, which in
      turn take precedence over those from the path.

      In the case that a repeated value occurs in both the query and the path,
      those values can be combined, but if that value also occurred in the body,
      it would override any other values.

      In the case of nested values from message fields, non-colliding values
      from subfields can be combined. For example, if '?a.c=10' occurs in the
      query string and "{'a': {'b': 11}}" occurs in the body, then they will be
      combined as

      {
        'a': {
          'b': 11,
          'c': 10,
        }
      }

      before being sent to the SPI server.

      Side effects:
        Updates self.request to apiserving format. (e.g. updating path to be the
        method name, and moving request parameters to the body.)

      Args:
        params: URL path parameter dict extracted by config_manager lookup.
        method_parameters: Dictionary; The parameters for the request from the
          API config of the method.
      """
      body_obj = {}

      for key, value in params.iteritems():


        body_obj[key] = [value]

      if self.request.parameters:

        for key, value in self.request.parameters.iteritems():
          if key in body_obj:
            body_obj[key] = value + body_obj[key]
          else:
            body_obj[key] = value



      for key, value in body_obj.items():
        current_parameter = method_parameters.get(key, {})
        repeated = current_parameter.get('repeated', False)

        if not repeated:
          body_obj[key] = body_obj[key][0]







        self._CheckParameter(key, body_obj[key], current_parameter)

        message_value = body_obj.pop(key)
        self._AddMessageField(key, message_value, body_obj)

      if self.request.body_obj:
        self._UpdateFromBody(body_obj, self.request.body_obj)

      self.request.body_obj = body_obj
      self.request.body = json.dumps(body_obj)

    def TransformJsonrpcRequest(self):
      """Translates a JsonRpc request/response into apiserving request/response.

      Side effects:
        Updates self.request to apiserving format. (e.g. updating path to be the
        method name, and moving request parameters to the body.)
      """
      body_obj = json.loads(self.request.body) if self.request.body else {}
      try:
        self.request.request_id = body_obj.get('id')
      except AttributeError:




        assert self._is_batch
        if len(body_obj) != 1:
          raise NotImplementedError('Batch requests with more than 1 element '
                                    'not supported in dev_appserver.  Found '
                                    '%d elements.' % len(self.request.body_obj))
        body_obj = body_obj[0]
        self.request.request_id = body_obj.get('id')
      body_obj = body_obj.get('params', {})
      self.request.body = json.dumps(body_obj)

    def TransformJsonrpcResponse(self, response_body):
      """Translates a apiserving response to a JsonRpc response.

      Side effects:
        Updates self.request to JsonRpc format. (e.g. restoring request id
        and moving body object into {'result': body_obj}

      Args:
        response_body: Backend response to transform back to JsonRPC

      Returns:
        Updated, JsonRPC-formatted request body
      """
      body_obj = {'result': json.loads(response_body)}
      if self.request.request_id is not None:
        body_obj['id'] = self.request.request_id
      if self._is_batch:
        body_obj = [body_obj]
      return json.dumps(body_obj)

  return ApiserverDispatcher(config_manager)
dataset/ETHPy150Open AppScale/appscale/AppServer/google/appengine/tools/dev_appserver_apiserver.py/CreateApiserverDispatcher/original