/usr/share/pyshared/circuits/web/websocket.py is in python-circuits 2.1.0-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | """
.. codeauthor: mnl
"""
from circuits.web.headers import Headers
from circuits.core.handlers import handler
import os
import random
import base64
from circuits.web import client
from circuits.net.protocols.websocket import WebSocketCodec
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
from circuits.core.components import BaseComponent
from circuits.web.client import NotConnected
from circuits.net.sockets import TCPClient, Connect, Write, Close
from circuits.net.protocols.http import HTTP
from socket import error as SocketError
from errno import ECONNRESET
class WebSocketClient(BaseComponent):
"""
An RFC 6455 compliant WebSocket client component. Upon receiving a
:class:`circuits.web.client.Connect` event, the component tries to
establish the connection to the server in a two stage process. First, a
:class:`circuits.net.sockets.Connect` event is sent to a child
:class:`~.sockets.TCPClient`. When the TCP connection has been established,
the HTTP request for opening the WebSocket is sent to the server.
A failure in this setup process is signaled by a
:class:`~.client.NotConnected` event.
When the server accepts the request, the WebSocket connection is
established and can be used very much like an ordinary socket
by handling :class:`~.sockets.Read` events on and sending
:class:`~.sockets.Write` events to the channel
specified as the ``wschannel`` parameter of the constructor. Firing
a :class:`~.sockets.Close` event on that channel closes the
connection in an orderly fashion (i.e. as specified by the
WebSocket protocol).
"""
channel = "wsclient"
def __init__(self, url, channel=channel, wschannel="ws", headers={}):
"""
:param url: the URL to connect to.
:param channel: the channel used by this component
:param wschannel: the channel used for the actual WebSocket
communication (read, write, close events)
:param headers: additional headers to be passed with the
WebSocket setup HTTP request
"""
super(WebSocketClient, self).__init__(channel=channel)
self._url = url
self._headers = headers
self._response = None
self._pending = 0
self._wschannel = wschannel
self._transport = TCPClient(channel=self.channel).register(self)
HTTP(channel=self.channel).register(self._transport)
@handler("connect", priority=0.1, filter=True)
def _on_connect(self, event, *args, **kwargs):
if not isinstance(event, client.Connect):
return
p = urlparse(self._url)
if not p.hostname:
raise ValueError("URL must be absolute")
self._host = p.hostname
if p.scheme == "ws":
self._secure = False
self._port = p.port or 80
elif p.scheme == "wss":
self._secure = True
self._port = p.port or 443
else:
self.fire(NotConnected())
return
self._resource = p.path or "/"
if p.query:
self._resource += "?" + p.query
self.fire(Connect(self._host, self._port, self._secure),
self._transport)
@handler("connected")
def _on_connected(self, host, port):
headers = Headers([(k, v) for k, v in self._headers.items()])
# Clients MUST include Host header in HTTP/1.1 requests (RFC 2616)
if not "Host" in headers:
headers["Host"] = self._host \
+ (":" + str(self._port)) if self._port else ""
headers["Upgrade"] = "websocket"
headers["Connection"] = "Upgrade"
try:
sec_key = os.urandom(16)
except NotImplementedError:
sec_key = "".join([chr(random.randint(0,255)) for i in range(16)])
headers["Sec-WebSocket-Key"] = base64.b64encode(sec_key)
headers["Sec-WebSocket-Version"] = "13"
command = "GET %s HTTP/1.1" % self._resource
message = "%s\r\n%s" % (command, headers)
self._pending += 1
self.fire(Write(message.encode('utf-8')), self._transport)
return True
@handler("response")
def _on_response(self, response):
self._response = response
self._pending -= 1
if response.headers.get("Connection") == "Close" \
or response.status != 101:
self.fire(Close(), self._transport)
self.fire(NotConnected())
WebSocketCodec(channel=self._wschannel).register(self)
@handler("error", filter=True, priority=10)
def _on_error(self, error, *args, **kwargs):
# For HTTP 1.1 we leave the connection open. If the peer closes
# it after some time and we have no pending request, that's OK.
if isinstance(error, SocketError) and error.args[0] == ECONNRESET \
and self._pending == 0:
return True
def close(self):
if self._transport != None:
self._transport.close()
@property
def connected(self):
return getattr(self._transport, "connected", False) \
if hasattr(self, "_transport") else False
|