request_interceptor 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +21 -0
- data/README.md +77 -0
- data/Rakefile +6 -0
- data/bin/console +8 -0
- data/bin/setup +7 -0
- data/circle.yml +3 -0
- data/lib/request_interceptor/application.rb +8 -0
- data/lib/request_interceptor/status.rb +34 -0
- data/lib/request_interceptor/version.rb +3 -0
- data/lib/request_interceptor.rb +184 -0
- data/request_interceptor.gemspec +28 -0
- metadata +146 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0a7e5effe2f0a9cd010dab836e26e21a3fa27926
|
4
|
+
data.tar.gz: 8bfddbba2c833abc8f795a78d313d5359ce4bba6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2837e6e5bb2a5bce1d3aa80beec3048af324153244bd0fe95bc2191398a98c610e47fa38c37a637b7b1339d196e9fa7fb9ddc442fb789f55794bd845104c871e
|
7
|
+
data.tar.gz: a4bf84df00666296fc4ed166c2d9475ff6fa5052198297ed3b501088898713307f94773577931f9aa3180f298c0c34c937c164e05fa104e4dc93f7782ec06bb9
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Konstantin Tennhard
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Request Interceptor
|
2
|
+
|
3
|
+
Request interceptor is a library for simulating foreign APIs using Sinatra applcations.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'request_interceptor'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install request_interceptor
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Once installed, request interceptors can be defined as follows:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
app = RequestInterceptor.define do
|
27
|
+
get "/" do
|
28
|
+
content_type "text/plain"
|
29
|
+
"Hello World"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
By default, request interceptors are `Sinatra` applications, but any `Rack` compatible application works.
|
35
|
+
To intercept HTTP requests, the code performing the request must be wrapped in an `RequestInterceptor#run` block:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
interceptor = RequestInterceptor.new(/.*example\.com$/ => app)
|
39
|
+
interceptor.run do
|
40
|
+
Net::HTTP.get(URI("http://example.com/")) # => "Hello World"
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
`RequestInterceptor` instances are initialized with hash mapping hostname patterns to applications.
|
45
|
+
The patterns are later matched against the hostname of the URI associated with a particular request.
|
46
|
+
In case of a match, the corresponding application is used to serve the request.
|
47
|
+
Otherwise, a real HTTP request is performed.
|
48
|
+
|
49
|
+
For the sake of convenience, the code above can be shortened using `RequestInterceptor.run`:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
log = RequestInterceptor.run(/.*example\.com$/ => app) do
|
53
|
+
Net::HTTP.get(URI("http://example.com/")) # => "Hello World"
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
In both cases, the result is a transaction log.
|
58
|
+
Each entry in the transaction log is a `RequestInterceptor::Transaction`.
|
59
|
+
A transaction is simply request/response pair.
|
60
|
+
The request can be obtained using the equally named `#request` method.
|
61
|
+
The `#response` method returns the response that corresponds to the particular request.
|
62
|
+
The code above would result in a transaction log with one entry:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
log.count # => 1
|
66
|
+
log.first.request # => Rack::MockRequest
|
67
|
+
log.first.response # => Rack::MockResponse
|
68
|
+
```
|
69
|
+
|
70
|
+
## Contributing
|
71
|
+
|
72
|
+
Bug reports and pull requests are welcome on GitHub at (t6d/request_interceptor)[https://github.com/t6d/request_interceptor].
|
73
|
+
|
74
|
+
## License
|
75
|
+
|
76
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
77
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/circle.yml
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
class RequestInterceptor::Status < Struct.new(:value, :description)
|
4
|
+
STATUSES = Rack::Utils::HTTP_STATUS_CODES
|
5
|
+
|
6
|
+
def self.from_code(code, description = nil)
|
7
|
+
description = STATUSES.fetch(code.to_i, "Unknown") if description.nil?
|
8
|
+
new(code.to_s, description)
|
9
|
+
end
|
10
|
+
|
11
|
+
def response_class
|
12
|
+
self.class.response_class(value)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.response_class(code)
|
16
|
+
@response_classes ||=
|
17
|
+
begin
|
18
|
+
wrapped_classes = Hash.new { Net::HTTPUnknownResponse }
|
19
|
+
Net::HTTPResponse::CODE_TO_OBJ.inject(wrapped_classes) do |classes, (code, original_class)|
|
20
|
+
new_class = Class.new(original_class) do
|
21
|
+
attr_accessor :body
|
22
|
+
end
|
23
|
+
|
24
|
+
new_class.define_singleton_method(:name) { original_class.name }
|
25
|
+
new_class.define_singleton_method(:to_s) { original_class.name }
|
26
|
+
|
27
|
+
classes[code] = new_class
|
28
|
+
classes
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@response_classes[code]
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require "request_interceptor/version"
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
require "rack/mock"
|
5
|
+
|
6
|
+
class RequestInterceptor
|
7
|
+
class Transaction < Struct.new(:request, :reponse); end
|
8
|
+
|
9
|
+
GET = "GET".freeze
|
10
|
+
POST = "POST".freeze
|
11
|
+
PUT = "PUT".freeze
|
12
|
+
DELETE = "DELETE".freeze
|
13
|
+
|
14
|
+
def self.template=(template)
|
15
|
+
@template =
|
16
|
+
case template
|
17
|
+
when Proc
|
18
|
+
Class.new(Application, &template)
|
19
|
+
else
|
20
|
+
template
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.template
|
25
|
+
@template || Application
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.define(super_class = nil, &application_definition)
|
29
|
+
Class.new(super_class || template, &application_definition)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.run(*args, &simulation)
|
33
|
+
new(*args).run(&simulation)
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :applications
|
37
|
+
attr_reader :transactions
|
38
|
+
|
39
|
+
def initialize(applications)
|
40
|
+
@applications = {}
|
41
|
+
@transactions = []
|
42
|
+
|
43
|
+
applications.each { |pattern, application| self[pattern] = application }
|
44
|
+
end
|
45
|
+
|
46
|
+
def [](pattern)
|
47
|
+
@applications[pattern]
|
48
|
+
end
|
49
|
+
|
50
|
+
def []=(pattern, application)
|
51
|
+
@applications[pattern] = application
|
52
|
+
end
|
53
|
+
|
54
|
+
def run(&simulation)
|
55
|
+
clear_transaction_log
|
56
|
+
cache_original_net_http_methods
|
57
|
+
override_net_http_methods
|
58
|
+
simulation.call
|
59
|
+
transactions
|
60
|
+
ensure
|
61
|
+
restore_net_http_methods
|
62
|
+
end
|
63
|
+
|
64
|
+
def request(http_context, request, body, &block)
|
65
|
+
# use Net::HTTP set_body_internal to
|
66
|
+
# keep the same behaviour as Net::HTTP
|
67
|
+
request.set_body_internal(body)
|
68
|
+
response = nil
|
69
|
+
|
70
|
+
if mock_request = mock_request_for_application(http_context, request)
|
71
|
+
mock_response = dispatch_mock_request(request, mock_request)
|
72
|
+
|
73
|
+
# create response
|
74
|
+
status = RequestInterceptor::Status.from_code(mock_response.status)
|
75
|
+
response = status.response_class.new("1.1", status.value, status.description)
|
76
|
+
|
77
|
+
# copy header to response
|
78
|
+
mock_response.original_headers.each do |k, v|
|
79
|
+
response.add_field(k, v)
|
80
|
+
end
|
81
|
+
|
82
|
+
# copy uri
|
83
|
+
response.uri = request.uri
|
84
|
+
|
85
|
+
# copy body to response
|
86
|
+
response.body = mock_response.body
|
87
|
+
|
88
|
+
# replace Net::HTTP::Response#read_body
|
89
|
+
def response.read_body(_, &block)
|
90
|
+
block.call(@body) unless block.nil?
|
91
|
+
@body
|
92
|
+
end
|
93
|
+
|
94
|
+
# yield the response because Net::HTTP#request does
|
95
|
+
block.call(response) unless block.nil?
|
96
|
+
else
|
97
|
+
response = real_request(http_context, request, body, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
log_transaction(request, response)
|
101
|
+
|
102
|
+
response
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def clear_transaction_log
|
109
|
+
@transactions = []
|
110
|
+
end
|
111
|
+
|
112
|
+
def cache_original_net_http_methods
|
113
|
+
@original_request_method = Net::HTTP.instance_method(:request)
|
114
|
+
@original_start_method = Net::HTTP.instance_method(:start)
|
115
|
+
@original_finish_method = Net::HTTP.instance_method(:finish)
|
116
|
+
end
|
117
|
+
|
118
|
+
def override_net_http_methods
|
119
|
+
runner = self
|
120
|
+
|
121
|
+
Net::HTTP.class_eval do
|
122
|
+
def start
|
123
|
+
@started = true
|
124
|
+
return yield(self) if block_given?
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
def finish
|
129
|
+
@started = false
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
define_method(:request) do |request, body = nil, &block|
|
134
|
+
runner.request(self, request, body, &block)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def restore_net_http_methods(instance = nil)
|
140
|
+
if instance.nil?
|
141
|
+
Net::HTTP.send(:define_method, :request, @original_request_method)
|
142
|
+
Net::HTTP.send(:define_method, :start, @original_start_method)
|
143
|
+
Net::HTTP.send(:define_method, :finish, @original_finish_method)
|
144
|
+
else
|
145
|
+
instance.define_singleton_method(:request, @original_request_method)
|
146
|
+
instance.define_singleton_method(:start, @original_start_method)
|
147
|
+
instance.define_singleton_method(:finish, @original_finish_method)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def mock_request_for_application(http_context, request)
|
152
|
+
_, application = applications.find { |pattern, _| pattern === http_context.address }
|
153
|
+
Rack::MockRequest.new(application) if application
|
154
|
+
end
|
155
|
+
|
156
|
+
def dispatch_mock_request(request, mock_request)
|
157
|
+
rack_env = request.to_hash
|
158
|
+
|
159
|
+
case request.method
|
160
|
+
when GET
|
161
|
+
mock_request.get(request.path, rack_env)
|
162
|
+
when POST
|
163
|
+
mock_request.post(request.path, rack_env.merge(input: request.body))
|
164
|
+
when PUT
|
165
|
+
mock_request.put(request.path, rack_env.merge(input: request.body))
|
166
|
+
when DELETE
|
167
|
+
mock_request.delete(request.path, rack_env)
|
168
|
+
else
|
169
|
+
raise NotImplementedError, "Simulating #{request.method} is not supported"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def real_request(http_context, request, body, &block)
|
174
|
+
restore_net_http_methods(http_context)
|
175
|
+
http_context.request(request, body, &block)
|
176
|
+
end
|
177
|
+
|
178
|
+
def log_transaction(request, response)
|
179
|
+
transactions << RequestInterceptor::Transaction.new(request, response)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
require "request_interceptor/application"
|
184
|
+
require "request_interceptor/status"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'request_interceptor/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "request_interceptor"
|
8
|
+
spec.version = RequestInterceptor::VERSION
|
9
|
+
spec.authors = ["Konstantin Tennhard", "Kevin Hughes"]
|
10
|
+
spec.email = ["me@t6d.de", "kevinhughes27@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Sinatra based foreign API simulation}
|
13
|
+
spec.homepage = "http://github.com/t6d/request_interceptor"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "sinatra"
|
22
|
+
spec.add_runtime_dependency "rack"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
spec.add_development_dependency "pry"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: request_interceptor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Konstantin Tennhard
|
8
|
+
- Kevin Hughes
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-11-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sinatra
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rack
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: bundler
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.9'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.9'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rake
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '10.0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '10.0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rspec
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: pry
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
description:
|
99
|
+
email:
|
100
|
+
- me@t6d.de
|
101
|
+
- kevinhughes27@gmail.com
|
102
|
+
executables: []
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".gitignore"
|
107
|
+
- ".rspec"
|
108
|
+
- ".travis.yml"
|
109
|
+
- Gemfile
|
110
|
+
- LICENSE.txt
|
111
|
+
- README.md
|
112
|
+
- Rakefile
|
113
|
+
- bin/console
|
114
|
+
- bin/setup
|
115
|
+
- circle.yml
|
116
|
+
- lib/request_interceptor.rb
|
117
|
+
- lib/request_interceptor/application.rb
|
118
|
+
- lib/request_interceptor/status.rb
|
119
|
+
- lib/request_interceptor/version.rb
|
120
|
+
- request_interceptor.gemspec
|
121
|
+
homepage: http://github.com/t6d/request_interceptor
|
122
|
+
licenses:
|
123
|
+
- MIT
|
124
|
+
metadata: {}
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options: []
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 2.4.5
|
142
|
+
signing_key:
|
143
|
+
specification_version: 4
|
144
|
+
summary: Sinatra based foreign API simulation
|
145
|
+
test_files: []
|
146
|
+
has_rdoc:
|