mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	
		
			
	
	
		
			396 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			396 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | """Async sockets, build on top of Sam Rushing's excellent async library""" | ||
|  | 
 | ||
|  | import asyncore | ||
|  | import socket | ||
|  | from socket import AF_INET, SOCK_STREAM | ||
|  | import string | ||
|  | import cStringIO | ||
|  | import mimetools | ||
|  | import httplib | ||
|  | 
 | ||
|  | 
 | ||
|  | __version__ = "0.9" | ||
|  | __author__ = "jvr" | ||
|  | 
 | ||
|  | BUFSIZE = 512 | ||
|  | 
 | ||
|  | VERBOSE = 1 | ||
|  | 
 | ||
|  | class Server(asyncore.dispatcher): | ||
|  | 	 | ||
|  | 	"""Generic asynchronous server class""" | ||
|  | 	 | ||
|  | 	def __init__(self, port, handler_class, backlog=1, host=""): | ||
|  | 		"""arguments:
 | ||
|  | 		- port: the port to listen to | ||
|  | 		- handler_class: class to handle requests | ||
|  | 		- backlog: backlog queue size (optional) (don't fully understand, see socket docs) | ||
|  | 		- host: host name (optional: can be empty to use default host name) | ||
|  | 		"""
 | ||
|  | 		if VERBOSE: | ||
|  | 			print "Starting", self.__class__.__name__ | ||
|  | 		self.handler_class = handler_class | ||
|  | 		asyncore.dispatcher.__init__(self) | ||
|  | 		self.create_socket(socket.AF_INET, socket.SOCK_STREAM) | ||
|  | 		self.bind((host, port)) | ||
|  | 		self.listen(backlog) | ||
|  | 	 | ||
|  | 	def handle_accept(self): | ||
|  | 		conn, addr = self.accept() | ||
|  | 		if VERBOSE: | ||
|  | 			print 'Incoming Connection from %s:%d' % addr | ||
|  | 		self.handler_class(conn) | ||
|  | 
 | ||
|  | 
 | ||
|  | class ProxyServer(Server): | ||
|  | 	 | ||
|  | 	"""Generic proxy server class""" | ||
|  | 	 | ||
|  | 	def __init__(self, port, handler_class, proxyaddr=None, closepartners=0): | ||
|  | 		"""arguments:
 | ||
|  | 		- port: the port to listen to | ||
|  | 		- handler_class: proxy class to handle requests | ||
|  | 		- proxyaddr: a tuple containing the address and  | ||
|  | 		  port of a remote host to connect to (optional) | ||
|  | 		- closepartners: boolean, specifies whether we should close | ||
|  | 		  all proxy connections or not (optional). http seems to *not* | ||
|  | 		  want this, but telnet does... | ||
|  | 		"""
 | ||
|  | 		Server.__init__(self, port, handler_class, 1, "") | ||
|  | 		self.proxyaddr = proxyaddr | ||
|  | 		self.closepartners = closepartners | ||
|  | 	 | ||
|  | 	def handle_accept(self): | ||
|  | 		conn, addr = self.accept() | ||
|  | 		if VERBOSE: | ||
|  | 			print 'Incoming Connection from %s:%d' % addr | ||
|  | 		self.handler_class(conn, self.proxyaddr, closepartner=self.closepartners) | ||
|  | 
 | ||
|  | 
 | ||
|  | class Connection(asyncore.dispatcher): | ||
|  | 	 | ||
|  | 	"""Generic connection class""" | ||
|  | 	 | ||
|  | 	def __init__(self, sock_or_address=None, readfunc=None, terminator=None): | ||
|  | 		"""arguments: 
 | ||
|  | 		- sock_or_address: either a socket, or a tuple containing the name  | ||
|  | 		and port number of a remote host | ||
|  | 		- readfunc: callback function (optional). Will be called whenever | ||
|  | 		  there is some data available, or when an appropraite terminator | ||
|  | 		  is found. | ||
|  | 		- terminator: string which specifies when a read is complete (optional)"""
 | ||
