htttee 0.5.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.
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