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

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

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.



57
58
59
60
61
62
63
64
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 57

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.



48
49
50
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 48

def config
  @config
end

#loggerObject (readonly)

Returns the value of attribute logger.



48
49
50
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 48

def logger
  @logger
end

#state_file_pathObject (readonly)

Returns the value of attribute state_file_path.



48
49
50
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 48

def state_file_path
  @state_file_path
end

Instance Method Details

#call(env) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 66

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.
  entrapment unless Base.trapped?

  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 ::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)
end

#cleanupObject

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



175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 175

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)


170
171
172
# File 'lib/right_develop/testing/servers/might_api/app/base.rb', line 170

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