em-websocket-server 0.13 → 0.15

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.
@@ -9,48 +9,108 @@
9
9
  ##Dependencies
10
10
  - eventmachine http://github.com/eventmachine/eventmachine
11
11
 
12
- ##Docs
12
+ ##Explain
13
+ To leverage em-websocket-server, you simply need to extend EM::WebSocket::Server
14
+ and register the server with eventmachine. When a client connects, EventMachine will
15
+ create a new instance of your class, and allow your application specific code to be
16
+ executed in the context of said instance.
17
+
18
+ ##Methods to override:
19
+
20
+ #called on exception
21
+ on_error error
22
+
23
+ #called when a client sends a message
24
+ on_receive msg
25
+
26
+ #called when a client connects
27
+ on_connect
28
+
29
+ #called when a client is disconnected
30
+ on_disconnect
31
+
32
+ ##Other useful methods
33
+
34
+ #send a message
35
+ send_message msg
36
+
37
+ #close the connection
38
+ unbind
39
+
40
+ ##Macros
41
+ macros are used to configure your application server.
42
+
43
+ class MySweetHandler < EM::WebSocket::Server
44
+
45
+ #secure incoming connections
46
+ secure
47
+
48
+ #secure incoming connections, with given key/cert
49
+ secure {
50
+ :private_key_file => "/path/to/private/key",
51
+ :cert_chain_file => "/path/to/ssl/certificate"
52
+ }
53
+
54
+ #provide a flash socket policy
55
+ flash_policy "/usr/local/policies/domain.com/crossdomain.xml"
56
+
57
+ end
13
58
 
14
- Not yet... coming soon
15
59
 
16
60
  ##Quick Example
17
61
 
18
- require 'rubygems'
19
- require 'em-websocket-server'
20
- require 'json'
62
+ require "rubygems"
63
+ require "em-websocket-server"
21
64
 
22
- #create a channel for pub sub
23
- $chatroom = EM::Channel.new
24
-
25
- class ChatServer < WebSocket::Server
26
-
27
- #subscribe to the channel on client connect
28
- def on_connect
29
- @sid = $chatroom.subscribe do |msg|
30
- send_message msg
31
- end
32
- end
33
-
34
- #unsubscribe on client disconnect
35
- def on_disconnect
36
- $chatroom.unsubscribe(@sid)
37
- end
38
-
39
- #publish the message to the channel on
40
- #client message received
41
- def on_receive msg
42
- $chatroom.push msg
43
- end
44
-
45
- end
46
-
47
- #start the event machine on port 8000 and have
48
- #it instantiate ChatServer objects for each
49
- #client connection
50
- EM.run do
51
- EM.start_server "0.0.0.0", 8000, ChatServer
52
- end
65
+ class EchoServer < EM::WebSocket::Server
66
+
67
+ def on_connect
68
+ EM::WebSocket::Log.debug "Connected"
69
+ end
70
+
71
+ def on_receive msg
72
+ send_message msg
73
+ end
74
+
75
+ end
76
+
77
+ EM.run do
78
+ EM.start_server "0.0.0.0", 8000, EchoServer
79
+ end
80
+
81
+ ##SSL
82
+
83
+ class SecureEchoServer < EM::WebSocket::Server
84
+
85
+ #provide cert and key
86
+ secure {
87
+ :private_key_file => "/path/to/private/key",
88
+ :cert_chain_file => "/path/to/ssl/certificate"
89
+ }
90
+
91
+ ...
92
+
93
+ end
94
+
95
+ EM.run do
96
+ EM.start_server "0.0.0.0", 443, SecureEchoServer
97
+ end
98
+
99
+ ##Custom Flash Policy
100
+
101
+ class FlashyEchoServer < EM::WebSocket::Server
102
+ flash_policy "/usr/local/policies/domain.com/crossdomain.xml"
103
+ end
104
+
105
+ EM.run do
106
+ EM.start_server "0.0.0.0", 8000, FlashyEchoServer
107
+ end
108
+
109
+ ##Todo
110
+ * Testing
111
+ * Better inline documentation
112
+ * Web client library with flash based fallback
53
113
 
