[Jython-checkins] jython: Adding support for SO_ACCEPTCONN, SO_ERROR, and SO_TYPE

alan.kennedy jython-checkins at python.org
Thu Aug 23 01:31:52 CEST 2012


http://hg.python.org/jython/rev/a39725e023f2
changeset:   6850:a39725e023f2
user:        Alan Kennedy <alan at xhaus.com>
date:        Thu Aug 23 00:30:24 2012 +0100
summary:
  Adding support for SO_ACCEPTCONN, SO_ERROR, and SO_TYPE

files:
  Lib/socket.py           |  154 +++++++++++++++++----------
  Lib/test/test_socket.py |   49 +++++++-
  2 files changed, 137 insertions(+), 66 deletions(-)


diff --git a/Lib/socket.py b/Lib/socket.py
--- a/Lib/socket.py
+++ b/Lib/socket.py
@@ -150,13 +150,15 @@
 
 }
 
-def _map_exception(java_exception, circumstance=ALL):
+def _map_exception(sock_object, java_exception, circumstance=ALL):
     mapped_exception = _exception_map.get((java_exception.__class__, circumstance))
     if mapped_exception:
         py_exception = mapped_exception(java_exception)
     else:
         py_exception = error(-1, 'Unmapped exception: %s' % java_exception)
     setattr(py_exception, 'java_exception', java_exception)
