Class: RightDevelop::Testing::Server::MightApi::App::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/right_develop/testing/servers/might_api/app/base.rb

Direct Known Subclasses

Admin, Echo, Playback, Record

Defined Under Namespace

Classes: MightError, MissingRoute

Constant Summary collapse

MUTEX =

semaphore for critical sections

::Mutex.new
MAX_REDIRECTS =

500 after so many redirects

10
DEFAULT_PROXY_SETTINGS =

Rack (and Skeletor) apps and some known AWS apps only accept dash and not underscore so ensure the default settings reflect the 80-20 rule.

RightSupport::Data::Mash.new(
  header: RightSupport::Data::Mash.new(
    case:      :capitalize,
    separator: :dash
  ).freeze
).freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Base

Returns a new instance of Base.

Parameters:

  • options (Hash) (defaults to: {})

    for initializer

Options Hash (options):

  • :config (String)

    for sevice (default = MightAPI Config singleton)

  • :logger (String)

    for sevice (default = MightAPI logger singleton)

  • :state_file_name (String)

    relative to fixtures directory or nil for no perisisted state.



60
61
62
63
64
65
66
67
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 60

def initialize(options = {})
  @config = options[:config] || ::RightDevelop::Testing::Server::MightApi::Config
  @logger = options[:logger] || ::RightDevelop::Testing::Server::MightApi.logger

  @state_file_path = options[:state_file_name] ?
    ::File.join(@config.fixtures_dir, options[:state_file_name]) :
    nil
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



51
52
53
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 51

def config
  @config
end

#loggerObject (readonly)

Returns the value of attribute logger.



51
52
53
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 51

def logger
  @logger
end

#state_file_pathObject (readonly)

Returns the value of attribute state_file_path.



51
52
53
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 51

def state_file_path
  @state_file_path
end

Class Method Details

.app_threadsObject



77
78
79
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 77

def self.app_threads
  @app_threads ||= ::Set.new
end

.interrupted=(value) ⇒ Object



73
74
75
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 73

def self.interrupted=(value)
  @interrupted = value
end

.interrupted?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 69

def self.interrupted?
  !!@interrupted
end

Instance Method Details

#call(env) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 81

def call(env)
  # HACK: chain trap interrupt on first call to app because the trap chain
  # does not exist, in rack terms, until just before app is run.
  # unforunately due to poor design of rack, this object gets no
  # calls from rack other than calls to handle requests (i.e. a call to
  # do run/shutdown would be nice).
  #
  # the downside here is that if the server never receives any request
  # then the trap chain is never setup so temporary files cannot be
  # cleaned-up on shutdown, etc. admin mode has a workaround whereby it is
  # able to clean-up any temporary files immediately after reading its
  # config and whenever the administered configuration changes.
  raise ::Interrupt if self.class.interrupted?
  MUTEX.synchronize do
    entrapment unless Base.trapped?
    self.class.app_threads << ::Thread.current
  end

  env['rack.logger'] ||= logger

  # read body from stream.
  request = ::Rack::Request.new(env)
  body = request.body.read

  # proxy any headers from env starting with HTTP_
  headers = env.inject({}) do |r, (k,v)|
    # note that HTTP_HOST refers to this proxy server instead of the
    # proxied target server. in the case of AWS authentication, it is
    # necessary to pass the value through unmodified or else AWS auth
    # fails.
    if k.start_with?('HTTP_')
      r[k[5..-1]] = v
    end
    r
  end

  # special cases.
  ['ACCEPT', 'CONTENT_TYPE', 'CONTENT_LENGTH', 'USER_AGENT'].each do |key|
    headers[key] = env[key] unless env[key].to_s.empty?
  end

  # prepare and call handler
  #
  # note that verb supposed to already be .to_s.upcase but we want to
  # ensure that we agree on that.
  verb = request.request_method.to_s.upcase
  uri = ::URI.parse(request.url)
  logger.info("#{verb} #{uri}")
  result = handle_request(env, verb, uri, headers, body)
  logger.info(result.first.to_s)
  result
rescue MissingRoute => e
  message = "#{e.class} #{e.message}"
  logger.error(message)
  if config.routes.empty?
    logger.error("No routes configured.")
  else
    logger.error("The following routes are configured:")
    config.routes.keys.each do |prefix|
      logger.error("  #{prefix}...")
    end
  end

  # not a 404 because this is a proxy/stub service and 40x might appear to
  # have come from a proxied request/response whereas 500 is never an
  # expected response.
  internal_server_error(message)
rescue ::RightDevelop::Testing::Client::Rest::Request::Playback::PeerResetConnectionError => e
  # FIX: have only implemented socket close for webrick; not sure if this
  # is needed for other rack implementations.
  if socket = ::Thread.current[:WEBrickSocket]
    # closing the socket causes 'peer reset connection' on client side and
    # also prevents any response coming back on connection.
    socket.close
  end
  message = e.message
  trace = [e.class.name] + (e.backtrace || [])
  logger.info(message)
  internal_server_error(message)
rescue ::RightDevelop::Testing::Recording::Metadata::PlaybackError => e
  # response has not been recorded, etc.
  message = e.message
  trace = [e.class.name] + (e.backtrace || [])
  logger.error(message)
  logger.debug(trace.join("\n"))
  internal_server_error(message)
rescue ::Interrupt
  # setting interrupted=true may or may not be redundant, depending on
  # visibility of interrupted flag to all outstanding app threads.
  # the problem is that we are not allowed to synchronize a mutex inside
  # of a trap context.
  self.class.interrupted = true
  internal_server_error('interrupt')
rescue ::Exception => e
  message = "Unhandled exception: #{e.class} #{e.message}"
  trace = e.backtrace || []
  if logger
    logger.error(message)
    logger.debug(trace.join("\n"))
  else
    env['rack.errors'].puts(message)
    env['rack.errors'].puts(trace.join("\n"))
  end
  internal_server_error(message)
ensure
  unless self.class.interrupted?
    MUTEX.synchronize do
      self.class.app_threads.delete(::Thread.current)
    end
  end
end

#cleanupObject

Removes state and/or fixtures for current mode (overridable).



207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 207

def cleanup
  # the state file, if any, is always temporary.
  if @state_file_path && ::File.file?(@state_file_path)
    ::File.unlink(@state_file_path)
  end

  # remove any directories listed as temporary by config.
  (config.cleanup_dirs || []).each do |dir|
    ::FileUtils.rm_rf(dir) if ::File.directory?(dir)
  end
  true
end

#handle_request(env, verb, uri, headers, body) ⇒ TrueClass

Handler.

Parameters:

  • env (Hash)

    from rack

  • verb (String)

    as one of [‘GET’, ‘POST’, etc.]

  • uri (URI)

    parsed from full url

  • headers (Hash)

    for proxy call with any non-proxy data omitted

  • body (String)

    streamed from payload or empty

Returns:

  • (TrueClass)

    always true

Raises:

  • (::NotImplementedError)


202
203
204
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 202

def handle_request(env, verb, uri, headers, body)
  raise ::NotImplementedError, 'Must be overridden'
end