http_spew 0.1.0

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.
data/ChangeLog ADDED
@@ -0,0 +1,170 @@
1
+ ChangeLog from http://bogomips.org/http_spew.git
2
+
3
+ commit 32223af63dc5296cd09f2472ad60263d98f379a2
4
+ Author: Eric Wong <normalperson@yhbt.net>
5
+ Date: Thu Feb 24 08:28:34 2011 +0000
6
+
7
+ HTTP spew 0.1.0 - initial release
8
+
9
+ Might as well...
10
+
11
+ commit e11489549eefff5ae031f2127bc72efe8187f938
12
+ Author: Eric Wong <normalperson@yhbt.net>
13
+ Date: Thu Feb 24 08:48:14 2011 +0000
14
+
15
+ fixes for Ruby 1.9.3dev
16
+
17
+ 1.9.3dev got louder about warnings and more careful about
18
+ shared strings.
19
+
20
+ commit aefc248decd16db8098fef6148ed825e83bebfc1
21
+ Author: Eric Wong <normalperson@yhbt.net>
22
+ Date: Thu Feb 10 13:48:15 2011 -0800
23
+
24
+ test/helper: cleanup fifo after using it
25
+
26
+ No need to flood our $TMPDIR :x
27
+
28
+ commit 5d23e362ee382d57672bf4f21563c94d3b0bca03
29
+ Author: Eric Wong <normalperson@yhbt.net>
30
+ Date: Thu Feb 10 10:44:21 2011 +0000
31
+
32
+ hit_n_run: an even more terrible HTTP requester
33
+
34
+ It just requests and never cares for a response!
35
+
36
+ commit cf5b6c3b0664471f3fbd384c50d940d2f6c2781a
37
+ Author: Eric Wong <normalperson@yhbt.net>
38
+ Date: Thu Feb 10 10:25:39 2011 +0000
39
+
40
+ add Content-MD5 input filter support
41
+
42
+ This means we can make requests with Content-MD5 trailers
43
+
44
+ commit 20f5a767769aabee3b26ae05c9d8c9532fa84b16
45
+ Author: Eric Wong <normalperson@yhbt.net>
46
+ Date: Thu Feb 10 10:23:35 2011 +0000
47
+
48
+ chunky_pipe: error transfer between threads
49
+
50
+ Calling IO#close is racy with threads, so we transfer
51
+ the error over to the reader if the writer has to error
52
+ out.
53
+
54
+ commit 9f97b6012ab0e777adfc342474bb95b5a55f9643
55
+ Author: Eric Wong <normalperson@yhbt.net>
56
+ Date: Thu Feb 10 10:21:27 2011 +0000
57
+
58
+ wait: do not poll if pollset is empty
59
+
60
+ No point :P
61
+
62
+ commit a9f76e7e7d78d4d2313819e43c486ecd33cc998a
63
+ Author: Eric Wong <normalperson@yhbt.net>
64
+ Date: Thu Feb 10 10:21:00 2011 +0000
65
+
66
+ do not clobber existing error when assigning
67
+
68
+ commit 711b3503dbcbe08850595796824b84eaf32ba4c4
69
+ Author: Eric Wong <normalperson@yhbt.net>
70
+ Date: Thu Feb 10 08:54:29 2011 +0000
71
+
72
+ split out chunky_pipe into it's own module
73
+
74
+ It should be top-level and it's a good name, too :D
75
+
76
+ commit bbb83625d3197d321c4fe4526e0fcdb80ddcc1b9
77
+ Author: Eric Wong <normalperson@yhbt.net>
78
+ Date: Thu Feb 10 08:37:43 2011 +0000
79
+
80
+ input_splitter -> input_spray, add tests
81
+
82
+ It seems to work alright in a unit test environment
83
+
84
+ commit fb0d48b2ccfced833f879e592d43700d40473c77
85
+ Author: Eric Wong <normalperson@yhbt.net>
86
+ Date: Tue Feb 8 19:46:31 2011 -0800
87
+
88
+ add untested input_splitter
89
+
90
+ It'll be useful for splitting out inputs into different
91
+ requests and processing them in parallel.
92
+
93
+ commit 89e2b2b90136525d7e7905b3e1fa0e711398f4b3
94
+ Author: Eric Wong <normalperson@yhbt.net>
95
+ Date: Tue Feb 8 18:59:32 2011 -0800
96
+
97
+ cleanup request handling of input
98
+
99
+ Remove unneeded Fiber dependency.
100
+
101
+ commit 9421276c001adbd8904366e92fe6181ea925a2db
102
+ Author: Eric Wong <normalperson@yhbt.net>
103
+ Date: Tue Feb 8 18:59:01 2011 -0800
104
+
105
+ test/helper: properly handle worker_processes != 4
106
+
107
+ Oops, we're about to start using it
108
+
109
+ commit 8a5b2fac7f448c35507cb081178ad9027843b335
110
+ Author: Eric Wong <normalperson@yhbt.net>
111
+ Date: Tue Feb 8 18:16:01 2011 -0800
112
+
113
+ HTTP_Spew.wait retries requests on EINTR
114
+
115
+ We may not have rv set yet.
116
+
117
+ commit 4809a6b7a97a0087899107820a93d70283c9502a
118
+ Author: Eric Wong <normalperson@yhbt.net>
119
+ Date: Tue Feb 8 18:13:40 2011 -0800
120
+
121
+ add wait_nonblock! and fix wait return values
122
+
123
+ Like Process.waitall, we want to wait on and return
124
+ all running requests possible.
125
+
126
+ commit a898603166528d6eaebcf151008ccd99b01ea115
127
+ Author: Eric Wong <normalperson@yhbt.net>
128
+ Date: Tue Feb 8 18:13:17 2011 -0800
129
+
130
+ request: document close method
131
+
132
+ We don't want to think it's unused.
133
+
134
+ commit 27696c3947f2339a8f708377ba5618044207340a
135
+ Author: Eric Wong <normalperson@yhbt.net>
136
+ Date: Tue Feb 8 18:12:07 2011 -0800
137
+
138
+ request: optimize string requests by avoiding Fiber
139
+
140
+ No need for Fiber in some cases...
141
+
142
+ commit 10283cdff905c4af58e633ef052d3823deba6db5
143
+ Author: Eric Wong <normalperson@yhbt.net>
144
+ Date: Tue Feb 8 18:11:23 2011 -0800
145
+
146
+ test_upload: switch this test to use a temporary file
147
+
148
+ We don't want to use too much memory
149
+
150
+ commit 5312aeb04ae29a6187f39cc655356f1b42402fe0
151
+ Author: Eric Wong <normalperson@yhbt.net>
152
+ Date: Tue Feb 8 18:10:14 2011 -0800
153
+
154
+ test/helper: allow worker_processes to be specified
155
+
156
+ We want to disable parallelization sometimes
157
+
158
+ commit 3a2e1ac5d3679bfa44056a67118034da50c948d1
159
+ Author: Eric Wong <normalperson@yhbt.net>
160
+ Date: Tue Feb 8 15:03:28 2011 -0800
161
+
162
+ switch from IO.select to to Kgio.poll
163
+
164
+ No high descriptor limits anymore
165
+
166
+ commit c25326ff75c1e7da0c8e366ce46c84aea6ad094b
167
+ Author: Eric Wong <normalperson@yhbt.net>
168
+ Date: Mon Feb 7 08:51:15 2011 +0000
169
+
170
+ initial commit
data/GIT-VERSION-FILE ADDED
@@ -0,0 +1 @@
1
+ GIT_VERSION = 0.1.0
data/GIT-VERSION-GEN ADDED
@@ -0,0 +1,40 @@
1
+ #!/bin/sh
2
+
3
+ GVF=GIT-VERSION-FILE
4
+ DEF_VER=v0.1.0.GIT
5
+
6
+ LF='
7
+ '
8
+
9
+ # First see if there is a version file (included in release tarballs),
10
+ # then try git-describe, then default.
11
+ if test -f version
12
+ then
13
+ VN=$(cat version) || VN="$DEF_VER"
14
+ elif test -d .git -o -f .git &&
15
+ VN=$(git describe --abbrev=4 HEAD 2>/dev/null) &&
16
+ case "$VN" in
17
+ *$LF*) (exit 1) ;;
18
+ v[0-9]*)
19
+ git update-index -q --refresh
20
+ test -z "$(git diff-index --name-only HEAD --)" ||
21
+ VN="$VN-dirty" ;;
22
+ esac
23
+ then
24
+ VN=$(echo "$VN" | sed -e 's/-/./g');
25
+ else
26
+ VN="$DEF_VER"
27
+ fi
28
+
29
+ VN=$(expr "$VN" : v*'\(.*\)')
30
+
31
+ if test -r $GVF
32
+ then
33
+ VC=$(sed -e 's/^GIT_VERSION = //' <$GVF)
34
+ else
35
+ VC=unset
36
+ fi
37
+ test "$VN" = "$VC" || {
38
+ echo >&2 "GIT_VERSION = $VN"
39
+ echo "GIT_VERSION = $VN" >$GVF
40
+ }
data/GNUmakefile ADDED
@@ -0,0 +1,5 @@
1
+ all::
2
+ RSYNC_DEST := bogomips.org:/srv/bogomips/http_spew
3
+ rfproject := rainbows
4
+ rfpackage := http_spew
5
+ include pkg.mk
data/LATEST ADDED
@@ -0,0 +1,4 @@
1
+ === HTTP spew 0.1.0 - initial release / 2011-02-24 08:50 UTC
2
+
3
+ Might as well...
4
+
data/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ HTTP Spew is copyrighted Free Software by all contributors, see logs in
2
+ revision control for names and email addresses of all of them.
3
+
4
+ You can redistribute it and/or modify it under the terms of the GNU
5
+ General Public License, version 2 *or* 3 ({GPLv3}[link:COPYING]) as
6
+ published by the Free Software Foundation. The project leader
7
+ (Eric Wong) reserves the right to relicense HTTP Spew under future versions
8
+ of the GPL.
9
+
10
+ HTTP Spew is distributed in the hope that it will be useful, but WITHOUT
11
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
13
+ License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with HTTP Spew; if not, write to the Free Software Foundation, Inc.,
17
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
data/NEWS ADDED
@@ -0,0 +1,4 @@
1
+ === HTTP spew 0.1.0 - initial release / 2011-02-24 08:50 UTC
2
+
3
+ Might as well...
4
+
data/README ADDED
@@ -0,0 +1,61 @@
1
+ = HTTP Spew - LAN-only HTTP spam^H^H^H^Hclient for Ruby
2
+
3
+ Do not use HTTP Spew for connecting to servers outside of your LAN.
4
+ Do not use HTTP Spew without the permission of your server admins.
5
+ Use HTTP Spew if you wish you could kinda multicast with HTTP...
6
+
7
+ == Problems
8
+
9
+ * No support for bidirectional HTTP streaming
10
+ * No support for "Expect: 100-continue" headers/responses
11
+ * No support for DNS resolution (WONTFIX, ever)
12
+ * No support for HTTPS
13
+ * No support for keepalive (yet?)
14
+ * No support for Ruby 1.8, this is Ruby 1.9-only
15
+ * Not remotely RFC-compliant
16
+ * Messes up analytics/reporting on servers
17
+ * Resets server connections
18
+ * May get you banned from the Internet!
19
+
20
+ == Still Interested?
21
+
22
+ HTTP Spew lets you fire off identical (or similar) requests to multiple
23
+ HTTP servers and wait for any number (or all) of them to complete or for
24
+ a timeout to expire.
25
+
26
+ HTTP Spew may be useful for implementing reverse proxy servers if you
27
+ 1) can't decide on a load balancing strategy for them
28
+ 2) have much more hardware than you actually use
29
+
30
+ It's also completely untested and unused anywhere!
31
+
32
+ == Hacking
33
+
34
+ You can get the latest source via git from the following locations:
35
+
36
+ git://bogomips.org/http_spew.git
37
+ git://repo.or.cz/http_spew.git (mirror)
38
+
39
+ You may browse the code from the web and download the latest snapshot
40
+ tarballs here:
41
+
42
+ * http://bogomips.org/http_spew.git (cgit)
43
+ * http://repo.or.cz/w/http_spew.git (gitweb)
44
+
45
+ Inline patches (from "git format-patch") to the mailing list are
46
+ preferred because they allow code review and comments in the reply to
47
+ the patch.
48
+
49
+ We will adhere to mostly the same conventions for patch submissions as
50
+ git itself. See the Documentation/SubmittingPatches document
51
+ distributed with git on on patch submission guidelines to follow. Just
52
+ don't email the git mailing list or maintainer with http_spew patches.
53
+
54
+ == Contact
55
+
56
+ All feedback (bug reports, user/development discussion, patches, pull
57
+ requests) go to the mailing list: mailto:http.spew@librelist.org
58
+
59
+ Mailing list archives in mbox format may be downloaded here:
60
+
61
+ http://bogomips.org/http_spew/archives/
data/http_spew.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ ENV["VERSION"] or abort "VERSION= must be specified"
2
+ manifest = File.readlines('.manifest').map! { |x| x.chomp! }
3
+ require 'wrongdoc'
4
+ extend Wrongdoc::Gemspec
5
+ name, summary, title = readme_metadata
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = %q{http_spew}
9
+ s.version = ENV["VERSION"].dup
10
+ s.authors = ["HTTP Spew hackers"]
11
+ s.date = Time.now.utc.strftime('%Y-%m-%d')
12
+ s.description = readme_description
13
+ s.email = %q{http.spew@librelist.org}
14
+ s.extra_rdoc_files = extra_rdoc_files(manifest)
15
+ s.files = manifest
16
+ s.homepage = Wrongdoc.config[:rdoc_url]
17
+ s.summary = summary
18
+ s.rdoc_options = rdoc_options
19
+ s.rubyforge_project = %q{rainbows}
20
+ s.test_files = Dir["test/test_*.rb"]
21
+ s.add_dependency(%q<kcar>, "~> 0.1.2")
22
+ s.add_dependency(%q<kgio>, "~> 2.3")
23
+ s.add_development_dependency(%q<wrongdoc>, "~> 1.5")
24
+ end
@@ -0,0 +1,12 @@
1
+ # -*- encoding: binary -*-
2
+ #
3
+ # HTTP Spew blows chunks (at HTTP servers)!
4
+ class HTTP_Spew::ChunkyPipe < Kgio::Pipe
5
+ attr_accessor :error
6
+
7
+ # makes read behave like readpartial without EOFError
8
+ def read(*args)
9
+ defined?(@error) and raise @error
10
+ kgio_read(*args)
11
+ end
12
+ end
@@ -0,0 +1,55 @@
1
+ # -*- encoding: binary -*-
2
+ require "digest/md5"
3
+ module HTTP_Spew::ContentMD5
4
+ class MismatchError < HTTP_Spew::Error
5
+ end
6
+ class LengthError < HTTP_Spew::Error
7
+ end
8
+
9
+ def self.input(env)
10
+ if trailer = env["HTTP_TRAILER"]
11
+ have_md5 = trailer.split(/\s*,\s*/).grep(/\AContent-MD5\z/i)[0]
12
+ return env["rack.input"] if have_md5 && env["HTTP_CONTENT_MD5"]
13
+ end
14
+ if trailer
15
+ unless have_md5
16
+ trailer << (trailer.empty? ? "Content-MD5" : ",Content-MD5")
17
+ end
18
+ else
19
+ env["HTTP_TRAILER"] = "Content-MD5"
20
+ end
21
+ env["HTTP_TRANSFER_ENCODING"] = "chunked"
22
+ rd, wr = HTTP_Spew::ChunkyPipe.new
23
+ md5 = env.delete("HTTP_CONTENT_MD5")
24
+ len = env.delete("CONTENT_LENGTH")
25
+ start_write_driver(env["rack.input"], rd, wr, md5, len)
26
+ rd
27
+ end
28
+
29
+ def self.start_write_driver(input, rd, wr, expect_md5, expect_len)
30
+ Thread.new do
31
+ begin
32
+ digest, buf, bytes = Digest::MD5.new, "", 0
33
+ while input.read(0x4000, buf)
34
+ n = buf.size
35
+ bytes += n
36
+ wr.kgio_write("#{n.to_s(16)}\r\n")
37
+ digest.update(buf)
38
+ wr.kgio_write(buf << "\r\n")
39
+ end
40
+ if expect_len && expect_len.to_i != bytes
41
+ raise LengthError, "expect=#{expect_len} != got=#{bytes}"
42
+ end
43
+ digest = [ digest.digest ].pack("m").strip!
44
+ if expect_md5 && expect_md5.strip != digest
45
+ raise MismatchError, "expect=#{expect_md5} != got=#{digest}"
46
+ end
47
+ wr.write "0\r\nContent-MD5: #{digest}\r\n\r\n"
48
+ rescue => e
49
+ rd.error = e
50
+ ensure
51
+ wr.close
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,43 @@
1
+ # -*- encoding: binary -*-
2
+ module HTTP_Spew::Headers
3
+ # :stopdoc:
4
+ TR = %w(- _)
5
+ REQUEST_METHOD = "REQUEST_METHOD"
6
+ REQUEST_URI = "REQUEST_URI"
7
+ CRLF = "\r\n"
8
+ QUERY_STRING = "QUERY_STRING"
9
+ PATH_INFO = "PATH_INFO"
10
+ CONTENT_TYPE = "CONTENT_TYPE" # specified by Rack to be !/^HTTP_/
11
+ # :startdoc:
12
+
13
+ def request_uri(env)
14
+ qs = env[QUERY_STRING]
15
+ qs.size == 0 ? env[PATH_INFO] : "#{env[PATH_INFO]}?#{qs}"
16
+ end
17
+ module_function :request_uri
18
+
19
+ def env_to_headers(env, input)
20
+ req = "#{env[REQUEST_METHOD]} " \
21
+ "#{env[REQUEST_URI] || request_uri(env)} HTTP/1.1\r\n" \
22
+ "Connection: close\r\n"
23
+ uscore, dash = *TR
24
+ env.each do |key,value|
25
+ %r{\AHTTP_(\w+)\z} =~ key or next
26
+ key = $1
27
+ %r{\A(?:VERSION|EXPECT|TRANSFER_ENCODING|CONNECTION|KEEP_ALIVE)\z}x =~
28
+ key and next
29
+
30
+ key.tr!(uscore, dash)
31
+ req << "#{key}: #{value}\r\n"
32
+ end
33
+ if input
34
+ req << (input.respond_to?(:size) ?
35
+ "Content-Length: #{input.size}\r\n" :
36
+ "Transfer-Encoding: chunked\r\n")
37
+ ct = env[CONTENT_TYPE] and req << "Content-Type: #{ct}\r\n"
38
+ end
39
+ req << CRLF
40
+ String === input ? (req << input) : [ req, input ]
41
+ end
42
+ module_function :env_to_headers
43
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # An even more horrible way to make HTTP requests, just make them without
4
+ # reading a response!
5
+ class HTTP_Spew::HitNRun < HTTP_Spew::Request
6
+
7
+ # frozen response
8
+ RESPONSE = [ 666,
9
+ {
10
+ "Content-Length" => "0".freeze,
11
+ "Content-Type" => "hit/run".freeze
12
+ }.freeze,
13
+ [].freeze ].freeze
14
+
15
+ def read_response
16
+ close
17
+ RESPONSE
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ # -*- encoding: binary -*-
2
+ #
3
+ # Use this to wrap and replace your input object for spraying to multiple
4
+ # servers.
5
+ class HTTP_Spew::InputSpray
6
+ attr_reader :readers
7
+ class NoWritersError < HTTP_Spew::Error
8
+ end
9
+
10
+ class SizedPipe < HTTP_Spew::ChunkyPipe
11
+ attr_accessor :size
12
+ end
13
+
14
+ def initialize(env, nr)
15
+ @input = env["rack.input"]
16
+ size = @input.respond_to?(:size) ? @input.size : env["CONTENT_LENGTH"]
17
+ size = size ? size.to_i : nil
18
+ klass = size ? SizedPipe : HTTP_Spew::ChunkyPipe
19
+ @readers, @writers = [], []
20
+ nr.times do
21
+ r, w = klass.new
22
+ r.size = size if size
23
+ @readers << r
24
+ @writers << w
25
+ end
26
+ @wr = start_write_driver
27
+ end
28
+
29
+ def write_fail?(wr, buf)
30
+ wr.kgio_write(buf)
31
+ false
32
+ rescue
33
+ wr.close
34
+ true
35
+ end
36
+
37
+ # TODO: splice(2) if @input is an IO
38
+ def start_write_driver
39
+ Thread.new do
40
+ begin
41
+ buf = ""
42
+ while buf = @input.read(0x4000, buf)
43
+ @writers.delete_if { |wr| write_fail?(wr, buf) }.empty? and
44
+ raise NoWritersError, "all writers have died"
45
+ end
46
+ rescue => e
47
+ @readers.each { |io| io.error = e }
48
+ ensure
49
+ @writers.each { |io| io.close unless io.closed? }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,77 @@
1
+ # -*- encoding: binary -*-
2
+ class HTTP_Spew::Request
3
+ attr_reader :to_io
4
+ attr_reader :error
5
+ attr_reader :response
6
+ class RequestError < HTTP_Spew::Error
7
+ end
8
+
9
+ include HTTP_Spew::Headers
10
+
11
+ def initialize(env, input, sock)
12
+ @to_io = Kgio::SocketMethods === sock ? sock : Kgio::Socket.start(sock)
13
+ if Hash === env
14
+ @buf, @input = env_to_headers(env, input)
15
+ else
16
+ @buf, @input = env, input
17
+ end
18
+ end
19
+
20
+ # returns a 3-element Rack response array on completion
21
+ # returns :wait_readable or :wait_writable if busy
22
+ def resume
23
+ if @buf
24
+ case rv = @to_io.kgio_trywrite(@buf)
25
+ when String
26
+ @buf = rv # loop retry, socket buffer could've expanded
27
+ when Symbol
28
+ return rv
29
+ else # done writing, read more
30
+ @buf = @input ? @input.read(0x4000, @buf) : nil
31
+ end while @buf
32
+ read_response
33
+ else
34
+ read_response
35
+ end
36
+ end
37
+
38
+ def read_response
39
+ buf = @to_io.kgio_trypeek(0x4000) or eof!
40
+ String === buf or return buf
41
+
42
+ # Kcar::Parser#headers shortens +buf+ for us
43
+ hdr_len = buf.size
44
+ r = Kcar::Parser.new.headers({}, buf) or too_big!
45
+
46
+ # discard the header data from the socket buffer
47
+ (hdr_len -= buf.size) > 0 and @to_io.kgio_read(hdr_len, buf)
48
+ r[2] = self
49
+ @response = r
50
+ end
51
+
52
+ def to_path
53
+ "/dev/fd/#{@to_io.fileno}"
54
+ end
55
+
56
+ def too_big!
57
+ raise RequestError.new(self), "response headers too large", []
58
+ end
59
+
60
+ def each
61
+ buf = ""
62
+ while buf = @to_io.kgio_read(0x4000, buf)
63
+ yield buf
64
+ end
65
+ end
66
+
67
+ def error=(exception)
68
+ close
69
+ @error = exception
70
+ end
71
+
72
+ # this may be called by a Rack web server
73
+ def close
74
+ @to_io.close
75
+ IO === @input and @input.close
76
+ end
77
+ end
data/lib/http_spew.rb ADDED
@@ -0,0 +1,97 @@
1
+ # -*- encoding: binary -*-
2
+ require "kgio"
3
+ require "kcar"
4
+
5
+ module HTTP_Spew
6
+ autoload :ChunkyPipe, "http_spew/chunky_pipe"
7
+ autoload :ContentMD5, "http_spew/content_md5"
8
+ autoload :HitNRun, "http_spew/hit_n_run"
9
+ autoload :InputSpray, "http_spew/input_spray"
10
+
11
+ class Error < RuntimeError; end
12
+ class TimeoutError < Error; end
13
+ class ConnectionReset < Error; end
14
+
15
+ def self.error_all(requests, error) # :nodoc:
16
+ requests.each { |req| req.error ||= error }
17
+ end
18
+
19
+ def self.done_early(ready, failed, requests) # :nodoc:
20
+ ready.concat(failed)
21
+ pending = requests - ready
22
+ unless pending.empty?
23
+ error = ConnectionReset.new("prematurely terminated")
24
+ ready.concat(error_all(pending, error))
25
+ end
26
+ ready.uniq!
27
+ ready
28
+ end
29
+
30
+ # Returns an array of requests that are complete, including those
31
+ # that have errored out. Incomplete requests remain in +requests+
32
+ # If +need+ is fullfilled, it closes all incomplete requests and
33
+ # returns all requests.
34
+ def self.wait_nonblock!(need, requests)
35
+ ready, failed = [], []
36
+ requests.delete_if do |req|
37
+ begin
38
+ case req.resume
39
+ when Symbol # :wait_writable, :wait_readable
40
+ false
41
+ else
42
+ (ready << req).size == need and
43
+ return done_early(ready, failed, requests)
44
+ true
45
+ end
46
+ rescue => e
47
+ req.error = e
48
+ failed << req
49
+ end
50
+ end
51
+ ready.concat(failed).empty? ? nil : ready
52
+ end
53
+
54
+ # Returns an array of requests that are complete, including those
55
+ # that have errored out.
56
+ # If +need+ is fullfilled, it closes all incomplete requests.
57
+ def self.wait(need, requests, timeout)
58
+ ready, failed = [], []
59
+ pollset = {}
60
+ begin
61
+ requests.each do |req|
62
+ begin
63
+ case rv = req.resume
64
+ when Symbol # :wait_writable, :wait_readable
65
+ pollset[req] = rv
66
+ else
67
+ (ready << req).size == need and
68
+ return done_early(ready, failed, requests)
69
+ pollset.delete(req)
70
+ end
71
+ rescue => e
72
+ req.error = e
73
+ failed << req
74
+ pollset.delete(req)
75
+ end
76
+ end
77
+ break if pollset.empty?
78
+
79
+ t0 = Time.now
80
+ busy = pollset.keys
81
+ rv = Kgio.poll(pollset, timeout.to_i) or break
82
+ timeout -= (Time.now - t0) * 1000
83
+ rescue Errno::EINTR
84
+ timeout -= (Time.now - t0) * 1000
85
+ retry
86
+ end while timeout > 0.0 && requests = rv.keys.concat(busy).uniq!
87
+
88
+ ready.concat(failed)
89
+ unless requests.empty?
90
+ ready.concat(error_all(requests, TimeoutError.new("request timed out")))
91
+ ready.uniq!
92
+ end
93
+ ready
94
+ end
95
+ end
96
+ require "http_spew/headers"
97
+ require "http_spew/request"