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.
@@ -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
@@ -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.
@@ -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
+ ```
@@ -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
@@ -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: []