Commits

Ginés Martínez Sánchez committed b3e287d Draft

developing websocket protocol

  • Participants
  • Parent commits 79d06b3

Comments (0)

Files changed (5)

File ginsfsm/protocols/http/common/response.py

 import logging
 import time
 
+from ginsfsm import __version__
 from ginsfsm.compat import (
     tobytes,
 )
 
         # Set the Server and Date field, if not yet specified. This is needed
         # if the server is used as a proxy.
-        ident = self.request.channel.identity
+        ident = 'ginsfsm-%s' % __version__
         if not server_header:
             response_headers.append(('Server', ident))
         else:

File ginsfsm/protocols/http/server/c_http_clisrv.py

     'maximum_simultaneous_requests': [int, 0, 0, None,
         "maximum simultaneous requests."
         ],
-    'identity': [str, 'ginsfsm', 0, None,
-                 "server identity (sent in Server: header)"],
     'url_scheme': [str, 'http', 0, None, "default ``http`` value"],
     'inactivity_timeout': [int, 5 * 60 * 60, 0, None,
         "Inactivity timeout in seconds."

File ginsfsm/protocols/sockjs/server/c_websocket.py

 
 from ginsfsm.protocols.sockjs.server.proto import json_decode
 from ginsfsm.gobj import GObj
-from ginsfsm.protocols.sockjs.server.basehandler import PreflightHandler
+from ginsfsm.protocols.sockjs.server.websocket import WebSocketHandler
 from ginsfsm.protocols.sockjs.server.session import BaseSession
 
 
 @view_config(
     context=GWebsocket,
     name='',
-    attr='get',
-    request_method='GET',
+    attr='execute',
+    #request_method='GET',
 )
-class WebsocketTransport(PreflightHandler):
+class WebsocketTransport(WebSocketHandler):
     name = 'websocket'
 
     def __init__(self, context, request):
         super(WebsocketTransport, self).__init__(
             context,
             request,
-            True  # Asynchronous response!
         )
-        self.session = None
-        self.active = True
+
+    def execute(self):
+        session_id = self.sid = self.context.parent.re_matched_name
+        return self._execute()
 
     def open(self, session_id):
         # Stats
 @view_config(
     context=GRawWebsocket,
     name='',
-    attr='get',
-    request_method='GET',
+    attr='execute',
+    #request_method='GET',
 )
-class RawWebsocketTransport(PreflightHandler):
+class RawWebsocketTransport(WebSocketHandler):
     name = 'rawwebsocket'
 
     def __init__(self, context, request):
         super(RawWebsocketTransport, self).__init__(
             context,
             request,
-            True  # Asynchronous response!
         )
         self.session = None
         self.active = True
         sid = '%0.9d' % random.randint(1, 2147483647)
         #if self.per_user:
         #    sid = (authenticated_userid(request), sid)
-
+        """
         session = manager.get(sid, True, request=request)
         request.environ['wsgi.sockjs_session'] = session
 
             return res
 
         return RawWebSocketTransport(session, request)
-
+        """
 
     def open(self):
         # Stats

File ginsfsm/protocols/sockjs/server/websocket.py

 
     This module is a modified version of the WebSocketHandler from
     tornado.sockjs adapted to ginsfsm.
+    "hixie-76/draft76" protocol is not supported.
 
     This module contains modified version of the WebSocketHandler from
     Tornado with some performance fixes and behavior changes for Hybi10
 import struct
 import time
 import base64
+
 import ginsfsm.escape
-#import tornado.web
+from ginsfsm.protocols.wsgi.webob.websocket_response import WebsocketResponse
 
 
 # Fake byte literal support:  In python 2.6+, you can say b"foo" to get
     to_string = lambda d: d.tostring()
 
 
-class WebSocketHandler(tornado.web.RequestHandler):
+class WebSocketHandler(WebsocketResponse):
     """Subclass this class to create a basic WebSocket handler.
 
     Override on_message to handle incoming messages. You can also override
 
     This script pops up an alert box that says "You said: Hello, world".
     """
-    def __init__(self, application, request, **kwargs):
-        tornado.web.RequestHandler.__init__(self, application, request,
-                                            **kwargs)
-        self.stream = request.connection.stream
+    def __init__(self, context, request):
+        super(WebSocketHandler, self).__init__(context, request)
+        #self.stream = request.connection.stream
+        self.stream = self.ginsfsm_channel
         self.ws_connection = None
 
-    def _execute(self, transforms, *args, **kwargs):
-        self.open_args = args
-        self.open_kwargs = kwargs
+    def _execute(self):
+        self.open_args = None  # args
+        self.open_kwargs = None  # kwargs
 
         # Websocket only supports GET method
+
         if self.request.method != "GET":
-            self.stream.write(ginsfsm.escape.utf8(
-                "HTTP/1.1 405 Method Not Allowed\r\n"
-                "Allow: GET\r\n"
-                "Connection: Close\r\n"
-                "\r\n"
-            ))
-            self.stream.close()
-            return
+            self.request.response.status = 405
+            self.request.response.headers = (
+                ('Allow', 'GET'),
+            )
+            #self.stream.close()
+            return self.request.response
 
         # Upgrade header should be present and should be equal to WebSocket
         if self.request.headers.get("Upgrade", "").lower() != "websocket":
                 "Sec-WebSocket-Version") in ("7", "8", "13"):
             self.ws_connection = WebSocketProtocol13(self, self.auto_decode())
             self.ws_connection.accept_connection()