54
114
  ##Thanks
55
- sidonath
56
- TheBreeze
115
+ * sidonath
116
+ * TheBreeze
@@ -1,32 +1,22 @@
1
1
  spec = Gem::Specification.new do |s|
2
- s.name = 'em-websocket-server'
3
- s.version = '0.13'
4
- s.date = '2009-12-14'
5
- s.summary = 'An evented ruby websocket server built on top of EventMachine'
6
- s.email = "dan.simpson@gmail.com"
7
- s.homepage = "http://github.com/dansimpson/em-websocket-server"
8
- s.description = "An evented ruby websocket server built on top of EventMachine"
9
- s.has_rdoc = false
10
-
2
+ s.name = 'em-websocket-server'
3
+ s.version = '0.15'
4
+ s.date = '2010-08-30'
5
+ s.summary = 'An evented ruby websocket server built on top of EventMachine'
6
+ s.email = "dan.simpson@gmail.com"
7
+ s.homepage = "http://github.com/dansimpson/em-websocket-server"
8
+ s.description = "An evented ruby websocket server built on top of EventMachine"
11
9
 
12
- s.authors = ["Dan Simpson"]
13
- s.add_dependency('eventmachine', '>= 0.12.4')
10
+ s.authors = ["Dan Simpson"]
11
+ s.add_dependency('eventmachine', '>= 0.12.10')
14
12
 
15
13
 
16
- s.files = [
17
- "README.markdown",
18
- "em-websocket-server.gemspec",
19
- "examples/chat.rb",
20
- "examples/chat.html",
21
- "examples/timesync.html",
22
- "examples/timesync.rb",
23
- "examples/tictactoe/tictactoe.html",
24
- "examples/tictactoe/tictactoe.js",
25
- "examples/tictactoe/tictactoe.rb",
26
- "lib/web_socket.rb",
27
- "lib/web_socket/client.rb",
28
- "lib/web_socket/server.rb",
29
- "lib/web_socket/frame.rb",
30
- "lib/web_socket/util.rb"
31
- ]
14
+ s.files = [
15
+ "README.markdown",
16
+ "em-websocket-server.gemspec",
17
+ "lib/em-websocket-server.rb",
18
+ "lib/em-websocket-server/server.rb",
19
+ "lib/em-websocket-server/request.rb",
20
+ "lib/em-websocket-server/protocol/version76.rb"
21
+ ]
32
22
  end
