webtube 1.0.0 → 1.1.0
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.
- 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: []
|