@@ -22,51 +22,89 @@ def __init__(self, url, protocols=None, extensions=None, heartbeat_freq=None, ss
2222 its own thread.
2323
2424 When an instance of this class is created, a :py:mod:`socket`
25- is created with the nagle's algorithm disabled and with
26- the capacity to reuse a port that was just used .
25+ is created. If the connection is a TCP socket,
26+ the nagle's algorithm is disabled .
2727
2828 The address of the server will be extracted from the given
2929 websocket url.
3030
3131 The websocket key is randomly generated, reset the
3232 `key` attribute if you want to provide yours.
3333
34+ For instance to create a TCP client:
35+
36+ .. code-block:: python
37+
38+ >>> from websocket.client import WebSocketBaseClient
39+ >>> ws = WebSocketBaseClient('ws://localhost/ws')
40+
41+
42+ Here is an example for a TCP client over SSL:
43+
44+ .. code-block:: python
45+
46+ >>> from websocket.client import WebSocketBaseClient
47+ >>> ws = WebSocketBaseClient('wss://localhost/ws')
48+
49+
50+ Finally an example of a Unix-domain connection:
51+
52+ .. code-block:: python
53+
54+ >>> from websocket.client import WebSocketBaseClient
55+ >>> ws = WebSocketBaseClient('ws+unix:///tmp/my.sock')
56+
57+ Note that in this case, the initial Upgrade request
58+ will be sent to ``/``. You may need to change this
59+ by setting the resource explicitely before connecting:
60+
61+ .. code-block:: python
62+
63+ >>> from websocket.client import WebSocketBaseClient
64+ >>> ws = WebSocketBaseClient('ws+unix:///tmp/my.sock')
65+ >>> ws.resource = '/ws'
66+ >>> ws.connect()
67+
3468 """
3569 self .url = url
3670 self .host = None
3771 self .scheme = None
3872 self .port = None
73+ self .unix_socket_path = None
3974 self .resource = None
4075 self .ssl_options = ssl_options or {}
4176
4277 self ._parse_url ()
4378
44- # Let's handle IPv4 and IPv6 addresses
45- # Simplified from CherryPy's code
46- try :
47- family , socktype , proto , canonname , sa = socket .getaddrinfo (self .host , self .port ,
48- socket .AF_UNSPEC ,
49- socket .SOCK_STREAM ,
50- 0 , socket .AI_PASSIVE )[0 ]
51- except socket .gaierror :
52- family = socket .AF_INET
53- if self .host .startswith ('::' ):
54- family = socket .AF_INET6
55-
56- socktype = socket .SOCK_STREAM
57- proto = 0
58- canonname = ""
59- sa = (self .host , self .port , 0 , 0 )
60-
61- sock = socket .socket (family , socktype , proto )
62- sock .setsockopt (socket .IPPROTO_TCP , socket .TCP_NODELAY , 1 )
63- sock .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
64- if hasattr (socket , 'AF_INET6' ) and family == socket .AF_INET6 and \
65- self .host .startswith ('::' ):
79+ if self .unix_socket_path :
80+ sock = socket .socket (socket .AF_UNIX , socket .SOCK_STREAM , 0 )
81+ else :
82+ # Let's handle IPv4 and IPv6 addresses
83+ # Simplified from CherryPy's code
6684 try :
67- sock .setsockopt (socket .IPPROTO_IPV6 , socket .IPV6_V6ONLY , 0 )
68- except (AttributeError , socket .error ):
69- pass
85+ family , socktype , proto , canonname , sa = socket .getaddrinfo (self .host , self .port ,
86+ socket .AF_UNSPEC ,
87+ socket .SOCK_STREAM ,
88+ 0 , socket .AI_PASSIVE )[0 ]
89+ except socket .gaierror :
90+ family = socket .AF_INET
91+ if self .host .startswith ('::' ):
92+ family = socket .AF_INET6
93+
94+ socktype = socket .SOCK_STREAM
95+ proto = 0
96+ canonname = ""
97+ sa = (self .host , self .port , 0 , 0 )
98+
99+ sock = socket .socket (family , socktype , proto )
100+ sock .setsockopt (socket .IPPROTO_TCP , socket .TCP_NODELAY , 1 )
101+ sock .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
102+ if hasattr (socket , 'AF_INET6' ) and family == socket .AF_INET6 and \
103+ self .host .startswith ('::' ):
104+ try :
105+ sock .setsockopt (socket .IPPROTO_IPV6 , socket .IPV6_V6ONLY , 0 )
106+ except (AttributeError , socket .error ):
107+ pass
70108
71109 WebSocket .__init__ (self , sock , protocols = protocols ,
72110 extensions = extensions ,
@@ -78,16 +116,33 @@ def __init__(self, url, protocols=None, extensions=None, heartbeat_freq=None, ss
78116
79117 # Adpated from: https://github.com/liris/websocket-client/blob/master/websocket.py#L105
80118 def _parse_url (self ):
81- if ":" not in self .url :
82- raise ValueError ("Invalid URL: %s" % self .url )
83-
119+ """
120+ Parses a URL which must have one of the following forms:
121+
122+ - ws://host[:port][path]
123+ - wss://host[:port][path]
124+ - ws+unix:///path/to/my.socket
125+
126+ In the first two cases, the ``host`` and ``port``
127+ attributes will be set to the parsed values. If no port
128+ is explicitely provided, it will be either 80 or 443
129+ based on the scheme. Also, the ``resource`` attribute is
130+ set to the path segment of the URL (alongside any querystring).
131+
132+ In addition, if the scheme is ``ws+unix``, the
133+ ``unix_socket_path`` attribute is set to the path to
134+ the Unix socket while the ``resource`` attribute is
135+ set to ``/``.
136+ """
84137 # Python 2.6.1 and below don't parse ws or wss urls properly. netloc is empty.
85138 # See: https://github.com/Lawouach/WebSocket-for-Python/issues/59
86139 scheme , url = self .url .split (":" , 1 )
87140
88141 parsed = urlsplit (url , scheme = "http" )
89142 if parsed .hostname :
90143 self .host = parsed .hostname
144+ elif '+unix' in scheme :
145+ self .host = 'localhost'
91146 else :
92147 raise ValueError ("Invalid hostname from: %s" , self .url )
93148
@@ -100,6 +155,8 @@ def _parse_url(self):
100155 elif scheme == "wss" :
101156 if not self .port :
102157 self .port = 443
158+ elif scheme in ('ws+unix' , 'wss+unix' ):
159+ pass
103160 else :
104161 raise ValueError ("Invalid scheme: %s" % scheme )
105162
@@ -108,12 +165,25 @@ def _parse_url(self):
108165 else :
109166 resource = "/"
110167
168+ if '+unix' in scheme :
169+ self .unix_socket_path = resource
170+ resource = '/'
171+
111172 if parsed .query :
112173 resource += "?" + parsed .query
113174
114175 self .scheme = scheme
115176 self .resource = resource
116177
178+ @property
179+ def bind_addr (self ):
180+ """
181+ Returns the Unix socket path if or a tuple
182+ ``(host, port)`` depending on the initial
183+ URL's scheme.
184+ """
185+ return self .unix_socket_path or (self .host , self .port )
186+
117187 def close (self , code = 1000 , reason = '' ):
118188 """
119189 Initiate the closing handshake with the server.
@@ -131,7 +201,7 @@ def connect(self):
131201 # default port is now 443; upgrade self.sender to send ssl
132202 self .sock = ssl .wrap_socket (self .sock , ** self .ssl_options )
133203
134- self .sock .connect (( self .host , self . port ) )
204+ self .sock .connect (self .bind_addr )
135205
136206 self ._write (self .handshake_request )
137207
@@ -202,8 +272,8 @@ def process_response_line(self, response_line):
202272 response to our request and if not raises :exc:`HandshakeError`.
203273 """
204274 protocol , code , status = response_line .split (enc (' ' ), 2 )
205- if code != enc ('101' ):
206- raise HandshakeError ("Invalid response status: %s %s" % (code , status ))
275+ # if code != enc('101'):
276+ # raise HandshakeError("Invalid response status: %s %s" % (code, status))
207277
208278 def process_handshake_header (self , headers ):
209279 """
0 commit comments