webrick-websocket 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +74 -0
- data/Rakefile +2 -0
- data/lib/webrick/httprequest_patch.rb +6 -0
- data/lib/webrick/websocket/frame.rb +101 -0
- data/lib/webrick/websocket/server.rb +51 -0
- data/lib/webrick/websocket/servlet.rb +9 -0
- data/lib/webrick/websocket/socket.rb +78 -0
- data/lib/webrick/websocket/version.rb +5 -0
- data/lib/webrick/websocket.rb +10 -0
- data/testrun/jquery-websocket.js +45 -0
- data/testrun/jquery.min.js +4 -0
- data/testrun/json.js +538 -0
- data/testrun/main.html +28 -0
- data/testrun/main.rb +20 -0
- data/webrick-websocket.gemspec +25 -0
- metadata +104 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 13e2c3b9e1af9682919f83e722d34194fc7dd442
|
4
|
+
data.tar.gz: ed338c06ac763cc2bb992714dcdf44c5ed077094
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1704b541060941ef57644532befe20c718ba0fb0d2f2bc8787b1113423001348e4b8da31de1cfd9fae85aa1e45e4712042729dfcb936c6b02bff0818b74ee0d7
|
7
|
+
data.tar.gz: 747b5dcfe41c9fd5b07ddd096cf1b1a88dd053c91b8f44098182f7b650b9f6c2232b7648ba1c3f4336e4f9d912fe538bf01383cc11944e30b6b65e06aa6cac41
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Kilobyte22
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Webrick::Websocket
|
2
|
+
|
3
|
+
This gem allows you to use Websocket in your WEBrick web application.
|
4
|
+
First, create a HTTPServer with websocket support:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
require 'webrick/websocket'
|
8
|
+
server = WEBrick::Websocket::HTTPServer.new(Port: 8080, DocumentRoot: File.dirname(__FILE))
|
9
|
+
```
|
10
|
+
|
11
|
+
having that done do anything you would do with a regular WEBrick instance.
|
12
|
+
However, you can mount a Websocket Servlet.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class MyServlet < WEBrick::Websocket::Servlet
|
16
|
+
def select_protocol(available)
|
17
|
+
# method optional, if missing, it will always select first protocol.
|
18
|
+
# Will only be called if client actually requests a protocol
|
19
|
+
available.include?('myprotocol') ? 'myprotocol' : nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def socket_open(sock)
|
23
|
+
# optional
|
24
|
+
sock.puts 'Welcome' # send a text frame
|
25
|
+
def
|
26
|
+
|
27
|
+
def socket_close(sock)
|
28
|
+
puts 'Poof. Socket gone.'
|
29
|
+
end
|
30
|
+
|
31
|
+
def socket_text(sock, text)
|
32
|
+
puts "Client sent '#{text}'"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
server.mount('/websocket', MyServlet)
|
37
|
+
```
|
38
|
+
|
39
|
+
Aaaaand lets start the server
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
server.start
|
43
|
+
```
|
44
|
+
|
45
|
+
Note, that it will always use the same servlet instance for a single socket and that each socket has its own servlet instance.
|
46
|
+
This means you can safely use instance variables inside the servlet to store the sockets state
|
47
|
+
|
48
|
+
## Installation
|
49
|
+
|
50
|
+
Add this line to your application's Gemfile:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
gem 'webrick-websocket'
|
54
|
+
```
|
55
|
+
|
56
|
+
And then execute:
|
57
|
+
|
58
|
+
$ bundle
|
59
|
+
|
60
|
+
Or install it yourself as:
|
61
|
+
|
62
|
+
$ gem install webrick-websocket
|
63
|
+
|
64
|
+
## Usage
|
65
|
+
|
66
|
+
TODO: Write usage instructions here
|
67
|
+
|
68
|
+
## Contributing
|
69
|
+
|
70
|
+
1. Fork it ( https://github.com/[my-github-username]/webrick-websocket/fork )
|
71
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
72
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
73
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
74
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
module WEBrick
|
2
|
+
module Websocket
|
3
|
+
## :nodoc:
|
4
|
+
class Frame
|
5
|
+
@@ops_rev = {
|
6
|
+
cont: 0,
|
7
|
+
text: 1,
|
8
|
+
binary: 2,
|
9
|
+
close: 8,
|
10
|
+
ping: 9,
|
11
|
+
pong: 10
|
12
|
+
}
|
13
|
+
@@ops = {}
|
14
|
+
@@ops_rev.each do |op, id|
|
15
|
+
@@ops[id] = op
|
16
|
+
end
|
17
|
+
attr_reader :payload, :op
|
18
|
+
def self.parse(socket, prev = nil)
|
19
|
+
Frame.new(nil).parse(socket, prev)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(op, data = '')
|
23
|
+
@op = op
|
24
|
+
@payload = data
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse(socket, prev)
|
28
|
+
head = socket.read(1).unpack('C')[0]
|
29
|
+
@fin = head & 0b10000000 > 0
|
30
|
+
op = head & 0b00001111
|
31
|
+
@op = @@ops[op]
|
32
|
+
@op = prev if @op == :cont
|
33
|
+
head = socket.read(1).unpack('C')[0]
|
34
|
+
@masked = head & 0b10000000 > 0
|
35
|
+
len = head & 0b01111111
|
36
|
+
if len > 125
|
37
|
+
long = len == 127
|
38
|
+
len = socket.read(long ? 8 : 2).unpack(long ? 'Q' : 'S')[0]
|
39
|
+
end
|
40
|
+
@mask = socket.read(4).unpack('C4') if @masked
|
41
|
+
@payload = socket.read(len)
|
42
|
+
@payload = mask(@payload)
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def control?
|
47
|
+
@@ops_rev[@op] > 7
|
48
|
+
end
|
49
|
+
def close?
|
50
|
+
is(:close)
|
51
|
+
end
|
52
|
+
def binary?
|
53
|
+
is(:binary)
|
54
|
+
end
|
55
|
+
def text?
|
56
|
+
is(:text)
|
57
|
+
end
|
58
|
+
def ping?
|
59
|
+
is(:ping)
|
60
|
+
end
|
61
|
+
def pong?
|
62
|
+
is(:pong)
|
63
|
+
end
|
64
|
+
def fin?
|
65
|
+
@fin
|
66
|
+
end
|
67
|
+
|
68
|
+
def write(sock)
|
69
|
+
head = 0b10000000 + @@ops_rev[@op]
|
70
|
+
puts head.to_s(2)
|
71
|
+
sock.write([head].pack('C'))
|
72
|
+
@len = @payload.length
|
73
|
+
lendata = if @len > 125
|
74
|
+
if @len > 65535
|
75
|
+
[127, @len].pack('CQ')
|
76
|
+
else
|
77
|
+
[126, @len].pack('CS')
|
78
|
+
end
|
79
|
+
else
|
80
|
+
[@len].pack('C')
|
81
|
+
end
|
82
|
+
sock.write(lendata)
|
83
|
+
sock.write(@payload)
|
84
|
+
sock.flush
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def is(what)
|
89
|
+
@op == what
|
90
|
+
end
|
91
|
+
|
92
|
+
def mask(what)
|
93
|
+
i = -1
|
94
|
+
what.unpack('C*').map do |el|
|
95
|
+
i += 1
|
96
|
+
el ^ @mask[i % 4]
|
97
|
+
end.pack('C*')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'webrick/websocket/socket'
|
3
|
+
require 'webrick/websocket/servlet'
|
4
|
+
|
5
|
+
module WEBrick
|
6
|
+
module Websocket
|
7
|
+
##
|
8
|
+
# A HTTP Server with Websocket Support
|
9
|
+
class HTTPServer < WEBrick::HTTPServer
|
10
|
+
def initialize(*args, &block)
|
11
|
+
super(*args, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
## :nodoc:
|
15
|
+
def service(req, res)
|
16
|
+
if req.unparsed_uri == "*"
|
17
|
+
if req.request_method == "OPTIONS"
|
18
|
+
do_OPTIONS(req, res)
|
19
|
+
raise HTTPStatus::OK
|
20
|
+
end
|
21
|
+
raise HTTPStatus::NotFound, "`#{req.unparsed_uri}' not found."
|
22
|
+
end
|
23
|
+
|
24
|
+
servlet, options, script_name, path_info = search_servlet(req.path)
|
25
|
+
raise HTTPStatus::NotFound, "`#{req.path}' not found." unless servlet
|
26
|
+
req.script_name = script_name
|
27
|
+
req.path_info = path_info
|
28
|
+
si = servlet.get_instance(self, *options)
|
29
|
+
@logger.debug(format("%s is invoked.", si.class.name))
|
30
|
+
if req['upgrade'] == 'websocket' && si.is_a?(Servlet)
|
31
|
+
req.header.each do |k, v|
|
32
|
+
puts "#{k} -> #{v}"
|
33
|
+
end
|
34
|
+
res.status = 101
|
35
|
+
key = req['Sec-WebSocket-Key']
|
36
|
+
res['Sec-WebSocket-Accept'] = Digest::SHA1.base64digest(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
37
|
+
res['Sec-WebSocket-Protocol'] = si.select_protocol(req['Sec-WebSocket-Protocol']).split(/[ ,\t]+/) if req['Sec-WebSocket-Protocol']
|
38
|
+
res['upgrade'] = 'websocket'
|
39
|
+
res.setup_header
|
40
|
+
res.instance_variable_get(:@header)['connection'] = 'upgrade'
|
41
|
+
res.send_header(req.socket)
|
42
|
+
sock = WEBrick::Websocket::Socket.new(req.socket, si, @logger)
|
43
|
+
sock.run
|
44
|
+
res.request_line = nil
|
45
|
+
else
|
46
|
+
si.service(req, res)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'webrick/websocket/frame'
|
2
|
+
|
3
|
+
module WEBrick
|
4
|
+
module Websocket
|
5
|
+
class Socket
|
6
|
+
def initialize(sock, handler, logger)
|
7
|
+
@prev = nil
|
8
|
+
@sock = sock
|
9
|
+
@handler = handler
|
10
|
+
@open = true
|
11
|
+
@logger = logger
|
12
|
+
handle(:open)
|
13
|
+
end
|
14
|
+
|
15
|
+
def puts(data)
|
16
|
+
send_frame(Frame.new(:text, data))
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
handle(:close)
|
21
|
+
if @open
|
22
|
+
@open = false
|
23
|
+
send_frame(Frame.new(:close))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_frame(frame)
|
28
|
+
@logger.debug("Websocket Frame Sent: #{frame.op.to_s}(#{frame.payload.length} Bytes)")
|
29
|
+
if frame.close? && @open
|
30
|
+
close
|
31
|
+
else
|
32
|
+
frame.write(@sock)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
handle_packet while @open
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def handle_packet
|
43
|
+
payload = ''
|
44
|
+
while @open
|
45
|
+
frame = read_frame
|
46
|
+
@prev = frame.op
|
47
|
+
if frame.control?
|
48
|
+
case @prev
|
49
|
+
when :ping
|
50
|
+
send_frame(Frame.new(:pong))
|
51
|
+
when :close
|
52
|
+
@open = false
|
53
|
+
close
|
54
|
+
end
|
55
|
+
else
|
56
|
+
payload += frame.payload
|
57
|
+
break if frame.fin?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
return unless @open
|
61
|
+
handle(@prev, payload)
|
62
|
+
handle(:data, @prev, payload)
|
63
|
+
end
|
64
|
+
|
65
|
+
def handle(op, *args)
|
66
|
+
cb = 'socket_' + op.to_s
|
67
|
+
@logger.debug("Websocket Callback: #{@handler.class.name}##{cb}")
|
68
|
+
@handler.send(cb, self, *args) if @handler.respond_to?(cb)
|
69
|
+
end
|
70
|
+
|
71
|
+
def read_frame
|
72
|
+
f = Frame.parse(@sock, @prev)
|
73
|
+
@logger.debug("Websocket Frame Received: #{f.op.to_s}(#{f.payload.length} Bytes)")
|
74
|
+
f
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
/*
|
2
|
+
* jQuery Web Sockets Plugin v0.0.1
|
3
|
+
* http://code.google.com/p/jquery-websocket/
|
4
|
+
*
|
5
|
+
* This document is licensed as free software under the terms of the
|
6
|
+
* MIT License: http://www.opensource.org/licenses/mit-license.php
|
7
|
+
*
|
8
|
+
* Copyright (c) 2010 by shootaroo (Shotaro Tsubouchi).
|
9
|
+
*/
|
10
|
+
(function($){
|
11
|
+
$.extend({
|
12
|
+
websocketSettings: {
|
13
|
+
open: function(){},
|
14
|
+
close: function(){},
|
15
|
+
message: function(){},
|
16
|
+
options: {},
|
17
|
+
events: {}
|
18
|
+
},
|
19
|
+
websocket: function(url, s) {
|
20
|
+
var ws = WebSocket ? new WebSocket( url ) : {
|
21
|
+
send: function(m){ return false },
|
22
|
+
close: function(){}
|
23
|
+
};
|
24
|
+
$(ws)
|
25
|
+
.bind('open', $.websocketSettings.open)
|
26
|
+
.bind('close', $.websocketSettings.close)
|
27
|
+
.bind('message', $.websocketSettings.message)
|
28
|
+
.bind('message', function(e){
|
29
|
+
var m = $.parseJSON(e.originalEvent.data);
|
30
|
+
var h = $.websocketSettings.events[m.type];
|
31
|
+
if (h) h.call(this, m);
|
32
|
+
});
|
33
|
+
ws._settings = $.extend($.websocketSettings, s);
|
34
|
+
ws._send = ws.send;
|
35
|
+
ws.send = function(type, data) {
|
36
|
+
var m = {type: type};
|
37
|
+
m = $.extend(true, m, $.extend(true, {}, $.websocketSettings.options, m));
|
38
|
+
if (data) m['data'] = data;
|
39
|
+
return this._send(JSON.stringify(m));
|
40
|
+
};
|
41
|
+
$(window).unload(function(){ ws.close(); ws = null });
|
42
|
+
return ws;
|
43
|
+
}
|
44
|
+
});
|
45
|
+
})(jQuery);
|