Python implementation much improved. Can send requests now. Fully interoperable with Obj-C implementation's test cases.
authorJens Alfke <jens@mooseyard.com>
Wed Jun 04 17:11:20 2008 -0700 (2008-06-04)
changeset 1384c2d38f924c
parent 12 710113961756
child 14 bb5faa9995d5
Python implementation much improved. Can send requests now. Fully interoperable with Obj-C implementation's test cases.
BLIP/BLIPTest.m
Python/BLIP.py
Python/BLIPConnectionTest.py
Python/BLIPListenerTest.py
     1.1 --- a/BLIP/BLIPTest.m	Tue Jun 03 22:24:21 2008 -0700
     1.2 +++ b/BLIP/BLIPTest.m	Wed Jun 04 17:11:20 2008 -0700
     1.3 @@ -308,7 +308,7 @@
     1.4          AssertEq(bytes[i],i % 256);
     1.5      
     1.6      AssertEqual([request valueOfProperty: @"Content-Type"], @"application/octet-stream");
     1.7 -    AssertEqual([request valueOfProperty: @"User-Agent"], @"BLIPConnectionTester");
     1.8 +    Assert([request valueOfProperty: @"User-Agent"] != nil);
     1.9      AssertEq([[request valueOfProperty: @"Size"] intValue], size);
    1.10  
    1.11      [request respondWithData: body contentType: request.contentType];
     2.1 --- a/Python/BLIP.py	Tue Jun 03 22:24:21 2008 -0700
     2.2 +++ b/Python/BLIP.py	Wed Jun 04 17:11:20 2008 -0700
     2.3 @@ -1,10 +1,9 @@
     2.4 -#!/usr/bin/env python
     2.5  # encoding: utf-8
     2.6  """
     2.7  BLIP.py
     2.8  
     2.9  Created by Jens Alfke on 2008-06-03.
    2.10 -Copyright (c) 2008 Jens Alfke. All rights reserved.
    2.11 +Copyright notice and BSD license at end of file.
    2.12  """
    2.13  
    2.14  import asynchat
    2.15 @@ -15,10 +14,17 @@
    2.16  import struct
    2.17  import sys
    2.18  import traceback
    2.19 -import unittest
    2.20  import zlib
    2.21  
    2.22  
    2.23 +# Connection status enumeration:
    2.24 +kDisconnected = -1
    2.25 +kClosed  = 0
    2.26 +kOpening = 1
    2.27 +kOpen    = 2
    2.28 +kClosing = 3
    2.29 +
    2.30 +
    2.31  # INTERNAL CONSTANTS -- NO TOUCHIES!
    2.32  
    2.33  kFrameMagicNumber   = 0x9B34F205
    2.34 @@ -47,57 +53,118 @@
    2.35      pass
    2.36  
    2.37  
    2.38 +### LISTENER AND CONNECTION CLASSES:
    2.39 +
    2.40 +
    2.41  class Listener (asyncore.dispatcher):
    2.42      "BLIP listener/server class"
    2.43      
    2.44 -    def __init__(self, port):
    2.45 +    def __init__(self, port, sslKeyFile=None, sslCertFile=None):
    2.46          "Create a listener on a port"
    2.47          asyncore.dispatcher.__init__(self)
    2.48          self.onConnected = self.onRequest = None
    2.49          self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
    2.50          self.bind( ('',port) )
    2.51          self.listen(5)
    2.52 +        self.sslKeyFile=sslKeyFile
    2.53 +        self.sslCertFile=sslCertFile
    2.54          log.info("Listening on port %u", port)
    2.55      
    2.56      def handle_accept( self ):
    2.57 -        client,address = self.accept()
    2.58 -        conn = Connection(address,client)
    2.59 +        socket,address = self.accept()
    2.60 +        if self.sslKeyFile:
    2.61 +            socket.ssl(socket,self.sslKeyFile,self.sslCertFile)
    2.62 +        conn = Connection(address, sock=socket, listener=self)
    2.63          conn.onRequest = self.onRequest
    2.64          if self.onConnected:
    2.65              self.onConnected(conn)
    2.66  
    2.67 +    def handle_error(self):
    2.68 +        (typ,val,trace) = sys.exc_info()
    2.69 +        log.error("Listener caught: %s %s\n%s", typ,val,traceback.format_exc())
    2.70 +        self.close()
    2.71 +    
    2.72 +
    2.73  
    2.74  class Connection (asynchat.async_chat):
    2.75 -    def __init__( self, address, conn=None ):
    2.76 +    def __init__( self, address, sock=None, listener=None, ssl=None ):
    2.77          "Opens a connection with the given address. If a connection/socket object is provided it'll use that,"
    2.78          "otherwise it'll open a new outgoing socket."
    2.79 -        asynchat.async_chat.__init__(self,conn)
    2.80 -        self.address = address
    2.81 -        if conn:
    2.82 +        if sock:
    2.83 +            asynchat.async_chat.__init__(self,sock)
    2.84              log.info("Accepted connection from %s",address)
    2.85 +            self.status = kOpen
    2.86          else:
    2.87 +            asynchat.async_chat.__init__(self)
    2.88              log.info("Opening connection to %s",address)
    2.89              self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
    2.90 +            self.status = kOpening
    2.91 +            if ssl:
    2.92 +                ssl(self.socket)
    2.93              self.connect(address)
    2.94 +        self.address = address
    2.95 +        self.listener = listener
    2.96          self.onRequest = None
    2.97          self.pendingRequests = {}
    2.98          self.pendingResponses = {}
    2.99          self.outBox = []
   2.100          self.inMessage = None
   2.101 -        self.inNumRequests = 0
   2.102 +        self.inNumRequests = self.outNumRequests = 0
   2.103          self._endOfFrame()
   2.104      
   2.105 -    #def handle_error(self,x):
   2.106 -    #    log.error("Uncaught exception: %s",x)
   2.107 -    #    self.close()
   2.108 +    def close(self):
   2.109 +        if self.status > kClosed:
   2.110 +            self.status = kClosing
   2.111 +            log.info("Connection closing...")
   2.112 +        asynchat.async_chat.close(self)
   2.113      
   2.114 -    def _fatal(self, error):
   2.115 -        log.error("Fatal BLIP connection error: %s",error)
   2.116 +    def handle_connect(self):
   2.117 +        log.info("Connection open!")
   2.118 +        self.status = kOpen
   2.119 +    
   2.120 +    def handle_error(self):
   2.121 +        (typ,val,trace) = sys.exc_info()
   2.122 +        log.error("Connection caught: %s %s\n%s", typ,val,traceback.format_exc())
   2.123 +        self.discard_buffers()
   2.124 +        self.status = kDisconnected
   2.125          self.close()
   2.126      
   2.127 +    def handle_close(self):
   2.128 +        log.info("Connection closed!")
   2.129 +        self.pendingRequests = self.pendingResponses = None
   2.130 +        self.outBox = None
   2.131 +        if self.status == kClosing:
   2.132 +            self.status = kClosed
   2.133 +        else:
   2.134 +            self.status = kDisconnected
   2.135 +        asynchat.async_chat.handle_close(self)
   2.136 +        
   2.137      
   2.138      ### SENDING:
   2.139      
   2.140 +    @property
   2.141 +    def canSend(self):
   2.142 +        return self.status==kOpening or self.status==kOpen
   2.143 +    
   2.144 +    def _sendMessage(self, msg):
   2.145 +        if self.canSend:
   2.146 +            self._outQueueMessage(msg,True)
   2.147 +            return True
   2.148 +        else:
   2.149 +            return False
   2.150 +    
   2.151 +    def _sendRequest(self, req):
   2.152 +        if self.canSend:
   2.153 +            requestNo = req.requestNo = self.outNumRequests = self.outNumRequests + 1
   2.154 +            response = req.response
   2.155 +            if response:
   2.156 +                response.requestNo = requestNo
   2.157 +                self.pendingResponses[requestNo] = response
   2.158 +                log.debug("pendingResponses[%i] := %s",requestNo,response)
   2.159 +            return self._sendMessage(req)
   2.160 +        else:
   2.161 +            return False
   2.162 +    
   2.163      def _outQueueMessage(self, msg,isNew=True):
   2.164          n = len(self.outBox)
   2.165          index = n
   2.166 @@ -116,7 +183,7 @@
   2.167          
   2.168          self.outBox.insert(index,msg)
   2.169          if isNew:
   2.170 -            log.info("Queuing outgoing message at index %i",index)
   2.171 +            log.info("Queuing %s at index %i",msg,index)
   2.172              if n==0:
   2.173                  self._sendNextFrame()
   2.174          else:
   2.175 @@ -144,7 +211,7 @@
   2.176                  self.inHeader = data
   2.177              else:
   2.178                  self.inHeader += data
   2.179 -        else:
   2.180 +        elif self.inMessage:
   2.181              self.inMessage._receivedData(data)
   2.182      
   2.183      def found_terminator(self):
   2.184 @@ -152,8 +219,8 @@
   2.185              # Got a header:
   2.186              (magic, requestNo, flags, frameLen) = struct.unpack(kFrameHeaderFormat,self.inHeader)
   2.187              self.inHeader = None
   2.188 -            if magic!=kFrameMagicNumber: self._fatal("Incorrect frame magic number %x" %magic)
   2.189 -            if frameLen < kFrameHeaderSize: self._fatal("Invalid frame length %u" %frameLen)
   2.190 +            if magic!=kFrameMagicNumber: raise ConnectionException, "Incorrect frame magic number %x" %magic
   2.191 +            if frameLen < kFrameHeaderSize: raise ConnectionException,"Invalid frame length %u" %frameLen
   2.192              frameLen -= kFrameHeaderSize
   2.193              log.debug("Incoming frame: type=%i, number=%i, flags=%x, length=%i",
   2.194                          (flags&kMsgFlag_TypeMask),requestNo,flags,frameLen)
   2.195 @@ -196,14 +263,14 @@
   2.196          self.set_terminator(kFrameHeaderSize) # wait for binary header
   2.197          if msg:
   2.198              log.debug("End of frame of %s",msg)
   2.199 -            if not msg.moreComing:
   2.200 +            if not msg._moreComing:
   2.201                  self._receivedMessage(msg)
   2.202      
   2.203      def _receivedMessage(self, msg):
   2.204          log.info("Received: %s",msg)
   2.205          # Remove from pending:
   2.206          if msg.isResponse:
   2.207 -            del self.pendingReplies[msg.requestNo]
   2.208 +            del self.pendingResponses[msg.requestNo]
   2.209          else:
   2.210              del self.pendingRequests[msg.requestNo]
   2.211          # Decode:
   2.212 @@ -216,16 +283,17 @@
   2.213              #FIX: Send an error reply
   2.214  
   2.215  
   2.216 -### MESSAGES:
   2.217 +### MESSAGE CLASSES:
   2.218  
   2.219  
   2.220  class Message (object):
   2.221      "Abstract superclass of all request/response objects"
   2.222      
   2.223 -    def __init__(self, connection, properties=None, body=None):
   2.224 +    def __init__(self, connection, body=None, properties=None):
   2.225          self.connection = connection
   2.226 +        self.body = body
   2.227          self.properties = properties or {}
   2.228 -        self.body = body
   2.229 +        self.requestNo = None
   2.230      
   2.231      @property
   2.232      def flags(self):
   2.233 @@ -236,15 +304,17 @@
   2.234          if self.urgent:     flags |= kMsgFlag_Urgent
   2.235          if self.compressed: flags |= kMsgFlag_Compressed
   2.236          if self.noReply:    flags |= kMsgFlag_NoReply
   2.237 -        if self.moreComing: flags |= kMsgFlag_MoreComing
   2.238 +        if self._moreComing:flags |= kMsgFlag_MoreComing
   2.239          return flags
   2.240      
   2.241      def __str__(self):
   2.242 -        s = "%s[#%i" %(type(self).__name__,self.requestNo)
   2.243 +        s = "%s[" %(type(self).__name__)
   2.244 +        if self.requestNo != None:
   2.245 +            s += "#%i" %self.requestNo
   2.246          if self.urgent:     s += " URG"
   2.247          if self.compressed: s += " CMP"
   2.248          if self.noReply:    s += " NOR"
   2.249 -        if self.moreComing: s += " MOR"
   2.250 +        if self._moreComing:s += " MOR"
   2.251          if self.body:       s += " %i bytes" %len(self.body)
   2.252          return s+"]"
   2.253      
   2.254 @@ -253,12 +323,12 @@
   2.255          if len(self.properties): s += repr(self.properties)
   2.256          return s
   2.257      
   2.258 -    @property 
   2.259 +    @property
   2.260      def isResponse(self):
   2.261          "Is this message a response?"
   2.262          return False
   2.263      
   2.264 -    @property 
   2.265 +    @property
   2.266      def contentType(self):
   2.267          return self.properties.get('Content-Type')
   2.268      
   2.269 @@ -278,17 +348,19 @@
   2.270          self.urgent     = (flags & kMsgFlag_Urgent) != 0
   2.271          self.compressed = (flags & kMsgFlag_Compressed) != 0
   2.272          self.noReply    = (flags & kMsgFlag_NoReply) != 0
   2.273 -        self.moreComing = (flags & kMsgFlag_MoreComing) != 0
   2.274 +        self._moreComing= (flags & kMsgFlag_MoreComing) != 0
   2.275          self.frames     = []
   2.276      
   2.277      def _beginFrame(self, flags):
   2.278 -        if (flags & kMsgFlag_MoreComing)==0:
   2.279 -            self.moreComing = False
   2.280 +        """Received a frame header."""
   2.281 +        self._moreComing = (flags & kMsgFlag_MoreComing)!=0
   2.282      
   2.283      def _receivedData(self, data):
   2.284 +        """Received data from a frame."""
   2.285          self.frames.append(data)
   2.286      
   2.287      def _finished(self):
   2.288 +        """The entire message has been received; now decode it."""
   2.289          encoded = "".join(self.frames)
   2.290          self.frames = None
   2.291          
   2.292 @@ -327,47 +399,54 @@
   2.293                     '\x09' : "Error-Domain"}
   2.294  
   2.295  
   2.296 -
   2.297  class OutgoingMessage (Message):
   2.298      "Abstract superclass of outgoing requests/responses."
   2.299      
   2.300 -    def __init__(self, connection, properties=None, body=None):
   2.301 -        Message.__init__(self,connection,properties,body)
   2.302 +    def __init__(self, connection, body=None, properties=None):
   2.303 +        Message.__init__(self,connection,body,properties)
   2.304          self.urgent = self.compressed = self.noReply = False
   2.305 -        self.moreComing = True
   2.306 +        self._moreComing = True
   2.307      
   2.308      def __setitem__(self, key,val):
   2.309          self.properties[key] = val
   2.310      def __delitem__(self, key):
   2.311          del self.properties[key]
   2.312      
   2.313 -    def send(self):
   2.314 -        "Sends this message."
   2.315 -        log.info("Sending %s",self)
   2.316 +    @property
   2.317 +    def sent(self):
   2.318 +        return 'encoded' in self.__dict__
   2.319 +    
   2.320 +    def _encode(self):
   2.321 +        "Generates the message's encoded form, prior to sending it."
   2.322          out = StringIO()
   2.323          for (key,value) in self.properties.iteritems():
   2.324 -            def _writePropString(str):
   2.325 -                out.write(str)    #FIX: Abbreviate
   2.326 +            def _writePropString(s):
   2.327 +                out.write(str(s))    #FIX: Abbreviate
   2.328                  out.write('\000')
   2.329              _writePropString(key)
   2.330              _writePropString(value)
   2.331 -        self.encoded = struct.pack('!H',out.tell()) + out.getvalue()
   2.332 -        out.close()
   2.333 +        propertiesSize = out.tell()
   2.334 +        assert propertiesSize<65536     #FIX: Return an error instead
   2.335          
   2.336          body = self.body
   2.337          if self.compressed:
   2.338 -            body = zlib.compress(body,5)
   2.339 -        self.encoded += body
   2.340 +            z = zlib.compressobj(6,zlib.DEFLATED,31)   # window size of 31 needed for gzip format
   2.341 +            out.write(z.compress(body))
   2.342 +            body = z.flush()
   2.343 +        out.write(body)
   2.344 +        
   2.345 +        self.encoded = struct.pack('!H',propertiesSize) + out.getvalue()
   2.346 +        out.close()
   2.347          log.debug("Encoded %s into %u bytes", self,len(self.encoded))
   2.348 -        
   2.349          self.bytesSent = 0
   2.350 -        self.connection._outQueueMessage(self)
   2.351      
   2.352      def _sendNextFrame(self, conn,maxLen):
   2.353          pos = self.bytesSent
   2.354          payload = self.encoded[pos:pos+maxLen]
   2.355          pos += len(payload)
   2.356 -        self.moreComing = (pos < len(self.encoded))
   2.357 +        self._moreComing = (pos < len(self.encoded))
   2.358 +        if not self._moreComing:
   2.359 +            self.encoded = None
   2.360          log.debug("Sending frame of %s; bytes %i--%i", self,pos-len(payload),pos)
   2.361          
   2.362          conn.push( struct.pack(kFrameHeaderFormat, kFrameMagicNumber,
   2.363 @@ -377,13 +456,15 @@
   2.364          conn.push( payload )
   2.365          
   2.366          self.bytesSent = pos
   2.367 -        return self.moreComing
   2.368 +        return self._moreComing
   2.369  
   2.370  
   2.371  class Request (object):
   2.372      @property
   2.373      def response(self):
   2.374          "The response object for this request."
   2.375 +        if self.noReply:
   2.376 +            return None
   2.377          r = self.__dict__.get('_response')
   2.378          if r==None:
   2.379              r = self._response = self._createResponse()
   2.380 @@ -391,7 +472,7 @@
   2.381  
   2.382  
   2.383  class Response (Message):
   2.384 -    def __init__(self, request):
   2.385 +    def _setRequest(self, request):
   2.386          assert not request.noReply
   2.387          self.request = request
   2.388          self.requestNo = request.requestNo
   2.389 @@ -402,19 +483,24 @@
   2.390          return True
   2.391  
   2.392  
   2.393 -
   2.394  class IncomingRequest (IncomingMessage, Request):
   2.395      def _createResponse(self):
   2.396          return OutgoingResponse(self)
   2.397  
   2.398 +
   2.399  class OutgoingRequest (OutgoingMessage, Request):
   2.400      def _createResponse(self):
   2.401          return IncomingResponse(self)
   2.402 +    
   2.403 +    def send(self):
   2.404 +        self._encode()
   2.405 +        return self.connection._sendRequest(self) and self.response
   2.406 +
   2.407  
   2.408  class IncomingResponse (IncomingMessage, Response):
   2.409      def __init__(self, request):
   2.410 -        IncomingMessage.__init__(self,request.connection,request.requestNo,0)
   2.411 -        Response.__init__(self,request)
   2.412 +        IncomingMessage.__init__(self,request.connection,None,0)
   2.413 +        self._setRequest(request)
   2.414          self.onComplete = None
   2.415      
   2.416      def _finished(self):
   2.417 @@ -424,40 +510,36 @@
   2.418                  self.onComplete(self)
   2.419              except Exception, x:
   2.420                  log.error("Exception dispatching response: %s", traceback.format_exc())
   2.421 -            
   2.422 +
   2.423 +
   2.424  class OutgoingResponse (OutgoingMessage, Response):
   2.425      def __init__(self, request):
   2.426          OutgoingMessage.__init__(self,request.connection)
   2.427 -        Response.__init__(self,request)
   2.428 +        self._setRequest(request)
   2.429 +    
   2.430 +    def send(self):
   2.431 +        self._encode()
   2.432 +        return self.connection._sendMessage(self)
   2.433  
   2.434  
   2.435 -### UNIT TESTS:
   2.436 -
   2.437 -
   2.438 -class BLIPTests(unittest.TestCase):
   2.439 -    def setUp(self):
   2.440 -        def handleRequest(request):
   2.441 -            logging.info("Got request!: %r",request)
   2.442 -            body = request.body
   2.443 -            assert len(body)<32768
   2.444 -            assert request.contentType == 'application/octet-stream'
   2.445 -            assert int(request['Size']) == len(body)
   2.446 -            assert request['User-Agent'] == 'BLIPConnectionTester'
   2.447 -            for i in xrange(0,len(request.body)):
   2.448 -                assert ord(body[i]) == i%256
   2.449 -            
   2.450 -            response = request.response
   2.451 -            response.body = request.body
   2.452 -            response['Content-Type'] = request.contentType
   2.453 -            response.send()
   2.454 -        
   2.455 -        listener = Listener(46353)
   2.456 -        listener.onRequest = handleRequest
   2.457 -    
   2.458 -    def testListener(self):
   2.459 -        logging.info("Waiting...")
   2.460 -        asyncore.loop()
   2.461 -
   2.462 -if __name__ == '__main__':
   2.463 -    logging.basicConfig(level=logging.INFO)
   2.464 -    unittest.main()
   2.465 \ No newline at end of file
   2.466 +"""
   2.467 + Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   2.468 + 
   2.469 + Redistribution and use in source and binary forms, with or without modification, are permitted
   2.470 + provided that the following conditions are met:
   2.471 + 
   2.472 + * Redistributions of source code must retain the above copyright notice, this list of conditions
   2.473 + and the following disclaimer.
   2.474 + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   2.475 + and the following disclaimer in the documentation and/or other materials provided with the
   2.476 + distribution.
   2.477 + 
   2.478 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   2.479 + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   2.480 + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   2.481 + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   2.482 + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   2.483 +  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   2.484 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   2.485 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   2.486 +"""
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/Python/BLIPConnectionTest.py	Wed Jun 04 17:11:20 2008 -0700
     3.3 @@ -0,0 +1,72 @@
     3.4 +#!/usr/bin/env python
     3.5 +# encoding: utf-8
     3.6 +"""
     3.7 +BLIPConnectionTest.py
     3.8 +
     3.9 +Created by Jens Alfke on 2008-06-04.
    3.10 +This source file is test/example code, and is in the public domain.
    3.11 +"""
    3.12 +
    3.13 +from BLIP import Connection, OutgoingRequest, kOpening
    3.14 +
    3.15 +import asyncore
    3.16 +from cStringIO import StringIO
    3.17 +from datetime import datetime
    3.18 +import logging
    3.19 +import random
    3.20 +import unittest
    3.21 +
    3.22 +
    3.23 +kSendInterval = 2.0
    3.24 +
    3.25 +def randbool():
    3.26 +    return random.randint(0,1) == 1
    3.27 +
    3.28 +
    3.29 +class BLIPConnectionTest(unittest.TestCase):
    3.30 +
    3.31 +    def setUp(self):
    3.32 +        self.connection = Connection( ('localhost',46353) )
    3.33 +   
    3.34 +    def sendRequest(self):
    3.35 +        size = random.randint(0,32767)
    3.36 +        io = StringIO()
    3.37 +        for i in xrange(0,size):
    3.38 +            io.write( chr(i % 256) )
    3.39 +        body = io.getvalue()
    3.40 +        io.close
    3.41 +    
    3.42 +        req = OutgoingRequest(self.connection, body,{'Content-Type': 'application/octet-stream',
    3.43 +                                                     'User-Agent':  'PyBLIP',
    3.44 +                                                     'Date': datetime.now(),
    3.45 +                                                     'Size': size})
    3.46 +        req.compressed = randbool()
    3.47 +        req.urgent     = randbool()
    3.48 +        req.response.onComplete = self.gotResponse
    3.49 +        return req.send()
    3.50 +    
    3.51 +    def gotResponse(self, response):
    3.52 +        logging.info("Got response!: %s",response)
    3.53 +        request = response.request
    3.54 +        assert response.body == request.body
    3.55 +
    3.56 +    def testClient(self):
    3.57 +        lastReqTime = None
    3.58 +        nRequests = 0
    3.59 +        while nRequests < 10:
    3.60 +            asyncore.loop(timeout=kSendInterval,count=1)
    3.61 +            
    3.62 +            now = datetime.now()
    3.63 +            if self.connection.status!=kOpening and not lastReqTime or (now-lastReqTime).seconds >= kSendInterval:
    3.64 +                lastReqTime = now
    3.65 +                if not self.sendRequest():
    3.66 +                    logging.warn("Couldn't send request (connection is probably closed)")
    3.67 +                    break;
    3.68 +                nRequests += 1
    3.69 +    
    3.70 +    def tearDown(self):
    3.71 +        self.connection.close()
    3.72 +
    3.73 +if __name__ == '__main__':
    3.74 +    logging.basicConfig(level=logging.INFO)
    3.75 +    unittest.main()
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/Python/BLIPListenerTest.py	Wed Jun 04 17:11:20 2008 -0700
     4.3 @@ -0,0 +1,46 @@
     4.4 +#!/usr/bin/env python
     4.5 +# encoding: utf-8
     4.6 +"""
     4.7 +BLIPListenerTest.py
     4.8 +
     4.9 +Created by Jens Alfke on 2008-06-04.
    4.10 +This source file is test/example code, and is in the public domain.
    4.11 +"""
    4.12 +
    4.13 +from BLIP import Listener
    4.14 +
    4.15 +import asyncore
    4.16 +import logging
    4.17 +import unittest
    4.18 +
    4.19 +
    4.20 +class BLIPListenerTest(unittest.TestCase):
    4.21 +    
    4.22 +    def testListener(self):
    4.23 +        def handleRequest(request):
    4.24 +            logging.info("Got request!: %r",request)
    4.25 +            body = request.body
    4.26 +            assert len(body)<32768
    4.27 +            assert request.contentType == 'application/octet-stream'
    4.28 +            assert int(request['Size']) == len(body)
    4.29 +            assert request['User-Agent'] != None
    4.30 +            for i in xrange(0,len(request.body)):
    4.31 +                assert ord(body[i]) == i%256
    4.32 +            
    4.33 +            response = request.response
    4.34 +            response.body = request.body
    4.35 +            response['Content-Type'] = request.contentType
    4.36 +            response.send()
    4.37 +        
    4.38 +        listener = Listener(46353)
    4.39 +        listener.onRequest = handleRequest
    4.40 +        logging.info("Listener is waiting...")
    4.41 +        
    4.42 +        try:
    4.43 +            asyncore.loop()
    4.44 +        except KeyboardInterrupt:
    4.45 +            logging.info("KeyboardInterrupt")
    4.46 +
    4.47 +if __name__ == '__main__':
    4.48 +    logging.basicConfig(level=logging.INFO)
    4.49 +    unittest.main()