+    if sock_object is not None:
+        setattr(sock_object, '_last_error', py_exception[0])
     return _add_exception_attrs(py_exception)
 
 _feature_support_map = {
@@ -243,16 +245,19 @@
 IPPROTO_TCP      =   6
 IPPROTO_UDP      =  17
 
-SO_BROADCAST   = 1
-SO_KEEPALIVE   = 2
-SO_LINGER      = 4
-SO_OOBINLINE   = 8
-SO_RCVBUF      = 16
-SO_REUSEADDR   = 32
-SO_SNDBUF      = 64
-SO_TIMEOUT     = 128
+SO_ACCEPTCONN  = 1
+SO_BROADCAST   = 2
+SO_ERROR       = 4
+SO_KEEPALIVE   = 8
+SO_LINGER      = 16
+SO_OOBINLINE   = 32
+SO_RCVBUF      = 64
+SO_REUSEADDR   = 128
+SO_SNDBUF      = 256
+SO_TIMEOUT     = 512
+SO_TYPE        = 1024
 
-TCP_NODELAY    = 256
+TCP_NODELAY    = 2048
 
 INADDR_ANY = "0.0.0.0"
 INADDR_BROADCAST = "255.255.255.255"
@@ -263,18 +268,15 @@
 # They are being added here so that code that refers to them
 # will not break with an AttributeError
 
-SO_ACCEPTCONN       = -1
-SO_DEBUG            = -2
-SO_DONTROUTE        = -4
-SO_ERROR            = -8
-SO_EXCLUSIVEADDRUSE = -16
-SO_RCVLOWAT         = -32
-SO_RCVTIMEO         = -64
-SO_REUSEPORT        = -128
-SO_SNDLOWAT         = -256
-SO_SNDTIMEO         = -512
-SO_TYPE             = -1024
-SO_USELOOPBACK      = -2048
+SO_DEBUG            = -1
+SO_DONTROUTE        = -1
+SO_EXCLUSIVEADDRUSE = -8
+SO_RCVLOWAT         = -16
+SO_RCVTIMEO         = -32
+SO_REUSEPORT        = -64
+SO_SNDLOWAT         = -128
+SO_SNDTIMEO         = -256
+SO_USELOOPBACK      = -512
 
 __all__ = [
     # Families
@@ -692,13 +694,13 @@
     try:
         return asPyString(java.net.InetAddress.getLocalHost().getHostName())
     except java.lang.Exception, jlx:
-        raise _map_exception(jlx)
+        raise _map_exception(None, jlx)
 
 def gethostbyname(name):
     try:
         return asPyString(java.net.InetAddress.getByName(name).getHostAddress())
     except java.lang.Exception, jlx:
-        raise _map_exception(jlx)
+        raise _map_exception(None, jlx)
 
 def gethostbyaddr(name):
     names, addrs = _gethostbyaddr(name)
@@ -974,7 +976,7 @@
                             results.append((family, result_socktype, result_proto, canonname, sock_tuple))
         return results
     except java.lang.Exception, jlx:
-        raise _map_exception(jlx)
+        raise _map_exception(None, jlx)
 
 def _getnameinfo_get_host(address, flags):
     if not isinstance(address, basestring):
@@ -1057,7 +1059,7 @@
                 bytes.append(byte)
         return "".join([chr(byte) for byte in bytes])
     except java.lang.Exception, jlx:
-        raise _map_exception(jlx)
+        raise _map_exception(None, jlx)
 
 def inet_ntop(family, packed_ip):
     try:
@@ -1073,7 +1075,7 @@
         ia = java.net.InetAddress.getByAddress(jByteArray)
         return ia.getHostAddress()
     except java.lang.Exception, jlx:
-        raise _map_exception(jlx)
+        raise _map_exception(None, jlx)
 
 def inet_aton(ip_string):
     return inet_pton(AF_INET, ip_string)
@@ -1083,9 +1085,9 @@
 
 class _nonblocking_api_mixin:
 
-    mode = MODE_BLOCKING
+    mode            = MODE_BLOCKING
     reference_count = 0
-    close_lock = threading.Lock()
+    close_lock      = threading.Lock()
 
     def __init__(self):
         self.timeout = _defaulttimeout
@@ -1122,40 +1124,52 @@
 
     def setsockopt(self, level, optname, value):
         try:
+            self._last_error = 0
             if self.sock_impl:
                 self.sock_impl.setsockopt(level, optname, value)
             else:
                 self.pending_options[ (level, optname) ] = value
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def getsockopt(self, level, optname):
+        # Handle "pseudo" options first
+        if level == SOL_SOCKET and optname == SO_TYPE:
+            return getattr(self, "type")
+        if level == SOL_SOCKET and optname == SO_ERROR:
+            return_value = self._last_error
+            self._last_error = 0
+            return return_value
+        # Now handle "real" options
         try:
             if self.sock_impl:
                 return self.sock_impl.getsockopt(level, optname)
             else:
                 return self.pending_options.get( (level, optname), None)
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def shutdown(self, how):
         assert how in (SHUT_RD, SHUT_WR, SHUT_RDWR)
         if not self.sock_impl:
             raise error(errno.ENOTCONN, "Transport endpoint is not connected")
         try:
+            self._last_error = 0
             self.sock_impl.shutdown(how)
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def close(self):
         try:
+            self._last_error = 0
             if self.sock_impl:
                 self.sock_impl.close()
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def getsockname(self):
         try:
+            self._last_error = 0
             if self.sock_impl is None:
                 # If the user has already bound an address, return that
                 if self.local_addr:
@@ -1165,15 +1179,16 @@
                 raise error(errno.EINVAL, "Invalid argument")
             return self.sock_impl.getsockname()
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def getpeername(self):
         try:
+            self._last_error = 0
             if self.sock_impl is None:
                 raise error(errno.ENOTCONN, "Socket is not connected")
             return self.sock_impl.getpeername()
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def _config(self):
         assert self.mode in _permitted_modes
@@ -1198,16 +1213,23 @@
 
 class _tcpsocket(_nonblocking_api_mixin):
 
-    sock_impl = None
-    istream = None
-    ostream = None
-    local_addr = None
-    server = 0
+    sock_impl   = None
+    istream     = None
+    ostream     = None
+    local_addr  = None
+    server      = 0
+    _last_error = 0
 
     def __init__(self):
         _nonblocking_api_mixin.__init__(self)
 
+    def getsockopt(self, level, optname):
+        if level == SOL_SOCKET and optname == SO_ACCEPTCONN:
+            return self.server
+        return _nonblocking_api_mixin.getsockopt(self, level, optname)
+
     def bind(self, addr):
+        self._last_error = 0
         assert not self.sock_impl
         assert not self.local_addr
         # Do the address format check
@@ -1217,22 +1239,25 @@
     def listen(self, backlog):
         "This signifies a server socket"
         try:
+            self._last_error = 0
             assert not self.sock_impl
             self.server = 1
             self.sock_impl = _server_socket_impl(_get_jsockaddr(self.local_addr, self.family, self.type, self.proto, AI_PASSIVE), 
                                   backlog, self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ])
             self._config()
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def accept(self):
         "This signifies a server socket"
         try:
+            self._last_error = 0
             if not self.sock_impl:
                 self.listen()
             assert self.server
             new_sock = self.sock_impl.accept()
             if not new_sock:
+                self._last_error = errno.EWOULDBLOCK
                 raise would_block_error()
             cliconn = _tcpsocket()
             cliconn.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ] = new_sock.jsocket.getReuseAddress()
