webrocket 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6f34536b978cbb71a56ae12ea296681c3f09e1d1
4
+ data.tar.gz: 210d6e96989a3657513fabc93278250800bfe491
5
+ SHA512:
6
+ metadata.gz: 0e2587f050883ca1fee5a31c50b9a2c2e863aa2f4268e0bada83d66ddb159ef44be21aa1d8e8ff5e9cd358309f1b7f2654ce3283ad413647b82b6980625d58fb
7
+ data.tar.gz: 9aafb6ae9a6ae54dcd78a04e36222a06f7022c51bad724669d01b8d7c05ba5ee9be354455fffef982287d5536ea9298ff8127308b7bdbecc29ce13438c280c4e
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.1
4
+ - 2.0.0
5
+ - 1.9.3
6
+ script:
7
+ - bundle exec rake build test
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in WEBrocket.gemspec
4
+ gemspec
@@ -0,0 +1,104 @@
1
+ [![Build Status](https://img.shields.io/travis/unak/WEBrocket.svg)](https://travis-ci.org/unak/WEBrocket)
2
+ [![Version ](https://img.shields.io/gem/v/webrocket.svg)](https://rubygems.org/gems/webrocket)
3
+
4
+ WEBrocket
5
+ =========
6
+
7
+ WebSocket extension for WEBrick.
8
+ This library is under development.
9
+
10
+ WebSocket specification is based on [RFC6455](http://www.rfc-editor.org/rfc/rfc6455.txt).
11
+
12
+
13
+ Requirements
14
+ ------------
15
+
16
+ + Ruby 1.9.3 or later.
17
+
18
+
19
+ Installation
20
+ ------------
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ gem 'WEBrocket'
25
+
26
+ And then execute:
27
+
28
+ $ bundle
29
+
30
+ Or install it yourself as:
31
+
32
+ $ gem install WEBrocket
33
+
34
+
35
+ How to use
36
+ ----------
37
+
38
+ require "webrick"
39
+ require "webrocket"
40
+
41
+ # write listner
42
+ class Listener
43
+ def on_open(websocket)
44
+ # do somthing
45
+ end
46
+
47
+ def on_close(websocket)
48
+ # do somthing
49
+ end
50
+
51
+ def on_recv(websocket, data, type)
52
+ # do something
53
+ end
54
+
55
+ def on_shutdown
56
+ # do something
57
+ end
58
+ end
59
+
60
+ server = WEBrick::HTTPServer.new
61
+ server.mount_websocket("sample", Listener.new, "test")
62
+ server.start
63
+
64
+ See sample directory for more details.
65
+
66
+
67
+ Limitations
68
+ -----------
69
+
70
+ + Fragmentation is not supported yet.
71
+ + TLS is not supported yet.
72
+
73
+
74
+ License
75
+ -------
76
+
77
+ Copyright (c) 2013 NAKAMURA Usaku usa@garbagecollect.jp
78
+
79
+ Redistribution and use in source and binary forms, with or without
80
+ modification, are permitted provided that the following conditions are met:
81
+
82
+ 1. Redistributions of source code must retain the above copyright notice,
83
+ this list of conditions and the following disclaimer.
84
+ 2. Redistributions in binary form must reproduce the above copyright notice,
85
+ this list of conditions and the following disclaimer in the documentation
86
+ and/or other materials provided with the distribution.
87
+
88
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
89
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
90
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
91
+ DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
92
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
93
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
94
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
95
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
96
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
97
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
98
+
99
+
100
+ Supplimentary
101
+ -------------
102
+
103
+ I've found that there are many projects named webrocket, but, sorry, I can't
104
+ stop naming this one the same name :)
@@ -0,0 +1,7 @@
1
+ # -*- Ruby -*-
2
+ require "bundler/gem_tasks"
3
+
4
+ desc "Runs test"
5
+ task :test do
6
+ ruby 'test/test_webrocket.rb', '-v'
7
+ end
@@ -0,0 +1,56 @@
1
+ require "base64"
2
+ require "digest/sha1"
3
+ require "webrocket/webrick-extender"
4
+ require "webrocket/websocket"
5
+ require "webrocket/version"
6
+
7
+ module WEBrocket
8
+ def self.attach(server, req, res, listener, subprotocol)
9
+ key = req["Sec-WebSocket-Key"]
10
+ unless key
11
+ res.status = WEBrick::HTTPStatus::RC_BAD_REQUEST
12
+ return
13
+ end
14
+ key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
15
+ res["Sec-WebSocket-Accept"] = Base64.strict_encode64(Digest::SHA1.digest(key))
16
+
17
+ if req["Sec-WebSocket-Version"].to_i < 13
18
+ res.status = WEBrick::HTTPStatus::RC_BAD_REQUEST
19
+ res["Sec-WebSocket-Version"] = 13
20
+ return
21
+ end
22
+
23
+ if req["Sec-WebSocket-Protocol"]
24
+ protocols = req["Sec-WebSocket-Protocol"].split(/\s*,\s*/)
25
+ if protocols.include?(subprotocol)
26
+ res["Sec-WebSocket-Protocol"] = subprotocol
27
+ elsif protocols.include?("null")
28
+ res["Sec-WebSocket-Protocol"] = "null"
29
+ end
30
+ end
31
+
32
+ res.status = WEBrick::HTTPStatus::RC_SWITCHING_PROTOCOLS
33
+ res["Upgrade"] = "websocket"
34
+ #res["Connection"] = "Upgrade"
35
+
36
+ @keep_alive = true
37
+
38
+ res.define_singleton_method(:send_response) do |sock|
39
+ begin
40
+ setup_header
41
+ @header["connection"] = "Upgrade"
42
+ send_header(sock)
43
+ ws = WEBrocket::WebSocket.new(sock, listener, subprotocol)
44
+ server.add_shutdown_listener(ws.method(:on_shutdown))
45
+ ws.start
46
+ @keep_alive = false
47
+ rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
48
+ @logger.debug(ex)
49
+ @keep_alive = false
50
+ rescue StandardError => ex
51
+ @logger.error(ex)
52
+ @keep_alive = false
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,3 @@
1
+ module WEBrocket
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ require "webrick/httpserver"
2
+ require "webrocket"
3
+
4
+ module WEBrick
5
+ class HTTPServer
6
+ def mount_websocket(path, listener, subprotocol)
7
+ mount_proc(path) do |req, res|
8
+ WEBrocket.attach(self, req, res, listener, subprotocol)
9
+ end
10
+ end
11
+
12
+ def add_shutdown_listener(prc = nil, &block)
13
+ @shutdown_listeners ||= []
14
+ @shutdown_listeners.push(prc || block)
15
+ end
16
+
17
+ alias _original_shutdown shutdown
18
+ def shutdown
19
+ if @shutdown_listeners
20
+ @shutdown_listeners.each do |listener|
21
+ listener.call
22
+ end
23
+ end
24
+ _original_shutdown
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,164 @@
1
+ require "webrocket"
2
+
3
+ module WEBrocket
4
+ class WebSocket
5
+ class InternalError < StandardError; end
6
+
7
+ OP_CONTINUE = 0x0
8
+ OP_TEXT = 0x1
9
+ OP_BINARY = 0x2
10
+ OP_CLOSE = 0x8
11
+ OP_PING = 0x9
12
+ OP_PONG = 0xA
13
+
14
+ CLOSE_OK = [1000].pack("n")
15
+ CLOSE_CLOSED = [1001].pack("n")
16
+ CLOSE_PROTOCOL_ERROR = [1002].pack("n")
17
+ CLOSE_BROKEN_DATA = [1003].pack("n")
18
+ CLOSE_BROKEN_MESSAGE = [1007].pack("n")
19
+ CLOSE_POLICY_ERROR = [1008].pack("n")
20
+ CLOSE_TOO_BIG = [1009].pack("n")
21
+ CLOSE_UNKNOWN_ERROR = [1011].pack("n")
22
+
23
+ def initialize(sock, listener, subprotocol)
24
+ @sock = sock
25
+ @listener = listener
26
+ @subprotocol = subprotocol
27
+ @type = nil
28
+ @reason = nil
29
+ end
30
+
31
+ attr_reader :subprotocol
32
+
33
+ def start
34
+ @listener.on_open(self) if @listener.respond_to?(:on_open)
35
+ begin
36
+ while true
37
+ r = IO.select([@sock])
38
+ recv_frame if r
39
+ end
40
+ rescue IOError
41
+ @reason ||= CLOSE_CLOSED
42
+ ensure
43
+ @reason ||= CLOSE_UNKNOWN_ERROR
44
+ send_close_frame(@reason) unless @sock.closed?
45
+ @listener.on_close(self) if @listener.respond_to?(:on_close)
46
+ end
47
+ end
48
+
49
+ def send(data, type = :text)
50
+ nil until IO.select([], [@sock])
51
+ case type
52
+ when :text
53
+ op = OP_TEXT
54
+ when :binary
55
+ op = OP_BINARY
56
+ else
57
+ raise InternalError, "unknown type: #{type}"
58
+ end
59
+ send_frame(op, data)
60
+ end
61
+
62
+ def on_shutdown
63
+ send_close_frame unless @sock.closed?
64
+ @listener.on_shutdown if @listener.respond_to?(:on_shutdown)
65
+ end
66
+
67
+ private
68
+ def send_close_frame(data = "")
69
+ send_frame(OP_CLOSE, data)
70
+ end
71
+
72
+ def send_frame(opcode, data = "")
73
+ data = data.dup.force_encoding('binary')
74
+ if data.bytesize <= 125
75
+ @sock.write([0x80 | opcode, data.bytesize].pack("cc") + data)
76
+ elsif data.bytesize <= 0xffff
77
+ @sock.write([0x80 | opcode, 126, data.bytesize].pack("ccn") + data)
78
+ else # under 64bit
79
+ @sock.write([0x80 | opcode, 127, data.bytesize / 0x1_0000_0000, data.bytesize % 0x1_0000_0000].pack("ccNN") + data)
80
+ end
81
+ end
82
+
83
+ def recv_frame
84
+ header = @sock.read(2)
85
+ return unless header
86
+ if header.bytesize != 2
87
+ @reason = CLOSE_PROTOCOL_ERROR
88
+ raise InternalError, "something wrong: header = '#{header}'"
89
+ end
90
+ h0, h1 = header.unpack("cc")
91
+ len = h1 & 0x7F
92
+ if len == 126
93
+ tmp = @sock.read(2)
94
+ if !tmp || tmp.bytesize != 2
95
+ @reason = CLOSE_PROTOCOL_ERROR
96
+ raise InternalError, "something wrong: size = '#{tmp}'"
97
+ end
98
+ len = tmp.unpack("n")
99
+ elsif len == 127
100
+ tmp = @sock.read(8)
101
+ if !tmp || tmp.bytesize != 8
102
+ @reason = CLOSE_PROTOCOL_ERROR
103
+ raise InternalError, "something wrong: size = '#{tmp}'"
104
+ end
105
+ l0, l1 = tmp.unpack("NN")
106
+ len = l0 * 0x1_0000_0000 + l1
107
+ end
108
+ if len > 0
109
+ if h1 & 0x80 != 0
110
+ key = @sock.read(4)
111
+ if !key || key.bytesize != 4
112
+ @reason = CLOSE_PROTOCOL_ERROR
113
+ raise InternalError, "something wrong: key = '#{key}'"
114
+ end
115
+ end
116
+
117
+ data = @sock.read(len)
118
+ if !data || data.bytesize != len
119
+ @reason = CLOSE_PROTOCOL_ERROR
120
+ raise InternalError, "something wrong"
121
+ end
122
+
123
+ if h1 & 0x80 != 0
124
+ data = data.bytes.map.with_index{|b, i| (b ^ (key[i % 4].ord)).chr}.join
125
+ end
126
+ end
127
+
128
+ op = h0 & 0xF
129
+
130
+ if h0 & 0x80 == 0
131
+ # XXX
132
+ @reason = CLOSE_TOO_BIG
133
+ raise InternalError, "fragmented data is not supported yet"
134
+ end
135
+
136
+ case op
137
+ when OP_CONTINUE, OP_TEXT, OP_BINARY
138
+ if op == OP_TEXT
139
+ @type = :text
140
+ elsif op == OP_BINARY
141
+ @type = :binary
142
+ elsif !@type
143
+ @reason = CLOSE_PROTOCOL_ERROR
144
+ raise InternalError, "continued data at the first"
145
+ end
146
+ if @type == :text
147
+ data.force_encoding('utf-8')
148
+ raise InternalError, "invalid encoding" unless data.valid_encoding?
149
+ end
150
+ @listener.on_message(self, data, @type) if @listener.respond_to?(:on_message)
151
+ when OP_CLOSE
152
+ @reason = CLOSE_OK
153
+ raise IOError
154
+ when OP_PING
155
+ send_frame(OP_PONG, data)
156
+ when OP_PONG
157
+ # nothing to do
158
+ else
159
+ @reason = CLOSE_PROTOCOL_ERROR
160
+ raise InternalError, "unknown opcode: 0x%x" % op
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,47 @@
1
+ <html lang="ja">
2
+ <head>
3
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
4
+ <title>chat sample</title>
5
+ <script type="text/javascript"><!--
6
+ var conn;
7
+
8
+ function escapeHTML(str) {
9
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
10
+ }
11
+
12
+ function login() {
13
+ console.log("login");
14
+ var user = document.getElementById("text").value;
15
+ if (user != "") {
16
+ document.getElementById("text").value = "";
17
+ document.getElementById("send").innerHTML = "send";
18
+ document.getElementById("send").setAttribute("onclick", "message();");
19
+
20
+ conn = new WebSocket("ws://localhost:10080/chat", "chat");
21
+ conn.onopen = function() {
22
+ conn.send("\uFEFF" + user);
23
+ };
24
+ conn.onerror = function(error) {
25
+ var elem = document.getElementById("server");
26
+ elem.innerHTML = "<b>WebSocket error: " + escapeHTML(error) + "</b><br>\n" + elem.innerHTML;
27
+ };
28
+ conn.onmessage = function(v) {
29
+ var elem = document.getElementById("server");
30
+ elem.innerHTML = escapeHTML(v.data) + "<br>\n" + elem.innerHTML;
31
+ };
32
+ }
33
+ }
34
+
35
+ function message() {
36
+ console.log("message");
37
+ conn.send(document.getElementById("text").value);
38
+ document.getElementById("text").value = "";
39
+ }
40
+ --></script>
41
+ </head>
42
+ <body>
43
+ <input id="text" value=""/><button id="send" onclick="login();">login</button>
44
+ <p id="server">
45
+ </p>
46
+ </body>
47
+ </html>
@@ -0,0 +1,60 @@
1
+ #!ruby
2
+ $LOAD_PATH.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
3
+ require "webrick"
4
+ require "webrocket"
5
+
6
+ # write listner
7
+ class Listener
8
+ def initialize
9
+ @clients = []
10
+ @m = Mutex.new
11
+ end
12
+
13
+ def on_open(websocket)
14
+ p [:open, websocket]
15
+ @m.synchronize do
16
+ @clients.push(websocket)
17
+ end
18
+ end
19
+
20
+ def on_close(websocket)
21
+ p [:close, websocket]
22
+ @m.synchronize do
23
+ user = websocket.instance_variable_get(:@chat_user)
24
+ if user
25
+ @clients.each do |cl|
26
+ cl.send("logout #{user}", :text)
27
+ end
28
+ end
29
+ @clients.delete(websocket)
30
+ end
31
+ end
32
+
33
+ def on_message(websocket, data, type)
34
+ user = websocket.instance_variable_get(:@chat_user)
35
+ @m.synchronize do
36
+ @clients.each do |cl|
37
+ if user
38
+ cl.send("#{user}: #{data}", :text)
39
+ else
40
+ cl.send("login #{data}", :text)
41
+ end
42
+ end
43
+ end
44
+ unless user
45
+ websocket.instance_variable_set(:@chat_user, data)
46
+ end
47
+ end
48
+
49
+ def on_shutdown
50
+ p [:shutdown]
51
+ # nothing to do
52
+ end
53
+ end
54
+
55
+ server = WEBrick::HTTPServer.new(Port: 10080)
56
+ server.mount_websocket("/chat", Listener.new, "chat")
57
+ trap(:INT) do
58
+ server.shutdown
59
+ end
60
+ server.start
@@ -0,0 +1,29 @@
1
+ <html lang="ja">
2
+ <head>
3
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
4
+ <script type="text/javascript"><!--
5
+ function escapeHTML(str){
6
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
7
+ }
8
+
9
+ var conn = new WebSocket("ws://localhost:10080/echo", "echo");
10
+ conn.onopen = function(){
11
+ var elem = document.getElementById("server");
12
+ elem.innerHTML = "<b>connected</b><br>\n" + elem.innerHTML;
13
+ };
14
+ conn.onerror = function(error){
15
+ var elem = document.getElementById("server");
16
+ elem.innerHTML = "<b>WebSocket error: " + escapeHTML(error) + "</b><br>\n" + elem.innerHTML;
17
+ };
18
+ conn.onmessage = function(v){
19
+ var elem = document.getElementById("server");
20
+ elem.innerHTML = escapeHTML(v.data) + "<br>\n" + elem.innerHTML;
21
+ }
22
+ --></script>
23
+ </head>
24
+ <body>
25
+ <input id="text" value="hello!"/><button onclick="conn.send(document.getElementById('text').value);">send</button>
26
+ <p id="server">
27
+ </p>
28
+ </body>
29
+ </html>
@@ -0,0 +1,35 @@
1
+ #!ruby
2
+ $LOAD_PATH.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
3
+ require "webrick"
4
+ require "webrocket"
5
+
6
+ # write listner
7
+ class Listener
8
+ def on_open(websocket)
9
+ # do somthing
10
+ p :open
11
+ end
12
+
13
+ def on_close(websocket)
14
+ # do somthing
15
+ p :close
16
+ end
17
+
18
+ def on_message(websocket, data, type)
19
+ # do something
20
+ p [:message, data, type]
21
+ websocket.send(data, type)
22
+ end
23
+
24
+ def on_shutdown
25
+ # do something
26
+ p :shutdown
27
+ end
28
+ end
29
+
30
+ server = WEBrick::HTTPServer.new(Port: 10080)
31
+ server.mount_websocket("/echo", Listener.new, "echo")
32
+ trap(:INT) do
33
+ server.shutdown
34
+ end
35
+ server.start
@@ -0,0 +1,115 @@
1
+ require 'base64'
2
+ require 'net/http'
3
+ require 'socket'
4
+ require 'test/unit'
5
+ require 'webrick'
6
+ $LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
7
+ require 'webrocket'
8
+
9
+ class TestWEBrocket < Test::Unit::TestCase
10
+ def start_test_server(&block)
11
+ logger = Object.new
12
+ logger.instance_eval do
13
+ def <<(msg)
14
+ @log ||= ''
15
+ @log << msg
16
+ end
17
+
18
+ def log
19
+ @log ||= ''
20
+ end
21
+ end
22
+
23
+ server = WEBrick::HTTPServer.new(
24
+ :BindAddress => "127.0.0.1",
25
+ :Port => 0,
26
+ :DocumentRoot => File.dirname(__FILE__),
27
+ :ShutdownSocketWithoutClose => true,
28
+ :ServerType => Thread,
29
+ :Logger => WEBrick::Log.new(logger),
30
+ :AccessLog => [[logger, ""]]
31
+ )
32
+
33
+ begin
34
+ th = server.start
35
+ addr = server.listeners[0].addr
36
+ block.call(server, addr[3], addr[1], logger)
37
+ ensure
38
+ server.shutdown
39
+ th.join
40
+ end
41
+
42
+ logger.log
43
+ end
44
+
45
+ class Listener
46
+ def initialize(open = proc{}, close = proc{}, message = proc{}, shutdown = proc{})
47
+ @open = open
48
+ @close = close
49
+ @message = message
50
+ @shutdown = shutdown
51
+ end
52
+
53
+ def on_open(websocket)
54
+ @open.call(websocket)
55
+ end
56
+
57
+ def on_close(websocket)
58
+ @close.call(websocket)
59
+ end
60
+
61
+ def on_message(websocket, data, type)
62
+ @message.call(websocket, data, type)
63
+ end
64
+
65
+ def on_shutdown
66
+ @shutdown.call
67
+ end
68
+ end
69
+
70
+ def test_websocket
71
+ shutdowned = false # yes, I know shutdown is a noun
72
+ start_test_server do |server, host, port, logger|
73
+ http = Net::HTTP.new(host, port)
74
+
75
+ req = Net::HTTP::Get.new('/')
76
+ http.request(req) do |res|
77
+ assert_kind_of Net::HTTPOK, res.response
78
+ end
79
+
80
+ opened = false
81
+ open = lambda {|websocket|
82
+ opened = true
83
+ }
84
+ close = lambda {|websocket|
85
+ opened = false
86
+ }
87
+ message = lambda {|websocket, data, type|
88
+ }
89
+ shutdown = lambda {
90
+ shutdowned = true
91
+ }
92
+ server.mount_websocket('/test', Listener.new(open, close, message, shutdown), 'test')
93
+
94
+ req = Net::HTTP::Get.new('/test')
95
+ req['Upgrade'] = 'websocket'
96
+ req['Connection'] = 'Upgrade'
97
+ req['Sec-WebSocket-Version'] = '13'
98
+ req['Sec-WebSocket-Key'] = Base64.encode64('0'*16)
99
+
100
+ refute opened
101
+ sock = nil
102
+ http.request(req) do |res|
103
+ assert_kind_of Net::HTTPSwitchProtocol, res.response
104
+ sock = http.instance_variable_get(:@socket).io.dup
105
+ end
106
+
107
+ assert opened
108
+
109
+ sock.shutdown
110
+
111
+ refute shutdowned
112
+ end
113
+ assert shutdowned
114
+ end
115
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'webrocket/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "webrocket"
8
+ spec.version = WEBrocket::VERSION
9
+ spec.authors = ["U.Nakamura"]
10
+ spec.email = ["usa@garbagecollect.jp"]
11
+ spec.description = %q{WebSocket extension for WEBrick}
12
+ spec.summary = %q{WebSocket extension for WEBrick}
13
+ spec.homepage = "https://github.com/unak/WEBRocket"
14
+ spec.license = "BSD-2-Clause"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "test-unit"
24
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: webrocket
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - U.Nakamura
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: WebSocket extension for WEBrick
56
+ email:
57
+ - usa@garbagecollect.jp
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".travis.yml"
64
+ - Gemfile
65
+ - README.md
66
+ - Rakefile
67
+ - lib/webrocket.rb
68
+ - lib/webrocket/version.rb
69
+ - lib/webrocket/webrick-extender.rb
70
+ - lib/webrocket/websocket.rb
71
+ - sample/chat.html
72
+ - sample/chat.rb
73
+ - sample/echo.html
74
+ - sample/echo.rb
75
+ - test/test_webrocket.rb
76
+ - webrocket.gemspec
77
+ homepage: https://github.com/unak/WEBRocket
78
+ licenses:
79
+ - BSD-2-Clause
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.2.2
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: WebSocket extension for WEBrick
101
+ test_files:
102
+ - test/test_webrocket.rb