8.1.5 Compressed network data
The server in the next code list responds to a file name request with a stream compressor that writes a compressed version of the file to a socket that communicates with the client.
import zlib import logging import socketserver import binascii BLOCK_SIZE = 64 class ZlibRequestHandler(socketserver.BaseRequestHandler): logger = logging.getLogger('Server') def handle(self): compressor = zlib.compressobj(1) # Find out which file the client wants. filename = self.request.recv(1024).decode('utf-8') self.logger.debug('client asked for: %r',filename) # Send chunks of the file as they are compressed. with open(filename,'rb') as input: while True: block = input.read(BLOCK_SIZE) if not block: break self.logger.debug('RAW %r',block) compressed = compressor.compress(block) if compressed: self.logger.debug( 'SENDING %r', binascii.hexlify(compressed)) self.request.send(compressed) else: self.logger.debug('BUFFERING') # Send any data being buffered by the compressor. remaining = compressor.flush() while remaining: to_send = remaining[:BLOCK_SIZE] remaining = remaining[BLOCK_SIZE:] self.logger.debug('FLUSHING %r', binascii.hexlify(to_send)) self.request.send(to_send) return if __name__ == '__main__': import socket import threading from io import BytesIO logging.basicConfig( level=logging.DEBUG, format='%(name)s: %(message)s', ) logger = logging.getLogger('Client') # Set up a servre,running in a separate thread. address = ('localhost',0) # Let the kernel assign a port. server = socketserver.TCPServer(address,ZlibRequestHandler) ip,port = server.server_address # What port was assigned? t = threading.Thread(target=server.serve_forever) t.setDaemon(True) t.start() # Connect to the server as a client. logger.info('Contacting server on %s:%s',ip,port) s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((ip,port)) # Ask for a file. requested_file = 'lorem.txt' logger.debug('sending filename:%r',requested_file) len_sent = s.send(requested_file.encode('utf-8')) # Receive a response. buffer = BytesIO() decompressor = zlib.decompressobj() while True: response = s.recv(BLOCK_SIZE) if not response: break logger.debug('READ %r',binascii.hexlify(response)) # Include any uncomsumed data when # feeding the decompressor. to_decompress = decompressor.unconsumed_tail + response while to_decompress: decompressed = decompressor.decompress(to_decompress) if decompressed: logger.debug('DECOMPRESSED %r',decompressed) buffer.write(decompressed) # Look for unconsumed data due to buffer overflow. to_decompress = decompressor.unconsumed_tail else: logger.debug('BUFFERING') to_decompress = None # Deal with reamining inside the decompressor buffer. remainder = decompressor.flush() if remainder: logger.debug('FLUSHED %r',remainder) buffer.write(remainder) full_response = buffer.getvalue() lorem = open('lorem.txt','rb').read() logger.debug('response matches file contents: %s', full_response == lorem) # Clean up. s.close() server.socket.close()
We think we've divided this code list to show buffering behavior, where data is passed to compress() or decompress(), but no fully compressed or uncompressed output block is available.The client connects to the socket and requests a file.It then loops to accept the compressed data block.Since a block may not contain enough information to fully decompress, previously received reputation data will be combined with the new data and passed to the decompressor.When data is decompressed, it is appended to a buffer, and the contents of the file are compared at the end of the processing cycle.
Warning: The head of this server village has an obvious security risk.Do not run this program in an open Internet or environment where security issues may have a serious impact.