websocket-driver 0.3.5-java → 0.4.0-java

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
2
  SHA1:
3
- metadata.gz: 24db628b86446bcd2f11741520ffb9a9938a6995
4
- data.tar.gz: ebe19156acc768b1696c571cdab8e02fe07428c7
3
+ metadata.gz: 79be3fa7f1a4429fda1463f0ab0f809004b82593
4
+ data.tar.gz: eed2e51b0a21fade327b48c4dab184afb92cc7dd
5
5
  SHA512:
6
- metadata.gz: 0ad1e18fe373697fdee0d438d5522cb11ee6524de7a136c4c8a72c77b7ca1be002f1bb1141850f493a26913a8f3a1dbfabf9d466f434b857e1d0653c5168e568
7
- data.tar.gz: a4637d8e37031dbb633489dc9534764e670416898ba3d83b00ca90701d43581125f01f042a243cb3033afe093dd72107921ffcc4e569c665e1c087c59cdb8218
6
+ metadata.gz: 1f718f23af5483c2c30995c63b2d40a64d2077cc23fd1d0e34e551a9f7d06618883a7a45b5d884c4d334d1d7036263b93e52c8249bcee84146295fe8c8cd0506
7
+ data.tar.gz: f0486b76187ee8a9d750621aac8498ac4dd4b4ab7f2f8d9a19e7af9b6c35532cab2ebdfd25b17ed38721eccb4d918bc8adc7c0c19f2eb70d914d34f79cd2bec4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ### 0.4.0 / 2014-11-08
2
+
3
+ * Support connection via HTTP proxies using `CONNECT`
4
+
1
5
  ### 0.3.5 / 2014-10-04
2
6
 
3
7
  * Fix bug where the `Server` driver doesn't pass `ping` callbacks to its delegate
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
- int n = RARRAY_LEN(payload), i, p, m;
21
- VALUE unmasked = rb_ary_new2(n);
24
+ n = RARRAY_LEN(payload);
25
+ unmasked = rb_ary_new2(n);
22
26
 
23
- int mask_array[] = {
24
- NUM2INT(rb_ary_entry(mask, 0)),
25
- NUM2INT(rb_ary_entry(mask, 1)),
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));
@@ -51,6 +51,7 @@ module WebSocket
51
51
  autoload :EventEmitter, root + '/event_emitter'
52
52
  autoload :Headers, root + '/headers'
53
53
  autoload :Hybi, root + '/hybi'
54
+ autoload :Proxy, root + '/proxy'
54
55
  autoload :Server, root + '/server'
55
56
 
56
57
  include EventEmitter
@@ -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
- uri = URI.parse(@socket.url)
43
- host = uri.host + (uri.port ? ":#{uri.port}" : '')
44
- path = (uri.path == '') ? '/' : uri.path
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
- upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
77
- upgrade << "Upgrade: WebSocket\r\n"
78
- upgrade << "Connection: Upgrade\r\n"
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
- upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
36
- upgrade << "Upgrade: WebSocket\r\n"
37
- upgrade << "Connection: Upgrade\r\n"
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
- @sent = Set.new
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
- sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
201
- return '' unless String === sec_key
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
Binary file
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.3.5
4
+ version: 0.4.0
5
5
  platform: java
6
6
  authors:
7
7
  - James Coglan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-04 00:00:00.000000000 Z
11
+ date: 2014-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eventmachine
@@ -69,16 +69,17 @@ files:
69
69
  - lib/websocket/driver.rb
70
70
  - lib/websocket/http.rb
71
71
  - lib/websocket/http/request.rb
72
- - lib/websocket/http/headers.rb
73
72
  - lib/websocket/http/response.rb
74
- - lib/websocket/driver/utf8_match.rb
75
- - lib/websocket/driver/headers.rb
73
+ - lib/websocket/http/headers.rb
76
74
  - lib/websocket/driver/hybi.rb
77
- - lib/websocket/driver/server.rb
78
75
  - lib/websocket/driver/client.rb
76
+ - lib/websocket/driver/draft76.rb
77
+ - lib/websocket/driver/proxy.rb
78
+ - lib/websocket/driver/server.rb
79
79
  - lib/websocket/driver/draft75.rb
80
+ - lib/websocket/driver/headers.rb
81
+ - lib/websocket/driver/utf8_match.rb
80
82
  - lib/websocket/driver/event_emitter.rb
81
- - lib/websocket/driver/draft76.rb
82
83
  - lib/websocket/driver/hybi/stream_reader.rb
83
84
  - lib/websocket_mask.jar
84
85
  homepage: http://github.com/faye/websocket-driver-ruby