pvl/socket.py
changeset 118 4f9bcf1e53e0
parent 116 89b7385d19ba
child 120 d342506c3ef3
equal deleted inserted replaced
117:58aebcd35e1a 118:4f9bcf1e53e0
     6 
     6 
     7 # XXX: absolute import plz
     7 # XXX: absolute import plz
     8 socket = __import__('socket')
     8 socket = __import__('socket')
     9 
     9 
    10 import select
    10 import select
       
    11 import errno
    11 
    12 
    12 import urlparse
    13 import urlparse
    13 
    14 
    14 import logging; log = logging.getLogger('pvl.socket')
    15 import logging; log = logging.getLogger('pvl.socket')
    15 
    16 
    16 URL = {
    17 # order matters!
    17     'tcp':  (0,                 socket.SOCK_STREAM  ), # AF_UNSPEC
    18 URL = (
    18     'udp':  (0,                 socket.SOCK_DGRAM   ), # AF_UNSPEC
    19     # scheme    family              socktype
    19     'unix': (socket.AF_UNIX,    None                ), # socktype is given
    20     ( 'unix',   (socket.AF_UNIX,    None                )   ), # socktype is given
    20 }
    21     ( 'tcp',    (0,                 socket.SOCK_STREAM  )   ), # AF_UNSPEC
       
    22     ( 'udp',    (0,                 socket.SOCK_DGRAM   )   ), # AF_UNSPEC
       
    23 )
       
    24 
       
    25 URL_SCHEMES = dict(URL)
    21 
    26 
    22 def parse (str, port=None, scheme='tcp', unix=socket.SOCK_DGRAM) :
    27 def parse (str, port=None, scheme='tcp', unix=socket.SOCK_DGRAM) :
    23     """
    28     """
    24         Parse given string into (AF_*, SOCK_*, host, port).
    29         Parse given string into (AF_*, SOCK_*, host, port).
    25 
    30 
    26         For AF_UNIX, the path is in host, and port is empty, and the socktype is the given unix=... value.
    31         For AF_UNIX, the path is in host, and port is empty, and the socktype is the given unix=... value.
    27     """
    32     """
    28    
    33    
    29     family, socktype = URL[scheme]
    34     family, socktype = URL_SCHEMES[scheme]
    30     url = urlparse.urlparse(str)
    35     url = urlparse.urlparse(str)
    31     
    36     
    32     # TODO: UNIX?
    37     # TODO: UNIX?
    33     if url.scheme and url.netloc :
    38     if url.scheme and url.netloc :
    34         # proper url
    39         # proper url
    35         family, socktype = URL[url.scheme]
    40         family, socktype = URL_SCHEMES[url.scheme]
    36 
    41 
    37         return family, socktype, url.hostname, url.port or port
    42         return family, socktype, url.hostname, url.port or port
    38 
    43 
    39     elif url.scheme and url.path :
    44     elif url.scheme and url.path :
    40         # host:port
    45         # host:port
    54 
    59 
    55     family, socktype, host, port = parse(str, *args, **kwargs)
    60     family, socktype, host, port = parse(str, *args, **kwargs)
    56 
    61 
    57     if family == socket.AF_UNIX :
    62     if family == socket.AF_UNIX :
    58         raise ValueError("XXX: AF_UNIX is not yet supported", str)
    63         raise ValueError("XXX: AF_UNIX is not yet supported", str)
       
    64 
    59     else : # AF_UNSPEC
    65     else : # AF_UNSPEC
    60         return connect_inet(host, port, family=family, socktype=socktype)
    66         return connect_inet(host, port, family=family, socktype=socktype)
    61  
    67  
    62 def connect_inet (host=None, port=None, family=socket.AF_UNSPEC, socktype=socket.SOCK_STREAM) :
    68 def connect_inet (host=None, port=None, family=socket.AF_UNSPEC, socktype=socket.SOCK_STREAM) :
    63     """
    69     """
   101         return sock
   107         return sock
   102 
   108 
   103     else :
   109     else :
   104         raise Exception("Unable to connect: %s:%s: %s" % (host, port, error))
   110         raise Exception("Unable to connect: %s:%s: %s" % (host, port, error))
   105 
   111 
   106 def reverse (self, sockaddr, numeric_host=False, numeric_port=True) :
   112 def reverse (sockaddr, numeric_host=False, numeric_port=True) :
   107     """
   113     """
   108         Resolve given sockaddr, returning (host, port).
   114         Resolve given sockaddr, returning (host, port).
   109     """
   115     """
   110 
   116 
   111     flags = 0
   117     flags = 0
   115     
   121     
   116     if numeric_port :
   122     if numeric_port :
   117         flags |= socket.NI_NUMERICSERV
   123         flags |= socket.NI_NUMERICSERV
   118 
   124 
   119     return socket.getnameinfo(sockaddr, flags)
   125     return socket.getnameinfo(sockaddr, flags)
       
   126 
       
   127 def socket_str (sock) :
       
   128     peer = sock.getpeername()
       
   129     
       
   130     # lookup scheme
       
   131     for scheme, (family, socktype) in URL :
       
   132         if family and family != sock.family :
       
   133             continue
       
   134         elif socktype and socktype != sock.type :
       
   135             continue
       
   136         else :
       
   137             break
       
   138     else :
       
   139         scheme = None
       
   140 
       
   141     host, port = reverse(peer)
       
   142     
       
   143     if scheme :
       
   144         return "{scheme}://{host}:{port}".format(scheme=scheme, host=host, port=port)
       
   145     else :
       
   146         return "{host}:{port}".format(host=host, port=port)
   120 
   147 
   121 def nonblocking (call, *args, **kwargs) :
   148 def nonblocking (call, *args, **kwargs) :
   122     """
   149     """
   123         Call the given function, which read/writes on a nonblocking file, and return None if it would have blocked.
   150         Call the given function, which read/writes on a nonblocking file, and return None if it would have blocked.
   124     """
   151     """
   161             Raises EOFError on EOF.
   188             Raises EOFError on EOF.
   162         """
   189         """
   163         
   190         
   164         buf = nonblocking(self.sock.recv, block)
   191         buf = nonblocking(self.sock.recv, block)
   165 
   192 
       
   193         log.debug("%s: %s", self, buf)
       
   194 
   166         # eof?
   195         # eof?
   167         if not buf :
   196         if buf :
       
   197             return buf
       
   198         else :
   168             raise EOFError()
   199             raise EOFError()
   169 
       
   170         # ok
       
   171         return buf
       
   172 
   200 
   173     def peek (self) :
   201     def peek (self) :
   174         """
   202         """
   175             Peek at data in buffer.
   203             Peek at data in buffer.
   176         """
   204         """
   213         # split out one line
   241         # split out one line
   214         line, self._buf = self._buf.split('\n', 1)
   242         line, self._buf = self._buf.split('\n', 1)
   215         
   243         
   216         # in case we had \r\n
   244         # in case we had \r\n
   217         line = line.rstrip('\r')
   245         line = line.rstrip('\r')
       
   246 
       
   247         log.debug("%s: %s", self, line)
   218 
   248 
   219         return line
   249         return line
   220     
   250     
   221     def readlines (self) :
   251     def readlines (self) :
   222         """
   252         """
   235                 return
   265                 return
   236             else :
   266             else :
   237                 yield line
   267                 yield line
   238 
   268 
   239     __iter__ = readlines
   269     __iter__ = readlines
       
   270 
       
   271     def __str__ (self) :
       
   272         return socket_str(self.sock)
   240 
   273 
   241 class WriteStream (object) :
   274 class WriteStream (object) :
   242     """
   275     """
   243         Writable stream, supporting non-blocking/buffered writes.
   276         Writable stream, supporting non-blocking/buffered writes.
   244 
   277 
   309     def writeline (self, line, eol=EOL) :
   342     def writeline (self, line, eol=EOL) :
   310         """
   343         """
   311             Write out line.
   344             Write out line.
   312         """
   345         """
   313 
   346 
   314         self.write(str(line) + eol)
   347         log.debug("%s: %s", self, line)
       
   348 
       
   349         self.write(str(line))
       
   350         self.write(eol)
   315     
   351     
   316     def __call__ (self, *lines) :
   352     def __call__ (self, *lines) :
   317         for line in lines :
   353         for line in lines :
   318             self.writeline(line)
   354             self.writeline(line)
   319         
   355         
   320         # TODO: flush
   356         # TODO: flush
       
   357 
       
   358     def __str__ (self) :
       
   359         return socket_str(self.sock)
       
   360 
       
   361