├── circle.yml
├── Gemfile
├── lib
├── websocket-client-simple
│ ├── version.rb
│ └── client.rb
└── websocket-client-simple.rb
├── Rakefile
├── .gitignore
├── test
├── test_helper.rb
├── echo_server.rb
├── test_connect_block.rb
└── test_websocket_client_simple.rb
├── sample
├── client.rb
├── webbrowser
│ ├── index.html
│ └── main.js
└── echo_server.rb
├── Gemfile.lock
├── LICENSE.txt
├── websocket-client-simple.gemspec
├── History.txt
└── README.md
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | ruby:
3 | version: "2.2.2"
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in websocket-client-simple.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/lib/websocket-client-simple/version.rb:
--------------------------------------------------------------------------------
1 | module WebSocket
2 | module Client
3 | module Simple
4 | VERSION = "0.3.0"
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rake/testtask"
3 |
4 | Rake::TestTask.new do |t|
5 | t.pattern = "test/test_*.rb"
6 | end
7 |
8 | task :default => :test
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | InstalledFiles
7 | _yardoc
8 | coverage
9 | doc/
10 | lib/bundler/man
11 | pkg
12 | rdoc
13 | spec/reports
14 | test/tmp
15 | test/version_tmp
16 | tmp
17 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler'
3 | require 'minitest/autorun'
4 | require 'websocket-client-simple'
5 | require 'eventmachine'
6 | require 'websocket-eventmachine-server'
7 | require_relative 'echo_server'
8 |
9 | $:.unshift File.expand_path '../lib', File.dirname(__FILE__)
10 |
--------------------------------------------------------------------------------
/lib/websocket-client-simple.rb:
--------------------------------------------------------------------------------
1 | require 'event_emitter'
2 | require 'websocket'
3 | require 'socket'
4 | require 'openssl'
5 | require 'uri'
6 |
7 | require 'websocket-client-simple/version'
8 | require 'websocket-client-simple/client'
9 |
10 | module WebSocket
11 | module Client
12 | module Simple
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/test/echo_server.rb:
--------------------------------------------------------------------------------
1 | module EchoServer
2 | def self.start
3 | WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => self.port) do |ws|
4 | @channel = EM::Channel.new
5 | ws.onopen do
6 | sid = @channel.subscribe do |mes|
7 | ws.send mes # echo to client
8 | end
9 | ws.onmessage do |msg|
10 | @channel.push msg
11 | end
12 | ws.onclose do
13 | @channel.unsubscribe sid
14 | end
15 | end
16 | end
17 | end
18 |
19 | def self.port
20 | (ENV['WS_PORT'] || 18080).to_i
21 | end
22 |
23 | def self.url
24 | "ws://localhost:#{self.port}"
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/sample/client.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | $:.unshift File.expand_path '../lib', File.dirname(__FILE__)
3 | require 'rubygems'
4 | require 'websocket-client-simple'
5 |
6 | puts "websocket-client-simple v#{WebSocket::Client::Simple::VERSION}"
7 |
8 | url = ARGV.shift || 'ws://localhost:8080'
9 |
10 | ws = WebSocket::Client::Simple.connect url
11 |
12 | ws.on :message do |msg|
13 | puts ">> #{msg.data}"
14 | end
15 |
16 | ws.on :open do
17 | puts "-- websocket open (#{ws.url})"
18 | end
19 |
20 | ws.on :close do |e|
21 | puts "-- websocket close (#{e.inspect})"
22 | exit 1
23 | end
24 |
25 | ws.on :error do |e|
26 | puts "-- error (#{e.inspect})"
27 | end
28 |
29 | loop do
30 | ws.send STDIN.gets.strip
31 | end
32 |
--------------------------------------------------------------------------------
/test/test_connect_block.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path 'test_helper', File.dirname(__FILE__)
2 |
3 | class TestWebSocketClientSimple < MiniTest::Test
4 |
5 | def test_onopen
6 |
7 | EM::run{
8 |
9 | EchoServer.start
10 |
11 | res = nil
12 |
13 | EM::add_timer 1 do
14 | WebSocket::Client::Simple.connect EchoServer.url do |client|
15 | client.on :open do
16 | client.send "hello world"
17 | end
18 |
19 | client.on :message do |msg|
20 | res = msg.to_s
21 | end
22 | end
23 | end
24 |
25 | EM::add_timer 2 do
26 | assert_equal res, "hello world"
27 | EM::stop_event_loop
28 | end
29 | }
30 |
31 | end
32 |
33 | end
34 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | websocket-client-simple (0.3.0)
5 | event_emitter
6 | websocket
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | event_emitter (0.2.5)
12 | eventmachine (1.0.9.1)
13 | minitest (5.8.4)
14 | rake (10.5.0)
15 | websocket (1.2.2)
16 | websocket-eventmachine-base (1.2.0)
17 | eventmachine (~> 1.0)
18 | websocket (~> 1.0)
19 | websocket-native (~> 1.0)
20 | websocket-eventmachine-server (1.0.1)
21 | websocket-eventmachine-base (~> 1.0)
22 | websocket-native (1.0.0)
23 |
24 | PLATFORMS
25 | ruby
26 |
27 | DEPENDENCIES
28 | bundler (~> 1.3)
29 | eventmachine
30 | minitest
31 | rake
32 | websocket-client-simple!
33 | websocket-eventmachine-server
34 |
35 | BUNDLED WITH
36 | 1.11.2
37 |
--------------------------------------------------------------------------------
/sample/webbrowser/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | websocket chat
6 |
7 |
8 |
9 |
10 | websocket chat
11 |
12 |
13 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/sample/webbrowser/main.js:
--------------------------------------------------------------------------------
1 | var ws = new WebSocket("ws://localhost:8080");
2 |
3 | ws.onmessage = function(e){
4 | print(e.data);
5 | };
6 |
7 | ws.onopen = function(e){
8 | log("websocket open");
9 | console.log(e);
10 | };
11 |
12 | ws.onclose = function(e){
13 | log("websocket close");
14 | console.log(e);
15 | };
16 |
17 | $(function(){
18 | $("#btn_post").click(post);
19 | $("#message").keydown(function(e){
20 | if(e.keyCode == 13) post();
21 | });
22 | });
23 |
24 | var post = function(){
25 | var name = $("#name").val();
26 | var mes = $("#message").val();
27 | ws.send(name+" : "+mes);
28 | $("input#message").val("");
29 | };
30 |
31 | var log = function(msg){
32 | console.log(msg);
33 | $("#chat").prepend($("").text("[log] "+msg));
34 | };
35 |
36 | var print = function(msg){
37 | $("#chat").prepend($("").text(msg));
38 | };
39 |
--------------------------------------------------------------------------------
/sample/echo_server.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'eventmachine'
3 | require 'websocket-eventmachine-server'
4 |
5 | PORT = (ARGV.shift || 8080).to_i
6 |
7 | EM::run do
8 | @channel = EM::Channel.new
9 |
10 | puts "start websocket server - port:#{PORT}"
11 |
12 | WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => PORT) do |ws|
13 | ws.onopen do
14 | sid = @channel.subscribe do |mes|
15 | ws.send mes
16 | end
17 | puts "<#{sid}> connect"
18 |
19 | @channel.push "hello new client <#{sid}>"
20 |
21 | ws.onmessage do |msg|
22 | puts "<#{sid}> #{msg}"
23 | @channel.push "<#{sid}> #{msg}"
24 | end
25 |
26 | ws.onclose do
27 | puts "<#{sid}> disconnected"
28 | @channel.unsubscribe sid
29 | @channel.push "<#{sid}> disconnected"
30 | end
31 | end
32 | end
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2014 Sho Hashimoto
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/websocket-client-simple.gemspec:
--------------------------------------------------------------------------------
1 | lib = File.expand_path('../lib', __FILE__)
2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3 | require 'websocket-client-simple/version'
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = "websocket-client-simple"
7 | spec.version = WebSocket::Client::Simple::VERSION
8 | spec.authors = ["Sho Hashimoto"]
9 | spec.email = ["hashimoto@shokai.org"]
10 | spec.description = %q{Simple WebSocket Client for Ruby}
11 | spec.summary = spec.description
12 | spec.homepage = "https://github.com/shokai/websocket-client-simple"
13 | spec.license = "MIT"
14 |
15 | spec.files = `git ls-files`.split($/).reject{|f| f == "Gemfile.lock" }
16 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18 | spec.require_paths = ["lib"]
19 |
20 | spec.add_development_dependency "bundler", "~> 1.3"
21 | spec.add_development_dependency "rake"
22 | spec.add_development_dependency "minitest"
23 | spec.add_development_dependency "websocket-eventmachine-server"
24 | spec.add_development_dependency "eventmachine"
25 |
26 | spec.add_dependency "websocket"
27 | spec.add_dependency "event_emitter"
28 | end
29 |
--------------------------------------------------------------------------------
/test/test_websocket_client_simple.rb:
--------------------------------------------------------------------------------
1 | require_relative 'test_helper'
2 |
3 | class TestWebSocketClientSimple < MiniTest::Test
4 |
5 | def test_echo
6 | msgs = ['foo','bar','baz']
7 | res1 = []
8 | res2 = []
9 |
10 | EM::run{
11 | EchoServer.start
12 |
13 | ## client1 --> server --> client2
14 | EM::add_timer 1 do
15 | client1 = WebSocket::Client::Simple.connect EchoServer.url
16 | client2 = WebSocket::Client::Simple.connect EchoServer.url
17 | assert_equal client1.open?, false
18 | assert_equal client2.open?, false
19 |
20 | client1.on :message do |msg|
21 | res1.push msg
22 | end
23 |
24 | client2.on :message do |msg|
25 | res2.push msg
26 | end
27 |
28 | client1.on :open do
29 | msgs.each do |m|
30 | client1.send m
31 | end
32 | end
33 |
34 | client1.on :close do
35 | EM::stop_event_loop
36 | end
37 |
38 | client2.on :close do
39 | EM::stop_event_loop
40 | end
41 |
42 | EM::add_timer 3 do
43 | assert_equal client1.open?, true
44 | assert_equal client2.open?, true
45 | client1.close
46 | client2.close
47 | EM::stop_event_loop
48 | end
49 | end
50 | }
51 |
52 | assert_equal msgs.size, res1.size
53 | assert_equal msgs.size, res2.size
54 |
55 | msgs.each_with_index do |msg,i|
56 | assert_equal msg, res1[i].to_s
57 | assert_equal msg, res2[i].to_s
58 | end
59 |
60 | end
61 |
62 | end
63 |
--------------------------------------------------------------------------------
/History.txt:
--------------------------------------------------------------------------------
1 | === 0.3.0 2016-02-20
2 |
3 | * "connect" method runs a given block before connecting WebSocket #12
4 | * thank you for suggestion @codekitchen
5 |
6 | === 0.2.5 2016-02-18
7 |
8 | * bugfixed sending when broken pipe #15
9 | * add :verify_mode option for SSL Context #14
10 | * thank you for contributing @michaelvilensky
11 |
12 | === 0.2.4 2015-11-12
13 |
14 | * support handshake headers #11
15 | * thank you for contributing @mathieugagne
16 |
17 | === 0.2.3 2015-10-26
18 |
19 | * kill thread at end of method
20 | * thank you for contributing @hansy
21 |
22 | === 0.2.2 2014-11-18
23 |
24 | * add :ssl_version option to specify version of SSL/TLS
25 | * thank you for contributing @tonybyrne
26 |
27 | === 0.2.1 2014-10-19
28 |
29 | * bugfix socket reading
30 |
31 | === 0.2.0 2014-06-07
32 |
33 | * SSL support with wss:// and https:// scheme
34 | * thank you for contributing @mallowlabs
35 |
36 | === 0.1.0 2014-05-08
37 |
38 | * add accessor Client#handshake #5
39 | * bugfix socket reading
40 |
41 | === 0.0.9 2014-04-03
42 |
43 | * emit "error" in receive thread
44 | * rescue only Errno::EPIPE, not all Errors
45 |
46 | === 0.0.8 2014-01-29
47 |
48 | * bugfix Client#close #4
49 |
50 | === 0.0.7 2014-01-29
51 |
52 | * send CLOSE frame in Client#close #4
53 |
54 | === 0.0.6 2014-01-17
55 |
56 | * add function Client#open?
57 |
58 | === 0.0.5 2013-03-23
59 |
60 | * kill read thread on close
61 |
62 | === 0.0.4 2013-03-23
63 |
64 | * fix sample and README
65 |
66 | === 0.0.3 2013-03-23
67 |
68 | * :type => :text option in send
69 |
70 | === 0.0.2 2013-03-22
71 |
72 | * remove unnecessary sleep
73 |
74 | === 0.0.1 2013-03-22
75 |
76 | * release
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | websocket-client-simple
2 | =======================
3 |
4 | :warning: Important notice :warning:
5 | ------------------------------------
6 |
7 | The development of this repository has moved to [ruby-jp/websocket-client-simple](https://github.com/ruby-jp/websocket-client-simple).
8 |
9 | -------------
10 |
11 | Simple WebSocket Client for Ruby
12 |
13 | - https://github.com/shokai/websocket-client-simple
14 | - https://rubygems.org/gems/websocket-client-simple
15 |
16 | [](https://circleci.com/gh/shokai/websocket-client-simple)
17 |
18 | Installation
19 | ------------
20 |
21 | gem install websocket-client-simple
22 |
23 |
24 | Usage
25 | -----
26 | ```ruby
27 | require 'rubygems'
28 | require 'websocket-client-simple'
29 |
30 | ws = WebSocket::Client::Simple.connect 'ws://example.com:8888'
31 |
32 | ws.on :message do |msg|
33 | puts msg.data
34 | end
35 |
36 | ws.on :open do
37 | ws.send 'hello!!!'
38 | end
39 |
40 | ws.on :close do |e|
41 | p e
42 | exit 1
43 | end
44 |
45 | ws.on :error do |e|
46 | p e
47 | end
48 |
49 | loop do
50 | ws.send STDIN.gets.strip
51 | end
52 | ```
53 |
54 | `connect` runs a given block before connecting websocket
55 |
56 | ```ruby
57 | WebSocket::Client::Simple.connect 'ws://example.com:8888' do |ws|
58 | ws.on :open do
59 | puts "connect!"
60 | end
61 |
62 | ws.on :message do |msg|
63 | puts msg.data
64 | end
65 | end
66 | ```
67 |
68 |
69 | Sample
70 | ------
71 | [websocket chat](https://github.com/shokai/websocket-client-simple/tree/master/sample)
72 |
73 |
74 | Test
75 | ----
76 |
77 | % gem install bundler
78 | % bundle install
79 | % export WS_PORT=8888
80 | % rake test
81 |
82 |
83 | Contributing
84 | ------------
85 | 1. Fork it
86 | 2. Create your feature branch (`git checkout -b my-new-feature`)
87 | 3. Commit your changes (`git commit -am 'Add some feature'`)
88 | 4. Push to the branch (`git push origin my-new-feature`)
89 | 5. Create new Pull Request
90 |
--------------------------------------------------------------------------------
/lib/websocket-client-simple/client.rb:
--------------------------------------------------------------------------------
1 | module WebSocket
2 | module Client
3 | module Simple
4 |
5 | def self.connect(url, options={})
6 | client = ::WebSocket::Client::Simple::Client.new
7 | yield client if block_given?
8 | client.connect url, options
9 | return client
10 | end
11 |
12 | class Client
13 | include EventEmitter
14 | attr_reader :url, :handshake
15 |
16 | def connect(url, options={})
17 | return if @socket
18 | @url = url
19 | uri = URI.parse url
20 | @socket = TCPSocket.new(uri.host,
21 | uri.port || (uri.scheme == 'wss' ? 443 : 80))
22 | if ['https', 'wss'].include? uri.scheme
23 | ctx = OpenSSL::SSL::SSLContext.new
24 | ctx.ssl_version = options[:ssl_version] || 'SSLv23'
25 | ctx.verify_mode = options[:verify_mode] || OpenSSL::SSL::VERIFY_NONE #use VERIFY_PEER for verification
26 | cert_store = OpenSSL::X509::Store.new
27 | cert_store.set_default_paths
28 | ctx.cert_store = cert_store
29 | @socket = ::OpenSSL::SSL::SSLSocket.new(@socket, ctx)
30 | @socket.connect
31 | end
32 | @handshake = ::WebSocket::Handshake::Client.new :url => url, :headers => options[:headers]
33 | @handshaked = false
34 | @pipe_broken = false
35 | frame = ::WebSocket::Frame::Incoming::Client.new
36 | @closed = false
37 | once :__close do |err|
38 | close
39 | emit :close, err
40 | end
41 |
42 | @thread = Thread.new do
43 | while !@closed do
44 | begin
45 | unless recv_data = @socket.getc
46 | sleep 1
47 | next
48 | end
49 | unless @handshaked
50 | @handshake << recv_data
51 | if @handshake.finished?
52 | @handshaked = true
53 | emit :open
54 | end
55 | else
56 | frame << recv_data
57 | while msg = frame.next
58 | emit :message, msg
59 | end
60 | end
61 | rescue => e
62 | emit :error, e
63 | end
64 | end
65 | end
66 |
67 | @socket.write @handshake.to_s
68 | end
69 |
70 | def send(data, opt={:type => :text})
71 | return if !@handshaked or @closed
72 | type = opt[:type]
73 | frame = ::WebSocket::Frame::Outgoing::Client.new(:data => data, :type => type, :version => @handshake.version)
74 | begin
75 | @socket.write frame.to_s
76 | rescue Errno::EPIPE => e
77 | @pipe_broken = true
78 | emit :__close, e
79 | end
80 | end
81 |
82 | def close
83 | return if @closed
84 | if !@pipe_broken
85 | send nil, :type => :close
86 | end
87 | @closed = true
88 | @socket.close if @socket
89 | @socket = nil
90 | emit :__close
91 | Thread.kill @thread if @thread
92 | end
93 |
94 | def open?
95 | @handshake.finished? and !@closed
96 | end
97 |
98 | end
99 |
100 | end
101 | end
102 | end
103 |
--------------------------------------------------------------------------------