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