oahu-dragonfly 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. data/.rspec +1 -0
  2. data/.yardopts +24 -0
  3. data/Gemfile +30 -0
  4. data/History.md +323 -0
  5. data/LICENSE +20 -0
  6. data/README.md +88 -0
  7. data/Rakefile +50 -0
  8. data/VERSION +1 -0
  9. data/config.ru +14 -0
  10. data/docs.watchr +1 -0
  11. data/dragonfly.gemspec +297 -0
  12. data/extra_docs/Analysers.md +66 -0
  13. data/extra_docs/Caching.md +23 -0
  14. data/extra_docs/Configuration.md +124 -0
  15. data/extra_docs/Couch.md +49 -0
  16. data/extra_docs/DataStorage.md +153 -0
  17. data/extra_docs/Encoding.md +67 -0
  18. data/extra_docs/GeneralUsage.md +121 -0
  19. data/extra_docs/Generators.md +60 -0
  20. data/extra_docs/Heroku.md +50 -0
  21. data/extra_docs/ImageMagick.md +125 -0
  22. data/extra_docs/Index.md +33 -0
  23. data/extra_docs/MimeTypes.md +40 -0
  24. data/extra_docs/Models.md +272 -0
  25. data/extra_docs/Mongo.md +45 -0
  26. data/extra_docs/Processing.md +77 -0
  27. data/extra_docs/Rack.md +52 -0
  28. data/extra_docs/Rails2.md +57 -0
  29. data/extra_docs/Rails3.md +62 -0
  30. data/extra_docs/Sinatra.md +25 -0
  31. data/extra_docs/URLs.md +169 -0
  32. data/features/images.feature +47 -0
  33. data/features/no_processing.feature +14 -0
  34. data/features/rails_3.0.5.feature +8 -0
  35. data/features/steps/common_steps.rb +8 -0
  36. data/features/steps/dragonfly_steps.rb +66 -0
  37. data/features/steps/rails_steps.rb +28 -0
  38. data/features/support/env.rb +13 -0
  39. data/features/support/setup.rb +32 -0
  40. data/fixtures/rails_3.0.5/files/app/models/album.rb +7 -0
  41. data/fixtures/rails_3.0.5/files/app/views/albums/new.html.erb +7 -0
  42. data/fixtures/rails_3.0.5/files/app/views/albums/show.html.erb +6 -0
  43. data/fixtures/rails_3.0.5/files/config/initializers/dragonfly.rb +4 -0
  44. data/fixtures/rails_3.0.5/files/features/manage_album_images.feature +38 -0
  45. data/fixtures/rails_3.0.5/files/features/step_definitions/helper_steps.rb +7 -0
  46. data/fixtures/rails_3.0.5/files/features/step_definitions/image_steps.rb +25 -0
  47. data/fixtures/rails_3.0.5/files/features/support/paths.rb +17 -0
  48. data/fixtures/rails_3.0.5/files/features/text_images.feature +7 -0
  49. data/fixtures/rails_3.0.5/template.rb +20 -0
  50. data/irbrc.rb +18 -0
  51. data/lib/dragonfly.rb +55 -0
  52. data/lib/dragonfly/active_model_extensions.rb +13 -0
  53. data/lib/dragonfly/active_model_extensions/attachment.rb +250 -0
  54. data/lib/dragonfly/active_model_extensions/attachment_class_methods.rb +148 -0
  55. data/lib/dragonfly/active_model_extensions/class_methods.rb +95 -0
  56. data/lib/dragonfly/active_model_extensions/instance_methods.rb +28 -0
  57. data/lib/dragonfly/active_model_extensions/validations.rb +41 -0
  58. data/lib/dragonfly/analyser.rb +58 -0
  59. data/lib/dragonfly/analysis/file_command_analyser.rb +32 -0
  60. data/lib/dragonfly/analysis/image_magick_analyser.rb +6 -0
  61. data/lib/dragonfly/app.rb +172 -0
  62. data/lib/dragonfly/config/heroku.rb +19 -0
  63. data/lib/dragonfly/config/image_magick.rb +6 -0
  64. data/lib/dragonfly/config/rails.rb +20 -0
  65. data/lib/dragonfly/configurable.rb +207 -0
  66. data/lib/dragonfly/core_ext/array.rb +7 -0
  67. data/lib/dragonfly/core_ext/hash.rb +7 -0
  68. data/lib/dragonfly/core_ext/object.rb +12 -0
  69. data/lib/dragonfly/core_ext/string.rb +9 -0
  70. data/lib/dragonfly/core_ext/symbol.rb +9 -0
  71. data/lib/dragonfly/data_storage.rb +9 -0
  72. data/lib/dragonfly/data_storage/couch_data_store.rb +64 -0
  73. data/lib/dragonfly/data_storage/file_data_store.rb +141 -0
  74. data/lib/dragonfly/data_storage/mongo_data_store.rb +86 -0
  75. data/lib/dragonfly/data_storage/s3data_store.rb +145 -0
  76. data/lib/dragonfly/encoder.rb +13 -0
  77. data/lib/dragonfly/encoding/image_magick_encoder.rb +6 -0
  78. data/lib/dragonfly/function_manager.rb +71 -0
  79. data/lib/dragonfly/generation/image_magick_generator.rb +6 -0
  80. data/lib/dragonfly/generator.rb +9 -0
  81. data/lib/dragonfly/hash_with_css_style_keys.rb +21 -0
  82. data/lib/dragonfly/image_magick/analyser.rb +51 -0
  83. data/lib/dragonfly/image_magick/config.rb +41 -0
  84. data/lib/dragonfly/image_magick/encoder.rb +57 -0
  85. data/lib/dragonfly/image_magick/generator.rb +145 -0
  86. data/lib/dragonfly/image_magick/processor.rb +99 -0
  87. data/lib/dragonfly/image_magick/utils.rb +72 -0
  88. data/lib/dragonfly/image_magick_utils.rb +4 -0
  89. data/lib/dragonfly/job.rb +451 -0
  90. data/lib/dragonfly/job_builder.rb +39 -0
  91. data/lib/dragonfly/job_definitions.rb +26 -0
  92. data/lib/dragonfly/job_endpoint.rb +15 -0
  93. data/lib/dragonfly/loggable.rb +28 -0
  94. data/lib/dragonfly/middleware.rb +20 -0
  95. data/lib/dragonfly/processing/image_magick_processor.rb +6 -0
  96. data/lib/dragonfly/processor.rb +9 -0
  97. data/lib/dragonfly/rails/images.rb +27 -0
  98. data/lib/dragonfly/response.rb +97 -0
  99. data/lib/dragonfly/routed_endpoint.rb +40 -0
  100. data/lib/dragonfly/serializer.rb +32 -0
  101. data/lib/dragonfly/server.rb +113 -0
  102. data/lib/dragonfly/simple_cache.rb +23 -0
  103. data/lib/dragonfly/temp_object.rb +175 -0
  104. data/lib/dragonfly/url_mapper.rb +78 -0
  105. data/samples/beach.png +0 -0
  106. data/samples/egg.png +0 -0
  107. data/samples/round.gif +0 -0
  108. data/samples/sample.docx +0 -0
  109. data/samples/taj.jpg +0 -0
  110. data/spec/dragonfly/active_model_extensions/model_spec.rb +1426 -0
  111. data/spec/dragonfly/active_model_extensions/spec_helper.rb +91 -0
  112. data/spec/dragonfly/analyser_spec.rb +123 -0
  113. data/spec/dragonfly/analysis/file_command_analyser_spec.rb +48 -0
  114. data/spec/dragonfly/app_spec.rb +135 -0
  115. data/spec/dragonfly/configurable_spec.rb +461 -0
  116. data/spec/dragonfly/core_ext/array_spec.rb +19 -0
  117. data/spec/dragonfly/core_ext/hash_spec.rb +19 -0
  118. data/spec/dragonfly/core_ext/string_spec.rb +17 -0
  119. data/spec/dragonfly/core_ext/symbol_spec.rb +17 -0
  120. data/spec/dragonfly/data_storage/couch_data_store_spec.rb +76 -0
  121. data/spec/dragonfly/data_storage/file_data_store_spec.rb +296 -0
  122. data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +57 -0
  123. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +258 -0
  124. data/spec/dragonfly/data_storage/shared_data_store_examples.rb +77 -0
  125. data/spec/dragonfly/function_manager_spec.rb +154 -0
  126. data/spec/dragonfly/hash_with_css_style_keys_spec.rb +24 -0
  127. data/spec/dragonfly/image_magick/analyser_spec.rb +64 -0
  128. data/spec/dragonfly/image_magick/encoder_spec.rb +41 -0
  129. data/spec/dragonfly/image_magick/generator_spec.rb +172 -0
  130. data/spec/dragonfly/image_magick/processor_spec.rb +233 -0
  131. data/spec/dragonfly/image_magick/utils_spec.rb +18 -0
  132. data/spec/dragonfly/job_builder_spec.rb +37 -0
  133. data/spec/dragonfly/job_definitions_spec.rb +35 -0
  134. data/spec/dragonfly/job_endpoint_spec.rb +173 -0
  135. data/spec/dragonfly/job_spec.rb +1046 -0
  136. data/spec/dragonfly/loggable_spec.rb +80 -0
  137. data/spec/dragonfly/middleware_spec.rb +47 -0
  138. data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
  139. data/spec/dragonfly/serializer_spec.rb +61 -0
  140. data/spec/dragonfly/server_spec.rb +278 -0
  141. data/spec/dragonfly/simple_cache_spec.rb +27 -0
  142. data/spec/dragonfly/temp_object_spec.rb +306 -0
  143. data/spec/dragonfly/url_mapper_spec.rb +126 -0
  144. data/spec/functional/deprecations_spec.rb +51 -0
  145. data/spec/functional/image_magick_app_spec.rb +27 -0
  146. data/spec/functional/model_urls_spec.rb +85 -0
  147. data/spec/functional/remote_on_the_fly_spec.rb +51 -0
  148. data/spec/functional/to_response_spec.rb +31 -0
  149. data/spec/spec_helper.rb +51 -0
  150. data/spec/support/argument_matchers.rb +19 -0
  151. data/spec/support/image_matchers.rb +47 -0
  152. data/spec/support/simple_matchers.rb +53 -0
  153. data/yard/handlers/configurable_attr_handler.rb +38 -0
  154. data/yard/setup.rb +15 -0
  155. data/yard/templates/default/fulldoc/html/css/common.css +107 -0
  156. data/yard/templates/default/layout/html/layout.erb +89 -0
  157. data/yard/templates/default/module/html/configuration_summary.erb +31 -0
  158. data/yard/templates/default/module/setup.rb +17 -0
  159. metadata +544 -0
