sham_rack-1.4.1/0000755000004100000410000000000013146333120013471 5ustar www-datawww-datasham_rack-1.4.1/Rakefile0000644000004100000410000000033413146333116015143 0ustar www-datawww-datarequire "rubygems" require "rake" task "default" => "spec" require "rspec/core/rake_task" RSpec::Core::RakeTask.new do |t| t.rspec_opts = ["--format", "doc"] end require 'bundler' Bundler::GemHelper.install_tasks sham_rack-1.4.1/spec/0000755000004100000410000000000013146333116014430 5ustar www-datawww-datasham_rack-1.4.1/spec/spec_helper.rb0000644000004100000410000000022613146333116017246 0ustar www-datawww-datarequire "rubygems" require "rspec" require "rspec/mocks" require "test_apps" RSpec.configure do |config| config.raise_errors_for_deprecations! end sham_rack-1.4.1/spec/sham_rack_spec.rb0000644000004100000410000002240313146333116017720 0ustar www-datawww-datarequire "spec_helper" require "sham_rack" require "sham_rack/patron" require "open-uri" require "restclient" require "mechanize" require "rack" RSpec.describe ShamRack do class NetHttpProhibited < StandardError; end before do allow_any_instance_of(Net::HTTP).to receive(:start) do raise NetHttpProhibited, "real network calls are not allowed" end end after(:each) do ShamRack.reset end describe "mounted Rack application" do before(:each) do ShamRack.at("www.greetings.com").mount(GreetingApp.new) end it "can be accessed using Net::HTTP" do response = Net::HTTP.start("www.greetings.com") do |http| http.request(Net::HTTP::Get.new("/")) end expect(response.body).to eq("Hello, world") end it "can be accessed using Net::HTTP#get_response" do response = Net::HTTP.get_response(URI.parse("http://www.greetings.com/")) expect(response.body).to eq("Hello, world") end it "can be accessed using open-uri" do response = open("http://www.greetings.com") expect(response.status).to eq(["200", "OK"]) expect(response.read).to eq("Hello, world") end it "can be accessed using RestClient" do response = RestClient.get("http://www.greetings.com") expect(response.code).to eq(200) expect(response.to_s).to eq("Hello, world") end it "can be accessed using Mechanize" do response = Mechanize.new.get("http://www.greetings.com") expect(response.body).to eq("Hello, world") end it "can be accessed using Patron" do patron = Patron::Session.new response = patron.get("http://www.greetings.com/foo/bar") expect(response.body).to eq("Hello, world") end end describe ".at" do context "with a block" do it "mounts associated block as an app" do ShamRack.at("simple.xyz") do |env| ["200 OK", { "Content-type" => "text/plain" }, ["Easy, huh?"]] end expect(open("http://simple.xyz").read).to eq("Easy, huh?") end end context "with a URL" do it "raises an ArgumentError" do expect do ShamRack.at("http://www.greetings.com") end.to raise_error(ArgumentError, "invalid address") end end describe "#mount" do it "mounts an app" do ShamRack.at("hello.xyz").mount(GreetingApp.new) expect(open("http://hello.xyz").read).to eq("Hello, world") end end describe "#unmount" do it "deregisters a mounted app" do ShamRack.at("gone.xyz").mount(GreetingApp.new) ShamRack.at("gone.xyz").unmount expect do open("http://gone.xyz").read end.to raise_error(NetHttpProhibited) end end describe "#rackup" do before do @return_value = ShamRack.at("rackup.xyz").rackup do use UpcaseBody run GreetingApp.new end end it "mounts an app created using Rack::Builder" do expect(open("http://rackup.xyz").read).to eq("HELLO, WORLD") end it "returns the app" do expect(@return_value).to respond_to(:call) end end describe "#sinatra" do before do @return_value = ShamRack.at("sinatra.xyz").sinatra do get "/hello/:subject" do "Hello, #{params[:subject]}" end end end it "mounts associated block as a Sinatra app" do expect(open("http://sinatra.xyz/hello/stranger").read).to eq("Hello, stranger") end it "returns the app" do expect(@return_value).to respond_to(:call) end end describe "#stub" do before do @return_value = ShamRack.at("stubbed.xyz").stub end it "mounts a StubWebService" do expect(ShamRack.application_for("stubbed.xyz")).to be_kind_of(ShamRack::StubWebService) end it "returns the StubWebService" do expect(@return_value).to eq(ShamRack.application_for("stubbed.xyz")) end end end describe "response" do before(:each) do ShamRack.at("www.greetings.com") do [ "456 Foo Bar", { "Content-Type" => "text/plain", "X-Foo" => "bar" }, ["BODY"] ] end end let(:response) { Net::HTTP.get_response(URI.parse("http://www.greetings.com/")) } it "has status returned by app" do expect(response.code).to eq("456") end it "has status message returned by app" do expect(response.message).to eq("Foo Bar") end it "has body returned by app" do expect(response.body).to eq("BODY") end it "has Content-Type returned by app" do expect(response.content_type).to eq("text/plain") end it "has other headers returned by app" do expect(response["x-foo"]).to eq("bar") end context "when the app returns a numeric status" do before(:each) do ShamRack.at("www.greetings.com") do [ 201, { "Content-Type" => "text/plain" }, ["BODY"] ] end @response = Net::HTTP.get_response(URI.parse("http://www.greetings.com/")) end it "has status returned by app" do expect(response.code).to eq("201") end it "derives a status message" do expect(response.message).to eq("Created") end end end describe ".allow_network_connections" do context "when false" do before do ShamRack.prevent_network_connections end after do ShamRack.allow_network_connections end it "prevents Net::HTTP requests" do expect { Net::HTTP.get_response(URI.parse("http://www.example.com/")) }.to raise_error(ShamRack::NetworkConnectionPrevented) end it "prevents Patron requests" do expect { Patron::Session.new.get("http://www.example.com/") }.to raise_error(ShamRack::NetworkConnectionPrevented) end end context "when true" do before do ShamRack.allow_network_connections end it "allows Net::HTTP requests" do expect { Net::HTTP.get_response(URI.parse("http://www.example.com/")) }.to raise_error(NetHttpProhibited) end end end describe "Rack environment" do before(:each) do @env_recorder = recorder = EnvRecorder.new(GreetingApp.new) ShamRack.at("env.xyz").rackup do use Rack::Lint run recorder end end def env @env_recorder.last_env end it "is valid" do open("http://env.xyz/blah?q=abc") expect(env["REQUEST_METHOD"]).to eq("GET") expect(env["SCRIPT_NAME"]).to eq("") expect(env["PATH_INFO"]).to eq("/blah") expect(env["QUERY_STRING"]).to eq("q=abc") expect(env["SERVER_NAME"]).to eq("env.xyz") expect(env["SERVER_PORT"]).to eq("80") expect(env["rack.version"]).to be_kind_of(Array) expect(env["rack.url_scheme"]).to eq("http") expect(env["rack.multithread"]).to eq(true) expect(env["rack.multiprocess"]).to eq(true) expect(env["rack.run_once"]).to eq(false) end it "provides request headers" do Net::HTTP.start("env.xyz") do |http| request = Net::HTTP::Get.new("/") request["Foo-bar"] = "baz" http.request(request) end expect(env["HTTP_FOO_BAR"]).to eq("baz") end it "supports POST" do RestClient.post("http://env.xyz/resource", "q" => "rack") expect(env["REQUEST_METHOD"]).to eq("POST") expect(env["CONTENT_TYPE"]).to eq("application/x-www-form-urlencoded") expect(env["rack.input"].read).to eq("q=rack") end it "supports POST using Net::HTTP" do Net::HTTP.start("env.xyz") do |http| http.post("/resource", "q=rack") end expect(env["REQUEST_METHOD"]).to eq("POST") expect(env["rack.input"].read).to eq("q=rack") end it "supports POST using Patron" do patron = Patron::Session.new response = patron.post("http://env.xyz/resource", "", "Content-Type" => "application/xml") expect(response.status).to eq(200) expect(env["REQUEST_METHOD"]).to eq("POST") expect(env["rack.input"].read).to eq("") expect(env["CONTENT_TYPE"]).to eq("application/xml") end it "supports PUT" do RestClient.put("http://env.xyz/thing1", "stuff", :content_type => "text/plain") expect(env["REQUEST_METHOD"]).to eq("PUT") expect(env["CONTENT_TYPE"]).to eq("text/plain") expect(env["rack.input"].read).to eq("stuff") end it "supports PUT using Patron" do patron = Patron::Session.new response = patron.put("http://env.xyz/resource", "stuff", "Content-Type" => "text/plain") expect(env["REQUEST_METHOD"]).to eq("PUT") expect(env["CONTENT_TYPE"]).to eq("text/plain") expect(env["rack.input"].read).to eq("stuff") end it "supports DELETE" do RestClient.delete("http://env.xyz/thing/1") expect(env["REQUEST_METHOD"]).to eq("DELETE") expect(env["PATH_INFO"]).to eq("/thing/1") end it "supports DELETE using Patron" do patron = Patron::Session.new response = patron.delete("http://env.xyz/resource") expect(env["REQUEST_METHOD"]).to eq("DELETE") expect(env["PATH_INFO"]).to eq("/resource") end end end sham_rack-1.4.1/spec/sham_rack/0000755000004100000410000000000013146333116016360 5ustar www-datawww-datasham_rack-1.4.1/spec/sham_rack/stub_web_service_spec.rb0000644000004100000410000000415213146333116023253 0ustar www-datawww-datarequire "spec_helper" require "sham_rack/stub_web_service" require "rack/test" RSpec.describe ShamRack::StubWebService do include Rack::Test::Methods attr_reader :app before(:each) do @app = ShamRack::StubWebService.new end describe "#last_request" do it "returns the last request" do get '/foo/bar' expect(@app.last_request.path_info).to eq("/foo/bar") end end describe "with no handlers registered" do describe "any request" do before do get "/foo/456" end it "returns a 404" do expect(last_response.status).to eq(404) end end end describe "with two handlers registered" do before(:each) do @app.handle do |request| [200, {}, ["response from first handler"]] if request.get? end @app.handle do |request| [200, {}, ["response from second handler"]] if request.path_info == "/stuff" end end describe "a request matching the first handler" do before do get "/foo/456" end it "receives a response from the first handler" do expect(last_response.body).to eq("response from first handler") end end describe "a request matching the second handler" do before do post "/stuff" end it "receives a response from the second handler" do expect(last_response.body).to eq("response from second handler") end end describe "a request matching both handlers" do before do get "/stuff" end it "receives a response from the second handler" do expect(last_response.body).to eq("response from second handler") end end end describe ".register_resource" do before do @app.register_resource("/stuff?foo=bar", "STUFF", "text/plain", 202) get "/stuff?foo=bar" end it "sets body" do expect(last_response.body).to eq("STUFF") end it "sets content-type" do expect(last_response.content_type).to eq("text/plain") end it "sets status code" do expect(last_response.status).to eq(202) end end end sham_rack-1.4.1/spec/test_apps.rb0000644000004100000410000000141413146333116016757 0ustar www-datawww-datarequire "rack" class GreetingApp include Rack::Utils def call(env) params = parse_nested_query(env["QUERY_STRING"]) salutation = params[:salutation] || "Hello" subject = params[:subject] || "world" message = "#{salutation}, #{subject}" [ "200 OK", { "Content-Type" => "text/plain", "Content-Length" => message.length.to_s }, [message] ] end end class EnvRecorder def initialize(app) @app = app end def call(env) @last_env = env @app.call(env) end attr_reader :last_env end class UpcaseBody def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) upcased_body = Array(body).map { |x| x.upcase } [status, headers, upcased_body] end end sham_rack-1.4.1/benchmark/0000755000004100000410000000000013146333116015430 5ustar www-datawww-datasham_rack-1.4.1/benchmark/benchmark.rb0000644000004100000410000000212513146333116017707 0ustar www-datawww-datarequire "rubygems" require "pathname" dir = Pathname(__FILE__).parent $LOAD_PATH.unshift(dir.parent + "lib") require dir + "hello_app" require "sham_rack" require "restclient" # mount an instance of the app using ShamRack ShamRack.mount(HelloApp.new, "hello.sham") # run another instance in a separate process server_pid = fork do puts "Starting HTTP server on port 3333" $stdout = File.new('/dev/null', 'w') HelloApp.run!(:port => 3333) end at_exit do puts "Killing HTTP server" Process.kill("TERM", server_pid) Process.wait(server_pid) end puts "Waiting for server to come up" begin puts RestClient.get("http://localhost:3333/hello/stranger") rescue SystemCallError => e retry end iterations = (ARGV.shift || 1000).to_i %w(hello.sham localhost:3333).each do |site| start = Time.now iterations.times do x = RestClient.get("http://#{site}/hello/stranger").to_s end elapsed_time = (Time.now - start) requests_per_second = iterations / elapsed_time.to_f printf "%-20s #{iterations} requests in %f; %f per second\n", site, elapsed_time, requests_per_second end sham_rack-1.4.1/benchmark/hello_app.rb0000644000004100000410000000023413146333116017717 0ustar www-datawww-datarequire "rubygems" require "sinatra/base" class HelloApp < Sinatra::Base get "/hello/:subject" do "Hello, #{params[:subject]}" end end sham_rack-1.4.1/lib/0000755000004100000410000000000013146333116014244 5ustar www-datawww-datasham_rack-1.4.1/lib/sham_rack/0000755000004100000410000000000013146333116016174 5ustar www-datawww-datasham_rack-1.4.1/lib/sham_rack/patron.rb0000644000004100000410000000331613146333116020027 0ustar www-datawww-datarequire "patron" require "sham_rack" require "uri" module Patron class Session alias :handle_request_without_sham_rack :handle_request def handle_request(patron_request) uri = URI.parse(patron_request.url) rack_app = ShamRack.application_for(uri.host, uri.port) if rack_app handle_request_with_rack(patron_request, rack_app) else handle_request_without_sham_rack(patron_request) end end private def handle_request_with_rack(patron_request, rack_app) env = rack_env_for(patron_request) rack_response = rack_app.call(env) patron_response(rack_response) end def rack_env_for(patron_request) env = Rack::MockRequest.env_for(patron_request.url, :method => patron_request.action, :input => patron_request.upload_data) env.merge!(header_env(patron_request)) env end def patron_response(rack_response) status, headers, body = rack_response Patron::Response.new("", status.to_i, 0, assemble_headers(status, headers), assemble_body(body)) end def header_env(patron_request) env = {} patron_request.headers.each do |header, content| key = header.upcase.gsub('-', '_') key = "HTTP_" + key unless key =~ /^CONTENT_(TYPE|LENGTH)$/ env[key] = content end env end def assemble_headers(status, headers) status_code = Rack::Utils::HTTP_STATUS_CODES[status.to_i] content = "HTTP/1.1 #{status} #{status_code}\r\n" headers.each do |k, v| content << "#{k}: #{v}\r\n" end content end def assemble_body(body) content = "" body.each { |fragment| content << fragment } content end end end sham_rack-1.4.1/lib/sham_rack/net_http.rb0000644000004100000410000000527213146333116020354 0ustar www-datawww-datarequire "net/http" require "rack" class << Net::HTTP alias :new_without_sham_rack :new def new(address, port = nil, *proxy_args) port ||= Net::HTTP.default_port rack_app = ShamRack.application_for(address, port) http_object = new_without_sham_rack(address, port, *proxy_args) if rack_app http_object.extend(ShamRack::NetHttp::Extensions) http_object.rack_app = rack_app end http_object end end module ShamRack module NetHttp module Extensions attr_accessor :rack_app def start if block_given? yield self else self end end def request(request, body = nil) rack_response = @rack_app.call(rack_env(request, body)) net_http_response = build_response(rack_response) yield net_http_response if block_given? return net_http_response end private def rack_env(request, body) rack_env = request_env(request, body) rack_env.merge!(header_env(request)) rack_env.merge!(server_env) end def server_env { "SERVER_NAME" => @address, "SERVER_PORT" => @port.to_s } end def header_env(request) env = {} request.each_header do |header, content| key = header.upcase.gsub('-', '_') key = "HTTP_" + key unless key =~ /^CONTENT_(TYPE|LENGTH)$/ env[key] = content end env end def request_env(request, body) body ||= request_body(request) Rack::MockRequest.env_for(request.path, :method => request.method, :input => body) end def request_body(request) return request.body unless request.body.nil? return request.body_stream.read unless request.body_stream.nil? "" end def build_response(rack_response) status, headers, body = rack_response code, message = status.to_s.split(" ", 2) message ||= Rack::Utils::HTTP_STATUS_CODES[code.to_i] response = Net::HTTPResponse.send(:response_class, code).new("Sham", code, message) response.instance_variable_set(:@body, assemble_body(body)) response.instance_variable_set(:@read, true) headers.each do |k,v| response.add_field(k, v) end response.extend ShamRack::NetHttp::ResponseExtensions return response end def assemble_body(body) content = "" body.each { |fragment| content << fragment } content end end module ResponseExtensions def read_body(dest = nil) yield @body if block_given? dest << @body if dest return @body end end end end sham_rack-1.4.1/lib/sham_rack/registration.rb0000644000004100000410000000322313146333116021233 0ustar www-datawww-datarequire "sham_rack/allowances" module ShamRack module Registration ADDRESS_PATTERN = /^[a-z0-9-]+(\.[a-z0-9-]+)*$/i def unmount_all registry.clear end def at(address, port = nil, &app_block) mount_point = mount_point_for(address, port) if app_block mount_point.mount(app_block) else mount_point end end def application_for(address, port = nil) port ||= Net::HTTP.default_port mount_point_for(address, port).app.tap do |app| return app unless app.nil? unless ShamRack.network_connections_allowed? raise NetworkConnectionPrevented, "connection to #{address}:#{port} not allowed" end end end private def mount_point_for(address, port) registry[mount_key(address, port)] end def registry @registry ||= Hash.new do |hash, key| hash[key] = MountPoint.new end end def mount_key(address, port) unless address =~ ADDRESS_PATTERN raise ArgumentError, "invalid address" end port ||= Net::HTTP.default_port port = Integer(port) [address, port] end end class MountPoint attr_reader :app def mount(app) @app = app end def unmount @app = nil end def rackup(&block) require "rack" mount(Rack::Builder.new(&block).to_app) end def sinatra(&block) require "sinatra/base" sinatra_app = Class.new(Sinatra::Base) sinatra_app.class_eval(&block) mount(sinatra_app.new) end def stub require "sham_rack/stub_web_service" mount(StubWebService.new) end end end sham_rack-1.4.1/lib/sham_rack/version.rb0000644000004100000410000000005013146333116020201 0ustar www-datawww-datamodule ShamRack VERSION = "1.4.1" end sham_rack-1.4.1/lib/sham_rack/allowances.rb0000644000004100000410000000060313146333116020650 0ustar www-datawww-datamodule ShamRack class << self def network_connections_allowed? @allow_network_connections end def allow_network_connections @allow_network_connections = true end def prevent_network_connections @allow_network_connections = false end end class NetworkConnectionPrevented < StandardError end end ShamRack.allow_network_connections sham_rack-1.4.1/lib/sham_rack/stub_web_service.rb0000644000004100000410000000201613146333116022052 0ustar www-datawww-datarequire "rack" module ShamRack # A simple Rack app that stubs out a web service, for testing. class StubWebService def initialize @handlers = [] end def last_request @request end def call(env) @request = Rack::Request.new(env) @handlers.each do |handler| response = handler.call(@request) return response if response end return default_response end def handle(&block) @handlers.unshift(block) end def register_resource(path, content, content_type = "application/xml", status = 200) handle do |request| request_path = request.path_info unless request.query_string.to_s.empty? request_path += "?" + request.query_string end [status, {"Content-Type" => content_type}, [content]] if request_path == path end end def reset @handlers.clear end protected def default_response [404, {"Content-Type" => "text/plain"}, ["Not found"]] end end end sham_rack-1.4.1/lib/sham_rack.rb0000644000004100000410000000055713146333116016530 0ustar www-datawww-datarequire "sham_rack/net_http" require "sham_rack/registration" require "sham_rack/version" # ShamRack allows access to Rack applications using Net::Http, but without network traffic. # # For more detail, see http://github.com/mdub/sham_rack # module ShamRack extend ShamRack::Registration def self.reset unmount_all allow_network_connections end end sham_rack-1.4.1/sham_rack.gemspec0000644000004100000410000000321013146333120016762 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = "sham_rack" s.version = "1.4.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Mike Williams"] s.date = "2017-08-20" s.description = "ShamRack plumbs Net::HTTP directly into Rack, for quick and easy HTTP testing." s.email = "mdub@dogbiscuit.org" s.files = ["Rakefile", "benchmark/benchmark.rb", "benchmark/hello_app.rb", "lib/sham_rack.rb", "lib/sham_rack/allowances.rb", "lib/sham_rack/net_http.rb", "lib/sham_rack/patron.rb", "lib/sham_rack/registration.rb", "lib/sham_rack/stub_web_service.rb", "lib/sham_rack/version.rb", "spec/sham_rack/stub_web_service_spec.rb", "spec/sham_rack_spec.rb", "spec/spec_helper.rb", "spec/test_apps.rb"] s.homepage = "http://github.com/mdub/sham_rack" s.require_paths = ["lib"] s.rubyforge_project = "shamrack" s.rubygems_version = "1.8.23" s.summary = "Net::HTTP-to-Rack plumbing" s.test_files = ["Rakefile", "benchmark/benchmark.rb", "benchmark/hello_app.rb", "spec/sham_rack/stub_web_service_spec.rb", "spec/sham_rack_spec.rb", "spec/spec_helper.rb", "spec/test_apps.rb"] if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q, [">= 0"]) else s.add_dependency(%q, [">= 0"]) end else s.add_dependency(%q, [">= 0"]) end end