-        elif (self.allow_draft76() and
-              "Sec-WebSocket-Version" not in self.request.headers):
-            self.ws_connection = WebSocketProtocol76(self, self.auto_decode())
-            self.ws_connection.accept_connection()
         else:
             self.stream.write(ginsfsm.escape.utf8(
                 "HTTP/1.1 426 Upgrade Required\r\n"
         """
         self.ws_connection.close()
 
-    def allow_draft76(self):
-        """Override to enable support for the older "draft76" protocol.
-
-        The draft76 version of the websocket protocol is disabled by
-        default due to security concerns, but it can be enabled by
-        overriding this method to return True.
-
-        Connections using the draft76 protocol do not support the
-        ``binary=True`` flag to `write_message`.
-
-        Support for the draft76 protocol is deprecated and will be
-        removed in a future version of Tornado.
-        """
-        return False
-
     def auto_decode(self):
         """Override to disable automatic utf-8 decoding of the string messages.
 
         self.close()  # let the subclass cleanup
 
 
-class WebSocketProtocol76(WebSocketProtocol):
-    """Implementation of the WebSockets protocol, version hixie-76.
-
-    This class provides basic functionality to process WebSockets requests as
-    specified in
-    http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
-    """
-    def __init__(self, handler, auto_decode):
-        WebSocketProtocol.__init__(self, handler, auto_decode)
-        self.challenge = None
-        self._waiting = None
-
-    def accept_connection(self):
-        try:
-            self._handle_websocket_headers()
-        except ValueError:
-            logging.debug("Malformed WebSocket request received")
-            self._abort()
-            return
-
-        scheme = self.handler.get_websocket_scheme()
-
-        # draft76 only allows a single subprotocol
-        subprotocol_header = ''
-        subprotocol = self.request.headers.get("Sec-WebSocket-Protocol", None)
-        if subprotocol:
-            selected = self.handler.select_subprotocol([subprotocol])
-            if selected:
-                assert selected == subprotocol
-                subprotocol_header = "Sec-WebSocket-Protocol: %s\r\n" % (
-                    selected)
-
-        # Write the initial headers before attempting to read the challenge.
-        # This is necessary when using proxies (such as HAProxy), which
-        # need to see the Upgrade headers before passing through the
-        # non-HTTP traffic that follows.
-        self.stream.write(ginsfsm.escape.utf8(
-            "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
-            "Upgrade: WebSocket\r\n"
-            "Connection: Upgrade\r\n"
-            "Server: ginsfsm/%(version)s\r\n"
-            "Sec-WebSocket-Origin: %(origin)s\r\n"
-            "Sec-WebSocket-Location: %(scheme)s://%(host)s%(uri)s\r\n"
-            "%(subprotocol)s"
-            "\r\n" % (dict(
-                    version=ginsfsm.__version__,
-                    origin=self.request.headers["Origin"],
-                    scheme=scheme,
-                    host=self.request.host,
-                    uri=self.request.uri,
-                    subprotocol=subprotocol_header))))
-        self.stream.read_bytes(8, self._handle_challenge)
-
-    def challenge_response(self, challenge):
-        """Generates the challenge response that's needed in the handshake
-
-        The challenge parameter should be the raw bytes as sent from the
-        client.
-        """
-        key_1 = self.request.headers.get("Sec-Websocket-Key1")
-        key_2 = self.request.headers.get("Sec-Websocket-Key2")
-        try:
-            part_1 = self._calculate_part(key_1)
-            part_2 = self._calculate_part(key_2)
-        except ValueError:
-            raise ValueError("Invalid Keys/Challenge")
-        return self._generate_challenge_response(part_1, part_2, challenge)
-
-    def _handle_challenge(self, challenge):
-        try:
-            challenge_response = self.challenge_response(challenge)
-        except ValueError:
-            logging.debug("Malformed key data in WebSocket request")
-            self._abort()
-            return
-        self._write_response(challenge_response)
-
-    def _write_response(self, challenge):
-        self.stream.write(challenge)
-        self.async_callback(self.handler.open)(
-            *self.handler.open_args,
-            **self.handler.open_kwargs
-        )
-        self._receive_message()
-
-    def _handle_websocket_headers(self):
-        """Verifies all invariant- and required headers
-
-        If a header is missing or have an incorrect value ValueError will be
-        raised
-        """
-        fields = ("Origin", "Host", "Sec-Websocket-Key1",
-                  "Sec-Websocket-Key2")
-        if not all(map(lambda f: self.request.headers.get(f), fields)):
-            raise ValueError("Missing/Invalid WebSocket headers")
-
-    def _calculate_part(self, key):
-        """Processes the key headers and calculates their key value.
-
-        Raises ValueError when feed invalid key."""
-        number = int(''.join(c for c in key if c.isdigit()))
-        spaces = len([c for c in key if c.isspace()])
-        try:
-            key_number = number // spaces
-        except (ValueError, ZeroDivisionError):
-            raise ValueError
-        return struct.pack(">I", key_number)
-
-    def _generate_challenge_response(self, part_1, part_2, part_3):
-        m = hashlib.md5()
-        m.update(part_1)
-        m.update(part_2)
-        m.update(part_3)
-        return m.digest()
-
-    def _receive_message(self):
-        self.stream.read_bytes(1, self._on_frame_type)
-
-    def _on_frame_type(self, byte):
-        frame_type = ord(byte)
-        if frame_type == 0x00:
-            self.stream.read_until(b("\xff"), self._on_end_delimiter)
-        elif frame_type == 0xff:
-            self.stream.read_bytes(1, self._on_length_indicator)
-        else:
-            self._abort()
-
-    def _on_end_delimiter(self, frame):
-        if not self.client_terminated:
-            msg = frame[:-1]
-
-            if self._auto_decode:
-                msg = msg.decode("utf-8", "replace")
-
-            self.async_callback(self.handler.on_message)(msg)
-        if not self.client_terminated:
-            self._receive_message()
-
-    def _on_length_indicator(self, byte):
-        if ord(byte) != 0x00:
-            self._abort()
-            return
-        self.client_terminated = True
-        self.close()
-
-    def write_message(self, message, binary=False):
-        """Sends the given message to the client of this Web Socket."""
-        if binary:
-            raise ValueError(
-                "Binary messages not supported by this version of websockets")
-        if isinstance(message, unicode):
-            message = message.encode("utf-8")
-        assert isinstance(message, bytes_type)
-        self.stream.write(b("\x00") + message + b("\xff"))
-
-    def close(self):
-        """Closes the WebSocket connection."""
-        if not self.server_terminated:
-            if not self.stream.closed():
-                self.stream.write("\xff\x00")
-            self.server_terminated = True
-        if self.client_terminated:
-            if self._waiting is not None:
-                self.stream.io_loop.remove_timeout(self._waiting)
-            self._waiting = None
-            self.stream.close()
-        elif self._waiting is None:
-            self._waiting = self.stream.io_loop.add_timeout(
-                time.time() + 5, self._abort)
-
-
 STRUCT_BB = struct.Struct("BB")
 STRUCT_BBH = struct.Struct("!BBH")
 STRUCT_BBQ = struct.Struct("!BBQ")

File ginsfsm/protocols/wsgi/webob/websocket_response.py

         raise Exception('async_response is depending of Pyramid or WebOb')
 
 
-class StopStreaming(StopIteration):
-    """ Connection has been disconnected. """
-
-
 class WebsocketResponse(Response):
-    def __init__(self, **kw):
-        super(WebsocketResponse, self).__init__(**kw)
-        self.ginsfsm_channel = None
+    def __init__(self, context, request):
+        super(WebsocketResponse, self).__init__()
 
         del self.app_iter  # Important trick to remove Content-Length.
         # WARNING: you cannot use body, because it sets Content-Length.
 
+        self.ginsfsm_channel = request.environ['ginsfsm.channel']
+        self.context = context
+        self.request = request
+
     def __call__(self, environ, start_response):
         "Override WSGI call"
-        self.ginsfsm_channel = environ['ginsfsm.channel']
 
-        if environ['wsgi.websocket_version'] == 'hixie-76':
-            part1, part2, socket = environ['wsgi.hixie-keys']
-
-            towrite = [
-                'HTTP/1.1 %s\r\n' % self.status,
-                'Date: %s\r\n' % format_date_time(time.time())]
-
-            for header in self._abs_headerlist(environ):
-                towrite.append("%s: %s\r\n" % header)
-
-            towrite.append("\r\n")
-            socket.sendall(''.join(towrite))
-
-            key3 = environ['wsgi.input'].read(8)
-            if not key3:
-                key3 = environ['wsgi.input'].rfile.read(8)
-
-            socket.sendall(
-                md5(struct.pack("!II", part1, part2) + key3).digest())
-        else:
-            write = start_response(
-                self.status, self._abs_headerlist(environ))
-            write(self.body)
-
-        try:
-            self.session.acquire(self.request)
-        except: # should use specific exception
-            self.websocket.send('o')
-            self.websocket.send(
-                close_frame(2010, "Another connection still open", '\n'))
-            self.websocket.close()
-            return StopStreaming()
-
-        if self.open():
-            gevent.joinall((gevent.spawn(self.send),
-                            gevent.spawn(self.receive)))
-        raise StopStreaming()
-
-        #
         start_response(self.status, self._abs_headerlist(environ))
-
-        # send the current body to the network
-        for chunk in self.app_iter:
-            self.write(chunk)
-            self.flush()
-        del self.app_iter
+        #write(self.body)
 
         raise ResponseInterrupt()