|  | 		self._out_buffer = "" | ||
|  | 		self._in_buffer = "" | ||
|  | 		self.readfunc = readfunc | ||
|  | 		self.terminator = terminator | ||
|  | 		asyncore.dispatcher.__init__(self) | ||
|  | 		if hasattr(sock_or_address, "fileno"): | ||
|  | 			self.set_socket(sock_or_address) | ||
|  | 		else: | ||
|  | 			sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
|  | 			sock.setblocking(0) | ||
|  | 			self.set_socket(sock) | ||
|  | 			if sock_or_address: | ||
|  | 				self.connect(sock_or_address) | ||
|  | 	 | ||
|  | 	# public methods | ||
|  | 	def send(self, data): | ||
|  | 		self._out_buffer = self._out_buffer + data | ||
|  | 	 | ||
|  | 	def recv(self, bytes=-1): | ||
|  | 		if bytes == -1: | ||
|  | 			bytes = len(self._in_buffer) | ||
|  | 		data = self._in_buffer[:bytes] | ||
|  | 		self._in_buffer = self._in_buffer[bytes:] | ||
|  | 		return data | ||
|  | 	 | ||
|  | 	def set_terminator(self, terminator): | ||
|  | 		self.terminator = terminator | ||
|  | 	 | ||
|  | 	# override this if you want to control the incoming data stream  | ||
|  | 	def handle_incoming_data(self, data): | ||
|  | 		if self.readfunc: | ||
|  | 			if self.terminator: | ||
|  | 				self._in_buffer = self._in_buffer + data | ||
|  | 				pos = string.find(self._in_buffer, self.terminator) | ||
|  | 				if pos < 0: | ||
|  | 					return | ||
|  | 				data = self._in_buffer[:pos] | ||
|  | 				self._in_buffer = self._in_buffer[pos + len(self.terminator):] | ||
|  | 				self.readfunc(data) | ||
|  | 			else: | ||
|  | 				self.readfunc(self._in_buffer + data) | ||
|  | 				self._in_buffer = "" | ||
|  | 		else: | ||
|  | 			self._in_buffer = self._in_buffer + data | ||
|  | 	 | ||
|  | 	# internal muck | ||
|  | 	def handle_read(self): | ||
|  | 		data = asyncore.dispatcher.recv(self, BUFSIZE) | ||
|  | 		if data: | ||
|  | 			if VERBOSE > 2: | ||
|  | 				print "incoming ->", "%x" % id(self), `data` | ||
|  | 			self.handle_incoming_data(data) | ||
|  | 	 | ||
|  | 	def handle_write(self): | ||
|  | 		if self._out_buffer: | ||
|  | 			sent = self.socket.send(self._out_buffer[:BUFSIZE]) | ||
|  | 			if VERBOSE > 2: | ||
|  | 				print "outgoing ->", "%x" % id(self), `self._out_buffer[:sent]` | ||
|  | 			self._out_buffer = self._out_buffer[sent:] | ||
|  | 	 | ||
|  | 	def close(self): | ||
|  | 		if self.readfunc and self._in_buffer: | ||
|  | 			self.readfunc(self._in_buffer) | ||
|  | 			self._in_buffer = "" | ||
|  | 		#elif VERBOSE > 1 and self._in_buffer: | ||
|  | 		#	print "--- there is unread data:", `self._in_buffer` | ||
|  | 		asyncore.dispatcher.close(self) | ||
|  | 	 | ||
|  | 	def handle_close(self): | ||
|  | 		self.close() | ||
|  | 	 | ||
|  | 	def handle_connect(self): | ||
|  | 		pass | ||
|  | 
 | ||
|  | 
 | ||
|  | class ConnectionUI: | ||
|  | 	 | ||
|  | 	"""Glue to let a connection tell things to the UI in a standardized way.
 | ||
|  | 	 | ||
|  | 	The protocoll defines four functions, which the connection will call: | ||
|  | 	 | ||
|  | 		def settotal(int total): gets called when the connection knows the data size | ||
|  | 		def setcurrent(int current): gets called when some new data has arrived | ||
|  | 		def done(): gets called when the transaction is complete | ||
|  | 		def error(type, value, tb): gets called wheneven an error occured | ||
|  | 	"""
 | ||
|  | 	 | ||
|  | 	def __init__(self, settotal_func, setcurrent_func, done_func, error_func): | ||
|  | 		self.settotal = settotal_func | ||
|  | 		self.setcurrent = setcurrent_func | ||
|  | 		self.done = done_func | ||
|  | 		self.error = error_func | ||
|  | 
 | ||
|  | 
 | ||
|  | class HTTPError(socket.error): pass | ||
|  | 
 | ||
|  | 
 | ||
