151 def copy(self): |
155 def copy(self): |
152 """Make a shallow, case sensitive copy of self.""" |
156 """Make a shallow, case sensitive copy of self.""" |
153 return dict(self) |
157 return dict(self) |
154 |
158 |
155 |
159 |
156 def fetch(url, payload=None, method=GET, headers={}, allow_truncated=False): |
160 def _is_fetching_self(url, method): |
|
161 """Checks if the fetch is for the same URL from which it originated. |
|
162 |
|
163 Args: |
|
164 url: str, The URL being fetched. |
|
165 method: value from _VALID_METHODS. |
|
166 |
|
167 Returns: |
|
168 boolean indicating whether or not it seems that the app is trying to fetch |
|
169 itself. |
|
170 """ |
|
171 if (method != GET or |
|
172 "HTTP_HOST" not in os.environ or |
|
173 "PATH_INFO" not in os.environ): |
|
174 return False |
|
175 |
|
176 scheme, host_port, path, query, fragment = urlparse.urlsplit(url) |
|
177 |
|
178 if (host_port == os.environ['HTTP_HOST'] and |
|
179 urllib2.unquote(path) == urllib2.unquote(os.environ['PATH_INFO'])): |
|
180 return True |
|
181 |
|
182 return False |
|
183 |
|
184 |
|
185 def fetch(url, payload=None, method=GET, headers={}, allow_truncated=False, |
|
186 follow_redirects=True): |
157 """Fetches the given HTTP URL, blocking until the result is returned. |
187 """Fetches the given HTTP URL, blocking until the result is returned. |
158 |
188 |
159 Other optional parameters are: |
189 Other optional parameters are: |
160 method: GET, POST, HEAD, PUT, or DELETE |
190 method: GET, POST, HEAD, PUT, or DELETE |
161 payload: POST or PUT payload (implies method is not GET, HEAD, or DELETE) |
191 payload: POST or PUT payload (implies method is not GET, HEAD, or DELETE) |
162 headers: dictionary of HTTP headers to send with the request |
192 headers: dictionary of HTTP headers to send with the request |
163 allow_truncated: if true, truncate large responses and return them without |
193 allow_truncated: if true, truncate large responses and return them without |
164 error. otherwise, ResponseTooLargeError will be thrown when a response is |
194 error. otherwise, ResponseTooLargeError will be thrown when a response is |
165 truncated. |
195 truncated. |
|
196 follow_redirects: if true (the default), redirects are |
|
197 transparently followed and the response (if less than 5 |
|
198 redirects) contains the final destination's payload and the |
|
199 response status is 200. You lose, however, the redirect chain |
|
200 information. If false, you see the HTTP response yourself, |
|
201 including the 'Location' header, and redirects are not |
|
202 followed. |
166 |
203 |
167 We use a HTTP/1.1 compliant proxy to fetch the result. |
204 We use a HTTP/1.1 compliant proxy to fetch the result. |
168 |
205 |
169 The returned data structure has the following fields: |
206 The returned data structure has the following fields: |
170 content: string containing the response from the server |
207 content: string containing the response from the server |
175 urlfetch.InvalidURLError. If the server cannot be contacted, we throw a |
212 urlfetch.InvalidURLError. If the server cannot be contacted, we throw a |
176 urlfetch.DownloadError. Note that HTTP errors are returned as a part |
213 urlfetch.DownloadError. Note that HTTP errors are returned as a part |
177 of the returned structure, so HTTP errors like 404 do not result in an |
214 of the returned structure, so HTTP errors like 404 do not result in an |
178 exception. |
215 exception. |
179 """ |
216 """ |
180 request = urlfetch_service_pb.URLFetchRequest() |
|
181 response = urlfetch_service_pb.URLFetchResponse() |
|
182 request.set_url(url) |
|
183 |
|
184 if isinstance(method, basestring): |
217 if isinstance(method, basestring): |
185 method = method.upper() |
218 method = method.upper() |
186 method = _URL_STRING_MAP.get(method, method) |
219 method = _URL_STRING_MAP.get(method, method) |
187 if method not in _VALID_METHODS: |
220 if method not in _VALID_METHODS: |
188 raise InvalidMethodError('Invalid method %s.' % str(method)) |
221 raise InvalidMethodError('Invalid method %s.' % str(method)) |
|
222 |
|
223 if _is_fetching_self(url, method): |
|
224 raise InvalidURLError("App cannot fetch the same URL as the one used for " |
|
225 "the request.") |
|
226 |
|
227 request = urlfetch_service_pb.URLFetchRequest() |
|
228 response = urlfetch_service_pb.URLFetchResponse() |
|
229 request.set_url(url) |
|
230 |
189 if method == GET: |
231 if method == GET: |
190 request.set_method(urlfetch_service_pb.URLFetchRequest.GET) |
232 request.set_method(urlfetch_service_pb.URLFetchRequest.GET) |
191 elif method == POST: |
233 elif method == POST: |
192 request.set_method(urlfetch_service_pb.URLFetchRequest.POST) |
234 request.set_method(urlfetch_service_pb.URLFetchRequest.POST) |
193 elif method == HEAD: |
235 elif method == HEAD: |
203 for key, value in headers.iteritems(): |
245 for key, value in headers.iteritems(): |
204 header_proto = request.add_header() |
246 header_proto = request.add_header() |
205 header_proto.set_key(key) |
247 header_proto.set_key(key) |
206 header_proto.set_value(value) |
248 header_proto.set_value(value) |
207 |
249 |
|
250 request.set_followredirects(follow_redirects) |
|
251 |
208 try: |
252 try: |
209 apiproxy_stub_map.MakeSyncCall('urlfetch', 'Fetch', request, response) |
253 apiproxy_stub_map.MakeSyncCall('urlfetch', 'Fetch', request, response) |
210 except apiproxy_errors.ApplicationError, e: |
254 except apiproxy_errors.ApplicationError, e: |
211 if (e.application_error == |
255 if (e.application_error == |
212 urlfetch_service_pb.URLFetchServiceError.INVALID_URL): |
256 urlfetch_service_pb.URLFetchServiceError.INVALID_URL): |