htttee 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +2 -0
- data/README.md +105 -0
- data/Rakefile +14 -0
- data/bin/htttee +39 -0
- data/bin/htttee-exec +18 -0
- data/config.ru +6 -0
- data/deploy/after_restart.rb +1 -0
- data/deploy/before_restart.rb +4 -0
- data/deploy/before_symlink.rb +2 -0
- data/deploy/cookbooks/cloudkick-plugins/recipes/default.rb +8 -0
- data/deploy/cookbooks/cloudkick-plugins/recipes/resque.rb +18 -0
- data/deploy/cookbooks/cloudkick-plugins/templates/default/resque.sh.erb +4 -0
- data/deploy/cookbooks/god/recipes/default.rb +50 -0
- data/deploy/cookbooks/god/templates/default/config.erb +3 -0
- data/deploy/cookbooks/god/templates/default/god-inittab.erb +3 -0
- data/deploy/cookbooks/main/attributes/owner_name.rb +3 -0
- data/deploy/cookbooks/main/attributes/recipes.rb +1 -0
- data/deploy/cookbooks/main/libraries/dnapi.rb +7 -0
- data/deploy/cookbooks/main/recipes/default.rb +2 -0
- data/deploy/cookbooks/nginx/files/default/chunkin-nginx-module-v0.22rc1.zip +0 -0
- data/deploy/cookbooks/nginx/files/default/nginx-1.0.0.tar.gz +0 -0
- data/deploy/cookbooks/nginx/recipes/default.rb +2 -0
- data/deploy/cookbooks/nginx/recipes/install.rb +0 -0
- data/deploy/cookbooks/nginx/templates/default/nginx.conf.erb +41 -0
- data/deploy/cookbooks/resque/recipes/default.rb +47 -0
- data/deploy/cookbooks/resque/templates/default/resque.rb.erb +73 -0
- data/deploy/cookbooks/resque/templates/default/resque.yml.erb +3 -0
- data/deploy/cookbooks/resque/templates/default/resque_scheduler.rb.erb +73 -0
- data/deploy/solo.rb +7 -0
- data/htttee.gemspec +30 -0
- data/lib/htttee.rb +0 -0
- data/lib/htttee/client.rb +16 -0
- data/lib/htttee/client/consumer.rb +49 -0
- data/lib/htttee/client/ext/net/http.rb +27 -0
- data/lib/htttee/server.rb +74 -0
- data/lib/htttee/server/api.rb +201 -0
- data/lib/htttee/server/chunked_body.rb +22 -0
- data/lib/htttee/server/ext/em-redis.rb +49 -0
- data/lib/htttee/server/ext/thin.rb +4 -0
- data/lib/htttee/server/ext/thin/connection.rb +8 -0
- data/lib/htttee/server/ext/thin/deferrable_body.rb +24 -0
- data/lib/htttee/server/ext/thin/deferred_request.rb +31 -0
- data/lib/htttee/server/ext/thin/deferred_response.rb +4 -0
- data/lib/htttee/server/middleware/async_fixer.rb +22 -0
- data/lib/htttee/server/middleware/dechunker.rb +63 -0
- data/lib/htttee/server/mock.rb +69 -0
- data/lib/htttee/server/pubsub_redis.rb +78 -0
- data/lib/htttee/version.rb +5 -0
- data/spec/client_spec.rb +126 -0
- data/spec/helpers/rackup.rb +15 -0
- data/spec/spec_helper.rb +21 -0
- metadata +201 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# htttee - unix's tee-as-a-service
|
2
|
+
|
3
|
+
|
4
|
+
## What is 'tee'?
|
5
|
+
|
6
|
+
$ man tee
|
7
|
+
NAME
|
8
|
+
tee -- pipe fitting
|
9
|
+
DESCRIPTION
|
10
|
+
The tee utility copies standard input to standard output, making a copy in zero or more files. The output is unbuffered.
|
11
|
+
|
12
|
+
It's very handy for piping the output of a script to a file and to STDOUT simultaneously.
|
13
|
+
|
14
|
+
## What is 'htttee'?
|
15
|
+
|
16
|
+
Instead of piping to a file, `htttee` pipes to a web service. Consumers can then stream the
|
17
|
+
piped output via the `htttee` command line. Alternately, the streamed output could be viewed
|
18
|
+
within a browser.
|
19
|
+
|
20
|
+
That is, a streaming output locked within a server can be made accessible to a console
|
21
|
+
or browser via `htttee`.
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
In one terminal:
|
26
|
+
|
27
|
+
ruby -e "STDOUT.sync = true; 1.upto(100) {|i| puts i; sleep(i/100.0)}" | \
|
28
|
+
htttee -e http://localhost:3000 -u SOMEUNIQUESTRING
|
29
|
+
|
30
|
+
In another terminal:
|
31
|
+
|
32
|
+
curl http://htttee.engineyard.com/SOMEUNIQUESTRING
|
33
|
+
|
34
|
+
Or to see the chunked information:
|
35
|
+
|
36
|
+
$ telnet localhost 3000
|
37
|
+
|
38
|
+
Then enter:
|
39
|
+
|
40
|
+
GET /1234 HTTP/1.1
|
41
|
+
Host: localhost
|
42
|
+
|
43
|
+
You will then see a flow of integers being chunked preceded by the size of the chunks. Here is the
|
44
|
+
final few numbers chunked through:
|
45
|
+
|
46
|
+
3
|
47
|
+
98
|
48
|
+
|
49
|
+
3
|
50
|
+
99
|
51
|
+
|
52
|
+
4
|
53
|
+
100
|
54
|
+
|
55
|
+
0
|
56
|
+
|
57
|
+
Connection closed by foreign host.
|
58
|
+
|
59
|
+
|
60
|
+
## Running the server
|
61
|
+
|
62
|
+
git clone git://github.com/benburkert/htttee.git
|
63
|
+
cd htttee
|
64
|
+
bundle
|
65
|
+
thin start
|
66
|
+
|
67
|
+
## Development
|
68
|
+
|
69
|
+
To pull down the repository and run the test suite:
|
70
|
+
|
71
|
+
git clone git://github.com/benburkert/htttee.git
|
72
|
+
cd htttee
|
73
|
+
bundle
|
74
|
+
rake
|
75
|
+
|
76
|
+
To install the gem locally from source:
|
77
|
+
|
78
|
+
rake install
|
79
|
+
|
80
|
+
To release the gem:
|
81
|
+
|
82
|
+
rake release
|
83
|
+
|
84
|
+
## License
|
85
|
+
|
86
|
+
Copyright (c) 2011 Ben Burkert, ben@benburkert.com
|
87
|
+
|
88
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
89
|
+
a copy of this software and associated documentation files (the
|
90
|
+
"Software"), to deal in the Software without restriction, including
|
91
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
92
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
93
|
+
permit persons to whom the Software is furnished to do so, subject to
|
94
|
+
the following conditions:
|
95
|
+
|
96
|
+
The above copyright notice and this permission notice shall be
|
97
|
+
included in all copies or substantial portions of the Software.
|
98
|
+
|
99
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
100
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
101
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
102
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
103
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
104
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
105
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rspec/core/rake_task' rescue nil
|
6
|
+
RSpec::Core::RakeTask.new do |t|
|
7
|
+
t.rspec_opts = %w[ -b -c -f documentation -r ./spec/spec_helper.rb ]
|
8
|
+
t.pattern = 'spec/**/*_spec.rb'
|
9
|
+
end
|
10
|
+
|
11
|
+
task :default => :spec
|
12
|
+
|
13
|
+
rescue LoadError
|
14
|
+
end
|
data/bin/htttee
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'trollop'
|
4
|
+
require 'htttee/client'
|
5
|
+
|
6
|
+
class MultiplexedIO < IO
|
7
|
+
instance_methods.each { |m| undef_method m unless (m =~ /^__/ || m =~ /^object_id$/ ) }
|
8
|
+
|
9
|
+
def initialize(input, output)
|
10
|
+
@input, @output = input, output
|
11
|
+
end
|
12
|
+
|
13
|
+
def readpartial(*a, &b)
|
14
|
+
data = @input.readpartial(*a,&b)
|
15
|
+
@output << data
|
16
|
+
data
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(method, *a, &b)
|
20
|
+
@input.send(method, *a, &b)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
opts = Trollop::options do
|
25
|
+
opt :uuid, "The UUID of the stream.", :short => '-u', :type => String
|
26
|
+
opt :'content-type', "The content-type of the stream.", :short => '-c', :type => String, :default => 'text/plain'
|
27
|
+
opt :endpoint, "The endpoint of the htttee service.", :short => '-e', :type => String, :default => 'http://htttee.engineyard.com/'
|
28
|
+
opt :quiet, "Don't echo to stdout.", :short => '-q', :default => false
|
29
|
+
end
|
30
|
+
|
31
|
+
Trollop::die :uuid, "is required" if opts[:uuid].nil?
|
32
|
+
|
33
|
+
$stdin.sync = true
|
34
|
+
$stdout.sync = true
|
35
|
+
|
36
|
+
input = opts[:quiet] ? $stdin : MultiplexedIO.new($stdin, $stdout)
|
37
|
+
client = EY::Tea::Client.new(:endpoint => opts[:endpoint])
|
38
|
+
|
39
|
+
client.up(input, opts[:uuid], opts[:'content-type'])
|
data/bin/htttee-exec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
stdout_uuid="$(head -c4096 /dev/urandom | sha256sum | awk '{ print $1 }')"
|
4
|
+
stderr_uuid="$(head -c4096 /dev/urandom | sha256sum | awk '{ print $1 }')"
|
5
|
+
|
6
|
+
mkfifo $stdout_uuid
|
7
|
+
mkfifo $stderr_uuid
|
8
|
+
|
9
|
+
bin/htttee --uuid $stdout_uuid --endpoint http://127.0.0.1:9292/ < $stdout_uuid &
|
10
|
+
bin/htttee --uuid $stderr_uuid --endpoint http://127.0.0.1:9292/ < $stderr_uuid &
|
11
|
+
|
12
|
+
echo "stdout: http://127.0.0.1:9292/$stdout_uuid"
|
13
|
+
echo "stderr: http://127.0.0.1:9292/$stderr_uuid"
|
14
|
+
|
15
|
+
exec 1>$stdout_uuid
|
16
|
+
exec 2>$stderr_uuid
|
17
|
+
|
18
|
+
exec $*
|
data/config.ru
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
sudo "god signal resque-#{app} QUIT"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
execute 'restart-cloudkick-agent' do
|
2
|
+
action :nothing
|
3
|
+
|
4
|
+
command 'pkill cloudkick-agent'
|
5
|
+
end
|
6
|
+
|
7
|
+
template "/usr/lib/cloudkick-agent/plugins/resque" do
|
8
|
+
mode "0755"
|
9
|
+
source "resque.sh.erb"
|
10
|
+
variables(
|
11
|
+
:dir => "/data/#{node.engineyard.environment.apps.first.name}/current",
|
12
|
+
:script => "script/cloudkick-resque",
|
13
|
+
:rack_env => node.engineyard.environment.framework_env,
|
14
|
+
:redis_uri => "#{node.engineyard.environment.db_host}:6379"
|
15
|
+
)
|
16
|
+
|
17
|
+
notifies :run, resources(:execute => 'restart-cloudkick-agent')
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
execute "restart god" do
|
2
|
+
command <<-SRC
|
3
|
+
if pgrep god; then
|
4
|
+
god quit
|
5
|
+
fi
|
6
|
+
SRC
|
7
|
+
action :nothing
|
8
|
+
end
|
9
|
+
|
10
|
+
gem_package "god" do
|
11
|
+
version "0.11.0"
|
12
|
+
action :install
|
13
|
+
notifies :run, resources(:execute => "restart god")
|
14
|
+
end
|
15
|
+
|
16
|
+
directory "/etc/god" do
|
17
|
+
owner 'root'
|
18
|
+
group 'root'
|
19
|
+
mode 0755
|
20
|
+
recursive true
|
21
|
+
end
|
22
|
+
|
23
|
+
template "/etc/god/config" do
|
24
|
+
owner "root"
|
25
|
+
group "root"
|
26
|
+
mode 0644
|
27
|
+
source "config.erb"
|
28
|
+
end
|
29
|
+
|
30
|
+
execute "telinit q" do
|
31
|
+
command "telinit q"
|
32
|
+
action :nothing
|
33
|
+
end
|
34
|
+
|
35
|
+
template "/tmp/god-inittab" do
|
36
|
+
owner "root"
|
37
|
+
group "root"
|
38
|
+
mode 0644
|
39
|
+
source "god-inittab.erb"
|
40
|
+
end
|
41
|
+
|
42
|
+
execute "make init work with god" do
|
43
|
+
command "cat /tmp/god-inittab >>/etc/inittab"
|
44
|
+
not_if "grep '# god config' /etc/inittab"
|
45
|
+
notifies :run, resources(:execute => "telinit q")
|
46
|
+
end
|
47
|
+
|
48
|
+
file "/tmp/god-inittab" do
|
49
|
+
action :delete
|
50
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
recipes "main"
|
Binary file
|
File without changes
|
@@ -0,0 +1,41 @@
|
|
1
|
+
user ey ey;
|
2
|
+
worker_processes 4;
|
3
|
+
pid /var/run/nginx.pid;
|
4
|
+
|
5
|
+
events {
|
6
|
+
worker_connections 8192;
|
7
|
+
use epoll;
|
8
|
+
}
|
9
|
+
|
10
|
+
http {
|
11
|
+
|
12
|
+
upstream thins {
|
13
|
+
server unix:/tmp/thin.sock;
|
14
|
+
}
|
15
|
+
|
16
|
+
access_log /var/log/foo.log;
|
17
|
+
error_log /var/bar.log debug;
|
18
|
+
|
19
|
+
server {
|
20
|
+
listen 80;
|
21
|
+
server_name *.amazonaws.com;
|
22
|
+
|
23
|
+
chunkin on;
|
24
|
+
proxy_buffering off;
|
25
|
+
chunked_transfer_encoding off;
|
26
|
+
|
27
|
+
location / {
|
28
|
+
proxy_pass http://thins;
|
29
|
+
proxy_set_header X-Real-IP $remote_addr;
|
30
|
+
proxy_buffering off;
|
31
|
+
#chunked_transfer_encoding on;
|
32
|
+
|
33
|
+
}
|
34
|
+
|
35
|
+
error_page 411 = @my_411_error;
|
36
|
+
location @my_411_error {
|
37
|
+
proxy_buffering off;
|
38
|
+
chunkin_resume;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
if node.engineyard.name == "resque" || node.engineyard.role == "solo"
|
2
|
+
require_recipe "god"
|
3
|
+
|
4
|
+
node[:applications].each do |app, data|
|
5
|
+
template "/etc/god/resque_#{app}.rb" do
|
6
|
+
owner "root"
|
7
|
+
group "root"
|
8
|
+
mode 0644
|
9
|
+
source "resque.rb.erb"
|
10
|
+
variables(
|
11
|
+
:app => app,
|
12
|
+
:app_root => "/data/#{app}/current",
|
13
|
+
:resque_workers_count => 4
|
14
|
+
)
|
15
|
+
notifies :run, resources(:execute => "restart god")
|
16
|
+
end
|
17
|
+
|
18
|
+
template "/etc/god/resque_scheduler_#{app}.rb" do
|
19
|
+
owner "root"
|
20
|
+
group "root"
|
21
|
+
mode 0644
|
22
|
+
source "resque_scheduler.rb.erb"
|
23
|
+
variables(
|
24
|
+
:app => app,
|
25
|
+
:app_root => "/data/#{app}/current",
|
26
|
+
:resque_workers_count => 1
|
27
|
+
)
|
28
|
+
notifies :run, resources(:execute => "restart god")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if %w[solo app app_master util].include? node[:instance_role]
|
34
|
+
node[:applications].each do |app, data|
|
35
|
+
template "/data/#{app}/shared/config/resque.yml" do
|
36
|
+
owner node[:owner_name]
|
37
|
+
group node[:owner_name]
|
38
|
+
mode 0655
|
39
|
+
source "resque.yml.erb"
|
40
|
+
variables(
|
41
|
+
:framework_env => node.engineyard.environment.framework_env,
|
42
|
+
:redis_host => node.engineyard.environment.db_host,
|
43
|
+
:redis_port => 6379
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
rack_env = "<%= @node[:environment][:framework_env] %>"
|
2
|
+
app_name = "<%= @app %>"
|
3
|
+
app_root = "<%= @app_root %>"
|
4
|
+
owner = "<%= @node[:owner_name] %>"
|
5
|
+
home = "/home/#{owner}"
|
6
|
+
instance_id = "<%= @node.engineyard.id %>"
|
7
|
+
|
8
|
+
<%= @resque_workers_count %>.times do |num|
|
9
|
+
inline = "#{home}/.ruby_inline/resque-#{app_name}-#{num}"
|
10
|
+
|
11
|
+
God.watch do |w|
|
12
|
+
w.name = "resque-#{app_name}-#{num}"
|
13
|
+
w.group = "resque-#{app_name}"
|
14
|
+
w.uid = owner
|
15
|
+
w.gid = owner
|
16
|
+
w.interval = 30.seconds
|
17
|
+
w.log = "#{app_root}/log/worker.#{num}.log"
|
18
|
+
w.dir = app_root
|
19
|
+
w.env = {
|
20
|
+
"USER" => owner,
|
21
|
+
"VERBOSE" => "true",
|
22
|
+
"INSTANCE_ID" => instance_id,
|
23
|
+
"GOD_WATCH" => w.name,
|
24
|
+
"RACK_ENV" => rack_env,
|
25
|
+
"HOME" => home,
|
26
|
+
"QUEUE" => "*",
|
27
|
+
}
|
28
|
+
|
29
|
+
w.start = "bundle exec rake --trace init resque:work"
|
30
|
+
|
31
|
+
w.behavior(:clean_pid_file)
|
32
|
+
|
33
|
+
w.start_grace = 2.minutes
|
34
|
+
w.restart_grace = 2.minutes
|
35
|
+
|
36
|
+
# retart if memory gets too high
|
37
|
+
w.transition(:up, :restart) do |on|
|
38
|
+
on.condition(:memory_usage) do |c|
|
39
|
+
c.above = 350.megabytes
|
40
|
+
c.times = 2
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# determine the state on startup
|
45
|
+
w.transition(:init, { true => :up, false => :start }) do |on|
|
46
|
+
on.condition(:process_running) do |c|
|
47
|
+
c.running = true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# determine when process has finished starting
|
52
|
+
w.transition([:start, :restart], :up) do |on|
|
53
|
+
on.condition(:process_running) do |c|
|
54
|
+
c.running = true
|
55
|
+
c.interval = 5.seconds
|
56
|
+
end
|
57
|
+
|
58
|
+
# failsafe
|
59
|
+
on.condition(:tries) do |c|
|
60
|
+
c.times = 5
|
61
|
+
c.transition = :start
|
62
|
+
c.interval = 5.seconds
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# start if process is not running
|
67
|
+
w.transition(:up, :start) do |on|
|
68
|
+
on.condition(:process_running) do |c|
|
69
|
+
c.running = false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|