harbor 0.16.1

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.
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