github.com/jingweno/gh@v2.1.1-0.20221007190738-04a7985fa9a1+incompatible/features/support/local_server.rb (about) 1 # based on <github.com/jnicklas/capybara/blob/ab62b27/lib/capybara/server.rb> 2 require 'net/http' 3 require 'rack/handler/webrick' 4 5 module Hub 6 class LocalServer 7 class Identify < Struct.new(:app) 8 def call(env) 9 if env["PATH_INFO"] == "/__identify__" 10 [200, {}, [app.object_id.to_s]] 11 else 12 app.call(env) 13 end 14 end 15 end 16 17 def self.ports 18 @ports ||= {} 19 end 20 21 class JsonParamsParser < Struct.new(:app) 22 def call(env) 23 if env['rack.input'] and not input_parsed?(env) and type_match?(env) 24 env['rack.request.form_input'] = env['rack.input'] 25 data = env['rack.input'].read 26 env['rack.request.form_hash'] = data.empty?? {} : JSON.parse(data) 27 end 28 app.call(env) 29 end 30 31 def input_parsed? env 32 env['rack.request.form_input'].eql? env['rack.input'] 33 end 34 35 def type_match? env 36 type = env['CONTENT_TYPE'] and 37 type.split(/\s*[;,]\s*/, 2).first.downcase =~ /application\/.*json/ 38 end 39 end 40 41 def self.start_sinatra(&block) 42 require 'json' 43 require 'sinatra/base' 44 klass = Class.new(Sinatra::Base) 45 klass.use JsonParamsParser 46 klass.set :environment, :test 47 klass.disable :protection 48 klass.before { content_type :json } 49 klass.class_eval(&block) 50 klass.helpers do 51 def json(value) 52 content_type :json 53 JSON.generate value 54 end 55 56 def assert(expected) 57 expected.each do |key, value| 58 if params[key] != value 59 halt 422, json( 60 :message => "expected %s to be %s; got %s" % [ 61 key.inspect, 62 value.inspect, 63 params[key].inspect 64 ] 65 ) 66 end 67 end 68 end 69 70 def assert_basic_auth(*expected) 71 require 'rack/auth/basic' 72 auth = Rack::Auth::Basic::Request.new(env) 73 if auth.credentials != expected 74 halt 401, json( 75 :message => "expected %p; got %p" % [ 76 expected, auth.credentials 77 ] 78 ) 79 end 80 end 81 end 82 83 new(klass.new).start 84 end 85 86 attr_reader :app, :host, :port 87 attr_accessor :server 88 89 def initialize(app, host = '127.0.0.1') 90 @app = app 91 @host = host 92 @server = nil 93 @server_thread = nil 94 end 95 96 def responsive? 97 return false if @server_thread && @server_thread.join(0) 98 99 res = Net::HTTP.start(host, port) { |http| http.get('/__identify__') } 100 101 res.is_a?(Net::HTTPSuccess) and res.body == app.object_id.to_s 102 rescue Errno::ECONNREFUSED, Errno::EBADF 103 return false 104 end 105 106 def start 107 @port = self.class.ports[app.object_id] 108 109 if not @port or not responsive? 110 @server_thread = start_handler(Identify.new(app)) do |server, host, port| 111 self.server = server 112 @port = self.class.ports[app.object_id] = port 113 end 114 115 Timeout.timeout(60) { @server_thread.join(0.01) until responsive? } 116 end 117 rescue TimeoutError 118 raise "Rack application timed out during boot" 119 else 120 self 121 end 122 123 def start_handler(app) 124 server = nil 125 thread = Rack::Handler::WEBrick.run(app, server_options) { |s| server = s } 126 addr = server.listeners[0].addr 127 yield server, addr[3], addr[1] 128 return thread 129 end 130 131 def server_options 132 { :Port => 0, 133 :BindAddress => '127.0.0.1', 134 :ShutdownSocketWithoutClose => true, 135 :ServerType => Thread, 136 :AccessLog => [], 137 :Logger => WEBrick::Log::new(nil, 0) 138 } 139 end 140 141 def stop 142 server.shutdown 143 @server_thread.join 144 end 145 end 146 end 147 148 WEBrick::HTTPStatus::StatusMessage[422] = "Unprocessable Entity"