Friday, April 23, 2010

Convenient thread logging in Python

I wrote a small logging thread class for easy logging.
My standard error now looks like this:
[Fri Apr 23 15:58:20 2010] [ClipboardReader-1] Starting log...
[Fri Apr 23 15:58:20 2010] [SocksValidator-1] Starting log...
[Fri Apr 23 15:58:20 2010] [SocksChecker-1] Starting log...
Every thread also has its own logfile in the "logs" directory by his class name. I made up the thread name to add the class name of the instance (won't be LoggingThread if you inherit from it).
class LoggingThread(threading.Thread):
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
if hasattr(self.__class__, "instance_count"):
self.__class__.instance_count += 1
else:
self.__class__.instance_count = 1

self.name = "%s-%d" % (self.__class__.__name__, self.__class__.instance_count)

if not os.path.isdir("logs"):
os.makedirs("logs")
self.logfile = open("logs/%s.log" % self.name, "w")
self.log("Starting log...")

def log(self, data):
logline = "[%s] [%s] %s" % (time.ctime(), self.name, data)
print >> self.logfile, logline
self.logfile.flush()
print >> sys.stdout, logline

Getting and setting text from the clipboard using Python

Just name this module clipboard.py and use GetClipboardText/SetClipboardText. Very handy, and doesn't really need the win32 extentions, although it's used here.


from ctypes import *
from win32con import CF_TEXT, GHND

OpenClipboard = windll.user32.OpenClipboard
EmptyClipboard = windll.user32.EmptyClipboard
GetClipboardData = windll.user32.GetClipboardData
SetClipboardData = windll.user32.SetClipboardData
CloseClipboard = windll.user32.CloseClipboard
GlobalLock = windll.kernel32.GlobalLock
GlobalAlloc = windll.kernel32.GlobalAlloc
GlobalUnlock = windll.kernel32.GlobalUnlock
memcpy = cdll.msvcrt.memcpy

def GetClipboardText():
text = ""
if OpenClipboard(c_int(0)):
hClipMem = GetClipboardData(c_int(CF_TEXT))
GlobalLock.restype = c_char_p
text = GlobalLock(c_int(hClipMem))
GlobalUnlock(c_int(hClipMem))
CloseClipboard()
return text

def SetClipboardText(text):
buffer = c_buffer(text)
bufferSize = sizeof(buffer)
hGlobalMem = GlobalAlloc(c_int(GHND), c_int(bufferSize))
GlobalLock.restype = c_void_p
lpGlobalMem = GlobalLock(c_int(hGlobalMem))
memcpy(lpGlobalMem, addressof(buffer), c_int(bufferSize))
GlobalUnlock(c_int(hGlobalMem))
if OpenClipboard(0):
EmptyClipboard()
SetClipboardData(c_int(CF_TEXT), c_int(hGlobalMem))
CloseClipboard()

Thursday, April 22, 2010

Here is a really nice piece of code which implements a TCP relay (tunnel) using Python's asyncore module. It's really interesting because the way I implemented it makes a lot of sense, in contrary to the same code written using select.

There's also something about using asyncore.dispatcher_with_send on Unix systems which might send an EWOULDBLOCK sometimes, but I didn't dig deep enough.

The 3 sockets playing a role here are:
* A Relay server socket - accepts connections from clients
* Relay clients, which trigger a new connection to the tunnel destination
* Relay connection for each relay client connected.

The cool thing about asyncore is that you can also decide if handle_read will be called or not, even if data is available, by overriding the "readable" function. In this implementation, a relay client does not start reading from a socket until the relay connection has been established successfully.




class RelayConnection(asyncore.dispatcher):
def __init__(self, client, address):
asyncore.dispatcher.__init__(self)
self.client = client
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
print "connecting to %s..." % str(address)
self.connect(address)

def handle_connect(self):
print "connected."
# Allow reading once the connection
# on the other side is open.
self.client.is_readable = True

def handle_read(self):
self.client.send(self.recv(1024))

class RelayClient(asyncore.dispatcher):
def __init__(self, server, client, address):
asyncore.dispatcher.__init__(self, client)
self.is_readable = False
self.server = server
self.relay = RelayConnection(self, address)

def handle_read(self):
self.relay.send(self.recv(1024))

def handle_close(self):
print "Closing relay..."
# If the client disconnects, close the
# relay connection as well.
self.relay.close()
self.close()

def readable(self):
return self.is_readable

class RelayServer(asyncore.dispatcher):
def __init__(self, bind_address, dest_address):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind(bind_address)
self.dest_address = dest_address
self.listen(10)

def handle_accept(self):
conn, addr = self.accept()
RelayClient(self, conn, self.dest_address)


RelayServer(("0.0.0.0", 8080), ("127.0.0.1", 1234))
asyncore.loop()