#!/usr/bin/python # # Copyright (C) 2008 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. """This module provides a common interface for all HTTP requests. HttpResponse: Represents the server's response to an HTTP request. Provides an interface identical to httplib.HTTPResponse which is the response expected from higher level classes which use HttpClient.request. GenericHttpClient: Provides an interface (superclass) for an object responsible for making HTTP requests. Subclasses of this object are used in AtomService and GDataService to make requests to the server. By changing the http_client member object, the AtomService is able to make HTTP requests using different logic (for example, when running on Google App Engine, the http_client makes requests using the App Engine urlfetch API). """ __author__ = 'api.jscudder (Jeff Scudder)' import StringIO USER_AGENT = '%s GData-Python/2.0.12' class Error(Exception): pass class UnparsableUrlObject(Error): pass class ContentLengthRequired(Error): pass class HttpResponse(object): def __init__(self, body=None, status=None, reason=None, headers=None): """Constructor for an HttpResponse object. HttpResponse represents the server's response to an HTTP request from the client. The HttpClient.request method returns a httplib.HTTPResponse object and this HttpResponse class is designed to mirror the interface exposed by httplib.HTTPResponse. Args: body: A file like object, with a read() method. The body could also be a string, and the constructor will wrap it so that HttpResponse.read(self) will return the full string. status: The HTTP status code as an int. Example: 200, 201, 404. reason: The HTTP status message which follows the code. Example: OK, Created, Not Found headers: A dictionary containing the HTTP headers in the server's response. A common header in the response is Content-Length. """ if body: if hasattr(body, 'read'): self._body = body else: self._body = StringIO.StringIO(body) else: self._body = None if status is not None: self.status = int(status) else: self.status = None self.reason = reason self._headers = headers or {} def getheader(self, name, default=None): if name in self._headers: return self._headers[name] else: return default def read(self, amt=None): if not amt: return self._body.read() else: return self._body.read(amt) class GenericHttpClient(object): debug = False def __init__(self, http_client, headers=None): """ Args: http_client: An object which provides a request method to make an HTTP request. The request method in GenericHttpClient performs a call-through to the contained HTTP client object. headers: A dictionary containing HTTP headers which should be included in every HTTP request. Common persistent headers include 'User-Agent'. """ self.http_client = http_client self.headers = headers or {} def request(self, operation, url, data=None, headers=None): all_headers = self.headers.copy() if headers: all_headers.update(headers) return self.http_client.request(operation, url, data=data, headers=all_headers) def get(self, url, headers=None): return self.request('GET', url, headers=headers) def post(self, url, data, headers=None): return self.request('POST', url, data=data, headers=headers) def put(self, url, data, headers=None): return self.request('PUT', url, data=data, headers=headers) def delete(self, url, headers=None): return self.request('DELETE', url, headers=headers) class GenericToken(object): """Represents an Authorization token to be added to HTTP requests. Some Authorization headers included calculated fields (digital signatures for example) which are based on the parameters of the HTTP request. Therefore the token is responsible for signing the request and adding the Authorization header. """ def perform_request(self, http_client, operation, url, data=None, headers=None): """For the GenericToken, no Authorization token is set.""" return http_client.request(operation, url, data=data, headers=headers) def valid_for_scope(self, url): """Tells the caller if the token authorizes access to the desired URL. Since the generic token doesn't add an auth header, it is not valid for any scope. """ return False