|  | class HTTPClient(Connection, httplib.HTTP): | ||
|  | 	 | ||
|  | 	"""Asynchronous HTTP connection""" | ||
|  | 	 | ||
|  | 	def __init__(self, (host, port), datahandler, ui=None): | ||
|  | 		Connection.__init__(self, (host, port)) | ||
|  | 		self.datahandler = datahandler | ||
|  | 		self.ui = ui | ||
|  | 		self.buf = "" | ||
|  | 		self.doneheaders = 0 | ||
|  | 		self.done = 0 | ||
|  | 		self.headers = None | ||
|  | 		self.length = None | ||
|  | 		self.pos = 0 | ||
|  | 	 | ||
|  | 	def getreply(self): | ||
|  | 		raise TypeError, "getreply() is not supported in async HTTP connection" | ||
|  | 	 | ||
|  | 	def handle_incoming_data(self, data): | ||
|  | 		assert not self.done | ||
|  | 		if not self.doneheaders: | ||
|  | 			self.buf = self.buf + data | ||
|  | 			pos = string.find(self.buf, "\r\n\r\n") | ||
|  | 			if pos >= 0: | ||
|  | 				self.handle_reply(self.buf[:pos+4]) | ||
|  | 				length = self.headers.getheader("Content-Length") | ||
|  | 				if length is not None: | ||
|  | 					self.length = int(length) | ||
|  | 					if self.ui is not None: | ||
|  | 						self.ui.settotal(self.length) | ||
|  | 				else: | ||
|  | 					self.length = -1 | ||
|  | 				self.doneheaders = 1 | ||
|  | 				self.handle_data(self.buf[pos+4:]) | ||
|  | 				self.buf = "" | ||
|  | 		else: | ||
|  | 			self.handle_data(data) | ||
|  | 	 | ||
|  | 	def handle_reply(self, data): | ||
|  | 		f = cStringIO.StringIO(data) | ||
|  | 		ver, code, msg = string.split(f.readline(), None, 2) | ||
|  | 		code = int(code) | ||
|  | 		msg = string.strip(msg) | ||
|  | 		if code <> 200: | ||
|  | 			# Hm, this is what *I* need, but probably not correct... | ||
|  | 			raise HTTPError, (code, msg) | ||
|  | 		self.headers = mimetools.Message(f) | ||
|  | 	 | ||
|  | 	def handle_data(self, data): | ||
|  | 		self.pos = self.pos + len(data) | ||
|  | 		if self.ui is not None: | ||
|  | 			self.ui.setcurrent(self.pos) | ||
|  | 		self.datahandler(data) | ||
|  | 		if self.pos >= self.length: | ||
|  | 			self.datahandler("") | ||
|  | 			self.done = 1 | ||
|  | 			if self.ui is not None: | ||
|  | 				self.ui.done() | ||
|  | 	 | ||
|  | 	def handle_error(self, type, value, tb): | ||
|  | 		if self.ui is not None: | ||
|  | 			self.ui.error(type, value, tb) | ||
|  | 		else: | ||
|  | 			Connection.handle_error(self, type, value, tb) | ||
|  | 	 | ||
|  | 	def log(self, message): | ||
|  | 		if VERBOSE: | ||
|  | 			print 'LOG:', message | ||
|  | 
 | ||
|  | 
 | ||
|  | class PyMessage: | ||
|  | 	 | ||
|  | 	def __init__(self): | ||
|  | 		self._buf = "" | ||
|  | 		self._len = None | ||
|  | 		self._checksum = None | ||
|  | 	 | ||
|  | 	def feed(self, data): | ||
|  | 		self._buf = self._buf + data | ||
|  | 		if self._len is None: | ||
|  | 			if len(self._buf) >= 8: | ||
|  | 				import struct | ||
|  | 				self._len, self._checksum = struct.unpack("ll", self._buf[:8]) | ||
|  | 				self._buf = self._buf[8:] | ||
|  | 		if self._len is not None: | ||
|  | 			if len(self._buf) >= self._len: | ||
|  | 				import zlib | ||
|  | 				data = self._buf[:self._len] | ||
|  | 				leftover = self._buf[self._len:] | ||
|  | 				self._buf = None | ||
|  | 				assert self._checksum == zlib.adler32(data), "corrupt data" | ||
|  | 				self.data = data | ||
|  | 				return 1, leftover | ||
|  | 			else: | ||
|  | 				return 0, None | ||
|  | 		else: | ||
|  | 			return 0, None | ||
|  | 
 | ||
|  | 
 | ||
|  | class PyConnection(Connection): | ||
|  | 	 | ||
|  | 	def __init__(self, sock_or_address): | ||
|  | 		Connection.__init__(self, sock_or_address) | ||
|  | 		self.currentmessage = PyMessage() | ||
|  | 	 | ||
|  | 	def handle_incoming_data(self, data): | ||
|  | 		while data: | ||
|  | 			done, data = self.currentmessage.feed(data) | ||
|  | 			if done: | ||
|  | 				import cPickle | ||
|  | 				self.handle_object(cPickle.loads(self.currentmessage.data)) | ||
|  | 				self.currentmessage = PyMessage() | ||
|  | 	 | ||
|  | 	def handle_object(self, object): | ||
|  | 		print 'unhandled object:', `object` | ||
|  | 	 | ||
|  | 	def send(self, object): | ||
|  | 		import cPickle, zlib, struct | ||
|  | 		data = cPickle.dumps(object, 1) | ||
|  | 		length = len(data) | ||
|  | 		checksum = zlib.adler32(data) | ||
|  | 		data = struct.pack("ll", length, checksum) + data | ||
|  | 		Connection.send(self, data) | ||
|  | 
 | ||
