├── .gitignore ├── Gemfile ├── Gemfile.lock ├── History.txt ├── LICENSE.txt ├── README.md ├── Rakefile ├── circle.yml ├── lib ├── websocket-client-simple.rb └── websocket-client-simple │ ├── client.rb │ └── version.rb ├── sample ├── client.rb ├── echo_server.rb └── webbrowser │ ├── index.html │ └── main.js ├── test ├── echo_server.rb ├── test_connect_block.rb ├── test_helper.rb └── test_websocket_client_simple.rb └── websocket-client-simple.gemspec /.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 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in websocket-client-simple.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | [![Circle CI](https://circleci.com/gh/shokai/websocket-client-simple.svg?style=svg)](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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | ruby: 3 | version: "2.2.2" 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------