htttee 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +2 -0
  3. data/README.md +105 -0
  4. data/Rakefile +14 -0
  5. data/bin/htttee +39 -0
  6. data/bin/htttee-exec +18 -0
  7. data/config.ru +6 -0
  8. data/deploy/after_restart.rb +1 -0
  9. data/deploy/before_restart.rb +4 -0
  10. data/deploy/before_symlink.rb +2 -0
  11. data/deploy/cookbooks/cloudkick-plugins/recipes/default.rb +8 -0
  12. data/deploy/cookbooks/cloudkick-plugins/recipes/resque.rb +18 -0
  13. data/deploy/cookbooks/cloudkick-plugins/templates/default/resque.sh.erb +4 -0
  14. data/deploy/cookbooks/god/recipes/default.rb +50 -0
  15. data/deploy/cookbooks/god/templates/default/config.erb +3 -0
  16. data/deploy/cookbooks/god/templates/default/god-inittab.erb +3 -0
  17. data/deploy/cookbooks/main/attributes/owner_name.rb +3 -0
  18. data/deploy/cookbooks/main/attributes/recipes.rb +1 -0
  19. data/deploy/cookbooks/main/libraries/dnapi.rb +7 -0
  20. data/deploy/cookbooks/main/recipes/default.rb +2 -0
  21. data/deploy/cookbooks/nginx/files/default/chunkin-nginx-module-v0.22rc1.zip +0 -0
  22. data/deploy/cookbooks/nginx/files/default/nginx-1.0.0.tar.gz +0 -0
  23. data/deploy/cookbooks/nginx/recipes/default.rb +2 -0
  24. data/deploy/cookbooks/nginx/recipes/install.rb +0 -0
  25. data/deploy/cookbooks/nginx/templates/default/nginx.conf.erb +41 -0
  26. data/deploy/cookbooks/resque/recipes/default.rb +47 -0
  27. data/deploy/cookbooks/resque/templates/default/resque.rb.erb +73 -0
  28. data/deploy/cookbooks/resque/templates/default/resque.yml.erb +3 -0
  29. data/deploy/cookbooks/resque/templates/default/resque_scheduler.rb.erb +73 -0
  30. data/deploy/solo.rb +7 -0
  31. data/htttee.gemspec +30 -0
  32. data/lib/htttee.rb +0 -0
  33. data/lib/htttee/client.rb +16 -0
  34. data/lib/htttee/client/consumer.rb +49 -0
  35. data/lib/htttee/client/ext/net/http.rb +27 -0
  36. data/lib/htttee/server.rb +74 -0
  37. data/lib/htttee/server/api.rb +201 -0
  38. data/lib/htttee/server/chunked_body.rb +22 -0
  39. data/lib/htttee/server/ext/em-redis.rb +49 -0
  40. data/lib/htttee/server/ext/thin.rb +4 -0
  41. data/lib/htttee/server/ext/thin/connection.rb +8 -0
  42. data/lib/htttee/server/ext/thin/deferrable_body.rb +24 -0
  43. data/lib/htttee/server/ext/thin/deferred_request.rb +31 -0
  44. data/lib/htttee/server/ext/thin/deferred_response.rb +4 -0
  45. data/lib/htttee/server/middleware/async_fixer.rb +22 -0
  46. data/lib/htttee/server/middleware/dechunker.rb +63 -0
  47. data/lib/htttee/server/mock.rb +69 -0
  48. data/lib/htttee/server/pubsub_redis.rb +78 -0
  49. data/lib/htttee/version.rb +5 -0
  50. data/spec/client_spec.rb +126 -0
  51. data/spec/helpers/rackup.rb +15 -0
  52. data/spec/spec_helper.rb +21 -0
  53. metadata +201 -0
@@ -0,0 +1,6 @@
1
+ .redcar
2
+ .DS_Store
3
+
4
+ Gemfile.lock
5
+ pkg
6
+ vendor
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org/'
2
+ gemspec
@@ -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.
@@ -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
@@ -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'])
@@ -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 $*
@@ -0,0 +1,6 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+
3
+ require 'htttee/server'
4
+
5
+ use Rack::CommonLogger
6
+ run EY::Tea::Server.app
@@ -0,0 +1 @@
1
+ sudo "god signal resque-#{app} QUIT"
@@ -0,0 +1,4 @@
1
+ on_app_servers do
2
+ env_custom = "#{shared_path}/config/env.custom"
3
+ run "echo 'export APP_CURRENT_PATH=\"#{current_path}\"' > #{env_custom}"
4
+ end
@@ -0,0 +1,2 @@
1
+ sudo "/usr/local/ey_resin/ruby/bin/chef-solo " \
2
+ "-c #{latest_release}/deploy/solo.rb -j /etc/chef/dna.json"
@@ -0,0 +1,8 @@
1
+ directory "/usr/lib/cloudkick-agent/plugins" do
2
+ owner "root"
3
+ group "root"
4
+ action :create
5
+ recursive true
6
+ end
7
+
8
+ require_recipe "cloudkick-plugins::resque"
@@ -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,4 @@
1
+ #!/bin/sh
2
+ export RACK_ENV=<%= @rack_env %>
3
+ export REDIS_URI=<%= @redis_uri %>
4
+ cd <%= @dir %> && bundle exec <%= @script %>
@@ -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,3 @@
1
+ Dir.glob("/etc/god/*.rb").each do |filename|
2
+ load filename
3
+ end
@@ -0,0 +1,3 @@
1
+ # god config
2
+ god:345:respawn:/usr/bin/god -c /etc/god/config -l /var/log/god.log --log-level info -D >> /root/god.log 2>&1
3
+ god0:06:wait:/usr/bin/god quit
@@ -0,0 +1,3 @@
1
+ # Set the owner_name to the first username
2
+ owner_name(@attribute[:users].first[:username])
3
+ owner_pass(@attribute[:users].first[:password])
@@ -0,0 +1 @@
1
+ recipes "main"
@@ -0,0 +1,7 @@
1
+ require "dnapi"
2
+
3
+ class Chef::Node
4
+ def engineyard
5
+ @engineyard ||= DNApi.from(File.read("/etc/chef/dna.json"))
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ require_recipe "resque"
2
+ #require_recipe "cloudkick-plugins"
@@ -0,0 +1,2 @@
1
+ require_recipe "nginx::install"
2
+ require_recipe "nginx::configure"
@@ -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