Class: HttpDecoy::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/http_decoy/server.rb

Overview

A real WEBrick HTTP server that runs in a background thread.

Uses WEBrick directly (no Rack::Handler) so it works with both Rack 2.x and Rack 3.x without the rackup gem.

Port 0 lets the OS pick a free port atomically — parallel test runners never collide.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(route_map) ⇒ Server

Returns a new instance of Server.



21
22
23
24
25
26
27
28
29
# File 'lib/http_decoy/server.rb', line 21

def initialize(route_map)
  @route_map   = route_map
  @request_log = RequestLog.new
  @webrick     = nil
  @thread      = nil
  @port        = nil
  @scenario    = nil
  @scenario_mu = Mutex.new
end

Instance Attribute Details

#portObject (readonly)

Returns the value of attribute port.



19
20
21
# File 'lib/http_decoy/server.rb', line 19

def port
  @port
end

#request_logObject (readonly)

Returns the value of attribute request_log.



19
20
21
# File 'lib/http_decoy/server.rb', line 19

def request_log
  @request_log
end

#route_mapObject (readonly)

Returns the value of attribute route_map.



19
20
21
# File 'lib/http_decoy/server.rb', line 19

def route_map
  @route_map
end

Instance Method Details

#base_urlObject



71
# File 'lib/http_decoy/server.rb', line 71

def base_url = "http://127.0.0.1:#{@port}"

#current_scenarioObject



85
86
87
# File 'lib/http_decoy/server.rb', line 85

def current_scenario
  @scenario_mu.synchronize { @scenario }
end

#rack_appObject

The Rack app is also exposed for WebMock’s to_rack() interception.



74
75
76
# File 'lib/http_decoy/server.rb', line 74

def rack_app
  @rack_app ||= build_rack_app
end

#startObject



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/http_decoy/server.rb', line 31

def start
  rack_app = build_rack_app

  # WEBrick binds to the OS-assigned port during initialization.
  @webrick = WEBrick::HTTPServer.new(
    Port: 0,
    Logger: WEBrick::Log.new(File::NULL),
    AccessLog: []
  )
  @port = @webrick.config[:Port]

  @webrick.mount_proc("/") do |req, res|
    status, headers, body = rack_app.call(rack_env_from(req))
    res.status = status.to_i
    headers.each { |k, v| res[k] = v }
    res.body = Array(body).join
  rescue StandardError => e
    res.status = 500
    res["Content-Type"] = "application/json"
    res.body = JSON.generate(error: "#{e.class}: #{e.message}")
  end

  @thread = Thread.new { @webrick.start }
  @thread.abort_on_exception = true

  # Poll until WEBrick enters its accept loop.
  deadline = Time.now + 5
  sleep(0.005) until @webrick.status == :Running || Time.now > deadline
  raise "http_decoy: server failed to start within 5 seconds" unless @webrick.status == :Running

  self
end

#stopObject



64
65
66
67
68
69
# File 'lib/http_decoy/server.rb', line 64

def stop
  @webrick&.shutdown
  @thread&.join(3)
  @thread  = nil
  @webrick = nil
end

#with_scenario(name) ⇒ Object



78
79
80
81
82
83
# File 'lib/http_decoy/server.rb', line 78

def with_scenario(name)
  @scenario_mu.synchronize { @scenario = name }
  yield
ensure
  @scenario_mu.synchronize { @scenario = nil }
end