Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

WIP, so far requests with no options should be proxied

  • Loading branch information...
commit 1aa14eb8f0766d37296fa5b27ee919b9527f2432 1 parent 19cbc8e
Lee Hambley authored
4 Gemfile
View
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
# Specify your gem's dependencies in rack-thumb-proxy.gemspec
gemspec
+
+group :test do
+ gem 'ruby-debug19'
+end
11 README.md
View
@@ -163,6 +163,17 @@ One complication is that when using the link generator functions, one
**must** use the configuration API, otherwise the default path will be
`/`.
+## ToDo
+
+ * Implement Railtie/helpers
+ * Ensure the hash signatures are checked
+ * Make it possible to control the cache control header
+ * Don't use open-uri
+ * Take the cache-control headers from upstream
+ if we can
+ * Allow a local cache for the images, perhaps somewhere
+ in `/tmp`
+
## Contributing
1. Fork it
119 lib/rack-thumb-proxy.rb
View
@@ -1,4 +1,8 @@
-require "rack-thumb-proxy/version"
+require 'CGI'
+require 'open-uri'
+require 'tempfile'
+require 'rack-thumb-proxy/version'
+require 'rack-thumb-proxy/configuration'
module Rack
@@ -19,52 +23,111 @@ def configuration
@configuration ||= Configuration.new
end
+ def call(env)
+ new(env).call
+ end
+
end
- class Configuration
+ def initialize(env)
+ @env = env
+ @path = env['PATH_INFO']
+ end
- attr_accessor :key_length
- attr_accessor :mount_point
+ def call
+ if request_matches?
+ validate_signature! &&
+ retreive_upstream! &&
+ transform_image! &&
+ format_response!
+ response.finish
+ else
+ [404, {'Content-Length' => 9}, ['Not Found']]
+ end
+ end
- attr_reader :option_labels
+ private
- def initialize
- initialize_defaults!
+ def handle_request
+ response
end
- def mount_point(new_mount_point = nil)
- @mount_point = new_mount_point if new_mount_point
- return @mount_point
+ def validate_signature!
+ true
end
- def key_length(new_key_length = nil)
- @key_length = new_key_length if new_key_length
- return @key_length
+ def retreive_upstream!
+ begin
+ open(request_url, 'rb') do |f|
+ tempfile.write(f.read)
+ tempfile.flush
+ end
+ rescue
+ response.status = 500
+ response.body << $!.message
+ return false
+ end
+ return true
end
- def secret(new_secret = nil)
- @secret = new_secret if new_secret
- return @secret
+ def format_response!
+ response.status = 200
+ response.headers["Content-Length"] = transformed_image_file_size_in_bytes
+ true
end
- def option_label(label, options)
- option_labels.merge!(label.to_sym => options)
+ def tempfile(&bock)
+ @_tempfile ||= Tempfile.new(escaped_request_url)
end
- def hash_signatures_in_use?
- !!@secret
+ def transform_image!
+ true
end
- def initialize_defaults!
- @secret = nil
- @key_length = 10
- @mount_point = '/'
- @option_labels = {}
+ def should_verify_hash_signature?
+ configuration.hash_signatures_in_use?
end
- alias :reset_defaults! :initialize_defaults!
- private :initialize_defaults!
- end
+ def configuration
+ self.class.configuration
+ end
+
+ def request_hash_signature
+ @_request_match_data["hash_signature"]
+ end
+
+ def request_options
+ @_request_match_data["options"]
+ end
+
+ def request_gravity
+ @_request_match_data["gravity"]
+ end
+
+ def request_url
+ CGI.unescape(escaped_request_url)
+ end
+
+ def escaped_request_url
+ @_request_match_data["escaped_url"]
+ end
+
+ def request_matches?
+ @_request_match_data = @path.match(routing_pattern)
+ end
+
+ def transformed_image_file_size_in_bytes
+ ::File.size(tempfile)
+ end
+
+ # Examples: http://rubular.com/r/oPRK1t31yv
+ def routing_pattern
+ /^\/(?<hash_signature>[a-z0-9]{10}|)\/?(?<options>(:?[0-9]*x+[0-9]*(?<gravity>c|n|ne|e|s|sw|w|nw|)|))\/?(?<escaped_url>https?.*)$/
+ end
+
+ def response
+ @_response ||= Rack::Response.new
+ end
end
53 lib/rack-thumb-proxy/configuration.rb
View
@@ -0,0 +1,53 @@
+module Rack
+ module Thumb
+ class Proxy
+
+ class Configuration
+
+ attr_reader :cache_control_headers
+ attr_reader :option_labels
+
+ def initialize
+ initialize_defaults!
+ end
+
+ def mount_point(new_mount_point = nil)
+ @mount_point = new_mount_point if new_mount_point
+ return @mount_point
+ end
+
+ def key_length(new_key_length = nil)
+ @key_length = new_key_length if new_key_length
+ return @key_length
+ end
+
+ def secret(new_secret = nil)
+ @secret = new_secret if new_secret
+ return @secret
+ end
+
+ def option_label(label, options)
+ option_labels.merge!(label.to_sym => options)
+ end
+
+ def hash_signatures_in_use?
+ !!@secret
+ end
+
+ def initialize_defaults!
+ @secret = nil
+ @key_length = 10
+ @mount_point = '/'
+ @option_labels = {}
+ @cache_control_headers = {'Cache-Control' => 'max-age=86400, public, must-revalidate'}
+ end
+ alias :reset_defaults! :initialize_defaults!
+ private :initialize_defaults!
+
+ end
+
+ end
+
+ end
+
+end
6 rack-thumb-proxy.gemspec
View
@@ -2,6 +2,7 @@
require File.expand_path('../lib/rack-thumb-proxy/version', __FILE__)
Gem::Specification.new do |gem|
+
gem.authors = ["Lee Hambley"]
gem.email = ["lee.hambley@gmail.com"]
gem.description = %q{TODO: Write a gem description}
@@ -13,8 +14,13 @@ Gem::Specification.new do |gem|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
gem.name = "rack-thumb-proxy"
gem.require_paths = ["lib"]
+
gem.version = Rack::Thumb::Proxy::VERSION
+ gem.add_dependency 'rack'
+
gem.add_development_dependency 'minitest', '~> 2.11'
+ gem.add_development_dependency 'webmock', '~> 1.8.0'
+ gem.add_development_dependency 'rack-test', '~> 0.6.1'
end
BIN  test/fixtures/250x.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 test/test_configuration_api.rb
View
@@ -12,6 +12,7 @@ def test_default_options
assert_equal nil, default_configuration.secret
assert_equal 10, default_configuration.key_length
assert_equal '/', default_configuration.mount_point
+ assert_equal({'Cache-Control' => 'max-age=86400, public, must-revalidate'}, default_configuration.cache_control_headers)
end
def test_it_should_take_a_block_which_is_class_evaluated_keeping_all_options
@@ -32,12 +33,12 @@ def test_setting_option_labels_via_the_configuration_api
assert configuration.option_labels.has_key?(:thumbnail)
end
- def test_that_hash_signatures_are_correctly_enabled_and_disabled_based_on_the_presense_of_the_secret
+ def test_hash_signatures_are_correctly_enabled_and_disabled_based_on_the_presense_of_the_secret
Rack::Thumb::Proxy.configuration.reset_defaults!
configuration = Rack::Thumb::Proxy.configuration
refute configuration.secret
refute configuration.hash_signatures_in_use?
- configuration.secret('abc123')
+ configuration.secret('something truthy')
assert configuration.secret
assert configuration.hash_signatures_in_use?
end
12 test/test_helper.rb
View
@@ -1,8 +1,18 @@
require 'rubygems'
require 'bundler/setup'
+require 'cgi'
+
require 'rack-thumb-proxy'
+require 'rack/test'
+
require 'minitest/autorun'
require 'minitest/pride'
-require 'minitest/spec'
+require 'minitest/mock'
+
+require 'webmock/minitest'
+
+WebMock.disable_net_connect!
+
+require 'ruby-debug'
48 test/test_rack_thumb_proxy.rb
View
@@ -0,0 +1,48 @@
+require 'test_helper'
+
+class TestRackThumbProxy < MiniTest::Unit::TestCase
+
+ include Rack::Test::Methods
+
+ def app
+ Rack::Thumb::Proxy
+ end
+
+ def test_it_should_repond_with_not_found_when_the_upstream_url_is_bunk
+ get "/something/that/doesnt/even/match/a/little/bit"
+ assert last_response.not_found?
+ end
+
+ def test_it_should_return_success_with_the_correct_content_length_when_a_matching_url_is_found_for_a_noop_image
+ stub_image_request!('250x.gif', 'http://www.example.com/images/kittens.gif')
+
+ get "/" + escape("http://www.example.com/images/kittens.gif")
+ assert last_response.ok?
+ assert_equal file_size_for_fixture('250x.gif'), last_response.headers.fetch('Content-Length')
+ end
+
+ private
+
+ def stub_image_request!(file, url)
+ stub_request(:any, url).to_return(
+ body: File.read(filename_for_fixture(file)),
+ headers: {
+ 'Content-Length' => file_size_for_fixture(file)
+ },
+ status: 200
+ )
+ end
+
+ def escape(string)
+ CGI.escape(string)
+ end
+
+ def file_size_for_fixture(file)
+ File.size(filename_for_fixture(file))
+ end
+
+ def filename_for_fixture(file)
+ File.expand_path('test/fixtures/' + file)
+ end
+
+end
Please sign in to comment.
Something went wrong with that request. Please try again.