websocket-driver 0.6.5 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 45f6962eb4b7b927eddff1e14c1368464227ef7f
4
- data.tar.gz: 8e98a4638c104fce2ccc752f87eadd1269f8a643
2
+ SHA256:
3
+ metadata.gz: 11392831462e0bee833bdb3ac3f6f09de232e2e8cc7a19799ce0ebb8e7238ea1
4
+ data.tar.gz: 3b4555cc645e77d0166ff9657dc23c306bf48296e94a1520de703f9ab72120f1
5
5
  SHA512:
6
- metadata.gz: fcc93ef62b0bd24fb9f1687874591ff7747c0a02896d395195ff699381b9c3d4d1888c4fbe079e017442f8e79a8c908c514fdf4550f872303fef6351c7484bb2
7
- data.tar.gz: 1f9215cc0926f4d7d6d083939dc2a8c83a6ca89e1eb30af2ae064ec16239936a12f69cd4a2dda12651358a2402a90fd59533b00b8d18e413d580269a418a2bc3
6
+ metadata.gz: 378aaf1b46a663dccd4dbc8ec25e515e9acb7b2c760b7b62c199185e11466d9c2fd6482b17fb51157a03030e0c9065c7f6a6201c61a7f45fc940365cce036626
7
+ data.tar.gz: 783523c78a09ced14fe3e4be497b7fb5e6f22755d6b2dafa5b9b02ed72427512d7b70185dbff43e1df3a924fa086a28334403d7c02b5bc2762fbb33dcf339e3a
@@ -1,123 +1,147 @@
1
+ ### 0.7.3 / 2020-07-09
2
+
3
+ - Let the client accept HTTP responses that have an empty reason phrase
4
+ following the `101` status code
5
+
6
+ ### 0.7.2 / 2020-05-22
7
+
8
+ - Emit `ping` and `pong` events from the `Server` driver
9
+ - Handle draft-76 handshakes correctly if the request's body is a frozen string
10
+
11
+ ### 0.7.1 / 2019-06-10
12
+
13
+ - Catch any exceptions produced while generating a handshake response and send a
14
+ `400 Bad Request` response to the client
15
+ - Pick the RFC-6455 protocol version if the request contains any of the headers
16
+ used by that version
17
+ - Handle errors encountered while handling malformed draft-76 requests
18
+ - Change license from MIT to Apache 2.0
19
+
20
+ ### 0.7.0 / 2017-09-11
21
+
22
+ - Add `ping` and `pong` to the set of events users can listen to
23
+
1
24
  ### 0.6.5 / 2017-01-22
2
25
 
3
- * Provide a pure-Ruby fallback for the native unmasking code
26
+ - Provide a pure-Ruby fallback for the native unmasking code
4
27
 
5
28
  ### 0.6.4 / 2016-05-20
6
29
 
7
- * Amend warnings issued when running with -W2
8
- * Make sure message strings passed in by the app are transcoded to UTF-8
9
- * Copy strings if necessary for frozen-string compatibility
30
+ - Amend warnings issued when running with -W2
31
+ - Make sure message strings passed in by the app are transcoded to UTF-8
32
+ - Copy strings if necessary for frozen-string compatibility
10
33
 
11
34
  ### 0.6.3 / 2015-11-06
12
35
 
13
- * Reject draft-76 handshakes if their Sec-WebSocket-Key headers are invalid
14
- * Throw a more helpful error if a client is created with an invalid URL
36
+ - Reject draft-76 handshakes if their Sec-WebSocket-Key headers are invalid
37
+ - Throw a more helpful error if a client is created with an invalid URL
15
38
 
16
39
  ### 0.6.2 / 2015-07-18
17
40
 
18
- * When the peer sends a close frame with no error code, emit 1000
41
+ - When the peer sends a close frame with no error code, emit 1000
19
42
 
20
43
  ### 0.6.1 / 2015-07-13
21
44
 
22
- * Fix how events are stored in `EventEmitter` to fix a backward-compatibility
45
+ - Fix how events are stored in `EventEmitter` to fix a backward-compatibility
23
46
  violation introduced in the last release
24
- * Use the `Array#pack` and `String#unpack` methods for reading/writing numbers
47
+ - Use the `Array#pack` and `String#unpack` methods for reading/writing numbers
25
48
  to buffers rather than including duplicate logic for this
26
49
 
27
50
  ### 0.6.0 / 2015-07-08
28
51
 
29
- * Use `SecureRandom` to generate the `Sec-WebSocket-Key` header
30
- * Allow the parser to recover cleanly if event listeners raise an error
31
- * Let the `on()` method take a lambda as a positional argument rather than a block
32
- * Add a `pong` method for sending unsolicited pong frames
52
+ - Use `SecureRandom` to generate the `Sec-WebSocket-Key` header
53
+ - Allow the parser to recover cleanly if event listeners raise an error
54
+ - Let the `on()` method take a lambda as a positional argument rather than a
55
+ block
56
+ - Add a `pong` method for sending unsolicited pong frames
33
57
 
34
58
  ### 0.5.4 / 2015-03-29
35
59
 
36
- * Don't emit extra close frames if we receive a close frame after we already
60
+ - Don't emit extra close frames if we receive a close frame after we already
37
61
  sent one
38
- * Fail the connection when the driver receives an invalid
62
+ - Fail the connection when the driver receives an invalid
39
63
  `Sec-WebSocket-Extensions` header
40
64
 
41
65
  ### 0.5.3 / 2015-02-22
42
66
 
43
- * Don't treat incoming data as WebSocket frames if a client driver is closed
67
+ - Don't treat incoming data as WebSocket frames if a client driver is closed
44
68
  before receiving the server handshake
