tcp_timeout 0.1.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 +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +39 -0
- data/lib/tcp_timeout.rb +163 -0
- data/tcp_timeout.gemspec +18 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7d9724ad60f2d4c559625ebe47208f5b80049a95
|
4
|
+
data.tar.gz: 391eab9bc46bcf87a44fbacd2521b576ae95d4ae
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2b2bc9187ae35e53c37088bfb37086ebe007aec67b8d2483174eab9bc4815c532351fb08a79c37a5a05b25639c774c1b9508b5864800beb5faf2a61fa96caeea
|
7
|
+
data.tar.gz: 64dfb308baacce68080840119a9b5edd63ed0cfe2a40268c5b5ecb47aec789b00c09acf5f502224158108c8f0503cd5d36069085bedfc2aa950809ac03f0d53b
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Lann Martin
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# TCPTimeout
|
2
|
+
|
3
|
+
A wrapper around Ruby Sockets providing timeouts for connect, write, and read
|
4
|
+
operations using `Socket#*_nonblock` methods and `IO.select` instead of
|
5
|
+
`Timeout.timeout`.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
`gem install tcp_timeout`
|
10
|
+
|
11
|
+
Pass one or more of `:connect_timeout`, `:write_timeout`, and `:read_timeout`
|
12
|
+
as options to TCPTimeout::TCPSocket.new. If a timeout is omitted or nil, that
|
13
|
+
operation will behave as a normal Socket would. On timeout, a
|
14
|
+
`TCPTimeout::SocketTimeout` (subclass of `SocketError`) will be raised.
|
15
|
+
|
16
|
+
When calling `#read` with a byte length it is possible for it to read some data
|
17
|
+
before timing out. If you need to avoid losing this data you can pass a buffer
|
18
|
+
string which will receive the data even after a timeout.
|
19
|
+
|
20
|
+
Other options:
|
21
|
+
|
22
|
+
- `:family` - set the address family for the connection, e.g. `:INET` or `:INET6`
|
23
|
+
- `:local_host` and `:local_port` - the host and port to bind to
|
24
|
+
|
25
|
+
TCPTimeout::TCPSocket supports only a subset of IO methods, including:
|
26
|
+
|
27
|
+
```close closed? read read_nonblock readbyte readpartial write write_nonblock```
|
28
|
+
|
29
|
+
**Example:**
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
begin
|
33
|
+
sock = TCPTimeout::TCPSocket.new(host, port, connect_timeout: 10, write_timeout: 9)
|
34
|
+
sock.write('data')
|
35
|
+
sock.close
|
36
|
+
rescue TCPTimeout::SocketTimeout
|
37
|
+
puts "Operation timed out!"
|
38
|
+
end
|
39
|
+
```
|
data/lib/tcp_timeout.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module TCPTimeout
|
4
|
+
VERSION = "0.1.1"
|
5
|
+
|
6
|
+
DELEGATED_METHODS = %w[
|
7
|
+
close closed?
|
8
|
+
getsockopt setsockopt
|
9
|
+
local_address remote_address
|
10
|
+
read_nonblock wrote_nonblock
|
11
|
+
fileno
|
12
|
+
]
|
13
|
+
|
14
|
+
class SocketTimeout < SocketError; end
|
15
|
+
|
16
|
+
class TCPSocket
|
17
|
+
DELEGATED_METHODS.each do |method|
|
18
|
+
class_eval(<<-EVAL, __FILE__, __LINE__)
|
19
|
+
def #{method}(*args)
|
20
|
+
@socket.__send__(:#{method}, *args)
|
21
|
+
end
|
22
|
+
EVAL
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(host, port, opts = {})
|
26
|
+
@connect_timeout = opts[:connect_timeout]
|
27
|
+
@write_timeout = opts[:write_timeout]
|
28
|
+
@read_timeout = opts[:read_timeout]
|
29
|
+
|
30
|
+
family = opts[:family] || Socket::AF_INET
|
31
|
+
address = Socket.getaddrinfo(host, nil, family).first[3]
|
32
|
+
@sockaddr = Socket.pack_sockaddr_in(port, address)
|
33
|
+
|
34
|
+
@socket = Socket.new(family, Socket::SOCK_STREAM, 0)
|
35
|
+
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
36
|
+
|
37
|
+
local_host = opts[:local_host]
|
38
|
+
local_port = opts[:local_port]
|
39
|
+
if local_host || local_port
|
40
|
+
local_host ||= ''
|
41
|
+
local_address = Socket.getaddrinfo(local_host, nil, family).first[3]
|
42
|
+
local_sockaddr = Socket.pack_sockaddr_in(local_port, local_address)
|
43
|
+
@socket.bind(local_sockaddr)
|
44
|
+
end
|
45
|
+
|
46
|
+
connect
|
47
|
+
end
|
48
|
+
|
49
|
+
def connect
|
50
|
+
return @socket.connect(@sockaddr) unless @connect_timeout
|
51
|
+
|
52
|
+
begin
|
53
|
+
@socket.connect_nonblock(@sockaddr)
|
54
|
+
rescue Errno::EINPROGRESS
|
55
|
+
select_timeout(:connect, @connect_timeout)
|
56
|
+
# If there was a failure this will raise an Error
|
57
|
+
begin
|
58
|
+
@socket.connect_nonblock(@sockaddr)
|
59
|
+
rescue Errno::EISCONN
|
60
|
+
# Successfully connected
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def write(data, timeout = nil)
|
66
|
+
timeout ||= @write_timeout
|
67
|
+
return @socket.write(data) unless timeout
|
68
|
+
|
69
|
+
length = data.bytesize
|
70
|
+
|
71
|
+
total_count = 0
|
72
|
+
loop do
|
73
|
+
begin
|
74
|
+
count = @socket.write_nonblock(data)
|
75
|
+
rescue Errno::EWOULDBLOCK
|
76
|
+
timeout = select_timeout(:write, timeout)
|
77
|
+
retry
|
78
|
+
end
|
79
|
+
|
80
|
+
total_count += count
|
81
|
+
return total_count if total_count >= length
|
82
|
+
data = data.byteslice(count..-1)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def read(length = nil, *args)
|
87
|
+
raise ArgumentError, 'too many arguments' if args.length > 2
|
88
|
+
|
89
|
+
timeout = (args.length > 1) ? args.pop : @read_timeout
|
90
|
+
return @socket.read(length, *args) unless length > 0 && timeout
|
91
|
+
|
92
|
+
buffer = args.first || ''.force_encoding(Encoding::ASCII_8BIT)
|
93
|
+
|
94
|
+
begin
|
95
|
+
# Drain internal buffers
|
96
|
+
@socket.read_nonblock(length, buffer)
|
97
|
+
return buffer if buffer.bytesize >= length
|
98
|
+
rescue Errno::EWOULDBLOCK
|
99
|
+
# Internal buffers were empty
|
100
|
+
buffer.clear
|
101
|
+
rescue EOFError
|
102
|
+
return nil
|
103
|
+
end
|
104
|
+
|
105
|
+
@chunk ||= ''.force_encoding(Encoding::ASCII_8BIT)
|
106
|
+
|
107
|
+
loop do
|
108
|
+
timeout = select_timeout(:read, timeout)
|
109
|
+
|
110
|
+
begin
|
111
|
+
@socket.read_nonblock(length, @chunk)
|
112
|
+
rescue Errno::EWOULDBLOCK
|
113
|
+
retry
|
114
|
+
rescue EOFError
|
115
|
+
return buffer.empty? ? nil : buffer
|
116
|
+
end
|
117
|
+
buffer << @chunk
|
118
|
+
|
119
|
+
if length
|
120
|
+
length -= @chunk.bytesize
|
121
|
+
return buffer if length <= 0
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def readpartial(length, *args)
|
127
|
+
raise ArgumentError, 'too many arguments' if args.length > 2
|
128
|
+
|
129
|
+
timeout = (args.length > 1) ? args.pop : @read_timeout
|
130
|
+
return @socket.readpartial(length, *args) unless length > 0 && timeout
|
131
|
+
|
132
|
+
begin
|
133
|
+
@socket.read_nonblock(length, *args)
|
134
|
+
rescue Errno::EWOULDBLOCK
|
135
|
+
timeout = select_timeout(:read, timeout)
|
136
|
+
retry
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def readbyte
|
141
|
+
readpartial(1).ord
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def select_timeout(type, timeout)
|
147
|
+
if timeout >= 0
|
148
|
+
if type == :read
|
149
|
+
read_array = [@socket]
|
150
|
+
else
|
151
|
+
write_array = [@socket]
|
152
|
+
end
|
153
|
+
|
154
|
+
start = Time.now
|
155
|
+
if IO.select(read_array, write_array, [@socket], timeout)
|
156
|
+
waited = Time.now - start
|
157
|
+
return timeout - waited
|
158
|
+
end
|
159
|
+
end
|
160
|
+
raise SocketTimeout, "#{type} timeout"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
data/tcp_timeout.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tcp_timeout'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "tcp_timeout"
|
8
|
+
spec.version = TCPTimeout::VERSION
|
9
|
+
spec.authors = ["Lann Martin"]
|
10
|
+
spec.email = ["tcptimeoutgem@lannbox.com"]
|
11
|
+
spec.summary = "TCPSocket proxy with select-based timeouts"
|
12
|
+
spec.description = "Wraps Socket, providing timeouts for connect, write, and read without Timeout.timeout."
|
13
|
+
spec.homepage = "https://github.com/lann/tcp-timeout-ruby"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tcp_timeout
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lann Martin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-05-15 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Wraps Socket, providing timeouts for connect, write, and read without
|
14
|
+
Timeout.timeout.
|
15
|
+
email:
|
16
|
+
- tcptimeoutgem@lannbox.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- LICENSE.txt
|
22
|
+
- README.md
|
23
|
+
- lib/tcp_timeout.rb
|
24
|
+
- tcp_timeout.gemspec
|
25
|
+
homepage: https://github.com/lann/tcp-timeout-ruby
|
26
|
+
licenses:
|
27
|
+
- MIT
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 2.0.0
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: TCPSocket proxy with select-based timeouts
|
49
|
+
test_files: []
|