@@ -1240,10 +1265,11 @@
             cliconn._setup()
             return cliconn, new_sock.getpeername()
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def _do_connect(self, addr):
         try:
+            self._last_error = 0
             assert not self.sock_impl
             self.sock_impl = _client_socket_impl()
             if self.local_addr: # Has the socket been bound to a local address?
@@ -1252,7 +1278,7 @@
             self._config() # Configure timeouts, etc, now that the socket exists
             self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0))
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def connect(self, addr):
         "This signifies a client socket"
@@ -1277,6 +1303,7 @@
 
     def recv(self, n):
         try:
+            self._last_error = 0
             if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected')
             if self.sock_impl.jchannel.isConnectionPending():
                 self.sock_impl.jchannel.finishConnect()
@@ -1286,33 +1313,37 @@
                 return ""
             elif m <= 0:
                 if self.mode == MODE_NONBLOCKING:
+                    self._last_error = errno.EWOULDBLOCK
                     raise would_block_error()
                 return ""
             if m < n:
                 data = data[:m]
             return data.tostring()
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def recvfrom(self, n):
         return self.recv(n), None
 
     def send(self, s):
         try:
+            self._last_error = 0
             if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected')
             if self.sock_impl.jchannel.isConnectionPending():
                 self.sock_impl.jchannel.finishConnect()
             numwritten = self.sock_impl.write(s)
             if numwritten == 0 and self.mode == MODE_NONBLOCKING:
+                self._last_error = errno.EWOULDBLOCK
                 raise would_block_error()
             return numwritten
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     sendall = send
 
     def close(self):
         try:
+            self._last_error = 0
             if self.istream:
                 self.istream.close()
             if self.ostream:
@@ -1320,20 +1351,22 @@
             if self.sock_impl:
                 self.sock_impl.close()
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
 
 class _udpsocket(_nonblocking_api_mixin):
 
-    sock_impl = None
-    connected = False
-    local_addr = None
+    sock_impl   = None
+    connected   = False
+    local_addr  = None
+    _last_error = 0
 
     def __init__(self):
         _nonblocking_api_mixin.__init__(self)
 
     def bind(self, addr):
         try:            
+            self._last_error = 0
             assert not self.sock_impl
             assert not self.local_addr
             # Do the address format check
@@ -1343,10 +1376,11 @@
                                                     self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ])
             self._config()
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def _do_connect(self, addr):
         try:
+            self._last_error = 0
             assert not self.connected, "Datagram Socket is already connected"
             if not self.sock_impl:
                 self.sock_impl = _datagram_socket_impl()
@@ -1354,7 +1388,7 @@
             self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0))
             self.connected = True
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def connect(self, addr):
         self._do_connect(addr)
@@ -1366,6 +1400,7 @@
 
     def sendto(self, data, p1, p2=None):
         try:
+            self._last_error = 0
             if not p2:
                 flags, addr = 0, p1
             else:
@@ -1377,9 +1412,10 @@
             result = self.sock_impl.sendto(byte_array, _get_jsockaddr(addr, self.family, self.type, self.proto, 0), flags)
             return result
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def send(self, data, flags=None):
+        self._last_error = 0
         if not self.connected: raise error(errno.ENOTCONN, "Socket is not connected")
         byte_array = java.lang.String(data).getBytes('iso-8859-1')
         return self.sock_impl.send(byte_array, flags)
