api_valve 0.0.1.alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +14 -0
- data/lib/api_valve/benchmarking.rb +11 -0
- data/lib/api_valve/error.rb +13 -0
- data/lib/api_valve/error_responder.rb +44 -0
- data/lib/api_valve/forwarder/request.rb +75 -0
- data/lib/api_valve/forwarder/response.rb +36 -0
- data/lib/api_valve/forwarder.rb +75 -0
- data/lib/api_valve/middleware/error_handling.rb +15 -0
- data/lib/api_valve/proxy.rb +68 -0
- data/lib/api_valve/router.rb +74 -0
- data/lib/api_valve.rb +38 -0
- metadata +215 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e6b393476b3a17c2f66a154ed51be1d1ffe1bd8425ba4bcd3758336da16a169a
|
4
|
+
data.tar.gz: b320775933274fd57cd9fc1792f0f0c9b881d27b3ceb87df16328adadcf1d6cf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2c687fb655c0eed88ff7e197c7ea02346e92a113fc1c01940195b3df15d962f2c3224ebe6de122cb42db9c07d911f055510604013f5547d8a0637f5113407cdf
|
7
|
+
data.tar.gz: 18409ae39eabc1d45aaf34e7cd8e8328d339a0c1123c823408dedc414096eaa0c4dc4aa3272706672b107308bef08e1f52a37d3d707f3fc9ac1cc1f0d6bdb038
|
data/README.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# ApiValve
|
2
|
+
|
3
|
+
Lightweight API reverse proxy written in ruby. Based on rack.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Just add the gem
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'api_valve'
|
11
|
+
```
|
12
|
+
|
13
|
+
See the [examples](https://github.com/mkon/api_valve/tree/master/examples) section on how to create & configure your own
|
14
|
+
proxy using this gem.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ApiValve
|
2
|
+
module Error
|
3
|
+
class Base < RuntimeError
|
4
|
+
class_attribute :http_status
|
5
|
+
self.http_status = :server_error
|
6
|
+
end
|
7
|
+
|
8
|
+
Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |sym, code|
|
9
|
+
next unless code >= 400
|
10
|
+
const_set sym.to_s.camelize, Class.new(Base) { self.http_status = sym }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ApiValve
|
2
|
+
class ErrorResponder
|
3
|
+
def initialize(error)
|
4
|
+
@error = error
|
5
|
+
end
|
6
|
+
|
7
|
+
def call
|
8
|
+
[
|
9
|
+
status,
|
10
|
+
{'Content-Type' => 'application/json'},
|
11
|
+
[MultiJson.dump({errors: [json_error]}, mode: :compat)]
|
12
|
+
]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def status
|
18
|
+
Rack::Utils::SYMBOL_TO_STATUS_CODE[@error.http_status] || 500
|
19
|
+
end
|
20
|
+
|
21
|
+
def json_error
|
22
|
+
{
|
23
|
+
status: status,
|
24
|
+
code: json_code,
|
25
|
+
detail: json_detail,
|
26
|
+
meta: json_meta
|
27
|
+
}.compact
|
28
|
+
end
|
29
|
+
|
30
|
+
def json_code
|
31
|
+
@error.try(:code) || Rack::Utils::HTTP_STATUS_CODES[status]
|
32
|
+
end
|
33
|
+
|
34
|
+
def json_detail
|
35
|
+
@error.message != @error.class.to_s ? @error.message : nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def json_meta
|
39
|
+
(@error.try(:to_hash).presence || {}).merge(
|
40
|
+
backtrace: ApiValve.expose_backtraces ? json_backtrace : nil
|
41
|
+
).compact.presence
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ApiValve
|
2
|
+
# Wraps original request
|
3
|
+
# Responsible for altering the request before it is forwarded
|
4
|
+
class Forwarder::Request
|
5
|
+
attr_reader :original_request, :options
|
6
|
+
|
7
|
+
WHITELISTED_HEADERS = %w(
|
8
|
+
Accept
|
9
|
+
Content-Type
|
10
|
+
Forwarded
|
11
|
+
User-Agent
|
12
|
+
X-Forwarded-For
|
13
|
+
X-Forwarded-Host
|
14
|
+
X-Forwarded-Port
|
15
|
+
X-Forwarded-Proto
|
16
|
+
).freeze
|
17
|
+
NOT_PREFIXED_HEADERS = %w(
|
18
|
+
Content-Length
|
19
|
+
Content-Type
|
20
|
+
).freeze
|
21
|
+
|
22
|
+
def initialize(original_request, options = {})
|
23
|
+
@original_request = original_request
|
24
|
+
@options = default_options.merge options
|
25
|
+
end
|
26
|
+
|
27
|
+
def method
|
28
|
+
@method ||= original_request.request_method.downcase.to_sym
|
29
|
+
end
|
30
|
+
|
31
|
+
def path
|
32
|
+
path = options['endpoint'] || ''
|
33
|
+
if pattern = options['path']
|
34
|
+
path += pattern % options.dig('match_data').named_captures.symbolize_keys
|
35
|
+
else
|
36
|
+
path += original_request.path_info
|
37
|
+
end
|
38
|
+
path.gsub(%r{^/}, '')
|
39
|
+
end
|
40
|
+
|
41
|
+
def headers
|
42
|
+
whitelisted_headers.each_with_object({}) do |key, h|
|
43
|
+
h[key] = header(key)
|
44
|
+
end.merge('X-Request-Id' => Thread.current[:request_id]).compact
|
45
|
+
end
|
46
|
+
|
47
|
+
def header(name)
|
48
|
+
name = "HTTP_#{name}" unless NOT_PREFIXED_HEADERS.include? name
|
49
|
+
name = name.upcase.tr('-', '_')
|
50
|
+
original_request.get_header(name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def body
|
54
|
+
return unless %i(put post patch).include? method
|
55
|
+
original_request.body.read
|
56
|
+
end
|
57
|
+
|
58
|
+
def url_params
|
59
|
+
return unless original_request.query_string.present?
|
60
|
+
@url_params ||= Rack::Utils.parse_nested_query(original_request.query_string)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def whitelisted_headers
|
66
|
+
@options[:whitelisted_headers]
|
67
|
+
end
|
68
|
+
|
69
|
+
def default_options
|
70
|
+
{
|
71
|
+
whitelisted_headers: WHITELISTED_HEADERS
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ApiValve
|
2
|
+
# Wraps faraday response
|
3
|
+
# Responsible for altering the response before it is returned
|
4
|
+
class Forwarder::Response
|
5
|
+
attr_reader :original_response
|
6
|
+
|
7
|
+
WHITELISTED_HEADERS = %w(
|
8
|
+
Content-Type
|
9
|
+
Cache-Control
|
10
|
+
).freeze
|
11
|
+
|
12
|
+
def initialize(original_response)
|
13
|
+
@original_response = original_response
|
14
|
+
end
|
15
|
+
|
16
|
+
def rack_response
|
17
|
+
[status, headers, [body]]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def status
|
23
|
+
original_response.status
|
24
|
+
end
|
25
|
+
|
26
|
+
def headers
|
27
|
+
WHITELISTED_HEADERS.each_with_object({}) do |k, h|
|
28
|
+
h[k] = original_response.headers[k]
|
29
|
+
end.compact
|
30
|
+
end
|
31
|
+
|
32
|
+
def body
|
33
|
+
original_response.body.to_s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ApiValve
|
2
|
+
class Forwarder
|
3
|
+
autoload :Request, 'api_valve/forwarder/request'
|
4
|
+
autoload :Response, 'api_valve/forwarder/response'
|
5
|
+
|
6
|
+
include Benchmarking
|
7
|
+
|
8
|
+
DEFAULT_OPTIONS = {
|
9
|
+
response_klass: Response,
|
10
|
+
request_klass: Request
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
attr_accessor :response_klass, :request_klass
|
14
|
+
attr_reader :endpoint
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
DEFAULT_OPTIONS.merge(options).each do |k, v|
|
18
|
+
public_send("#{k}=", v)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(original_request, request_options = {})
|
23
|
+
request = request_klass.new(original_request, request_options)
|
24
|
+
response_klass.new(run_request(request)).rack_response
|
25
|
+
end
|
26
|
+
|
27
|
+
# Enforce trailing slash
|
28
|
+
def endpoint=(endpoint)
|
29
|
+
@endpoint = File.join(endpoint, '')
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def run_request(request)
|
35
|
+
log_request request
|
36
|
+
response, elapsed_time = benchmark do
|
37
|
+
faraday.run_request(request.method, request.path, request.body, request.headers) do |req|
|
38
|
+
req.params.update(request.url_params) if request.url_params
|
39
|
+
end
|
40
|
+
end
|
41
|
+
log_response response, elapsed_time
|
42
|
+
response
|
43
|
+
end
|
44
|
+
|
45
|
+
def log_request(request)
|
46
|
+
ApiValve.logger.info do
|
47
|
+
format(
|
48
|
+
'-> %<method>s %<endpoint>s%<path>s',
|
49
|
+
method: request.method.upcase,
|
50
|
+
endpoint: endpoint,
|
51
|
+
path: request.path
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def log_response(response, elapsed_time)
|
57
|
+
ApiValve.logger.info do
|
58
|
+
format(
|
59
|
+
'<- %<status>s in %<ms>dms',
|
60
|
+
status: response.status,
|
61
|
+
ms: elapsed_time * 1000
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def faraday
|
67
|
+
@faraday ||= Faraday.new(
|
68
|
+
url: endpoint,
|
69
|
+
ssl: {verify: false}
|
70
|
+
) do |config|
|
71
|
+
config.adapter Faraday.default_adapter
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ApiValve
|
2
|
+
module Middleware
|
3
|
+
class ErrorHandling
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
@app.call(env)
|
10
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
11
|
+
ErrorResponder.new(e).call
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'byebug'
|
2
|
+
|
3
|
+
module ApiValve
|
4
|
+
class Proxy
|
5
|
+
include ActiveSupport::Rescuable
|
6
|
+
|
7
|
+
FORWARDER_OPTIONS = %w(endpoint).freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def from_yaml(file_path)
|
11
|
+
from_hash YAML.load_file(file_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def from_hash(config)
|
15
|
+
config = config.with_indifferent_access
|
16
|
+
forwarder = Forwarder.new(config.slice(*FORWARDER_OPTIONS))
|
17
|
+
new(forwarder).tap { |proxy| proxy.build_routes_from_config config }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :forwarder, :router
|
22
|
+
|
23
|
+
def initialize(forwarder)
|
24
|
+
@forwarder = forwarder
|
25
|
+
@router = Router.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(*args)
|
29
|
+
@router.call(*args)
|
30
|
+
rescue ApiValve::Error::Base => e
|
31
|
+
ErrorResponder.new(e).call
|
32
|
+
end
|
33
|
+
|
34
|
+
delegate :add_route, to: :router
|
35
|
+
|
36
|
+
def build_routes_from_config(config)
|
37
|
+
config['routes']&.each do |route_config|
|
38
|
+
method, path_regexp, req_conf = *route_config.values_at('method', 'path', 'request')
|
39
|
+
if route_config['raise']
|
40
|
+
deny method, path_regexp, with: route_config['raise']
|
41
|
+
else
|
42
|
+
forward method, path_regexp, req_conf
|
43
|
+
end
|
44
|
+
end
|
45
|
+
forward_all
|
46
|
+
end
|
47
|
+
|
48
|
+
def forward(methods, path_regexp = nil, config = {})
|
49
|
+
Array.wrap(methods).each do |method|
|
50
|
+
router.public_send(method, path_regexp, proc { |request, match_data|
|
51
|
+
forwarder.call request, {'match_data' => match_data}.merge(config || {})
|
52
|
+
})
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def forward_all
|
57
|
+
router.any do |request, match_data|
|
58
|
+
forwarder.call request, 'match_data' => match_data
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def deny(methods, path_regexp = nil, with: 'Error::Forbidden')
|
63
|
+
Array.wrap(methods).each do |method|
|
64
|
+
router.public_send(method, path_regexp, ->(*_args) { raise ApiValve.const_get(with) })
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module ApiValve
|
2
|
+
class Router
|
3
|
+
METHODS = %i(get post put patch delete head).freeze
|
4
|
+
|
5
|
+
Route = Struct.new(:regexp, :block) do
|
6
|
+
delegate :call, to: :block
|
7
|
+
|
8
|
+
def match(path_info)
|
9
|
+
return {} if regexp.nil? # return empty 'match data' on catch all
|
10
|
+
regexp.match(path_info)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
reset_routes
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
match Rack::Request.new(env)
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete(path = nil, callee = nil)
|
23
|
+
add_route :delete, path, callee || Proc.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(path = nil, callee = nil)
|
27
|
+
add_route :get, path, callee || Proc.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def head(path = nil, callee = nil)
|
31
|
+
add_route :head, path, callee || Proc.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def patch(path = nil, callee = nil)
|
35
|
+
add_route :patch, path, callee || Proc.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def post(path = nil, callee = nil)
|
39
|
+
add_route :post, path, callee || Proc.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def put(path = nil, callee = nil)
|
43
|
+
add_route :put, path, callee || Proc.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def any(path = nil, callee = nil)
|
47
|
+
add_route METHODS, path, callee || Proc.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def reset_routes
|
51
|
+
@routes = Hash[METHODS.map { |v| [v, []] }].freeze
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def add_route(methods, regexp, callee)
|
57
|
+
Array.wrap(methods).each do |method|
|
58
|
+
@routes[method] << Route.new(regexp, callee)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def match(request)
|
63
|
+
# For security reasons do not allow URLs that could break out of the proxy namespace on the
|
64
|
+
# server. Preferably an nxing/apache rewrite will kill these URLs before they hit us
|
65
|
+
raise 'URL not supported' if request.path_info.include?('/../')
|
66
|
+
@routes && @routes[request.request_method.downcase.to_sym].each do |route|
|
67
|
+
if match_data = route.match(request.path_info)
|
68
|
+
return route.call request, match_data
|
69
|
+
end
|
70
|
+
end
|
71
|
+
raise Error::NotFound, 'Endpoint not found'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/api_valve.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'active_support/configurable'
|
2
|
+
require 'active_support/core_ext/class'
|
3
|
+
require 'active_support/core_ext/hash'
|
4
|
+
require 'active_support/core_ext/module'
|
5
|
+
require 'active_support/rescuable'
|
6
|
+
require 'benchmark'
|
7
|
+
require 'faraday'
|
8
|
+
require 'multi_json'
|
9
|
+
require 'logger'
|
10
|
+
|
11
|
+
module ApiValve
|
12
|
+
autoload :Benchmarking, 'api_valve/benchmarking'
|
13
|
+
autoload :Error, 'api_valve/error'
|
14
|
+
autoload :ErrorResponder, 'api_valve/error_responder'
|
15
|
+
autoload :Forwarder, 'api_valve/forwarder'
|
16
|
+
autoload :Proxy, 'api_valve/proxy'
|
17
|
+
autoload :Router, 'api_valve/router'
|
18
|
+
|
19
|
+
include ActiveSupport::Configurable
|
20
|
+
|
21
|
+
module Middleware
|
22
|
+
autoload :ErrorHandling, 'api_valve/middleware/error_handling'
|
23
|
+
end
|
24
|
+
|
25
|
+
config_accessor :logger do
|
26
|
+
Logger.new(STDOUT)
|
27
|
+
end
|
28
|
+
|
29
|
+
config_accessor :expose_backtraces do
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
# :nocov:
|
34
|
+
def self.configure
|
35
|
+
yield config
|
36
|
+
end
|
37
|
+
# :nocov:
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: api_valve
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.alpha
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- mkon
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.0.2
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '6'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 5.0.2
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '6'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: faraday
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.14'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0.14'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: multi_json
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rack
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2'
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rack-test
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rspec
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: rubocop
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - '='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 0.54.0
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - '='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 0.54.0
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: rubocop-rspec
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - '='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 1.22.2
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - '='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 1.22.2
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: simplecov
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: timecop
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
- !ruby/object:Gem::Dependency
|
160
|
+
name: webmock
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - "~>"
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '2'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - "~>"
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '2'
|
173
|
+
description:
|
174
|
+
email:
|
175
|
+
- konstantin@munteanu.de
|
176
|
+
executables: []
|
177
|
+
extensions: []
|
178
|
+
extra_rdoc_files: []
|
179
|
+
files:
|
180
|
+
- README.md
|
181
|
+
- lib/api_valve.rb
|
182
|
+
- lib/api_valve/benchmarking.rb
|
183
|
+
- lib/api_valve/error.rb
|
184
|
+
- lib/api_valve/error_responder.rb
|
185
|
+
- lib/api_valve/forwarder.rb
|
186
|
+
- lib/api_valve/forwarder/request.rb
|
187
|
+
- lib/api_valve/forwarder/response.rb
|
188
|
+
- lib/api_valve/middleware/error_handling.rb
|
189
|
+
- lib/api_valve/proxy.rb
|
190
|
+
- lib/api_valve/router.rb
|
191
|
+
homepage: https://github.com/mkon/api_valve
|
192
|
+
licenses:
|
193
|
+
- MIT
|
194
|
+
metadata: {}
|
195
|
+
post_install_message:
|
196
|
+
rdoc_options: []
|
197
|
+
require_paths:
|
198
|
+
- lib
|
199
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
200
|
+
requirements:
|
201
|
+
- - ">="
|
202
|
+
- !ruby/object:Gem::Version
|
203
|
+
version: '0'
|
204
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: 1.3.1
|
209
|
+
requirements: []
|
210
|
+
rubyforge_project:
|
211
|
+
rubygems_version: 2.7.6
|
212
|
+
signing_key:
|
213
|
+
specification_version: 4
|
214
|
+
summary: Lightweight ruby/rack API reverse proxy or gateway
|
215
|
+
test_files: []
|