From feca97bda37e524b4a6205fd20d3a3279445ac4b Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Fri, 22 Oct 2010 16:06:27 +0200 Subject: [PATCH] Completed the WebDav library with standard GET and POST HTTP requests. MultiPart not yet implemented. --- gen/plone25/skin/widgets/boolean.pt | 4 +- shared/dav.py | 122 ++++++++++++++++++++++++++-- shared/utils.py | 2 +- 3 files changed, 118 insertions(+), 10 deletions(-) diff --git a/gen/plone25/skin/widgets/boolean.pt b/gen/plone25/skin/widgets/boolean.pt index e9a475e..b486886 100644 --- a/gen/plone25/skin/widgets/boolean.pt +++ b/gen/plone25/skin/widgets/boolean.pt @@ -1,5 +1,7 @@ View macro for a Boolean. - + + + Edit macro for an Boolean. diff --git a/shared/dav.py b/shared/dav.py index 15f5e79..963d717 100644 --- a/shared/dav.py +++ b/shared/dav.py @@ -1,24 +1,89 @@ # ------------------------------------------------------------------------------ -import os, re, httplib, sys, stat +import os, re, httplib, sys, stat, urlparse, time +from urllib import quote from StringIO import StringIO from mimetypes import guess_type from base64 import encodestring from appy.shared.utils import copyData +from appy.gen.utils import sequenceTypes + +# ------------------------------------------------------------------------------ +class DataEncoder: + '''Allows to encode form data for sending it through a HTTP request.''' + def __init__(self, data): + self.data = data # The data to encode, as a dict + + def marshalValue(self, name, value): + if isinstance(value, basestring): + return '%s=%s' % (name, quote(str(value))) + elif isinstance(value, float): + return '%s:float=%s' % (name, value) + elif isinstance(value, int): + return '%s:int=%s' % (name, value) + elif isinstance(value, long): + res = '%s:long=%s' % (name, value) + if res[-1] == 'L': + res = res[:-1] + return res + else: + raise 'Cannot encode value %s' % str(value) + + def encode(self): + res = [] + for name, value in self.data.iteritems(): + res.append(self.marshalValue(name, value)) + return '&'.join(res) + +# ------------------------------------------------------------------------------ +class HttpResponse: + '''Stores information about a HTTP response.''' + def __init__(self, code, text, headers, body, duration=None): + self.code = code # The return code, ie 404, 200, ... + self.text = text # Textual description of the code + self.headers = headers # A dict-like object containing the headers + self.body = body # The body of the HTTP response + # The following attribute may contain specific data extracted from + # the previous fields. For example, when response if 302 (Redirect), + # self.data contains the URI where we must redirect the user to. + self.data = self.extractData() + # p_duration, if given, is the time, in seconds, we have waited, before + # getting this response after having sent the request. + self.duration = duration + + def __repr__(self): + duration = '' + if self.duration: duration = ', got in %.4f seconds' % self.duration + return '' % (self.code, self.text, duration) + + def extractData(self): + '''This method extracts, from the various parts of the HTTP response, + some useful information. For example, it will find the URI where to + redirect the user to if self.code is 302.''' + if self.code == 302: + return urlparse.urlparse(self.headers['location'])[2] # ------------------------------------------------------------------------------ urlRex = re.compile(r'http://([^:/]+)(:[0-9]+)?(/.+)?', re.I) binaryRex = re.compile(r'[\000-\006\177-\277]') -# ------------------------------------------------------------------------------ class Resource: '''Every instance of this class represents some web resource accessible through WebDAV.''' - def __init__(self, url, username=None, password=None): + def __init__(self, url, username=None, password=None, measure=False): self.username = username self.password = password self.url = url - + # If some headers must be sent with any request sent through this + # resource (like a cookie), you can store them in the following dict. + self.headers = {} + # If p_measure is True, we will measure, for every request sent, the + # time we wait until we receive the response. + self.measure = measure + # If measure is True, we will store hereafter, the total time (in + # seconds) spent waiting for the server for all requests sent through + # this resource object. + self.serverTime = 0 # Split the URL into its components res = urlRex.match(url) if res: @@ -50,16 +115,29 @@ class Resource: '''Sends a HTTP request with p_method, for p_uri.''' conn = httplib.HTTP() conn.connect(self.host, self.port) + # Tell what kind of HTTP request it will be. conn.putrequest(method, uri) # Add HTTP headers self.updateHeaders(headers) + if self.headers: headers.update(self.headers) for n, v in headers.items(): conn.putheader(n, v) conn.endheaders() - if body: copyData(body, conn, 'send', type=bodyType) - ver, code, msg = conn.getreply() - data = conn.getfile().read() + # Add HTTP body + if body: + if not bodyType: bodyType = 'string' + copyData(body, conn, 'send', type=bodyType) + # Send the request, get the reply + if self.measure: startTime = time.time() + code, text, headers = conn.getreply() + if self.measure: endTime = time.time() + body = conn.getfile().read() conn.close() - return data + # Return a smart object containing the various parts of the response + duration = None + if self.measure: + duration = endTime - startTime + self.serverTime += duration + return HttpResponse(code, text, headers, body, duration=duration) def mkdir(self, name): '''Creates a folder named p_name in this resource.''' @@ -105,4 +183,32 @@ class Resource: res = self.sendRequest('PUT', fileUri, body, headers, bodyType=bodyType) # Close the file when relevant if type =='fileName': body.close() + return res + + def _encodeFormData(self, data): + '''Returns the encoded form p_data.''' + res = [] + for name, value in data.items(): + n = name.rfind( '__') + if n > 0: + tag = name[n+2:] + key = name[:n] + else: tag = 'string' + func = varfuncs.get(tag, marshal_string) + res.append(func(name, value)) + return '&'.join(res) + + def get(self, uri=None, headers={}): + '''Perform a HTTP GET on the server.''' + if not uri: uri = self.uri + return self.sendRequest('GET', uri, headers=headers) + + def post(self, data, uri=None, headers={}): + '''Perform a HTTP POST on the server.''' + if not uri: uri = self.uri + # Format the form data and prepare headers + body = DataEncoder(data).encode() + headers['Content-Type'] = 'application/x-www-form-urlencoded' + headers['Content-Length'] = str(len(body)) + return self.sendRequest('POST', uri, headers=headers, body=body) # ------------------------------------------------------------------------------ diff --git a/shared/utils.py b/shared/utils.py index 36168c3..3a627a9 100644 --- a/shared/utils.py +++ b/shared/utils.py @@ -95,7 +95,7 @@ def copyData(data, target, targetMethod, type='string', encoding=None, that is compatible with the content of p_data, ie file('myFile.doc','wb') if content is binary.''' dump = getattr(target, targetMethod) - if type == 'string': dump(encodeData(data, encoding)) + if not type or (type == 'string'): dump(encodeData(data, encoding)) elif type == 'file': while True: chunk = data.read(chunkSize)