blobsterix 0.0.9
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/.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
|