@@ -1393,6 +1429,7 @@
         http://bugs.sun.com/view_bug.do?bug_id=6621689
         """
         try:
+            self._last_error = 0
             # This is the old 2.1 behaviour
             #assert self.sock_impl
             # This is amak's preferred interpretation
@@ -1403,14 +1440,15 @@
                 self._config()
             return self.sock_impl.recvfrom(num_bytes, flags)
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def recv(self, num_bytes, flags=None):
         if not self.sock_impl: raise error(errno.ENOTCONN, "Socket is not connected")
         try:
+            self._last_error = 0
             return self.sock_impl.recv(num_bytes, flags)
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def __del__(self):
         self.close()
@@ -1812,7 +1850,7 @@
             self._in_buf = java.io.BufferedInputStream(self.ssl_sock.getInputStream())
             self._out_buf = java.io.BufferedOutputStream(self.ssl_sock.getOutputStream())
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def _make_ssl_socket(self, plain_socket, auto_close=0):
         java_net_socket = plain_socket._get_jsocket()
@@ -1835,7 +1873,7 @@
                 data = data[:m]
             return data.tostring()
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def write(self, s):
         try:
@@ -1843,13 +1881,13 @@
             self._out_buf.flush()
             return len(s)
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def _get_server_cert(self):
         try:
             return self.ssl_sock.getSession().getPeerCertificates()[0]
         except java.lang.Exception, jlx:
-            raise _map_exception(jlx)
+            raise _map_exception(self, jlx)
 
     def server(self):
         cert = self._get_server_cert()
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -898,10 +898,48 @@
         self._testOption(socket.IPPROTO_TCP, socket.TCP_NODELAY, [0, 1])
         self._testInheritedOption(socket.IPPROTO_TCP, socket.TCP_NODELAY, [0, 1])
 
-class TestUnsupportedOptions(TestSocketOptions):
+class TestPseudoOptions(unittest.TestCase):
 
     def testSO_ACCEPTCONN(self):
-        self.failUnless(hasattr(socket, 'SO_ACCEPTCONN'))
+        for socket_type, listen, expected_result in [
+            #(socket.SOCK_STREAM, 0, 0),
+            #(socket.SOCK_STREAM, 1, 1),
+            (socket.SOCK_DGRAM,   0, Exception),
+            ]:
+            s = socket.socket(socket.AF_INET, socket_type)
+            if listen:
+                s.listen(1)
+            try:
+                result = s.getsockopt(socket.SOL_SOCKET, socket.SO_ACCEPTCONN)
+                if expected_result is not Exception:
+                    self.failUnlessEqual(result, expected_result)
+            except socket.error, se:
+                if expected_result is Exception:
+                    if se[0] != errno.ENOPROTOOPT:
+                        self.fail("getsockopt(SO_ACCEPTCONN) on wrong socket type raised wrong exception: %s" % str(se))
+                else:
+                    self.fail("getsocket(SO_ACCEPTCONN) on valid socket type should not have raised exception: %s" % (str(se)))
+
+    def testSO_ERROR(self):
+        for socket_type in [socket.SOCK_STREAM, socket.SOCK_DGRAM]:
+            s = socket.socket(socket.AF_INET, socket_type)
+            self.failUnlessEqual(s.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR), 0)
+            try:
+                # Now cause an error
+                s.connect(("localhost", 100000))
+                self.fail("Operation '%s' that should have failed to generate SO_ERROR did not" % operation)
+            except socket.error, se:
+                so_error = s.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+                self.failUnlessEqual(so_error, se[0])
+            # Now retrieve the option again - it should be zero
+            self.failUnlessEqual(s.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR), 0)
+
+    def testSO_TYPE(self):
+        for socket_type in [socket.SOCK_STREAM, socket.SOCK_DGRAM]:
+            s = socket.socket(socket.AF_INET, socket_type)
+            self.failUnlessEqual(s.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE), socket_type)
+
+class TestUnsupportedOptions(TestSocketOptions):
 
     def testSO_DEBUG(self):
         self.failUnless(hasattr(socket, 'SO_DEBUG'))
@@ -909,9 +947,6 @@
     def testSO_DONTROUTE(self):
         self.failUnless(hasattr(socket, 'SO_DONTROUTE'))
 
-    def testSO_ERROR(self):
-        self.failUnless(hasattr(socket, 'SO_ERROR'))
-
     def testSO_EXCLUSIVEADDRUSE(self):
         # this is an MS specific option that will not be appearing on java
         # http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6421091
@@ -935,9 +970,6 @@
     def testSO_SNDTIMEO(self):
         self.failUnless(hasattr(socket, 'SO_SNDTIMEO'))
 
-    def testSO_TYPE(self):
-        self.failUnless(hasattr(socket, 'SO_TYPE'))
-
     def testSO_USELOOPBACK(self):
         self.failUnless(hasattr(socket, 'SO_USELOOPBACK'))
 
@@ -2429,6 +2461,7 @@
         GeneralModuleTests,
         IPAddressTests,
         TestSupportedOptions,
+        TestPseudoOptions,
         TestUnsupportedOptions,
         BasicTCPTest,
         TCPServerTimeoutTest,

-- 
Repository URL: http://hg.python.org/jython


More information about the Jython-checkins mailing list