direct-io.hg

changeset 2510:fbfc2497d55e

bitkeeper revision 1.1159.1.165 (4151594cmY68ImBqRlb00RkEm61RcQ)

Split the xend client code into separate synch and asynch client
protocols, and load the asynch one on demand. This speeds up xm
as it doesn't load the asynch code.
author mjw@wray-m-3.hpl.hp.com
date Wed Sep 22 10:51:56 2004 +0000 (2004-09-22)
parents 03bd2991dbd4
children f7b2e90dac20
files .rootkeys tools/examples/README tools/python/xen/sv/DomInfo.py tools/python/xen/xend/XendAsynchProtocol.py tools/python/xen/xend/XendClient.py tools/python/xen/xend/XendProtocol.py
line diff
     1.1 --- a/.rootkeys	Tue Sep 21 14:29:16 2004 +0000
     1.2 +++ b/.rootkeys	Wed Sep 22 10:51:56 2004 +0000
     1.3 @@ -454,6 +454,7 @@ 40c9c468Um_qc66OQeLEceIz1pgD5g tools/pyt
     1.4  40c9c468U8EVl0d3G--8YXVg6VJD3g tools/python/xen/xend/EventTypes.py
     1.5  40c9c468QJTEuk9g4qHxGpmIi70PEQ tools/python/xen/xend/PrettyPrint.py
     1.6  40e15b7eeQxWE_hUPB2YTgM9fsZ1PQ tools/python/xen/xend/Vifctl.py
     1.7 +4151594bBq8h-bwTfEt8dbBuojMtcA tools/python/xen/xend/XendAsynchProtocol.py
     1.8  40c9c4688m3eqnC8fhLu1APm36VOVA tools/python/xen/xend/XendClient.py
     1.9  40c9c468t6iIKTjwuYoe-UMCikDcOQ tools/python/xen/xend/XendConsole.py
    1.10  40c9c468WnXs6eOUSff23IIGI4kMfQ tools/python/xen/xend/XendDB.py
    1.11 @@ -465,6 +466,7 @@ 40f50d99YiiaMI1fZBh1VCDFLD57qg tools/pyt
    1.12  40ffc44eGsgTEY355E3nN4mPLZHhMQ tools/python/xen/xend/XendLogging.py
    1.13  40c9c46854nsHmuxHQHncKk5rAs5NA tools/python/xen/xend/XendMigrate.py
    1.14  40c9c468M96gA1EYDvNa5w5kQNYLFA tools/python/xen/xend/XendNode.py
    1.15 +4151594bhib4aUerB2SMKDl-iCtc4Q tools/python/xen/xend/XendProtocol.py
    1.16  40c9c4686jruMyZIqiaZRMiMoqMJtg tools/python/xen/xend/XendRoot.py
    1.17  40c9c468xzANp6o2D_MeCYwNmOIUsQ tools/python/xen/xend/XendVnet.py
    1.18  40c9c468x191zetrVlMnExfsQWHxIQ tools/python/xen/xend/__init__.py
     2.1 --- a/tools/examples/README	Tue Sep 21 14:29:16 2004 +0000
     2.2 +++ b/tools/examples/README	Wed Sep 22 10:51:56 2004 +0000
     2.3 @@ -12,7 +12,6 @@ send it (preferably with a little summar
     2.4  network             - default network setup script called by xend at startup.
     2.5  vif-bridge          - default virtual network interface setup script.
     2.6  xend-config.sxp     - default xend configuration file.
     2.7 -xmdefconfig         - default configuration script for 'xm create'.
     2.8 -xmdefconfig-example - a more complex configuration script for 'xm create'.
     2.9 -xmdefconfig-netbsd  - an 'xm create' configuration script for NetBSD domains.
    2.10 +xmexample1          - example configuration script for 'xm create'.
    2.11 +xmexample2          - a more complex configuration script for 'xm create'.
    2.12  
     3.1 --- a/tools/python/xen/sv/DomInfo.py	Tue Sep 21 14:29:16 2004 +0000
     3.2 +++ b/tools/python/xen/sv/DomInfo.py	Wed Sep 22 10:51:56 2004 +0000
     3.3 @@ -1,4 +1,5 @@
     3.4 -from xen.xend.XendClient import aserver as server
     3.5 +from xen.xend.XendClient import getAsynchServer
     3.6 +server = getAsynchServer()
     3.7  from xen.xend import PrettyPrint
     3.8  
     3.9  from xen.sv.HTMLBase import HTMLBase
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/tools/python/xen/xend/XendAsynchProtocol.py	Wed Sep 22 10:51:56 2004 +0000
     4.3 @@ -0,0 +1,94 @@
     4.4 +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com>
     4.5 +
     4.6 +from twisted.protocols import http
     4.7 +from twisted.internet.protocol import ClientCreator
     4.8 +from twisted.internet.defer import Deferred
     4.9 +from twisted.internet import reactor
    4.10 +
    4.11 +from XendProtocol import XendClientProtocol, XendRequest
    4.12 +
    4.13 +class AsynchXendClient(http.HTTPClient):
    4.14 +    """A subclass of twisted's HTTPClient to deal with a connection to xend.
    4.15 +    Makes the request when connected, and delegates handling responses etc.
    4.16 +    to its protocol (usually an AsynchXendClientProtocol instance).
    4.17 +    """
    4.18 +    def __init__(self, protocol, request):
    4.19 +        self.protocol = protocol
    4.20 +        self.request = request
    4.21 +
    4.22 +    def connectionMade(self):
    4.23 +        request = self.request
    4.24 +        url = self.request.url
    4.25 +        self.sendCommand(request.method, url.fullpath())
    4.26 +        self.sendHeader('Host', url.location())
    4.27 +        for (k, v) in request.headers.items():
    4.28 +            self.sendHeader(k, v)
    4.29 +        if request.data:
    4.30 +            self.sendHeader('Content-Length', len(request.data))
    4.31 +        self.endHeaders()
    4.32 +        if request.data:
    4.33 +            self.transport.write(request.data)
    4.34 +
    4.35 +    def handleStatus(self, version, status, message):
    4.36 +        return self.protocol.handleStatus(version, status, message)
    4.37 +
    4.38 +    def handleHeader(self, key, val):
    4.39 +        return self.protocol.handleHeader(key, val)
    4.40 +
    4.41 +    def handleResponse(self, data):
    4.42 +        return self.protocol.handleResponse(data)
    4.43 +
    4.44 +class AsynchXendClientProtocol(XendClientProtocol):
    4.45 +    """An asynchronous xend client. Uses twisted to connect to xend
    4.46 +    and make the request. It does not block waiting for the result,
    4.47 +    but sets up a deferred that is called when the result becomes available.
    4.48 +
    4.49 +    Uses AsynchXendClient to manage the connection.
    4.50 +    """
    4.51 +    def __init__(self):
    4.52 +        self.err = None
    4.53 +        self.headers = {}
    4.54 +
    4.55 +    def xendRequest(self, url, method, args=None):
    4.56 +        """Make a request to xend. The returned deferred is called when
    4.57 +        the result is available.
    4.58 +        
    4.59 +        @param url:    xend request url
    4.60 +        @param method: http method: POST or GET
    4.61 +        @param args:   request arguments (dict)
    4.62 +        @return: deferred
    4.63 +        """
    4.64 +        request = XendRequest(url, method, args)
    4.65 +        self.deferred = Deferred()
    4.66 +        clientCreator = ClientCreator(reactor, AsynchXendClient, self, request)
    4.67 +        clientCreator.connectTCP(url.host, url.port)
    4.68 +        return self.deferred
    4.69 +
    4.70 +    def callErrback(self, err):
    4.71 +        if not self.deferred.called:
    4.72 +            self.err = err
    4.73 +            self.deferred.errback(err)
    4.74 +        return err
    4.75 +
    4.76 +    def callCallback(self, val):
    4.77 +        if not self.deferred.called:
    4.78 +            self.deferred.callback(val)
    4.79 +        return val
    4.80 +
    4.81 +    def handleException(self, err):
    4.82 +        return self.callErrback(err)
    4.83 +
    4.84 +    def handleHeader(self, key, val):
    4.85 +        self.headers[key.lower()] = val
    4.86 +
    4.87 +    def getHeader(self, key):
    4.88 +        return self.headers.get(key.lower())
    4.89 +
    4.90 +    def handleResponse(self, data):
    4.91 +        if self.err: return self.err
    4.92 +        val = XendClientProtocol.handleResponse(self, data)
    4.93 +        if isinstance(val, Exception):
    4.94 +            self.callErrback(val)
    4.95 +        else:
    4.96 +            self.callCallback(val)
    4.97 +        return val
     5.1 --- a/tools/python/xen/xend/XendClient.py	Tue Sep 21 14:29:16 2004 +0000
     5.2 +++ b/tools/python/xen/xend/XendClient.py	Wed Sep 22 10:51:56 2004 +0000
     5.3 @@ -11,27 +11,14 @@ is accessible via this API.
     5.4  """
     5.5  import os
     5.6  import sys
     5.7 -import httplib
     5.8  import types
     5.9 -from StringIO import StringIO
    5.10 -
    5.11  
    5.12 -from twisted.protocols import http
    5.13 -from twisted.internet.protocol import ClientCreator
    5.14 -from twisted.internet.defer import Deferred
    5.15 -from twisted.internet import reactor
    5.16 -
    5.17 -from encode import *
    5.18  import sxp
    5.19  import PrettyPrint
    5.20 +from XendProtocol import XendClientProtocol, SynchXendClientProtocol, XendError
    5.21  
    5.22  DEBUG = 0
    5.23  
    5.24 -class XendError(RuntimeError):
    5.25 -    """Error class for 'expected errors' when talking to xend.
    5.26 -    """
    5.27 -    pass
    5.28 -
    5.29  def fileof(val):
    5.30      """Converter for passing configs or other 'large' data.
    5.31      Handles lists, files directly.
    5.32 @@ -102,229 +89,6 @@ class URL:
    5.33                     query=query,
    5.34                     frag=frag)
    5.35  
    5.36 -class XendRequest:
    5.37 -    """A request to xend.
    5.38 -    """
    5.39 -
    5.40 -    def __init__(self, url, method, args):
    5.41 -        """Create a request. Sets up the headers, argument data, and the
    5.42 -        url.
    5.43 -
    5.44 -        @param url:    the url to request
    5.45 -        @param method: request method, GET or POST
    5.46 -        @param args:   dict containing request args, if any
    5.47 -        """
    5.48 -        if url.proto != 'http':
    5.49 -            raise ValueError('Invalid protocol: ' + url.proto)
    5.50 -        (hdr, data) = encode_data(args)
    5.51 -        if args and method == 'GET':
    5.52 -            url.query = data
    5.53 -            data = None
    5.54 -        if method == "POST" and url.path.endswith('/'):
    5.55 -            url.path = url.path[:-1]
    5.56 -
    5.57 -        self.headers = hdr
    5.58 -        self.data = data
    5.59 -        self.url = url
    5.60 -        self.method = method
    5.61 -
    5.62 -class XendClientProtocol:
    5.63 -    """Abstract class for xend clients.
    5.64 -    """
    5.65 -    def xendRequest(self, url, method, args=None):
    5.66 -        """Make a request to xend.
    5.67 -        Implement in a subclass.
    5.68 -
    5.69 -        @param url:    xend request url
    5.70 -        @param method: http method: POST or GET
    5.71 -        @param args:   request arguments (dict)
    5.72 -        """
    5.73 -        raise NotImplementedError()
    5.74 -
    5.75 -    def xendGet(self, url, args=None):
    5.76 -        """Make a xend request using HTTP GET.
    5.77 -        Requests using GET are usually 'safe' and may be repeated without
    5.78 -        nasty side-effects.
    5.79 -
    5.80 -        @param url:    xend request url
    5.81 -        @param data:   request arguments (dict)
    5.82 -        """
    5.83 -        return self.xendRequest(url, "GET", args)
    5.84 -
    5.85 -    def xendPost(self, url, args):
    5.86 -        """Make a xend request using HTTP POST.
    5.87 -        Requests using POST potentially cause side-effects, and should
    5.88 -        not be repeated unless you really want to repeat the side
    5.89 -        effect.
    5.90 -
    5.91 -        @param url:    xend request url
    5.92 -        @param args:   request arguments (dict)
    5.93 -        """
    5.94 -        return self.xendRequest(url, "POST", args)
    5.95 -
    5.96 -    def handleStatus(self, version, status, message):
    5.97 -        """Handle the status returned from the request.
    5.98 -        """
    5.99 -        status = int(status)
   5.100 -        if status in [ http.NO_CONTENT ]:
   5.101 -            return None
   5.102 -        if status not in [ http.OK, http.CREATED, http.ACCEPTED ]:
   5.103 -            return self.handleException(XendError(message))
   5.104 -        return 'ok'
   5.105 -
   5.106 -    def handleResponse(self, data):
   5.107 -        """Handle the data returned in response to the request.
   5.108 -        """
   5.109 -        if data is None: return None
   5.110 -        type = self.getHeader('Content-Type')
   5.111 -        if type != sxp.mime_type:
   5.112 -            return data
   5.113 -        try:
   5.114 -            pin = sxp.Parser()
   5.115 -            pin.input(data);
   5.116 -            pin.input_eof()
   5.117 -            val = pin.get_val()
   5.118 -        except sxp.ParseError, err:
   5.119 -            return self.handleException(err)
   5.120 -        if isinstance(val, types.ListType) and sxp.name(val) == 'xend.err':
   5.121 -            err = XendError(val[1])
   5.122 -            return self.handleException(err)
   5.123 -        return val
   5.124 -
   5.125 -    def handleException(self, err):
   5.126 -        """Handle an exception during the request.
   5.127 -        May be overridden in a subclass.
   5.128 -        """
   5.129 -        raise err
   5.130 -
   5.131 -    def getHeader(self, key):
   5.132 -        """Get a header from the response.
   5.133 -        Case is ignored in the key.
   5.134 -
   5.135 -        @param key: header key
   5.136 -        @return: header
   5.137 -        """
   5.138 -        raise NotImplementedError()
   5.139 -
   5.140 -class SynchXendClientProtocol(XendClientProtocol):
   5.141 -    """A synchronous xend client. This will make a request, wait for
   5.142 -    the reply and return the result.
   5.143 -    """
   5.144 -
   5.145 -    resp = None
   5.146 -
   5.147 -    def xendRequest(self, url, method, args=None):
   5.148 -        """Make a request to xend.
   5.149 -
   5.150 -        @param url:    xend request url
   5.151 -        @param method: http method: POST or GET
   5.152 -        @param args:   request arguments (dict)
   5.153 -        """
   5.154 -        self.request = XendRequest(url, method, args)
   5.155 -        conn = httplib.HTTPConnection(url.location())
   5.156 -        if DEBUG: conn.set_debuglevel(1)
   5.157 -        conn.request(method, url.fullpath(), self.request.data, self.request.headers)
   5.158 -        resp = conn.getresponse()
   5.159 -        self.resp = resp
   5.160 -        val = self.handleStatus(resp.version, resp.status, resp.reason)
   5.161 -        if val is None:
   5.162 -            data = None
   5.163 -        else:
   5.164 -            data = resp.read()
   5.165 -        conn.close()
   5.166 -        val = self.handleResponse(data)
   5.167 -        return val
   5.168 -
   5.169 -    def getHeader(self, key):
   5.170 -        return self.resp.getheader(key)
   5.171 -
   5.172 -
   5.173 -class AsynchXendClient(http.HTTPClient):
   5.174 -    """A subclass of twisted's HTTPClient to deal with a connection to xend.
   5.175 -    Makes the request when connected, and delegates handling responses etc.
   5.176 -    to its protocol (usually an AsynchXendClientProtocol instance).
   5.177 -    """
   5.178 -    def __init__(self, protocol, request):
   5.179 -        self.protocol = protocol
   5.180 -        self.request = request
   5.181 -
   5.182 -    def connectionMade(self):
   5.183 -        request = self.request
   5.184 -        url = self.request.url
   5.185 -        self.sendCommand(request.method, url.fullpath())
   5.186 -        self.sendHeader('Host', url.location())
   5.187 -        for (k, v) in request.headers.items():
   5.188 -            self.sendHeader(k, v)
   5.189 -        if request.data:
   5.190 -            self.sendHeader('Content-Length', len(request.data))
   5.191 -        self.endHeaders()
   5.192 -        if request.data:
   5.193 -            self.transport.write(request.data)
   5.194 -
   5.195 -    def handleStatus(self, version, status, message):
   5.196 -        return self.protocol.handleStatus(version, status, message)
   5.197 -
   5.198 -    def handleHeader(self, key, val):
   5.199 -        return self.protocol.handleHeader(key, val)
   5.200 -
   5.201 -    def handleResponse(self, data):
   5.202 -        return self.protocol.handleResponse(data)
   5.203 -
   5.204 -class AsynchXendClientProtocol(XendClientProtocol):
   5.205 -    """An asynchronous xend client. Uses twisted to connect to xend
   5.206 -    and make the request. It does not block waiting for the result,
   5.207 -    but sets up a deferred that is called when the result becomes available.
   5.208 -
   5.209 -    Uses AsynchXendClient to manage the connection.
   5.210 -    """
   5.211 -    def __init__(self):
   5.212 -        self.err = None
   5.213 -        self.headers = {}
   5.214 -
   5.215 -    def xendRequest(self, url, method, args=None):
   5.216 -        """Make a request to xend. The returned deferred is called when
   5.217 -        the result is available.
   5.218 -        
   5.219 -        @param url:    xend request url
   5.220 -        @param method: http method: POST or GET
   5.221 -        @param args:   request arguments (dict)
   5.222 -        @return: deferred
   5.223 -        """
   5.224 -        request = XendRequest(url, method, args)
   5.225 -        self.deferred = Deferred()
   5.226 -        clientCreator = ClientCreator(reactor, AsynchXendClient, self, request)
   5.227 -        clientCreator.connectTCP(url.host, url.port)
   5.228 -        return self.deferred
   5.229 -
   5.230 -    def callErrback(self, err):
   5.231 -        if not self.deferred.called:
   5.232 -            self.err = err
   5.233 -            self.deferred.errback(err)
   5.234 -        return err
   5.235 -
   5.236 -    def callCallback(self, val):
   5.237 -        if not self.deferred.called:
   5.238 -            self.deferred.callback(val)
   5.239 -        return val
   5.240 -
   5.241 -    def handleException(self, err):
   5.242 -        return self.callErrback(err)
   5.243 -
   5.244 -    def handleHeader(self, key, val):
   5.245 -        self.headers[key.lower()] = val
   5.246 -
   5.247 -    def getHeader(self, key):
   5.248 -        return self.headers.get(key.lower())
   5.249 -
   5.250 -    def handleResponse(self, data):
   5.251 -        if self.err: return self.err
   5.252 -        val = XendClientProtocol.handleResponse(self, data)
   5.253 -        if isinstance(val, Exception):
   5.254 -            self.callErrback(val)
   5.255 -        else:
   5.256 -            self.callCallback(val)
   5.257 -        return val
   5.258 -        
   5.259  class Xend:
   5.260      """Client interface to Xend.
   5.261      """
   5.262 @@ -586,9 +350,28 @@ class Xend:
   5.263                               {'op'      : 'inject',
   5.264                                'event'   : fileof(sxpr) })
   5.265  
   5.266 +def getAsynchXendClientProtocol():
   5.267 +    """Load AsynchXendClientProtocol on demand to avoid the cost.
   5.268 +    """
   5.269 +    global AsynchXendClientProtocol
   5.270 +    try:
   5.271 +       AsynchXendClientProtocol
   5.272 +    except:
   5.273 +        from XendAsynchProtocol import AsynchXendClientProtocol
   5.274 +    return AsynchXendClientProtocol
   5.275 +
   5.276 +def getAsynchServer():
   5.277 +    """Load AsynchXendClientProtocol and create an asynch xend client.
   5.278 +
   5.279 +    @return asynch Xend
   5.280 +    """
   5.281 +    getAsynchXendClientProtocol()
   5.282 +    return Xend(AsynchXendClientProtocol())
   5.283 +
   5.284  def xendmain(srv, asynch, fn, args):
   5.285      if asynch:
   5.286 -        client = AsynchXendClientProtocol()
   5.287 +        getAsynchXendClientProtocol()
   5.288 +        client = AsynchXendClientProtocol() 
   5.289      else:
   5.290          client = None
   5.291      xend = Xend(srv=srv, client=client)
   5.292 @@ -655,4 +438,3 @@ if __name__ == "__main__":
   5.293      main(sys.argv)
   5.294  else:    
   5.295      server = Xend()
   5.296 -    aserver = Xend( AsynchXendClientProtocol() )
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/tools/python/xen/xend/XendProtocol.py	Wed Sep 22 10:51:56 2004 +0000
     6.3 @@ -0,0 +1,156 @@
     6.4 +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com>
     6.5 +
     6.6 +import httplib
     6.7 +import types
     6.8 +
     6.9 +from encode import *
    6.10 +import sxp
    6.11 +
    6.12 +DEBUG = 0
    6.13 +
    6.14 +HTTP_OK                              = 200
    6.15 +HTTP_CREATED                         = 201
    6.16 +HTTP_ACCEPTED                        = 202
    6.17 +HTTP_NO_CONTENT                      = 204
    6.18 +
    6.19 +class XendError(RuntimeError):
    6.20 +    """Error class for 'expected errors' when talking to xend.
    6.21 +    """
    6.22 +    pass
    6.23 +
    6.24 +class XendRequest:
    6.25 +    """A request to xend.
    6.26 +    """
    6.27 +
    6.28 +    def __init__(self, url, method, args):
    6.29 +        """Create a request. Sets up the headers, argument data, and the
    6.30 +        url.
    6.31 +
    6.32 +        @param url:    the url to request
    6.33 +        @param method: request method, GET or POST
    6.34 +        @param args:   dict containing request args, if any
    6.35 +        """
    6.36 +        if url.proto != 'http':
    6.37 +            raise ValueError('Invalid protocol: ' + url.proto)
    6.38 +        (hdr, data) = encode_data(args)
    6.39 +        if args and method == 'GET':
    6.40 +            url.query = data
    6.41 +            data = None
    6.42 +        if method == "POST" and url.path.endswith('/'):
    6.43 +            url.path = url.path[:-1]
    6.44 +
    6.45 +        self.headers = hdr
    6.46 +        self.data = data
    6.47 +        self.url = url
    6.48 +        self.method = method
    6.49 +
    6.50 +class XendClientProtocol:
    6.51 +    """Abstract class for xend clients.
    6.52 +    """
    6.53 +    def xendRequest(self, url, method, args=None):
    6.54 +        """Make a request to xend.
    6.55 +        Implement in a subclass.
    6.56 +
    6.57 +        @param url:    xend request url
    6.58 +        @param method: http method: POST or GET
    6.59 +        @param args:   request arguments (dict)
    6.60 +        """
    6.61 +        raise NotImplementedError()
    6.62 +
    6.63 +    def xendGet(self, url, args=None):
    6.64 +        """Make a xend request using HTTP GET.
    6.65 +        Requests using GET are usually 'safe' and may be repeated without
    6.66 +        nasty side-effects.
    6.67 +
    6.68 +        @param url:    xend request url
    6.69 +        @param data:   request arguments (dict)
    6.70 +        """
    6.71 +        return self.xendRequest(url, "GET", args)
    6.72 +
    6.73 +    def xendPost(self, url, args):
    6.74 +        """Make a xend request using HTTP POST.
    6.75 +        Requests using POST potentially cause side-effects, and should
    6.76 +        not be repeated unless you really want to repeat the side
    6.77 +        effect.
    6.78 +
    6.79 +        @param url:    xend request url
    6.80 +        @param args:   request arguments (dict)
    6.81 +        """
    6.82 +        return self.xendRequest(url, "POST", args)
    6.83 +
    6.84 +    def handleStatus(self, version, status, message):
    6.85 +        """Handle the status returned from the request.
    6.86 +        """
    6.87 +        status = int(status)
    6.88 +        if status in [ HTTP_NO_CONTENT ]:
    6.89 +            return None
    6.90 +        if status not in [ HTTP_OK, HTTP_CREATED, HTTP_ACCEPTED ]:
    6.91 +            return self.handleException(XendError(message))
    6.92 +        return 'ok'
    6.93 +
    6.94 +    def handleResponse(self, data):
    6.95 +        """Handle the data returned in response to the request.
    6.96 +        """
    6.97 +        if data is None: return None
    6.98 +        type = self.getHeader('Content-Type')
    6.99 +        if type != sxp.mime_type:
   6.100 +            return data
   6.101 +        try:
   6.102 +            pin = sxp.Parser()
   6.103 +            pin.input(data);
   6.104 +            pin.input_eof()
   6.105 +            val = pin.get_val()
   6.106 +        except sxp.ParseError, err:
   6.107 +            return self.handleException(err)
   6.108 +        if isinstance(val, types.ListType) and sxp.name(val) == 'xend.err':
   6.109 +            err = XendError(val[1])
   6.110 +            return self.handleException(err)
   6.111 +        return val
   6.112 +
   6.113 +    def handleException(self, err):
   6.114 +        """Handle an exception during the request.
   6.115 +        May be overridden in a subclass.
   6.116 +        """
   6.117 +        raise err
   6.118 +
   6.119 +    def getHeader(self, key):
   6.120 +        """Get a header from the response.
   6.121 +        Case is ignored in the key.
   6.122 +
   6.123 +        @param key: header key
   6.124 +        @return: header
   6.125 +        """
   6.126 +        raise NotImplementedError()
   6.127 +
   6.128 +class SynchXendClientProtocol(XendClientProtocol):
   6.129 +    """A synchronous xend client. This will make a request, wait for
   6.130 +    the reply and return the result.
   6.131 +    """
   6.132 +
   6.133 +    resp = None
   6.134 +
   6.135 +    def xendRequest(self, url, method, args=None):
   6.136 +        """Make a request to xend.
   6.137 +
   6.138 +        @param url:    xend request url
   6.139 +        @param method: http method: POST or GET
   6.140 +        @param args:   request arguments (dict)
   6.141 +        """
   6.142 +        self.request = XendRequest(url, method, args)
   6.143 +        conn = httplib.HTTPConnection(url.location())
   6.144 +        if DEBUG: conn.set_debuglevel(1)
   6.145 +        conn.request(method, url.fullpath(), self.request.data, self.request.headers)
   6.146 +        resp = conn.getresponse()
   6.147 +        self.resp = resp
   6.148 +        val = self.handleStatus(resp.version, resp.status, resp.reason)
   6.149 +        if val is None:
   6.150 +            data = None
   6.151 +        else:
   6.152 +            data = resp.read()
   6.153 +        conn.close()
   6.154 +        val = self.handleResponse(data)
   6.155 +        return val
   6.156 +
   6.157 +    def getHeader(self, key):
   6.158 +        return self.resp.getheader(key)
   6.159 +