OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 Google Inc. All Rights Reserved. |
| 2 # |
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # you may not use this file except in compliance with the License. |
| 5 # You may obtain a copy of the License at |
| 6 # |
| 7 # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 # |
| 9 # Unless required by applicable law or agreed to in writing, software |
| 10 # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 # See the License for the specific language governing permissions and |
| 13 # limitations under the License. |
| 14 |
| 15 """Cloud Endpoints API request-related data and functions.""" |
| 16 |
| 17 from __future__ import with_statement |
| 18 |
| 19 # pylint: disable=g-bad-name |
| 20 import copy |
| 21 import json |
| 22 import logging |
| 23 import urllib |
| 24 import urlparse |
| 25 import zlib |
| 26 |
| 27 import util |
| 28 |
| 29 |
| 30 class ApiRequest(object): |
| 31 """Simple data object representing an API request. |
| 32 |
| 33 Parses the request from environment variables into convenient pieces |
| 34 and stores them as members. |
| 35 """ |
| 36 def __init__(self, environ, base_paths=None): |
| 37 """Constructor. |
| 38 |
| 39 Args: |
| 40 environ: An environ dict for the request as defined in PEP-333. |
| 41 |
| 42 Raises: |
| 43 ValueError: If the path for the request is invalid. |
| 44 """ |
| 45 self.headers = util.get_headers_from_environ(environ) |
| 46 self.http_method = environ['REQUEST_METHOD'] |
| 47 self.url_scheme = environ['wsgi.url_scheme'] |
| 48 self.server = environ['SERVER_NAME'] |
| 49 self.port = environ['SERVER_PORT'] |
| 50 self.path = environ['PATH_INFO'] |
| 51 self.query = environ.get('QUERY_STRING') |
| 52 self.body = environ['wsgi.input'].read() |
| 53 if self.body and self.headers.get('CONTENT-ENCODING') == 'gzip': |
| 54 # Increasing wbits to 16 + MAX_WBITS is necessary to be able to decode |
| 55 # gzipped content (as opposed to zlib-encoded content). |
| 56 # If there's an error in the decompression, it could be due to another |
| 57 # part of the serving chain that already decompressed it without clearing |
| 58 # the header. If so, just ignore it and continue. |
| 59 try: |
| 60 self.body = zlib.decompress(self.body, 16 + zlib.MAX_WBITS) |
| 61 except zlib.error: |
| 62 pass |
| 63 self.source_ip = environ.get('REMOTE_ADDR') |
| 64 self.relative_url = self._reconstruct_relative_url(environ) |
| 65 |
| 66 if not base_paths: |
| 67 base_paths = set() |
| 68 elif isinstance(base_paths, list): |
| 69 base_paths = set(base_paths) |
| 70 |
| 71 # Find a base_path in the path |
| 72 for base_path in base_paths: |
| 73 if self.path.startswith(base_path): |
| 74 self.path = self.path[len(base_path):] |
| 75 self.base_path = base_path |
| 76 break |
| 77 else: |
| 78 raise ValueError('Invalid request path: %s' % self.path) |
| 79 |
| 80 if self.query: |
| 81 self.parameters = urlparse.parse_qs(self.query, keep_blank_values=True) |
| 82 else: |
| 83 self.parameters = {} |
| 84 self.body_json = self._process_req_body(self.body) if self.body else {} |
| 85 self.request_id = None |
| 86 |
| 87 # Check if it's a batch request. We'll only handle single-element batch |
| 88 # requests on the dev server (and we need to handle them because that's |
| 89 # what RPC and JS calls typically show up as). Pull the request out of the |
| 90 # list and record the fact that we're processing a batch. |
| 91 if isinstance(self.body_json, list): |
| 92 if len(self.body_json) != 1: |
| 93 logging.warning('Batch requests with more than 1 element aren\'t ' |
| 94 'supported in devappserver2. Only the first element ' |
| 95 'will be handled. Found %d elements.', |
| 96 len(self.body_json)) |
| 97 else: |
| 98 logging.info('Converting batch request to single request.') |
| 99 self.body_json = self.body_json[0] |
| 100 self.body = json.dumps(self.body_json) |
| 101 self._is_batch = True |
| 102 else: |
| 103 self._is_batch = False |
| 104 |
| 105 def _process_req_body(self, body): |
| 106 """Process the body of the HTTP request. |
| 107 |
| 108 If the body is valid JSON, return the JSON as a dict. |
| 109 Else, convert the key=value format to a dict and return that. |
| 110 |
| 111 Args: |
| 112 body: The body of the HTTP request. |
| 113 """ |
| 114 try: |
| 115 return json.loads(body) |
| 116 except ValueError: |
| 117 return urlparse.parse_qs(body, keep_blank_values=True) |
| 118 |
| 119 def _reconstruct_relative_url(self, environ): |
| 120 """Reconstruct the relative URL of this request. |
| 121 |
| 122 This is based on the URL reconstruction code in Python PEP 333: |
| 123 http://www.python.org/dev/peps/pep-0333/#url-reconstruction. Rebuild the |
| 124 URL from the pieces available in the environment. |
| 125 |
| 126 Args: |
| 127 environ: An environ dict for the request as defined in PEP-333. |
| 128 |
| 129 Returns: |
| 130 The portion of the URL from the request after the server and port. |
| 131 """ |
| 132 url = urllib.quote(environ.get('SCRIPT_NAME', '')) |
| 133 url += urllib.quote(environ.get('PATH_INFO', '')) |
| 134 if environ.get('QUERY_STRING'): |
| 135 url += '?' + environ['QUERY_STRING'] |
| 136 return url |
| 137 |
| 138 def copy(self): |
| 139 return copy.deepcopy(self) |
| 140 |
| 141 def is_rpc(self): |
| 142 # Google's JsonRPC protocol creates a handler at /rpc for any Cloud |
| 143 # Endpoints API, with api name, version, and method name being in the |
| 144 # body of the request. |
| 145 # If the request is sent to /rpc, we will treat it as JsonRPC. |
| 146 # The client libraries for iOS's Objective C use RPC and not the REST |
| 147 # versions of the API. |
| 148 return self.path == 'rpc' |
| 149 |
| 150 def is_batch(self): |
| 151 return self._is_batch |
OLD | NEW |