diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..aa78bbc --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,19 @@ +Layout/SpaceInsideParens: + Enabled: false + +Metrics/LineLength: + Max: 120 + +Metrics/ModuleLength: + Exclude: + - "**/*_spec.rb" + +Metrics/BlockLength: + Exclude: + - "**/*_spec.rb" + +Layout/TrailingWhitespace: + Enabled: false + +Style/Semicolon: + Enabled: false diff --git a/Gemfile b/Gemfile index adcf261..a334033 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,9 @@ -source "https://rubygems.org" +# frozen_string_literal: true + +source 'https://rubygems.org' # Specify your gem's dependencies in roda-proxy.gemspec gemspec -gem "rake", "~> 12.0" -gem "rspec", "~> 3.0" +gem 'rake', '~> 12.0' +gem 'rspec', '~> 3.0' diff --git a/Rakefile b/Rakefile index b7e9ed5..82bb534 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,8 @@ -require "bundler/gem_tasks" -require "rspec/core/rake_task" +# frozen_string_literal: true + +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) -task :default => :spec +task default: :spec diff --git a/bin/console b/bin/console index b9b7536..3efd5ff 100755 --- a/bin/console +++ b/bin/console @@ -1,7 +1,8 @@ #!/usr/bin/env ruby +# frozen_string_literal: true -require "bundler/setup" -require "roda/proxy" +require 'bundler/setup' +require 'roda/proxy' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. @@ -10,5 +11,5 @@ require "roda/proxy" # require "pry" # Pry.start -require "irb" +require 'irb' IRB.start(__FILE__) diff --git a/config.ru b/config.ru index 4a37b60..95bea64 100644 --- a/config.ru +++ b/config.ru @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require './addressing' run App diff --git a/lib/roda/proxy.rb b/lib/roda/proxy.rb index 4efada2..35488e6 100644 --- a/lib/roda/proxy.rb +++ b/lib/roda/proxy.rb @@ -1,10 +1,22 @@ -require "faraday" -require "roda/proxy/version" +# frozen_string_literal: true +require 'faraday' +require 'roda/proxy/version' + +# :nodoc: class Roda + # :nodoc: module RodaPlugins + # Roda plugin for simple API proxying module Proxy + # Respond to the configure method to set the destination when proxying + # Expects the following options: + # [to] Required. The scheme and host of the proxy. Should not end with a slash. + # [path] Optional. The path to append to the above for proxying. + # Should begin with a +/+. Defaults to +/+. + # Example: + # plugin :proxy, to: 'https://foo.bar', path: '/my/api' def self.configure(app, opts = {}) app.opts[:proxy_to] = opts.fetch(:to, nil) app.opts[:proxy_path] = opts.fetch(:path, '/') @@ -12,45 +24,67 @@ class Roda raise 'Proxy host not set, use "plugin :proxy, to: http://example.com"' unless app.opts[:proxy_to] end - + # :nodoc: module RequestMethods + + # Proxies the request, forwarding all headers except +Host+ which is + # rewritten to be the destination host. The response headers, body and + # status are returned to the client. def proxy - #pp @_request.env - #pp @_request.body.read method = Faraday.method(env['REQUEST_METHOD'].downcase.to_sym) - pp _proxy_url - pp _proxy_headers - pp roda_class f_response = method.call(_proxy_url) { |req| _proxy_request(req) } - response.status = f_response.status - f_response.headers.each { |k,v| response[k] = v } - response.write(f_response.body) - halt + _respond(f_response) + end + + # Conditionally proxies when +condition+ is true and with selective probability. + # For instance, to proxy 50% of the time: + # r.proxy_when(probability: 0.5) + # Condition can be a truthy value or a block / lambda, in which case + # the result from the +#call+ is expected to be truthy. + # r.proxy_when( r.env['HTTP_PROXY_ME'] == 'true' ) + # The two parameters can be combined, the probability is evaluated first. + # r.proxy_when( r.env['HTTP_PROXY_ME'] == 'true', probability: 0.8 ) + # If and only if this method choses not to proxy is the block evaluated. + # The block is then expected to return a meaningful response to Roda. + def proxy_when(condition = true, probability: 1.0) + shall_proxy = Random.rand(0.0..1.0) <= probability + + if shall_proxy && ( condition.respond_to?(:call) ? condition.call : condition ) + yield(self) + else + proxy + end end private def _proxy_url - @_proxy_uri ||= URI(roda_class.opts[:proxy_to]) - .then { |uri| uri.path = roda_class.opts[:proxy_path] ; uri } - .then { |uri| uri.query = env['QUERY_STRING'] ; uri } + @_proxy_url ||= URI(roda_class.opts[:proxy_to]) + .then { |uri| uri.path = roda_class.opts[:proxy_path]; uri } + .then { |uri| uri.query = env['QUERY_STRING']; uri } end def _proxy_headers env - .select { |k,_v| k.start_with? 'HTTP_'} - .reject { |k,_v| k == 'HTTP_HOST' } + .select { |k, _v| k.start_with? 'HTTP_' } + .reject { |k, _v| k == 'HTTP_HOST' } .transform_keys do |k| k.sub(/^HTTP_/, '') - .split('_') - .map(&:capitalize) - .join('-') + .split('_') + .map(&:capitalize) + .join('-') end.merge({ 'Host' => "#{_proxy_url.host}:#{_proxy_url.port}" }) end def _proxy_request(req) req.headers = _proxy_headers end + + def _respond(proxied_response) + response.status = proxied_response.status + proxied_response.headers.each { |k, v| response[k] = v } + response.write(proxied_response.body) + end end end diff --git a/lib/roda/proxy/version.rb b/lib/roda/proxy/version.rb index 62b281c..c38094e 100644 --- a/lib/roda/proxy/version.rb +++ b/lib/roda/proxy/version.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + class Roda module Proxy - VERSION = "0.1.0" + VERSION = '0.1.0' end end diff --git a/roda-proxy.gemspec b/roda-proxy.gemspec index 3f9421a..0d22232 100644 --- a/roda-proxy.gemspec +++ b/roda-proxy.gemspec @@ -1,34 +1,37 @@ +# frozen_string_literal: true + require_relative 'lib/roda/proxy/version' Gem::Specification.new do |spec| - spec.name = "roda-proxy" + spec.name = 'roda-proxy' spec.version = Roda::Proxy::VERSION - spec.authors = ["Nigel Brookes-Thomas"] - spec.email = ["nigel@brookes-thomas.co.uk"] + spec.authors = ['Nigel Brookes-Thomas'] + spec.email = ['nigel@brookes-thomas.co.uk'] spec.summary = 'Proxy service for Roda' spec.description = 'Roda proxy service' - spec.homepage = "http://foo.bar" - spec.license = "MIT" - spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") + spec.homepage = 'http://foo.bar' + spec.license = 'MIT' + spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0') - spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" + spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'" - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = "http://foo.bar" - spec.metadata["changelog_uri"] = "http://foo.bar" + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'http://foo.bar' + spec.metadata['changelog_uri'] = 'http://foo.bar' # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do + spec.files = Dir.chdir(File.expand_path(__dir__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end - spec.bindir = "exe" + spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] + spec.require_paths = ['lib'] spec.add_dependency 'faraday', '~> 1.0' spec.add_dependency 'roda', '~> 3.0' spec.add_development_dependency 'rerun', '~> 0.13' + spec.add_development_dependency 'rubocop', '~> 0.80' end diff --git a/spec/roda/proxy_spec.rb b/spec/roda/proxy_spec.rb index c666fec..8baf6dc 100644 --- a/spec/roda/proxy_spec.rb +++ b/spec/roda/proxy_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + RSpec.describe Roda::Proxy do - it "has a version number" do + it 'has a version number' do expect(Roda::Proxy::VERSION).not_to be nil end - it "does something useful" do + it 'does something useful' do expect(false).to eq(true) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e519d25..b624ad1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,11 @@ -require "bundler/setup" -require "roda/proxy" +# frozen_string_literal: true + +require 'bundler/setup' +require 'roda/proxy' RSpec.configure do |config| # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = ".rspec_status" + config.example_status_persistence_file_path = '.rspec_status' # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching!