emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] 80/119: helpers for handling web socket connections


From: Eric Schulte
Subject: [elpa] 80/119: helpers for handling web socket connections
Date: Mon, 10 Mar 2014 16:57:43 +0000

eschulte pushed a commit to branch master
in repository elpa.

commit 6e9f8168f25660609385b6436fa7de84f2bb032b
Author: Eric Schulte <address@hidden>
Date:   Tue Jan 7 09:26:42 2014 -0700

    helpers for handling web socket connections
---
 .gitignore               |    1 +
 examples/9-web-socket.el |   15 ++--
 web-server.el            |  198 ++++++++++++++++++++++++----------------------
 3 files changed, 113 insertions(+), 101 deletions(-)

diff --git a/.gitignore b/.gitignore
index c531d98..65b8fcb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 *.elc
+stuff
diff --git a/examples/9-web-socket.el b/examples/9-web-socket.el
index 3b39568..1ad9276 100644
--- a/examples/9-web-socket.el
+++ b/examples/9-web-socket.el
@@ -1,11 +1,14 @@
 ;;; web-sockets.el --- communicate via web-sockets
 
-(defvar web-socket-page "<html>
+(defvar web-socket-port 8888)
+
+(defvar web-socket-page
+  (format "<html>
 <head>
 <script type=\"text/javascript\">
 var ws;
 function connect(){
-  ws = new WebSocket(\"ws://localhost:9999/\");
+  ws = new WebSocket(\"ws://localhost:%d/\");
 
   ws.onopen    = function()    { alert(\"connected\"); };
   ws.onmessage = function(msg) { alert(\"Server: \" + msg.data); };
@@ -22,14 +25,12 @@ function close(){ ws.close(); };
 <a href=\"javascript:message()\">message</a>
 <a href=\"javascript:close()\">close</a>
 </body>
-</html>")
+</html>" web-socket-port))
 
 (defun web-socket-server (request)
   (with-slots (process headers) request
-    (ws/web-socket-messages-do headers message
-      ;; (ws/web-socket-send message)
-      (message "GOT:%S" message))
+    (ws-web-socket-connect request 'ws-web-socket-send)
     (ws-response-header process 200 '("Content-type" . "text/html"))
     (process-send-string process web-socket-page)))
 
-(ws-start '(((:GET . ".*") . web-socket-server)) 9999)
+(ws-start '(((:GET . ".*") . web-socket-server)) web-socket-port)
diff --git a/web-server.el b/web-server.el
index cbb68fc..b0cbfa6 100644
--- a/web-server.el
+++ b/web-server.el
@@ -305,8 +305,51 @@ Return non-nil only when parsing is complete."
 
 
 ;;; Web Socket
-(defvar ws/web-socket-handler nil
-  "Function to handle web-socket messages, should take a single argument.")
+(defclass ws-message ()                 ; web socket message object
+  ((process  :initarg :process  :accessor process  :initform "")
+   (pending  :initarg :pending  :accessor pending  :initform "")
+   (active   :initarg :active   :accessor active   :initform nil)
+   (new      :initarg :new      :accessor new      :initform nil)
+   (data     :initarg :data     :accessor data     :initform "")
+   (handler  :initarg :handler  :accessor handler  :initform "")))
+
+(defun ws-web-socket-connect (request handler)
+  "Establish a web socket connection with request.
+If the connection is successful this function will throw
+`:keep-alive' to `close-connection' skipping any remaining code
+in the request handler.  If no web-socket connection is
+established (e.g., because REQUEST is not attempting to establish
+a connection) then no actions are taken and nil is returned.
+
+Second argument HANDLER should be a function of one argument
+which will be called on all complete messages as they are
+received and parsed from the network."
+  (with-slots (process headers) request
+    (when (assoc :SEC-WEBSOCKET-KEY headers)
+      ;; Accept the connection
+      (ws-response-header process 101
+        (cons "Upgrade" "websocket")
+        (cons "Connection" "upgrade")
+        (cons "Sec-WebSocket-Accept"
+              (ws-web-socket-handshake
+               (cdr (assoc :SEC-WEBSOCKET-KEY headers)))))
+      ;; Setup the process filter
+      (set-process-coding-system process 'binary)
+      (set-process-plist
+       process (list :message (make-instance 'ws-message
+                                :handler handler :process process)))
+      (set-process-filter process 'ws-web-socket-filter)
+      (throw 'close-connection :keep-alive))))
+
+(defun ws-web-socket-filter (process string)
+  (let ((message (plist-get (process-plist process) :message)))
+    ;; don't re-start if message is being parsed
+    (if (active message)
+        (setf (new message) string)
+      (setf (pending message) (concat (pending message) string))
+      (setf (active message) t)
+      (ws-web-socket-parse-messages message))
+    (setf (active message) nil)))
 
 (defun int-to-bits (int size)
   (let ((result (make-bool-vector size nil)))
@@ -324,7 +367,7 @@ Return non-nil only when parsing is complete."
                           (prog1 (if bit (expt 2 place) 0) (incf place)))
                         (reverse bits)))))
 
-(defun ws/web-socket-mask (masking-key data)
+(defun ws-web-socket-mask (masking-key data)
   (let ((masking-data (apply #'concat (make-list (+ 1 (/ (length data) 4))
                                                  masking-key))))
     (apply #'string (cl-mapcar #'logxor masking-data data))))
@@ -351,99 +394,66 @@ Return non-nil only when parsing is complete."
 ;; |                     Payload Data continued ...                |
 ;; +---------------------------------------------------------------+
 ;;
-(defun ws-make-web-socket-filter (handler)
+(defun ws-web-socket-parse-messages (message)
   "Web socket filter to pass whole frames to the client.
 See RFC6455."
-  (lexical-let ((my-handler handler))
-    (lambda proc string
-      (catch 'wait ; TODO: this needs more complete partial input handling
-        (when (plist-get (process-plist proc) :active)
-          (let ((pending (plist-get (process-plist proc) :pending)))
-            (set-process-plist proc
-                               (plist-put (process-plist proc)
-                                          :pending (concat pending string))))
-          (throw 'wait nil))
-        ;; set to active
-        (set-process-plist proc (plist-put (process-plist proc) :active t))
-        (let ((index 0))
-          (cl-flet ((bits (length)
-                          (apply #'append
-                                 (mapcar (lambda (int) (int-to-bits int 8))
-                                         (subseq string index (incf index 
length))))))
-            (let ((data (plist-get (process-plist proc) :parsed))
-                  fin rsvs opcode mask pl mask-key)
-              (let ((byte (bits 1)))
-                (setq fin (car byte)
-                      rsvs (subseq byte 1 4)
-                      opcode
-                      (let ((it (bits-to-int (subseq byte 4))))
-                        (case it
-                          (0 :CONTINUATION)
-                          (1 :TEXT)
-                          (2 :BINARY)
-                          ((3 4 5 6 7) :NON-CONTROL)
-                          (9 :PING)
-                          (10 :PONG)
-                          ((11 12 13 14 15) :CONTROL)
-                          ;; If an unknown opcode is received, the receiving
-                          ;; endpoint MUST _Fail the WebSocket Connection_.
-                          (t (ws-error proc "Web Socket Fail: bad opcode %d" 
it))))))
-              (unless (cl-every #'null rsvs)
-                ;; MUST be 0 unless an extension is negotiated that defines
-                ;; meanings for non-zero values.
-                (ws-error proc "Web Socket Fail: non-zero RSV 1 2 or 3"))
-              (let ((byte (bits 1)))
-                (setq mask (car byte)
-                      pl (bits-to-int (subseq byte 1))))
-              (unless (eq mask t)
-                ;; All frames sent from client to server have this bit set to 
1.
-                (ws-error proc "Web Socket Fail: client must mask data"))
-              (cond
-               ((= pl 126) (setq pl (bits-to-int (bits 2))))
-               ((= pl 127) (setq pl (bits-to-int (bits 8)))))
-              (when mask (setq mask-key (subseq string index (incf index 4))))
-              (setq data (concat data
-                                 (ws/web-socket-mask
-                                  mask-key (subseq string index (+ index 
pl)))))
-              ;; set to inactive
-              (set-process-plist proc (plist-put (process-plist proc) :active 
nil))
-              (if fin
-                  (funcall my-handler data) ; call the web-socket handler
-                ;; add parsed data to the process plist
-                (let ((plist (process-plist proc)))
-                  (set-process-plist
-                   (plist-put plist :parsed (concat (plist-get plist :parsed)
-                                                    data))))
-                ;; add any remaining un-parsed network data to pending
-                (when (< (+ index pl) (length string))
-                  (let ((plist (process-plist proc)))
-                    (set-process-plist
-                     (plist-put plist :pending
-                                (concat (substring string (+ index pl))
-                                        (or (plist-get plist :pending) ""))))))
-                ;; possibly re-parse any pending input
-                (when (plist-get (process-plist proc) :pending)
-                  (set-process-plist (plist-put (process-plist proc) :pending 
nil))
-                  (ws-web-socket-filter
-                   proc (plist-get (process-plist proc) :pending)))))))))))
-
-(defmacro ws/web-socket-messages-do (headers variable body)
-  "Helper macro to set the `ws-web-socket-filter' appropriately."
-  `(when (assoc :SEC-WEBSOCKET-KEY ,(identity headers))
-     (ws-response-header process 101
-       (cons "Upgrade" "websocket")
-       (cons "Connection" "upgrade")
-       (cons "Sec-WebSocket-Accept"
-             (ws-web-socket-handshake
-              (cdr (assoc :SEC-WEBSOCKET-KEY headers)))))
-     (set-process-plist process (list :parsed "" :pending nil :active nil))
-     (set-process-coding-system process 'binary)
-     (set-process-filter process (ws-make-web-socket-filter
-                                  (lambda ,(list variable) ,@body)))
-     (throw 'close-connection :keep-alive)))
-
-(defun ws/web-socket-send (string)
-  )
+  (let ((index 0))
+    (cl-flet ((bits (length)
+                    (apply #'append
+                           (mapcar (lambda (int) (int-to-bits int 8))
+                                   (subseq string index (incf index 
length))))))
+      (with-slots (process pending data handler new) message
+        (let (fin rsvs opcode mask pl mask-key)
+          ;; Parse fin bit, rsvs bits and opcode
+          (let ((byte (bits 1)))
+            (setq fin (car byte)
+                  rsvs (subseq byte 1 4)
+                  opcode
+                  (let ((it (bits-to-int (subseq byte 4))))
+                    (case it
+                      (0 :CONTINUATION)
+                      (1 :TEXT)
+                      (2 :BINARY)
+                      ((3 4 5 6 7) :NON-CONTROL)
+                      (9 :PING)
+                      (10 :PONG)
+                      ((11 12 13 14 15) :CONTROL)
+                      ;; If an unknown opcode is received, the receiving
+                      ;; endpoint MUST _Fail the WebSocket Connection_.
+                      (t (ws-error process
+                                   "Web Socket Fail: bad opcode %d" it))))))
+          (unless (cl-every #'null rsvs)
+            ;; MUST be 0 unless an extension is negotiated that defines
+            ;; meanings for non-zero values.
+            (ws-error process "Web Socket Fail: non-zero RSV 1 2 or 3"))
+          ;; Parse mask and payload length
+          (let ((byte (bits 1)))
+            (setq mask (car byte)
+                  pl (bits-to-int (subseq byte 1))))
+          (unless (eq mask t)
+            ;; All frames sent from client to server have this bit set to 1.
+            (ws-error process "Web Socket Fail: client must mask data"))
+          (cond
+           ((= pl 126) (setq pl (bits-to-int (bits 2))))
+           ((= pl 127) (setq pl (bits-to-int (bits 8)))))
+          ;; unmask data
+          (when mask (setq mask-key (subseq string index (incf index 4))))
+          (setq data (concat data
+                             (ws-web-socket-mask
+                              mask-key (subseq string index (+ index pl)))))
+          (if fin
+              ;; wipe the message state and call the handler
+              (let ((it data))
+                (setq data "" active nil pending "" new nil)
+                (funcall handler it))
+            ;; add any remaining un-parsed network data to pending
+            (when (< (+ index pl) (length pending))
+              (setq pending (substring pending (+ index pl)))))))
+      ;; possibly re-parse any pending input
+      (when (new message) (ws-web-socket-parse-messages message)))))
+
+(defun ws-web-socket-send (string)
+  (message "TODO: send %S" string))
 
 
 ;;; Convenience functions to write responses



reply via email to

[Prev in Thread] Current Thread [Next in Thread]