mirror of
https://github.com/Hopiu/linkchecker.git
synced 2026-03-21 16:30:28 +00:00
git-svn-id: https://linkchecker.svn.sourceforge.net/svnroot/linkchecker/trunk/linkchecker@209 e7d03fd6-7b0d-0410-9947-9c21f3af8025
346 lines
10 KiB
Python
346 lines
10 KiB
Python
"""Timeout Socket
|
|
|
|
This module enables a timeout mechanism on all TCP connections. It
|
|
does this by inserting a shim on top of the socket module. After
|
|
this module has been imported, all socket creation goes through this
|
|
shim. As a result, every TCP connection will support a timeout.
|
|
|
|
The beauty of this method is that it immediately and transparently
|
|
enables the entire python library to support timeouts on TCP sockets.
|
|
As an example, if you wanted to SMTP connections to have a 20 second
|
|
timeout:
|
|
|
|
import timeoutsocket
|
|
import smtplib
|
|
timeoutsocket.setDefaultSocketTimeout(20)
|
|
|
|
|
|
The timeout applies to the socket functions that normally block on
|
|
execution: read, write, connect, and accept. If any of these
|
|
operations exceeds the specified timeout, the exception Timeout
|
|
will be raised.
|
|
|
|
The default timeout value is set to None. As a result, importing
|
|
this module does not change the default behavior of a socket. The
|
|
timeout mechanism only activates when the timeout has been set to
|
|
a numeric value. (This behavior mimics the behavior of the
|
|
select.select() function.)
|
|
|
|
This module works by replacing the socket module in the sys.modules
|
|
array. As a result, any reference to the original socket module
|
|
will instead reference this module. Whenever any creates a TCP
|
|
socket using the socket.socket() function, this module returns an
|
|
instance of the TimeoutSocket object. It is this object that handles
|
|
the timeouts.
|
|
|
|
This module implements two classes: TimeoutSocket and TimeoutFile.
|
|
|
|
The TimeoutSocket class defines a socket-like object that attempts to
|
|
avoid the condition where a socket may block indefinitely. The
|
|
TimeoutSocket class raises a Timeout exception whenever the
|
|
current operation delays too long.
|
|
|
|
The TimeoutFile class defines a file-like object that uses the TimeoutSocket
|
|
class. When the makefile() method of TimeoutSocket is called, it returns
|
|
an instance of a TimeoutFile.
|
|
|
|
Each of these objects adds two methods to manage the timeout value:
|
|
|
|
get_timeout() --> returns the timeout of the socket or file
|
|
set_timeout() --> sets the timeout of the socket or file
|
|
|
|
|
|
As an example, one might use the timeout feature to create httplib
|
|
connections that will timeout after 30 seconds:
|
|
|
|
import timeoutsocket
|
|
import httplib
|
|
H = httplib.HTTP("www.python.org")
|
|
H.sock.set_timeout(30)
|
|
|
|
Note: When used in this manner, the connect() routine may still
|
|
block because it happens before the timeout is set. To avoid
|
|
this, use the 'timeout.setDefaultSocketTimeout()' function.
|
|
|
|
Good Luck!
|
|
|
|
"""
|
|
|
|
#
|
|
# Revision history
|
|
# 1.4 Updated document string
|
|
# 1.3 Changed name to timeoutsocket.py on Pehr's suggestion
|
|
# 1.2 Added the silent replacement of the socket module
|
|
# 1.1. First version of safesocket.py
|
|
#
|
|
|
|
__version__ = "$Revision$"
|
|
__author__ = "Timothy O'Malley <timo@alum.mit.edu>"
|
|
|
|
#
|
|
# Imports
|
|
#
|
|
import select, errno
|
|
import string
|
|
try:
|
|
from _timeoutsocket import *
|
|
except ImportError:
|
|
from socket import *
|
|
_socket = socket
|
|
del socket
|
|
|
|
#
|
|
# Default timeout value for ALL TimeoutSockets
|
|
#
|
|
_DefaultTimeout = None
|
|
def setDefaultSocketTimeout(timeout):
|
|
global _DefaultTimeout
|
|
_DefaultTimeout = timeout
|
|
def getDefaultSocketTimeout():
|
|
return _DefaultTimeout
|
|
|
|
#
|
|
# Exceptions for socket errors and timeouts
|
|
#
|
|
Error = error
|
|
class Timeout(Exception):
|
|
pass
|
|
|
|
|
|
#
|
|
# Factory function
|
|
#
|
|
def socket(family=AF_INET, type=SOCK_STREAM, proto=None):
|
|
if family != AF_INET or type != SOCK_STREAM:
|
|
if proto:
|
|
return _socket(family, type, proto)
|
|
else:
|
|
return _socket(family, type)
|
|
return TimeoutSocket( _socket(family, type), _DefaultTimeout )
|
|
# end socket
|
|
|
|
#
|
|
# The TimeoutSocket class definition
|
|
#
|
|
class TimeoutSocket:
|
|
"""TimeoutSocket object
|
|
Implements a socket-like object that raises Timeout whenever
|
|
an operation takes too long.
|
|
The definition of 'too long' can be changed using the
|
|
set_timeout() method.
|
|
"""
|
|
|
|
def __init__(self, sock, timeout):
|
|
self._sock = sock
|
|
self._timeout = timeout
|
|
# end __init__
|
|
|
|
def __getattr__(self, key):
|
|
return getattr(self._sock, key)
|
|
# end __getattr__
|
|
|
|
def get_timeout(self):
|
|
return self._timeout
|
|
# end set_timeout
|
|
|
|
def set_timeout(self, timeout=None):
|
|
self._timeout = timeout
|
|
# end set_timeout
|
|
|
|
def connect(self, addr, dumbhack=None):
|
|
# In case we were called as connect(host, port)
|
|
#if port != None: addr = (addr, port)
|
|
|
|
# Shortcuts
|
|
sock = self._sock
|
|
timeout = self._timeout
|
|
|
|
# First, make a non-blocking call to connect
|
|
try:
|
|
sock.setblocking(0)
|
|
sock.connect(addr)
|
|
sock.setblocking(1)
|
|
return
|
|
except Error, why:
|
|
# If we are already connected, then return success
|
|
# If we got a genuine error, re-raise it.
|
|
errcode = why[0]
|
|
if errcode == errno.EISCONN:
|
|
return
|
|
if errcode == 10035 and why[1] == 'winsock error':
|
|
# Windows error code from G.Li@med.ge.com
|
|
return
|
|
vals = (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK)
|
|
if errcode not in vals:
|
|
raise
|
|
|
|
# Now, wait for the connect to happen
|
|
# ONLY if dumbhack indicates this is pass number one.
|
|
# If select raises an error, we pass it on.
|
|
# Is this the right behavior?
|
|
if not dumbhack:
|
|
r,w,e = select.select([], [sock], [], timeout)
|
|
if w:
|
|
return self.connect(addr, dumbhack=1)
|
|
|
|
# If we get here, then we should raise Timeout
|
|
raise Timeout("Attempted connect to %s timed out." % str(addr) )
|
|
# end connect
|
|
|
|
def accept(self, dumbhack=1):
|
|
# Shortcuts
|
|
sock = self._sock
|
|
timeout = self._timeout
|
|
|
|
# First, make a non-blocking call to connect
|
|
try:
|
|
sock.setblocking(0)
|
|
sa = sock.accept()
|
|
sock.setblocking(1)
|
|
return sa
|
|
except Error, why:
|
|
# If we are already connected, then return success
|
|
# If we got a genuine error, re-raise it.
|
|
errcode = why[0]
|
|
vals = (errno.EAGAIN, errno.EWOULDBLOCK)
|
|
if errcode not in vals:
|
|
raise
|
|
|
|
# Now, wait for the accept to happen
|
|
# ONLY if dumbhack indicates this is pass number one.
|
|
# If select raises an error, we pass it on.
|
|
# Is this the right behavior?
|
|
if not dumbhack:
|
|
r,w,e = select.select([sock], [], [], timeout)
|
|
if r:
|
|
return self.accept(dumbhack=1)
|
|
|
|
# If we get here, then we should raise Timeout
|
|
raise Timeout("Attempted accept timed out.")
|
|
# end accept
|
|
|
|
def send(self, data, flags=0):
|
|
sock = self._sock
|
|
totallen = 0
|
|
while data:
|
|
r,w,e = select.select([],[sock], [], self._timeout)
|
|
if not w:
|
|
raise Timeout("Send timed out")
|
|
sentlen = sock.send(data, flags)
|
|
data = data[sentlen:]
|
|
totallen = totallen + sentlen
|
|
return totallen
|
|
# end send
|
|
|
|
def recv(self, bufsize, flags=0):
|
|
sock = self._sock
|
|
r,w,e = select.select([sock], [], [], self._timeout)
|
|
if r:
|
|
data = sock.recv(bufsize, flags)
|
|
return data
|
|
raise Timeout("Recv timed out")
|
|
# end recv
|
|
|
|
def makefile(self, flags="r", bufsize=-1):
|
|
return TimeoutFile(self, flags, bufsize)
|
|
# end makefile
|
|
|
|
# end TimeoutSocket
|
|
|
|
|
|
class TimeoutFile:
|
|
"""TimeoutFile object
|
|
Implements a file-like object on top of TimeoutSocket.
|
|
"""
|
|
|
|
def __init__(self, sock, mode="r", bufsize=4096):
|
|
self._sock = sock
|
|
self._bufsize = 4096
|
|
if bufsize > 0: self._bufsize = bufsize
|
|
if not hasattr(sock, "_inqueue"): self._sock._inqueue = ""
|
|
|
|
# end __init__
|
|
|
|
def __getattr__(self, key):
|
|
return getattr(self._sock, key)
|
|
# end __getattr__
|
|
|
|
def write(self, data):
|
|
self.send(data)
|
|
# end write
|
|
|
|
def read(self, size=-1):
|
|
data = self._sock._inqueue
|
|
self._sock._inqueue = ""
|
|
while 1:
|
|
datalen = len(data)
|
|
if datalen >= size > 0:
|
|
break
|
|
bufsize = self._bufsize
|
|
if size > 0:
|
|
bufsize = min(bufsize, size - datalen)
|
|
buf = self.recv(bufsize)
|
|
if not buf:
|
|
break
|
|
data = data + buf
|
|
if datalen > size > 0:
|
|
self._sock._inqueue = data[size:]
|
|
data = data[:size]
|
|
return data
|
|
# end read
|
|
|
|
def readline(self, size=-1):
|
|
data = self._sock._inqueue
|
|
self._sock._inqueue = ""
|
|
while 1:
|
|
idx = string.find(data, "\n")
|
|
if idx >= 0:
|
|
break
|
|
datalen = len(data)
|
|
if datalen >= size > 0:
|
|
break
|
|
bufsize = self._bufsize
|
|
if size > 0:
|
|
bufsize = min(bufsize, size - datalen )
|
|
buf = self.recv(bufsize)
|
|
if not buf:
|
|
break
|
|
data = data + buf
|
|
|
|
if idx >= 0:
|
|
idx = idx + 1
|
|
self._sock._inqueue = data[idx:]
|
|
data = data[:idx]
|
|
elif size > 0 and datalen > size:
|
|
self._sock._inqueue = data[size:]
|
|
data = data[:size]
|
|
return data
|
|
# end readline
|
|
|
|
def readlines(self, sizehint=-1):
|
|
result = []
|
|
while 1:
|
|
line = self.readline()
|
|
if not line: break
|
|
result.append(line)
|
|
return line
|
|
# end readlines
|
|
|
|
def flush(self): pass
|
|
|
|
# end TimeoutFile
|
|
|
|
|
|
#
|
|
# Silently replace the standard socket module
|
|
#
|
|
import sys
|
|
if sys.modules["socket"].__name__ != __name__:
|
|
me = sys.modules[__name__]
|
|
sys.modules["_timeoutsocket"] = sys.modules["socket"]
|
|
sys.modules["socket"] = me
|
|
for mod in sys.modules.values():
|
|
if hasattr(mod, "socket") and type(mod.socket) == type(me):
|
|
mod.socket = me
|
|
|
|
# Finis
|