conditional proxy

This commit is contained in:
Nigel Brookes-Thomas 2020-03-12 16:37:13 +00:00
parent 0a10320fa7
commit d73f0761a8
10 changed files with 117 additions and 48 deletions

19
.rubocop.yml Normal file
View File

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

View File

@ -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'

View File

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

View File

@ -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__)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require './addressing'
run App

View File

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

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
class Roda
module Proxy
VERSION = "0.1.0"
VERSION = '0.1.0'
end
end

View File

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

View File

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

View File

@ -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!