{"action":"created","comment":{"url":"https://api.github.com/repos/bokeh/bokeh/pulls/comments/38653994","id":38653994,"diff_hunk":"@@ -0,0 +1,111 @@\n+''' Provide a web socket handler for the Bokeh Server application.\n+\n+'''\n+from __future__ import absolute_import, print_function\n+\n+import logging\n+log = logging.getLogger(__name__)\n+\n+from tornado import gen\n+from tornado.websocket import WebSocketHandler\n+\n+from ..exceptions import MessageError, ProtocolError, ValidationError\n+from ..core.server_session import ServerSession\n+from ..protocol import Protocol\n+from ..protocol.receiver import Receiver\n+\n+class WSHandler(WebSocketHandler):\n+    ''' Implements a custom Tornado WebSocketHandler for the Bokeh Server.\n+\n+    '''\n+\n+    clients = set()\n+\n+    def __init__(self, *args, **kw):\n+        super(WSHandler, self).__init__(*args, **kw)\n+        self.session = None\n+\n+    def open(self):\n+        ''' Initialize a connection to a client.\n+\n+        '''\n+        log.info('WebSocket connection opened')\n+\n+        proto_version = self.request.headers.get(\"bokeh-protocol-version\", None)\n+        if not proto_version:\n+            self.close()\n+            raise ProtocolError(\"No protocol version specified\")\n+\n+        try:\n+            protocol = Protocol(proto_version)\n+            self.receiver = Receiver(protocol)\n+            log.debug(\"Receiver created created for %r\", protocol)\n+\n+            self.session = ServerSession(protocol)\n+            log.info(\"ServerSession created (id: %s)\", self.session.id)\n+\n+        except ProtocolError as e:\n+            log.error(\"Could not create new server session, reason: %s\", e)\n+            self.close()\n+            raise\n+\n+        self.clients.add(self)\n+\n+        msg = self.session.protocol.create('ACK', self.session.id)\n+        self.send_message(msg)\n+\n+    @gen.coroutine\n+    def on_message(self, fragment):\n+        ''' Process an individual wire protocol fragment.\n+\n+        Args:\n+            fragment (UTF-8 or bytes) : wire fragment to process\n+\n+        '''\n+        try:\n+            message = yield self.receiver.consume(fragment)\n+        except (MessageError, ProtocolError, ValidationError) as e:\n+            log.error(e)\n+            # TODO (bev) : if self.receiver.failures > MAX FAILUES\n+            raise gen.Return(None)\n+\n+        if message is None:\n+            raise gen.Return(None)\n+\n+        # make sure the session ID from the client message matches\n+        if message.header['sessid'] != self.session.id:","path":"bokeh/server/views/ws.py","position":76,"original_position":76,"commit_id":"2dfbfdbe983ba988a57f3a78200c842e4a590a30","original_commit_id":"7fad722bca8ef6c9a0cd1241f73053bde9dfa813","user":{"login":"havocp","id":218689,"avatar_url":"https://avatars.githubusercontent.com/u/218689?v=3","gravatar_id":"","url":"https://api.github.com/users/havocp","html_url":"https://github.com/havocp","followers_url":"https://api.github.com/users/havocp/followers","following_url":"https://api.github.com/users/havocp/following{/other_user}","gists_url":"https://api.github.com/users/havocp/gists{/gist_id}","starred_url":"https://api.github.com/users/havocp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/havocp/subscriptions","organizations_url":"https://api.github.com/users/havocp/orgs","repos_url":"https://api.github.com/users/havocp/repos","events_url":"https://api.github.com/users/havocp/events{/privacy}","received_events_url":"https://api.github.com/users/havocp/received_events","type":"User","site_admin":false},"body":"what I meant I guess is you could send a special \"my session id is ___\" message as the first thing on the connection and then omit it from each message, then it can't be wrong and doesn't need to be checked per-message\r\n","created_at":"2015-09-03T14:49:12Z","updated_at":"2015-09-03T14:49:12Z","html_url":"https://github.com/bokeh/bokeh/pull/2794#discussion_r38653994","pull_request_url":"https://api.github.com/repos/bokeh/bokeh/pulls/2794","_links":{"self":{"href":"https://api.github.com/repos/bokeh/bokeh/pulls/comments/38653994"},"html":{"href":"https://github.com/bokeh/bokeh/pull/2794#discussion_r38653994"},"pull_request":{"href":"https://api.github.com/repos/bokeh/bokeh/pulls/2794"}}},"pull_request":{"url":"https://api.github.com/repos/bokeh/bokeh/pulls/2794","id":43879434,"html_url":"https://github.com/bokeh/bokeh/pull/2794","diff_url":"https://github.com/bokeh/bokeh/pull/2794.diff","patch_url":"https://github.com/bokeh/bokeh/pull/2794.patch","issue_url":"https://api.github.com/repos/bokeh/bokeh/issues/2794","number":2794,"state":"open","locked":false,"title":"Extreme WIP for new tornado server discussion","user":{"login":"bryevdv","id":1078448,"avatar_url":"https://avatars.githubusercontent.com/u/1078448?v=3","gravatar_id":"","url":"https://api.github.com/users/bryevdv","html_url":"https://github.com/bryevdv","followers_url":"https://api.github.com/users/bryevdv/followers","following_url":"https://api.github.com/users/bryevdv/following{/other_user}","gists_url":"https://api.github.com/users/bryevdv/gists{/gist_id}","starred_url":"https://api.github.com/users/bryevdv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bryevdv/subscriptions","organizations_url":"https://api.github.com/users/bryevdv/orgs","repos_url":"https://api.github.com/users/bryevdv/repos","events_url":"https://api.github.com/users/bryevdv/events{/privacy}","received_events_url":"https://api.github.com/users/bryevdv/received_events","type":"User","site_admin":false},"body":"This PR is intended to be a place to have concrete discussions about technical points in the new server development. I intend to submit a new PR with all commits squashed, when this is ready to merge. \r\n\r\nNotes\r\n-------\r\nTo see some basic operation with messages passed back and forth from client and server, run\r\n```\r\npython bk.py  # basic server\r\n```\r\nand \r\n```\r\npython client.py # basic client\r\n```\r\nThe class/logic in `client.py` is eventually intended to be rolled into `bokeh.session` and then real control, updating of the server will be possible with *no more ugly polling, ever*.\r\n\r\nConcepts\r\n--------\r\n* `WSHandler`  &mdash; takes individual message framents, passes to `Recevier` until a message is completed, then passes message to a handler\r\n* `Receiver` &mdash; responsible for assembling message fragments into complete messages\r\n* `Message` &mdash; define operations on the server or client\r\n* `Protocol` &mdash; defines a specific collection of message revisions and helps automate their creation\r\n* `ServerSession` &mdash; an in-memory document cache for one client connection (e.g., a single browser tab)\r\n* `ServerDocument` encapsulate a Bokeh Document on the server. Manages a work queue that serializes any storage operations on the document. \r\n\r\nWire Protocol\r\n----\r\nModeled loosely after the IPython protocol. A message comprises these sub-parts, sent as individual websocket messags:\r\n```\r\n[\r\n    # these are required\r\n    b'foobarbaz',       # HMAC signature\r\n    b'{header}',        # serialized header dict\r\n    b'{metadata}',      # serialized metadata dict\r\n    b'{content},        # serialized content dict\r\n\r\n    # these are optional, and come in pairs\r\n    b'{buf_header}',    # serialized buffer header dict\r\n    b'array'            # raw buffer payload data, possibly binary\r\n    ...\r\n]\r\n```\r\n\r\nNew Document Idea\r\n----------------\r\n\r\nThe basic idea is for `Document` to be a data structure composed of three parts: `variables`, `models`, `stores`:\r\n```\r\nclass Document(HasProps):\r\n\r\n    # basically a namespace for simple state to be included in Bokeh docs\r\n    vars = Dict(String, Any)\r\n\r\n    # like current models, except all lightweight -- no actual data\r\n    models = Dict(String, Instance(Model))\r\n \r\n    # actual data goes here\r\n    stores = Dict(String, Instance(DataStore))\r\n```\r\n\r\nVarious use-cases are possible: \r\n* sessions can copy everything when the session is created (no updates pushed to sessions)\r\n* sessions could share/be linked to some combination of `vars`, `models`, `stores` (updates get pushed to sessions)\r\n\r\nAdditionally there should be some provision or mode for clients to *store* changes that that push to a session back to the persistent backend. Need to distinguish between changes pushed to session cache vs changes inteded to be saved. GH commit vs push analogy?\r\n\r\nQuestions\r\n-----------\r\n* `Message` subclasses have `handle_server` and `handle_client` methods on them, which are intended used by the server and client respectively to handle incoming messages. Would it be worthwhile to factor out the handlers? There are definitely some nice reasons to do that, but it would also spread out the codebase somewhat even more.\r\n\r\n* The `create` method on the `Protocol`, picks out the right message and calls `create` on the message class. It just passes on any required params as `args` and `kwargs`, but the user who calls `create` has to just know what any additional params are for the specific message type. Maybe there is a better way? \r\n\r\n* Is there any particularly good way to start a tornado server in ipython notebook? Obviously we could just use `subprocess`, etc. but though perhaps there might be some tighter ipython integration possible.\r\n\r\n* `Protocol` is defined in an `__init__` there's probably a better arrangement. Source layout suggestions welcome in general.  \r\n\r\n* Currently, switching between encoded strings/bytes seems a bit ad-hoc and clunky. More consistent approach?\r\n\r\nTodo\r\n-----\r\n* [ ] specify remaining messages/operations\r\n* [ ] implement new document class\r\n* [ ] specify storage API\r\n* [ ] Implement storage API\r\n - [ ] in-memory\r\n - [ ] redis   \r\n* [ ] implement message handlers\r\n* [ ] implement JS client\r\n* [ ] improve python client, `Session` integration\r\n* [ ] admin page\r\n* [ ] index page\r\n* [ ] integrate/delegate auth checking\r\n* [ ] update polling demos to not do polling\r\n\r\n","created_at":"2015-09-01T14:06:44Z","updated_at":"2015-09-03T14:49:12Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b2df8a0bad981e88dcbfc4c46e6734f2f8140f80","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/bokeh/bokeh/pulls/2794/commits","review_comments_url":"https://api.github.com/repos/bokeh/bokeh/pulls/2794/comments","review_comment_url":"https://api.github.com/repos/bokeh/bokeh/pulls/comments{/number}","comments_url":"https://api.github.com/repos/bokeh/bokeh/issues/2794/comments","statuses_url":"https://api.github.com/repos/bokeh/bokeh/statuses/2dfbfdbe983ba988a57f3a78200c842e4a590a30","head":{"label":"bokeh:tornado","ref":"tornado","sha":"2dfbfdbe983ba988a57f3a78200c842e4a590a30","user":{"login":"bokeh","id":8440965,"avatar_url":"https://avatars.githubusercontent.com/u/8440965?v=3","gravatar_id":"","url":"https://api.github.com/users/bokeh","html_url":"https://github.com/bokeh","followers_url":"https://api.github.com/users/bokeh/followers","following_url":"https://api.github.com/users/bokeh/following{/other_user}","gists_url":"https://api.github.com/users/bokeh/gists{/gist_id}","starred_url":"https://api.github.com/users/bokeh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bokeh/subscriptions","organizations_url":"https://api.github.com/users/bokeh/orgs","repos_url":"https://api.github.com/users/bokeh/repos","events_url":"https://api.github.com/users/bokeh/events{/privacy}","received_events_url":"https://api.github.com/users/bokeh/received_events","type":"Organization","site_admin":false},"repo":{"id":3834332,"name":"bokeh","full_name":"bokeh/bokeh","owner":{"login":"bokeh","id":8440965,"avatar_url":"https://avatars.githubusercontent.com/u/8440965?v=3","gravatar_id":"","url":"https://api.github.com/users/bokeh","html_url":"https://github.com/bokeh","followers_url":"https://api.github.com/users/bokeh/followers","following_url":"https://api.github.com/users/bokeh/following{/other_user}","gists_url":"https://api.github.com/users/bokeh/gists{/gist_id}","starred_url":"https://api.github.com/users/bokeh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bokeh/subscriptions","organizations_url":"https://api.github.com/users/bokeh/orgs","repos_url":"https://api.github.com/users/bokeh/repos","events_url":"https://api.github.com/users/bokeh/events{/privacy}","received_events_url":"https://api.github.com/users/bokeh/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/bokeh/bokeh","description":"Interactive Web Plotting for Python","fork":false,"url":"https://api.github.com/repos/bokeh/bokeh","forks_url":"https://api.github.com/repos/bokeh/bokeh/forks","keys_url":"https://api.github.com/repos/bokeh/bokeh/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bokeh/bokeh/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bokeh/bokeh/teams","hooks_url":"https://api.github.com/repos/bokeh/bokeh/hooks","issue_events_url":"https://api.github.com/repos/bokeh/bokeh/issues/events{/number}","events_url":"https://api.github.com/repos/bokeh/bokeh/events","assignees_url":"https://api.github.com/repos/bokeh/bokeh/assignees{/user}","branches_url":"https://api.github.com/repos/bokeh/bokeh/branches{/branch}","tags_url":"https://api.github.com/repos/bokeh/bokeh/tags","blobs_url":"https://api.github.com/repos/bokeh/bokeh/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bokeh/bokeh/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bokeh/bokeh/git/refs{/sha}","trees_url":"https://api.github.com/repos/bokeh/bokeh/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bokeh/bokeh/statuses/{sha}","languages_url":"https://api.github.com/repos/bokeh/bokeh/languages","stargazers_url":"https://api.github.com/repos/bokeh/bokeh/stargazers","contributors_url":"https://api.github.com/repos/bokeh/bokeh/contributors","subscribers_url":"https://api.github.com/repos/bokeh/bokeh/subscribers","subscription_url":"https://api.github.com/repos/bokeh/bokeh/subscription","commits_url":"https://api.github.com/repos/bokeh/bokeh/commits{/sha}","git_commits_url":"https://api.github.com/repos/bokeh/bokeh/git/commits{/sha}","comments_url":"https://api.github.com/repos/bokeh/bokeh/comments{/number}","issue_comment_url":"https://api.github.com/repos/bokeh/bokeh/issues/comments{/number}","contents_url":"https://api.github.com/repos/bokeh/bokeh/contents/{+path}","compare_url":"https://api.github.com/repos/bokeh/bokeh/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bokeh/bokeh/merges","archive_url":"https://api.github.com/repos/bokeh/bokeh/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bokeh/bokeh/downloads","issues_url":"https://api.github.com/repos/bokeh/bokeh/issues{/number}","pulls_url":"https://api.github.com/repos/bokeh/bokeh/pulls{/number}","milestones_url":"https://api.github.com/repos/bokeh/bokeh/milestones{/number}","notifications_url":"https://api.github.com/repos/bokeh/bokeh/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bokeh/bokeh/labels{/name}","releases_url":"https://api.github.com/repos/bokeh/bokeh/releases{/id}","created_at":"2012-03-26T15:40:01Z","updated_at":"2015-09-02T22:35:33Z","pushed_at":"2015-09-03T13:49:19Z","git_url":"git://github.com/bokeh/bokeh.git","ssh_url":"git@github.com:bokeh/bokeh.git","clone_url":"https://github.com/bokeh/bokeh.git","svn_url":"https://github.com/bokeh/bokeh","homepage":"http://bokeh.pydata.org/en/latest/","size":165042,"stargazers_count":3178,"watchers_count":3178,"language":"CoffeeScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":556,"mirror_url":null,"open_issues_count":514,"forks":556,"open_issues":514,"watchers":3178,"default_branch":"master"}},"base":{"label":"bokeh:master","ref":"master","sha":"70455c469d3563697d20118e808bf1ab9d36ae3f","user":{"login":"bokeh","id":8440965,"avatar_url":"https://avatars.githubusercontent.com/u/8440965?v=3","gravatar_id":"","url":"https://api.github.com/users/bokeh","html_url":"https://github.com/bokeh","followers_url":"https://api.github.com/users/bokeh/followers","following_url":"https://api.github.com/users/bokeh/following{/other_user}","gists_url":"https://api.github.com/users/bokeh/gists{/gist_id}","starred_url":"https://api.github.com/users/bokeh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bokeh/subscriptions","organizations_url":"https://api.github.com/users/bokeh/orgs","repos_url":"https://api.github.com/users/bokeh/repos","events_url":"https://api.github.com/users/bokeh/events{/privacy}","received_events_url":"https://api.github.com/users/bokeh/received_events","type":"Organization","site_admin":false},"repo":{"id":3834332,"name":"bokeh","full_name":"bokeh/bokeh","owner":{"login":"bokeh","id":8440965,"avatar_url":"https://avatars.githubusercontent.com/u/8440965?v=3","gravatar_id":"","url":"https://api.github.com/users/bokeh","html_url":"https://github.com/bokeh","followers_url":"https://api.github.com/users/bokeh/followers","following_url":"https://api.github.com/users/bokeh/following{/other_user}","gists_url":"https://api.github.com/users/bokeh/gists{/gist_id}","starred_url":"https://api.github.com/users/bokeh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bokeh/subscriptions","organizations_url":"https://api.github.com/users/bokeh/orgs","repos_url":"https://api.github.com/users/bokeh/repos","events_url":"https://api.github.com/users/bokeh/events{/privacy}","received_events_url":"https://api.github.com/users/bokeh/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/bokeh/bokeh","description":"Interactive Web Plotting for Python","fork":false,"url":"https://api.github.com/repos/bokeh/bokeh","forks_url":"https://api.github.com/repos/bokeh/bokeh/forks","keys_url":"https://api.github.com/repos/bokeh/bokeh/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bokeh/bokeh/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bokeh/bokeh/teams","hooks_url":"https://api.github.com/repos/bokeh/bokeh/hooks","issue_events_url":"https://api.github.com/repos/bokeh/bokeh/issues/events{/number}","events_url":"https://api.github.com/repos/bokeh/bokeh/events","assignees_url":"https://api.github.com/repos/bokeh/bokeh/assignees{/user}","branches_url":"https://api.github.com/repos/bokeh/bokeh/branches{/branch}","tags_url":"https://api.github.com/repos/bokeh/bokeh/tags","blobs_url":"https://api.github.com/repos/bokeh/bokeh/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bokeh/bokeh/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bokeh/bokeh/git/refs{/sha}","trees_url":"https://api.github.com/repos/bokeh/bokeh/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bokeh/bokeh/statuses/{sha}","languages_url":"https://api.github.com/repos/bokeh/bokeh/languages","stargazers_url":"https://api.github.com/repos/bokeh/bokeh/stargazers","contributors_url":"https://api.github.com/repos/bokeh/bokeh/contributors","subscribers_url":"https://api.github.com/repos/bokeh/bokeh/subscribers","subscription_url":"https://api.github.com/repos/bokeh/bokeh/subscription","commits_url":"https://api.github.com/repos/bokeh/bokeh/commits{/sha}","git_commits_url":"https://api.github.com/repos/bokeh/bokeh/git/commits{/sha}","comments_url":"https://api.github.com/repos/bokeh/bokeh/comments{/number}","issue_comment_url":"https://api.github.com/repos/bokeh/bokeh/issues/comments{/number}","contents_url":"https://api.github.com/repos/bokeh/bokeh/contents/{+path}","compare_url":"https://api.github.com/repos/bokeh/bokeh/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bokeh/bokeh/merges","archive_url":"https://api.github.com/repos/bokeh/bokeh/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bokeh/bokeh/downloads","issues_url":"https://api.github.com/repos/bokeh/bokeh/issues{/number}","pulls_url":"https://api.github.com/repos/bokeh/bokeh/pulls{/number}","milestones_url":"https://api.github.com/repos/bokeh/bokeh/milestones{/number}","notifications_url":"https://api.github.com/repos/bokeh/bokeh/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bokeh/bokeh/labels{/name}","releases_url":"https://api.github.com/repos/bokeh/bokeh/releases{/id}","created_at":"2012-03-26T15:40:01Z","updated_at":"2015-09-02T22:35:33Z","pushed_at":"2015-09-03T13:49:19Z","git_url":"git://github.com/bokeh/bokeh.git","ssh_url":"git@github.com:bokeh/bokeh.git","clone_url":"https://github.com/bokeh/bokeh.git","svn_url":"https://github.com/bokeh/bokeh","homepage":"http://bokeh.pydata.org/en/latest/","size":165042,"stargazers_count":3178,"watchers_count":3178,"language":"CoffeeScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":556,"mirror_url":null,"open_issues_count":514,"forks":556,"open_issues":514,"watchers":3178,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/bokeh/bokeh/pulls/2794"},"html":{"href":"https://github.com/bokeh/bokeh/pull/2794"},"issue":{"href":"https://api.github.com/repos/bokeh/bokeh/issues/2794"},"comments":{"href":"https://api.github.com/repos/bokeh/bokeh/issues/2794/comments"},"review_comments":{"href":"https://api.github.com/repos/bokeh/bokeh/pulls/2794/comments"},"review_comment":{"href":"https://api.github.com/repos/bokeh/bokeh/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/bokeh/bokeh/pulls/2794/commits"},"statuses":{"href":"https://api.github.com/repos/bokeh/bokeh/statuses/2dfbfdbe983ba988a57f3a78200c842e4a590a30"}}}}