45
69
 
46
70
  ### 0.5.2 / 2015-02-19
47
71
 
48
- * Don't emit multiple `error` events
72
+ - Don't emit multiple `error` events
49
73
 
50
74
  ### 0.5.1 / 2014-12-18
51
75
 
52
- * Don't allow drivers to be created with unrecognized options
76
+ - Don't allow drivers to be created with unrecognized options
53
77
 
54
78
  ### 0.5.0 / 2014-12-13
55
79
 
56
- * Support protocol extensions via the websocket-extensions module
80
+ - Support protocol extensions via the websocket-extensions module
57
81
 
58
82
  ### 0.4.0 / 2014-11-08
59
83
 
60
- * Support connection via HTTP proxies using `CONNECT`
84
+ - Support connection via HTTP proxies using `CONNECT`
61
85
 
62
86
  ### 0.3.5 / 2014-10-04
63
87
 
64
- * Fix bug where the `Server` driver doesn't pass `ping` callbacks to its
88
+ - Fix bug where the `Server` driver doesn't pass `ping` callbacks to its
65
89
  delegate
66
- * Fix an arity error when calling `fail_request`
67
- * Allow `close` to be called before `start` to close the driver
90
+ - Fix an arity error when calling `fail_request`
91
+ - Allow `close` to be called before `start` to close the driver
68
92
 
69
93
  ### 0.3.4 / 2014-07-06
70
94
 
71
- * Don't hold references to frame buffers after a message has been emitted
72
- * Make sure that `protocol` and `version` are exposed properly by the TCP driver
73
- * Correct HTTP header parsing based on RFC 7230; header names cannot contain
95
+ - Don't hold references to frame buffers after a message has been emitted
96
+ - Make sure that `protocol` and `version` are exposed properly by the TCP driver
97
+ - Correct HTTP header parsing based on RFC 7230; header names cannot contain
74
98
  backslashes
75
99
 
76
100
  ### 0.3.3 / 2014-04-24
77
101
 
78
- * Fix problems with loading C and Java native extension code
79
- * Correct the acceptable characters used in the HTTP parser
80
- * Correct the draft-76 status line reason phrase
102
+ - Fix problems with loading C and Java native extension code
103
+ - Correct the acceptable characters used in the HTTP parser
104
+ - Correct the draft-76 status line reason phrase
81
105
 
82
106
  ### 0.3.2 / 2013-12-29
83
107
 
84
- * Expand `max_length` to cover sequences of continuation frames and
108
+ - Expand `max_length` to cover sequences of continuation frames and
85
109
  `draft-{75,76}`
86
- * Decrease default maximum frame buffer size to 64MB
87
- * Stop parsing when the protocol enters a failure mode, to save CPU cycles
110
+ - Decrease default maximum frame buffer size to 64MB
111
+ - Stop parsing when the protocol enters a failure mode, to save CPU cycles
88
112
 
89
113
  ### 0.3.1 / 2013-12-03
90
114
 
91
- * Add a `max_length` option to limit allowed frame size
115
+ - Add a `max_length` option to limit allowed frame size
92
116
 
93
117
  ### 0.3.0 / 2013-09-09
94
118
 
95
- * Support client URLs with Basic Auth credentials
119
+ - Support client URLs with Basic Auth credentials
96
120
 
97
121
  ### 0.2.3 / 2013-08-04
98
122
 
99
- * Fix bug in EventEmitter#emit when listeners are removed
123
+ - Fix bug in EventEmitter#emit when listeners are removed
100
124
 
101
125
  ### 0.2.2 / 2013-08-04
102
126
 
103
- * Fix bug in EventEmitter#listener_count for unregistered events
127
+ - Fix bug in EventEmitter#listener_count for unregistered events
104
128
 
105
129
  ### 0.2.1 / 2013-07-05
106
130
 
107
- * Queue sent messages if the client has not begun trying to connect
108
- * Encode all strings sent to I/O as `ASCII-8BIT`
131
+ - Queue sent messages if the client has not begun trying to connect
132
+ - Encode all strings sent to I/O as `ASCII-8BIT`
109
133
 
110
134
  ### 0.2.0 / 2013-05-12
111
135
 
112
- * Add API for setting and reading headers
113
- * Add Driver.server() method for getting a driver for TCP servers
136
+ - Add API for setting and reading headers
137
+ - Add Driver.server() method for getting a driver for TCP servers
114
138
 
115
139
  ### 0.1.0 / 2013-05-04
116
140
 
117
- * First stable release
141
+ - First stable release
118
142
 
119
143
  ### 0.0.0 / 2013-04-22
120
144
 
121
- * First release
122
- * Proof of concept for people to try out
123
- * Might be unstable
145
+ - First release
146
+ - Proof of concept for people to try out
147
+ - Might be unstable
data/LICENSE.md CHANGED
@@ -1,22 +1,12 @@
1
- # License
1
+ Copyright 2010-2020 James Coglan
2
2
 
3
- (The MIT License)
3
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4
+ this file except in compliance with the License. You may obtain a copy of the
5
+ License at
4
6
 
5
- Copyright (c) 2010-2017 James Coglan
7
+ http://www.apache.org/licenses/LICENSE-2.0
6
8
 
