harbor 0.16.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/Rakefile +76 -0
  2. data/bin/harbor +7 -0
  3. data/lib/harbor.rb +17 -0
  4. data/lib/harbor/accessor_injector.rb +30 -0
  5. data/lib/harbor/application.rb +172 -0
  6. data/lib/harbor/auth/basic.rb +51 -0
  7. data/lib/harbor/block_io.rb +63 -0
  8. data/lib/harbor/cache.rb +90 -0
  9. data/lib/harbor/cache/disk.rb +99 -0
  10. data/lib/harbor/cache/item.rb +48 -0
  11. data/lib/harbor/cache/memory.rb +35 -0
  12. data/lib/harbor/cascade.rb +75 -0
  13. data/lib/harbor/console.rb +34 -0
  14. data/lib/harbor/container.rb +134 -0
  15. data/lib/harbor/contrib/debug.rb +236 -0
  16. data/lib/harbor/contrib/session/data_mapper.rb +74 -0
  17. data/lib/harbor/daemon.rb +105 -0
  18. data/lib/harbor/errors.rb +49 -0
  19. data/lib/harbor/events.rb +45 -0
  20. data/lib/harbor/exception_notifier.rb +59 -0
  21. data/lib/harbor/file.rb +66 -0
  22. data/lib/harbor/file_store.rb +69 -0
  23. data/lib/harbor/file_store/file.rb +100 -0
  24. data/lib/harbor/file_store/local.rb +71 -0
  25. data/lib/harbor/file_store/mosso.rb +154 -0
  26. data/lib/harbor/file_store/mosso/private.rb +8 -0
  27. data/lib/harbor/generator.rb +56 -0
  28. data/lib/harbor/generator/help.rb +34 -0
  29. data/lib/harbor/generator/setup.rb +82 -0
  30. data/lib/harbor/generator/skeletons/basic/config.ru.skel +21 -0
  31. data/lib/harbor/generator/skeletons/basic/lib/appname.rb.skel +49 -0
  32. data/lib/harbor/generator/skeletons/basic/lib/appname/controllers/home.rb +9 -0
  33. data/lib/harbor/generator/skeletons/basic/lib/appname/views/home/index.html.erb.skel +23 -0
  34. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/application.html.erb.skel +48 -0
  35. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/exception.html.erb.skel +13 -0
  36. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/login.html.erb.skel +11 -0
  37. data/lib/harbor/generator/skeletons/basic/log/development.log +0 -0
  38. data/lib/harbor/hooks.rb +105 -0
  39. data/lib/harbor/json_cookies.rb +37 -0
  40. data/lib/harbor/layouts.rb +61 -0
  41. data/lib/harbor/locale.rb +50 -0
  42. data/lib/harbor/locales.txt +22 -0
  43. data/lib/harbor/logging.rb +39 -0
  44. data/lib/harbor/logging/appenders/email.rb +84 -0
  45. data/lib/harbor/logging/request_logger.rb +34 -0
  46. data/lib/harbor/mail_servers/abstract.rb +12 -0
  47. data/lib/harbor/mail_servers/sendmail.rb +19 -0
  48. data/lib/harbor/mail_servers/smtp.rb +25 -0
  49. data/lib/harbor/mail_servers/test.rb +17 -0
  50. data/lib/harbor/mailer.rb +96 -0
  51. data/lib/harbor/messages.rb +17 -0
  52. data/lib/harbor/mime.rb +206 -0
  53. data/lib/harbor/plugin.rb +52 -0
  54. data/lib/harbor/plugin_list.rb +38 -0
  55. data/lib/harbor/request.rb +138 -0
  56. data/lib/harbor/response.rb +281 -0
  57. data/lib/harbor/router.rb +112 -0
  58. data/lib/harbor/script.rb +155 -0
  59. data/lib/harbor/session.rb +75 -0
  60. data/lib/harbor/session/abstract.rb +27 -0
  61. data/lib/harbor/session/cookie.rb +17 -0
  62. data/lib/harbor/shellwords.rb +26 -0
  63. data/lib/harbor/test/mailer.rb +10 -0
  64. data/lib/harbor/test/request.rb +28 -0
  65. data/lib/harbor/test/response.rb +17 -0
  66. data/lib/harbor/test/session.rb +11 -0
  67. data/lib/harbor/test/test.rb +22 -0
  68. data/lib/harbor/version.rb +3 -0
  69. data/lib/harbor/view.rb +89 -0
  70. data/lib/harbor/view_context.rb +134 -0
  71. data/lib/harbor/view_context/helpers.rb +7 -0
  72. data/lib/harbor/view_context/helpers/cache.rb +77 -0
  73. data/lib/harbor/view_context/helpers/form.rb +34 -0
  74. data/lib/harbor/view_context/helpers/html.rb +26 -0
  75. data/lib/harbor/view_context/helpers/text.rb +120 -0
  76. data/lib/harbor/view_context/helpers/url.rb +11 -0
  77. data/lib/harbor/xml_view.rb +57 -0
  78. data/lib/harbor/zipped_io.rb +203 -0
  79. data/lib/vendor/cloudfiles-1.3.0/cloudfiles.rb +77 -0
  80. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/authentication.rb +46 -0
  81. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/connection.rb +280 -0
  82. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/container.rb +260 -0
  83. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/storage_object.rb +253 -0
  84. metadata +155 -0
