tcp_timeout 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|