Browse code
(init)
Ed Langley authored on 22/10/2018 06:33:47
Showing 2 changed files
Showing 2 changed files
0 | 3 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,84 @@ |
1 |
+(defpackage :fwoar.web-test |
|
2 |
+ (:use :cl ) |
|
3 |
+ (:export )) |
|
4 |
+(in-package :fwoar.web-test) |
|
5 |
+ |
|
6 |
+(defclass websocket-server (hunchensocket:websocket-acceptor |
|
7 |
+ hunchentoot:easy-acceptor) |
|
8 |
+ ()) |
|
9 |
+ |
|
10 |
+(defgeneric find-websocket-resource (acceptor request)) |
|
11 |
+ |
|
12 |
+(defmethod hunchentoot:acceptor-dispatch-request ((acceptor websocket-server) |
|
13 |
+ (request hunchensocket::websocket-request)) |
|
14 |
+ "Attempt WebSocket connection, else fall back to HTTP" |
|
15 |
+ (cond ((and (member "upgrade" (cl-ppcre:split "\\s*,\\s*" (hunchentoot:header-in* :connection)) |
|
16 |
+ :test #'string-equal) |
|
17 |
+ (string= "websocket" (string-downcase (hunchentoot:header-in* :upgrade)))) |
|
18 |
+ (cond ((setf (hunchensocket:websocket-resource hunchentoot:*request*) |
|
19 |
+ (find-websocket-resource acceptor hunchentoot:*request*)) |
|
20 |
+ ;; Found the websocket resource |
|
21 |
+ (hunchensocket::handle-handshake acceptor hunchentoot:*request* hunchentoot:*reply*) |
|
22 |
+ ;; HACK! the empty string is also important because if there's |
|
23 |
+ ;; no content Hunchentoot will declare the connection closed and |
|
24 |
+ ;; set "Connection: Closed". But there can't be any actual |
|
25 |
+ ;; content since otherwise it will piggyback onto the first |
|
26 |
+ ;; websocket frame, which is interpreted as invalid by the |
|
27 |
+ ;; client. It's also forbidden by the HTTP RFC2616: |
|
28 |
+ ;; |
|
29 |
+ ;; [...] All 1xx (informational), 204 (no content), and 304 |
|
30 |
+ ;; (not modified) responses MUST NOT include a |
|
31 |
+ ;; message-body. [...] |
|
32 |
+ ;; |
|
33 |
+ ;; There is a slight non-conformance here: this trick makes |
|
34 |
+ ;; Hunchentoot send "Content-length: 0". Most browsers don't |
|
35 |
+ ;; seem to care, but RFC2616 kind of implies that is forbidden, |
|
36 |
+ ;; since it says the |
|
37 |
+ ;; |
|
38 |
+ ;; [...] the presence of a message-body is signaled by the |
|
39 |
+ ;; inclusion of a Content-Length or Transfer-Encoding header |
|
40 |
+ ;; field in the requests's message-headers [...] |
|
41 |
+ ;; |
|
42 |
+ ;; Note however, we're sending a response, not a request. |
|
43 |
+ ;; |
|
44 |
+ (values "" nil nil)) |
|
45 |
+ (t |
|
46 |
+ ;; Didn't find the websocket-specific resource, return 404. |
|
47 |
+ (setf (hunchentoot:return-code hunchentoot:*reply*) |
|
48 |
+ hunchentoot:+http-not-found+) |
|
49 |
+ (values nil nil nil)))) |
|
50 |
+ (t |
|
51 |
+ ;; Client is not requesting websockets, let Hunchentoot do its HTTP |
|
52 |
+ ;; thing undisturbed. |
|
53 |
+ (call-next-method)))) |
|
54 |
+ |
|
55 |
+(defclass my-server (websocket-server) |
|
56 |
+ ()) |
|
57 |
+ |
|
58 |
+(defclass chat-room (hunchensocket:websocket-resource) |
|
59 |
+ ((name :initarg :name :initform (error "Name this room!") :reader name)) |
|
60 |
+ (:default-initargs :client-class 'user)) |
|
61 |
+ |
|
62 |
+(defclass user (hunchensocket:websocket-client) |
|
63 |
+ ((name :initarg :user-agent :reader name :initform (error "Name this user!")))) |
|
64 |
+ |
|
65 |
+(defvar *chat-rooms* |
|
66 |
+ (list (make-instance 'chat-room :name "/bongo") |
|
67 |
+ (make-instance 'chat-room :name "/fury"))) |
|
68 |
+ |
|
69 |
+(defmethod find-websocket-resource ((acceptor my-server) request) |
|
70 |
+ (find (hunchentoot:script-name request) |
|
71 |
+ *chat-rooms* :test #'string= :key #'name)) |
|
72 |
+ |
|
73 |
+(defun broadcast (room message &rest args) |
|
74 |
+ (loop for peer in (hunchensocket:clients room) |
|
75 |
+ do (hunchensocket:send-text-message peer (apply #'format nil message args)))) |
|
76 |
+ |
|
77 |
+(defmethod hunchensocket:client-connected ((room chat-room) user) |
|
78 |
+ (broadcast room "~a has joined ~a" (name user) (name room))) |
|
79 |
+ |
|
80 |
+(defmethod hunchensocket:client-disconnected ((room chat-room) user) |
|
81 |
+ (broadcast room "~a has left ~a" (name user) (name room))) |
|
82 |
+ |
|
83 |
+(defmethod hunchensocket:text-message-received ((room chat-room) user message) |
|
84 |
+ (broadcast room "~a says ~a" (name user) message)) |