websocket-driver 0.3.5 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +45 -0
- data/ext/websocket-driver/websocket_mask.c +9 -8
- data/lib/websocket/driver.rb +1 -0
- data/lib/websocket/driver/client.rb +29 -23
- data/lib/websocket/driver/draft75.rb +8 -8
- data/lib/websocket/driver/draft76.rb +9 -8
- data/lib/websocket/driver/headers.rb +6 -2
- data/lib/websocket/driver/hybi.rb +11 -15
- data/lib/websocket/driver/proxy.rb +68 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8bb8b233ccc5e031bc6da97c6c716ed50aab3d07
|
4
|
+
data.tar.gz: 47be97004eb7ff7714b91d1e970b14fa53a8adb9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b48d1dd1e68a35529b77f1f775146e6ea40961939ec124e2c31f42356473a9071533e61465574a0ddc2e8f482908316f36775c5a02f9447c8839ca435ff9b17
|
7
|
+
data.tar.gz: 2d07d08ae2caa027f6b22d07325bcb2ba509c2d9349362731ef34a3f47396fc29ef3ebccb60afba594740b8e024b7d09162115695eaf094f2063d606d74f28cb
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -193,6 +193,51 @@ sent back by the server:
|
|
193
193
|
* `driver.headers` - a hash-like object containing the response headers
|
194
194
|
|
195
195
|
|
196
|
+
### HTTP Proxies
|
197
|
+
|
198
|
+
The client driver supports connections via HTTP proxies using the `CONNECT`
|
199
|
+
method. Instead of sending the WebSocket handshake immediately, it will send a
|
200
|
+
`CONNECT` request, wait for a `200` response, and then proceed as normal.
|
201
|
+
|
202
|
+
To use this feature, call `proxy = driver.proxy(url)` where `url` is the origin
|
203
|
+
of the proxy, including a username and password if required. This produces an
|
204
|
+
object that manages the process of connecting via the proxy. You should call
|
205
|
+
`proxy.start` to begin the connection process, and pass data you receive via the
|
206
|
+
socket to `proxy.parse(data)`. When the proxy emits `:connect`, you should then
|
207
|
+
start sending incoming data to `driver.parse(data)` as normal, and call
|
208
|
+
`driver.start`.
|
209
|
+
|
210
|
+
```rb
|
211
|
+
proxy = driver.proxy('http://username:password@proxy.example.com')
|
212
|
+
|
213
|
+
proxy.on :connect do
|
214
|
+
driver.start
|
215
|
+
end
|
216
|
+
```
|
217
|
+
|
218
|
+
The proxy's `:connect` event is also where you should perform a TLS handshake on
|
219
|
+
your TCP stream, if you are connecting to a `wss:` endpoint.
|
220
|
+
|
221
|
+
In the event that proxy connection fails, `proxy` will emit an `:error`. You can
|
222
|
+
inspect the proxy's response via `proxy.status` and `proxy.headers`.
|
223
|
+
|
224
|
+
```rb
|
225
|
+
proxy.on :error do |error|
|
226
|
+
puts error.message
|
227
|
+
puts proxy.status
|
228
|
+
puts proxy.headers.inspect
|
229
|
+
end
|
230
|
+
```
|
231
|
+
|
232
|
+
Before calling `proxy.start` you can set custom headers using
|
233
|
+
`proxy.set_header`:
|
234
|
+
|
235
|
+
```rb
|
236
|
+
proxy.set_header('User-Agent', 'ruby')
|
237
|
+
proxy.start
|
238
|
+
```
|
239
|
+
|
240
|
+
|
196
241
|
### Driver API
|
197
242
|
|
198
243
|
Drivers are created using one of the following methods:
|
@@ -13,19 +13,20 @@ void Init_websocket_mask() {
|
|
13
13
|
}
|
14
14
|
|
15
15
|
VALUE method_websocket_mask(VALUE self, VALUE payload, VALUE mask) {
|
16
|
+
int n, i, p, m;
|
17
|
+
int mask_array[4];
|
18
|
+
VALUE unmasked;
|
19
|
+
|
16
20
|
if (mask == Qnil || RARRAY_LEN(mask) == 0) {
|
17
21
|
return payload;
|
18
22
|
}
|
19
23
|
|
20
|
-
|
21
|
-
|
24
|
+
n = RARRAY_LEN(payload);
|
25
|
+
unmasked = rb_ary_new2(n);
|
22
26
|
|
23
|
-
|
24
|
-
NUM2INT(rb_ary_entry(mask,
|
25
|
-
|
26
|
-
NUM2INT(rb_ary_entry(mask, 2)),
|
27
|
-
NUM2INT(rb_ary_entry(mask, 3))
|
28
|
-
};
|
27
|
+
for (i = 0; i < 4; i++) {
|
28
|
+
mask_array[i] = NUM2INT(rb_ary_entry(mask, i));
|
29
|
+
}
|
29
30
|
|
30
31
|
for (i = 0; i < n; i++) {
|
31
32
|
p = NUM2INT(rb_ary_entry(payload, i));
|
data/lib/websocket/driver.rb
CHANGED
@@ -15,12 +15,36 @@ module WebSocket
|
|
15
15
|
@key = Client.generate_key
|
16
16
|
@accept = Hybi.generate_accept(@key)
|
17
17
|
@http = HTTP::Response.new
|
18
|
+
|
19
|
+
uri = URI.parse(@socket.url)
|
20
|
+
host = uri.host + (uri.port ? ":#{uri.port}" : '')
|
21
|
+
path = (uri.path == '') ? '/' : uri.path
|
22
|
+
@pathname = path + (uri.query ? '?' + uri.query : '')
|
23
|
+
|
24
|
+
@headers['Host'] = host
|
25
|
+
@headers['Upgrade'] = 'websocket'
|
26
|
+
@headers['Connection'] = 'Upgrade'
|
27
|
+
@headers['Sec-WebSocket-Key'] = @key
|
28
|
+
@headers['Sec-WebSocket-Version'] = '13'
|
29
|
+
|
30
|
+
if @protocols.size > 0
|
31
|
+
@headers['Sec-WebSocket-Protocol'] = @protocols * ', '
|
32
|
+
end
|
33
|
+
|
34
|
+
if uri.user
|
35
|
+
auth = Base64.encode64([uri.user, uri.password] * ':').gsub(/\n/, '')
|
36
|
+
@headers['Authorization'] = 'Basic ' + auth
|
37
|
+
end
|
18
38
|
end
|
19
39
|
|
20
40
|
def version
|
21
41
|
'hybi-13'
|
22
42
|
end
|
23
43
|
|
44
|
+
def proxy(origin, options = {})
|
45
|
+
Proxy.new(self, origin, options)
|
46
|
+
end
|
47
|
+
|
24
48
|
def start
|
25
49
|
return false unless @ready_state == -1
|
26
50
|
@socket.write(Driver.encode(handshake_request, :binary))
|
@@ -30,8 +54,10 @@ module WebSocket
|
|
30
54
|
|
31
55
|
def parse(buffer)
|
32
56
|
return super if @ready_state > 0
|
57
|
+
|
33
58
|
@http.parse(buffer)
|
34
59
|
return fail_handshake('Invalid HTTP response') if @http.error?
|
60
|
+
|
35
61
|
validate_handshake if @http.complete?
|
36
62
|
parse(@http.body) if @ready_state == 1
|
37
63
|
end
|
@@ -39,29 +65,9 @@ module WebSocket
|
|
39
65
|
private
|
40
66
|
|
41
67
|
def handshake_request
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
query = uri.query ? "?#{uri.query}" : ''
|
46
|
-
|
47
|
-
headers = [ "GET #{path}#{query} HTTP/1.1",
|
48
|
-
"Host: #{host}",
|
49
|
-
"Upgrade: websocket",
|
50
|
-
"Connection: Upgrade",
|
51
|
-
"Sec-WebSocket-Key: #{@key}",
|
52
|
-
"Sec-WebSocket-Version: 13"
|
53
|
-
]
|
54
|
-
|
55
|
-
if @protocols.size > 0
|
56
|
-
headers << "Sec-WebSocket-Protocol: #{@protocols * ', '}"
|
57
|
-
end
|
58
|
-
|
59
|
-
if uri.user
|
60
|
-
auth = Base64.encode64([uri.user, uri.password] * ':').gsub(/\n/, '')
|
61
|
-
headers << "Authorization: Basic #{auth}"
|
62
|
-
end
|
63
|
-
|
64
|
-
(headers + [@headers.to_s, '']).join("\r\n")
|
68
|
+
start = "GET #{@pathname} HTTP/1.1"
|
69
|
+
headers = [start, @headers.to_s, '']
|
70
|
+
headers.join("\r\n")
|
65
71
|
end
|
66
72
|
|
67
73
|
def fail_handshake(message)
|
@@ -5,6 +5,11 @@ module WebSocket
|
|
5
5
|
def initialize(socket, options = {})
|
6
6
|
super
|
7
7
|
@stage = 0
|
8
|
+
|
9
|
+
@headers['Upgrade'] = 'WebSocket'
|
10
|
+
@headers['Connection'] = 'Upgrade'
|
11
|
+
@headers['WebSocket-Origin'] = @socket.env['HTTP_ORIGIN']
|
12
|
+
@headers['WebSocket-Location'] = @socket.url
|
8
13
|
end
|
9
14
|
|
10
15
|
def version
|
@@ -73,14 +78,9 @@ module WebSocket
|
|
73
78
|
private
|
74
79
|
|
75
80
|
def handshake_response
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
upgrade << "WebSocket-Origin: #{@socket.env['HTTP_ORIGIN']}\r\n"
|
80
|
-
upgrade << "WebSocket-Location: #{@socket.url}\r\n"
|
81
|
-
upgrade << @headers.to_s
|
82
|
-
upgrade << "\r\n"
|
83
|
-
upgrade
|
81
|
+
start = 'HTTP/1.1 101 Web Socket Protocol Handshake'
|
82
|
+
headers = [start, @headers.to_s, '']
|
83
|
+
headers.join("\r\n")
|
84
84
|
end
|
85
85
|
|
86
86
|
def parse_leading_byte(data)
|
@@ -9,6 +9,12 @@ module WebSocket
|
|
9
9
|
input = @socket.env['rack.input']
|
10
10
|
@stage = -1
|
11
11
|
@body = input ? input.read.bytes.to_a : []
|
12
|
+
|
13
|
+
@headers.clear
|
14
|
+
@headers['Upgrade'] = 'WebSocket'
|
15
|
+
@headers['Connection'] = 'Upgrade'
|
16
|
+
@headers['Sec-WebSocket-Origin'] = @socket.env['HTTP_ORIGIN']
|
17
|
+
@headers['Sec-WebSocket-Location'] = @socket.url
|
12
18
|
end
|
13
19
|
|
14
20
|
def version
|
@@ -32,14 +38,9 @@ module WebSocket
|
|
32
38
|
private
|
33
39
|
|
34
40
|
def handshake_response
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
upgrade << "Sec-WebSocket-Origin: #{@socket.env['HTTP_ORIGIN']}\r\n"
|
39
|
-
upgrade << "Sec-WebSocket-Location: #{@socket.url}\r\n"
|
40
|
-
upgrade << @headers.to_s
|
41
|
-
upgrade << "\r\n"
|
42
|
-
upgrade
|
41
|
+
start = 'HTTP/1.1 101 WebSocket Protocol Handshake'
|
42
|
+
headers = [start, @headers.to_s, '']
|
43
|
+
headers.join("\r\n")
|
43
44
|
end
|
44
45
|
|
45
46
|
def handshake_signature
|
@@ -6,13 +6,17 @@ module WebSocket
|
|
6
6
|
|
7
7
|
def initialize(received = {})
|
8
8
|
@raw = received
|
9
|
-
|
10
|
-
@lines = []
|
9
|
+
clear
|
11
10
|
|
12
11
|
@received = {}
|
13
12
|
@raw.each { |k,v| @received[HTTP.normalize_header(k)] = v }
|
14
13
|
end
|
15
14
|
|
15
|
+
def clear
|
16
|
+
@sent = Set.new
|
17
|
+
@lines = []
|
18
|
+
end
|
19
|
+
|
16
20
|
def [](name)
|
17
21
|
@received[HTTP.normalize_header(name)]
|
18
22
|
end
|
@@ -62,9 +62,17 @@ module WebSocket
|
|
62
62
|
|
63
63
|
return unless @socket.respond_to?(:env)
|
64
64
|
|
65
|
+
sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
|
66
|
+
protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
|
67
|
+
|
68
|
+
@headers['Upgrade'] = 'websocket'
|
69
|
+
@headers['Connection'] = 'Upgrade'
|
70
|
+
@headers['Sec-WebSocket-Accept'] = Hybi.generate_accept(sec_key)
|
71
|
+
|
65
72
|
if protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
|
66
73
|
protos = protos.split(/\s*,\s*/) if String === protos
|
67
74
|
@protocol = protos.find { |p| @protocols.include?(p) }
|
75
|
+
@headers['Sec-WebSocket-Protocol'] = @protocol if @protocol
|
68
76
|
end
|
69
77
|
end
|
70
78
|
|
@@ -197,21 +205,9 @@ module WebSocket
|
|
197
205
|
private
|
198
206
|
|
199
207
|
def handshake_response
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
headers = [
|
204
|
-
"HTTP/1.1 101 Switching Protocols",
|
205
|
-
"Upgrade: websocket",
|
206
|
-
"Connection: Upgrade",
|
207
|
-
"Sec-WebSocket-Accept: #{Hybi.generate_accept(sec_key)}"
|
208
|
-
]
|
209
|
-
|
210
|
-
if @protocol
|
211
|
-
headers << "Sec-WebSocket-Protocol: #{@protocol}"
|
212
|
-
end
|
213
|
-
|
214
|
-
(headers + [@headers.to_s, '']).join("\r\n")
|
208
|
+
start = 'HTTP/1.1 101 Switching Protocols'
|
209
|
+
headers = [start, @headers.to_s, '']
|
210
|
+
headers.join("\r\n")
|
215
211
|
end
|
216
212
|
|
217
213
|
def shutdown(code, reason)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module WebSocket
|
2
|
+
class Driver
|
3
|
+
|
4
|
+
class Proxy
|
5
|
+
include EventEmitter
|
6
|
+
|
7
|
+
PORTS = {'ws' => 80, 'wss' => 443}
|
8
|
+
|
9
|
+
attr_reader :status, :headers
|
10
|
+
|
11
|
+
def initialize(client, origin, options)
|
12
|
+
super()
|
13
|
+
|
14
|
+
@client = client
|
15
|
+
@http = HTTP::Response.new
|
16
|
+
@socket = client.instance_variable_get(:@socket)
|
17
|
+
@origin = URI.parse(@socket.url)
|
18
|
+
@url = URI.parse(origin)
|
19
|
+
@options = options
|
20
|
+
@state = 0
|
21
|
+
|
22
|
+
@headers = Headers.new
|
23
|
+
@headers['Host'] = @origin.host + (@origin.port ? ":#{@origin.port}" : '')
|
24
|
+
@headers['Connection'] = 'keep-alive'
|
25
|
+
@headers['Proxy-Connection'] = 'keep-alive'
|
26
|
+
|
27
|
+
if @url.user
|
28
|
+
auth = Base64.encode64([@url.user, @url.password] * ':').gsub(/\n/, '')
|
29
|
+
@headers['Proxy-Authorization'] = 'Basic ' + auth
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_header(name, value)
|
34
|
+
return false unless @state == 0
|
35
|
+
@headers[name] = value
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
def start
|
40
|
+
return false unless @state == 0
|
41
|
+
@state = 1
|
42
|
+
|
43
|
+
port = @origin.port || PORTS[@origin.scheme]
|
44
|
+
start = "CONNECT #{@origin.host}:#{port} HTTP/1.1"
|
45
|
+
headers = [start, @headers.to_s, '']
|
46
|
+
|
47
|
+
@socket.write(headers.join("\r\n"))
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse(buffer)
|
52
|
+
@http.parse(buffer)
|
53
|
+
return unless @http.complete?
|
54
|
+
|
55
|
+
@status = @http.code
|
56
|
+
@headers = Headers.new(@http.headers)
|
57
|
+
|
58
|
+
if @status == 200
|
59
|
+
emit(:connect)
|
60
|
+
else
|
61
|
+
message = "Can't establish a connection to the server at #{@socket.url}"
|
62
|
+
emit(:error, ProtocolError.new(message))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
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.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Coglan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: eventmachine
|
@@ -74,6 +74,7 @@ files:
|
|
74
74
|
- lib/websocket/driver/headers.rb
|
75
75
|
- lib/websocket/driver/hybi.rb
|
76
76
|
- lib/websocket/driver/hybi/stream_reader.rb
|
77
|
+
- lib/websocket/driver/proxy.rb
|
77
78
|
- lib/websocket/driver/server.rb
|
78
79
|
- lib/websocket/driver/utf8_match.rb
|
79
80
|
- lib/websocket/http.rb
|