sabbath 0.0.1

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