http_spew 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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"