зеркало из https://github.com/mislav/hub.git
188 строки
4.9 KiB
Ruby
188 строки
4.9 KiB
Ruby
# based on <github.com/jnicklas/capybara/blob/ab62b27/lib/capybara/server.rb>
|
|
require 'net/http'
|
|
require 'rack/handler/webrick'
|
|
require 'json'
|
|
require 'sinatra/base'
|
|
|
|
module Hub
|
|
class LocalServer
|
|
class Identify < Struct.new(:app)
|
|
def call(env)
|
|
if env["PATH_INFO"] == "/__identify__"
|
|
[200, {}, [app.object_id.to_s]]
|
|
else
|
|
app.call(env)
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.ports
|
|
@ports ||= {}
|
|
end
|
|
|
|
class JsonParamsParser < Struct.new(:app)
|
|
def call(env)
|
|
if env['rack.input'] and not input_parsed?(env) and type_match?(env)
|
|
env['rack.request.form_input'] = env['rack.input']
|
|
data = env['rack.input'].read
|
|
env['rack.request.form_hash'] = data.empty?? {} : JSON.parse(data)
|
|
end
|
|
app.call(env)
|
|
end
|
|
|
|
def input_parsed? env
|
|
env['rack.request.form_input'].eql? env['rack.input']
|
|
end
|
|
|
|
def type_match? env
|
|
type = env['CONTENT_TYPE'] and
|
|
type.split(/\s*[;,]\s*/, 2).first.downcase =~ /[\/+]json$/
|
|
end
|
|
end
|
|
|
|
class App < Sinatra::Base
|
|
def invoke
|
|
res = super
|
|
content_type :json unless response.content_type
|
|
response.body = '{}' if blank_response?(response.body) ||
|
|
(response.body.respond_to?(:[]) && blank_response?(response.body[0]))
|
|
res
|
|
end
|
|
|
|
def blank_response?(obj)
|
|
obj.nil? || (obj.respond_to?(:empty?) && obj.empty?)
|
|
end
|
|
end
|
|
|
|
def self.start_sinatra(&block)
|
|
klass = Class.new(App)
|
|
klass.use JsonParamsParser
|
|
klass.set :environment, :test
|
|
klass.disable :protection
|
|
klass.error(404, 401) { content_type :json; nil }
|
|
klass.class_eval(&block)
|
|
klass.helpers do
|
|
def json(value)
|
|
content_type :json
|
|
JSON.generate value
|
|
end
|
|
|
|
def assert(expected, data = params)
|
|
expected.each do |key, value|
|
|
if :no == value
|
|
halt 422, json(
|
|
:message => "did not expect any value for %p; got %p" % [key, data[key]]
|
|
) if data.key?(key.to_s)
|
|
elsif Regexp === value
|
|
halt 422, json(
|
|
:message => "expected %p to match %p; got %p" % [key, value, data[key] ]
|
|
) unless value =~ data[key]
|
|
elsif Hash === value
|
|
assert(value, data[key])
|
|
elsif data[key] != value
|
|
halt 422, json(
|
|
:message => "expected %p to be %p; got %p" % [key, value, data[key]]
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
def assert_basic_auth(*expected)
|
|
require 'rack/auth/basic'
|
|
auth = Rack::Auth::Basic::Request.new(env)
|
|
if auth.credentials != expected
|
|
halt 401, json(:message => "Bad credentials")
|
|
end
|
|
end
|
|
|
|
def generate_patch(subject)
|
|
<<PATCH
|
|
From 7eb75a26ee8e402aad79fcf36a4c1461e3ec2592 Mon Sep 17 00:00:00 2001
|
|
From: Mislav <mislav.marohnic@gmail.com>
|
|
Date: Tue, 24 Jun 2014 11:07:05 -0700
|
|
Subject: [PATCH] #{subject}
|
|
---
|
|
diff --git a/README.md b/README.md
|
|
new file mode 100644
|
|
index 0000000..ce01362
|
|
--- /dev/null
|
|
+++ b/README.md
|
|
+hello
|
|
--
|
|
1.9.3
|
|
PATCH
|
|
end
|
|
end
|
|
|
|
new(klass.new).start
|
|
end
|
|
|
|
attr_reader :app, :host, :port
|
|
attr_accessor :server
|
|
|
|
def initialize(app, host = '127.0.0.1')
|
|
@app = app
|
|
@host = host
|
|
@server = nil
|
|
@server_thread = nil
|
|
end
|
|
|
|
def responsive?
|
|
return false if @server_thread && @server_thread.join(0)
|
|
|
|
res = Net::HTTP.start(host, port) { |http| http.get('/__identify__') }
|
|
|
|
res.is_a?(Net::HTTPSuccess) and res.body == app.object_id.to_s
|
|
rescue Errno::ECONNREFUSED, Errno::EBADF
|
|
return false
|
|
end
|
|
|
|
def start
|
|
@port = self.class.ports[app.object_id]
|
|
|
|
if not @port or not responsive?
|
|
tries = 0
|
|
begin
|
|
@server_thread = start_handler(Identify.new(app)) do |server, host, port|
|
|
self.server = server
|
|
@port = self.class.ports[app.object_id] = port
|
|
end
|
|
|
|
Timeout.timeout(5) { @server_thread.join(0.01) until responsive? }
|
|
rescue Timeout::Error
|
|
tries += 1
|
|
retry if tries < 3
|
|
raise "Rack application timed out during boot after #{tries} tries"
|
|
end
|
|
end
|
|
|
|
self
|
|
end
|
|
|
|
def start_handler(app)
|
|
server = nil
|
|
thread = Rack::Handler::WEBrick.run(app, server_options) { |s| server = s }
|
|
addr = server.listeners[0].addr
|
|
yield server, addr[3], addr[1]
|
|
return thread
|
|
end
|
|
|
|
def server_options
|
|
{ :Port => 0,
|
|
:BindAddress => '127.0.0.1',
|
|
:ShutdownSocketWithoutClose => true,
|
|
:ServerType => Thread,
|
|
:AccessLog => [],
|
|
:Logger => WEBrick::Log::new(nil, 0)
|
|
}
|
|
end
|
|
|
|
def stop
|
|
server.shutdown
|
|
@server_thread.join
|
|
end
|
|
end
|
|
end
|
|
|
|
WEBrick::HTTPStatus::StatusMessage[422] = "Unprocessable Entity"
|