|  | 
 | ||
|  | class Echo(Connection): | ||
|  | 	 | ||
|  | 	"""Simple echoing connection: it sends everything back it receives."""  | ||
|  | 	 | ||
|  | 	def handle_incoming_data(self, data): | ||
|  | 		self.send(data) | ||
|  | 
 | ||
|  | 
 | ||
|  | class Proxy(Connection): | ||
|  | 	 | ||
|  | 	"""Generic proxy connection""" | ||
|  | 	 | ||
|  | 	def __init__(self, sock_or_address=None, proxyaddr=None, closepartner=0): | ||
|  | 		"""arguments:
 | ||
|  | 		- sock_or_address is either a socket or a tuple containing the  | ||
|  | 		name and port number of a remote host | ||
|  | 		- proxyaddr: a tuple containing a name and a port number of a  | ||
|  | 		  remote host (optional). | ||
|  | 		- closepartner: boolean, specifies whether we should close | ||
|  | 		  the proxy connection (optional)"""
 | ||
|  | 		 | ||
|  | 		Connection.__init__(self, sock_or_address) | ||
|  | 		self.other = None | ||
|  | 		self.proxyaddr = proxyaddr | ||
|  | 		self.closepartner = closepartner | ||
|  | 	 | ||
|  | 	def close(self): | ||
|  | 		if self.other: | ||
|  | 			other = self.other | ||
|  | 			self.other = None | ||
|  | 			other.other = None | ||
|  | 			if self.closepartner: | ||
|  | 				other.close() | ||
|  | 		Connection.close(self) | ||
|  | 	 | ||
|  | 	def handle_incoming_data(self, data): | ||
|  | 		if not self.other: | ||
|  | 			# pass data for possible automatic remote host detection | ||
|  | 			# (see HTTPProxy) | ||
|  | 			data = self.connectproxy(data) | ||
|  | 		self.other.send(data) | ||
|  | 	 | ||
|  | 	def connectproxy(self, data): | ||
|  | 		other = self.__class__(self.proxyaddr, closepartner=self.closepartner) | ||
|  | 		self.other = other | ||
|  | 		other.other = self | ||
|  | 		return data | ||
|  | 
 | ||
|  | 
 | ||
|  | class HTTPProxy(Proxy): | ||
|  | 	 | ||
|  | 	"""Simple, useless, http proxy. It figures out itself where to connect to.""" | ||
|  | 	 | ||
|  | 	def connectproxy(self, data): | ||
|  | 		if VERBOSE: | ||
|  | 			print "--- proxy request", `data` | ||
|  | 		addr, data = de_proxify(data) | ||
|  | 		other = Proxy(addr) | ||
|  | 		self.other = other | ||
|  | 		other.other = self | ||
|  | 		return data | ||
|  | 
 | ||
|  | 
 | ||
|  | # helper for HTTPProxy | ||
|  | def de_proxify(data): | ||
|  | 	import re | ||
|  | 	req_pattern = "GET http://([a-zA-Z0-9-_.]+)(:([0-9]+))?" | ||
|  | 	m = re.match(req_pattern, data) | ||
|  | 	host, dummy, port = m.groups() | ||
|  | 	if not port: | ||
|  | 		port = 80 | ||
|  | 	else: | ||
|  | 		port = int(port) | ||
|  | 	# change "GET http://xx.xx.xx/yy" into "GET /yy" | ||
|  | 	data = re.sub(req_pattern, "GET ", data) | ||
|  | 	return (host, port), data | ||
|  | 
 | ||
|  | 
 | ||
|  | # if we're running "under W", let's register the socket poller to the event loop | ||
|  | try: | ||
|  | 	import W | ||
|  | except: | ||
|  | 	pass | ||
|  | else: | ||
|  | 	W.getapplication().addidlefunc(asyncore.poll) | ||
|  | 
 | ||
|  | 
 | ||
|  | ## testing muck | ||
|  | #testserver = Server(10000, Connection) | ||
|  | #echoserver = Server(10007, Echo) | ||
|  | #httpproxyserver = Server(8088, HTTPProxy, 5) | ||
|  | #asyncore.close_all() |