-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.scm
executable file
·235 lines (195 loc) · 7.7 KB
/
server.scm
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#! /bin/sh
#| -*- scheme -*-
exec csi -include-path /usr/local/share/scheme -s $0 "$@"
|#
(use srfi-1)
(use srfi-4)
(use spiffy)
(use intarweb)
(use uri-common)
(use base64)
(use simple-sha1)
(define-record-type websocket
(make-websocket inbound-port outbound-port send-bytes read-frame-proc)
websocket?
(inbound-port websocket-inbound-port)
(outbound-port websocket-outbound-port)
(send-bytes websocket-send-bytes)
(read-frame-proc websocket-read-frame-proc))
(define (string->bytes str)
;; XXX this wont work unless it's all ascii.
(let* ((lst (map char->integer (string->list str)))
(bv (make-u8vector (length lst))))
(let loop ((lst lst)
(pos 0))
(if (null? lst) bv
(begin
(u8vector-set! bv pos (car lst))
(loop (cdr lst) (+ pos 1)))))))
(define (hex-string->string hexstr)
;; convert a string like "a745ff12" to a string
(let ((result (make-string (/ (string-length hexstr) 2))))
(let loop ((hexs (string->list hexstr))
(i 0))
(if (< (length hexs) 2)
result
(let ((ascii (string->number (string (car hexs) (cadr hexs)) 16)))
(string-set! result i (integer->char ascii))
(loop (cddr hexs)
(+ i 1)))))))
(define (websocket-send-frame ws opcode data last-frame)
(let* ((frame-fin (if last-frame 1 0))
(frame-rsv1 0)
(frame-rsv2 0)
(frame-rsv3 0)
(frame-opcode opcode)
(octet0 (bitwise-ior (arithmetic-shift frame-fin 7)
(arithmetic-shift frame-rsv1 6)
(arithmetic-shift frame-rsv2 5)
(arithmetic-shift frame-rsv3 4)
frame-opcode))
(frame-masked 0)
(frame-payload-length (cond ((< (u8vector-length data) 126)
(u8vector-length data))
((< (u8vector-length data) 65536) 126)
(else 127)))
(octet1 (bitwise-ior (arithmetic-shift frame-masked 7)
frame-payload-length))
(outbound-port (websocket-outbound-port ws)))
(write-u8vector (u8vector octet0 octet1) outbound-port)
(write-u8vector
(cond
((= frame-payload-length 126)
(u8vector
(arithmetic-shift (bitwise-and (u8vector-length data) 65280) -8)
(bitwise-and (u8vector-length data) 255)))
((= frame-payload-length 127)
(u8vector
0 0 0 0
(arithmetic-shift
(bitwise-and (u8vector-length data) 4278190080) -24)
(arithmetic-shift
(bitwise-and (u8vector-length data) 16711680) -16)
(arithmetic-shift
(bitwise-and (u8vector-length data) 65280) -8)
(bitwise-and (u8vector-length data) 255)))
(else (u8vector)))
outbound-port)
(write-u8vector data outbound-port)
#t))
(define (websocket-send ws data)
;; XXX break up large data into multiple frames?
(websocket-send-frame ws 1 data #t))
(define (websocket-read-frame-payload inbound-port frame-payload-length
frame-masked frame-masking-key)
(let ((masked-data (read-u8vector frame-payload-length inbound-port)))
(cond (frame-masked
(let ((unmasked-data (make-u8vector frame-payload-length)))
(let loop ((pos 0)
(mask-pos 0))
(cond ((= pos frame-payload-length) unmasked-data)
(else
(let ((octet (u8vector-ref masked-data pos))
(mask (vector-ref frame-masking-key mask-pos)))
(u8vector-set!
unmasked-data pos (bitwise-xor octet mask))
(loop (+ pos 1) (modulo (+ mask-pos 1) 4))))))
unmasked-data))
(else
masked-data))))
(define (websocket-read-frame ws)
(let* ((inbound-port (websocket-inbound-port ws))
;; first byte
(b0 (read-byte inbound-port)))
(cond
((eof-object? b0) b0)
(else
(let* ((frame-fin (> (bitwise-and b0 128) 0))
(frame-opcode (bitwise-and b0 15))
;; second byte
(b1 (read-byte inbound-port))
(frame-masked (> (bitwise-and b1 128) 0))
(frame-payload-length (bitwise-and b1 127)))
(cond ((= frame-payload-length 126)
(let ((bl0 (read-byte inbound-port))
(bl1 (read-byte inbound-port)))
(set! frame-payload-length (+ (arithmetic-shift bl0 8) bl1))))
((= frame-payload-length 127)
(error "8 byte payload length unsupported")))
(let* ((frame-masking-key
(if frame-masked
(let* ((fm0 (read-byte inbound-port))
(fm1 (read-byte inbound-port))
(fm2 (read-byte inbound-port))
(fm3 (read-byte inbound-port)))
(vector fm0 fm1 fm2 fm3))
#f)))
(cond
((= frame-opcode 1)
;; (if (= frame-fin 1) ;; something?
(websocket-read-frame-payload inbound-port frame-payload-length
frame-masked frame-masking-key))
((= frame-opcode 8)
;; eof frame
#!eof)
((= frame-opcode 10)
;; pong frame
;; we aren't required to respond to an unsolicited pong
#t)
(else
(error "websocket got unhandled opcode: " frame-opcode "\n")
#f))))))))
(define (websocket-close ws)
(websocket-send-frame ws 8 (make-u8vector 0) #t))
(define (sha1-sum in-bv)
(hex-string->string (string->sha1sum in-bv)))
(define (websocket-compute-handshake client-key)
(let* ((key-and-magic
(string-append client-key "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
(key-and-magic-sha1 (sha1-sum key-and-magic)))
(base64-encode key-and-magic-sha1)))
(define (sec-websocket-accept-unparser header-contents)
(map (lambda (header-content)
(car (vector-ref header-content 0)))
header-contents))
(header-unparsers
(alist-update! 'sec-websocket-accept
sec-websocket-accept-unparser
(header-unparsers)))
(define (websocket-accept)
(let* ((headers (request-headers (current-request)))
(client-key (header-value 'sec-websocket-key headers))
(ws-handshake (websocket-compute-handshake client-key))
(ws (make-websocket
(request-port (current-request))
(response-port (current-response))
websocket-send websocket-read-frame)))
(with-headers
`((upgrade ("WebSocket" . #f))
(connection (upgrade . #t))
(sec-websocket-accept (,ws-handshake . #t)))
(lambda ()
(send-response status: 'switching-protocols)))
ws))
(define (make-websocket-handler app-code)
(lambda (spiffy-continue)
(cond ((equal? (uri-path (request-uri (current-request))) '(/ "web-socket"))
(let ((ws (websocket-accept)))
(app-code ws)))
((equal? (uri-path (request-uri (current-request))) '(/ ""))
((handle-file) "index.html"))
(else
(spiffy-continue)))))
(define (application-code ws)
(websocket-send ws (string->bytes "testing"))
(let ((data (websocket-read-frame ws)))
(display "got from browser: ")
(write (apply string (map integer->char (u8vector->list data))))
(newline)
(websocket-close ws)
))
(vhost-map `(("localhost" . ,(make-websocket-handler application-code))))
(server-port 8888)
;; (root-path "./web")
(debug-log (current-error-port))
(start-server)