webtube 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README +715 -560
- data/bin/wsc +53 -34
- data/lib/webtube.rb +510 -257
- data/lib/webtube/vital-statistics.rb +31 -24
- data/lib/webtube/webrick.rb +79 -59
- data/sample-server.rb +4 -4
- data/webtube.gemspec +6 -6
- metadata +9 -9
@@ -3,20 +3,24 @@ require 'thread'
|
|
3
3
|
require 'webtube'
|
4
4
|
|
5
5
|
class Webtube
|
6
|
-
# A tracker for live [[Webtube]] instances and their threads.
|
7
|
-
# shutdowning WEBrick to gently close the
|
6
|
+
# A tracker for live [[Webtube]] instances and their threads.
|
7
|
+
# This allows a shutdowning WEBrick to gently close the
|
8
|
+
# pending WebSockets.
|
8
9
|
class Vital_Statistics
|
9
|
-
# A [[ThreadGroup]] into which the Webtube threads can add
|
10
|
-
# Note that [[Vital_Statistics]] does not
|
11
|
-
#
|
12
|
-
#
|
10
|
+
# A [[ThreadGroup]] into which the Webtube threads can add
|
11
|
+
# themselves. Note that [[Vital_Statistics]] does not
|
12
|
+
# forcefully move them (nor could it -- a Webtube does not
|
13
|
+
# get a thread before [[Webtube#run]] is called, which is
|
14
|
+
# normally _after_ [[Vital_Statistics#birth]] gets called).
|
13
15
|
#
|
14
|
-
# When Webtube is being integrated with WEBrick by
|
15
|
-
# assigning Webtube-specific threads
|
16
|
-
#
|
17
|
-
# to
|
18
|
-
#
|
19
|
-
#
|
16
|
+
# When Webtube is being integrated with WEBrick by
|
17
|
+
# [[webtube/webrick.rb]], assigning Webtube-specific threads
|
18
|
+
# into this group will cause WEBrick's standard shutdown
|
19
|
+
# procedure to not try to [[Thread#join]] them as it does to
|
20
|
+
# ordinary WEBrick threads. Instead, the integration code
|
21
|
+
# will call [[Vital_Statistics#close_all]], to request that
|
22
|
+
# each Webtube close itself, and then join the threads from
|
23
|
+
# [[Vital_Statistics#thread_group]].
|
20
24
|
attr_reader :thread_group
|
21
25
|
|
22
26
|
def initialize logger
|
@@ -42,24 +46,27 @@ class Webtube
|
|
42
46
|
return
|
43
47
|
end
|
44
48
|
|
45
|
-
# Construct a list of all the currently living [[Webtube]]
|
46
|
-
# that while the operation is atomic, the
|
47
|
-
# inherently multithreaded, so the
|
48
|
-
#
|
49
|
+
# Construct a list of all the currently living [[Webtube]]
|
50
|
+
# instances. Note that while the operation is atomic, the
|
51
|
+
# Webtube infrastructure is inherently multithreaded, so the
|
52
|
+
# list can get slightly stale immediately and should, in
|
53
|
+
# most contexts, be considered informative.
|
49
54
|
def to_a
|
50
55
|
return @mutex.synchronize{@webtubes.to_a}
|
51
56
|
end
|
52
57
|
|
53
|
-
# The default status code in a shutdown situation is 1001
|
58
|
+
# The default status code in a shutdown situation is 1001
|
59
|
+
# 'going away'.
|
54
60
|
def close_all status_code = 1001, explanation = ""
|
55
|
-
# Note that we're only mutexing off extracting the content
|
56
|
-
# [[@webtubes]] (see [[to_a]]). We can't mutex the
|
57
|
-
# the webtubes will be closing,
|
58
|
-
# that is also
|
61
|
+
# Note that we're only mutexing off extracting the content
|
62
|
+
# of [[@webtubes]] (see [[to_a]]). We can't mutex the
|
63
|
+
# whole block, for as the webtubes will be closing,
|
64
|
+
# they'll want to notify us about it, and that is also
|
65
|
+
# mutexed.
|
59
66
|
#
|
60
|
-
# This is not as bad as it may sound, for the webserver
|
61
|
-
# accepting new connections anymore anyway by
|
62
|
-
# closing the old ones.
|
67
|
+
# This is not as bad as it may sound, for the webserver
|
68
|
+
# shouldn't be accepting new connections anymore anyway by
|
69
|
+
# the time it'll start closing the old ones.
|
63
70
|
self.to_a.each do |webtube|
|
64
71
|
begin
|
65
72
|
webtube.close status_code, explanation
|
data/lib/webtube/webrick.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
# webtube/webrick.rb -- WEBrick integration for Webtube, an
|
2
|
-
# the WebSocket protocol
|
1
|
+
# webtube/webrick.rb -- WEBrick integration for Webtube, an
|
2
|
+
# implementation of the WebSocket protocol
|
3
3
|
|
4
|
+
require 'openssl'
|
4
5
|
require 'webrick'
|
5
6
|
require 'webrick/httprequest'
|
6
7
|
require 'webrick/httpserver'
|
@@ -21,8 +22,9 @@ module WEBrick
|
|
21
22
|
end
|
22
23
|
|
23
24
|
class HTTPServer
|
24
|
-
# Attach a [[Webtube::Vital_Statistics]] to new
|
25
|
-
# instances so that the live
|
25
|
+
# Attach a [[Webtube::Vital_Statistics]] to new
|
26
|
+
# [[WEBrick::HTTPServer]] instances so that the live
|
27
|
+
# webtubes could be closed upon shutdown
|
26
28
|
|
27
29
|
alias orig_initialize_before_webtube_integration initialize
|
28
30
|
def initialize config = {}, default = Config::HTTP
|
@@ -35,106 +37,123 @@ module WEBrick
|
|
35
37
|
result = @webtubes
|
36
38
|
# Usually, this should be it.
|
37
39
|
if result.nil? then # ... but ...
|
38
|
-
# Well, it would seem that our extended constructor was
|
39
|
-
# How could this have happened?
|
40
|
+
# Well, it would seem that our extended constructor was
|
41
|
+
# not called. How could this have happened?
|
40
42
|
result = @webtubes = Webtube::Vital_Statistics.new
|
41
|
-
@logger.warn "@webtubes
|
42
|
-
"
|
43
|
-
"
|
44
|
-
"
|
45
|
-
"
|
43
|
+
@logger.warn "@webtubes (in a WEBrick::HTTPServer) " +
|
44
|
+
"has not been set up before accessing it. I " +
|
45
|
+
"have attempted to correct this ex post facto, " +
|
46
|
+
"but doing it now is a race condition, and I may " +
|
47
|
+
"have lost track of some webtubes as a result. " +
|
48
|
+
"The next time, please load webtube/webrick.rb " +
|
49
|
+
"/before/ instantiating your WEBrick::HTTPServer."
|
46
50
|
end
|
47
51
|
return result
|
48
52
|
end
|
49
53
|
|
50
54
|
alias orig_shutdown_before_webtube_integration shutdown
|
51
55
|
def shutdown
|
52
|
-
# We'll need to call the original shutdown code first, for
|
53
|
-
# stop accepting new Webtube connections before
|
54
|
-
# connections' will have a proper,
|
56
|
+
# We'll need to call the original shutdown code first, for
|
57
|
+
# we want to stop accepting new Webtube connections before
|
58
|
+
# 'close all Webtube connections' will have a proper,
|
59
|
+
# thread-safe meaning.
|
55
60
|
orig_shutdown_before_webtube_integration
|
56
61
|
webtubes.close_all
|
57
62
|
webtubes.thread_group.list.each &:join
|
58
63
|
return
|
59
64
|
end
|
60
65
|
|
61
|
-
# Given a [[request]] and a [[response]] object, as prepared
|
62
|
-
# [[WEBrick::HTTPServer]] for processing in a portlet,
|
63
|
-
# the client's request to establish a
|
64
|
-
# [[request]] must actually
|
66
|
+
# Given a [[request]] and a [[response]] object, as prepared
|
67
|
+
# by a [[WEBrick::HTTPServer]] for processing in a portlet,
|
68
|
+
# attempt to accept the client's request to establish a
|
69
|
+
# WebSocket connection. The [[request]] must actually
|
70
|
+
# contain such a request; see
|
65
71
|
# [[websocket_upgrade_request?]].
|
66
72
|
#
|
67
|
-
# The attempt will fail in the theoretical case the client
|
68
|
-
# can't agree on the protocol version to use.
|
69
|
-
# [[accept_webtube]] will prepare a 426
|
70
|
-
# explaining in plain text what
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
73
|
+
# The attempt will fail in the theoretical case the client
|
74
|
+
# and the server can't agree on the protocol version to use.
|
75
|
+
# In such a case, [[accept_webtube]] will prepare a 426
|
76
|
+
# 'Upgrade required' response, explaining in plain text what
|
77
|
+
# the problem is and advertising, using the
|
78
|
+
# [[Sec-WebSocket-Version]] header field, the protocol
|
79
|
+
# version (specifically, 13) it is prepared to speak. When
|
80
|
+
# this happens, the WebSocket session will never be set up
|
81
|
+
# and no [[listener]] events will be called.
|
75
82
|
#
|
76
|
-
# Note that [[accept_webtube]] will manipulate [[response]]
|
77
|
-
# immediately. The actual WebSocket session will
|
78
|
-
# attempts to deliver the [[response]],
|
79
|
-
#
|
80
|
-
# [[listener]].
|
83
|
+
# Note that [[accept_webtube]] will manipulate [[response]]
|
84
|
+
# and return immediately. The actual WebSocket session will
|
85
|
+
# begin once WEBrick attempts to deliver the [[response]],
|
86
|
+
# and will be marked by the newly constructed [[Webtube]]
|
87
|
+
# instance delivering an [[onopen]] event to [[listener]].
|
81
88
|
#
|
82
|
-
# Also note that the loop to process incoming WebSocket
|
83
|
-
# whole thread; in order to deliver
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
89
|
+
# Also note that the loop to process incoming WebSocket
|
90
|
+
# frames will hog the whole thread; in order to deliver
|
91
|
+
# asynchronous messages over the WebSocket,
|
92
|
+
# [[Webtube#send_message]] needs to be called from another
|
93
|
+
# thread. (For synchronous messages, it can safely be
|
94
|
+
# called from the handlers inside [[listener]].)
|
87
95
|
#
|
88
|
-
# See [[Webtube#run]] for a list of the supported methods
|
89
|
-
# [[listener]].
|
96
|
+
# See [[Webtube#run]] for a list of the supported methods
|
97
|
+
# for the [[listener]].
|
90
98
|
def accept_webtube request, response, listener,
|
91
99
|
session: nil, context: nil
|
92
100
|
# Check that the client speaks our version
|
93
|
-
unless (request['Sec-WebSocket-Version'] || '').
|
101
|
+
unless (request['Sec-WebSocket-Version'] || '').
|
102
|
+
strip.split(/\s*,\s*/).
|
94
103
|
include? '13' then
|
95
104
|
@logger.error "Sec-WebSocket-Version mismatch"
|
96
|
-
response.status, response.reason_phrase =
|
105
|
+
response.status, response.reason_phrase =
|
106
|
+
'426', 'Upgrade required'
|
97
107
|
response['Content-type'] = 'text/plain'
|
98
108
|
response['Sec-WebSocket-Version'] = '13'
|
99
109
|
# advertise the version we speak
|
100
|
-
response.body = "This WebSocket server only speaks
|
101
|
-
"protocol, as specified by
|
110
|
+
response.body = "This WebSocket server only speaks " +
|
111
|
+
"version 13 of the protocol, as specified by " +
|
112
|
+
"RFC 6455.\n"
|
102
113
|
else
|
103
|
-
response.status, response.reason_phrase =
|
114
|
+
response.status, response.reason_phrase =
|
115
|
+
'101', 'Hello WebSocket'
|
104
116
|
response['Upgrade'] = 'websocket'
|
105
|
-
response['Sec-WebSocket-Accept'] =
|
106
|
-
|
107
|
-
|
117
|
+
response['Sec-WebSocket-Accept'] =
|
118
|
+
OpenSSL::Digest::SHA1.base64digest(
|
119
|
+
request['Sec-WebSocket-Key'] +
|
120
|
+
'258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
108
121
|
response['Sec-WebSocket-Version'] = '13'
|
109
122
|
response.keep_alive = false
|
110
|
-
# so that WEBrick will close the TCP socket when
|
123
|
+
# so that WEBrick will close the TCP socket when
|
124
|
+
# we're done
|
111
125
|
vital_statistics = self.webtubes
|
112
126
|
(class << response; self; end).instance_eval do
|
113
|
-
# We'll need to deliver the [[Connection: Upgrade]]
|
114
|
-
# unfortunately, HTTPResponse#setup_header
|
115
|
-
# this header field in the
|
116
|
-
#
|
127
|
+
# We'll need to deliver the [[Connection: Upgrade]]
|
128
|
+
# header; unfortunately, HTTPResponse#setup_header
|
129
|
+
# would munge it if we set this header field in the
|
130
|
+
# ordinary way. Accordingly, we'll have to override
|
131
|
+
# the method.
|
117
132
|
define_method :setup_header do ||
|
118
133
|
super()
|
119
134
|
@header['connection'] = 'Upgrade'
|
120
135
|
return
|
121
136
|
end
|
122
137
|
|
123
|
-
# Replace [[response.send_body]] with the WS engine.
|
124
|
-
# call it automatically after sending the
|
138
|
+
# Replace [[response.send_body]] with the WS engine.
|
139
|
+
# WEBrick will call it automatically after sending the
|
140
|
+
# response header.
|
125
141
|
#
|
126
|
-
# Also notify the server's attached
|
127
|
-
# instance so that
|
142
|
+
# Also notify the server's attached
|
143
|
+
# [[Webtube::Vital_Statistics]] instance so that
|
144
|
+
# server shutdown could also close all pending
|
128
145
|
# Webtubes.
|
129
146
|
define_method :send_body do |socket|
|
130
|
-
webtube = Webtube.new
|
147
|
+
webtube = Webtube.new socket, true,
|
148
|
+
close_socket: false
|
131
149
|
begin
|
132
150
|
vital_statistics.birth webtube
|
133
151
|
webtube.header = request
|
134
152
|
webtube.session = session
|
135
153
|
webtube.context = context
|
136
|
-
# Reassign us from the WEBrick's thread group to
|
137
|
-
# maintained by
|
154
|
+
# Reassign us from the WEBrick's thread group to
|
155
|
+
# the one maintained by
|
156
|
+
# [[Webtube::Vital_Statistics]].
|
138
157
|
vital_statistics.thread_group.add Thread.current
|
139
158
|
# And now, run!
|
140
159
|
webtube.run listener
|
@@ -171,7 +190,8 @@ module WEBrick
|
|
171
190
|
# advertise the version we speak
|
172
191
|
# prepare a human-readable content
|
173
192
|
response['Content-type'] = 'text/plain'
|
174
|
-
response.body =
|
193
|
+
response.body =
|
194
|
+
"426\n\nThis is a WebSocket-only resource.\n"
|
175
195
|
end
|
176
196
|
return
|
177
197
|
end
|
data/sample-server.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
#! /usr/bin/ruby
|
2
2
|
|
3
|
-
# A sample WEBrick server using the Webtube API. It listens
|
4
|
-
# provides two services: [[/diag]], which
|
5
|
-
# [[Webtube#run]] and remains silent
|
6
|
-
#
|
3
|
+
# A sample WEBrick server using the Webtube API. It listens
|
4
|
+
# on port 8888 and provides two services: [[/diag]], which
|
5
|
+
# logs all the events from [[Webtube#run]] and remains silent
|
6
|
+
# towards the client, and [[/echo]], which echos.
|
7
7
|
|
8
8
|
require 'webrick'
|
9
9
|
require 'webtube/webrick'
|
data/webtube.gemspec
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'webtube'
|
3
|
-
s.version = '1.
|
4
|
-
s.date = '
|
3
|
+
s.version = '1.1.0'
|
4
|
+
s.date = '2018-04-26'
|
5
5
|
s.homepage = 'https://github.com/digwuren/webtube'
|
6
|
-
s.summary = 'A Ruby implementation of the
|
6
|
+
s.summary = 'A Ruby implementation of the WebSocket protocol'
|
7
7
|
s.author = 'Andres Soolo'
|
8
8
|
s.email = 'dig@mirky.net'
|
9
9
|
s.files = File.read('Manifest.txt').split(/\n/)
|
10
10
|
s.executables << 'wsc'
|
11
|
-
s.license = 'GPL-3'
|
11
|
+
s.license = 'GPL-3.0'
|
12
12
|
s.description = <<EOD
|
13
|
-
Webtube is an implementation of the
|
14
|
-
integration with the
|
13
|
+
Webtube is an implementation of the WebSocket protocol for Ruby.
|
14
|
+
Some integration with the WEBrick server is also included.
|
15
15
|
EOD
|
16
16
|
s.has_rdoc = false
|
17
17
|
end
|
metadata
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webtube
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andres Soolo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
|
-
Webtube is an implementation of the
|
15
|
-
integration with the
|
14
|
+
Webtube is an implementation of the WebSocket protocol for Ruby.
|
15
|
+
Some integration with the WEBrick server is also included.
|
16
16
|
email: dig@mirky.net
|
17
17
|
executables:
|
18
18
|
- wsc
|
@@ -30,7 +30,7 @@ files:
|
|
30
30
|
- webtube.gemspec
|
31
31
|
homepage: https://github.com/digwuren/webtube
|
32
32
|
licenses:
|
33
|
-
- GPL-3
|
33
|
+
- GPL-3.0
|
34
34
|
metadata: {}
|
35
35
|
post_install_message:
|
36
36
|
rdoc_options: []
|
@@ -38,18 +38,18 @@ require_paths:
|
|
38
38
|
- lib
|
39
39
|
required_ruby_version: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
|
-
- -
|
41
|
+
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: '0'
|
44
44
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
|
-
- -
|
46
|
+
- - ">="
|
47
47
|
- !ruby/object:Gem::Version
|
48
48
|
version: '0'
|
49
49
|
requirements: []
|
50
50
|
rubyforge_project:
|
51
|
-
rubygems_version: 2.
|
51
|
+
rubygems_version: 2.5.2.1
|
52
52
|
signing_key:
|
53
53
|
specification_version: 4
|
54
|
-
summary: A Ruby implementation of the
|
54
|
+
summary: A Ruby implementation of the WebSocket protocol
|
55
55
|
test_files: []
|