websocket-driver 0.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ ### 0.0.0 / 2013-04-22
2
+
3
+ * First release
4
+ * Proof of concept for people to try out
5
+ * Might be unstable
6
+
data/README.md ADDED
@@ -0,0 +1,266 @@
1
+ # websocket-driver [![Build Status](https://travis-ci.org/faye/websocket-driver-ruby.png)](https://travis-ci.org/faye/websocket-driver-ruby)
2
+
3
+ This module provides a complete implementation of the WebSocket protocols that
4
+ can be hooked up to any TCP library. It aims to simplify things by decoupling
5
+ the protocol details from the I/O layer, such that users only need to implement
6
+ code to stream data in and out of it without needing to know anything about how
7
+ the protocol actually works. Think of it as a complete WebSocket system with
8
+ pluggable I/O.
9
+
10
+ Due to this design, you get a lot of things for free. In particular, if you
11
+ hook this module up to some I/O object, it will do all of this for you:
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
+ * Buffer sent messages until the handshake process is finished
18
+ * Deal with proxies that defer delivery of the draft-76 handshake body
19
+ * Notify you when the socket is open and closed and when messages arrive
20
+ * Recombine fragmented messages
21
+ * Dispatch text, binary, ping and close frames
22
+ * Manage the socket-closing handshake process
23
+ * Automatically reply to ping frames with a matching pong
24
+ * Apply masking to messages sent by the client
25
+
26
+ This library was originally extracted from the [Faye](http://faye.jcoglan.com)
27
+ project but now aims to provide simple WebSocket support for any Ruby server or
28
+ I/O system.
29
+
30
+
31
+ ## Installation
32
+
33
+ ```
34
+ $ gem install websocket-driver
35
+ ```
36
+
37
+
38
+ ## Usage
39
+
40
+ To build either a server-side or client-side socket, the only requirement is
41
+ that you supply a `socket` object with these methods:
42
+
43
+ * `socket.url` - returns the full URL of the socket as a string.
44
+ * `socket.write(string)` - writes the given string to a TCP stream.
45
+
46
+ Server-side sockets require one additional method:
47
+
48
+ * `socket.env` - returns a Rack-style env hash that will contain some of the
49
+ following fields. Their values are strings containing the value of the named
50
+ header, unless stated otherwise.
51
+ * `HTTP_CONNECTION`
52
+ * `HTTP_HOST`
53
+ * `HTTP_ORIGIN`
54
+ * `HTTP_SEC_WEBSOCKET_KEY`
55
+ * `HTTP_SEC_WEBSOCKET_KEY1`
56
+ * `HTTP_SEC_WEBSOCKET_KEY2`
57
+ * `HTTP_SEC_WEBSOCKET_PROTOCOL`
58
+ * `HTTP_SEC_WEBSOCKET_VERSION`
59
+ * `HTTP_UPGRADE`
60
+ * `rack.input`, an `IO` object representing the request body
61
+ * `REQUEST_METHOD`, the request's HTTP verb
62
+
63
+
64
+ ### Server-side
65
+
66
+ To handle a server-side WebSocket connection, you need to check whether the
67
+ request is a WebSocket handshake, and if so create a protocol driver for it.
68
+ You must give the driver an object with the `env`, `url` and `write` methods.
69
+ A simple example might be:
70
+
71
+ ```ruby
72
+ require 'websocket/driver'
73
+ require 'eventmachine'
74
+
75
+ class WS
76
+ attr_reader :env, :url
77
+
78
+ def initialize(env)
79
+ @env = env
80
+
81
+ secure = Rack::Request.new(env).ssl?
82
+ scheme = secure ? 'wss:' : 'ws:'
83
+ @url = scheme + '//' + env['HTTP_HOST'] + env['REQUEST_URI']
84
+
85
+ @driver = WebSocket::Driver.rack(self)
86
+
87
+ env['rack.hijack'].call
88
+ @io = env['rack.hijack_io']
89
+
90
+ EM.attach(@io, Reader) { |conn| conn.driver = @driver }
91
+
92
+ @driver.start
93
+ end
94
+
95
+ def write(string)
96
+ @io.write(string)
97
+ end
98
+
99
+ module Reader
100
+ attr_writer :driver
101
+
102
+ def receive_data(string)
103
+ @driver.parse(string)
104
+ end
105
+ end
106
+ end
107
+ ```
108
+
109
+ To explain what's going on here: the `WS` class implements the `env`, `url` and
110
+ `write(string)` methods as required. When instantiated with a Rack environment,
111
+ it stores the environment and infers the complete URL from it. Having set up
112
+ the `env` and `url`, it asks `WebSocket::Driver` for a server-side driver for
113
+ the socket. Then it uses the Rack hijack API to gain access to the TCP stream,
114
+ and uses EventMachine to stream in incoming data from the client, handing
115
+ incoming data off to the driver for parsing. Finally, we tell the driver to
116
+ `start`, which will begin sending the handshake response. This will invoke the
117
+ `WS#write` method, which will send the response out over the TCP socket.
118
+
119
+ Having defined this class we could use it like this when handling a request:
120
+
121
+ ```ruby
122
+ if WebSocket::Driver.websocket?(env)
123
+ socket = WS.new(env)
124
+ end
125
+ ```
126
+
127
+ The driver API is described in full below.
128
+
129
+
130
+ ### Client-side
131
+
132
+ Similarly, to implement a WebSocket client you need an object with `url` and
133
+ `write` methods. Once you have one such object, you ask for a driver for it:
134
+
135
+ ```ruby
136
+ driver = WebSocket::Driver.client(socket)
137
+ ```
138
+
139
+ After this you use the driver API as described below to process incoming data
140
+ and send outgoing data.
141
+
142
+
143
+ ### Driver API
144
+
145
+ Drivers are created using one of the following methods:
146
+
147
+ ```ruby
148
+ driver = WebSocket::Driver.rack(socket, options)
149
+ driver = WebSocket::Driver.client(socket, options)
150
+ ```
151
+
152
+ The `rack` method returns a driver chosen using the socket's `env`. The
153
+ `client` method always returns a driver for the RFC version of the protocol
154
+ with masking enabled on outgoing frames.
155
+
156
+ The `options` argument is optional, and is a hash. It may contain the following
157
+ keys:
158
+
159
+ * `:protocols` - an array of strings representing acceptable subprotocols for
160
+ use over the socket. The driver will negotiate one of these to use via the
161
+ `Sec-WebSocket-Protocol` header if supported by the other peer.
162
+
163
+ All drivers respond to the following API methods, but some of them are no-ops
164
+ depending on whether the client supports the behaviour.
165
+
166
+ Note that most of these methods are commands: if they produce data that should
167
+ be sent over the socket, they will give this to you by calling
168
+ `socket.write(string)`.
169
+
170
+ #### `driver.on('open') { |event| }`
171
+
172
+ Sets the callback block to execute when the socket becomes open.
173
+
174
+ #### `driver.on('message') { |event| }`
175
+
176
+ Sets the callback block to execute when a message is received. `event` will
177
+ have a `data` attribute containing either a string in the case of a text
178
+ message or an array of integers in the case of a binary message.
179
+
180
+ #### `driver.on('error') { |event| }`
181
+
182
+ Sets the callback to execute when a protocol error occurs due to the other peer
183
+ sending an invalid byte sequence. `event` will have a `message` attribute
184
+ describing the error.
185
+
186
+ #### `driver.on('close') { |event| }`
187
+
188
+ Sets the callback block to execute when the socket becomes closed. The `event`
189
+ object has `code` and `reason` attributes.
190
+
191
+ #### `driver.start`
192
+
193
+ Initiates the protocol by sending the handshake - either the response for a
194
+ server-side driver or the request for a client-side one. This should be the
195
+ first method you invoke. Returns `true` iff a handshake was sent.
196
+
197
+ #### `driver.parse(string)`
198
+
199
+ Takes a string and parses it, potentially resulting in message events being
200
+ emitted (see `on('message')` above) or in data being sent to `socket.write`.
201
+ You should send all data you receive via I/O to this method.
202
+
203
+ #### `driver.text(string)`
204
+
205
+ Sends a text message over the socket. If the socket handshake is not yet
206
+ complete, the message will be queued until it is. Returns `true` if the message
207
+ was sent or queued, and `false` if the socket can no longer send messages.
208
+
209
+ #### `driver.binary(array)`
210
+
211
+ Takes an array of byte-sized integers and sends them as a binary message. Will
212
+ queue and return `true` or `false` the same way as the `text` method. It will
213
+ also return `false` if the driver does not support binary messages.
214
+
215
+ #### `driver.ping(string = '', &callback)`
216
+
217
+ Sends a ping frame over the socket, queueing it if necessary. `string` and the
218
+ `callback` block are both optional. If a callback is given, it will be invoked
219
+ when the socket receives a pong frame whose content matches `string`. Returns
220
+ `false` if frames can no longer be sent, or if the driver does not support
221
+ ping/pong.
222
+
223
+ #### `driver.close`
224
+
225
+ Initiates the closing handshake if the socket is still open. For drivers with
226
+ no closing handshake, this will result in the immediate execution of the
227
+ `on('close')` callback. For drivers with a closing handshake, this sends a
228
+ closing frame and `emit('close')` will execute when a response is received or a
229
+ protocol error occurs.
230
+
231
+ #### `driver.version`
232
+
233
+ Returns the WebSocket version in use as a string. Will either be `hixie-75`,
234
+ `hixie-76` or `hybi-$version`.
235
+
236
+ #### `driver.protocol`
237
+
238
+ Returns a string containing the selected subprotocol, if any was agreed upon
239
+ using the `Sec-WebSocket-Protocol` mechanism. This value becomes available
240
+ after `emit('open')` has fired.
241
+
242
+
243
+ ## License
244
+
245
+ (The MIT License)
246
+
247
+ Copyright (c) 2010-2013 James Coglan
248
+
249
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
250
+ this software and associated documentation files (the 'Software'), to deal in
251
+ the Software without restriction, including without limitation the rights to
252
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
253
+ of the Software, and to permit persons to whom the Software is furnished to do
254
+ so, subject to the following conditions:
255
+
256
+ The above copyright notice and this permission notice shall be included in all
257
+ copies or substantial portions of the Software.
258
+
259
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
260
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
261
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
262
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
263
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
264
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
265
+ SOFTWARE.
266
+
@@ -0,0 +1,61 @@
1
+ package com.jcoglan.websocket;
2
+
3
+ import java.lang.Long;
4
+ import java.io.IOException;
5
+
6
+ import org.jruby.Ruby;
7
+ import org.jruby.RubyArray;
8
+ import org.jruby.RubyClass;
9
+ import org.jruby.RubyFixnum;
10
+ import org.jruby.RubyModule;
11
+ import org.jruby.RubyObject;
12
+ import org.jruby.anno.JRubyMethod;
13
+ import org.jruby.runtime.ObjectAllocator;
14
+ import org.jruby.runtime.ThreadContext;
15
+ import org.jruby.runtime.builtin.IRubyObject;
16
+ import org.jruby.runtime.load.BasicLibraryService;
17
+
18
+ public class WebsocketMaskService implements BasicLibraryService {
19
+ private Ruby runtime;
20
+
21
+ public boolean basicLoad(Ruby runtime) throws IOException {
22
+ this.runtime = runtime;
23
+ RubyModule websocket = runtime.defineModule("WebSocket");
24
+
25
+ RubyClass webSocketMask = websocket.defineClassUnder("Mask", runtime.getObject(), new ObjectAllocator() {
26
+ public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
27
+ return new WebsocketMask(runtime, rubyClass);
28
+ }
29
+ });
30
+
31
+ webSocketMask.defineAnnotatedMethods(WebsocketMask.class);
32
+ return true;
33
+ }
34
+
35
+ public class WebsocketMask extends RubyObject {
36
+ public WebsocketMask(final Ruby runtime, RubyClass rubyClass) {
37
+ super(runtime, rubyClass);
38
+ }
39
+
40
+ @JRubyMethod
41
+ public IRubyObject mask(ThreadContext context, IRubyObject payload, IRubyObject mask) {
42
+ int n = ((RubyArray)payload).getLength(), i;
43
+ long p, m;
44
+ RubyArray unmasked = RubyArray.newArray(runtime, n);
45
+
46
+ long[] maskArray = {
47
+ (Long)((RubyArray)mask).get(0),
48
+ (Long)((RubyArray)mask).get(1),
49
+ (Long)((RubyArray)mask).get(2),
50
+ (Long)((RubyArray)mask).get(3)
51
+ };
52
+
53
+ for (i = 0; i < n; i++) {
54
+ p = (Long)((RubyArray)payload).get(i);
55
+ m = maskArray[i % 4];
56
+ unmasked.set(i, p ^ m);
57
+ }
58
+ return unmasked;
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,5 @@
1
+ require 'mkmf'
2
+ extension_name = 'websocket_mask'
3
+ dir_config(extension_name)
4
+ create_makefile(extension_name)
5
+
@@ -0,0 +1,33 @@
1
+ #include <ruby.h>
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 Init_websocket_mask() {
10
+ WebSocket = rb_define_module("WebSocket");
11
+ WebSocketMask = rb_define_module_under(WebSocket, "Mask");
12
+ rb_define_singleton_method(WebSocketMask, "mask", method_websocket_mask, 2);
13
+ }
14
+
15
+ VALUE method_websocket_mask(VALUE self, VALUE payload, VALUE mask) {
16
+ int n = RARRAY_LEN(payload), i, p, m;
17
+ VALUE unmasked = rb_ary_new2(n);
18
+
19
+ int mask_array[] = {
20
+ NUM2INT(rb_ary_entry(mask, 0)),
21
+ NUM2INT(rb_ary_entry(mask, 1)),
22
+ NUM2INT(rb_ary_entry(mask, 2)),
23
+ NUM2INT(rb_ary_entry(mask, 3))
24
+ };
25
+
26
+ for (i = 0; i < n; i++) {
27
+ p = NUM2INT(rb_ary_entry(payload, i));
28
+ m = mask_array[i % 4];
29
+ rb_ary_store(unmasked, i, INT2NUM(p ^ m));
30
+ }
31
+ return unmasked;
32
+ }
33
+
@@ -0,0 +1,158 @@
1
+ # Protocol references:
2
+ #
3
+ # * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
4
+ # * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
5
+ # * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
6
+
7
+ require 'base64'
8
+ require 'digest/md5'
9
+ require 'digest/sha1'
10
+ require 'net/http'
11
+ require 'stringio'
12
+ require 'uri'
13
+
14
+ module WebSocket
15
+ class Driver
16
+
17
+ root = File.expand_path('../driver', __FILE__)
18
+ require root + '/../../websocket_mask'
19
+
20
+ if RUBY_PLATFORM =~ /java/
21
+ require 'jruby'
22
+ com.jcoglan.websocket.WebsocketMaskService.new.basicLoad(JRuby.runtime)
23
+ end
24
+
25
+ unless Mask.respond_to?(:mask)
26
+ def Mask.mask(payload, mask)
27
+ @instance ||= new
28
+ @instance.mask(payload, mask)
29
+ end
30
+ end
31
+
32
+ unless String.instance_methods.include?(:force_encoding)
33
+ require root + '/utf8_match'
34
+ end
35
+
36
+ STATES = [:connecting, :open, :closing, :closed]
37
+
38
+ class OpenEvent < Struct.new(nil) ; end
39
+ class MessageEvent < Struct.new(:data) ; end
40
+ class CloseEvent < Struct.new(:code, :reason) ; end
41
+
42
+ class ProtocolError < StandardError ; end
43
+
44
+ autoload :EventEmitter, root + '/event_emitter'
45
+ autoload :Draft75, root + '/draft75'
46
+ autoload :Draft76, root + '/draft76'
47
+ autoload :Hybi, root + '/hybi'
48
+ autoload :Client, root + '/client'
49
+
50
+ include EventEmitter
51
+ attr_reader :protocol, :ready_state
52
+
53
+ def initialize(socket, options = {})
54
+ super()
55
+
56
+ @socket = socket
57
+ @options = options
58
+ @queue = []
59
+ @ready_state = 0
60
+ end
61
+
62
+ def state
63
+ return nil unless @ready_state >= 0
64
+ STATES[@ready_state]
65
+ end
66
+
67
+ def start
68
+ return false unless @ready_state == 0
69
+ @socket.write(handshake_response)
70
+ open unless @stage == -1
71
+ true
72
+ end
73
+
74
+ def text(message)
75
+ frame(message)
76
+ end
77
+
78
+ def binary(message)
79
+ false
80
+ end
81
+
82
+ def ping(*args)
83
+ false
84
+ end
85
+
86
+ def close(reason = nil, code = nil)
87
+ return false unless @ready_state == 1
88
+ @ready_state = 3
89
+ emit(:close, CloseEvent.new(nil, nil))
90
+ true
91
+ end
92
+
93
+ private
94
+
95
+ def open
96
+ @ready_state = 1
97
+ @queue.each { |message| frame(*message) }
98
+ @queue = []
99
+ emit(:open, OpenEvent.new)
100
+ end
101
+
102
+ def queue(message)
103
+ @queue << message
104
+ true
105
+ end
106
+
107
+ def self.encode(string, validate_encoding = false)
108
+ if Array === string
109
+ string = utf8_string(string)
110
+ return nil if validate_encoding and !valid_utf8?(string)
111
+ end
112
+ utf8_string(string)
113
+ end
114
+
115
+ def self.client(socket, options = {})
116
+ Client.new(socket, options.merge(:masking => true))
117
+ end
118
+
119
+ def self.rack(socket, options = {})
120
+ env = socket.env
121
+ if env['HTTP_SEC_WEBSOCKET_VERSION']
122
+ Hybi.new(socket, options.merge(:require_masking => true))
123
+ elsif env['HTTP_SEC_WEBSOCKET_KEY1']
124
+ Draft76.new(socket, options)
125
+ else
126
+ Draft75.new(socket, options)
127
+ end
128
+ end
129
+
130
+ def self.utf8_string(string)
131
+ string = string.pack('C*') if Array === string
132
+ string.respond_to?(:force_encoding) ?
133
+ string.force_encoding('UTF-8') :
134
+ string
135
+ end
136
+
137
+ def self.valid_utf8?(byte_array)
138
+ string = utf8_string(byte_array)
139
+ if defined?(UTF8_MATCH)
140
+ UTF8_MATCH =~ string ? true : false
141
+ else
142
+ string.valid_encoding?
143
+ end
144
+ end
145
+
146
+ def self.websocket?(env)
147
+ return false unless env['REQUEST_METHOD'] == 'GET'
148
+ connection = env['HTTP_CONNECTION'] || ''
149
+ upgrade = env['HTTP_UPGRADE'] || ''
150
+
151
+ env['REQUEST_METHOD'] == 'GET' and
152
+ connection.downcase.split(/\s*,\s*/).include?('upgrade') and
153
+ upgrade.downcase == 'websocket'
154
+ end
155
+
156
+ end
157
+ end
158
+