sabbath 0.0.1

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/README.rdoc ADDED
@@ -0,0 +1,22 @@
1
+ = Sabbath
2
+
3
+ REST interface to beanstalkd (or probably other queues at some point).
4
+
5
+ == Usage
6
+
7
+ >> sabbath -h
8
+
9
+ << Usage: sabbath [options]
10
+ <<
11
+ << Options:
12
+ << -p, --port[OPTIONAL] Port (default: 11300)
13
+ << -h, --host[OPTIONAL] Host (default: localhost)
14
+ << -P, --web-port[OPTIONAL] Web port (default: 4848)
15
+ << -H, --web-host[OPTIONAL] Web host (default: 0.0.0.0)
16
+ << --help Show this help message.
17
+
18
+ == Why?
19
+
20
+ This allows you to interface with beanstalk over normal HTTP calls. I've included a really simple example in the +examples+ directory. It consumes
21
+ and pushes jobs onto a queue from jQuery.
22
+
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "sabbath"
7
+ s.description = s.summary = "Rest for your work (queues)"
8
+ s.email = "joshbuddy@gmail.com"
9
+ s.homepage = "http://github.com/joshbuddy/sabbath"
10
+ s.authors = ["Joshua Hull"].sort
11
+ s.files = FileList["[A-Z]*", "{lib,bin,examples}/**/*"]
12
+ s.add_dependency 'thin'
13
+ s.add_dependency 'eventmachine'
14
+ s.add_dependency 'em-beanstalk', '>=0.0.6'
15
+ s.add_dependency 'rack'
16
+ s.add_dependency 'usher'
17
+ s.add_dependency 'json'
18
+ s.add_dependency 'uuid'
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
23
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/sabbath ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby -rubygems
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'sabbath')
4
+ require 'optparse'
5
+
6
+ options = {}
7
+ options[:port] = 11300
8
+ options[:host] = 'localhost'
9
+ options[:web_port] = 4848
10
+ options[:web_host] = '0.0.0.0'
11
+
12
+ parser = OptionParser.new do |opts|
13
+ opts.banner = "Usage: sabbath [options]"
14
+
15
+ opts.separator ""
16
+ opts.separator "Options:"
17
+
18
+ opts.on("-p[OPTIONAL]", "--port", "Port (default: #{options[:port]})") do |v|
19
+ options[:port] = v
20
+ end
21
+
22
+ opts.on("-h[OPTIONAL]", "--host", "Host (default: #{options[:host]})") do |v|
23
+ options[:host] = v
24
+ end
25
+
26
+ opts.on("-P[OPTIONAL]", "--web-port", "Web port (default: #{options[:web_port]})") do |v|
27
+ options[:port] = v
28
+ end
29
+
30
+ opts.on("-H[OPTIONAL]", "--web-host", "Web host (default: #{options[:web_host]})") do |v|
31
+ options[:host] = v
32
+ end
33
+
34
+ opts.on_tail("-h", "--help", "Show this help message.") { puts opts; exit }
35
+ end
36
+ parser.parse!(ARGV)
37
+
38
+ Sabbath.new(options).start
@@ -0,0 +1,173 @@
1
+ <html>
2
+ <head>
3
+ <script src="http://www.google.com/jsapi"></script>
4
+ <script>
5
+ // Load jQuery
6
+ google.load("jquery", "1.3.2");
7
+
8
+ google.setOnLoadCallback(function() {
9
+ $(document).ready(function() {
10
+ var base = 'http://localhost:4848';
11
+
12
+ function loadJob(force) {
13
+ if (force || $('#jobs div').length == 0) $('#jobs').html('waiting for job...');
14
+ if ($('#jobs div').length == 0) {
15
+
16
+
17
+ $.ajax({
18
+ type: "GET",
19
+ dataType: "jsonp",
20
+ url: base + "/default",
21
+
22
+ async: true, /* If set to non-async, browser shows page as "Loading.."*/
23
+ cache: false,
24
+ timeout: 50000, /* Timeout in ms */
25
+
26
+ success: function (data, textStatus) {
27
+ // data could be xmlDoc, jsonObj, html, text, etc...
28
+ $('#jobs').html('<div id="' + data.id + '">' + data.body + '</div>');
29
+ },
30
+ error: function (XMLHttpRequest, textStatus, errorThrown) {
31
+ // typically only one of textStatus or errorThrown
32
+ // will have info
33
+ alert("fail:" +textStatus);
34
+ }
35
+ });
36
+ }
37
+ }
38
+
39
+ loadJob();
40
+
41
+ $('#approve').click(function() {
42
+ if ($('#jobs div').length > 0) {
43
+ var id = $('#jobs div')[0].id;
44
+
45
+ // do your approval here
46
+
47
+ $.ajax({
48
+ type: "GET",
49
+ dataType: "jsonp",
50
+ url: base + "/default/" + id + "?_method=delete",
51
+
52
+ async: true, /* If set to non-async, browser shows page as "Loading.."*/
53
+ cache: false,
54
+ timeout: 50000, /* Timeout in ms */
55
+
56
+ success: function (data, textStatus) {
57
+ loadJob(true);
58
+ alert('Approved!');
59
+ },
60
+ error: function (XMLHttpRequest, textStatus, errorThrown) {
61
+ // typically only one of textStatus or errorThrown
62
+ // will have info
63
+ alert("fail:" +textStatus);
64
+ }
65
+ });
66
+ } else {
67
+ alert('you have no jobs pending');
68
+ }
69
+ });
70
+
71
+ $('#reject').click(function() {
72
+ if ($('#jobs div').length > 0) {
73
+ var id = $('#jobs div')[0].id;
74
+
75
+ // do your rejection here
76
+
77
+ $.ajax({
78
+ type: "GET",
79
+ dataType: "jsonp",
80
+ url: base + "/default/" + id + "?_method=delete",
81
+
82
+ async: true, /* If set to non-async, browser shows page as "Loading.."*/
83
+ cache: false,
84
+ timeout: 50000, /* Timeout in ms */
85
+
86
+ success: function (data, textStatus) {
87
+ loadJob(true);
88
+ alert('Approved!');
89
+ },
90
+ error: function (XMLHttpRequest, textStatus, errorThrown) {
91
+ // typically only one of textStatus or errorThrown
92
+ // will have info
93
+ alert("fail:" +textStatus);
94
+ }
95
+ });
96
+ } else {
97
+ alert('you have no jobs pending');
98
+ }
99
+ });
100
+
101
+ $('#defer').click(function() {
102
+ if ($('#jobs div').length > 0) {
103
+ var id = $('#jobs div')[0].id;
104
+
105
+ // do your rejection here
106
+
107
+ $.ajax({
108
+ type: "GET",
109
+ dataType: "jsonp",
110
+ url: base + "/default/" + id + "/release?_method=put",
111
+
112
+ async: true, /* If set to non-async, browser shows page as "Loading.."*/
113
+ cache: false,
114
+ timeout: 50000, /* Timeout in ms */
115
+
116
+ success: function (data, textStatus) {
117
+ loadJob(true);
118
+ alert('Approved!');
119
+ },
120
+ error: function (XMLHttpRequest, textStatus, errorThrown) {
121
+ // typically only one of textStatus or errorThrown
122
+ // will have info
123
+ alert("fail:" +textStatus);
124
+ }
125
+ });
126
+ } else {
127
+ alert('you have no jobs pending');
128
+ }
129
+ });
130
+
131
+ $("#submit").submit(function(e) {
132
+ alert('sending!');
133
+ e.preventDefault();
134
+ $.ajax({
135
+ type: "GET",
136
+ dataType: "jsonp",
137
+ url: base + "/default",
138
+ data: {body: $('#body').val(), _method: 'post'},
139
+ async: true, /* If set to non-async, browser shows page as "Loading.."*/
140
+ cache: false,
141
+ timeout: 50000, /* Timeout in ms */
142
+
143
+ success: function (data, textStatus) {
144
+ alert('submitted new job');
145
+ loadJob();
146
+ },
147
+ error: function (XMLHttpRequest, textStatus, errorThrown) {
148
+ // typically only one of textStatus or errorThrown
149
+ // will have info
150
+ alert("fail:" +textStatus);
151
+ }
152
+ });
153
+
154
+
155
+
156
+ });
157
+ });
158
+
159
+ });
160
+
161
+ </script>
162
+ </head>
163
+ <body>
164
+ <h1>Jobs</h1>
165
+ <div id="jobs"></div>
166
+ <input type="button" id="approve" value="Approve"><input type="button" id="reject" value="Reject"><input type="button" id="defer" value="Defer">
167
+
168
+ <form id="submit">
169
+ <textarea id="body"></textarea><br>
170
+ <input type="submit" value="Submit">
171
+ </form>
172
+ </body>
173
+ </html>
@@ -0,0 +1,43 @@
1
+ require 'em-beanstalk'
2
+
3
+ class Sabbath
4
+ class Backend
5
+ class Beanstalk
6
+
7
+ attr_reader :host, :port
8
+
9
+ Name = 'Beanstalkd'.freeze
10
+
11
+ def name
12
+ Name
13
+ end
14
+
15
+ def initialize(host, port)
16
+ @host, @port = host, port
17
+ @conns = {}
18
+ end
19
+
20
+ def connection(id, tube = 'default')
21
+ @conns[id] ||= EM::Beanstalk.new(:host => host, :port => port, :tube => tube, :retry_count => 0, :raise_on_disconnect => false)
22
+ end
23
+
24
+ def get_latest_job(conn_id, tube, timeout = nil, &block)
25
+ connection(conn_id, tube).reserve(timeout, &block)
26
+ end
27
+
28
+ def get_job(conn_idtube, id, &block)
29
+ connection(conn_id, tube).peek(id, &block)
30
+ end
31
+
32
+ def create_job(conn_id, tube, body, &block)
33
+ connection(conn_id, tube).put(body, &block)
34
+ end
35
+
36
+ def delete_job(conn_id, tube, id, &block)
37
+ puts "deleting job #{tube.inspect} #{id.inspect}"
38
+ connection(conn_id, tube).delete(id, &block)
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1 @@
1
+ require 'sabbath/backend/beanstalk'
@@ -0,0 +1,143 @@
1
+ module Thin
2
+ class Server
3
+ # Start the server and listen for connections.
4
+ def start
5
+ raise ArgumentError, 'app required' unless @app
6
+
7
+ log ">> Sabbath ---> connected to #{app.backend.name} on port #{app.backend.port}, host #{app.backend.host}"
8
+ log ">> Using Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
9
+ debug ">> Debugging ON"
10
+ trace ">> Tracing ON"
11
+
12
+ log ">> Maximum connections set to #{@backend.maximum_connections}"
13
+ log ">> Listening on #{@backend}, CTRL+C to stop"
14
+
15
+ @backend.start
16
+ end
17
+ alias :start! :start
18
+ end
19
+ end
20
+
21
+ class Sabbath
22
+ class Server
23
+
24
+ class DeferrableBody
25
+ include EventMachine::Deferrable
26
+
27
+ attr_accessor :jsonp_callback
28
+
29
+ def call(body)
30
+ body.each do |chunk|
31
+ @body_callback.call(chunk)
32
+ end
33
+ end
34
+
35
+ def each(&blk)
36
+ @body_callback = blk
37
+ end
38
+
39
+ def succeed_with(body)
40
+ data = if jsonp_callback
41
+ "#{jsonp_callback}(#{body.to_json})"
42
+ else
43
+ body.to_json
44
+ end
45
+
46
+ puts "sending #{data.inspect}"
47
+ call(Array(data))
48
+ succeed
49
+ end
50
+
51
+
52
+ end
53
+
54
+ AsyncResponse = [-1, {}, []].freeze
55
+
56
+ attr_reader :backend, :web_host, :web_port, :cookie_name
57
+
58
+ def initialize(backend, web_host, web_port, cookie_name = 'sabbath_id')
59
+ @backend = backend
60
+ @web_host, @web_port, @cookie_name = web_host, web_port, cookie_name
61
+ @router = Usher.new(:request_methods => [:request_method], :delimiters => ['/', '.'])
62
+ @router.add_route('/:tube', :conditions => {:request_method => 'GET'}) .name(:get_latest_job)
63
+ @router.add_route('/:tube/:job_id', :conditions => {:request_method => 'GET'}) .name(:get_job)
64
+ @router.add_route('/:tube', :conditions => {:request_method => 'POST'}) .name(:create_job)
65
+ @router.add_route('/:tube/:job_id', :conditions => {:request_method => 'DELETE'}) .name(:delete_job)
66
+ @router.add_route('/:tube/:job_id/release', :conditions => {:request_method => 'PUT'}) .name(:release_job)
67
+ end
68
+
69
+ def call(env)
70
+ request = Rack::Request.new(env)
71
+ query_params = Rack::Utils.parse_query(request.query_string)
72
+ env['REQUEST_METHOD'] = query_params['_method'].upcase if query_params['_method']
73
+
74
+ id = request.cookies[cookie_name] || UUID.new.generate
75
+ p id
76
+ common_response_headers = {'Content-Type' => 'text/javascript'}
77
+
78
+ common_response_headers['Set-cookie'] = Rack::Utils.build_query(cookie_name => id) unless request.cookies[cookie_name]
79
+
80
+ body = DeferrableBody.new
81
+ # Get the headers out there asap, let the client know we're alive...
82
+ EventMachine::next_tick {
83
+ body.jsonp_callback = query_params['callback']
84
+ case response = @router.recognize(request)
85
+ when nil
86
+ env['async.callback'].call([404, {}, []])
87
+ else
88
+ params = response.params_as_hash
89
+ case response.path.route.named
90
+ when :get_latest_job
91
+ puts "latest job..."
92
+ env['async.callback'].call([200, common_response_headers, body])
93
+ backend.get_latest_job(id, params[:tube], params['timeout']) {|job|
94
+ puts "sending job.body: #{job.body}"
95
+ body.succeed_with(:id => job.id, :body => job.body)
96
+ }.on_error {|message|
97
+ puts "message.. #{message}"
98
+ body.succeed_with(:error => message)
99
+ }
100
+ when :get_job
101
+ env['async.callback'].call([200, common_response_headers, body])
102
+ backend.get_job(id, params[:tube], params[:job_id]) {|job|
103
+ body.succeed_with(:id => job.id, :body => job.body)
104
+ }.on_error {|message|
105
+ body.succeed_with(:error => message)
106
+ }
107
+ when :create_job
108
+ env['async.callback'].call([200, common_response_headers, body])
109
+ backend.create_job(id, params[:tube], query_params['body']) {|id|
110
+ body.succeed_with(:id => id)
111
+ }.on_error {|message|
112
+ body.succeed_with(:error => message)
113
+ }
114
+ when :delete_job
115
+ env['async.callback'].call([200, common_response_headers, body])
116
+ backend.delete_job(id, params[:tube], params[:job_id]) {
117
+ body.succeed_with(:success => true)
118
+ }.on_error {|message|
119
+ body.succeed_with(:error => message)
120
+ }
121
+ when :release_job
122
+ env['async.callback'].call([200, common_response_headers, body])
123
+ backend.release_job(id, params[:tube], params[:job_id]) {
124
+ body.succeed_with(:success => true)
125
+ }.on_error {|message|
126
+ body.succeed_with(:error => message)
127
+ }
128
+ end
129
+ end
130
+ }
131
+ AsyncResponse
132
+ end
133
+
134
+ def start
135
+ @server = self
136
+ EM.run do
137
+ Thin::Server.start(web_host, web_port, self)
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+
data/lib/sabbath.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'thin'
2
+ require 'rack'
3
+ require 'usher'
4
+ require 'json'
5
+ require 'uuid'
6
+
7
+ $LOAD_PATH << File.dirname(__FILE__)
8
+
9
+ require 'sabbath/server'
10
+ require 'sabbath/backend'
11
+
12
+ class Sabbath
13
+
14
+ attr_reader :options
15
+
16
+ def initialize(options)
17
+ @options = options
18
+ @backend = Backend::Beanstalk.new(options[:host], options[:port])
19
+ end
20
+
21
+ def start
22
+ Server.new(@backend, options[:web_host], options[:web_port]).start
23
+ end
24
+
25
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sabbath
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joshua Hull
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-10 00:00:00 -05:00
13
+ default_executable: sabbath
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thin
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: eventmachine
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: em-beanstalk
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.0.6
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: rack
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: usher
57
+ type: :runtime
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ - !ruby/object:Gem::Dependency
66
+ name: json
67
+ type: :runtime
68
+ version_requirement:
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ - !ruby/object:Gem::Dependency
76
+ name: uuid
77
+ type: :runtime
78
+ version_requirement:
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ description: Rest for your work (queues)
86
+ email: joshbuddy@gmail.com
87
+ executables:
88
+ - sabbath
89
+ extensions: []
90
+
91
+ extra_rdoc_files:
92
+ - README.rdoc
93
+ files:
94
+ - README.rdoc
95
+ - Rakefile
96
+ - VERSION
97
+ - bin/sabbath
98
+ - examples/basic/index.html
99
+ - lib/sabbath.rb
100
+ - lib/sabbath/backend.rb
101
+ - lib/sabbath/backend/beanstalk.rb
102
+ - lib/sabbath/server.rb
103
+ has_rdoc: true
104
+ homepage: http://github.com/joshbuddy/sabbath
105
+ licenses: []
106
+
107
+ post_install_message:
108
+ rdoc_options:
109
+ - --charset=UTF-8
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: "0"
117
+ version:
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: "0"
123
+ version:
124
+ requirements: []
125
+
126
+ rubyforge_project:
127
+ rubygems_version: 1.3.5
128
+ signing_key:
129
+ specification_version: 3
130
+ summary: Rest for your work (queues)
131
+ test_files: []
132
+