blobsterix 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +27 -0
- data/CHANGELOG.txt +13 -0
- data/Gemfile +16 -0
- data/LICENSE +22 -0
- data/README.md +122 -0
- data/Rakefile +13 -0
- data/bin/blobsterix +152 -0
- data/bin/test +26 -0
- data/blobsterix.gemspec +39 -0
- data/config/lighttpd.conf +50 -0
- data/lib/blobsterix.rb +213 -0
- data/lib/blobsterix/blob/blob_api.rb +55 -0
- data/lib/blobsterix/blob/blob_url_helper.rb +55 -0
- data/lib/blobsterix/helper/accept_type.rb +62 -0
- data/lib/blobsterix/helper/blob_access.rb +73 -0
- data/lib/blobsterix/helper/config_loader.rb +33 -0
- data/lib/blobsterix/helper/data_response.rb +54 -0
- data/lib/blobsterix/helper/http.rb +47 -0
- data/lib/blobsterix/helper/logable.rb +11 -0
- data/lib/blobsterix/helper/murmur.rb +137 -0
- data/lib/blobsterix/helper/status_info.rb +42 -0
- data/lib/blobsterix/helper/template_renderer.rb +39 -0
- data/lib/blobsterix/mimemagic/magic.rb +138 -0
- data/lib/blobsterix/mimemagic/tables.rb +1770 -0
- data/lib/blobsterix/mimemagic/version.rb +5 -0
- data/lib/blobsterix/router/app_router.rb +134 -0
- data/lib/blobsterix/s3/s3_api.rb +92 -0
- data/lib/blobsterix/s3/s3_url_helper.rb +93 -0
- data/lib/blobsterix/service.rb +34 -0
- data/lib/blobsterix/status/status_api.rb +62 -0
- data/lib/blobsterix/status/status_url_helper.rb +11 -0
- data/lib/blobsterix/storage/blob_meta_data.rb +60 -0
- data/lib/blobsterix/storage/bucket.rb +36 -0
- data/lib/blobsterix/storage/bucket_entry.rb +29 -0
- data/lib/blobsterix/storage/bucket_list.rb +26 -0
- data/lib/blobsterix/storage/cache.rb +90 -0
- data/lib/blobsterix/storage/file_system.rb +132 -0
- data/lib/blobsterix/storage/file_system_meta_data.rb +136 -0
- data/lib/blobsterix/storage/storage.rb +30 -0
- data/lib/blobsterix/transformation/image_transformation.rb +439 -0
- data/lib/blobsterix/transformation/transformation.rb +30 -0
- data/lib/blobsterix/transformation/transformation_chain.rb +78 -0
- data/lib/blobsterix/transformation/transformation_manager.rb +115 -0
- data/lib/blobsterix/version.rb +3 -0
- data/scripts/download.rb +30 -0
- data/scripts/test +6 -0
- data/spec/lib/blob/blob_api_spec.rb +81 -0
- data/spec/lib/helper/blob_access_spec.rb +72 -0
- data/spec/lib/s3/s3_api_spec.rb +183 -0
- data/spec/lib/service_spec.rb +12 -0
- data/spec/lib/status/status_api_spec.rb +42 -0
- data/spec/lib/storage/cache_spec.rb +135 -0
- data/spec/lib/storage/file_system_spec.rb +84 -0
- data/spec/spec_helper.rb +139 -0
- data/templates/app/Gemfile +12 -0
- data/templates/app/Rakefile +7 -0
- data/templates/app/config.rb +61 -0
- data/templates/app/config/environments/development.rb +40 -0
- data/templates/app/config/environments/production.rb +40 -0
- data/templates/app/storages/.keep +0 -0
- data/templates/app/transformators/.keep +0 -0
- data/templates/app/views/.keep +0 -0
- data/templates/storage_template.rb +30 -0
- data/templates/transformation_template.rb +41 -0
- data/templates/views/error_page.erb +18 -0
- data/templates/views/status_page.erb +31 -0
- metadata +325 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module Jsonizer
|
3
|
+
def json_var(*var_names)
|
4
|
+
@json_vars = (@json_vars||[])+var_names.flatten
|
5
|
+
end
|
6
|
+
|
7
|
+
def json_vars
|
8
|
+
@json_vars||= []
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class AppRouterBase
|
13
|
+
|
14
|
+
extend Jsonizer
|
15
|
+
include Logable
|
16
|
+
|
17
|
+
#attr_reader :logger
|
18
|
+
attr_accessor :env
|
19
|
+
|
20
|
+
def initialize(env)
|
21
|
+
@env = env
|
22
|
+
#@logger = env["rack.logger"]
|
23
|
+
end
|
24
|
+
|
25
|
+
def storage
|
26
|
+
@storage ||= Blobsterix.storage
|
27
|
+
end
|
28
|
+
|
29
|
+
def cache
|
30
|
+
@cache ||= Blobsterix.cache
|
31
|
+
end
|
32
|
+
|
33
|
+
def transformation
|
34
|
+
@transformation ||= Blobsterix.transformation
|
35
|
+
end
|
36
|
+
|
37
|
+
def next_api
|
38
|
+
Http.NextApi
|
39
|
+
end
|
40
|
+
|
41
|
+
def renderer
|
42
|
+
@@renderer||=(Blobsterix.respond_to?(:env) && Blobsterix.env == :production) ? TemplateRenderer.new(binding) : ReloadTemplateRenderer.new(binding)
|
43
|
+
end
|
44
|
+
|
45
|
+
def render(template_name, status_code=200, bind=nil)
|
46
|
+
begin
|
47
|
+
Http.Response(status_code, renderer.render(template_name, bind||binding), "html")
|
48
|
+
rescue Errno::ENOENT => e
|
49
|
+
Http.NotFound
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def render_json(obj=nil)
|
54
|
+
Http.OK (obj||self).to_json, "json"
|
55
|
+
end
|
56
|
+
|
57
|
+
def render_xml(obj=nil)
|
58
|
+
obj = Nokogiri::XML::Builder.new do |xml|
|
59
|
+
yield xml
|
60
|
+
end if block_given?
|
61
|
+
Http.OK (obj||self).to_xml, "xml"
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_json
|
65
|
+
stuff = Hash.new
|
66
|
+
self.class.json_vars.each{|var_name|
|
67
|
+
stuff[var_name.to_sym]=send(var_name) if respond_to?(var_name)
|
68
|
+
}
|
69
|
+
stuff.to_json
|
70
|
+
end
|
71
|
+
def to_xml()
|
72
|
+
xml = Nokogiri::XML::Builder.new do |xml|
|
73
|
+
xml.BlobsterixStatus() {
|
74
|
+
self.class.json_vars.each{|var_name|
|
75
|
+
var = send(var_name)
|
76
|
+
var = var.to_xml if var.respond_to?(:to_xml)
|
77
|
+
xml.send(var_name, var) if respond_to?(var_name)
|
78
|
+
}
|
79
|
+
}
|
80
|
+
end
|
81
|
+
xml.to_xml
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.options(opt)
|
85
|
+
opt = {:function => opt.to_sym} if opt.class != Hash
|
86
|
+
{:controller => self.name, :function => :call}.merge(opt)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.get(path, opt = {})
|
90
|
+
path = Journey::Path::Pattern.new path
|
91
|
+
router.routes.add_route(lambda{|env| call_controller(options(opt), env)}, path, {:request_method => "GET"}, {})
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.post(path, opt = {})
|
95
|
+
path = Journey::Path::Pattern.new path
|
96
|
+
router.routes.add_route(lambda{|env| call_controller(options(opt), env)}, path, {:request_method => "POST"}, {})
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.put(path, opt = {})
|
100
|
+
path = Journey::Path::Pattern.new path
|
101
|
+
router.routes.add_route(lambda{|env| call_controller(options(opt), env)}, path, {:request_method => "PUT"}, {})
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.delete(path, opt = {})
|
105
|
+
path = Journey::Path::Pattern.new path
|
106
|
+
router.routes.add_route(lambda{|env| call_controller(options(opt), env)}, path, {:request_method => "DELETE"}, {})
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.head(path, opt = {})
|
110
|
+
path = Journey::Path::Pattern.new path
|
111
|
+
router.routes.add_route(lambda{|env| call_controller(options(opt), env)}, path, {:request_method => "HEAD"}, {})
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.call(env)
|
115
|
+
Blobsterix::StatusInfo.connections+=1
|
116
|
+
result=router.call(env)
|
117
|
+
Blobsterix::StatusInfo.connections-=1
|
118
|
+
result
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.call_controller(options, env)
|
122
|
+
options[:controller].respond_to?(options[:function]) ? options[:controller].send(options[:function], env) : Blobsterix.const_get(options[:controller]).new(env).send(options[:function])
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
def self.routes()
|
127
|
+
(@@routes ||= {})[self.name] ||= Journey::Routes.new
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.router()
|
131
|
+
(@@router ||= {})[self.name] ||= Journey::Router.new routes, {}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
class S3Api < AppRouterBase
|
3
|
+
include S3UrlHelper
|
4
|
+
|
5
|
+
get "/", :list_buckets
|
6
|
+
|
7
|
+
get "/*bucket_or_file.:format", :get_file
|
8
|
+
get "/*bucket_or_file", :get_file
|
9
|
+
|
10
|
+
head "/*bucket_or_file.:format", :get_file_head
|
11
|
+
head "/*bucket_or_file", :get_file_head
|
12
|
+
|
13
|
+
put "/", :create_bucket
|
14
|
+
|
15
|
+
put "/*file.:format", :upload_data
|
16
|
+
put "/*file", :upload_data
|
17
|
+
|
18
|
+
delete "/", :delete_bucket
|
19
|
+
delete "/*file.:format", :delete_file
|
20
|
+
delete "/*file", :delete_file
|
21
|
+
|
22
|
+
get "*any", :next_api
|
23
|
+
put "*any", :next_api
|
24
|
+
delete "*any", :next_api
|
25
|
+
head "*any", :next_api
|
26
|
+
post "*any", :next_api
|
27
|
+
|
28
|
+
private
|
29
|
+
def list_buckets
|
30
|
+
Blobsterix.event("s3_api.list_bucket",:bucket => bucket)
|
31
|
+
Http.OK storage.list(bucket).to_xml, "xml"
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_file(send_with_data=true)
|
35
|
+
return Http.NotFound if favicon
|
36
|
+
|
37
|
+
if bucket?
|
38
|
+
if meta = storage.get(bucket, file)
|
39
|
+
send_with_data ? meta.response(true, env["HTTP_IF_NONE_MATCH"], env, env["HTTP_X_FILE"] === "yes", false) : meta.response(false)
|
40
|
+
else
|
41
|
+
Http.NotFound
|
42
|
+
end
|
43
|
+
else
|
44
|
+
Http.OK storage.list(bucket).to_xml, "xml"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_file_head
|
49
|
+
#TODO: add event?
|
50
|
+
get_file(false)
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_bucket
|
54
|
+
Blobsterix.event("s3_api.upload",:bucket => bucket)
|
55
|
+
Http.OK storage.create(bucket), "xml"
|
56
|
+
end
|
57
|
+
|
58
|
+
def upload_data
|
59
|
+
source = cached_upload
|
60
|
+
accept = AcceptType.new("*/*")#source.accept_type()
|
61
|
+
|
62
|
+
trafo_current = trafo(trafo_string)
|
63
|
+
file_current = file
|
64
|
+
bucket_current = bucket
|
65
|
+
Blobsterix.event("s3_api.upload", :bucket => bucket_current,
|
66
|
+
:file => file_current, :accept_type => accept.type, :trafo => trafo_current)
|
67
|
+
blob_access=BlobAccess.new(:source => source, :bucket => bucket_current, :id => file_current, :accept_type => accept, :trafo => trafo_current)
|
68
|
+
data = transformation.run(blob_access)
|
69
|
+
cached_upload_clear
|
70
|
+
storage.put(bucket_current, file_current, data).response(false)
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete_bucket
|
74
|
+
Blobsterix.event("s3_api.delete_bucket", :bucket => bucket)
|
75
|
+
|
76
|
+
if bucket?
|
77
|
+
Http.OK_no_data storage.delete(bucket), "xml"
|
78
|
+
else
|
79
|
+
Http.NotFound "no such bucket"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete_file
|
84
|
+
Blobsterix.event("s3_api.delete_file", :bucket => bucket,:file => file)
|
85
|
+
if bucket?
|
86
|
+
Http.OK_no_data storage.delete_key(bucket, file), "xml"
|
87
|
+
else
|
88
|
+
Http.NotFound "no such bucket"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module S3UrlHelper
|
3
|
+
HOST_PATH = /(\w+)(\.s3)?\.\w+\.\w+/
|
4
|
+
def bucket_matcher(str)
|
5
|
+
if str.include?("s3")
|
6
|
+
str.match(/(\w+)\.s3\.\w+\.\w+/)
|
7
|
+
else
|
8
|
+
str.match(/(\w+)\.\w+\.\w+/)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def favicon
|
13
|
+
@favicon ||= file.match /favicon/
|
14
|
+
end
|
15
|
+
|
16
|
+
def cache_upload
|
17
|
+
cache.put(cache_upload_key, env['rack.input'].read)
|
18
|
+
end
|
19
|
+
|
20
|
+
def cached_upload
|
21
|
+
cache_upload if not cache.exists?(cache_upload_key)
|
22
|
+
cache.get(cache_upload_key)
|
23
|
+
end
|
24
|
+
|
25
|
+
def cached_upload_clear
|
26
|
+
cache.delete(cache_upload_key)
|
27
|
+
end
|
28
|
+
|
29
|
+
def cache_upload_key
|
30
|
+
#@cache_upload_key ||= "upload/"+bucket.gsub("/", "_")+"_"+file.gsub("/", "_")
|
31
|
+
@cache_upload_key ||= Blobsterix::BlobAccess.new(:bucket => bucket, :id => "upload_#{file.gsub("/", "_")}")
|
32
|
+
end
|
33
|
+
|
34
|
+
def trafo_string
|
35
|
+
@trafo ||= env["HTTP_X_AMZ_META_TRAFO"] || ""
|
36
|
+
end
|
37
|
+
|
38
|
+
#TransformationCommand
|
39
|
+
def trafo(trafo_s='')
|
40
|
+
trafo_a = []
|
41
|
+
trafo_s.split(",").each{|command|
|
42
|
+
parts = command.split("_")
|
43
|
+
key = parts.delete_at(0)
|
44
|
+
trafo_a << [key, parts.join("_")]
|
45
|
+
}
|
46
|
+
trafo_a
|
47
|
+
end
|
48
|
+
|
49
|
+
def bucket
|
50
|
+
host = bucket_matcher(env['HTTP_HOST'])
|
51
|
+
if host
|
52
|
+
host[1]
|
53
|
+
elsif (env[nil] && env[nil][:bucket])
|
54
|
+
env[nil][:bucket]
|
55
|
+
elsif (env[nil] && env[nil][:bucket_or_file])
|
56
|
+
if env[nil][:bucket_or_file].include?("/")
|
57
|
+
env[nil][:bucket_or_file].split("/")[0]
|
58
|
+
else
|
59
|
+
env[nil][:bucket_or_file]
|
60
|
+
end
|
61
|
+
else
|
62
|
+
"root"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def bucket?
|
67
|
+
host = bucket_matcher(env['HTTP_HOST'])
|
68
|
+
host || env[nil][:bucket] || included_bucket
|
69
|
+
end
|
70
|
+
|
71
|
+
def format
|
72
|
+
@format ||= env[nil][:format]
|
73
|
+
end
|
74
|
+
|
75
|
+
def included_bucket
|
76
|
+
if env[nil][:bucket_or_file] && env[nil][:bucket_or_file].include?("/")
|
77
|
+
env[nil][:bucket] = env[nil][:bucket_or_file].split("/")[0]
|
78
|
+
env[nil][:bucket_or_file] = env[nil][:bucket_or_file].gsub("#{env[nil][:bucket]}/", "")
|
79
|
+
true
|
80
|
+
else
|
81
|
+
false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def file
|
86
|
+
if format
|
87
|
+
[env[nil][:file] || env[nil][:bucket_or_file] || "", format].join(".")
|
88
|
+
else
|
89
|
+
env[nil][:file] || env[nil][:bucket_or_file] || ""
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
class Service < Goliath::API
|
3
|
+
use Goliath::Rack::Params
|
4
|
+
=begin
|
5
|
+
def on_headers(env, headers)
|
6
|
+
env.logger.info 'received headers: ' + headers.inspect
|
7
|
+
env['async-headers'] = headers
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_body(env, data)
|
11
|
+
env.logger.info 'received data: ' + data
|
12
|
+
(env['async-body'] ||= '') << data
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_close(env)
|
16
|
+
env.logger.info 'closing connection'
|
17
|
+
end
|
18
|
+
=end
|
19
|
+
def response(env)
|
20
|
+
call_stack(env, BlobApi, StatusApi, S3Api)
|
21
|
+
end
|
22
|
+
|
23
|
+
def call_stack(env, *apis)
|
24
|
+
last_answer = [404,{}, ""]
|
25
|
+
apis.each do |api|
|
26
|
+
last_answer = api.call(env)
|
27
|
+
if last_answer[0] != 600
|
28
|
+
return last_answer
|
29
|
+
end
|
30
|
+
end
|
31
|
+
last_answer[0] != 600 ? last_answer : [404,{}, ""]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
class StatusApi < AppRouterBase
|
3
|
+
include StatusUrlHelper
|
4
|
+
|
5
|
+
get "/status(.:format)", :status
|
6
|
+
|
7
|
+
get "*any", :next_api
|
8
|
+
put "*any", :next_api
|
9
|
+
delete "*any", :next_api
|
10
|
+
head "*any", :next_api
|
11
|
+
post "*any", :next_api
|
12
|
+
|
13
|
+
json_var :cache_hits, :cache_misses, :cache_errors, :cache_accesses, :connections, :cache_hit_rate, :ram_usage, :uptime
|
14
|
+
|
15
|
+
def status
|
16
|
+
case format
|
17
|
+
when :json
|
18
|
+
render_json
|
19
|
+
when :xml
|
20
|
+
render_xml
|
21
|
+
else
|
22
|
+
render "status_page"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def ram_usage
|
27
|
+
`pmap #{Process.pid} | tail -1`[10,40].strip
|
28
|
+
end
|
29
|
+
|
30
|
+
def uptime
|
31
|
+
@uptime||=StatusInfo.uptime
|
32
|
+
end
|
33
|
+
|
34
|
+
def cache_hits
|
35
|
+
@cache_hits||=StatusInfo.cache_hit
|
36
|
+
end
|
37
|
+
|
38
|
+
def cache_hit_rate
|
39
|
+
if cache_hits > 0 && cache_accesses > 0
|
40
|
+
cache_hits.to_f/cache_accesses.to_f
|
41
|
+
else
|
42
|
+
1.to_f
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def cache_misses
|
47
|
+
@cache_misses||=StatusInfo.cache_miss
|
48
|
+
end
|
49
|
+
|
50
|
+
def cache_errors
|
51
|
+
@cache_errors||=StatusInfo.cache_error
|
52
|
+
end
|
53
|
+
|
54
|
+
def cache_accesses
|
55
|
+
@cache_accesses||=StatusInfo.cache_access
|
56
|
+
end
|
57
|
+
|
58
|
+
def connections
|
59
|
+
@connections||=StatusInfo.connections
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module Storage
|
3
|
+
class BlobMetaData
|
4
|
+
def check(key)
|
5
|
+
false
|
6
|
+
end
|
7
|
+
def etag
|
8
|
+
""
|
9
|
+
end
|
10
|
+
def read
|
11
|
+
data()
|
12
|
+
end
|
13
|
+
def data
|
14
|
+
""
|
15
|
+
end
|
16
|
+
def path
|
17
|
+
""
|
18
|
+
end
|
19
|
+
def last_modified
|
20
|
+
end
|
21
|
+
def last_accessed
|
22
|
+
end
|
23
|
+
def payload
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
def header
|
27
|
+
{}
|
28
|
+
end
|
29
|
+
def mimetype
|
30
|
+
"*/*"
|
31
|
+
end
|
32
|
+
def mediatype
|
33
|
+
"*"
|
34
|
+
end
|
35
|
+
def size
|
36
|
+
0
|
37
|
+
end
|
38
|
+
def accept_type
|
39
|
+
AcceptType.new
|
40
|
+
end
|
41
|
+
def valid
|
42
|
+
false
|
43
|
+
end
|
44
|
+
def valid?
|
45
|
+
valid
|
46
|
+
end
|
47
|
+
def write
|
48
|
+
if block_given?
|
49
|
+
#should yield file
|
50
|
+
end
|
51
|
+
self
|
52
|
+
end
|
53
|
+
def delete
|
54
|
+
end
|
55
|
+
def response(with_data=true, _etag=nil, env = nil, xfile = false, allow_chunks=true)
|
56
|
+
Blobsterix::Http::DataResponse.new(self, with_data, _etag, env).call(xfile, allow_chunks)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|