blob: 6e243944c0daca8d0934d8cd16efaf47a99bfd9e [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# 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.
#
"""Cloud Endpoints API request-related data and functions."""
from __future__ import with_statement
import cgi
import copy
import json
import logging
import urllib
from google.appengine.tools.devappserver2 import util
class ApiRequest(object):
"""Simple data object representing an API request.
Parses the request from environment variables into convenient pieces
and stores them as members.
"""
_API_PREFIX = '/_ah/api/'
def __init__(self, environ):
"""Constructor.
Args:
environ: An environ dict for the request as defined in PEP-333.
Raises:
ValueError: If the path for the request is invalid.
"""
self.headers = util.get_headers_from_environ(environ)
self.http_method = environ['REQUEST_METHOD']
self.server = environ['SERVER_NAME']
self.port = environ['SERVER_PORT']
self.path = environ['PATH_INFO']
self.query = environ.get('QUERY_STRING')
self.body = environ['wsgi.input'].read()
self.source_ip = environ.get('REMOTE_ADDR')
self.relative_url = self._reconstruct_relative_url(environ)
if not self.path.startswith(self._API_PREFIX):
raise ValueError('Invalid request path: %s' % self.path)
self.path = self.path[len(self._API_PREFIX):]
if self.query:
self.parameters = cgi.parse_qs(self.query, keep_blank_values=True)
else:
self.parameters = {}
self.body_json = json.loads(self.body) if self.body else {}
self.request_id = None
# Check if it's a batch request. We'll only handle single-element batch
# requests on the dev server (and we need to handle them because that's
# what RPC and JS calls typically show up as). Pull the request out of the
# list and record the fact that we're processing a batch.
if isinstance(self.body_json, list):
if len(self.body_json) != 1:
logging.warning('Batch requests with more than 1 element aren\'t '
'supported in devappserver2. Only the first element '
'will be handled. Found %d elements.',
len(self.body_json))
else:
logging.info('Converting batch request to single request.')
self.body_json = self.body_json[0]
self.body = json.dumps(self.body_json)
self._is_batch = True
else:
self._is_batch = False
def _reconstruct_relative_url(self, environ):
"""Reconstruct the relative URL of this request.
This is based on the URL reconstruction code in Python PEP 333:
http://www.python.org/dev/peps/pep-0333/#url-reconstruction. Rebuild the
URL from the pieces available in the environment.
Args:
environ: An environ dict for the request as defined in PEP-333.
Returns:
The portion of the URL from the request after the server and port.
"""
url = urllib.quote(environ.get('SCRIPT_NAME', ''))
url += urllib.quote(environ.get('PATH_INFO', ''))
if environ.get('QUERY_STRING'):
url += '?' + environ['QUERY_STRING']
return url
def copy(self):
return copy.deepcopy(self)
def is_rpc(self):
# Google's JsonRPC protocol creates a handler at /rpc for any Cloud
# Endpoints API, with api name, version, and method name being in the
# body of the request.
# If the request is sent to /rpc, we will treat it as JsonRPC.
# The client libraries for iOS's Objective C use RPC and not the REST
# versions of the API.
return self.path == 'rpc'
def is_batch(self):
return self._is_batch