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/.document +6 -0
- data/.gitignore +8 -0
- data/.manifest +33 -0
- data/.wrongdoc.yml +4 -0
- data/COPYING +674 -0
- data/ChangeLog +170 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +5 -0
- data/LATEST +4 -0
- data/LICENSE +17 -0
- data/NEWS +4 -0
- data/README +61 -0
- data/http_spew.gemspec +24 -0
- data/lib/http_spew/chunky_pipe.rb +12 -0
- data/lib/http_spew/content_md5.rb +55 -0
- data/lib/http_spew/headers.rb +43 -0
- data/lib/http_spew/hit_n_run.rb +19 -0
- data/lib/http_spew/input_spray.rb +53 -0
- data/lib/http_spew/request.rb +77 -0
- data/lib/http_spew.rb +97 -0
- data/pkg.mk +171 -0
- data/setup.rb +1585 -0
- data/test/content-md5.ru +21 -0
- data/test/helper.rb +41 -0
- data/test/mirror.ru +22 -0
- data/test/sha1.ru +18 -0
- data/test/test_content_md5.rb +85 -0
- data/test/test_hit_n_run.rb +47 -0
- data/test/test_input_spray.rb +101 -0
- data/test/test_mirror.rb +60 -0
- data/test/test_request.rb +54 -0
- data/test/test_upload.rb +127 -0
- metadata +166 -0
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
data/LATEST
ADDED
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
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"
|