bogo-websocket 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/CONTRIBUTING.md +25 -0
- data/LICENSE +13 -0
- data/README.md +67 -0
- data/bogo-websocket.gemspec +17 -0
- data/lib/bogo-websocket/version.rb +5 -0
- data/lib/bogo-websocket/websocket.rb +184 -0
- data/lib/bogo-websocket.rb +9 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 538a7b638863e49064d58a438df0541bda67566a
|
4
|
+
data.tar.gz: bbe48fd88ed074d4e58bbc41390445b69d6e9c8c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0b460274ce5dd7f7d030e4ed7ab51464e5f6d5847e2047a760797138d0e0cf99b8b8ffeaf330b972c02020fecd51a02400306194be240cd163da77351a062a36
|
7
|
+
data.tar.gz: 8199bfc24c40d690ed1ffe19b2528cc27cd7e562234fcf11b1f2f06c52df91c99c2eacc72d9e3490a1d593bd1ad7fb70fa4292379e45b0856335e7a4dea2c5ec
|
data/CHANGELOG.md
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
## Branches
|
4
|
+
|
5
|
+
### `master` branch
|
6
|
+
|
7
|
+
The master branch is the current stable released version.
|
8
|
+
|
9
|
+
### `develop` branch
|
10
|
+
|
11
|
+
The develop branch is the current edge of development.
|
12
|
+
|
13
|
+
## Pull requests
|
14
|
+
|
15
|
+
* https://github.com/spox/bogo-websocket/pulls
|
16
|
+
|
17
|
+
Please base all pull requests of the `develop` branch. Merges to
|
18
|
+
`master` only occur through the `develop` branch. Pull requests
|
19
|
+
based on `master` will likely be cherry picked.
|
20
|
+
|
21
|
+
## Issues
|
22
|
+
|
23
|
+
Need to report an issue? Use the github issues:
|
24
|
+
|
25
|
+
* https://github.com/spox/bogo-websocket/issues
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2015 Chris Roberts
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Bogo Websocket
|
2
|
+
|
3
|
+
Simple websocket library
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
### Basic Usage
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'bogo-websocket'
|
11
|
+
|
12
|
+
socket = Bogo::Websocket::Client.new(
|
13
|
+
:destination => 'ws://example.com:8080',
|
14
|
+
:path => '/websocket',
|
15
|
+
:params => {
|
16
|
+
:fubar => true
|
17
|
+
},
|
18
|
+
:headers => {
|
19
|
+
'X-WOW' => 'custom header'
|
20
|
+
},
|
21
|
+
:on_connect => proc{
|
22
|
+
puts 'Socket Connected'
|
23
|
+
},
|
24
|
+
:on_disconnect => proc{
|
25
|
+
puts 'Socket Disconnected'
|
26
|
+
},
|
27
|
+
:on_error => proc{|error|
|
28
|
+
puts "Error caught: #{error.class} - #{error}"
|
29
|
+
},
|
30
|
+
:on_message => proc{|msg|
|
31
|
+
puts "Received message: #{msg.inspect}"
|
32
|
+
}
|
33
|
+
)
|
34
|
+
socket.write('stuff')
|
35
|
+
```
|
36
|
+
|
37
|
+
### SSL Usage
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
socket = Bogo::Websocket::Client.new(
|
41
|
+
:destination => 'wss://example.com:8080',
|
42
|
+
...
|
43
|
+
```
|
44
|
+
|
45
|
+
#### SSL Usage with Client Key/Certificate
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
socket = Bogo::Websocket::Client.new(
|
49
|
+
:destination => 'wss://example.com:8080',
|
50
|
+
:ssl_key => '/local/path/to/key',
|
51
|
+
:ssl_certificate => '/local/path/to/cert',
|
52
|
+
...
|
53
|
+
```
|
54
|
+
|
55
|
+
#### SSL Usage with Custom Context
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
ssl_ctx = OpenSSL::SSL::SSLContext.new
|
59
|
+
...
|
60
|
+
socket = Bogo::Websocket::Client.new(
|
61
|
+
:destination => 'wss://example.com:8080',
|
62
|
+
:ssl_context => ssl_ctx,
|
63
|
+
...
|
64
|
+
```
|
65
|
+
|
66
|
+
## Info
|
67
|
+
* Repository: https://github.com/spox/bogo-websocket
|
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
|
2
|
+
require 'bogo-websocket/version'
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'bogo-websocket'
|
5
|
+
s.version = Bogo::Websocket::VERSION.version
|
6
|
+
s.summary = 'Simple websocket libraries'
|
7
|
+
s.author = 'Chris Roberts'
|
8
|
+
s.email = 'code@chrisroberts.org'
|
9
|
+
s.homepage = 'https://github.com/spox/bogo-websocket'
|
10
|
+
s.description = 'Simple websocket libraries'
|
11
|
+
s.require_path = 'lib'
|
12
|
+
s.license = 'Apache 2.0'
|
13
|
+
s.add_runtime_dependency 'bogo', '>= 0.1.30', '< 1.0.0'
|
14
|
+
s.add_runtime_dependency 'websocket', '~> 1.2.2'
|
15
|
+
s.add_development_dependency 'minitest'
|
16
|
+
s.files = Dir['lib/**/*'] + %w(bogo-websocket.gemspec README.md CHANGELOG.md CONTRIBUTING.md LICENSE)
|
17
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'websocket'
|
3
|
+
require 'bogo-websocket'
|
4
|
+
|
5
|
+
module Bogo
|
6
|
+
module Websocket
|
7
|
+
|
8
|
+
# Simple websocket client
|
9
|
+
class Client
|
10
|
+
|
11
|
+
include Bogo::Lazy
|
12
|
+
include Bogo::Memoization
|
13
|
+
|
14
|
+
attribute :destination, URI, :required => true, :coerce => lambda{|v| URI.parse(v) }
|
15
|
+
attribute :path, String
|
16
|
+
attribute :params, Hash, :default => {}
|
17
|
+
attribute :headers, Hash, :default => {}
|
18
|
+
attribute :ssl_context, OpenSSL::SSL::SSLContext
|
19
|
+
attribute :ssl_key, String
|
20
|
+
attribute :ssl_certificate, String
|
21
|
+
|
22
|
+
attribute :on_connect, Proc, :default => proc{}
|
23
|
+
attribute :on_disconnect, Proc, :default => proc{}
|
24
|
+
attribute :on_error, Proc, :default => proc{|error|}
|
25
|
+
attribute :on_message, Proc, :default => proc{|message|}
|
26
|
+
|
27
|
+
# @return [TCPSocket, OpenSSL::SSL::SSLSocket]
|
28
|
+
attr_reader :connection
|
29
|
+
# @return [Thread]
|
30
|
+
attr_reader :container
|
31
|
+
# @return [WebSocket::Frame::Incoming::Client]
|
32
|
+
attr_reader :client
|
33
|
+
# @return [WebSocket::Handshake::Client]
|
34
|
+
attr_reader :handshake
|
35
|
+
# @return [TrueClass, FalseClass]
|
36
|
+
attr_reader :die
|
37
|
+
# @return [IO]
|
38
|
+
attr_reader :control_r
|
39
|
+
# @return [IO]
|
40
|
+
attr_reader :control_w
|
41
|
+
|
42
|
+
# Create a new websocket client
|
43
|
+
#
|
44
|
+
# @return [self]
|
45
|
+
def initialize(args={})
|
46
|
+
load_data(args)
|
47
|
+
@control_r, @control_w = IO.pipe
|
48
|
+
@die = false
|
49
|
+
setup_connection
|
50
|
+
perform_handshake
|
51
|
+
@lock = Mutex.new
|
52
|
+
@container = start!
|
53
|
+
end
|
54
|
+
|
55
|
+
# Write to socket
|
56
|
+
#
|
57
|
+
# @param line [String]
|
58
|
+
def write(line)
|
59
|
+
transmit(line, :text)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Close the connection
|
63
|
+
def close
|
64
|
+
connection.close
|
65
|
+
@die = true
|
66
|
+
control_w.write 'closed'
|
67
|
+
container.join
|
68
|
+
end
|
69
|
+
|
70
|
+
# Start the reader
|
71
|
+
def start!
|
72
|
+
unless(@container)
|
73
|
+
@container = Thread.new do
|
74
|
+
until(die || connection.closed?)
|
75
|
+
begin
|
76
|
+
unless(die || connection.closed?)
|
77
|
+
client << connection.read_nonblock(2046)
|
78
|
+
if(message = client.next)
|
79
|
+
handle_message(message)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
rescue IO::WaitReadable
|
83
|
+
unless(die || connection.closed?)
|
84
|
+
IO.select([connection, control_r])
|
85
|
+
retry
|
86
|
+
end
|
87
|
+
rescue EOFError
|
88
|
+
connection.close
|
89
|
+
rescue => error
|
90
|
+
on_error.call(error)
|
91
|
+
raise
|
92
|
+
end
|
93
|
+
end
|
94
|
+
@connection = nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Handle an incoming message
|
100
|
+
#
|
101
|
+
# @param message [WebSocket::Frame::Incoming::Client]
|
102
|
+
def handle_message(message)
|
103
|
+
case message.type
|
104
|
+
when :binary, :text
|
105
|
+
on_message.call(message.data)
|
106
|
+
when :ping
|
107
|
+
transmit(message.data, :pong)
|
108
|
+
when :close
|
109
|
+
connection.close
|
110
|
+
on_disconnect.call
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Send data to socket
|
115
|
+
#
|
116
|
+
# @param data [String]
|
117
|
+
# @param type [Symbol]
|
118
|
+
def transmit(data, type)
|
119
|
+
message = WebSocket::Frame::Outgoing::Client.new(
|
120
|
+
:version => handshake.version,
|
121
|
+
:data => data,
|
122
|
+
:type => type
|
123
|
+
)
|
124
|
+
result = connection.write message.to_s
|
125
|
+
connection.flush
|
126
|
+
result
|
127
|
+
end
|
128
|
+
|
129
|
+
# Setup the handshake and perform handshake with remote connection
|
130
|
+
#
|
131
|
+
# @return [TrueClass]
|
132
|
+
def perform_handshake
|
133
|
+
port = destination.port || destination.scheme == 'wss' ? 443 : 80
|
134
|
+
@handshake = WebSocket::Handshake::Client.new(
|
135
|
+
:host => destination.host,
|
136
|
+
:port => port,
|
137
|
+
:secure => destination.scheme == 'wss',
|
138
|
+
:path => path,
|
139
|
+
:query => URI.encode_www_form(params),
|
140
|
+
:headers => headers
|
141
|
+
)
|
142
|
+
connection.write handshake.to_s
|
143
|
+
reply = ''
|
144
|
+
until(handshake.finished?)
|
145
|
+
reply << connection.read(1)
|
146
|
+
if(reply.index(/\r\n\r\n/m))
|
147
|
+
handshake << reply
|
148
|
+
reply = ''
|
149
|
+
end
|
150
|
+
end
|
151
|
+
unless(handshake.valid?)
|
152
|
+
raise ArgumentError.new 'Invalid handshake. Failed to connect!'
|
153
|
+
end
|
154
|
+
@client = WebSocket::Frame::Incoming::Client.new(:version => handshake.version)
|
155
|
+
true
|
156
|
+
end
|
157
|
+
|
158
|
+
# @return [TCPSocket, OpenSSL::SSL::SSLSocket]
|
159
|
+
def setup_connection
|
160
|
+
socket = TCPSocket.new(destination.host, destination.port)
|
161
|
+
if(destination.scheme == 'wss')
|
162
|
+
socket = OpenSSL::SSL::SSLSocket.new(socket, build_ssl_context)
|
163
|
+
socket.connect
|
164
|
+
end
|
165
|
+
@connection = socket
|
166
|
+
end
|
167
|
+
|
168
|
+
# @return [OpenSSL::SSL::SSLContext]
|
169
|
+
def build_ssl_context
|
170
|
+
if(ssl_context)
|
171
|
+
ssl_context
|
172
|
+
else
|
173
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
174
|
+
if(ssl_key || ssl_certificate)
|
175
|
+
ctx.cert = OpenSSL::X509::Certificate.new(File.read(ssl_certificate))
|
176
|
+
ctx.key = OpenSSL::PKey::RSA.new(File.read(ssl_key))
|
177
|
+
end
|
178
|
+
ctx
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bogo-websocket
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Roberts
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bogo
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.30
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.1.30
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: websocket
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.2.2
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.2.2
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: minitest
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
description: Simple websocket libraries
|
62
|
+
email: code@chrisroberts.org
|
63
|
+
executables: []
|
64
|
+
extensions: []
|
65
|
+
extra_rdoc_files: []
|
66
|
+
files:
|
67
|
+
- CHANGELOG.md
|
68
|
+
- CONTRIBUTING.md
|
69
|
+
- LICENSE
|
70
|
+
- README.md
|
71
|
+
- bogo-websocket.gemspec
|
72
|
+
- lib/bogo-websocket.rb
|
73
|
+
- lib/bogo-websocket/version.rb
|
74
|
+
- lib/bogo-websocket/websocket.rb
|
75
|
+
homepage: https://github.com/spox/bogo-websocket
|
76
|
+
licenses:
|
77
|
+
- Apache 2.0
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.4.8
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Simple websocket libraries
|
99
|
+
test_files: []
|