@@ -0,0 +1,76 @@
1
+ require "rubygems"
2
+ require "pathname"
3
+ require "rake"
4
+ require "rake/rdoctask"
5
+ require "rake/testtask"
6
+
7
+ # Tests
8
+ task :default => [:test]
9
+
10
+ Rake::TestTask.new do |t|
11
+ t.libs << "test"
12
+ t.test_files = FileList["test/**/*_test.rb"]
13
+ t.verbose = true
14
+ end
15
+
16
+ # rdoc
17
+
18
+ # desc "Generate RDoc documentation"
19
+ # Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ # rdoc.options << '--line-numbers' << '--inline-source' <<
21
+ # '--main' << 'README' <<
22
+ # '--title' << 'Harbor Documentation' <<
23
+ # '--charset' << 'utf-8' << "--exclude" << "lib/harbor/generator/"
24
+ # rdoc.rdoc_dir = "doc"
25
+ # rdoc.rdoc_files.include 'README'
26
+ # rdoc.
27
+ # rdoc.rdoc_files.include('lib/rack.rb')
28
+ # rdoc.rdoc_files.include('lib/rack/*.rb')
29
+ # rdoc.rdoc_files.include('lib/rack/*/*.rb')
30
+ # end
31
+
32
+ task :rdoc do
33
+ sh <<-EOS.strip
34
+ rdoc -T harbor#{" --op " + ENV["OUTPUT_DIRECTORY"] if ENV["OUTPUT_DIRECTORY"]} --line-numbers --main README --title "Harbor Documentation" --exclude "lib/harbor/generator/*" lib/harbor.rb lib/harbor README
35
+ EOS
36
+ end
37
+
38
+ # Performance
39
+ task :perf => :performance
40
+ task :performance do
41
+ puts `ruby #{Pathname(__FILE__).dirname + "script/performance.rb"}`
42
+ end
43
+
44
+ # Gem
45
+
46
+ require "lib/harbor/version"
47
+ require "rake/gempackagetask"
48
+
49
+ NAME = "harbor"
50
+ SUMMARY = "Harbor Framework"
51
+ GEM_VERSION = Harbor::VERSION
52
+
53
+ spec = Gem::Specification.new do |s|
54
+ s.name = NAME
55
+ s.summary = s.description = SUMMARY
56
+ s.author = "Wieck Media"
57
+ s.homepage = "http://wiecklabs.com"
58
+ s.email = "dev@wieck.com"
59
+ s.version = GEM_VERSION
60
+ s.platform = Gem::Platform::RUBY
61
+ s.require_path = 'lib'
62
+ s.files = %w(Rakefile) + Dir.glob("lib/**/*")
63
+ s.executables = ['harbor']
64
+
65
+ s.add_dependency "rack", "~> 1.0.0"
66
+ s.add_dependency "erubis"
67
+ end
68
+
69
+ Rake::GemPackageTask.new(spec) do |pkg|
70
+ pkg.gem_spec = spec
71
+ end
72
+
73
+ desc "Install Harbor as a gem"
74
+ task :install => [:repackage] do
75
+ sh %{gem install pkg/#{NAME}-#{GEM_VERSION}}
76
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/harbor/generator')
4
+
5
+ command = ARGV.shift
6
+
7
+ Harbor::Generator.run('harbor', command, ARGV)
@@ -0,0 +1,17 @@
1
+ require "rubygems"
2
+ require "pathname"
3
+
4
+ $:.unshift(Pathname(__FILE__).dirname.expand_path)
5
+
6
+ require "harbor/version"
7
+ require "harbor/hooks"
8
+ require "harbor/file_store"
9
+ require "harbor/shellwords"
10
+ require "harbor/file"
11
+ require "harbor/container"
12
+ require "harbor/router"
13
+ require "harbor/application"
14
+ require "harbor/cascade"
15
+ require "harbor/plugin"
16
+ require "harbor/mime"
17
+ require "harbor/errors"
@@ -0,0 +1,30 @@
1
+ module Harbor
2
+ ##
3
+ # A simple module to facilitate accessor based injection. This is used by Harbor::Plugin
4
+ # to allow you to easily construct plugins from hashes, i.e.:
5
+ #
6
+ # <%= plugin("user/tabs", :user => @user)
7
+ #
8
+ # It can of course be used for anything, however:
9
+ #
10
+ # class Dog
11
+ # include Harbor::AccessorInjector
12
+ # attr_accessor :owner
13
+ # end
14
+ #
15
+ # dog = Dog.new.inject(:owner => "Tom")
16
+ # dog.owner # => "Tom"
17
+ ##
18
+ module AccessorInjector
19
+
20
+ def inject(options = {})
21
+ options.each do |key, value|
22
+ setter = "#{key}="
23
+ send(setter, value) if respond_to?(setter)
24
+ end
25
+
26
+ self
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,172 @@
1
+ gem "rack", "~> 1.0.0"
2
+ require "rack"
3
+
4
+ require "yaml"
5
+
6
+ require Pathname(__FILE__).dirname + "request"
7
+ require Pathname(__FILE__).dirname + "response"
8
+ require Pathname(__FILE__).dirname + "block_io"
9
+ require Pathname(__FILE__).dirname + "zipped_io"
10
+ require Pathname(__FILE__).dirname + "events"
11
+ require Pathname(__FILE__).dirname + "messages"
12
+
13
+ module Harbor
14
+ class Application
15
+
16
+ include Harbor::Events
17
+
18
+ ##
19
+ # Routes are defined in this method. Note that Harbor does not define any default routes,
20
+ # so you must reimplement this method in your application.
21
+ ##
22
+ def self.routes(services)
23
+ raise NotImplementedError.new("Your application must redefine #{self}::routes.")
24
+ end
25
+
26
+ attr_reader :router, :environment, :services
27
+
28
+ def initialize(services, *args)
29
+ unless services.is_a?(Harbor::Container)
30
+ raise ArgumentError.new("Harbor::Application#services must be a Harbor::Container")
31
+ end
32
+
33
+ @services = services
34
+
35
+ @router = (!args.empty? && !args[0].is_a?(String) && args[0].respond_to?(:match)) ? args.shift : self.class.routes(@services)
36
+ @environment = args.last || "development"
37
+ end
38
+
39
+ ##
40
+ # Request entry point called by Rack. It creates a request and response
41
+ # object based on the incoming request environment, checks for public
42
+ # files, and dispatches the request.
43
+ #
44
+ # It returns a rack response hash.
45
+ ##
46
+ def call(env)
47
+ env["APP_ENVIRONMENT"] = environment
48
+ request = Request.new(self, env)
49
+ response = Response.new(request)
50
+
51
+ catch(:abort_request) do
52
+ if file = find_public_file(request.path_info[1..-1])
53
+ response.cache(nil, ::File.mtime(file), 86400) do
54
+ response.stream_file(file)
55
+ end
56
+
57
+ return response.to_a
58
+ end
59
+
60
+ handler = @router.match(request)
61
+
62
+ dispatch_request(handler, request, response)
63
+ end
64
+
65
+ response.to_a
66
+ end
67
+
68
+ ##
69
+ # Request dispatch function, which handles 404's, exceptions,
70
+ # and logs requests.
71
+ ##
72
+ def dispatch_request(handler, request, response)
73
+ start = Time.now
74
+
75
+ return handle_not_found(request, response) unless handler
76
+
77
+ handler.call(request, response)
78
+ rescue StandardError, LoadError, SyntaxError => e
79
+ handle_exception(e, request, response)
80
+ ensure
81
+ raise_event(:request_complete, request, response, start, Time.now)
82
+ end
83
+
84
+ ##
85
+ # Method used to nicely handle cases where no routes or public files
86
+ # match the incoming request.
87
+ #
88
+ # By default, it will render "The page you requested could not be found".
89
+ #
90
+ # To use a custom 404 message, create a view "exceptions/404.html.erb", and
91
+ # optionally create a view "layouts/exception.html.erb" to style it.
92
+ ##
93
+ def handle_not_found(request, response)
94
+ response.flush
95
+ response.status = 404
96
+
97
+ response.layout = "layouts/exception" if Harbor::View.exists?("layouts/exception")
98
+
99
+ if Harbor::View.exists?("exceptions/404.html.erb")
100
+ response.render "exceptions/404.html.erb"
101
+ else
102
+ response.puts "The page you requested could not be found"
103
+ end
104
+
105
+ raise_event(:not_found, request, response)
106
+ end
107
+
108
+ ##
109
+ # Method used to nicely handle uncaught exceptions.
110
+ #
111
+ # Logs full error messages to the configured 'error' logger.
112
+ #
113
+ # By default, it will render "We're sorry, but something went wrong."
114
+ #
115
+ # To use a custom 500 message, create a view "exceptions/500.html.erb", and
116
+ # optionally create a view "layouts/exception.html.erb" to style it.
117
+ ##
118
+ def handle_exception(exception, request, response)
119
+ response.flush
120
+ response.status = 500
121
+
122
+ trace = build_exception_trace(exception, request)
123
+
124
+ if environment == "development"
125
+ response.content_type = "text/html"
126
+ response.puts(Rack::ShowExceptions.new(nil).pretty(request.env, exception))
127
+ else
128
+ response.layout = "layouts/exception" if Harbor::View.exists?("layouts/exception")
129
+
130
+ if Harbor::View.exists?("exceptions/500.html.erb")
131
+ response.render "exceptions/500.html.erb", :exception => exception
132
+ else
133
+ response.puts "We're sorry, but something went wrong."
134
+ end
135
+ end
136
+
137
+ raise_event(:exception, exception, request, response, trace)
138
+
139
+ nil
140
+ end
141
+
142
+ def find_public_file(file) #:nodoc:
143
+ public_path = Pathname(self.class.respond_to?(:public_path) ? self.class.public_path : "public")
144
+ path = public_path + file
145
+
146
+ path.file? ? path : nil
147
+ end
148
+
149
+ def default_layout
150
+ warn "Harbor::Application#default_layout has been deprecated. See Harbor::Layouts."
151
+ end
152
+
153
+ private
154
+
155
+ def build_exception_trace(exception, request)
156
+ trace = ""
157
+ trace << "="*80
158
+ trace << "\n"
159
+ trace << "== [ #{self.class}: #{exception} @ #{Time.now} ] =="
160
+ trace << "\n"
161
+ trace << exception.backtrace.join("\n")
162
+ trace << "\n"
163
+ trace << "== [ Request ] =="
164
+ trace << "\n"
165
+ trace << request.env.to_yaml
166
+ trace << "\n"
167
+ trace << "="*80
168
+ trace << "\n"
169
+ end
170
+
171
+ end
172
+ end
@@ -0,0 +1,51 @@
1
+ module Harbor
2
+ module Auth
3
+ ##
4
+ # Simple mechanism for implementing HTTP basic authentication.
5
+ #
6
+ # get("/secret-page") do |request, response|
7
+ # Harbor::Auth::Basic.authenticate(request, response) { |username, password| username == "wieck" }
8
+ #
9
+ # response.puts "Secret Stuff"
10
+ # end
11
+ ##
12
+ class Basic
13
+
14
+ AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION']
15
+
16
+ attr_accessor :realm
17
+
18
+ ##
19
+ # Checks the credentials provided in the request against the provided
20
+ # block. If the block returns false, the request aborted.
21
+ ##
22
+ def self.authenticate(request, response) #:yields: username, password
23
+ auth = new(request)
24
+
25
+ unless auth.provided? && yield(auth.credentials)
26
+ response.headers["WWW-Authenticate"] = 'Basic realm="%s"' % auth.realm
27
+ response.unauthorized!
28
+ end
29
+ end
30
+
31
+ def initialize(request)
32
+ @request = request
33
+ end
34
+
35
+ def provided?
36
+ !!authorization_key
37
+ end
38
+
39
+ def credentials
40
+ @request.env[authorization_key].split(' ', 2).last.unpack("m*").first.split(/:/, 2)
41
+ end
42
+
43
+ private
44
+
45
+ def authorization_key
46
+ @authorization_key ||= AUTHORIZATION_KEYS.detect { |key| @request.env.has_key?(key) }
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,63 @@
1
+ module Harbor
2
+ ##
3
+ # Used by Harbor::Response#send_file and Harbor::Response#stream_file to send
4
+ # large files or streams in chunks. This is a fallback measure for the cases
5
+ # where X-Sendfile cannot be used.
6
+ ##
7
+ class BlockIO
8
+
9
+ def self.block_size
10
+ @@block_size ||= 500_000 # 500kb
11
+ end
12
+
13
+ def self.block_size=(value)
14
+ raise ArgumentError.new("Harbor::BlockIO::block_size value must be a Fixnum") unless value.is_a?(Fixnum)
15
+ @@block_size = value
16
+ end
17
+
18
+ def initialize(path_or_io)
19
+ case path_or_io
20
+ when ::IO
21
+ @io = path_or_io
22
+ @size = @io.stat.size
23
+ when StringIO
24
+ @io = path_or_io
25
+ @size = @io.size
26
+ when Harbor::FileStore::File
27
+ @io = path_or_io
28
+ @size = @io.size
29
+ @path = path_or_io.absolute_path
30
+ when Pathname
31
+ @path = path_or_io.expand_path.to_s
32
+ @io = path_or_io.open('r')
33
+ @size = path_or_io.size
34
+ else
35
+ @path = path_or_io.to_s
36
+ @io = ::File::open(@path, 'r')
37
+ @size = ::File.size(@path)
38
+ end
39
+ end
40
+
41
+ def path
42
+ @path
43
+ end
44
+
45
+ def to_s
46
+ @io.read
47
+ end
48
+
49
+ def size
50
+ @size
51
+ end
52
+
53
+ def close
54
+ @io.close
55
+ end
56
+
57
+ def each
58
+ while data = @io.read(Harbor::BlockIO::block_size) do
59
+ yield data
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,90 @@
1
+ require "thread"
2
+ require Pathname(__FILE__).dirname + "cache/item"
3
+
4
+ module Harbor
5
+
6
+ class Cache
7
+
8
+ class PutArgumentError < ArgumentError; end
9
+
10
+ attr_accessor :logger
11
+
12
+ def initialize(store)
13
+ raise ArgumentError.new("Harbor::Cache.new expects a non-null 'store' parameter") unless store
14
+
15
+ @store = store
16
+ @semaphore = Mutex.new
17
+ end
18
+
19
+ def put(key, content, ttl, maximum_age = nil)
20
+ raise PutArgumentError.new("Harbor::Cache::Memory#put expects a String value for 'key', got #{key}") unless key.is_a?(String)
21
+ raise PutArgumentError.new("Harbor::Cache::Memory#put expects a Fixnum value greater than 0 for 'ttl', got #{ttl}") unless ttl.is_a?(Fixnum) && ttl > 0
22
+ raise PutArgumentError.new("Harbor::Cache::Memory#put expects nil, or a Fixnum value greater than 0 for 'maximum_age', got #{maximum_age}") unless maximum_age.nil? || (maximum_age.is_a?(Fixnum) && maximum_age > 0)
23
+ raise PutArgumentError.new("Harbor::Cache::Memory#put expects a maximum_age greater than the ttl, got ttl: #{ttl}, maximum_age: #{maximum_age}") if maximum_age && ttl && (maximum_age <= ttl)
24
+
25
+ @semaphore.synchronize do
26
+ # Prevent multiple writes of similar content to the cache
27
+ return true if (cached_item = @store.get(key)) && cached_item.fresh? && cached_item.content.hash == content.hash
28
+ @store.put(key, ttl, maximum_age, content, Time.now)
29
+ end
30
+ rescue
31
+ log("Harbor::Cache#put unable to store cached content.", $!)
32
+
33
+ raise if $!.is_a?(PutArgumentError)
34
+ ensure
35
+ content
36
+ end
37
+
38
+ def get(key)
39
+ if item = @store.get(key)
40
+ if item.fresh?
41
+ @semaphore.synchronize do
42
+ @store.bump(key)
43
+ end
44
+
45
+ item
46
+ else
47
+ delete(key)
48
+
49
+ item = nil
50
+ end
51
+ else
52
+ item = nil
53
+ end
54
+ rescue
55
+ log("Harbor::Cache#get unable to retrieve cached content.", $!)
56
+ ensure
57
+ defined?(item) ? item : nil
58
+ end
59
+
60
+ def delete(key)
61
+ @semaphore.synchronize do
62
+ @store.delete(key)
63
+ end
64
+ rescue
65
+ log("Harbor::Cache#put unable to delete cached content.", $!)
66
+ ensure
67
+ nil
68
+ end
69
+
70
+ def delete_matching(key)
71
+ @semaphore.synchronize do
72
+ @store.delete_matching(key)
73
+ end
74
+ rescue
75
+ log("Harbor::Cache#put unable to delete cached content.", $!)
76
+ ensure
77
+ nil
78
+ end
79
+
80
+ private
81
+
82
+ def log(message, error)
83
+ if @logger
84
+ @logger.fatal("#{message} #{$!}\n#{$!.message}\nBacktrace:\n#{$!.backtrace.join("\n")}")
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ end