scales-worker 0.0.1.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/bin/scales-worker +6 -0
- data/lib/scales-worker.rb +19 -0
- data/lib/scales-worker/application.rb +7 -0
- data/lib/scales-worker/application/rails.rb +38 -0
- data/lib/scales-worker/base.rb +52 -0
- data/lib/scales-worker/boot/autoload.rb +9 -0
- data/lib/scales-worker/boot/initializers/jsonpath.rb +1 -0
- data/lib/scales-worker/boot/initializers/nokogiri.rb +1 -0
- data/lib/scales-worker/boot/initializers/rake.rb +2 -0
- data/lib/scales-worker/cache.rb +28 -0
- data/lib/scales-worker/cache/destroy.rb +28 -0
- data/lib/scales-worker/cache/html.rb +72 -0
- data/lib/scales-worker/cache/json.rb +68 -0
- data/lib/scales-worker/cache/push.rb +35 -0
- data/lib/scales-worker/cache/update.rb +20 -0
- data/lib/scales-worker/cache/xml.rb +68 -0
- data/lib/scales-worker/job.rb +15 -0
- data/lib/scales-worker/path.rb +89 -0
- data/lib/scales-worker/pusher.rb +75 -0
- data/lib/scales-worker/response.rb +40 -0
- data/lib/scales-worker/status.rb +72 -0
- data/lib/scales-worker/version.rb +5 -0
- data/lib/scales-worker/worker.rb +85 -0
- data/lib/scales/up.rb +51 -0
- data/lib/scales/up/rails.rb +3 -0
- metadata +151 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Thomas Fankhauser
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/scales-worker
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "scales-worker/boot/autoload"
|
2
|
+
|
3
|
+
module Scales
|
4
|
+
module Worker
|
5
|
+
autoload :Application, "scales-worker/application"
|
6
|
+
autoload :Job, "scales-worker/job"
|
7
|
+
autoload :Response, "scales-worker/response"
|
8
|
+
autoload :Worker, "scales-worker/worker"
|
9
|
+
autoload :Pusher, "scales-worker/pusher"
|
10
|
+
autoload :Cache, "scales-worker/cache"
|
11
|
+
autoload :Path, "scales-worker/path"
|
12
|
+
autoload :Status, "scales-worker/status"
|
13
|
+
end
|
14
|
+
|
15
|
+
autoload :Up, "scales/up"
|
16
|
+
end
|
17
|
+
|
18
|
+
require "scales-worker/version"
|
19
|
+
require "scales-worker/base"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
module Application
|
4
|
+
module Rails
|
5
|
+
ENV['RAILS_ENV'] ||= Scales.env
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
@@app = nil
|
10
|
+
|
11
|
+
def initialize_app!
|
12
|
+
@@app ||= load_application.initialize!
|
13
|
+
raise "Could not load Rails Application" if @@app.nil?
|
14
|
+
@@app
|
15
|
+
end
|
16
|
+
alias_method :app, :initialize_app!
|
17
|
+
alias_method :initialize_environment!, :initialize_app!
|
18
|
+
|
19
|
+
def name
|
20
|
+
"Rails #{app.class.to_s.split("::").first} (#{ENV['RAILS_ENV']})"
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def load_application
|
26
|
+
before_modules = Object.constants
|
27
|
+
require './config/application.rb'
|
28
|
+
after_modules = Object.constants
|
29
|
+
delta_modules = after_modules - before_modules
|
30
|
+
|
31
|
+
Kernel.const_get(delta_modules.last)::Application
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def run!
|
6
|
+
Worker.new.work!
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Cache Methods
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def push(params)
|
16
|
+
Worker::Cache::Push.push(params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def append(params)
|
20
|
+
Worker::Cache.class_for(params).append(params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def prepend(params)
|
24
|
+
Worker::Cache.class_for(params).prepend(params)
|
25
|
+
end
|
26
|
+
|
27
|
+
def set(params)
|
28
|
+
Worker::Cache.class_for(params).set(params)
|
29
|
+
end
|
30
|
+
|
31
|
+
def replace(params)
|
32
|
+
Worker::Cache.class_for(params).replace(params)
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove(format, params)
|
36
|
+
Worker::Cache.class_for(format).remove(params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def destroy(*paths)
|
40
|
+
Worker::Cache::Destroy.destroy(*paths)
|
41
|
+
end
|
42
|
+
|
43
|
+
def update(*paths, params)
|
44
|
+
Worker::Cache::Update.update(*paths, params)
|
45
|
+
end
|
46
|
+
|
47
|
+
def get(path)
|
48
|
+
Storage::Sync::get_content(path)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'scales-core'
|
2
|
+
|
3
|
+
autoload :Rake, "scales-worker/boot/initializers/rake"
|
4
|
+
autoload :Nokogiri, "scales-worker/boot/initializers/nokogiri"
|
5
|
+
autoload :JsonPath, "scales-worker/boot/initializers/jsonpath"
|
6
|
+
|
7
|
+
module Rake
|
8
|
+
autoload :TaskLib, "scales-worker/boot/initializers/rake"
|
9
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'jsonpath'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'nokogiri'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
module Cache
|
4
|
+
autoload :HTML, "scales-worker/cache/html"
|
5
|
+
autoload :JSON, "scales-worker/cache/json"
|
6
|
+
autoload :XML, "scales-worker/cache/xml"
|
7
|
+
autoload :Push, "scales-worker/cache/push"
|
8
|
+
autoload :Destroy, "scales-worker/cache/destroy"
|
9
|
+
autoload :Update, "scales-worker/cache/update"
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def class_for params_or_format
|
14
|
+
params = params_or_format.is_a?(Symbol) ? { params_or_format => nil } : params_or_format
|
15
|
+
|
16
|
+
return HTML if params.keys.include?(:html)
|
17
|
+
return JSON if params.keys.include?(:json)
|
18
|
+
return XML if params.keys.include?(:xml)
|
19
|
+
end
|
20
|
+
|
21
|
+
def resource_or_partial?(path)
|
22
|
+
(path =~ /^\//) ? "resource" : "partial"
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
module Cache
|
4
|
+
module Destroy
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def destroy(*paths)
|
8
|
+
paths.each do |path|
|
9
|
+
publish_destroy(path)
|
10
|
+
Storage::Sync.del_content(path)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def publish_destroy(path)
|
17
|
+
data = {
|
18
|
+
:path => path,
|
19
|
+
:type => "destroy_#{Cache.resource_or_partial?(path)}"
|
20
|
+
}
|
21
|
+
Storage::Sync.connection.publish "scales_monitor_events", data.to_json
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
module Cache
|
4
|
+
module HTML
|
5
|
+
include Helper::ContentTypes
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def append(params = {})
|
10
|
+
html = params[:html]
|
11
|
+
path = params[:to] || params[:at]
|
12
|
+
change_content_at(path, params[:select]) do |selection|
|
13
|
+
selection.each{ |element| element.children.last.after html }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def prepend(params = {})
|
18
|
+
html = params[:html]
|
19
|
+
path = params[:to] || params[:at]
|
20
|
+
change_content_at(path, params[:select]) do |selection|
|
21
|
+
selection.each{ |element| element.children.first.before html }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def set(params = {})
|
26
|
+
html = params[:html]
|
27
|
+
path = params[:to] || params[:at]
|
28
|
+
change_content_at(path, params[:select]) do |selection|
|
29
|
+
selection.each{ |element| element.inner_html = html }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def replace(params = {})
|
34
|
+
html = params[:html]
|
35
|
+
path = params[:to] || params[:at]
|
36
|
+
change_content_at(path, params[:select]) do |selection|
|
37
|
+
selection.each{ |element| element.replace html }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def remove(params = {})
|
42
|
+
path = params[:to] || params[:at]
|
43
|
+
change_content_at(path, params[:select]) do |selection|
|
44
|
+
selection.each{ |element| element.remove }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def is_html_page?(page)
|
52
|
+
page.match /<html>/
|
53
|
+
end
|
54
|
+
|
55
|
+
def change_content_at path, selector
|
56
|
+
raise 'No path defined like this :to => "/tracks"' if path.nil?
|
57
|
+
raise 'No selector defined like this :select => "#tracks"' if selector.nil?
|
58
|
+
|
59
|
+
html = Storage::Sync.get_content(path)
|
60
|
+
html = is_html_page?(html) ? Nokogiri::HTML.parse(html) : Nokogiri::HTML.fragment(html)
|
61
|
+
|
62
|
+
yield html.css(selector)
|
63
|
+
|
64
|
+
Storage::Sync.set_content(path, html.inner_html)
|
65
|
+
html.inner_html
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
module Cache
|
4
|
+
module JSON
|
5
|
+
include Helper::ContentTypes
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def append(params = {})
|
10
|
+
json = params[:json]
|
11
|
+
path = params[:to] || params[:at]
|
12
|
+
change_content_at(path, params[:select]) do |selection, selector|
|
13
|
+
selection.gsub(selector){ |selection| selection << parse(json) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def prepend(params = {})
|
18
|
+
json = params[:json]
|
19
|
+
path = params[:to] || params[:at]
|
20
|
+
change_content_at(path, params[:select]) do |selection, selector|
|
21
|
+
selection.gsub(selector){ |selection| (selection.reverse << parse(json)).reverse }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def set(params = {})
|
26
|
+
json = params[:json]
|
27
|
+
path = params[:to] || params[:at]
|
28
|
+
change_content_at(path, params[:select]) do |selection, selector|
|
29
|
+
selection.gsub(selector){ |selection| selection = parse(json) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def replace(params = {})
|
34
|
+
set(params)
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove(params = {})
|
38
|
+
path = params[:to] || params[:at]
|
39
|
+
change_content_at(path, params[:select]) do |selection, selector|
|
40
|
+
selection.delete(selector)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def parse(json)
|
48
|
+
::JSON.parse(json)
|
49
|
+
end
|
50
|
+
|
51
|
+
def change_content_at path, selector
|
52
|
+
raise 'No path defined like this :to => "/tracks.json"' if path.nil?
|
53
|
+
raise 'No selector defined like this :select => "#tracks"' if selector.nil?
|
54
|
+
|
55
|
+
json = Storage::Sync.get_content(path)
|
56
|
+
json = JsonPath.for(json)
|
57
|
+
|
58
|
+
json = yield json, selector
|
59
|
+
|
60
|
+
Storage::Sync.set_content(path, json.to_hash.to_json)
|
61
|
+
json
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
module Cache
|
4
|
+
module Push
|
5
|
+
include Helper::ContentTypes
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def push(params = {})
|
10
|
+
path = params.delete(:to)
|
11
|
+
format, content = params.to_a.first
|
12
|
+
raise "Don't know where to push, missing :to => '/a/path' parameter" if path.nil?
|
13
|
+
raise "Unknown format :#{format}" if format.to_content_type.nil?
|
14
|
+
|
15
|
+
publish_push(path, format)
|
16
|
+
Storage::Sync.set_content(path, content)
|
17
|
+
content
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def publish_push(path, format)
|
23
|
+
data = {
|
24
|
+
:path => path,
|
25
|
+
:format => format.to_s.upcase,
|
26
|
+
:type => "push_#{Cache.resource_or_partial?(path)}"
|
27
|
+
}
|
28
|
+
Storage::Sync.connection.publish "scales_monitor_events", data.to_json
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
module Cache
|
4
|
+
module Update
|
5
|
+
include Helper::ContentTypes
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def update(*paths, params)
|
10
|
+
raise "Please define a format like this :format => :html" unless params.is_a?(Hash)
|
11
|
+
format = params.delete(:format)
|
12
|
+
raise "Unknown format :#{format}" if format.to_content_type.nil?
|
13
|
+
paths.each{ |path| Thread.current[:post_process_queue] << { :format => format, :to => path }}
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
module Cache
|
4
|
+
module XML
|
5
|
+
include Helper::ContentTypes
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def append(params = {})
|
10
|
+
xml = params[:xml]
|
11
|
+
path = params[:to] || params[:at]
|
12
|
+
change_content_at(path, params[:select]) do |selection|
|
13
|
+
selection.each{ |element| element.children.last.after xml }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def prepend(params = {})
|
18
|
+
xml = params[:xml]
|
19
|
+
path = params[:to] || params[:at]
|
20
|
+
change_content_at(path, params[:select]) do |selection|
|
21
|
+
selection.each{ |element| element.children.first.before xml }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def set(params = {})
|
26
|
+
xml = params[:xml]
|
27
|
+
path = params[:to] || params[:at]
|
28
|
+
change_content_at(path, params[:select]) do |selection|
|
29
|
+
selection.each{ |element| element.inner_html = xml }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def replace(params = {})
|
34
|
+
xml = params[:xml]
|
35
|
+
path = params[:to] || params[:at]
|
36
|
+
change_content_at(path, params[:select]) do |selection|
|
37
|
+
selection.each{ |element| element.replace xml }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def remove(params = {})
|
42
|
+
path = params[:to] || params[:at]
|
43
|
+
change_content_at(path, params[:select]) do |selection|
|
44
|
+
selection.each{ |element| element.remove }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def change_content_at path, selector
|
52
|
+
raise 'No path defined like this :to => "/tracks.xml"' if path.nil?
|
53
|
+
raise 'No selector defined like this :select => "/tracks"' if selector.nil?
|
54
|
+
|
55
|
+
xml = Storage::Sync.get_content(path)
|
56
|
+
xml = Nokogiri::XML.parse(xml)
|
57
|
+
|
58
|
+
yield xml.xpath(selector)
|
59
|
+
|
60
|
+
Storage::Sync.set_content(path, xml.inner_html)
|
61
|
+
xml.inner_html
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
module Path
|
4
|
+
include Helper::ContentTypes
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def parse_path(original_path)
|
9
|
+
path, query = original_path.split "?"
|
10
|
+
[original_path, path, query]
|
11
|
+
end
|
12
|
+
|
13
|
+
def with_options_to_env(options)
|
14
|
+
full, path, query = parse_path(options[:to])
|
15
|
+
content_type = options[:format].to_content_type
|
16
|
+
|
17
|
+
{
|
18
|
+
"GATEWAY_INTERFACE"=>"CGI/1.1",
|
19
|
+
"CONTENT_TYPE" => content_type,
|
20
|
+
"PATH_INFO"=>path,
|
21
|
+
"QUERY_STRING"=>query,
|
22
|
+
"REMOTE_ADDR"=>"127.0.0.1",
|
23
|
+
"REMOTE_HOST"=>"loopback",
|
24
|
+
"REQUEST_METHOD"=>"GET",
|
25
|
+
"REQUEST_URI"=>"http://localhost:3000#{full}",
|
26
|
+
"SCRIPT_NAME"=>"",
|
27
|
+
"SERVER_NAME"=>"localhost",
|
28
|
+
"SERVER_PORT"=>"3000",
|
29
|
+
"SERVER_PROTOCOL"=>"HTTP/1.1",
|
30
|
+
"SERVER_SOFTWARE"=>"Scales-Server #{Scales::Worker::VERSION}",
|
31
|
+
"HTTP_HOST"=>"localhost:3000",
|
32
|
+
"HTTP_USER_AGENT"=>"Scales-Worker #{Scales::Worker::VERSION}",
|
33
|
+
"HTTP_ACCEPT"=>"*/*",
|
34
|
+
"HTTP_COOKIE"=>"",
|
35
|
+
"HTTP_ACCEPT_LANGUAGE"=>"en-us",
|
36
|
+
"HTTP_ACCEPT_ENCODING"=>"gzip, deflate",
|
37
|
+
"HTTP_CONNECTION"=>"keep-alive",
|
38
|
+
"rack.version"=>[1, 1],
|
39
|
+
"rack.input"=>StringIO.new,
|
40
|
+
"rack.multithread"=>false,
|
41
|
+
"rack.multiprocess"=>false,
|
42
|
+
"rack.run_once"=>false,
|
43
|
+
"rack.url_scheme"=>"http",
|
44
|
+
"HTTP_VERSION"=>"HTTP/1.1",
|
45
|
+
"REQUEST_PATH"=>path,
|
46
|
+
"ORIGINAL_FULLPATH"=>full
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_env(options, env)
|
51
|
+
full, path, query = parse_path(options[:to])
|
52
|
+
content_type = options[:format].to_content_type rescue env["CONTENT_TYPE"]
|
53
|
+
{
|
54
|
+
"GATEWAY_INTERFACE"=>"CGI/1.1",
|
55
|
+
"CONTENT_TYPE" => content_type,
|
56
|
+
"PATH_INFO"=>path,
|
57
|
+
"QUERY_STRING"=>query,
|
58
|
+
"REMOTE_ADDR"=>"127.0.0.1",
|
59
|
+
"REMOTE_HOST"=>"loopback",
|
60
|
+
"REQUEST_METHOD"=>"GET",
|
61
|
+
"REQUEST_URI"=>"http://localhost:3000#{full}",
|
62
|
+
"SCRIPT_NAME"=>"",
|
63
|
+
"SERVER_NAME"=>"localhost",
|
64
|
+
"SERVER_PORT"=>"3000",
|
65
|
+
"SERVER_PROTOCOL"=>"HTTP/1.1",
|
66
|
+
"SERVER_SOFTWARE"=>"Scales-Server #{Scales::Worker::VERSION}",
|
67
|
+
"HTTP_HOST"=>"localhost:3000",
|
68
|
+
"HTTP_USER_AGENT"=>"Scales-Worker #{Scales::Worker::VERSION}",
|
69
|
+
"HTTP_ACCEPT"=>"*/*",
|
70
|
+
"HTTP_COOKIE"=>env["HTTP_COOKIE"],
|
71
|
+
"HTTP_ACCEPT_LANGUAGE"=>env["HTTP_ACCEPT_LANGUAGE"],
|
72
|
+
"HTTP_ACCEPT_ENCODING"=>env["HTTP_ACCEPT_ENCODING"],
|
73
|
+
"HTTP_CONNECTION"=>"keep-alive",
|
74
|
+
"rack.version"=>[1, 1],
|
75
|
+
"rack.input"=>StringIO.new,
|
76
|
+
"rack.multithread"=>false,
|
77
|
+
"rack.multiprocess"=>false,
|
78
|
+
"rack.run_once"=>false,
|
79
|
+
"rack.url_scheme"=>"http",
|
80
|
+
"HTTP_VERSION"=>"HTTP/1.1",
|
81
|
+
"REQUEST_PATH"=>path,
|
82
|
+
"ORIGINAL_FULLPATH"=>full
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
class Pusher
|
4
|
+
attr_reader :app
|
5
|
+
attr_reader :type
|
6
|
+
attr_reader :total
|
7
|
+
attr_reader :done
|
8
|
+
attr_reader :progress
|
9
|
+
|
10
|
+
def initialize(type = Application::Rails)
|
11
|
+
@type, @app = type, type.app
|
12
|
+
reset_progress!
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset_progress!
|
16
|
+
@total, @done = 0, 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def progress
|
20
|
+
((@done.to_f / @total.to_f) * 100).to_i rescue 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def process!(path)
|
24
|
+
env = Path.with_options_to_env(path)
|
25
|
+
|
26
|
+
response = @app.call(env)
|
27
|
+
response.last.close
|
28
|
+
|
29
|
+
Storage::Sync.set_content(path[:to], Response.to_string(response)) if path[:push]
|
30
|
+
|
31
|
+
env
|
32
|
+
end
|
33
|
+
|
34
|
+
def post_process!(env)
|
35
|
+
while path = Thread.current[:post_process_queue].pop
|
36
|
+
request = Path.to_env(path, env)
|
37
|
+
|
38
|
+
begin
|
39
|
+
response = @app.call(request)
|
40
|
+
response.last.close
|
41
|
+
rescue Exception => e
|
42
|
+
puts e
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Process a single path in thread
|
48
|
+
def process_push!(path)
|
49
|
+
Thread.current[:post_process_queue] = []
|
50
|
+
env = process!(path)
|
51
|
+
post_process!(env)
|
52
|
+
@done += 1
|
53
|
+
end
|
54
|
+
|
55
|
+
def push!(paths)
|
56
|
+
raise "No Paths added".red if paths.nil? or paths.empty?
|
57
|
+
|
58
|
+
puts "Environment: #{Scales.env}".green
|
59
|
+
puts "Application: #{@type.name}".green
|
60
|
+
puts "Path: #{Dir.pwd}".green
|
61
|
+
puts "Redis: #{Scales.config.host}:#{Scales.config.port}/#{Scales.config.database}".green
|
62
|
+
|
63
|
+
@total, @done = paths.size, 0
|
64
|
+
paths.each do |path|
|
65
|
+
print "Pushing paths: #{progress}% (#{@done}/#{@total})".green
|
66
|
+
process_push!(path)
|
67
|
+
print "\r"
|
68
|
+
end
|
69
|
+
|
70
|
+
puts "Pushing paths: #{progress}% (#{@done}/#{@total})".green
|
71
|
+
puts "Done.".green
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
module Response
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def to_job(id, response)
|
7
|
+
status, headers, chunked_body = response
|
8
|
+
|
9
|
+
headers = headers.dup.keep_if do |key, value|
|
10
|
+
key.match /^HTTP_/ or
|
11
|
+
[
|
12
|
+
'Location',
|
13
|
+
'Content-Type',
|
14
|
+
'Set-Cookie',
|
15
|
+
'REQUEST_METHOD',
|
16
|
+
'SCRIPT_NAME',
|
17
|
+
'PATH_INFO',
|
18
|
+
'QUERY_STRING',
|
19
|
+
'SERVER_NAME',
|
20
|
+
'SERVER_PORT'
|
21
|
+
].include?(key)
|
22
|
+
end
|
23
|
+
headers['scales.id'] = id
|
24
|
+
|
25
|
+
body = ""
|
26
|
+
chunked_body.each{ |chunk| body << chunk }
|
27
|
+
|
28
|
+
[status, headers, body]
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_string(response)
|
32
|
+
body = ""
|
33
|
+
response.last.each{ |chunk| body << chunk }
|
34
|
+
body
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
|
4
|
+
class Status
|
5
|
+
attr_reader :key, :id, :address, :port
|
6
|
+
|
7
|
+
def initialize address, port = nil
|
8
|
+
@id = SecureRandom.hex(8)
|
9
|
+
@key = "scales_worker_#{@id}"
|
10
|
+
@address, @port = address.to_s, port.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def start!
|
14
|
+
data = {
|
15
|
+
:id => @id,
|
16
|
+
:key => @key,
|
17
|
+
:type => "worker_started",
|
18
|
+
:spawned_at => Time.now.to_i,
|
19
|
+
:env => Scales.env,
|
20
|
+
:ip => @address,
|
21
|
+
:port => @port
|
22
|
+
}
|
23
|
+
json = JSON.generate(data)
|
24
|
+
|
25
|
+
Storage::Sync.connection.set(@key, json)
|
26
|
+
Storage::Sync.connection.publish("scales_monitor_events", json)
|
27
|
+
@already_stopped = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop!
|
31
|
+
return if @already_stopped
|
32
|
+
|
33
|
+
data = {
|
34
|
+
:id => @id,
|
35
|
+
:key => @key,
|
36
|
+
:type => "worker_stopped"
|
37
|
+
}
|
38
|
+
json = JSON.generate(data)
|
39
|
+
Storage::Sync.connection.del(@key)
|
40
|
+
Storage::Sync.connection.publish("scales_monitor_events", json)
|
41
|
+
@already_stopped = true
|
42
|
+
end
|
43
|
+
|
44
|
+
def took_request_from_queue!(job)
|
45
|
+
data = {
|
46
|
+
:id => job['scales.id'],
|
47
|
+
:worker_id => @id,
|
48
|
+
:type => "worker_took_request_from_queue",
|
49
|
+
:path => job['PATH_INFO'],
|
50
|
+
:method => job['REQUEST_METHOD']
|
51
|
+
}
|
52
|
+
json = JSON.generate(data)
|
53
|
+
Storage::Sync.connection.publish("scales_monitor_events", json)
|
54
|
+
end
|
55
|
+
|
56
|
+
def put_response_in_queue!(response)
|
57
|
+
data = {
|
58
|
+
:id => response[1]['scales.id'],
|
59
|
+
:worker_id => @id,
|
60
|
+
:type => "worker_put_response_in_queue",
|
61
|
+
:path => response[1]['PATH_INFO'],
|
62
|
+
:method => response[1]['REQUEST_METHOD'],
|
63
|
+
:status => response[0]
|
64
|
+
}
|
65
|
+
json = JSON.generate(data)
|
66
|
+
Storage::Sync.connection.publish("scales_monitor_events", json)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Scales
|
2
|
+
module Worker
|
3
|
+
class Worker
|
4
|
+
attr_reader :app
|
5
|
+
attr_reader :type
|
6
|
+
|
7
|
+
def initialize(type = Application::Rails)
|
8
|
+
@type, @app, @status = type, type.app, Status.new("localhost")
|
9
|
+
at_exit{ @status.stop! }
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(job)
|
13
|
+
Job.to_env(job)
|
14
|
+
end
|
15
|
+
|
16
|
+
def process!(job)
|
17
|
+
env = parse(job)
|
18
|
+
id = env['scales.id']
|
19
|
+
|
20
|
+
@status.took_request_from_queue!(env)
|
21
|
+
|
22
|
+
begin
|
23
|
+
response = @app.call(env)
|
24
|
+
response.last.close
|
25
|
+
[id, Response.to_job(id, response)]
|
26
|
+
rescue
|
27
|
+
[id, [500, {}, ""]]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def post_process!(job)
|
32
|
+
env = parse(job)
|
33
|
+
while path = Thread.current[:post_process_queue].pop
|
34
|
+
request = Path.to_env(path, env)
|
35
|
+
|
36
|
+
begin
|
37
|
+
response = @app.call(request)
|
38
|
+
response.last.close
|
39
|
+
rescue Exception => e
|
40
|
+
puts e
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Wait for a request, process it, publish the response and exit
|
46
|
+
def process_request!(should_wait_for_request_to_finish = false)
|
47
|
+
job = Scales::Queue::Sync.pop
|
48
|
+
id, response = nil, nil
|
49
|
+
|
50
|
+
Thread.abort_on_exception = true
|
51
|
+
thread = Thread.new do
|
52
|
+
Thread.current[:post_process_queue] = []
|
53
|
+
id, response = process!(job)
|
54
|
+
print "#{id} -> " + "#{response.first}".green + " - #{Thread.current[:post_process_queue].size} post jobs -> "
|
55
|
+
post_process!(job)
|
56
|
+
print "done".green + " - publishing -> "
|
57
|
+
@status.put_response_in_queue!(response)
|
58
|
+
Scales::PubSub::Sync.publish("scales_response_#{id}", JSON.generate(response))
|
59
|
+
puts "done".green
|
60
|
+
end
|
61
|
+
|
62
|
+
thread.join if should_wait_for_request_to_finish
|
63
|
+
|
64
|
+
[id, response]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Loop the processing of requests
|
68
|
+
def work!
|
69
|
+
@status.start!
|
70
|
+
|
71
|
+
puts "Environment: #{Scales.env}".green
|
72
|
+
puts "Application: #{@type.name}".green
|
73
|
+
puts "Path: #{Dir.pwd}".green
|
74
|
+
puts "Redis: #{Scales.config.host}:#{Scales.config.port}/#{Scales.config.database}".green
|
75
|
+
|
76
|
+
begin
|
77
|
+
loop{ process_request! }
|
78
|
+
rescue Interrupt => e
|
79
|
+
puts "Goodbye".green
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/scales/up.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Scales
|
2
|
+
class Up < ::Rake::TaskLib
|
3
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
4
|
+
include Helper::ContentTypes
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :app
|
8
|
+
attr_accessor :paths
|
9
|
+
|
10
|
+
def push format, options
|
11
|
+
paths << { :format => format, :push => true }.merge(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def update *new_paths, params
|
15
|
+
raise "Please define a format like this :format => :html" unless params.is_a?(Hash)
|
16
|
+
format = params.delete(:format)
|
17
|
+
raise "Unknown format :#{format}" if format.to_content_type.nil?
|
18
|
+
new_paths.each{ |path| paths << { :format => format, :to => path }}
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(*args)
|
22
|
+
@name = args.shift || :up
|
23
|
+
@paths = []
|
24
|
+
@app = nil
|
25
|
+
@pusher = Scales::Worker::Pusher.new
|
26
|
+
|
27
|
+
desc "Scale up task" unless ::Rake.application.last_comment
|
28
|
+
task name do
|
29
|
+
RakeFileUtils.send(:verbose, verbose) do
|
30
|
+
@app = Up.application.initialize_environment!
|
31
|
+
|
32
|
+
yield self if block_given?
|
33
|
+
|
34
|
+
@pusher.push!(@paths)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
@@application = Worker::Application::Rails
|
41
|
+
|
42
|
+
def application
|
43
|
+
@@application
|
44
|
+
end
|
45
|
+
|
46
|
+
def application=(application)
|
47
|
+
@@application = application
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scales-worker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.beta.1
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Thomas Fankhauser
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: scales-core
|
16
|
+
requirement: &70223081169640 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - =
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.0.1.beta.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70223081169640
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rails
|
27
|
+
requirement: &70223081168560 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.2.6
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70223081168560
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: sqlite3
|
38
|
+
requirement: &70223081167480 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.3.6
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70223081167480
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &70223081166840 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.11'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70223081166840
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rake
|
60
|
+
requirement: &70223081166000 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 0.9.2.2
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70223081166000
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: nokogiri
|
71
|
+
requirement: &70223081165260 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.5.5
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70223081165260
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: jsonpath
|
82
|
+
requirement: &70223081164100 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: 0.5.0
|
88
|
+
type: :runtime
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70223081164100
|
91
|
+
description: Super Scale Caching Framework - Worker
|
92
|
+
email:
|
93
|
+
- tommylefunk@googlemail.com
|
94
|
+
executables:
|
95
|
+
- scales-worker
|
96
|
+
extensions: []
|
97
|
+
extra_rdoc_files: []
|
98
|
+
files:
|
99
|
+
- LICENSE
|
100
|
+
- lib/scales/up/rails.rb
|
101
|
+
- lib/scales/up.rb
|
102
|
+
- lib/scales-worker/application/rails.rb
|
103
|
+
- lib/scales-worker/application.rb
|
104
|
+
- lib/scales-worker/base.rb
|
105
|
+
- lib/scales-worker/boot/autoload.rb
|
106
|
+
- lib/scales-worker/boot/initializers/jsonpath.rb
|
107
|
+
- lib/scales-worker/boot/initializers/nokogiri.rb
|
108
|
+
- lib/scales-worker/boot/initializers/rake.rb
|
109
|
+
- lib/scales-worker/cache/destroy.rb
|
110
|
+
- lib/scales-worker/cache/html.rb
|
111
|
+
- lib/scales-worker/cache/json.rb
|
112
|
+
- lib/scales-worker/cache/push.rb
|
113
|
+
- lib/scales-worker/cache/update.rb
|
114
|
+
- lib/scales-worker/cache/xml.rb
|
115
|
+
- lib/scales-worker/cache.rb
|
116
|
+
- lib/scales-worker/job.rb
|
117
|
+
- lib/scales-worker/path.rb
|
118
|
+
- lib/scales-worker/pusher.rb
|
119
|
+
- lib/scales-worker/response.rb
|
120
|
+
- lib/scales-worker/status.rb
|
121
|
+
- lib/scales-worker/version.rb
|
122
|
+
- lib/scales-worker/worker.rb
|
123
|
+
- lib/scales-worker.rb
|
124
|
+
- !binary |-
|
125
|
+
YmluL3NjYWxlcy13b3JrZXI=
|
126
|
+
homepage: http://itscales.org
|
127
|
+
licenses: []
|
128
|
+
post_install_message:
|
129
|
+
rdoc_options: []
|
130
|
+
require_paths:
|
131
|
+
- lib
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ! '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
140
|
+
requirements:
|
141
|
+
- - ! '>'
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: 1.3.1
|
144
|
+
requirements: []
|
145
|
+
rubyforge_project:
|
146
|
+
rubygems_version: 1.8.11
|
147
|
+
signing_key:
|
148
|
+
specification_version: 3
|
149
|
+
summary: Launches an instance of a rack app like Rails and processes jobs from the
|
150
|
+
request queue.
|
151
|
+
test_files: []
|