raptor-io 0.0.1
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 +15 -0
- data/LICENSE +30 -0
- data/README.md +51 -0
- data/lib/rack/handler/raptor-io.rb +130 -0
- data/lib/raptor-io.rb +11 -0
- data/lib/raptor-io/error.rb +19 -0
- data/lib/raptor-io/protocol.rb +6 -0
- data/lib/raptor-io/protocol/error.rb +10 -0
- data/lib/raptor-io/protocol/http.rb +34 -0
- data/lib/raptor-io/protocol/http/client.rb +685 -0
- data/lib/raptor-io/protocol/http/error.rb +16 -0
- data/lib/raptor-io/protocol/http/headers.rb +132 -0
- data/lib/raptor-io/protocol/http/message.rb +67 -0
- data/lib/raptor-io/protocol/http/request.rb +307 -0
- data/lib/raptor-io/protocol/http/request/manipulator.rb +117 -0
- data/lib/raptor-io/protocol/http/request/manipulators.rb +217 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticator.rb +110 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/basic.rb +36 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/digest.rb +135 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/negotiate.rb +69 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/ntlm.rb +29 -0
- data/lib/raptor-io/protocol/http/request/manipulators/redirect_follower.rb +65 -0
- data/lib/raptor-io/protocol/http/response.rb +166 -0
- data/lib/raptor-io/protocol/http/server.rb +446 -0
- data/lib/raptor-io/ruby.rb +4 -0
- data/lib/raptor-io/ruby/hash.rb +24 -0
- data/lib/raptor-io/ruby/ipaddr.rb +15 -0
- data/lib/raptor-io/ruby/openssl.rb +23 -0
- data/lib/raptor-io/ruby/string.rb +27 -0
- data/lib/raptor-io/socket.rb +175 -0
- data/lib/raptor-io/socket/comm.rb +143 -0
- data/lib/raptor-io/socket/comm/local.rb +94 -0
- data/lib/raptor-io/socket/comm/sapni.rb +75 -0
- data/lib/raptor-io/socket/comm/socks.rb +237 -0
- data/lib/raptor-io/socket/comm_chain.rb +30 -0
- data/lib/raptor-io/socket/error.rb +45 -0
- data/lib/raptor-io/socket/switch_board.rb +183 -0
- data/lib/raptor-io/socket/switch_board/route.rb +42 -0
- data/lib/raptor-io/socket/tcp.rb +231 -0
- data/lib/raptor-io/socket/tcp/ssl.rb +77 -0
- data/lib/raptor-io/socket/tcp_server.rb +16 -0
- data/lib/raptor-io/socket/tcp_server/ssl.rb +52 -0
- data/lib/raptor-io/socket/udp.rb +0 -0
- data/lib/raptor-io/version.rb +6 -0
- data/lib/tasks/yard.rake +26 -0
- data/spec/rack/handler/raptor_spec.rb +140 -0
- data/spec/raptor-io/protocol/http/client_spec.rb +671 -0
- data/spec/raptor-io/protocol/http/headers_spec.rb +189 -0
- data/spec/raptor-io/protocol/http/message_spec.rb +5 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticator_spec.rb +193 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/basic_spec.rb +32 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/digest_spec.rb +76 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/negotiate_spec.rb +52 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/ntlm_spec.rb +37 -0
- data/spec/raptor-io/protocol/http/request/manipulators/redirect_follower_spec.rb +51 -0
- data/spec/raptor-io/protocol/http/request/manipulators_spec.rb +202 -0
- data/spec/raptor-io/protocol/http/request_spec.rb +965 -0
- data/spec/raptor-io/protocol/http/response_spec.rb +236 -0
- data/spec/raptor-io/protocol/http/server_spec.rb +345 -0
- data/spec/raptor-io/ruby/hash_spec.rb +20 -0
- data/spec/raptor-io/ruby/string_spec.rb +20 -0
- data/spec/raptor-io/socket/comm/local_spec.rb +50 -0
- data/spec/raptor-io/socket/switch_board/route_spec.rb +49 -0
- data/spec/raptor-io/socket/switch_board_spec.rb +87 -0
- data/spec/raptor-io/socket/tcp/ssl_spec.rb +18 -0
- data/spec/raptor-io/socket/tcp_server/ssl_spec.rb +59 -0
- data/spec/raptor-io/socket/tcp_server_spec.rb +19 -0
- data/spec/raptor-io/socket/tcp_spec.rb +14 -0
- data/spec/raptor-io/socket_spec.rb +16 -0
- data/spec/raptor-io/version_spec.rb +10 -0
- data/spec/spec_helper.rb +56 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/manifoolators/fooer.rb +25 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/niccolo_machiavelli.rb +20 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/options_validator.rb +28 -0
- data/spec/support/fixtures/raptor/socket/ssl_server.crt +18 -0
- data/spec/support/fixtures/raptor/socket/ssl_server.key +15 -0
- data/spec/support/lib/path_helpers.rb +11 -0
- data/spec/support/lib/webserver_option_parser.rb +26 -0
- data/spec/support/lib/webservers.rb +120 -0
- data/spec/support/shared/contexts/with_ssl_server.rb +70 -0
- data/spec/support/shared/contexts/with_tcp_server.rb +58 -0
- data/spec/support/shared/examples/raptor/comm_examples.rb +26 -0
- data/spec/support/shared/examples/raptor/protocols/http/message.rb +106 -0
- data/spec/support/shared/examples/raptor/socket_examples.rb +135 -0
- data/spec/support/webservers/raptor/protocols/http/client.rb +100 -0
- data/spec/support/webservers/raptor/protocols/http/client_close_connection.rb +29 -0
- data/spec/support/webservers/raptor/protocols/http/client_https.rb +43 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/basic.rb +9 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/digest.rb +22 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/redirect_follower.rb +11 -0
- metadata +336 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'openssl'
|
|
2
|
+
|
|
3
|
+
class OpenSSL::SSL::SSLServer
|
|
4
|
+
# Guard this in case stdlib ever implements it
|
|
5
|
+
unless method_defined?(:accept_nonblock)
|
|
6
|
+
# Non-blocking version of accept, stolen directly from the blocking
|
|
7
|
+
# version, OpenSSL::SSL::SSLServer#accept.
|
|
8
|
+
def accept_nonblock
|
|
9
|
+
sock = @svr.accept_nonblock
|
|
10
|
+
|
|
11
|
+
begin
|
|
12
|
+
ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
|
|
13
|
+
ssl.sync_close = true
|
|
14
|
+
ssl.accept if @start_immediately
|
|
15
|
+
ssl
|
|
16
|
+
rescue OpenSSL::SSL::SSLError => ex
|
|
17
|
+
sock.close
|
|
18
|
+
raise ex
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class String
|
|
2
|
+
|
|
3
|
+
# @return [String] `self` with 8-bit unsigned characters.
|
|
4
|
+
def repack
|
|
5
|
+
unpack( 'C*' ).pack( 'C*' )
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Forces `self` to UTF-8 and replaces invalid characters.
|
|
9
|
+
def force_utf8!
|
|
10
|
+
force_encoding( 'utf-8' )
|
|
11
|
+
encode!( 'utf-16be', invalid: :replace, undef: :replace ).encode( 'utf-8' )
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [String] Copy of `self`, {#force_utf8! forced to UTF-8}.
|
|
15
|
+
def force_utf8
|
|
16
|
+
dup.force_utf8!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Bool]
|
|
20
|
+
# `true` if `self` is binary, `false` if regular text.
|
|
21
|
+
def binary?
|
|
22
|
+
encoding == Encoding::ASCII_8BIT ||
|
|
23
|
+
index( "\x00" ) ||
|
|
24
|
+
count( "\x00-\x7F", "^ -~\t\r\n").fdiv( length ) > 0.3
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
require 'raptor-io/ruby'
|
|
3
|
+
|
|
4
|
+
# A basic class for specific transports to inherit from. Analogous to
|
|
5
|
+
# stdlib's BasicSocket
|
|
6
|
+
class RaptorIO::Socket
|
|
7
|
+
extend Forwardable
|
|
8
|
+
|
|
9
|
+
require 'raptor-io/socket/error'
|
|
10
|
+
require 'raptor-io/socket/switch_board'
|
|
11
|
+
require 'raptor-io/socket/tcp'
|
|
12
|
+
require 'raptor-io/socket/tcp/ssl'
|
|
13
|
+
require 'raptor-io/socket/tcp_server'
|
|
14
|
+
require 'raptor-io/socket/tcp_server/ssl'
|
|
15
|
+
|
|
16
|
+
# Like IO.select, but smarter
|
|
17
|
+
#
|
|
18
|
+
# OpenSSL does its own buffering which can result in a consumed TCP
|
|
19
|
+
# buffer, leading `IO.select` to think that the SSLSocket has no more
|
|
20
|
+
# data to provide, when that's not the case, effectively making
|
|
21
|
+
# `IO.select` block forever, even though the SSLSocket's buffer has
|
|
22
|
+
# not yet been consumed.
|
|
23
|
+
#
|
|
24
|
+
# We work around this by attempting a non-blocking read of one byte on
|
|
25
|
+
# each of the `read_array`, and putting the byte back with
|
|
26
|
+
# `Socket#ungetc` if it worked, or running it through the the real
|
|
27
|
+
# `IO.select` if it doesn't.
|
|
28
|
+
#
|
|
29
|
+
# @see http://bugs.ruby-lang.org/issues/8875
|
|
30
|
+
# @see http://jira.codehaus.org/browse/JRUBY-6874
|
|
31
|
+
# @param read_array [Array] (see IO.select)
|
|
32
|
+
# @param write_array [Array] (see IO.select)
|
|
33
|
+
# @param error_array [Array] (see IO.select)
|
|
34
|
+
# @param timeout [Fixnum,nil] (see IO.select)
|
|
35
|
+
#
|
|
36
|
+
# @return [Array] An Array containing three arrays of IO objects that
|
|
37
|
+
# are ready for reading, ready for writing, or have pending errors,
|
|
38
|
+
# respectively.
|
|
39
|
+
# @return [nil] If optional `timeout` is given and `timeout` seconds
|
|
40
|
+
# elapse before any data is available
|
|
41
|
+
def self.select(read_array=[], write_array=[], error_array=[], timeout=nil)
|
|
42
|
+
read_array ||= []
|
|
43
|
+
write_array ||= []
|
|
44
|
+
error_array ||= []
|
|
45
|
+
|
|
46
|
+
readers_with_data = []
|
|
47
|
+
|
|
48
|
+
selectable_readers = read_array.dup.delete_if do |reader|
|
|
49
|
+
begin
|
|
50
|
+
# If this socket doesn't have a read_nonblock method, then it's
|
|
51
|
+
# a server of some kind and we have to run it through the real
|
|
52
|
+
# select to see if it can {TCPServer#accept accept}.
|
|
53
|
+
next false unless reader.respond_to? :read_nonblock
|
|
54
|
+
|
|
55
|
+
byte = reader.read_nonblock(1)
|
|
56
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
|
57
|
+
# Then this thing needs to go through the real select to be able
|
|
58
|
+
# to tell if it has data.
|
|
59
|
+
#
|
|
60
|
+
# Note that {IO::WaitWritable} is needed here because OpenSSL
|
|
61
|
+
# sockets can block for writing when calling `read*` because of
|
|
62
|
+
# session renegotiation and the like.
|
|
63
|
+
false
|
|
64
|
+
rescue EOFError
|
|
65
|
+
# Then this thing has an empty read buffer and there's no more
|
|
66
|
+
# on the wire. We mark it as having data so a subsequent
|
|
67
|
+
# read or read_nonblock will raise EOFError appropriately.
|
|
68
|
+
readers_with_data << reader
|
|
69
|
+
true
|
|
70
|
+
else
|
|
71
|
+
# Then this thing has data already in its read buffer and we can
|
|
72
|
+
# skip the real select for it.
|
|
73
|
+
reader.ungetc(byte)
|
|
74
|
+
readers_with_data << reader
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if readers_with_data.any?
|
|
80
|
+
if selectable_readers.any? || write_array.any? || error_array.any?
|
|
81
|
+
#$stderr.puts(" ----- Selecting readers:")
|
|
82
|
+
#pp selectable_readers
|
|
83
|
+
# Then see if anything has data right now by using a 0 timeout
|
|
84
|
+
r,w,e = IO.select(selectable_readers, write_array, error_array, 0)
|
|
85
|
+
|
|
86
|
+
real = [
|
|
87
|
+
readers_with_data | (r || []),
|
|
88
|
+
w || [],
|
|
89
|
+
e || []
|
|
90
|
+
]
|
|
91
|
+
else
|
|
92
|
+
# Then there's nothing selectable and we can just return stuff
|
|
93
|
+
# that has buffered data
|
|
94
|
+
real = [ readers_with_data, [], [] ]
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
# Then wait the given timeout, regardless of whether the arrays
|
|
98
|
+
# are empty
|
|
99
|
+
real = IO.select(read_array, write_array, error_array, timeout)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
#$stderr.puts '------ RaptorIO::Socket.select result ------'
|
|
103
|
+
#pp real
|
|
104
|
+
return real
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class << self
|
|
108
|
+
|
|
109
|
+
# Captures Ruby exceptions and converts them to RaptorIO Errors.
|
|
110
|
+
#
|
|
111
|
+
# @param [Block] block Block to run.
|
|
112
|
+
def translate_errors( &block )
|
|
113
|
+
block.call
|
|
114
|
+
rescue ::Errno::EPIPE, ::Errno::ECONNRESET => e
|
|
115
|
+
raise RaptorIO::Socket::Error::BrokenPipe, e.to_s
|
|
116
|
+
rescue ::Errno::ECONNREFUSED => e
|
|
117
|
+
raise RaptorIO::Socket::Error::ConnectionRefused, e.to_s
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Delegates to `::Socket.getaddrinfo`.
|
|
121
|
+
def getaddrinfo( *args )
|
|
122
|
+
begin
|
|
123
|
+
::Socket.getaddrinfo( *args )
|
|
124
|
+
# OSX raises SocketError.
|
|
125
|
+
rescue ::SocketError, ::Errno::ENOENT => e
|
|
126
|
+
raise RaptorIO::Socket::Error::CouldNotResolve.new( e.to_s )
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Delegate to Ruby Socket.
|
|
131
|
+
def method_missing(meth, *args, &block)
|
|
132
|
+
#$stderr.puts("Socket.method_missing(#{meth}, #{args.inspect}")
|
|
133
|
+
if ::Socket.respond_to?(meth)
|
|
134
|
+
translate_errors do
|
|
135
|
+
::Socket.__send__(meth, *args, &block)
|
|
136
|
+
end
|
|
137
|
+
else
|
|
138
|
+
super
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def respond_to_missing?(meth, include_private=false)
|
|
143
|
+
::Socket.respond_to?(meth, include_private)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Options for this socket.
|
|
148
|
+
#
|
|
149
|
+
# @return [Hash<Symbol,Object>]
|
|
150
|
+
attr_accessor :options
|
|
151
|
+
|
|
152
|
+
# @!method to_io
|
|
153
|
+
# Used by Kernel.select
|
|
154
|
+
# @return [IO]
|
|
155
|
+
def_delegator :@socket, :to_io, :to_io
|
|
156
|
+
|
|
157
|
+
# @param socket [IO] An already-connected socket.
|
|
158
|
+
# @param options [Hash] Options (see {#options}).
|
|
159
|
+
def initialize( socket, options = {} )
|
|
160
|
+
@socket = socket
|
|
161
|
+
@options = options
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @!method closed?
|
|
165
|
+
def_delegator :@socket, :closed?, :closed?
|
|
166
|
+
|
|
167
|
+
# @!method close
|
|
168
|
+
def_delegator :@socket, :close, :close
|
|
169
|
+
|
|
170
|
+
# Whether this socket is an SSL stream.
|
|
171
|
+
def ssl?
|
|
172
|
+
false
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# -*- coding: binary -*-
|
|
2
|
+
require 'raptor-io/socket'
|
|
3
|
+
require 'ipaddr'
|
|
4
|
+
|
|
5
|
+
###
|
|
6
|
+
#
|
|
7
|
+
# Provides the basic interface that a derived class must implement
|
|
8
|
+
# in order to be a routable socket creator.
|
|
9
|
+
#
|
|
10
|
+
# See {RaptorIO::Socket::Comm::Local} for an implementation using sockets
|
|
11
|
+
# created with standard Ruby Socket classes.
|
|
12
|
+
#
|
|
13
|
+
# Subclasses must implement the following methods:
|
|
14
|
+
#
|
|
15
|
+
# * `resolve`
|
|
16
|
+
# * `create_tcp`
|
|
17
|
+
# * `create_tcp_server`
|
|
18
|
+
# * `create_udp`
|
|
19
|
+
# * `create_udp_server`
|
|
20
|
+
# * `support_ipv6?`
|
|
21
|
+
#
|
|
22
|
+
###
|
|
23
|
+
class RaptorIO::Socket::Comm
|
|
24
|
+
require 'raptor-io/socket/comm/local'
|
|
25
|
+
require 'raptor-io/socket/comm/socks'
|
|
26
|
+
require 'raptor-io/socket/comm/sapni'
|
|
27
|
+
|
|
28
|
+
# @param uri [URI]
|
|
29
|
+
def self.from_uri(uri, opts = {})
|
|
30
|
+
raise ArgumentError unless uri.kind_of? URI
|
|
31
|
+
|
|
32
|
+
prev_comm = opts[:prev_comm] || RaptorIO::Socket::Comm::Local.new
|
|
33
|
+
|
|
34
|
+
comm = case uri.scheme.downcase
|
|
35
|
+
when "sapni"
|
|
36
|
+
uri.port ||= 3299
|
|
37
|
+
RaptorIO::Socket::Comm::SAPNI.new(
|
|
38
|
+
sap_host: uri.host,
|
|
39
|
+
sap_port: uri.port,
|
|
40
|
+
sap_comm: prev_comm,
|
|
41
|
+
)
|
|
42
|
+
when "socks"
|
|
43
|
+
uri.port ||= 1080
|
|
44
|
+
RaptorIO::Socket::Comm::SOCKS.new(
|
|
45
|
+
socks_host: uri.host,
|
|
46
|
+
socks_port: uri.port,
|
|
47
|
+
socks_comm: prev_comm,
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
comm
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Creates a socket on this Comm based on the supplied uniform
|
|
55
|
+
# parameters.
|
|
56
|
+
#
|
|
57
|
+
# @option options :switch_board [SwitchBoard]
|
|
58
|
+
# @option options :port [Fixnum] Optional based on proto
|
|
59
|
+
# @option options :protocol [Symbol]
|
|
60
|
+
# * `:tcp`
|
|
61
|
+
# * `:udp`
|
|
62
|
+
#
|
|
63
|
+
# @return [RaptorIO::Socket]
|
|
64
|
+
def create( options )
|
|
65
|
+
options = options.dup
|
|
66
|
+
options[:peer_host] = IPAddr.parse(options[:peer_host])
|
|
67
|
+
|
|
68
|
+
case options.delete(:protocol)
|
|
69
|
+
when :tcp
|
|
70
|
+
options[:server] ? create_tcp_server(options) : create_tcp(options)
|
|
71
|
+
|
|
72
|
+
when :udp
|
|
73
|
+
options[:server] ? create_udp_server(options) : create_udp(options)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Resolves a hostname to an IP address using this comm.
|
|
78
|
+
#
|
|
79
|
+
# @abstract
|
|
80
|
+
#
|
|
81
|
+
# @param [String] hostname
|
|
82
|
+
def resolve( hostname )
|
|
83
|
+
raise NotImplementedError
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Resolves an IP address to a hostname using this comm.
|
|
87
|
+
#
|
|
88
|
+
# @abstract
|
|
89
|
+
#
|
|
90
|
+
# @param ip_address [String]
|
|
91
|
+
def reverse_resolve( ip_address )
|
|
92
|
+
raise NotImplementedError
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Connect to a host over TCP.
|
|
96
|
+
#
|
|
97
|
+
# @abstract
|
|
98
|
+
#
|
|
99
|
+
# @option options :peer_host [String,IPAddr]
|
|
100
|
+
# @option options :peer_port [Fixnum]
|
|
101
|
+
# @option options :local_host [String,IPAddr]
|
|
102
|
+
# @option options :local_port [Fixnum]
|
|
103
|
+
# @return [RaptorIO::Socket::TCP]
|
|
104
|
+
def create_tcp(options)
|
|
105
|
+
raise NotImplementedError
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Create a UDP socket bound to the given :peer_host
|
|
109
|
+
#
|
|
110
|
+
# @abstract
|
|
111
|
+
#
|
|
112
|
+
# @option options :peer_host [String,IPAddr]
|
|
113
|
+
# @option options :peer_port [Fixnum]
|
|
114
|
+
# @option options :local_host [String,IPAddr]
|
|
115
|
+
# @option options :local_port [Fixnum]
|
|
116
|
+
def create_udp(options)
|
|
117
|
+
raise NotImplementedError
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Create a TCP server listening on :local_port
|
|
121
|
+
#
|
|
122
|
+
# @abstract
|
|
123
|
+
#
|
|
124
|
+
# @option options :local_host [String,IPAddr]
|
|
125
|
+
# @option options :local_port [Fixnum]
|
|
126
|
+
# @option options :ssl_context [OpenSSL::SSL::Context]
|
|
127
|
+
def create_tcp_server(options)
|
|
128
|
+
raise NotImplementedError
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Create a UDP server listening on :local_port
|
|
132
|
+
#
|
|
133
|
+
# @abstract
|
|
134
|
+
#
|
|
135
|
+
# @option options :local_host [String,IPAddr]
|
|
136
|
+
# @option options :local_port [Fixnum]
|
|
137
|
+
def create_udp_server(options)
|
|
138
|
+
raise NotImplementedError
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
end
|
|
143
|
+
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# -*- coding: binary -*-
|
|
2
|
+
require 'timeout'
|
|
3
|
+
require 'socket'
|
|
4
|
+
require 'resolv'
|
|
5
|
+
|
|
6
|
+
# Local communication using Ruby `::Socket`s
|
|
7
|
+
class RaptorIO::Socket::Comm::Local < RaptorIO::Socket::Comm
|
|
8
|
+
|
|
9
|
+
# Determine whether we support IPv6
|
|
10
|
+
#
|
|
11
|
+
# We attempt to discover this by creating an unbound UDP socket with
|
|
12
|
+
# the AF_INET6 address family
|
|
13
|
+
def support_ipv6?
|
|
14
|
+
return @supports_ipv6 unless @supports_ipv6.nil?
|
|
15
|
+
|
|
16
|
+
@supports_ipv6 = false
|
|
17
|
+
|
|
18
|
+
if ::Socket.const_defined?('AF_INET6')
|
|
19
|
+
begin
|
|
20
|
+
::Socket.new(::Socket::AF_INET6, ::Socket::SOCK_DGRAM, ::Socket::IPPROTO_UDP).close
|
|
21
|
+
@supports_ipv6 = true
|
|
22
|
+
rescue
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@supports_ipv6
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Resolves a hostname to an IP address using this comm.
|
|
30
|
+
#
|
|
31
|
+
# @param hostname [String]
|
|
32
|
+
def resolve( hostname )
|
|
33
|
+
::Resolv.getaddress hostname
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Resolves an IP address to a hostname using this comm.
|
|
37
|
+
#
|
|
38
|
+
# @param [String] ip_address
|
|
39
|
+
def reverse_resolve( ip_address )
|
|
40
|
+
::Resolv.getname ip_address
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Connect to `:peer_host`
|
|
44
|
+
#
|
|
45
|
+
# @option (see Comm#create_tcp)
|
|
46
|
+
# @return [Socket::TCP]
|
|
47
|
+
# @raise [RaptorIO::Socket::Error::ConnectTimeout]
|
|
48
|
+
def create_tcp( options )
|
|
49
|
+
phost = IPAddr.parse( options[:peer_host] )
|
|
50
|
+
|
|
51
|
+
# Passing an explicit ::Socket::IPPROTO_TCP is broken on jruby
|
|
52
|
+
# See https://github.com/jruby/jruby/issues/785
|
|
53
|
+
socket = ::Socket.new(phost.family, ::Socket::SOCK_STREAM, 0)
|
|
54
|
+
socket.do_not_reverse_lookup = true
|
|
55
|
+
|
|
56
|
+
if options[:local_port] || options[:local_host]
|
|
57
|
+
socket.bind(::Socket.pack_sockaddr_in(options[:local_port], options[:local_host]))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
socket.connect_nonblock(::Socket.pack_sockaddr_in(options[:peer_port], phost.to_s))
|
|
62
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
|
63
|
+
raise RaptorIO::Socket::Error::ConnectionRefused
|
|
64
|
+
rescue Errno::EINPROGRESS
|
|
65
|
+
# This should almost always be raised with a call to
|
|
66
|
+
# connect_nonblock. When the socket finishes connecting it
|
|
67
|
+
# becomes available for writing.
|
|
68
|
+
res = select(nil, [socket], nil, options[:connect_timeout] || 2)
|
|
69
|
+
if res.nil?
|
|
70
|
+
raise RaptorIO::Socket::Error::ConnectionTimeout
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if options[:ssl_context]
|
|
75
|
+
RaptorIO::Socket::TCP::SSL.new(socket, options)
|
|
76
|
+
else
|
|
77
|
+
RaptorIO::Socket::TCP.new(socket, options)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Listen locally on `:local_port`
|
|
82
|
+
#
|
|
83
|
+
# @option (see Comm#create_tcp_server)
|
|
84
|
+
def create_tcp_server( options )
|
|
85
|
+
socket = TCPServer.new( options[:local_host], options[:local_port] )
|
|
86
|
+
|
|
87
|
+
if (options[:context] = options.delete(:ssl_context))
|
|
88
|
+
RaptorIO::Socket::TCPServer::SSL.new( socket, options )
|
|
89
|
+
else
|
|
90
|
+
RaptorIO::Socket::TCPServer.new( socket, options )
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|