mirror of
https://github.com/Hopiu/linkchecker.git
synced 2026-03-25 10:20:23 +00:00
git-svn-id: https://linkchecker.svn.sourceforge.net/svnroot/linkchecker/trunk/linkchecker@5 e7d03fd6-7b0d-0410-9947-9c21f3af8025
266 lines
6.5 KiB
Python
266 lines
6.5 KiB
Python
# -*- Mode: Python; tab-width: 4 -*-
|
|
# $Id$
|
|
# Author: Sam Rushing <rushing@nightmare.com>
|
|
|
|
# A simple unix version of the asynchronous socket support.
|
|
# There are lots of problems with this still - I only wrote it to show
|
|
# that it could be done, and for my own testing purposes.
|
|
# [960206: servtest, asynfing, asynhttp, and pop3demo work, asyndns doesn't.]
|
|
# [960321: servtest, asynfing, asynhttp, pop3demo, pop3_2 work]
|
|
import select
|
|
import socket
|
|
import sys
|
|
|
|
# you need to generate ERRNO.py from Tools/scripts/h2py.py in the Python
|
|
# distribution.
|
|
|
|
try:
|
|
import ERRNO
|
|
except ImportError:
|
|
raise ImportError,'you need to generate ERRNO.py from Tools/scripts/h2py.py in the Python distribution'
|
|
|
|
# look what I can get away with... 8^)
|
|
socket.socket_map = {}
|
|
|
|
ALL_EVENTS = []
|
|
|
|
DEFAULT_TIMEOUT = 30.0
|
|
|
|
loop_running = 0
|
|
|
|
stop_loop_exception = "stop running the select loop"
|
|
|
|
# we want to select for read only those sockets
|
|
# to which we are already connected to, -OR- those
|
|
# sockets we are accepting on.
|
|
def readables (sock_fds):
|
|
sm = socket.socket_map
|
|
def readable_test (fd, sm=sm):
|
|
sock = sm[fd]
|
|
return sock.connected or sock.accepting
|
|
return filter (readable_test, sock_fds)
|
|
|
|
# only those fd's we are 'write blocked' on, -OR-
|
|
# those sockets we are waiting for a connection on.
|
|
def writables (sock_fds):
|
|
sm = socket.socket_map
|
|
def writable_test (fd, sm=sm):
|
|
sock = sm[fd]
|
|
return sock.write_blocked or not sock.connected
|
|
return filter (writable_test, sock_fds)
|
|
|
|
def loop(timeout=DEFAULT_TIMEOUT):
|
|
loop_running = 1
|
|
try:
|
|
while 1:
|
|
sock_fds = socket.socket_map.keys()
|
|
|
|
read_fds = readables (sock_fds)
|
|
write_fds = writables (sock_fds)
|
|
expt_fds = sock_fds[:]
|
|
|
|
(read_fds,
|
|
write_fds,
|
|
expt_fds) = select.select (read_fds,
|
|
write_fds,
|
|
expt_fds,
|
|
timeout)
|
|
print read_fds,write_fds,expt_fds
|
|
try:
|
|
for x in expt_fds:
|
|
socket.socket_map[x].handle_expt_event()
|
|
for x in read_fds:
|
|
socket.socket_map[x].handle_read_event()
|
|
for x in write_fds:
|
|
socket.socket_map[x].handle_write_event()
|
|
except KeyError:
|
|
# handle_expt handle_read might remove as socket
|
|
# from the map by calling self.close().
|
|
pass
|
|
except stop_loop_exception:
|
|
print 'loop stopped'
|
|
|
|
class dispatcher:
|
|
def __init__ (self, sock=None):
|
|
self.debug = 0
|
|
self.log_queue = []
|
|
self.connected = 0
|
|
self.accepting = 0
|
|
self.write_blocked = 1
|
|
if sock:
|
|
self.socket = sock
|
|
self.fileno = self.socket.fileno()
|
|
# I think it should inherit this anyway
|
|
self.socket.setblocking (0)
|
|
self.connected = 1
|
|
self.add_channel()
|
|
|
|
def add_channel (self, events=ALL_EVENTS):
|
|
self.log ('adding channel %s' % self)
|
|
socket.socket_map [self.fileno] = self
|
|
|
|
def del_channel (self):
|
|
if socket.socket_map.has_key (self.fileno):
|
|
del socket.socket_map [self.fileno]
|
|
if not len(socket.socket_map.keys()):
|
|
raise stop_loop_exception
|
|
|
|
def create_socket (self, family, type):
|
|
self.socket = socket.socket (family, type)
|
|
self.socket.setblocking(0)
|
|
self.fileno = self.socket.fileno()
|
|
self.add_channel()
|
|
|
|
def bind (self, *args):
|
|
return apply (self.socket.bind, args)
|
|
|
|
def go (self):
|
|
if not loop_running:
|
|
loop()
|
|
|
|
def listen (self, num):
|
|
self.accepting = 1
|
|
self.socket.listen (num)
|
|
|
|
def accept (self):
|
|
return self.socket.accept()
|
|
|
|
def connect (self, host, port):
|
|
try:
|
|
self.socket.connect (host, port)
|
|
except socket.error, why:
|
|
if type(why) == type(()) \
|
|
and why[0] in (ERRNO.EINPROGRESS, ERRNO.EALREADY, ERRNO.EWOULDBLOCK):
|
|
return
|
|
else:
|
|
raise socket.error, why
|
|
self.connected = 1
|
|
self.handle_connect()
|
|
|
|
def send (self, data):
|
|
try:
|
|
result = self.socket.send (data)
|
|
if result != len(data):
|
|
self.write_blocked = 1
|
|
else:
|
|
self.write_blocked = 0
|
|
return result
|
|
except socket.error, why:
|
|
if type(why) == type(()) and why[0] == ERRNO.EWOULDBLOCK:
|
|
self.write_blocked = 1
|
|
return 0
|
|
else:
|
|
raise socket.error, why
|
|
return 0
|
|
|
|
def recv (self, buffer_size):
|
|
data = self.socket.recv (buffer_size)
|
|
if not data:
|
|
self.handle_close()
|
|
return ''
|
|
else:
|
|
return data
|
|
|
|
def close (self):
|
|
self.socket.close()
|
|
self.del_channel()
|
|
|
|
def shutdown (self, how):
|
|
self.socket.shutdown (how)
|
|
|
|
def log (self, message):
|
|
#self.log_queue.append ('%s:%d %s' %
|
|
# (self.__class__.__name__, self.fileno, message))
|
|
print 'log:', message
|
|
|
|
def done (self):
|
|
self.print_log()
|
|
|
|
def print_log (self):
|
|
for x in self.log_queue:
|
|
print x
|
|
|
|
def handle_read_event (self):
|
|
# getting a read implies that we are connected
|
|
if not self.connected:
|
|
self.handle_connect()
|
|
self.connected = 1
|
|
self.handle_read()
|
|
elif self.accepting:
|
|
if not self.connected:
|
|
self.connected = 1
|
|
self.handle_accept()
|
|
else:
|
|
self.handle_read()
|
|
|
|
def more_to_send (self, yesno=1):
|
|
self.write_blocked = yesno
|
|
|
|
def handle_write_event (self):
|
|
# getting a read implies that we are connected
|
|
if not self.connected:
|
|
self.handle_connect()
|
|
self.connected = 1
|
|
self.write_blocked = 0
|
|
self.handle_write()
|
|
|
|
def handle_expt_event (self):
|
|
self.handle_error()
|
|
|
|
def handle_error (self, error=0):
|
|
self.close()
|
|
|
|
def handle_read (self):
|
|
self.log ('unhandled FD_READ')
|
|
|
|
def handle_write (self):
|
|
self.log ('unhandled FD_WRITE')
|
|
|
|
def handle_connect (self):
|
|
self.log ('unhandled FD_CONNECT')
|
|
|
|
def handle_oob (self):
|
|
self.log ('unhandled FD_OOB')
|
|
|
|
def handle_accept (self):
|
|
self.log ('unhandled FD_ACCEPT')
|
|
|
|
def handle_close (self):
|
|
self.log ('unhandled FD_CLOSE')
|
|
|
|
def handle_disconnect (self, error):
|
|
self.log ('unexpected disconnect, error:%d' % error)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# adds async send capability, useful for simple clients.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class dispatcher_with_send (dispatcher):
|
|
def __init__ (self, sock=None):
|
|
dispatcher.__init__ (self, sock)
|
|
self.out_buffer = ''
|
|
|
|
def initiate_send (self):
|
|
while self.out_buffer:
|
|
num_sent = 0
|
|
num_sent = dispatcher.send (self, self.out_buffer[:512])
|
|
self.out_buffer = self.out_buffer[num_sent:]
|
|
|
|
def handle_write (self):
|
|
self.initiate_send()
|
|
|
|
def send (self, data):
|
|
if self.debug:
|
|
self.log ('sending %s' % repr(data))
|
|
self.out_buffer = data
|
|
self.initiate_send()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# used a lot when debugging
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def close_all ():
|
|
for x in socket.socket_map.items():
|
|
x[1].socket.close()
|
|
socket.socket_map = {}
|
|
|