7
- Permission is hereby granted, free of charge, to any person obtaining a copy of
8
- this software and associated documentation files (the 'Software'), to deal in
9
- the Software without restriction, including without limitation the rights to
10
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11
- the Software, and to permit persons to whom the Software is furnished to do so,
12
- subject to the following conditions:
13
-
14
- The above copyright notice and this permission notice shall be included in all
15
- copies or substantial portions of the Software.
16
-
17
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9
+ Unless required by applicable law or agreed to in writing, software distributed
10
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
12
+ specific language governing permissions and limitations under the License.
data/README.md CHANGED
@@ -10,21 +10,21 @@ pluggable I/O.
10
10
  Due to this design, you get a lot of things for free. In particular, if you hook
11
11
  this module up to some I/O object, it will do all of this for you:
12
12
 
13
- * Select the correct server-side driver to talk to the client
14
- * Generate and send both server- and client-side handshakes
15
- * Recognize when the handshake phase completes and the WS protocol begins
16
- * Negotiate subprotocol selection based on `Sec-WebSocket-Protocol`
17
- * Negotiate and use extensions via the
13
+ - Select the correct server-side driver to talk to the client
14
+ - Generate and send both server- and client-side handshakes
15
+ - Recognize when the handshake phase completes and the WS protocol begins
16
+ - Negotiate subprotocol selection based on `Sec-WebSocket-Protocol`
17
+ - Negotiate and use extensions via the
18
18
  [websocket-extensions](https://github.com/faye/websocket-extensions-ruby)
19
19
  module
20
- * Buffer sent messages until the handshake process is finished
21
- * Deal with proxies that defer delivery of the draft-76 handshake body
22
- * Notify you when the socket is open and closed and when messages arrive
23
- * Recombine fragmented messages
24
- * Dispatch text, binary, ping, pong and close frames
25
- * Manage the socket-closing handshake process
26
- * Automatically reply to ping frames with a matching pong
27
- * Apply masking to messages sent by the client
20
+ - Buffer sent messages until the handshake process is finished
21
+ - Deal with proxies that defer delivery of the draft-76 handshake body
22
+ - Notify you when the socket is open and closed and when messages arrive
23
+ - Recombine fragmented messages
24
+ - Dispatch text, binary, ping, pong and close frames
25
+ - Manage the socket-closing handshake process
26
+ - Automatically reply to ping frames with a matching pong
27
+ - Apply masking to messages sent by the client
28
28
 
29
29
  This library was originally extracted from the [Faye](http://faye.jcoglan.com)
30
30
  project but now aims to provide simple WebSocket support for any Ruby server or
@@ -43,12 +43,12 @@ $ gem install websocket-driver
43
43
  To build either a server-side or client-side socket, the only requirement is
44
44
  that you supply a `socket` object with these methods:
45
45
 
46
- * `socket.url` - returns the full URL of the socket as a string.
47
- * `socket.write(string)` - writes the given string to a TCP stream.
46
+ - `socket.url` - returns the full URL of the socket as a string.
47
+ - `socket.write(string)` - writes the given string to a TCP stream.
48
48
 
49
49
  Server-side sockets require one additional method:
50
50
 
51
- * `socket.env` - returns a Rack-style env hash that will contain some of the
51
+ - `socket.env` - returns a Rack-style env hash that will contain some of the
52
52
  following fields. Their values are strings containing the value of the named
53
53
  header, unless stated otherwise.
54
54
  * `HTTP_CONNECTION`
@@ -193,8 +193,8 @@ and send outgoing data.
193
193
  Client drivers have two additional methods for reading the HTTP data that was
194
194
  sent back by the server:
195
195
 
196
- * `driver.status` - the integer value of the HTTP status code
197
- * `driver.headers` - a hash-like object containing the response headers
196
+ - `driver.status` - the integer value of the HTTP status code
197
+ - `driver.headers` - a hash-like object containing the response headers
198
198
 
199
199
 
200
200
  ### HTTP Proxies
@@ -261,9 +261,9 @@ frames.
261
261
  The `options` argument is optional, and is a hash. It may contain the following
262
262
  keys:
263
263
 
264
- * `:max_length` - the maximum allowed size of incoming message frames, in bytes.
264
+ - `:max_length` - the maximum allowed size of incoming message frames, in bytes.
265
265
  The default value is `2^26 - 1`, or 1 byte short of 64 MiB.
266
- * `:protocols` - an array of strings representing acceptable subprotocols for
266
+ - `:protocols` - an array of strings representing acceptable subprotocols for
267
267
  use over the socket. The driver will negotiate one of these to use via the
268
268
  `Sec-WebSocket-Protocol` header if supported by the other peer.
269
269
 
@@ -274,27 +274,38 @@ Note that most of these methods are commands: if they produce data that should
274
274
  be sent over the socket, they will give this to you by calling
275
275
  `socket.write(string)`.
276
276
 
277
- #### `driver.on :open, -> (event) { }`
277
+ #### `driver.on :open, -> (event) {}`
278
278
 
279
279
  Adds a callback block to execute when the socket becomes open.
280
280
 
281
- #### `driver.on :message, -> (event) { }`
281
+ #### `driver.on :message, -> (event) {}`
282
282
 
283
283
  Adds a callback block to execute when a message is received. `event` will have a
284
284
  `data` attribute containing either a string in the case of a text message or an
285
285
  array of integers in the case of a binary message.
286
286
 
287
- #### `driver.on :error, -> (event) { }`
287
+ #### `driver.on :error, -> (event) {}`
288
288
 
289
289
  Adds a callback to execute when a protocol error occurs due to the other peer
290
290
  sending an invalid byte sequence. `event` will have a `message` attribute
291
291
  describing the error.
292
292
 
293
- #### `driver.on :close, -> (event) { }`
293
+ #### `driver.on :close, -> (event) {}`
294
294
 
295
295
  Adds a callback block to execute when the socket becomes closed. The `event`
296
296
  object has `code` and `reason` attributes.
297
297
 
298
+ #### `driver.on :ping, -> (event) {}`
299
+
300
+ Adds a callback block to execute when a ping is received. You do not need to
301
+ handle this by sending a pong frame yourself; the driver handles this for you.
302
+
303
+ #### `driver.on :pong, -> (event) {}`
304
+
305
+ Adds a callback block to execute when a pong is received. If this was in
306
+ response to a ping you sent, you can also handle this event via the
307
+ `driver.ping(message) { ... }` callback.
308
+
298
309
  #### `driver.add_extension(extension)`
299
310
 
300
311
  Registers a protocol extension whose operation will be negotiated via the
@@ -1,8 +1,6 @@
1
1
  package com.jcoglan.websocket;
2
2
 
3
- import java.lang.Long;
4
3
  import java.io.IOException;
5
-
6
4
  import org.jruby.Ruby;
7
5
  import org.jruby.RubyClass;
8
6
  import org.jruby.RubyModule;
@@ -15,41 +13,45 @@ import org.jruby.runtime.builtin.IRubyObject;
15
13
  import org.jruby.runtime.load.BasicLibraryService;
16
14
 
17
15
  public class WebsocketMaskService implements BasicLibraryService {
18
- private Ruby runtime;
16
+ private Ruby runtime;
19
17
 
20
- public boolean basicLoad(Ruby runtime) throws IOException {
21
- this.runtime = runtime;
22
- RubyModule websocket = runtime.defineModule("WebSocket");
18
+ public boolean basicLoad(Ruby runtime) throws IOException {
19
+ this.runtime = runtime;
23
20
 
24
- RubyClass webSocketMask = websocket.defineClassUnder("Mask", runtime.getObject(), new ObjectAllocator() {
25
- public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
26
- return new WebsocketMask(runtime, rubyClass);
27
- }
28
- });
21
+ RubyModule websocket = runtime.defineModule("WebSocket");
22
+ RubyClass webSocketMask = websocket.defineClassUnder("Mask", runtime.getObject(), getAllocator());
29
23
 
30
- webSocketMask.defineAnnotatedMethods(WebsocketMask.class);
31
- return true;
32
- }
24
+ webSocketMask.defineAnnotatedMethods(WebsocketMask.class);
25
+ return true;
26
+ }
33
27
 
34
- public class WebsocketMask extends RubyObject {
35
- public WebsocketMask(final Ruby runtime, RubyClass rubyClass) {
36
- super(runtime, rubyClass);
28
+ ObjectAllocator getAllocator() {
29
+ return new ObjectAllocator() {
30
+ public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
31
+ return new WebsocketMask(runtime, rubyClass);
32
+ }
33
+ };
37
34
  }
38
35
 
39
- @JRubyMethod
40
- public IRubyObject mask(ThreadContext context, IRubyObject payload, IRubyObject mask) {
41
- if (mask.isNil()) return payload;
36
+ public class WebsocketMask extends RubyObject {
37
+ public WebsocketMask(final Ruby runtime, RubyClass rubyClass) {
38
+ super(runtime, rubyClass);
39
+ }
40
+
41
+ @JRubyMethod
42
+ public IRubyObject mask(ThreadContext context, IRubyObject payload, IRubyObject mask) {
43
+ if (mask.isNil()) return payload;
42
44
 
43
- byte[] payload_a = ((RubyString)payload).getBytes();
44
- byte[] mask_a = ((RubyString)mask).getBytes();
45
- int i, n = payload_a.length;
45
+ byte[] payload_a = ((RubyString)payload).getBytes();
46
+ byte[] mask_a = ((RubyString)mask).getBytes();
47
+ int i, n = payload_a.length;
46
48
 
47
- if (n == 0) return payload;
49
+ if (n == 0) return payload;
48
50
 
49
- for (i = 0; i < n; i++) {
50
- payload_a[i] ^= mask_a[i % 4];
51
- }
52
- return RubyString.newStringNoCopy(runtime, payload_a);
51
+ for (i = 0; i < n; i++) {
52
+ payload_a[i] ^= mask_a[i % 4];
53
+ }
54
+ return RubyString.newStringNoCopy(runtime, payload_a);
55
+ }
53
56
  }
54
- }
55
57
  }
@@ -1,41 +1,32 @@
1
1
  #include <ruby.h>
2
2
 
3
- VALUE WebSocket = Qnil;
4
- VALUE WebSocketMask = Qnil;
5
-
6
- void Init_websocket_mask();
7
- VALUE method_websocket_mask(VALUE self, VALUE payload, VALUE mask);
8
-
9
- void
10
- Init_websocket_mask()
3
+ VALUE method_websocket_mask(VALUE self, VALUE payload, VALUE mask)
11
4
  {
12
- WebSocket = rb_define_module("WebSocket");
13
- WebSocketMask = rb_define_module_under(WebSocket, "Mask");
14
- rb_define_singleton_method(WebSocketMask, "mask", method_websocket_mask, 2);
15
- }
5
+ char *payload_s, *mask_s, *unmasked_s;
6
+ long i, n;
7
+ VALUE unmasked;
16
8
 
17
- VALUE
18
- method_websocket_mask(VALUE self,
19
- VALUE payload,
20
- VALUE mask)
21
- {
22
- char *payload_s, *mask_s, *unmasked_s;
23
- long i, n;
24
- VALUE unmasked;
9
+ if (mask == Qnil || RSTRING_LEN(mask) != 4) {
10
+ return payload;
11
+ }
25
12
 
26
- if (mask == Qnil || RSTRING_LEN(mask) != 4) {
27
- return payload;
28
- }
13
+ payload_s = RSTRING_PTR(payload);
14
+ mask_s = RSTRING_PTR(mask);
15
+ n = RSTRING_LEN(payload);
29
16
 
30
- payload_s = RSTRING_PTR(payload);
31
- mask_s = RSTRING_PTR(mask);
32
- n = RSTRING_LEN(payload);
17
+ unmasked = rb_str_new(0, n);
18
+ unmasked_s = RSTRING_PTR(unmasked);
33
19
 
34
- unmasked = rb_str_new(0, n);
35
- unmasked_s = RSTRING_PTR(unmasked);
20
+ for (i = 0; i < n; i++) {
21
+ unmasked_s[i] = payload_s[i] ^ mask_s[i % 4];
22
+ }
23
+ return unmasked;
24
+ }
25
+
26
+ void Init_websocket_mask()
27
+ {
28
+ VALUE WebSocket = rb_define_module("WebSocket");
29
+ VALUE Mask = rb_define_module_under(WebSocket, "Mask");
36
30
 
37
- for (i = 0; i < n; i++) {
38
- unmasked_s[i] = payload_s[i] ^ mask_s[i % 4];
39
- }
40
- return unmasked;
31
+ rb_define_singleton_method(Mask, "mask", method_websocket_mask, 2);
41
32
  }
@@ -50,6 +50,8 @@ module WebSocket
50
50
  ConnectEvent = Struct.new(nil)
51
51
  OpenEvent = Struct.new(nil)
52
52
  MessageEvent = Struct.new(:data)
53
+ PingEvent = Struct.new(:data)
54
+ PongEvent = Struct.new(:data)
53
55
  CloseEvent = Struct.new(:code, :reason)
54
56
 
55
57
  ProtocolError = Class.new(StandardError)
@@ -99,8 +101,17 @@ module WebSocket
99
101
 
100
102
  def start
101
103
  return false unless @ready_state == 0
102
- response = handshake_response
103
- return false unless response
104
+
105
+ unless Driver.websocket?(@socket.env)
106
+ return fail_handshake(ProtocolError.new('Not a WebSocket request'))
107
+ end
108
+
109
+ begin
110
+ response = handshake_response
111
+ rescue => error
112
+ return fail_handshake(error)
113
+ end
114
+
104
115
  @socket.write(response)
105
116
  open unless @stage == -1
106
117
  true
@@ -132,6 +143,24 @@ module WebSocket
132
143
 
133
144
  private
134
145
 
146
+ def fail_handshake(error)
147
+ headers = Headers.new
148
+ headers['Content-Type'] = 'text/plain'
149
+ headers['Content-Length'] = error.message.bytesize
150
+
151
+ headers = ['HTTP/1.1 400 Bad Request', headers.to_s, error.message]
152
+ @socket.write(headers.join("\r\n"))
153
+ fail(:protocol_error, error.message)
154
+
155
+ false
156
+ end
157
+
158
+ def fail(type, message)
159
+ @ready_state = 2
160
+ emit(:error, ProtocolError.new(message))
161
+ close
162
+ end
163
+
135
164
  def open
136
165
  @ready_state = 1
137
166
  @queue.each { |message| frame(*message) }
@@ -153,10 +182,15 @@ module WebSocket
153
182
  end
154
183
 
155
184
  def self.rack(socket, options = {})
156
- env = socket.env
157
- if env['HTTP_SEC_WEBSOCKET_VERSION']
185
+ env = socket.env
186
+ version = env['HTTP_SEC_WEBSOCKET_VERSION']
187
+ key = env['HTTP_SEC_WEBSOCKET_KEY']
188
+ key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
189
+ key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
190
+
191
+ if version or key
158
192
  Hybi.new(socket, options.merge(:require_masking => true))
159
- elsif env['HTTP_SEC_WEBSOCKET_KEY1']
193
+ elsif key1 or key2
160
194
  Draft76.new(socket, options)
161
195
  else
162
196
  Draft75.new(socket, options)
@@ -181,7 +215,7 @@ module WebSocket
181
215
  def self.validate_options(options, valid_keys)
182
216
  options.keys.each do |key|
183
217
  unless valid_keys.include?(key)
184
- raise ConfigurationError, "Unrecognized option: #{key.inspect}"
218
+ raise ConfigurationError, "Unrecognized option: #{ key.inspect }"
185
219
  end
186
220
  end
187
221
  end
@@ -20,10 +20,10 @@ module WebSocket
20
20
 
21
21
  uri = URI.parse(@socket.url)
22
22
  unless VALID_SCHEMES.include?(uri.scheme)
23
- raise URIError, "#{socket.url} is not a valid WebSocket URL"
23
+ raise URIError, "#{ socket.url } is not a valid WebSocket URL"
24
24
  end
25
25
 
26
- host = uri.host + (uri.port ? ":#{uri.port}" : '')
26
+ host = uri.host + (uri.port ? ":#{ uri.port }" : '')
27
27
  path = (uri.path == '') ? '/' : uri.path
28
28
  @pathname = path + (uri.query ? '?' + uri.query : '')
29
29
 
@@ -31,7 +31,7 @@ module WebSocket
31
31
  @headers['Upgrade'] = 'websocket'
32
32
  @headers['Connection'] = 'Upgrade'
33
33
  @headers['Sec-WebSocket-Key'] = @key
34
- @headers['Sec-WebSocket-Version'] = '13'
34
+ @headers['Sec-WebSocket-Version'] = VERSION
35
35
 
36
36
  if @protocols.size > 0
37
37
  @headers['Sec-WebSocket-Protocol'] = @protocols * ', '
@@ -44,7 +44,7 @@ module WebSocket
44
44
  end
45
45
 
46
46
  def version
47
- 'hybi-13'
47
+ "hybi-#{ VERSION }"
48
48
  end
49
49
 
50
50
  def proxy(origin, options = {})
@@ -73,19 +73,19 @@ module WebSocket
73
73
  parse(@http.body)
74
74
  end
75
75
 
76
- private
76
+ private
77
77
 
78
78
  def handshake_request
79
79
  extensions = @extensions.generate_offer
80
80
  @headers['Sec-WebSocket-Extensions'] = extensions if extensions
81
81
 
82
- start = "GET #{@pathname} HTTP/1.1"
82
+ start = "GET #{ @pathname } HTTP/1.1"
83
83
  headers = [start, @headers.to_s, '']
84
84
  headers.join("\r\n")
85
85
  end
86
86
 
87
87
  def fail_handshake(message)
88
- message = "Error during WebSocket handshake: #{message}"
88
+ message = "Error during WebSocket handshake: #{ message }"
89
89
  @ready_state = 3
90
90
  emit(:error, ProtocolError.new(message))
91
91
  emit(:close, CloseEvent.new(ERRORS[:protocol_error], message))
@@ -96,7 +96,7 @@ module WebSocket
96
96
  @headers = Headers.new(@http.headers)
97
97
 
98
98
  unless @http.code == 101
99
- return fail_handshake("Unexpected response code: #{@http.code}")
99
+ return fail_handshake("Unexpected response code: #{ @http.code }")
100
100
  end
101
101
 
102
102
  upgrade = @http['Upgrade'] || ''
@@ -6,9 +6,10 @@ module WebSocket
6
6
 
7
7
  def initialize(socket, options = {})
8
8
  super
9
- input = @socket.env['rack.input']
9
+ input = (@socket.env['rack.input'] || StringIO.new('')).read
10
+ input = input.dup if input.frozen?
10
11
  @stage = -1
11
- @body = (input ? input.read : String.new('')).force_encoding(BINARY)
12
+ @body = input.force_encoding(BINARY)
12
13
 
13
14
  @headers.clear
14
15
  @headers['Upgrade'] = 'WebSocket'
@@ -29,7 +30,7 @@ module WebSocket
29
30
 
30
31
  def close(reason = nil, code = nil)
31
32
  return false if @ready_state == 3
32
- @socket.write([0xFF, 0x00].pack('C*'))
33
+ @socket.write([0xFF, 0x00].pack('C*')) if @ready_state == 1
33
34
  @ready_state = 3
34
35
  emit(:close, CloseEvent.new(nil, nil))
35
36
  true
@@ -39,19 +40,20 @@ module WebSocket
39
40
 
40
41
  def handshake_response
41
42
  env = @socket.env
42
-
43
43
  key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
44
+ key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
45
+
46
+ raise ProtocolError.new('Missing required header: Sec-WebSocket-Key1') unless key1
47
+ raise ProtocolError.new('Missing required header: Sec-WebSocket-Key2') unless key2
48
+
44
49
  number1 = number_from_key(key1)
45
50
  spaces1 = spaces_in_key(key1)
46
51
 
47
- key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
48
52
  number2 = number_from_key(key2)
49
53
  spaces2 = spaces_in_key(key2)
50
54
 
51
55
  if number1 % spaces1 != 0 or number2 % spaces2 != 0
52
- emit(:error, ProtocolError.new('Client sent invalid Sec-WebSocket-Key headers'))
53
- close
54
- return nil
56
+ raise ProtocolError.new('Client sent invalid Sec-WebSocket-Key headers')
55
57
  end
56
58
 
57
59
  @key_values = [number1 / spaces1, number2 / spaces2]
@@ -84,7 +86,8 @@ module WebSocket
84
86
  end
85
87
 
86
88
  def number_from_key(key)
87
- key.scan(/[0-9]/).join('').to_i(10)
89
+ number = key.scan(/[0-9]/).join('')
90
+ number == '' ? Float::NAN : number.to_i(10)
88
91
  end
89
92
 
90
93
  def spaces_in_key(key)
@@ -25,7 +25,7 @@ module WebSocket
25
25
  return if value.nil?
26
26
  key = HTTP.normalize_header(name)
27
27
  return unless @sent.add?(key) or ALLOWED_DUPLICATES.include?(key)
28
- @lines << "#{name.strip}: #{value.to_s.strip}\r\n"
28
+ @lines << "#{ name.strip }: #{ value.to_s.strip }\r\n"
29
29
  end
30
30
 
31
31
  def inspect
@@ -11,7 +11,8 @@ module WebSocket
11
11
  Base64.strict_encode64(Digest::SHA1.digest(key + GUID))
12
12
  end
13
13
 
14
- GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
14
+ VERSION = '13'
15
+ GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
15
16
 
16
17
  BYTE = 0b11111111
17
18
  FIN = MASK = 0b10000000
@@ -51,7 +52,7 @@ module WebSocket
51
52
  MIN_RESERVED_ERROR = 3000
52
53
  MAX_RESERVED_ERROR = 4999
53
54
 
54
- PACK_FORMATS = {2 => 'n', 8 => 'Q>'}
55
+ PACK_FORMATS = { 2 => 'n', 8 => 'Q>' }
55
56
 
56
57
  def initialize(socket, options = {})
57
58
  super
@@ -68,22 +69,16 @@ module WebSocket
68
69
 
69
70
  return unless @socket.respond_to?(:env)
70
71
 
71
- sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
72
- protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
73
-
74
- @headers['Upgrade'] = 'websocket'
75
- @headers['Connection'] = 'Upgrade'
76
- @headers['Sec-WebSocket-Accept'] = Hybi.generate_accept(sec_key)
77
-
78
72
  if protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
79
73
  protos = protos.split(/ *, */) if String === protos
80
74
  @protocol = protos.find { |p| @protocols.include?(p) }
81
- @headers['Sec-WebSocket-Protocol'] = @protocol if @protocol
75
+ else
76
+ @protocol = nil
82
77
  end
83
78
  end
84
79
 
85
80
  def version
86
- "hybi-#{@socket.env['HTTP_SEC_WEBSOCKET_VERSION']}"
81
+ "hybi-#{ VERSION }"
87
82
  end
88
83
 
89
84
  def add_extension(extension)
@@ -229,13 +224,24 @@ module WebSocket
229
224
  end
230
225
 
231
226
  def handshake_response
232
- begin
233
- extensions = @extensions.generate_response(@socket.env['HTTP_SEC_WEBSOCKET_EXTENSIONS'])
234
- rescue => error
235
- fail(:protocol_error, error.message)
236
- return nil
227
+ sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
228
+ version = @socket.env['HTTP_SEC_WEBSOCKET_VERSION']
229
+
230
+ unless version == VERSION
231
+ raise ProtocolError.new("Unsupported WebSocket version: #{ VERSION }")
232
+ end
233
+
234
+ unless sec_key
235
+ raise ProtocolError.new('Missing handshake request header: Sec-WebSocket-Key')
237
236
  end
238
237
 
238
+ @headers['Upgrade'] = 'websocket'
239
+ @headers['Connection'] = 'Upgrade'
240
+ @headers['Sec-WebSocket-Accept'] = Hybi.generate_accept(sec_key)
241
+
242
+ @headers['Sec-WebSocket-Protocol'] = @protocol if @protocol
243
+
244
+ extensions = @extensions.generate_response(@socket.env['HTTP_SEC_WEBSOCKET_EXTENSIONS'])
239
245
  @headers['Sec-WebSocket-Extensions'] = extensions if extensions
240
246
 
241
247
  start = 'HTTP/1.1 101 Switching Protocols'
@@ -275,17 +281,17 @@ module WebSocket
275
281
 
276
282
  unless @extensions.valid_frame_rsv?(@frame)
277
283
  return fail(:protocol_error,
278
- "One or more reserved bits are on: reserved1 = #{@frame.rsv1 ? 1 : 0}" +
279
- ", reserved2 = #{@frame.rsv2 ? 1 : 0 }" +
280
- ", reserved3 = #{@frame.rsv3 ? 1 : 0 }")
284
+ "One or more reserved bits are on: reserved1 = #{ @frame.rsv1 ? 1 : 0 }" +
285
+ ", reserved2 = #{ @frame.rsv2 ? 1 : 0 }" +
286
+ ", reserved3 = #{ @frame.rsv3 ? 1 : 0 }")
281
287
  end
282
288
 
283
289
  unless OPCODES.values.include?(@frame.opcode)
284
- return fail(:protocol_error, "Unrecognized frame opcode: #{@frame.opcode}")
290
+ return fail(:protocol_error, "Unrecognized frame opcode: #{ @frame.opcode }")
285
291
  end
286
292
 
287
293
  unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.final
288
- return fail(:protocol_error, "Received fragmented control frame: opcode = #{@frame.opcode}")
294
+ return fail(:protocol_error, "Received fragmented control frame: opcode = #{ @frame.opcode }")
289
295
  end
290
296
 
291
297
  if @message and OPENING_OPCODES.include?(@frame.opcode)
@@ -315,7 +321,7 @@ module WebSocket
315
321
  @stage = @frame.masked ? 3 : 4
316
322
 
317
323
  unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.length <= 125
318
- return fail(:protocol_error, "Received control frame having too long payload: #{@frame.length}")
324
+ return fail(:protocol_error, "Received control frame having too long payload: #{ @frame.length }")
319
325
  end
320
326
 
321
327
  return unless check_frame_length
@@ -368,12 +374,14 @@ module WebSocket
368
374
 
369
375
  when OPCODES[:ping] then
370
376
  frame(payload, :pong)
377
+ emit(:ping, PingEvent.new(payload))
371
378
 
372
379
  when OPCODES[:pong] then
373
380
  message = Driver.encode(payload, UNICODE)
374
381
  callback = @ping_callbacks[message]
375
382
  @ping_callbacks.delete(message)
376
383
  callback.call if callback
384
+ emit(:pong, PongEvent.new(payload))
377
385
  end
378
386
 
379
387
  emit_message if frame.final and MESSAGE_OPCODES.include?(opcode)
@@ -4,7 +4,7 @@ module WebSocket
4
4
  class Proxy
5
5
  include EventEmitter
6
6
 
7
- PORTS = {'ws' => 80, 'wss' => 443}
7
+ PORTS = { 'ws' => 80, 'wss' => 443 }
8
8
 
9
9
  attr_reader :status, :headers
10
10
 
@@ -20,7 +20,7 @@ module WebSocket
20
20
  @state = 0
21
21
 
22
22
  @headers = Headers.new
23
- @headers['Host'] = @origin.host + (@origin.port ? ":#{@origin.port}" : '')
23
+ @headers['Host'] = @origin.host + (@origin.port ? ":#{ @origin.port }" : '')
24
24
  @headers['Connection'] = 'keep-alive'
25
25
  @headers['Proxy-Connection'] = 'keep-alive'
26
26
 
@@ -41,7 +41,7 @@ module WebSocket
41
41
  @state = 1
42
42
 
43
43
  port = @origin.port || PORTS[@origin.scheme]
44
- start = "CONNECT #{@origin.host}:#{port} HTTP/1.1"
44
+ start = "CONNECT #{ @origin.host }:#{ port } HTTP/1.1"
45
45
  headers = [start, @headers.to_s, '']
46
46
 
47
47
  @socket.write(headers.join("\r\n"))
@@ -58,7 +58,7 @@ module WebSocket
58
58
  if @status == 200
59
59
  emit(:connect, ConnectEvent.new)
60
60
  else
61
- message = "Can't establish a connection to the server at #{@socket.url}"
61
+ message = "Can't establish a connection to the server at #{ @socket.url }"
62
62
  emit(:error, ProtocolError.new(message))
63
63
  end
64
64
  end
@@ -2,7 +2,7 @@ module WebSocket
2
2
  class Driver
3
3
 
4
4
  class Server < Driver
5
- EVENTS = %w[open message error close]
5
+ EVENTS = %w[open message error close ping pong]
6
6
 
7
7
  def initialize(socket, options = {})
8
8
  super
@@ -17,9 +17,9 @@ module WebSocket
17
17
  def url
18
18
  return nil unless e = env
19
19
 
20
- url = "ws://#{e['HTTP_HOST']}"
20
+ url = "ws://#{ e['HTTP_HOST'] }"
21
21
  url << e['PATH_INFO']
22
- url << "?#{e['QUERY_STRING']}" unless e['QUERY_STRING'] == ''
22
+ url << "?#{ e['QUERY_STRING'] }" unless e['QUERY_STRING'] == ''
23
23
  url
24
24
  end
25
25
 
@@ -30,11 +30,11 @@ module WebSocket
30
30
  super
31
31
  @headers.each do |name, value|
32
32
  rack_name = name.upcase.gsub(/-/, '_')
33
- rack_name = "HTTP_#{rack_name}" unless RESERVED_HEADERS.include?(name)
33
+ rack_name = "HTTP_#{ rack_name }" unless RESERVED_HEADERS.include?(name)
34
34
  @env[rack_name] = value
35
35
  end
36
36
  if host = @env['HTTP_HOST']
37
- uri = URI.parse("http://#{host}")
37
+ uri = URI.parse("http://#{ host }")
38
38
  @env['SERVER_NAME'] = uri.host
39
39
  @env['SERVER_PORT'] = uri.port.to_s
40
40
  end
@@ -4,7 +4,7 @@ module WebSocket
4
4
  class Response
5
5
  include Headers
6
6
 
7
- STATUS_LINE = /^(HTTP\/[0-9]+\.[0-9]+) ([0-9]{3}) ([\x20-\x7e]+)$/
7
+ STATUS_LINE = /^(HTTP\/[0-9]+\.[0-9]+) ([0-9]{3}) ([\x20-\x7e]*)$/
8
8
 
9
9
  attr_reader :code
10
10
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: websocket-driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Coglan
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-22 00:00:00.000000000 Z
11
+ date: 2020-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: websocket-extensions
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: rake-compiler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 0.8.0
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 0.8.0
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +80,7 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
- description:
83
+ description:
84
84
  email: jcoglan@gmail.com
85
85
  executables: []
86
86
  extensions:
@@ -91,7 +91,6 @@ files:
91
91
  - CHANGELOG.md
92
92
  - LICENSE.md
93
93
  - README.md
94
- - examples/tcp_server.rb
95
94
  - ext/websocket-driver/WebsocketMaskService.java
96
95
  - ext/websocket-driver/extconf.rb
97
96
  - ext/websocket-driver/websocket_mask.c
@@ -113,11 +112,11 @@ files:
113
112
  - lib/websocket/http/response.rb
114
113
  - lib/websocket/mask.rb
115
114
  - lib/websocket/websocket_mask.rb
116
- homepage: http://github.com/faye/websocket-driver-ruby
115
+ homepage: https://github.com/faye/websocket-driver-ruby
117
116
  licenses:
118
- - MIT
117
+ - Apache-2.0
119
118
  metadata: {}
120
- post_install_message:
119
+ post_install_message:
121
120
  rdoc_options:
122
121
  - "--main"
123
122
  - README.md
@@ -136,9 +135,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
135
  - !ruby/object:Gem::Version
137
136
  version: '0'
138
137
  requirements: []
139
- rubyforge_project:
140
- rubygems_version: 2.6.8
141
- signing_key:
138
+ rubygems_version: 3.1.2
139
+ signing_key:
142
140
  specification_version: 4
143
141
  summary: WebSocket protocol handler with pluggable I/O
144
142
  test_files: []
@@ -1,28 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler/setup'
3
- require 'eventmachine'
4
- require 'websocket/driver'
5
- require 'permessage_deflate'
6
-
7
- module Connection
8
- def initialize
9
- @driver = WebSocket::Driver.server(self)
10
- @driver.add_extension(PermessageDeflate)
11
-
12
- @driver.on(:connect) { |e| @driver.start if WebSocket::Driver.websocket? @driver.env }
13
- @driver.on(:message) { |e| @driver.frame(e.data) }
14
- @driver.on(:close) { |e| close_connection_after_writing }
15
- end
16
-
17
- def receive_data(data)
18
- @driver.parse(data)
19
- end
20
-
21
- def write(data)
22
- send_data(data)
23
- end
24
- end
25
-
26
- EM.run {
27
- EM.start_server('127.0.0.1', ARGV[0], Connection)
28
- }