socky-client 0.4.3 → 0.5.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+ pkg/
3
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ script: "bundle exec rake spec"
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - ree
6
+ - rbx
7
+ - jruby
data/CHANGELOG.md CHANGED
@@ -1,11 +1,24 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ ## 0.5.0.beta1 / 2011-08-01
5
+
6
+ Socky was rewritten from scratch. From this version it's Rack application and is based on
7
+ open protocol, and have a lot of new features. Some of them:
8
+
9
+ - Rack app - you can run both Socky and your web-application in the same process!
10
+ - New, standarized communication protocol(it will be easy to implement Socky in other languages)
11
+ - New user authentication process - much faster and more secure
12
+ - Allow users to dynamicly subscribe/unsubscribe from channels
13
+ - New events system - easier to learn and much more powerfull
14
+
15
+ And many more - please check [Socky website](http://socky.org) for more or check specific Socky elements at [Github](http://github.com/socky).
16
+
4
17
  ## 0.4.3 / 2010-10-30
5
18
 
6
19
  - new features:
7
20
  - new, simpler syntax
8
- - bugfixes
21
+ - bugfixes:
9
22
  - none
10
23
 
11
24
  ## 0.4.2 / 2010-10-29
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
data/README.md CHANGED
@@ -1,22 +1,40 @@
1
- Socky - client in Ruby
2
- ===========
1
+ # Socky - client in Ruby [![](http://travis-ci.org/socky/socky-client-ruby.png)](http://travis-ci.org/socky/socky-client-ruby)
3
2
 
4
- Socky is push server for Ruby based on WebSockets. It allows you to break border between your application and client browser by letting the server initialize a connection and push data to the client.
3
+ Also important information can be found on our [google group](http://groups.google.com/group/socky-users).
5
4
 
6
- ## Getting Started
5
+ ## Installation
7
6
 
8
- - [Install](http://github.com/socky/socky-client-ruby/wiki/install) the gem
9
- - Read up about its [Usage](http://github.com/socky/socky-client-ruby/wiki/usage) and [Configuration](http://github.com/socky/socky-client-ruby/wiki/configuration)
10
- - Try [Example Application](http://github.com/socky/socky-example) or [demo page](http://sockydemo.imanel.org)
11
- - Fork and Contribute your own modifications
12
- - See [sites using socky](http://github.com/socky/socky-server-ruby/wiki/sites)
13
- - Discuss on [google group](http://groups.google.com/group/socky-framework)
7
+ ``` bash
8
+ $ gem install socky-client --pre
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ First require Socky Client:
14
+
15
+ ``` ruby
16
+ require 'socky/client'
17
+ ```
18
+
19
+ Then createn new Client instance. Parameters required are full address of Socky Server(including app name) and secret of app.
20
+
21
+ ``` ruby
22
+ $socky_client = Socky::Client.new('http://ws.socky.org:3000/http/test_app', 'my_secret')
23
+ ```
24
+
25
+ This instance of Socky Client can trigger events for all users of server. To do so you can use one of methods:
26
+
27
+ ``` ruby
28
+ $socky_client.trigger!('my_event', :channel => 'my_channel', :data => 'my data') # Will raise on error
29
+ $socky_client.trigger('my_event', :channel => 'my_channel', :data => 'my data') # Will return false on error
30
+ $socky_client.trigger_async('my_event', :channel => 'my_channel', :data => 'my data') # Async method
31
+ ```
14
32
 
15
33
  ## License
16
34
 
17
35
  (The MIT License)
18
36
 
19
- Copyright (c) 2010 Bernard Potocki
37
+ Copyright (c) 2010 Bernard Potocki
20
38
 
21
39
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
22
40
 
data/Rakefile CHANGED
@@ -1,27 +1,11 @@
1
- require 'rake'
2
- require 'rake/clean'
3
- CLEAN.include %w(**/*.{log,rbc})
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
4
3
 
5
4
  require 'rspec/core/rake_task'
6
5
 
7
- task :default => :spec
8
-
9
- RSpec::Core::RakeTask.new(:spec) do |t|
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.rspec_opts = ["-c", "-f progress"]
8
+ t.pattern = 'spec/**/*_spec.rb'
10
9
  end
11
10
 
12
- begin
13
- require 'jeweler'
14
- Jeweler::Tasks.new do |gemspec|
15
- gemspec.name = "socky-client"
16
- gemspec.summary = "Socky is a WebSocket server and client for Ruby"
17
- gemspec.description = "Socky is a WebSocket server and client for Ruby"
18
- gemspec.email = "bernard.potocki@imanel.org"
19
- gemspec.homepage = "http://imanel.org/projects/socky"
20
- gemspec.authors = ["Bernard Potocki"]
21
- gemspec.add_dependency 'json'
22
- gemspec.add_development_dependency 'rspec', '~> 2.0'
23
- gemspec.files.exclude ".gitignore"
24
- end
25
- rescue LoadError
26
- puts "Jeweler not available. Install it with: gem install jeweler"
27
- end
11
+ task :default => :spec
@@ -0,0 +1,50 @@
1
+ require 'socky/authenticator'
2
+ require 'multi_json'
3
+ require 'crack/core_extensions' # Used for Hash#to_params
4
+
5
+ module Socky
6
+ class Client
7
+ class Request
8
+
9
+ attr_reader :client, :event, :channel, :data
10
+
11
+ def initialize(client, event, channel, data = nil)
12
+ @client = client
13
+ @event = event
14
+ @channel = channel
15
+ @data = MultiJson.encode(data)
16
+ end
17
+
18
+ def timestamp
19
+ @timestamp ||= Time.now.to_i
20
+ end
21
+
22
+ def body
23
+ content = {}
24
+ content['event'] = @event
25
+ content['channel'] = @channel
26
+ content['timestamp'] = timestamp
27
+ content['data'] = @data
28
+ content['auth'] = auth_string
29
+ content.to_params
30
+ end
31
+
32
+ private
33
+
34
+ def auth_string
35
+ Authenticator.authenticate({
36
+ :connection_id => timestamp,
37
+ :channel => @channel,
38
+ :event => @event,
39
+ :data => @data
40
+ }, {
41
+ :secret => @client.secret,
42
+ :method => :http
43
+ })['auth']
44
+ rescue => e
45
+ raise AuthenticationError, e.message
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ module Socky
2
+ class Client
3
+ VERSION = '0.5.0.beta1'
4
+ end
5
+ end
@@ -0,0 +1,181 @@
1
+ # Mostly copied from pusher gem - they did great job and probably there are no reason to reinvent wheel ;)
2
+ autoload 'Logger', 'logger'
3
+ require File.expand_path(File.dirname(__FILE__)) + '/client/request'
4
+
5
+ module Socky
6
+ class Client
7
+ # All Socky errors descend from this class so you can easily rescue Socky errors
8
+ #
9
+ # @example
10
+ # begin
11
+ # socky_client.trigger!('an_event', :channel => 'my_channel', :data => {:some => 'data'})
12
+ # rescue Socky::Client::Error => e
13
+ # # Do something on error
14
+ # end
15
+ class Error < RuntimeError; end
16
+ class ArgumentError < Error; end
17
+ class AuthenticationError < Error; end
18
+ class ConfigurationError < Error; end
19
+ class HTTPError < Error; attr_accessor :original_error; end
20
+
21
+ class << self
22
+ attr_writer :logger
23
+
24
+ # @private
25
+ def logger
26
+ @logger ||= begin
27
+ log = Logger.new($stdout)
28
+ log.level = Logger::INFO
29
+ log
30
+ end
31
+ end
32
+ end
33
+
34
+ attr_reader :uri, :secret
35
+ attr_writer :logger
36
+
37
+ # Create Socky::Client instance for later use.
38
+ # This is usually needed only once per application so it's good idea to put it in global variable
39
+ #
40
+ # @example
41
+ # $socky_client = Socky::Client.new('http://example.org/http/my_app', 'my_secret')
42
+ #
43
+ # @param uri [String] Full uri(including app name) to Socky server
44
+ # @param secret [String] Socky App secret
45
+ #
46
+ def initialize(uri, secret)
47
+ @uri = URI.parse(uri)
48
+ @secret = secret
49
+ end
50
+
51
+ # Trigger event
52
+ #
53
+ # @example
54
+ # begin
55
+ # $socky_client.trigger!('an_event', :channel => 'my_channe', :data => {:some => 'data'})
56
+ # rescue Socky::Client::Error => e
57
+ # # Do something on error
58
+ # end
59
+ #
60
+ # @param [String] event Event name to be triggered in javascript.
61
+ # @param [Hash] opts Special options for request
62
+ # @option opts [String] :channel Channel to which event will be sent
63
+ # @option opts [Object] :data Data for trigger - Objects other than strings will be converted to JSON
64
+ #
65
+ # @raise [Socky::Client::Error] on invalid Socky Server response - see the error message for more details
66
+ # @raise [Socky::Client::HTTPError] on any error raised inside Net::HTTP - the original error is available in the original_error attribute
67
+ #
68
+ def trigger!(event, opts = {})
69
+ require 'net/http' unless defined?(Net::HTTP)
70
+ require 'net/https' if (ssl? && !defined?(Net::HTTPS))
71
+
72
+ channel = opts[:channel] || opts['channel']
73
+ data = opts[:data] || opts['data']
74
+ raise ArgumentError, 'no channel provided' unless channel
75
+
76
+ request = Socky::Client::Request.new(self, event, channel, data)
77
+
78
+ @http_sync ||= begin
79
+ http = Net::HTTP.new(@uri.host, @uri.port)
80
+ http.use_ssl = true if ssl?
81
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if ssl?
82
+ http
83
+ end
84
+
85
+ begin
86
+ response = @http_sync.post(@uri.path,
87
+ request.body, { 'Content-Type'=> 'application/json' })
88
+ rescue Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED,
89
+ Timeout::Error, EOFError,
90
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
91
+ Net::ProtocolError => e
92
+ error = Socky::Client::HTTPError.new("#{e.message} (#{e.class})")
93
+ error.original_error = e
94
+ raise error
95
+ end
96
+
97
+ return handle_response(response.code.to_i, response.body.chomp)
98
+ end
99
+
100
+ # Trigger event, catching and logging any errors.
101
+ #
102
+ # @note CAUTION! No exceptions will be raised on failure
103
+ # @param (see #trigger!)
104
+ #
105
+ def trigger(event, opts = {})
106
+ trigger!(event, opts)
107
+ rescue Socky::Client::Error => e
108
+ Socky::Client.logger.error("#{e.message} (#{e.class})")
109
+ Socky::Client.logger.debug(e.backtrace.join("\n"))
110
+ false
111
+ end
112
+
113
+ # Trigger event asynchronously using EventMachine::HttpRequest
114
+ #
115
+ # @param (see #trigger!)
116
+ #
117
+ # @return [EM::DefaultDeferrable]
118
+ # Attach a callback to be notified of success (with no parameters).
119
+ # Attach an errback to be notified of failure (with an error parameter
120
+ # which includes the HTTP status code returned)
121
+ #
122
+ # @raise [LoadError] unless em-http-request gem is available
123
+ # @raise [Socky::Client::Error] unless the eventmachine reactor is running.
124
+ # You probably want to run your application inside a server such as thin.
125
+ #
126
+ def trigger_async(event, opts = {}, &block)
127
+ unless defined?(EventMachine) && EventMachine.reactor_running?
128
+ raise Error, "In order to use trigger_async you must be running inside an eventmachine loop"
129
+ end
130
+ require 'em-http' unless defined?(EventMachine::HttpRequest)
131
+
132
+ channel = opts[:channel] || opts['channel']
133
+ data = opts[:data] || opts['data']
134
+ raise ArgumentError, 'no channel provided' unless channel
135
+
136
+ request = Socky::Client::Request.new(self, event, channel, data)
137
+
138
+ deferrable = EM::DefaultDeferrable.new
139
+
140
+ http = EventMachine::HttpRequest.new(@uri).post({
141
+ :timeout => 5, :body => request.body, :head => {'Content-Type'=> 'application/json'}
142
+ })
143
+ http.callback {
144
+ begin
145
+ handle_response(http.response_header.status, http.response.chomp)
146
+ deferrable.succeed
147
+ rescue => e
148
+ deferrable.fail(e)
149
+ end
150
+ }
151
+ http.errback {
152
+ Socky::Client.logger.debug("Network error connecting to socky server: #{http.inspect}")
153
+ deferrable.fail(Error.new("Network error connecting to socky server"))
154
+ }
155
+
156
+ deferrable
157
+ end
158
+
159
+ private
160
+
161
+ def handle_response(status_code, body)
162
+ case status_code
163
+ when 202
164
+ return true
165
+ when 400
166
+ raise Error, "Bad request: #{body}"
167
+ when 401
168
+ raise AuthenticationError, body
169
+ when 404
170
+ raise Error, "Resource not found: app name is probably invalid"
171
+ else
172
+ raise Error, "Unknown error (status code #{status_code}): #{body}"
173
+ end
174
+ end
175
+
176
+ def ssl?
177
+ @uri.scheme == 'https'
178
+ end
179
+
180
+ end
181
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "socky/client/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "socky-client"
7
+ s.version = Socky::Client::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Bernard Potocki"]
10
+ s.email = ["bernard.potocki@imanel.org"]
11
+ s.homepage = "http://socky.org"
12
+ s.summary = %q{Socky is a WebSocket server and client for Ruby}
13
+ s.description = %q{Socky is a WebSocket server and client for Ruby}
14
+
15
+ s.add_dependency 'crack', "~> 0.1.0"
16
+ s.add_dependency 'multi_json', '~> 1.0'
17
+ s.add_dependency 'socky-authenticator', '~> 0.5.0.beta5'
18
+ s.add_development_dependency 'rspec', '~> 2.0'
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ end
data/spec/spec_helper.rb CHANGED
@@ -2,6 +2,4 @@ require 'rubygems'
2
2
  require 'rspec'
3
3
 
4
4
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
5
- require 'socky-client'
6
-
7
- Socky.logger.level = Logger::ERROR
5
+ require 'socky/client'
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Socky::Client do
4
+ end
metadata CHANGED
@@ -1,104 +1,105 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: socky-client
3
- version: !ruby/object:Gem::Version
4
- hash: 9
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 4
9
- - 3
10
- version: 0.4.3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0.beta1
5
+ prerelease: 6
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Bernard Potocki
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2010-10-30 00:00:00 +02:00
12
+ date: 2011-08-01 00:00:00.000000000 +02:00
19
13
  default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: json
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: crack
17
+ requirement: &70226763937280 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 0.1.0
23
+ type: :runtime
23
24
  prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
+ version_requirements: *70226763937280
26
+ - !ruby/object:Gem::Dependency
27
+ name: multi_json
28
+ requirement: &70226763936780 !ruby/object:Gem::Requirement
25
29
  none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
33
34
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: rspec
37
35
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
36
+ version_requirements: *70226763936780
37
+ - !ruby/object:Gem::Dependency
38
+ name: socky-authenticator
39
+ requirement: &70226763936320 !ruby/object:Gem::Requirement
39
40
  none: false
40
- requirements:
41
+ requirements:
41
42
  - - ~>
42
- - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 2
46
- - 0
47
- version: "2.0"
43
+ - !ruby/object:Gem::Version
44
+ version: 0.5.0.beta5
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *70226763936320
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec
50
+ requirement: &70226763935760 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '2.0'
48
56
  type: :development
49
- version_requirements: *id002
57
+ prerelease: false
58
+ version_requirements: *70226763935760
50
59
  description: Socky is a WebSocket server and client for Ruby
51
- email: bernard.potocki@imanel.org
60
+ email:
61
+ - bernard.potocki@imanel.org
52
62
  executables: []
53
-
54
63
  extensions: []
55
-
56
- extra_rdoc_files:
57
- - README.md
58
- files:
64
+ extra_rdoc_files: []
65
+ files:
66
+ - .gitignore
67
+ - .travis.yml
59
68
  - CHANGELOG.md
69
+ - Gemfile
60
70
  - README.md
61
71
  - Rakefile
62
- - VERSION
63
- - lib/socky-client.rb
64
- - lib/socky-client/websocket.rb
65
- - socky_hosts.yml
66
- - spec/socky-client_spec.rb
72
+ - lib/socky/client.rb
73
+ - lib/socky/client/request.rb
74
+ - lib/socky/client/version.rb
75
+ - socky-client.gemspec
67
76
  - spec/spec_helper.rb
77
+ - spec/unit/socky/client_spec.rb
68
78
  has_rdoc: true
69
- homepage: http://imanel.org/projects/socky
79
+ homepage: http://socky.org
70
80
  licenses: []
71
-
72
81
  post_install_message:
73
- rdoc_options:
74
- - --charset=UTF-8
75
- require_paths:
82
+ rdoc_options: []
83
+ require_paths:
76
84
  - lib
77
- required_ruby_version: !ruby/object:Gem::Requirement
85
+ required_ruby_version: !ruby/object:Gem::Requirement
78
86
  none: false
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- hash: 3
83
- segments:
84
- - 0
85
- version: "0"
86
- required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
92
  none: false
88
- requirements:
89
- - - ">="
90
- - !ruby/object:Gem::Version
91
- hash: 3
92
- segments:
93
- - 0
94
- version: "0"
93
+ requirements:
94
+ - - ! '>'
95
+ - !ruby/object:Gem::Version
96
+ version: 1.3.1
95
97
  requirements: []
96
-
97
98
  rubyforge_project:
98
- rubygems_version: 1.3.7
99
+ rubygems_version: 1.6.2
99
100
  signing_key:
100
101
  specification_version: 3
101
102
  summary: Socky is a WebSocket server and client for Ruby
102
- test_files:
103
- - spec/socky-client_spec.rb
103
+ test_files:
104
104
  - spec/spec_helper.rb
105
+ - spec/unit/socky/client_spec.rb
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.4.3
@@ -1,230 +0,0 @@
1
- # Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
2
- # Lincense: New BSD Lincense
3
- # Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
4
-
5
- require "socket"
6
- require "uri"
7
- require "digest/md5"
8
- require "openssl"
9
-
10
-
11
- class WebSocket
12
-
13
- class << self
14
-
15
- attr_accessor(:debug)
16
-
17
- end
18
-
19
- class Error < RuntimeError
20
-
21
- end
22
-
23
- def initialize(arg, params = {})
24
-
25
- uri = arg.is_a?(String) ? URI.parse(arg) : arg
26
-
27
- if uri.scheme == "ws"
28
- default_port = 80
29
- elsif uri.scheme = "wss"
30
- default_port = 443
31
- else
32
- raise(WebSocket::Error, "unsupported scheme: #{uri.scheme}")
33
- end
34
-
35
- @path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")
36
- host = uri.host + (uri.port == default_port ? "" : ":#{uri.port}")
37
- origin = params[:origin] || "http://#{uri.host}"
38
- key1 = generate_key()
39
- key2 = generate_key()
40
- key3 = generate_key3()
41
-
42
- socket = TCPSocket.new(uri.host, uri.port || default_port)
43
-
44
- if uri.scheme == "ws"
45
- @socket = socket
46
- else
47
- @socket = ssl_handshake(socket)
48
- end
49
-
50
- write(
51
- "GET #{@path} HTTP/1.1\r\n" +
52
- "Upgrade: WebSocket\r\n" +
53
- "Connection: Upgrade\r\n" +
54
- "Host: #{host}\r\n" +
55
- "Origin: #{origin}\r\n" +
56
- "Sec-WebSocket-Key1: #{key1}\r\n" +
57
- "Sec-WebSocket-Key2: #{key2}\r\n" +
58
- "\r\n" +
59
- "#{key3}")
60
- flush()
61
-
62
- line = gets().chomp()
63
- raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n)
64
- read_header()
65
- if @header["Sec-WebSocket-Origin"] != origin
66
- raise(WebSocket::Error,
67
- "origin doesn't match: '#{@header["WebSocket-Origin"]}' != '#{origin}'")
68
- end
69
- reply_digest = read(16)
70
- expected_digest = security_digest(key1, key2, key3)
71
- if reply_digest != expected_digest
72
- raise(WebSocket::Error,
73
- "security digest doesn't match: %p != %p" % [reply_digest, expected_digest])
74
- end
75
- @handshaked = true
76
-
77
- @closing_started = false
78
- end
79
-
80
- attr_reader(:header, :path)
81
-
82
- def send(data)
83
- if !@handshaked
84
- raise(WebSocket::Error, "call WebSocket\#handshake first")
85
- end
86
- data = force_encoding(data.dup(), "ASCII-8BIT")
87
- write("\x00#{data}\xff")
88
- flush()
89
- end
90
-
91
- def receive()
92
- if !@handshaked
93
- raise(WebSocket::Error, "call WebSocket\#handshake first")
94
- end
95
- packet = gets("\xff")
96
- return nil if !packet
97
- if packet =~ /\A\x00(.*)\xff\z/nm
98
- return force_encoding($1, "UTF-8")
99
- elsif packet == "\xff" && read(1) == "\x00" # closing
100
- if @server
101
- @socket.close()
102
- else
103
- close()
104
- end
105
- return nil
106
- else
107
- raise(WebSocket::Error, "input must be either '\\x00...\\xff' or '\\xff\\x00'")
108
- end
109
- end
110
-
111
- def tcp_socket
112
- return @socket
113
- end
114
-
115
- def host
116
- return @header["Host"]
117
- end
118
-
119
- def origin
120
- return @header["Origin"]
121
- end
122
-
123
- # Does closing handshake.
124
- def close()
125
- return if @closing_started
126
- write("\xff\x00")
127
- @socket.close() if !@server
128
- @closing_started = true
129
- end
130
-
131
- def close_socket()
132
- @socket.close()
133
- end
134
-
135
- private
136
-
137
- NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a()
138
-
139
- def read_header()
140
- @header = {}
141
- while line = gets()
142
- line = line.chomp()
143
- break if line.empty?
144
- if !(line =~ /\A(\S+): (.*)\z/n)
145
- raise(WebSocket::Error, "invalid request: #{line}")
146
- end
147
- @header[$1] = $2
148
- end
149
- if @header["Upgrade"] != "WebSocket"
150
- raise(WebSocket::Error, "invalid Upgrade: " + @header["Upgrade"])
151
- end
152
- if @header["Connection"] != "Upgrade"
153
- raise(WebSocket::Error, "invalid Connection: " + @header["Connection"])
154
- end
155
- end
156
-
157
- def gets(rs = $/)
158
- line = @socket.gets(rs)
159
- $stderr.printf("recv> %p\n", line) if WebSocket.debug
160
- return line
161
- end
162
-
163
- def read(num_bytes)
164
- str = @socket.read(num_bytes)
165
- $stderr.printf("recv> %p\n", str) if WebSocket.debug
166
- return str
167
- end
168
-
169
- def write(data)
170
- if WebSocket.debug
171
- data.scan(/\G(.*?(\n|\z))/n) do
172
- $stderr.printf("send> %p\n", $&) if !$&.empty?
173
- end
174
- end
175
- @socket.write(data)
176
- end
177
-
178
- def flush()
179
- @socket.flush()
180
- end
181
-
182
- def security_digest(key1, key2, key3)
183
- bytes1 = websocket_key_to_bytes(key1)
184
- bytes2 = websocket_key_to_bytes(key2)
185
- return Digest::MD5.digest(bytes1 + bytes2 + key3)
186
- end
187
-
188
- def generate_key()
189
- spaces = 1 + rand(12)
190
- max = 0xffffffff / spaces
191
- number = rand(max + 1)
192
- key = (number * spaces).to_s()
193
- (1 + rand(12)).times() do
194
- char = NOISE_CHARS[rand(NOISE_CHARS.size)]
195
- pos = rand(key.size + 1)
196
- key[pos...pos] = char
197
- end
198
- spaces.times() do
199
- pos = 1 + rand(key.size - 1)
200
- key[pos...pos] = " "
201
- end
202
- return key
203
- end
204
-
205
- def generate_key3()
206
- return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
207
- end
208
-
209
- def websocket_key_to_bytes(key)
210
- num = key.gsub(/[^\d]/n, "").to_i() / key.scan(/ /).size
211
- return [num].pack("N")
212
- end
213
-
214
- def force_encoding(str, encoding)
215
- if str.respond_to?(:force_encoding)
216
- return str.force_encoding(encoding)
217
- else
218
- return str
219
- end
220
- end
221
-
222
- def ssl_handshake(socket)
223
- ssl_context = OpenSSL::SSL::SSLContext.new()
224
- ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
225
- ssl_socket.sync_close = true
226
- ssl_socket.connect()
227
- return ssl_socket
228
- end
229
-
230
- end
data/lib/socky-client.rb DELETED
@@ -1,135 +0,0 @@
1
- require 'json'
2
- require 'logger'
3
- require 'yaml'
4
-
5
- require File.dirname(__FILE__) + '/socky-client/websocket'
6
-
7
- module Socky
8
-
9
- class << self
10
-
11
- attr_accessor :config_path, :logger
12
- def config_path
13
- @config_path ||= 'socky_hosts.yml'
14
- end
15
-
16
- def config
17
- @config ||= YAML.load_file(config_path).freeze
18
- end
19
-
20
- def send(*args)
21
- options = normalize_options(*args)
22
- send_message(options.delete(:data), options)
23
- end
24
-
25
- def show_connections
26
- send_query(:show_connections)
27
- end
28
-
29
- def hosts
30
- config[:hosts]
31
- end
32
-
33
- def logger
34
- @logger ||= Logger.new(STDOUT)
35
- end
36
-
37
- def deprecation_warning(msg)
38
- logger.warn "DEPRECATION WARNING: " + msg.to_s
39
- end
40
-
41
- private
42
-
43
- def normalize_options(data, options = {})
44
- case data
45
- when Hash
46
- options, data = data, nil
47
- when String, Symbol
48
- options[:data] = data
49
- else
50
- options.merge!(:data => data)
51
- end
52
-
53
- options[:data] = options[:data].to_s
54
- options
55
- end
56
-
57
- def send_message(data, opts = {})
58
- to = opts[:to] || {}
59
- except = opts[:except] || {}
60
-
61
- # Move to new syntax
62
- if opts[:to] || opts[:except]
63
- deprecation_warning "Using of :to and :except will be removed in next version - please move to new syntax."
64
- end
65
- to[:client] ||= opts[:client]
66
- to[:clients] ||= opts[:clients]
67
- to[:channel] ||= opts[:channel]
68
- to[:channels] ||= opts[:channels]
69
- # end of new syntax
70
-
71
- unless to.is_a?(Hash) && except.is_a?(Hash)
72
- raise "recipiend data should be in hash format"
73
- end
74
-
75
- to_clients = to[:client] || to[:clients]
76
- to_channels = to[:channel] || to[:channels]
77
- except_clients = except[:client] || except[:clients]
78
- except_channels = except[:channel] || except[:channels]
79
-
80
- # If clients or channels are non-nil but empty then there's no users to target message
81
- return if (to_clients.is_a?(Array) && to_clients.empty?) || (to_channels.is_a?(Array) && to_channels.empty?)
82
-
83
- hash = {
84
- :command => :broadcast,
85
- :body => data,
86
- :to => {
87
- :clients => to_clients,
88
- :channels => to_channels,
89
- },
90
- :except => {
91
- :clients => except_clients,
92
- :channels => except_channels,
93
- }
94
- }
95
-
96
- [:to, :except].each do |type|
97
- hash[type].reject! { |key,val| val.nil? || (type == :except && val.empty?)}
98
- hash.delete(type) if hash[type].empty?
99
- end
100
-
101
- send_data(hash)
102
- end
103
-
104
- def send_query(type)
105
- hash = {
106
- :command => :query,
107
- :type => type
108
- }
109
- send_data(hash, true)
110
- end
111
-
112
- def send_data(hash, response = false)
113
- res = []
114
- hosts.each do |address|
115
- begin
116
- scheme = (address[:secure] ? "wss" : "ws")
117
- @socket = WebSocket.new("#{scheme}://#{address[:host]}:#{address[:port]}/?admin=1&client_secret=#{address[:secret]}")
118
- @socket.send(hash.to_json)
119
- res << @socket.receive if response
120
- rescue
121
- puts "ERROR: Connection to server at '#{scheme}://#{address[:host]}:#{address[:port]}' failed"
122
- ensure
123
- @socket.close if @socket && !@socket.tcp_socket.closed?
124
- end
125
- end
126
- if response
127
- res.collect {|r| JSON.parse(r)["body"] }
128
- else
129
- true
130
- end
131
- end
132
-
133
- end
134
-
135
- end
data/socky_hosts.yml DELETED
@@ -1,5 +0,0 @@
1
- :hosts:
2
- - :host: 127.0.0.1
3
- :port: 8080
4
- :secret: my_secret_key
5
- :secure: false
@@ -1,194 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Socky do
4
- it "should have config in hash form" do
5
- Socky.config.should_not be_nil
6
- Socky.config.class.should eql(Hash)
7
- end
8
-
9
- it "should have host list taken from config" do
10
- Socky.hosts.should eql(Socky.config[:hosts])
11
- end
12
-
13
- context "#send" do
14
- before(:each) do
15
- Socky.stub!(:send_data)
16
- end
17
- it "should send broadcast with data" do
18
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
19
- Socky.send("test")
20
- end
21
- context "should normalize options" do
22
- it "when nil given" do
23
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => ""})
24
- Socky.send(nil)
25
- end
26
- it "when string given" do
27
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
28
- Socky.send("test")
29
- end
30
- it "when hash given" do
31
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
32
- Socky.send({:data => "test"})
33
- end
34
- it "when hash without body given" do
35
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => ""})
36
- Socky.send({})
37
- end
38
- end
39
- context "should handle recipient conditions for" do
40
- # New syntax
41
- it ":client" do
42
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :clients => "first" }})
43
- Socky.send("test", :client => "first")
44
- end
45
- it ":clients" do
46
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :clients => ["first","second"] }})
47
- Socky.send("test", :clients => ["first","second"])
48
- end
49
- it ":channel" do
50
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :channels => "first" }})
51
- Socky.send("test", :channel => "first")
52
- end
53
- it ":channels" do
54
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :channels => ["first","second"] }})
55
- Socky.send("test", :channels => ["first","second"])
56
- end
57
- it "combination" do
58
- Socky.should_receive(:send_data).with({
59
- :command => :broadcast,
60
- :body => "test",
61
- :to => {
62
- :clients => "allowed_user",
63
- :channels => "allowed_channel"
64
- }
65
- })
66
- Socky.send("test", :clients => "allowed_user", :channels => "allowed_channel")
67
- end
68
- # Old syntax
69
- it ":to => :client" do
70
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :clients => "first" }})
71
- Socky.send("test", :to => { :client => "first" })
72
- end
73
- it ":to => :clients" do
74
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :clients => ["first","second"] }})
75
- Socky.send("test", :to => { :clients => ["first","second"] })
76
- end
77
- it ":to => :channel" do
78
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :channels => "first" }})
79
- Socky.send("test", :to => { :channel => "first" })
80
- end
81
- it ":to => :channels" do
82
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :channels => ["first","second"] }})
83
- Socky.send("test", :to => { :channels => ["first","second"] })
84
- end
85
- it ":except => :client" do
86
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :except => { :clients => "first" }})
87
- Socky.send("test", :except => { :client => "first" })
88
- end
89
- it ":except => :clients" do
90
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :except => { :clients => ["first","second"] }})
91
- Socky.send("test", :except => { :clients => ["first","second"] })
92
- end
93
- it ":except => :channel" do
94
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :except => { :channels => "first" }})
95
- Socky.send("test", :except => { :channel => "first" })
96
- end
97
- it ":except => :channels" do
98
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :except => { :channels => ["first","second"] }})
99
- Socky.send("test", :except => { :channels => ["first","second"] })
100
- end
101
- it "combination" do
102
- Socky.should_receive(:send_data).with({
103
- :command => :broadcast,
104
- :body => "test",
105
- :to => {
106
- :clients => "allowed_user",
107
- :channels => "allowed_channel"
108
- },
109
- :except => {
110
- :clients => "disallowed_user",
111
- :channels => "disallowed_channel"
112
- }
113
- })
114
- Socky.send("test", :to => {
115
- :clients => "allowed_user",
116
- :channels => "allowed_channel"
117
- },
118
- :except => {
119
- :clients => "disallowed_user",
120
- :channels => "disallowed_channel"
121
- })
122
- end
123
- end
124
- context "should ignore nil value for" do
125
- # New syntax
126
- it ":clients" do
127
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
128
- Socky.send("test", :clients => nil)
129
- end
130
- it ":channels" do
131
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
132
- Socky.send("test", :channels => nil)
133
- end
134
-
135
- # Old syntax
136
- it ":to => :clients" do
137
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
138
- Socky.send("test", :to => { :clients => nil })
139
- end
140
- it ":to => :channels" do
141
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
142
- Socky.send("test", :to => { :channels => nil })
143
- end
144
- it ":except => :clients" do
145
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
146
- Socky.send("test", :except => { :clients => nil })
147
- end
148
- it ":except => :channels" do
149
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
150
- Socky.send("test", :except => { :channels => nil })
151
- end
152
- end
153
- context "should handle empty array for" do
154
- # New syntax
155
- it ":clients by not sending message" do
156
- Socky.should_not_receive(:send_data)
157
- Socky.send("test", :clients => [])
158
- end
159
- it ":channels by not sending message" do
160
- Socky.should_not_receive(:send_data)
161
- Socky.send("test", :channels => [])
162
- end
163
-
164
- # Old syntax
165
- it ":to => :clients by not sending message" do
166
- Socky.should_not_receive(:send_data)
167
- Socky.send("test", :to => { :clients => [] })
168
- end
169
- it ":to => :channels by not sending message" do
170
- Socky.should_not_receive(:send_data)
171
- Socky.send("test", :to => { :channels => [] })
172
- end
173
- it ":except => :clients by ignoring it" do
174
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
175
- Socky.send("test", :except => { :clients => [] })
176
- end
177
- it ":except => :channels by ignoring it" do
178
- Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
179
- Socky.send("test", :except => { :channels => [] })
180
- end
181
- end
182
- end
183
-
184
- context "#show_connections" do
185
- before(:each) do
186
- Socky.stub!(:send_data)
187
- end
188
- it "should send query :show_connections" do
189
- Socky.should_receive(:send_data).with({:command => :query, :type => :show_connections}, true)
190
- Socky.show_connections
191
- end
192
- end
193
-
194
- end