Completed the WebDav library with standard GET and POST HTTP requests. MultiPart not yet implemented.

This commit is contained in:
Gaetan Delannay 2010-10-22 16:06:27 +02:00
parent 2aedf8c88a
commit feca97bda3
3 changed files with 118 additions and 10 deletions

View file

@ -1,5 +1,7 @@
<tal:comment replace="nothing">View macro for a Boolean.</tal:comment> <tal:comment replace="nothing">View macro for a Boolean.</tal:comment>
<metal:view define-macro="view"><span tal:replace="value"/></metal:view> <metal:view define-macro="view">
<span tal:attributes="class widget/master_css; id rawValue" tal:content="value"></span>
</metal:view>
<tal:comment replace="nothing">Edit macro for an Boolean.</tal:comment> <tal:comment replace="nothing">Edit macro for an Boolean.</tal:comment>
<metal:edit define-macro="edit"> <metal:edit define-macro="edit">

View file

@ -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 StringIO import StringIO
from mimetypes import guess_type from mimetypes import guess_type
from base64 import encodestring from base64 import encodestring
from appy.shared.utils import copyData 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 '<HttpResponse %s (%s)%s>' % (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) urlRex = re.compile(r'http://([^:/]+)(:[0-9]+)?(/.+)?', re.I)
binaryRex = re.compile(r'[\000-\006\177-\277]') binaryRex = re.compile(r'[\000-\006\177-\277]')
# ------------------------------------------------------------------------------
class Resource: class Resource:
'''Every instance of this class represents some web resource accessible '''Every instance of this class represents some web resource accessible
through WebDAV.''' through WebDAV.'''
def __init__(self, url, username=None, password=None): def __init__(self, url, username=None, password=None, measure=False):
self.username = username self.username = username
self.password = password self.password = password
self.url = url 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 # Split the URL into its components
res = urlRex.match(url) res = urlRex.match(url)
if res: if res:
@ -50,16 +115,29 @@ class Resource:
'''Sends a HTTP request with p_method, for p_uri.''' '''Sends a HTTP request with p_method, for p_uri.'''
conn = httplib.HTTP() conn = httplib.HTTP()
conn.connect(self.host, self.port) conn.connect(self.host, self.port)
# Tell what kind of HTTP request it will be.
conn.putrequest(method, uri) conn.putrequest(method, uri)
# Add HTTP headers # Add HTTP headers
self.updateHeaders(headers) self.updateHeaders(headers)
if self.headers: headers.update(self.headers)
for n, v in headers.items(): conn.putheader(n, v) for n, v in headers.items(): conn.putheader(n, v)
conn.endheaders() conn.endheaders()
if body: copyData(body, conn, 'send', type=bodyType) # Add HTTP body
ver, code, msg = conn.getreply() if body:
data = conn.getfile().read() 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() 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): def mkdir(self, name):
'''Creates a folder named p_name in this resource.''' '''Creates a folder named p_name in this resource.'''
@ -105,4 +183,32 @@ class Resource:
res = self.sendRequest('PUT', fileUri, body, headers, bodyType=bodyType) res = self.sendRequest('PUT', fileUri, body, headers, bodyType=bodyType)
# Close the file when relevant # Close the file when relevant
if type =='fileName': body.close() 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)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -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') that is compatible with the content of p_data, ie file('myFile.doc','wb')
if content is binary.''' if content is binary.'''
dump = getattr(target, targetMethod) dump = getattr(target, targetMethod)
if type == 'string': dump(encodeData(data, encoding)) if not type or (type == 'string'): dump(encodeData(data, encoding))
elif type == 'file': elif type == 'file':
while True: while True:
chunk = data.read(chunkSize) chunk = data.read(chunkSize)