raktr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE.md +29 -0
- data/README.md +77 -0
- data/Rakefile +53 -0
- data/lib/raktr/connection/callbacks.rb +71 -0
- data/lib/raktr/connection/error.rb +120 -0
- data/lib/raktr/connection/peer_info.rb +90 -0
- data/lib/raktr/connection/tls.rb +164 -0
- data/lib/raktr/connection.rb +339 -0
- data/lib/raktr/global.rb +24 -0
- data/lib/raktr/iterator.rb +249 -0
- data/lib/raktr/queue.rb +89 -0
- data/lib/raktr/tasks/base.rb +57 -0
- data/lib/raktr/tasks/delayed.rb +33 -0
- data/lib/raktr/tasks/one_off.rb +30 -0
- data/lib/raktr/tasks/periodic.rb +58 -0
- data/lib/raktr/tasks/persistent.rb +29 -0
- data/lib/raktr/tasks.rb +105 -0
- data/lib/raktr/version.rb +13 -0
- data/lib/raktr.rb +707 -0
- data/spec/raktr/connection/tls_spec.rb +348 -0
- data/spec/raktr/connection_spec.rb +74 -0
- data/spec/raktr/iterator_spec.rb +203 -0
- data/spec/raktr/queue_spec.rb +91 -0
- data/spec/raktr/tasks/base.rb +8 -0
- data/spec/raktr/tasks/delayed_spec.rb +71 -0
- data/spec/raktr/tasks/one_off_spec.rb +66 -0
- data/spec/raktr/tasks/periodic_spec.rb +57 -0
- data/spec/raktr/tasks/persistent_spec.rb +54 -0
- data/spec/raktr/tasks_spec.rb +155 -0
- data/spec/raktr_spec.rb +20 -0
- data/spec/raktr_tls_spec.rb +20 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/fixtures/handlers/echo_client.rb +34 -0
- data/spec/support/fixtures/handlers/echo_client_tls.rb +10 -0
- data/spec/support/fixtures/handlers/echo_server.rb +12 -0
- data/spec/support/fixtures/handlers/echo_server_tls.rb +8 -0
- data/spec/support/fixtures/pems/cacert.pem +37 -0
- data/spec/support/fixtures/pems/client/cert.pem +37 -0
- data/spec/support/fixtures/pems/client/foo-cert.pem +39 -0
- data/spec/support/fixtures/pems/client/foo-key.pem +51 -0
- data/spec/support/fixtures/pems/client/key.pem +51 -0
- data/spec/support/fixtures/pems/server/cert.pem +37 -0
- data/spec/support/fixtures/pems/server/key.pem +51 -0
- data/spec/support/helpers/paths.rb +23 -0
- data/spec/support/helpers/utilities.rb +135 -0
- data/spec/support/lib/server_option_parser.rb +29 -0
- data/spec/support/lib/servers/runner.rb +13 -0
- data/spec/support/lib/servers.rb +133 -0
- data/spec/support/servers/echo.rb +14 -0
- data/spec/support/servers/echo_tls.rb +22 -0
- data/spec/support/servers/echo_unix.rb +14 -0
- data/spec/support/servers/echo_unix_tls.rb +22 -0
- data/spec/support/shared/connection.rb +696 -0
- data/spec/support/shared/raktr.rb +834 -0
- data/spec/support/shared/task.rb +21 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2aa060f18df13540e73b42a5e9c68bdf86eaf03ae21104da0e827e57252dffe3
|
4
|
+
data.tar.gz: 5f1dd3fa7626e764f5212424c8e25b26a16bb52d4d00a2d2e6e4e5684575123a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8889f36f41d78a38dbd5afb95af6f4c2836e2056bb00451dabb10141c19d34c1ce02a7809c54e3c807713ab0b3bb94e6d969eb423878ab2c8514db703c868f85
|
7
|
+
data.tar.gz: fa3426f952f1980e9916aeaa1af050da734fec2c8de94a8e352c5223271e4309f66abd9fba46399d5470394ad9b4d65801620c4b060e9b4db1c0775d23a8859e
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# ChangeLog
|
data/LICENSE.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# License
|
2
|
+
|
3
|
+
Copyright (C) 2022, Ecsypno <https://ecsypno.com/>
|
4
|
+
All rights reserved.
|
5
|
+
|
6
|
+
Redistribution and use in source and binary forms, with or without modification,
|
7
|
+
are permitted provided that the following conditions are met:
|
8
|
+
|
9
|
+
* Redistributions of source code must retain the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer.
|
11
|
+
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
14
|
+
and/or other materials provided with the distribution.
|
15
|
+
|
16
|
+
* Neither the name of the copyright holder nor the names of its contributors
|
17
|
+
may be used to endorse or promote products derived from this software
|
18
|
+
without specific prior written permission.
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
21
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
22
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
24
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
25
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
26
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
27
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
28
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
29
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Raktr
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<th>Version</th>
|
6
|
+
<td>0.0.1</td>
|
7
|
+
</tr>
|
8
|
+
<tr>
|
9
|
+
<th>Github page</th>
|
10
|
+
<td><a href="http://github.com/qadron/raktr">http://github.com/qadron/raktr</a></td>
|
11
|
+
<tr/>
|
12
|
+
<tr>
|
13
|
+
<th>Code Documentation</th>
|
14
|
+
<td><a href="http://rubydoc.info/github/qadron/raktr/">http://rubydoc.info/github/qadron/raktr/</a></td>
|
15
|
+
</tr>
|
16
|
+
<tr>
|
17
|
+
<th>Author</th>
|
18
|
+
<td><a href="http://twitter.com/Zap0tek">Tasos Laskos</a></td>
|
19
|
+
</tr>
|
20
|
+
<tr>
|
21
|
+
<th>Copyright</th>
|
22
|
+
<td>2022 <a href="https://ecsypno.com">Ecsypno</a></td>
|
23
|
+
</tr>
|
24
|
+
<tr>
|
25
|
+
<th>License</th>
|
26
|
+
<td><a href="file.LICENSE.html">3-clause BSD</a></td>
|
27
|
+
</tr>
|
28
|
+
</table>
|
29
|
+
|
30
|
+
## Synopsis
|
31
|
+
|
32
|
+
Raktr is a simple, lightweight, pure-Ruby implementation of the
|
33
|
+
[Reactor](http://en.wikipedia.org/wiki/Reactor_pattern) pattern, mainly focused
|
34
|
+
on network connections -- and less so on generic tasks.
|
35
|
+
|
36
|
+
## Features
|
37
|
+
|
38
|
+
- Extremely lightweight.
|
39
|
+
- Very simple design.
|
40
|
+
- Support for TCP/IP and UNIX-domain sockets.
|
41
|
+
- TLS encryption.
|
42
|
+
- Pure-Ruby.
|
43
|
+
- Multi-platform.
|
44
|
+
|
45
|
+
## Supported platforms
|
46
|
+
|
47
|
+
- Rubies:
|
48
|
+
- MRI >= 1.9
|
49
|
+
- Rubinius
|
50
|
+
- JRuby
|
51
|
+
- Operating Systems:
|
52
|
+
- Linux
|
53
|
+
- OSX
|
54
|
+
- Windows
|
55
|
+
|
56
|
+
## Examples
|
57
|
+
|
58
|
+
For examples please see the `examples/` directory.
|
59
|
+
|
60
|
+
## Installation
|
61
|
+
|
62
|
+
gem install raktr
|
63
|
+
|
64
|
+
## Running the Specs
|
65
|
+
|
66
|
+
rake spec
|
67
|
+
|
68
|
+
## Bug reports/Feature requests
|
69
|
+
|
70
|
+
Please send your feedback using GitHub's issue system at
|
71
|
+
[http://github.com/qadron/raktr/issues](http://github.com/qadron/raktr/issues).
|
72
|
+
|
73
|
+
|
74
|
+
## License
|
75
|
+
|
76
|
+
Raktr is provided under the 3-clause BSD license.
|
77
|
+
See the [LICENSE](https://github.com/qadron/raktr/blob/master/LICENSE.md) file for more information.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Raktr project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Raktr
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require File.expand_path( File.dirname( __FILE__ ) ) + '/lib/raktr/version'
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'rspec'
|
14
|
+
require 'rspec/core/rake_task'
|
15
|
+
|
16
|
+
RSpec::Core::RakeTask.new
|
17
|
+
rescue
|
18
|
+
end
|
19
|
+
|
20
|
+
task default: [ :build, :spec ]
|
21
|
+
|
22
|
+
desc 'Generate docs'
|
23
|
+
task :docs do
|
24
|
+
outdir = '../raktr-docs'
|
25
|
+
sh "rm -rf #{outdir}"
|
26
|
+
sh "mkdir -p #{outdir}"
|
27
|
+
|
28
|
+
sh "yardoc -o #{outdir}"
|
29
|
+
|
30
|
+
sh 'rm -rf .yardoc'
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'Clean up'
|
34
|
+
task :clean do
|
35
|
+
sh 'rm *.gem || true'
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'Build the gem.'
|
39
|
+
task build: [ :clean ] do
|
40
|
+
sh "gem build raktr.gemspec"
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'Build and install the gem.'
|
44
|
+
task install: [ :build ] do
|
45
|
+
sh "gem install raktr-#{Raktr::VERSION}.gem"
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'Push a new version to Rubygems'
|
49
|
+
task publish: [ :build ] do
|
50
|
+
sh "git tag -a v#{Raktr::VERSION} -m 'Version #{Raktr::VERSION}'"
|
51
|
+
sh "gem push raktr-#{Raktr::VERSION}.gem"
|
52
|
+
end
|
53
|
+
task release: [ :publish ]
|
@@ -0,0 +1,71 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Raktr project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Raktr
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
class Raktr
|
10
|
+
class Connection
|
11
|
+
|
12
|
+
# Callbacks to be invoked per event.
|
13
|
+
#
|
14
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
15
|
+
module Callbacks
|
16
|
+
|
17
|
+
# Called after the connection has been established.
|
18
|
+
#
|
19
|
+
# @abstract
|
20
|
+
def on_connect
|
21
|
+
end
|
22
|
+
|
23
|
+
# Called after the connection has been attached to a {#raktr}.
|
24
|
+
#
|
25
|
+
# @abstract
|
26
|
+
def on_attach
|
27
|
+
end
|
28
|
+
|
29
|
+
# Called right the connection is detached from the {#raktr}.
|
30
|
+
#
|
31
|
+
# @abstract
|
32
|
+
def on_detach
|
33
|
+
end
|
34
|
+
|
35
|
+
# @note If a connection could not be established no {#socket} may be
|
36
|
+
# available.
|
37
|
+
#
|
38
|
+
# Called when the connection gets closed.
|
39
|
+
#
|
40
|
+
# @param [Exception] reason
|
41
|
+
# Reason for the close.
|
42
|
+
#
|
43
|
+
# @abstract
|
44
|
+
def on_close( reason )
|
45
|
+
end
|
46
|
+
|
47
|
+
# Called when data are available.
|
48
|
+
#
|
49
|
+
# @param [String] data
|
50
|
+
# Incoming data.
|
51
|
+
#
|
52
|
+
# @abstract
|
53
|
+
def on_read( data )
|
54
|
+
end
|
55
|
+
|
56
|
+
# Called after each {#write} call.
|
57
|
+
#
|
58
|
+
# @abstract
|
59
|
+
def on_write
|
60
|
+
end
|
61
|
+
|
62
|
+
# Called after the {#write buffered data} have all been sent to the peer.
|
63
|
+
#
|
64
|
+
# @abstract
|
65
|
+
def on_flush
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Raktr project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Raktr
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
class Raktr
|
10
|
+
class Connection
|
11
|
+
|
12
|
+
# {Connection} error namespace.
|
13
|
+
#
|
14
|
+
# All {Connection} errors inherit from and live under it.
|
15
|
+
#
|
16
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
17
|
+
class Error < Raktr::Error
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Captures Ruby exceptions and converts them to an appropriate
|
21
|
+
# subclass of {Error}.
|
22
|
+
#
|
23
|
+
# @param [Block] block Block to run.
|
24
|
+
def translate( &block )
|
25
|
+
block.call
|
26
|
+
rescue IOError, Errno::ENOTCONN, Errno::ENOTSOCK => e
|
27
|
+
raise_with_proper_backtrace( e, Closed )
|
28
|
+
rescue SocketError, Errno::ENOENT => e
|
29
|
+
raise_with_proper_backtrace( e, HostNotFound )
|
30
|
+
rescue Errno::EPIPE => e
|
31
|
+
raise_with_proper_backtrace( e, BrokenPipe )
|
32
|
+
rescue Errno::ECONNREFUSED,
|
33
|
+
# JRuby throws Errno::EADDRINUSE when trying to connect to a
|
34
|
+
# non-existent server.
|
35
|
+
Errno::EADDRINUSE => e
|
36
|
+
raise_with_proper_backtrace( e, Refused )
|
37
|
+
rescue Errno::ECONNRESET, Errno::ECONNABORTED => e
|
38
|
+
raise_with_proper_backtrace( e, Reset )
|
39
|
+
rescue Errno::EACCES => e
|
40
|
+
raise_with_proper_backtrace( e, Permission )
|
41
|
+
rescue Errno::EBADF => e
|
42
|
+
raise_with_proper_backtrace( e, BadSocket )
|
43
|
+
|
44
|
+
# Catch and forward these before handling OpenSSL::OpenSSLError because
|
45
|
+
# all SSL errors inherit from it, including OpenSSL::SSL::SSLErrorWaitReadable
|
46
|
+
# and OpenSSL::SSL::SSLErrorWaitWritable which also inherit from
|
47
|
+
# IO::WaitReadable and IO::WaitWritable and need special treatment.
|
48
|
+
rescue IO::WaitReadable, IO::WaitWritable, Errno::EINPROGRESS
|
49
|
+
raise
|
50
|
+
|
51
|
+
# We're mainly interested in translating SSL handshake errors but there
|
52
|
+
# aren't any specific exceptions for these.
|
53
|
+
#
|
54
|
+
# Why make things easy and clean, right?
|
55
|
+
rescue OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError => e
|
56
|
+
raise_with_proper_backtrace( e, SSL )
|
57
|
+
end
|
58
|
+
|
59
|
+
def raise_with_proper_backtrace( ruby, raktr )
|
60
|
+
e = raktr.new( ruby.to_s )
|
61
|
+
e.set_backtrace ruby.backtrace
|
62
|
+
raise e
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Like a `SocketError.getaddrinfo` exception.
|
67
|
+
#
|
68
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
69
|
+
class HostNotFound < Error
|
70
|
+
end
|
71
|
+
|
72
|
+
# Like a `Errno::EACCES` exception.
|
73
|
+
#
|
74
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
75
|
+
class Permission < Error
|
76
|
+
end
|
77
|
+
|
78
|
+
# Like a `Errno::ECONNREFUSED` exception.
|
79
|
+
#
|
80
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
81
|
+
class Refused < Error
|
82
|
+
end
|
83
|
+
|
84
|
+
# Like a `Errno::ECONNRESET` exception.
|
85
|
+
#
|
86
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
87
|
+
class Reset < Error
|
88
|
+
end
|
89
|
+
|
90
|
+
# Like a `Errno::EPIPE` exception.
|
91
|
+
#
|
92
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
93
|
+
class BrokenPipe < Error
|
94
|
+
end
|
95
|
+
|
96
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
97
|
+
class Timeout < Error
|
98
|
+
end
|
99
|
+
|
100
|
+
# Like a `IOError` exception.
|
101
|
+
#
|
102
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
103
|
+
class Closed < Error
|
104
|
+
end
|
105
|
+
|
106
|
+
# Like a `Errno::EBADF` exception.
|
107
|
+
#
|
108
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
109
|
+
class BadSocket < Error
|
110
|
+
end
|
111
|
+
|
112
|
+
# Like a `OpenSSL::OpenSSLError` exception.
|
113
|
+
#
|
114
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
115
|
+
class SSL < Error
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Raktr project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Raktr
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
class Raktr
|
10
|
+
class Connection
|
11
|
+
|
12
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
13
|
+
module PeerInfo
|
14
|
+
|
15
|
+
# @param [Bool] resolve
|
16
|
+
# Resolve IP address to hostname.
|
17
|
+
# @return [Hash]
|
18
|
+
# Peer address information:
|
19
|
+
#
|
20
|
+
# * IP socket:
|
21
|
+
# * Without `resolve`:
|
22
|
+
#
|
23
|
+
# {
|
24
|
+
# protocol: 'AF_INET',
|
25
|
+
# port: 10314,
|
26
|
+
# hostname: '127.0.0.1',
|
27
|
+
# ip_address: '127.0.0.1'
|
28
|
+
# }
|
29
|
+
#
|
30
|
+
# * With `resolve`:
|
31
|
+
#
|
32
|
+
# {
|
33
|
+
# protocol: 'AF_INET',
|
34
|
+
# port: 10314,
|
35
|
+
# hostname: 'localhost',
|
36
|
+
# ip_address: '127.0.0.1'
|
37
|
+
# }
|
38
|
+
#
|
39
|
+
# * UNIX-domain socket:
|
40
|
+
#
|
41
|
+
# {
|
42
|
+
# protocol: 'AF_UNIX',
|
43
|
+
# path: '/tmp/my-socket'
|
44
|
+
# }
|
45
|
+
def peer_address_info( resolve = false )
|
46
|
+
if Raktr.supports_unix_sockets? && to_io.is_a?( UNIXSocket )
|
47
|
+
{
|
48
|
+
protocol: to_io.peeraddr.first,
|
49
|
+
path: to_io.path
|
50
|
+
}
|
51
|
+
else
|
52
|
+
protocol, port, hostname, ip_address = to_io.peeraddr( resolve )
|
53
|
+
|
54
|
+
{
|
55
|
+
protocol: protocol,
|
56
|
+
port: port,
|
57
|
+
hostname: hostname,
|
58
|
+
ip_address: ip_address
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [String]
|
64
|
+
# Peer's IP address or socket path.
|
65
|
+
def peer_address
|
66
|
+
peer_ip_address || peer_address_info[:path]
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [String]
|
70
|
+
# Peer's IP address.
|
71
|
+
def peer_ip_address
|
72
|
+
peer_address_info[:ip_address]
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [String]
|
76
|
+
# Peer's hostname.
|
77
|
+
def peer_hostname
|
78
|
+
peer_address_info(true)[:hostname]
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [String]
|
82
|
+
# Peer's port.
|
83
|
+
def peer_port
|
84
|
+
peer_address_info[:port]
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Raktr project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Raktr
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
class Raktr
|
10
|
+
class Connection
|
11
|
+
|
12
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
13
|
+
module TLS
|
14
|
+
|
15
|
+
# Converts the {#socket} to an SSL one.
|
16
|
+
#
|
17
|
+
# @param [Hash] options
|
18
|
+
# @option [String] :certificate
|
19
|
+
# Path to a PEM certificate.
|
20
|
+
# @option [String] :private_key
|
21
|
+
# Path to a PEM private key.
|
22
|
+
# @option [String] :ca
|
23
|
+
# Path to a PEM CA.
|
24
|
+
def start_tls( options = {} )
|
25
|
+
if @socket.is_a? OpenSSL::SSL::SSLSocket
|
26
|
+
@ssl_context = @socket.context
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
@ssl_context = OpenSSL::SSL::SSLContext.new
|
31
|
+
@ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
32
|
+
|
33
|
+
if options[:certificate] && options[:private_key]
|
34
|
+
|
35
|
+
@ssl_context.cert =
|
36
|
+
OpenSSL::X509::Certificate.new( File.open( options[:certificate] ) )
|
37
|
+
@ssl_context.key =
|
38
|
+
OpenSSL::PKey::RSA.new( File.open( options[:private_key] ) )
|
39
|
+
|
40
|
+
@ssl_context.ca_file = options[:ca]
|
41
|
+
@ssl_context.verify_mode =
|
42
|
+
OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
43
|
+
|
44
|
+
elsif @role == :server
|
45
|
+
@ssl_context.key = OpenSSL::PKey::RSA.new( 2048 )
|
46
|
+
@ssl_context.cert = OpenSSL::X509::Certificate.new
|
47
|
+
@ssl_context.cert.subject = OpenSSL::X509::Name.new( [['CN', 'localhost']] )
|
48
|
+
@ssl_context.cert.issuer = @ssl_context.cert.subject
|
49
|
+
@ssl_context.cert.public_key = @ssl_context.key
|
50
|
+
@ssl_context.cert.not_before = Time.now
|
51
|
+
@ssl_context.cert.not_after = Time.now + 60 * 60 * 24
|
52
|
+
@ssl_context.cert.version = 2
|
53
|
+
@ssl_context.cert.serial = 1
|
54
|
+
|
55
|
+
@ssl_context.cert.sign( @ssl_context.key, OpenSSL::Digest::SHA1.new )
|
56
|
+
end
|
57
|
+
|
58
|
+
if @role == :server
|
59
|
+
@socket = OpenSSL::SSL::SSLServer.new( @socket, @ssl_context )
|
60
|
+
else
|
61
|
+
@socket = OpenSSL::SSL::SSLSocket.new( @socket, @ssl_context )
|
62
|
+
@socket.sync_close = true
|
63
|
+
|
64
|
+
# We've switched to SSL, a connection needs to be re-established
|
65
|
+
# via the SSL handshake.
|
66
|
+
@connected = false
|
67
|
+
|
68
|
+
_connect if unix?
|
69
|
+
end
|
70
|
+
|
71
|
+
@socket
|
72
|
+
end
|
73
|
+
|
74
|
+
# Performs an SSL handshake in addition to a plaintext connect operation.
|
75
|
+
#
|
76
|
+
# @private
|
77
|
+
def _connect
|
78
|
+
return if @ssl_connected
|
79
|
+
|
80
|
+
Error.translate do
|
81
|
+
@plaintext_connected ||= super
|
82
|
+
return if !@plaintext_connected
|
83
|
+
|
84
|
+
# Mark the connection as not connected due to the pending SSL handshake.
|
85
|
+
@connected = false
|
86
|
+
|
87
|
+
@socket.connect_nonblock
|
88
|
+
@ssl_connected = @connected = true
|
89
|
+
end
|
90
|
+
rescue IO::WaitReadable, IO::WaitWritable, Errno::EINPROGRESS
|
91
|
+
rescue Error => e
|
92
|
+
close e
|
93
|
+
end
|
94
|
+
|
95
|
+
# First checks if there's a pending SSL #accept operation when this
|
96
|
+
# connection is a server handler which has been passed an accepted
|
97
|
+
# plaintext connection.
|
98
|
+
#
|
99
|
+
# @private
|
100
|
+
def _write( *args )
|
101
|
+
return ssl_accept if accept?
|
102
|
+
|
103
|
+
super( *args )
|
104
|
+
end
|
105
|
+
|
106
|
+
# First checks if there's a pending SSL #accept operation when this
|
107
|
+
# connection is a server handler which has been passed an accepted
|
108
|
+
# plaintext connection.
|
109
|
+
#
|
110
|
+
# @private
|
111
|
+
def _read
|
112
|
+
return ssl_accept if accept?
|
113
|
+
|
114
|
+
super
|
115
|
+
rescue OpenSSL::SSL::SSLErrorWaitReadable
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def ssl_accept
|
121
|
+
Error.translate do
|
122
|
+
@accepted = !!@socket.accept_nonblock
|
123
|
+
end
|
124
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
125
|
+
rescue Error => e
|
126
|
+
close e
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
130
|
+
def accept?
|
131
|
+
return false if @accepted
|
132
|
+
return false if role != :server || !@socket.is_a?( OpenSSL::SSL::SSLSocket )
|
133
|
+
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
137
|
+
# Accepts a new SSL client connection.
|
138
|
+
#
|
139
|
+
# @return [OpenSSL::SSL::SSLSocket, nil]
|
140
|
+
# New connection or `nil` if the socket isn't ready to accept new
|
141
|
+
# connections yet.
|
142
|
+
#
|
143
|
+
# @private
|
144
|
+
def socket_accept
|
145
|
+
Error.translate do
|
146
|
+
socket = to_io.accept_nonblock
|
147
|
+
|
148
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(
|
149
|
+
socket,
|
150
|
+
@ssl_context
|
151
|
+
)
|
152
|
+
ssl_socket.sync_close = true
|
153
|
+
ssl.accept if @start_immediately
|
154
|
+
ssl_socket
|
155
|
+
end
|
156
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
157
|
+
rescue Error => e
|
158
|
+
close e
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|