Removing out-dated WebSocket implementation
This commit is contained in:
parent
678f0e9136
commit
0c96946bdd
|
@ -8,7 +8,6 @@
|
||||||
@include-section["http-client.scrbl"]
|
@include-section["http-client.scrbl"]
|
||||||
@include-section["url.scrbl"]
|
@include-section["url.scrbl"]
|
||||||
@include-section["uri-codec.scrbl"]
|
@include-section["uri-codec.scrbl"]
|
||||||
@include-section["websocket.scrbl"]
|
|
||||||
@include-section["ftp.scrbl"]
|
@include-section["ftp.scrbl"]
|
||||||
@include-section["sendurl.scrbl"]
|
@include-section["sendurl.scrbl"]
|
||||||
@include-section["smtp.scrbl"]
|
@include-section["smtp.scrbl"]
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
#lang scribble/doc
|
|
||||||
@(require "common.rkt"
|
|
||||||
scribble/bnf
|
|
||||||
(for-label net/url unstable/contract web-server/http racket/list
|
|
||||||
racket/async-channel
|
|
||||||
(prefix-in raw: (for-label net/tcp-unit))
|
|
||||||
net/websocket
|
|
||||||
net/websocket/client
|
|
||||||
net/websocket/server
|
|
||||||
net/websocket/conn))
|
|
||||||
|
|
||||||
@title[#:tag "websocket"]{WebSocket}
|
|
||||||
|
|
||||||
@defmodule[net/websocket]
|
|
||||||
|
|
||||||
The @racketmodname[net/websocket] library provides
|
|
||||||
utilities to run and communicate with WebSocket servers,
|
|
||||||
as specified in @link["http://www.whatwg.org/specs/web-socket-protocol/"]{the WebSocket protocol} IETF draft
|
|
||||||
as of August 16th, 2010.
|
|
||||||
|
|
||||||
This module provides the exports from @racketmodname[net/websocket/client] and @racketmodname[net/websocket/server].
|
|
||||||
|
|
||||||
@section{Client API}
|
|
||||||
|
|
||||||
@defmodule[net/websocket/client]
|
|
||||||
|
|
||||||
@defproc[(ws-url? [x any/c]) boolean?]{ Returns true if @racket[x] is a @racket[url?] and has a @racket[url-scheme] equal to @litchar["ws"] or @litchar["wss"]. }
|
|
||||||
|
|
||||||
@defproc[(wss-url? [x any/c]) boolean?]{ Returns true if @racket[x] is a @racket[url?] and has a @racket[url-scheme] equal to @litchar["wss"]. }
|
|
||||||
|
|
||||||
@defproc[(ws-connect [u ws-url?]
|
|
||||||
[#:headers headers (listof header?) empty])
|
|
||||||
open-ws-conn?]{
|
|
||||||
Connects to the WebSocket server specified by @racket[u], providing @racket[headers] as additional headers.
|
|
||||||
Returns the connection handle.
|
|
||||||
}
|
|
||||||
|
|
||||||
This module also provides the exports from @racketmodname[net/websocket/conn].
|
|
||||||
|
|
||||||
@section{Server API}
|
|
||||||
|
|
||||||
@defmodule[net/websocket/server]
|
|
||||||
|
|
||||||
@defproc[(ws-serve [conn-handle (open-ws-conn? any/c . -> . void)]
|
|
||||||
[#:conn-headers
|
|
||||||
conn-headers
|
|
||||||
(bytes? (listof header?) . -> . (values (listof header?) any/c))
|
|
||||||
(λ (b hs) (values empty (void)))]
|
|
||||||
[#:tcp@ tcp@ (unit/c (import) (export tcp^)) raw:tcp@]
|
|
||||||
[#:port port tcp-listen-port? 80]
|
|
||||||
[#:listen-ip listen-ip (or/c string? false/c) #f]
|
|
||||||
[#:max-waiting max-waiting integer? 4]
|
|
||||||
[#:timeout timeout integer? (* 60 60)]
|
|
||||||
[#:confirmation-channel
|
|
||||||
confirm-ch
|
|
||||||
(or/c false/c async-channel?)
|
|
||||||
#f])
|
|
||||||
(-> void)]{
|
|
||||||
|
|
||||||
Starts a WebSocket server where each new connection uses @racket[conn-headers] to compute
|
|
||||||
what headers the client receives based on the client's request line and headers. @racket[conn-headers]
|
|
||||||
also returns a piece of state that will be passed to @racket[conn-handle] as its second argument.
|
|
||||||
After the connection handshake is finished, @racket[conn-handle] receives the connection and is in
|
|
||||||
sole control until the WebSocket connection completes.
|
|
||||||
|
|
||||||
All other arguments are used as in a @secref["dispatch-server-unit" #:doc '(lib "web-server/scribblings/web-server-internal.scrbl")]. Similarly, the return result is a function that shuts down the server, just like a dispatch server.
|
|
||||||
|
|
||||||
The @racket[#:tcp@] keyword is provided for building an SSL server.
|
|
||||||
}
|
|
||||||
|
|
||||||
This module also provides the exports from @racketmodname[net/websocket/conn].
|
|
||||||
|
|
||||||
@section{Connections}
|
|
||||||
|
|
||||||
@defmodule[net/websocket/conn]
|
|
||||||
|
|
||||||
WebSocket connection are synchronizable events.
|
|
||||||
|
|
||||||
@defparam[framing-mode mode (symbols 'old 'new)]{ Controls whether framing is as before August 16th, 2010 or after. (Most Web browsers currently support only @racket['old] and they are incompatible, so you must choose the correct one.) Defaults to @racket['old].}
|
|
||||||
|
|
||||||
@defproc[(ws-conn? [x any/c]) boolean?]{ Returns true if @racket[x] is a WebSocket connection. }
|
|
||||||
|
|
||||||
@defproc[(open-ws-conn? [x any/c]) boolean?]{ Returns true if @racket[x] is an open WebSocket connection. }
|
|
||||||
|
|
||||||
@defproc[(ws-conn-line [ws ws-conn?]) bytes?]{ Returns the request/response line of the WebSocket connection. }
|
|
||||||
@defproc[(ws-conn-closed? [ws ws-conn?]) boolean?]{ Returns true if the WebSocket connection has been closed. }
|
|
||||||
@defproc[(ws-conn-headers [ws ws-conn?]) (listof header?)]{ Returns the headers of the WebSocket connection. }
|
|
||||||
|
|
||||||
WebSocket connection support only blocking calls:
|
|
||||||
|
|
||||||
@defproc[(ws-send! [ws open-ws-conn?] [s string?]) void]{ Sends @racket[s] over @racket[ws]. }
|
|
||||||
@defproc[(ws-recv [ws open-ws-conn?]) (or/c string? eof-object?)]{ Receives a string from @racket[ws]. Returns @racket[eof] if the other end closes the connection. }
|
|
||||||
|
|
||||||
@defproc[(ws-close! [ws open-ws-conn?]) void]{ Closes @racket[ws]. }
|
|
||||||
|
|
||||||
@section{Example}
|
|
||||||
|
|
||||||
This is a WebSocket echo server compatible with the browser origin security model:
|
|
||||||
|
|
||||||
@racketblock[
|
|
||||||
(ws-serve
|
|
||||||
#:port 8080
|
|
||||||
(λ (wsc _)
|
|
||||||
(let loop ()
|
|
||||||
(define m (ws-recv wsc))
|
|
||||||
(printf "~a\n" m)
|
|
||||||
(unless (eof-object? m)
|
|
||||||
(ws-send! wsc m)
|
|
||||||
(loop))))
|
|
||||||
#:conn-headers
|
|
||||||
(λ (_ hs)
|
|
||||||
(define origin
|
|
||||||
(header-value (headers-assq* #"Origin" hs)))
|
|
||||||
(values
|
|
||||||
(list
|
|
||||||
(make-header #"Sec-WebSocket-Origin" origin)
|
|
||||||
(make-header #"Sec-WebSocket-Location"
|
|
||||||
#"ws://localhost:8080/"))
|
|
||||||
#f)))
|
|
||||||
]
|
|
|
@ -1,4 +1,3 @@
|
||||||
#lang info
|
#lang info
|
||||||
|
|
||||||
(define test-responsibles '(("websocket.rkt" jay)
|
(define test-responsibles '(("url-port.rkt" jay)))
|
||||||
("url-port.rkt" jay)))
|
|
||||||
|
|
|
@ -14,8 +14,7 @@
|
||||||
(prefix-in cookie: "cookie.rkt")
|
(prefix-in cookie: "cookie.rkt")
|
||||||
(prefix-in encoders: "encoders.rkt")
|
(prefix-in encoders: "encoders.rkt")
|
||||||
(prefix-in mime: "mime.rkt")
|
(prefix-in mime: "mime.rkt")
|
||||||
(prefix-in url-port: "url-port.rkt")
|
(prefix-in url-port: "url-port.rkt"))
|
||||||
(prefix-in websocket: "websocket.rkt"))
|
|
||||||
|
|
||||||
(define (tests)
|
(define (tests)
|
||||||
(test do (ip:tests)
|
(test do (ip:tests)
|
||||||
|
@ -28,7 +27,6 @@
|
||||||
do (cookie:tests)
|
do (cookie:tests)
|
||||||
do (encoders:tests)
|
do (encoders:tests)
|
||||||
do (mime:tests)
|
do (mime:tests)
|
||||||
do (url-port:tests)
|
do (url-port:tests)))
|
||||||
do (websocket:tests)))
|
|
||||||
|
|
||||||
(tests)
|
(tests)
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
#lang racket/base
|
|
||||||
(require tests/stress
|
|
||||||
net/websocket
|
|
||||||
net/url
|
|
||||||
racket/async-channel)
|
|
||||||
|
|
||||||
(fit "websocket echo server"
|
|
||||||
500
|
|
||||||
(λ (n)
|
|
||||||
(define confirm (make-async-channel))
|
|
||||||
(define shutdown!
|
|
||||||
(ws-serve #:port 0
|
|
||||||
#:confirmation-channel confirm
|
|
||||||
(λ (wsc _)
|
|
||||||
(let loop ()
|
|
||||||
(define m (ws-recv wsc))
|
|
||||||
(unless (eof-object? m)
|
|
||||||
(ws-send! wsc m)
|
|
||||||
(loop))))))
|
|
||||||
(define port (async-channel-get confirm))
|
|
||||||
|
|
||||||
(define THREADS 10)
|
|
||||||
(define REQS n)
|
|
||||||
|
|
||||||
(for-each thread-wait
|
|
||||||
(for/list ([t (in-range THREADS)])
|
|
||||||
(thread
|
|
||||||
(λ ()
|
|
||||||
(define conn (ws-connect (string->url (format "ws://localhost:~a" port))))
|
|
||||||
(for ([r (in-range REQS)])
|
|
||||||
(ws-send! conn "ping")
|
|
||||||
(ws-recv conn))))))
|
|
||||||
|
|
||||||
(shutdown!)))
|
|
||||||
|
|
||||||
(module+ test
|
|
||||||
(module config info
|
|
||||||
(define random? #t)))
|
|
|
@ -1,97 +0,0 @@
|
||||||
#lang racket
|
|
||||||
(require net/websocket/client
|
|
||||||
net/websocket/server
|
|
||||||
net/websocket/conn
|
|
||||||
net/websocket/handshake
|
|
||||||
racket/async-channel
|
|
||||||
net/url
|
|
||||||
rackunit
|
|
||||||
tests/net/available
|
|
||||||
tests/eli-tester)
|
|
||||||
|
|
||||||
(define RANDOM-K 100)
|
|
||||||
|
|
||||||
(provide tests)
|
|
||||||
(module+ main (tests))
|
|
||||||
(define (tests)
|
|
||||||
(test
|
|
||||||
(for ([i (in-range RANDOM-K)])
|
|
||||||
(define o (random 256))
|
|
||||||
(define t (random 256))
|
|
||||||
(define bot (if (o . < . t) o t))
|
|
||||||
(define top (if (o . < . t) t o))
|
|
||||||
(define botc (integer->char bot))
|
|
||||||
(define topc (integer->char top))
|
|
||||||
(test #:failure-prefix (format "~a / ~a" botc topc)
|
|
||||||
(<= bot (char->integer (random-char-between botc topc)) top)))
|
|
||||||
|
|
||||||
(for ([i (in-range RANDOM-K)])
|
|
||||||
(test (char-alphabetic? (random-alpha-char))))
|
|
||||||
|
|
||||||
(count-spaces "") => 0
|
|
||||||
(count-spaces " ") => 3
|
|
||||||
(count-spaces (make-string RANDOM-K #\space)) => RANDOM-K
|
|
||||||
|
|
||||||
(count-spaces "18x 6]8vM;54 *(5: { U1]8 z [ 8") => 12
|
|
||||||
(count-spaces "1_ tx7X d < nw 334J702) 7]o}` 0") => 10
|
|
||||||
|
|
||||||
(for ([i (in-range RANDOM-K)])
|
|
||||||
(define len (add1 i))
|
|
||||||
(define s (make-string len #\0))
|
|
||||||
(define how-many (random len))
|
|
||||||
(test (count-spaces (add-spaces how-many s)) => how-many))
|
|
||||||
|
|
||||||
(remove-alphas "A0A") => "0"
|
|
||||||
(remove-alphas "0") => "0"
|
|
||||||
(remove-alphas (make-string RANDOM-K #\A)) => ""
|
|
||||||
|
|
||||||
(remove-alphas "18x 6]8vM;54 *(5: { U1]8 z [ 8") => "1868545188"
|
|
||||||
(remove-alphas "1_ tx7X d < nw 334J702) 7]o}` 0") => "1733470270"
|
|
||||||
|
|
||||||
(for ([i (in-range RANDOM-K)])
|
|
||||||
(define s (number->string i))
|
|
||||||
(test (remove-alphas (add-alphas s)) => s))
|
|
||||||
|
|
||||||
(key->number "18x 6]8vM;54 *(5: { U1]8 z [ 8") => 155712099
|
|
||||||
(key->number "1_ tx7X d < nw 334J702) 7]o}` 0") => 173347027
|
|
||||||
|
|
||||||
(for ([i (in-range RANDOM-K)])
|
|
||||||
(test (key->number (number->key i)) => i))
|
|
||||||
|
|
||||||
(for ([i (in-range RANDOM-K)])
|
|
||||||
(define-values (k1 k2 k3 ans) (generate-key))
|
|
||||||
(test (handshake-solution k1 k2 k3) => ans))
|
|
||||||
|
|
||||||
(handshake-solution "18x 6]8vM;54 *(5: { U1]8 z [ 8"
|
|
||||||
"1_ tx7X d < nw 334J702) 7]o}` 0"
|
|
||||||
#"Tm[K T2u")
|
|
||||||
=> #"fQJ,fN/4F4!~K~MH"
|
|
||||||
|
|
||||||
(local [(define (test-echo-server)
|
|
||||||
(define r (number->string (random 1000)))
|
|
||||||
(define confirm (make-async-channel))
|
|
||||||
(define shutdown!
|
|
||||||
(ws-serve #:port 0
|
|
||||||
#:confirmation-channel confirm
|
|
||||||
(λ (wsc _)
|
|
||||||
(let loop ()
|
|
||||||
(define m (ws-recv wsc))
|
|
||||||
(unless (eof-object? m)
|
|
||||||
(ws-send! wsc m)
|
|
||||||
(loop))))))
|
|
||||||
(define p (async-channel-get confirm))
|
|
||||||
(define conn
|
|
||||||
(ws-connect (string->url (format "ws://localhost:~a" p))))
|
|
||||||
(test (ws-send! conn r)
|
|
||||||
(ws-recv conn) => r
|
|
||||||
(ws-send! conn "a")
|
|
||||||
(ws-recv conn) => "a"
|
|
||||||
(ws-close! conn))
|
|
||||||
(test (shutdown!)))]
|
|
||||||
(when (tcp-localhost-available?)
|
|
||||||
(test #:failure-prefix "old"
|
|
||||||
(parameterize ([framing-mode 'old]) (test-echo-server))
|
|
||||||
#:failure-prefix "new"
|
|
||||||
(parameterize ([framing-mode 'new]) (test-echo-server)))))))
|
|
||||||
|
|
||||||
(module+ test (require (submod ".." main))) ; for raco test & drdr
|
|
|
@ -1,38 +0,0 @@
|
||||||
#lang racket
|
|
||||||
(require net/websocket
|
|
||||||
web-server/http
|
|
||||||
racket/runtime-path
|
|
||||||
web-server/templates
|
|
||||||
web-server/servlet-env)
|
|
||||||
|
|
||||||
(module test racket/base)
|
|
||||||
|
|
||||||
(framing-mode 'old)
|
|
||||||
|
|
||||||
(define stop-ws!
|
|
||||||
(ws-serve (λ (wsc _)
|
|
||||||
(let loop ()
|
|
||||||
(define m (ws-recv wsc))
|
|
||||||
(printf "~a\n" m)
|
|
||||||
(unless (eof-object? m)
|
|
||||||
(ws-send! wsc m)
|
|
||||||
(loop))))
|
|
||||||
#:conn-headers
|
|
||||||
(λ (_ hs)
|
|
||||||
(define origin (header-value (headers-assq* #"Origin" hs)))
|
|
||||||
(values (list (make-header #"Sec-WebSocket-Origin" origin)
|
|
||||||
(make-header #"Sec-WebSocket-Location" #"ws://localhost:8080/"))
|
|
||||||
#f))
|
|
||||||
#:port 8080))
|
|
||||||
|
|
||||||
(define-runtime-path example-pth ".")
|
|
||||||
|
|
||||||
(serve/servlet (λ (req)
|
|
||||||
(response/full
|
|
||||||
200 #"Okay"
|
|
||||||
(current-seconds) TEXT/HTML-MIME-TYPE
|
|
||||||
empty
|
|
||||||
(list (string->bytes/utf-8 (include-template "index.html")))))
|
|
||||||
#:servlet-path "/"
|
|
||||||
#:port 8081
|
|
||||||
#:extra-files-paths (list example-pth))
|
|
|
@ -1,25 +0,0 @@
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
|
||||||
<title>Websocket Test</title>
|
|
||||||
<link rel="stylesheet" href="style.css" type="text/css"/>
|
|
||||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>
|
|
||||||
<script src="script.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="body">
|
|
||||||
<div id="inbox">
|
|
||||||
</div>
|
|
||||||
<div id="input">
|
|
||||||
<form action="#" method="post" id="commandForm" onSubmit="return newCommand()">
|
|
||||||
<fieldset>
|
|
||||||
<legend>Command</legend>
|
|
||||||
<input id="commandInput" type="text" style="width:500px" />
|
|
||||||
<input id="submit" type="submit" value="Send" />
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,3 +0,0 @@
|
||||||
#lang info
|
|
||||||
|
|
||||||
(define test-responsibles '((all jay)))
|
|
|
@ -1,72 +0,0 @@
|
||||||
var ws;
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
if (!window.console) window.console = {};
|
|
||||||
if (!window.console.log) window.console.log = function() {};
|
|
||||||
|
|
||||||
ws = new WebSocket("ws://localhost:8080/");
|
|
||||||
|
|
||||||
ws.onopen = function() {
|
|
||||||
console.log("websocket connected");
|
|
||||||
ws.onmessage = function(event) {
|
|
||||||
showCommand(eval("\"" + event.data + "\""));
|
|
||||||
};
|
|
||||||
|
|
||||||
$(window).bind('beforeunload', function() {
|
|
||||||
ws.close();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = function() {
|
|
||||||
console.log("websocket disconnected");
|
|
||||||
};
|
|
||||||
|
|
||||||
$("#commandForm").bind("submit", function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
newCommand($(this));
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#commandForm").bind("keypress", function(e) {
|
|
||||||
if (e.keyCode == 13) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
newCommand($(this));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#commandInput").select();
|
|
||||||
});
|
|
||||||
|
|
||||||
function newCommand(form) {
|
|
||||||
var submit = form.find("#commandSubmit");
|
|
||||||
var text = form.find("#commandInput");
|
|
||||||
submit.disable();
|
|
||||||
ws.send(text.val());
|
|
||||||
text.val("").select();
|
|
||||||
submit.enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showCommand(message) {
|
|
||||||
var node = $("<p>" + message + "</p>");
|
|
||||||
node.hide();
|
|
||||||
$("#inbox").append(node);
|
|
||||||
node.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
jQuery.fn.enable = function(opt_enable) {
|
|
||||||
if (arguments.length && !opt_enable) {
|
|
||||||
this.attr("disabled", "disabled");
|
|
||||||
} else {
|
|
||||||
this.removeAttr("disabled");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.disable = function() {
|
|
||||||
this.enable(false);
|
|
||||||
return this;
|
|
||||||
};
|
|
|
@ -1,48 +0,0 @@
|
||||||
body {
|
|
||||||
background: white;
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body,
|
|
||||||
input {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 10pt;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
border: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#body {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 10px;
|
|
||||||
left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#input {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#inbox div {
|
|
||||||
padding-top: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav {
|
|
||||||
float: right;
|
|
||||||
z-index: 99;
|
|
||||||
}
|
|
||||||
|
|
||||||
legend {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
border: none;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user