@@ -0,0 +1,39 @@
1
+ module Dragonfly
2
+ class JobBuilder
3
+
4
+ def initialize(&block)
5
+ @block = block
6
+ end
7
+
8
+ def build(job, *args)
9
+ evaluate_block(job, false, *args)
10
+ end
11
+
12
+ def build!(job, *args)
13
+ evaluate_block(job, true, *args)
14
+ end
15
+
16
+ Job.step_names.each do |step|
17
+
18
+ # fetch, process, etc.
19
+ define_method step do |*args|
20
+ if @perform_with_bangs
21
+ @job.send("#{step}!", *args)
22
+ else
23
+ @job = @job.send(step, *args)
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ private
30
+
31
+ def evaluate_block(job, perform_with_bangs, *args)
32
+ @job = job
33
+ @perform_with_bangs = perform_with_bangs
34
+ instance_exec(*args, &@block)
35
+ @job
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ module Dragonfly
2
+ class JobDefinitions < Module
3
+
4
+ def initialize
5
+ @job_definitions = {}
6
+ end
7
+
8
+ def add(name, &definition_proc)
9
+ job_definitions[name] = JobBuilder.new(&definition_proc)
10
+ jd = job_definitions # Needed because we're about to change 'self'
11
+
12
+ define_method name do |*args|
13
+ jd[name].build(self, *args)
14
+ end
15
+
16
+ define_method "#{name}!" do |*args|
17
+ jd[name].build!(self, *args)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :job_definitions
24
+
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ module Dragonfly
2
+ class JobEndpoint
3
+
4
+ def initialize(job)
5
+ @job = job
6
+ end
7
+
8
+ def call(env={})
9
+ Response.new(job, env).to_response
10
+ end
11
+
12
+ attr_reader :job
13
+
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ require 'logger'
2
+
3
+ module Dragonfly
4
+ module Loggable
5
+
6
+ def log
7
+ case @log_object
8
+ when nil
9
+ @log_object = Logger.new($stdout)
10
+ when Proc
11
+ @log_object[]
12
+ when Logger
13
+ @log_object
14
+ end
15
+ end
16
+
17
+ def log=(object)
18
+ @log_object = object
19
+ end
20
+
21
+ attr_reader :log_object
22
+
23
+ def use_same_log_as(object)
24
+ self.log = proc{ object.log }
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ module Dragonfly
2
+ class Middleware
3
+
4
+ def initialize(app, dragonfly_app_name, deprecated_arg=nil)
5
+ raise ArgumentError, "mounting Dragonfly::Middleware with a mount point is deprecated - just use Dragonfly::Middleware, #{dragonfly_app_name.inspect}" if deprecated_arg
6
+ @app = app
7
+ @dragonfly_app_name = dragonfly_app_name
8
+ end
9
+
10
+ def call(env)
11
+ response = Dragonfly[@dragonfly_app_name].call(env)
12
+ if response[1]['X-Cascade'] == 'pass'
13
+ @app.call(env)
14
+ else
15
+ response
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ module Dragonfly
2
+ module Processing
3
+ puts "WARNING: Dragonfly::Processing::ImageMagickProcessor is DEPRECATED and will soon be removed. Please use Dragonfly::ImageMagick::Processor instead."
4
+ ImageMagickProcessor = ImageMagick::Processor
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Dragonfly
2
+ class Processor < FunctionManager
3
+
4
+ def process(temp_object, method, *args)
5
+ call_last(method, temp_object, *args)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ require 'dragonfly'
2
+ require 'uri'
3
+
4
+ ### The dragonfly app ###
5
+ app = Dragonfly[:images]
6
+ app.configure_with(:rails)
7
+ app.configure_with(:imagemagick)
8
+
9
+ ### Extend active record ###
10
+ if defined?(ActiveRecord::Base)
11
+ app.define_macro(ActiveRecord::Base, :image_accessor)
12
+ app.define_macro(ActiveRecord::Base, :file_accessor)
13
+ end
14
+
15
+ ### Insert the middleware ###
16
+ Rails.application.middleware.insert 0, 'Dragonfly::Middleware', :images
17
+
18
+ begin
19
+ require 'rack/cache'
20
+ Rails.application.middleware.insert_before 'Dragonfly::Middleware', 'Rack::Cache', {
21
+ :verbose => true,
22
+ :metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded because Windows
23
+ :entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body") # has problems with spaces
24
+ }
25
+ rescue LoadError => e
26
+ app.log.warn("Warning: couldn't find rack-cache for caching dragonfly content")
27
+ end
@@ -0,0 +1,97 @@
1
+ require 'uri'
2
+
3
+ module Dragonfly
4
+ class Response
5
+
6
+ DEFAULT_FILENAME = proc{|job, request|
7
+ if job.basename
8
+ extname = job.encoded_extname || (".#{job.ext}" if job.ext)
9
+ "#{job.basename}#{extname}"
10
+ end
11
+ }
12
+
13
+ def initialize(job, env)
14
+ @job, @env = job, env
15
+ @app = @job.app
16
+ end
17
+
18
+ def to_response
19
+ if !(request.head? || request.get?)
20
+ [405, method_not_allowed_headers, ["#{request.request_method} method not allowed"]]
21
+ elsif etag_matches?
22
+ [304, cache_headers, []]
23
+ elsif request.head?
24
+ [200, success_headers.merge(cache_headers), []]
25
+ elsif request.get?
26
+ [200, success_headers.merge(cache_headers), job.result]
27
+ end
28
+ rescue DataStorage::DataNotFound => e
29
+ app.log.warn(e.message)
30
+ [404, {"Content-Type" => 'text/plain'}, ['Not found']]
31
+ end
32
+
33
+ def served?
34
+ request.get? && !etag_matches?
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :job, :env, :app
40
+
41
+ def request
42
+ @request ||= Rack::Request.new(env)
43
+ end
44
+
45
+ def cache_headers
46
+ {
47
+ "Cache-Control" => "public, max-age=#{app.cache_duration}",
48
+ "ETag" => %("#{job.unique_signature}")
49
+ }
50
+ end
51
+
52
+ def etag_matches?
53
+ return @etag_matches unless @etag_matches.nil?
54
+ if_none_match = env['HTTP_IF_NONE_MATCH']
55
+ @etag_matches = if if_none_match
56
+ if_none_match.tr!('"','')
57
+ if_none_match.split(',').include?(job.unique_signature) || if_none_match == '*'
58
+ else
59
+ false
60
+ end
61
+ end
62
+
63
+ def success_headers
64
+ {
65
+ "Content-Type" => job.mime_type,
66
+ "Content-Length" => job.size.to_s
67
+ }.merge(content_disposition_header)
68
+ end
69
+
70
+ def content_disposition_header
71
+ parts = []
72
+ parts << content_disposition if content_disposition
73
+ parts << %(filename="#{URI.encode(filename)}") if filename
74
+ parts.any? ? {"Content-Disposition" => parts.join('; ')} : {}
75
+ end
76
+
77
+ def method_not_allowed_headers
78
+ {
79
+ 'Content-Type' => 'text/plain',
80
+ 'Allow' => 'GET, HEAD'
81
+ }
82
+ end
83
+
84
+ def content_disposition
85
+ @content_disposition ||= evaluate(app.content_disposition)
86
+ end
87
+
88
+ def filename
89
+ @filename ||= evaluate(app.content_filename)
90
+ end
91
+
92
+ def evaluate(attribute)
93
+ attribute.respond_to?(:call) ? attribute.call(job, request) : attribute
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,40 @@
1
+ module Dragonfly
2
+ class RoutedEndpoint
3
+
4
+ class NoRoutingParams < RuntimeError; end
5
+
6
+ def initialize(app, &block)
7
+ @app = app
8
+ @block = block
9
+ end
10
+
11
+ def call(env)
12
+ params = symbolize_keys_of Rack::Request.new(env).params
13
+ job = @block.call(params.merge(routing_params(env)), @app)
14
+ Response.new(job, env).to_response
15
+ rescue Job::NoSHAGiven => e
16
+ [400, {"Content-Type" => 'text/plain'}, ["You need to give a SHA parameter"]]
17
+ rescue Job::IncorrectSHA => e
18
+ [400, {"Content-Type" => 'text/plain'}, ["The SHA parameter you gave (#{e}) is incorrect"]]
19
+ end
20
+
21
+ private
22
+
23
+ def routing_params(env)
24
+ env['rack.routing_args'] ||
25
+ env['action_dispatch.request.path_parameters'] ||
26
+ env['router.params'] ||
27
+ env['usher.params'] ||
28
+ env['dragonfly.params'] ||
29
+ raise(NoRoutingParams, "couldn't find any routing parameters in env #{env.inspect}")
30
+ end
31
+
32
+ def symbolize_keys_of(hash)
33
+ hash.inject({}) do |h, (key, value)|
34
+ h[(key.to_sym rescue key) || key] = value
35
+ h
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ require 'base64'
3
+
4
+ module Dragonfly
5
+ module Serializer
6
+
7
+ # Exceptions
8
+ class BadString < RuntimeError; end
9
+
10
+ extend self # So we can do Serializer.b64_encode, etc.
11
+
12
+ def b64_encode(string)
13
+ Base64.encode64(string).tr("\n=",'')
14
+ end
15
+
16
+ def b64_decode(string)
17
+ padding_length = string.length % 4
18
+ Base64.decode64(string + '=' * padding_length)
19
+ end
20
+
21
+ def marshal_encode(object)
22
+ b64_encode(Marshal.dump(object))
23
+ end
24
+
25
+ def marshal_decode(string)
26
+ Marshal.load(b64_decode(string))
27
+ rescue TypeError, ArgumentError => e
28
+ raise BadString, "couldn't decode #{string} - got #{e}"
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,113 @@
1
+ module Dragonfly
2
+ class Server
3
+
4
+ include Loggable
5
+ include Configurable
6
+
7
+ configurable_attr :dragonfly_url, '/dragonfly'
8
+ configurable_attr :protect_from_dos_attacks, false
9
+ configurable_attr :url_format, '/:job/:basename.:format'
10
+ configurable_attr :url_host
11
+
12
+ extend Forwardable
13
+ def_delegator :url_mapper, :params_in_url
14
+
15
+ def initialize(app)
16
+ @app = app
17
+ use_same_log_as(app)
18
+ use_as_fallback_config(app)
19
+ end
20
+
21
+ def before_serve(&block)
22
+ self.before_serve_callback = block
23
+ end
24
+ configuration_method :before_serve
25
+
26
+ def after_serve(&block)
27
+ self.after_serve_callback = block
28
+ end
29
+ configuration_method :after_serve
30
+
31
+ def call(env)
32
+ if dragonfly_url == env["PATH_INFO"]
33
+ dragonfly_response
34
+ elsif (params = url_mapper.params_for(env["PATH_INFO"], env["QUERY_STRING"])) && params['job']
35
+ job = Job.deserialize(params['job'], app)
36
+ job.validate_sha!(params['sha']) if protect_from_dos_attacks
37
+ response = Response.new(job, env)
38
+ catch(:halt) do
39
+ if before_serve_callback && response.served?
40
+ before_serve_callback.call(job, env)
41
+ end
42
+ resp = response.to_response
43
+ if after_serve_callback && resp[0] == 200 && resp[2].is_a?(Dragonfly::TempObject)
44
+ after_serve_callback.call(job, env)
45
+ end
46
+ resp
47
+ end
48
+ else
49
+ [404, {'Content-Type' => 'text/plain', 'X-Cascade' => 'pass'}, ['Not found']]
50
+ end
51
+ rescue Serializer::BadString, Job::InvalidArray => e
52
+ log.warn(e.message)
53
+ [404, {'Content-Type' => 'text/plain'}, ['Not found']]
54
+ rescue Job::NoSHAGiven => e
55
+ [400, {"Content-Type" => 'text/plain'}, ["You need to give a SHA parameter"]]
56
+ rescue Job::IncorrectSHA => e
57
+ [400, {"Content-Type" => 'text/plain'}, ["The SHA parameter you gave (#{e}) is incorrect"]]
58
+ end
59
+
60
+ def url_for(job, opts={})
61
+ opts = opts.dup
62
+ host = opts.delete(:host) || url_host
63
+ params = stringify_keys(opts)
64
+ params['job'] = job.serialize
65
+ params['sha'] = job.sha if protect_from_dos_attacks
66
+ url = url_mapper.url_for(params)
67
+ "#{host}#{url}"
68
+ end
69
+
70
+ private
71
+
72
+ attr_reader :app
73
+ attr_accessor :before_serve_callback
74
+ attr_accessor :after_serve_callback
75
+
76
+ def url_mapper
77
+ @url_mapper ||= UrlMapper.new(url_format,
78
+ :job => '\w',
79
+ :basename => '[^\/]',
80
+ :format => '[^\.]'
81
+ )
82
+ end
83
+
84
+ def stringify_keys(params)
85
+ params.inject({}) do |hash, (k, v)|
86
+ hash[k.to_s] = v
87
+ hash
88
+ end
89
+ end
90
+
91
+ def dragonfly_response
92
+ body = <<-DRAGONFLY
93
+ _o|o_
94
+ _~~---._( )_.---~~_
95
+ ( . \\ / . )
96
+ `-.~--' |=| '--~.-'
97
+ _~-.~'" /|=|\\ "'~.-~_
98
+ ( ./ |=| \\. )
99
+ `~~`"` |=| `"'ME"
100
+ |-|
101
+ <->
102
+ V
103
+ DRAGONFLY
104
+ [200, {
105
+ 'Content-Type' => 'text/plain',
106
+ 'Content-Size' => body.bytesize.to_s
107
+ },
108
+ [body]
109
+ ]
110
+ end
111
+
112
+ end
113
+ end