@@ -0,0 +1,15 @@
1
+ require "rubygems"
2
+ require "eventmachine"
3
+ require "logger"
4
+ require "digest"
5
+
6
+ module EM
7
+ module WebSocket
8
+ Version = 0.50
9
+ Log = Logger.new STDOUT
10
+ end
11
+ end
12
+
13
+ require "em-websocket-server/server.rb"
14
+ require "em-websocket-server/request.rb"
15
+ require "em-websocket-server/protocol/version76.rb"
@@ -0,0 +1,49 @@
1
+ module EM
2
+ module WebSocket
3
+ module Protocol
4
+ module Version76
5
+
6
+ # generate protocol 76 compatible response headers
7
+ def response
8
+ response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
9
+ response << "Upgrade: WebSocket\r\n"
10
+ response << "Connection: Upgrade\r\n"
11
+ response << "Sec-WebSocket-Origin: #{origin}\r\n"
12
+ response << "Sec-WebSocket-Location: #{scheme}://#{host}#{path}\r\n"
13
+
14
+ if protocol
15
+ response << "Sec-WebSocket-Protocol: #{protocol}\r\n"
16
+ end
17
+
18
+ response << "\r\n"
19
+ response << Digest::MD5.digest(keyset)
20
+
21
+ response
22
+ end
23
+
24
+ protected
25
+
26
+ # generate a keyset from the 3 secure keys
27
+ def keyset
28
+ [:sec_websocket_key1,:sec_websocket_key2].collect { |k|
29
+ partify(@headers[k])
30
+ }.push(@headers[:sec_websocket_key3]).join
31
+ end
32
+
33
+ # decode a websocket key and create a token for
34
+ # use in the response
35
+ # +key+ the key value to decode
36
+ def partify key
37
+ nums = key.scan(/[0-9]/).join.to_i
38
+ spaces = key.scan(/ /).size
39
+
40
+ raise "Key Error: #{key} has no spaces" if spaces == 0
41
+ raise "Key Error: #{key} nums #{nums} is not an integral multiple of #{spaces}" if (nums % spaces) != 0
42
+
43
+ [nums / spaces].pack("N*")
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,109 @@
1
+ module EM
2
+ module WebSocket
3
+
4
+ class Request
5
+
6
+ Path = /^GET (\/[^\s]*) HTTP\/[\d|\.]+$/.freeze
7
+ Header = /^([^:]+):\s*(.+)$/.freeze
8
+
9
+ # create a new request object
10
+ # +data+ the string value of the HTTP headers for parsing
11
+ def initialize data=nil
12
+ parse(data) if data
13
+ end
14
+
15
+ # parse the HTTP headers from an incoming
16
+ # request into actionable information
17
+ # +data+ The header data as a string
18
+ def parse data
19
+
20
+ lines = data.split("\r\n")
21
+
22
+ if Path =~ lines.shift
23
+ @headers = {
24
+ :path => $1
25
+ }
26
+ else
27
+ raise "Invalid request: #{data}"
28
+ end
29
+
30
+ #breaks when we get to the empty line
31
+ while((line = lines.shift) && !line.empty?)
32
+ if Header =~ line
33
+ self[$1] = $2
34
+ else
35
+ raise "Invalid header: #{line}"
36
+ end
37
+ end
38
+
39
+ if is_secure?
40
+ if lines.empty?
41
+ raise "Key 3 is required for protocol version 76"
42
+ end
43
+ @headers[:sec_websocket_key3] = lines.last
44
+ extend Protocol::Version76
45
+ end
46
+
47
+ end
48
+
49
+ # generate a response for this request
50
+ # may be overridden by other protocol modules
51
+ def response
52
+ response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
53
+ response << "Upgrade: WebSocket\r\n"
54
+ response << "Connection: Upgrade\r\n"
55
+ response << "WebSocket-Origin: #{origin}\r\n"
56
+ response << "WebSocket-Location: #{scheme}://#{host}#{path}\r\n"
57
+ response << "\r\n"
58
+ response
59
+ end
60
+
61
+
62
+ # returns a header value
63
+ # +key+ the symbol value of the header field
64
+ def [](key)
65
+ @headers[key.to_sym]
66
+ end
67
+
68
+ # sets the header value
69
+ # +key+ the header field name
70
+ # +val+ the value of said field
71
+ def []=(key,val)
72
+ @headers[key.downcase.gsub(/-/,"_").to_sym] = val
73
+ end
74
+
75
+ # is the websocket connection supplying secure
76
+ # web socket fields in the header
77
+ def is_secure?
78
+ @headers.has_key? :sec_websocket_key1
79
+ end
80
+
81
+ # the websocket protocol that is used
82
+ def protocol
83
+ @headers[:sec_websocket_protocol]
84
+ end
85
+
86
+ # the origin domain of the connected client
87
+ def origin
88
+ @headers[:origin]
89
+ end
90
+
91
+ # the host which the client connected to
92
+ def host
93
+ @headers[:host]
94
+ end
95
+
96
+ # the request path portion of the request URI
97
+ def path
98
+ @headers[:path]
99
+ end
100
+
101
+ # the websocket scheme, either ws, or wss for
102
+ # a TLS secured connection
103
+ def scheme
104
+ "ws"
105
+ end
106
+
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,157 @@
1
+ module EM
2
+ module WebSocket
3
+ class Server < EM::Connection
4
+
5
+ Pack = /\000([^\377]*)\377/.freeze
6
+ Frame = /^[\x00]|[\xff]$/.freeze
7
+
8
+ attr_accessor :connected, :request
9
+
10
+ def initialize *args
11
+ super
12
+ @request = nil
13
+ @buffer = ""
14
+ @connected = false
15
+ end
16
+
17
+ # do not override!
18
+ def post_init
19
+ start_tls(self.class.tls_opts) if self.class.secure?
20
+ end
21
+
22
+ # close the connection
23
+ def unbind
24
+ on_disconnect
25
+ end
26
+
27
+ # send a message to the websocket client
28
+ # +msg+ the message the client should receive
29
+ def send_message msg
30
+ send_data "\x00#{msg}\xff"
31
+ end
32
+
33
+ protected
34
+
35
+ # override
36
+ def on_receive msg
37
+ end
38
+
39
+ # override this method
40
+ def on_connect
41
+ end
42
+
43
+ # override this method
44
+ def on_disconnect
45
+ end
46
+
47
+ # override this method
48
+ def on_error ex
49
+ Log.fatal ex
50
+ end
51
+
52
+ private
53
+
54
+ # called when the web socket connection
55
+ # is fully ready
56
+ def on_ready
57
+ @connected = true
58
+ on_connect
59
+ end
60
+
61
+ # when the connection receives data from the client
62
+ # we either handshake or handle the message at
63
+ # the app layer
64
+ def receive_data data
65
+
66
+ if @connected
67
+ #parse each frame and dispatch
68
+ while msg = data.slice!(Pack)
69
+ on_receive msg.gsub(Frame, "")
70
+ end
71
+ else
72
+ if data =~ /</
73
+ Log.debug "Sending flash policy #{self.class.policy_content}"
74
+ send_data self.class.policy
75
+ close_connection_after_writing
76
+ else
77
+ handshake data
78
+ end
79
+ end
80
+ end
81
+
82
+ # parse the request, validate the origin and path
83
+ # and respond with appropiate headers for a
84
+ # healthy relationship with the client
85
+ def handshake data
86
+ begin
87
+ @request = Request.new(data)
88
+ send_data @request.response
89
+ on_ready
90
+ rescue Exception => ex
91
+ on_error ex
92
+ close_connection
93
+ end
94
+ end
95
+
96
+ class << self
97
+
98
+ # set the flash policy this is sent to flash clients
99
+ # +policy+ either a string containing XML or a
100
+ # path to a XML policy file
101
+ def flash_policy policy
102
+ if policy =~ /\.xml$/
103
+ @policy = File.read(policy)
104
+ else
105
+ @policy = policy
106
+ end
107
+ end
108
+
109
+ # get the flash policy content
110
+ def policy_content
111
+ @policy || default_policy
112
+ end
113
+
114
+ # secure any instance of this connection
115
+ # with TLS
116
+ # +opts+ the EM specific options hash for starting
117
+ # tls on the connection. Important options:
118
+ # :private_key_file
119
+ # :cert_chain_file
120
+ def secure opts={}
121
+ @tls_opts = opts
122
+ end
123
+
124
+ # is the connection secured with TLS?
125
+ def secure?
126
+ @tls_opts != nil
127
+ end
128
+
129
+ # the TLS options used for the secured connection
130
+ def tls_opts
131
+ @tls_opts
132
+ end
133
+
134
+ # add a domain to the list of allowable domains
135
+ # +domain+ the domain that is allowed eg: cnet.com
136
+ def accept_domain domain
137
+ @accepted = [] unless @accepted
138
+ @accepted << domain
139
+ end
140
+
141
+ # the set of domains that the server should
142
+ # accept the connection from. If the list is
143
+ # empty, the server will accept all connections
144
+ def accept_domains
145
+ @accepted || []
146
+ end
147
+
148
+ # the default flash policy content, which accepts
149
+ # from all domains to all ports (maybe not a good thing)
150
+ def default_policy
151
+ "<?xml version=\"1.0\"?><cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\"/></cross-domain-policy>"
152
+ end
153
+
154
+ end
155
+ end
156
+ end
157
+ end