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"